xref: /AOO41X/main/apple_remote/MultiClickRemoteBehavior.m (revision cdf0e10c4e3984b49a9502b011690b615761d4a3)
1*cdf0e10cSrcweir/*****************************************************************************
2*cdf0e10cSrcweir * MultiClickRemoteBehavior.m
3*cdf0e10cSrcweir * RemoteControlWrapper
4*cdf0e10cSrcweir *
5*cdf0e10cSrcweir * Created by Martin Kahr on 11.03.06 under a MIT-style license.
6*cdf0e10cSrcweir * Copyright (c) 2006 martinkahr.com. All rights reserved.
7*cdf0e10cSrcweir *
8*cdf0e10cSrcweir * Code modified and adapted to OpenOffice.org
9*cdf0e10cSrcweir * by Eric Bachard on 11.08.2008 under the same License
10*cdf0e10cSrcweir *
11*cdf0e10cSrcweir * Permission is hereby granted, free of charge, to any person obtaining a
12*cdf0e10cSrcweir * copy of this software and associated documentation files (the "Software"),
13*cdf0e10cSrcweir * to deal in the Software without restriction, including without limitation
14*cdf0e10cSrcweir * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15*cdf0e10cSrcweir * and/or sell copies of the Software, and to permit persons to whom the
16*cdf0e10cSrcweir * Software is furnished to do so, subject to the following conditions:
17*cdf0e10cSrcweir *
18*cdf0e10cSrcweir * The above copyright notice and this permission notice shall be included
19*cdf0e10cSrcweir * in all copies or substantial portions of the Software.
20*cdf0e10cSrcweir *
21*cdf0e10cSrcweir * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22*cdf0e10cSrcweir * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23*cdf0e10cSrcweir * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24*cdf0e10cSrcweir * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25*cdf0e10cSrcweir * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26*cdf0e10cSrcweir * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27*cdf0e10cSrcweir * THE SOFTWARE.
28*cdf0e10cSrcweir *
29*cdf0e10cSrcweir *****************************************************************************/
30*cdf0e10cSrcweir
31*cdf0e10cSrcweir#import "MultiClickRemoteBehavior.h"
32*cdf0e10cSrcweir
33*cdf0e10cSrcweirconst NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE = 0.35;
34*cdf0e10cSrcweirconst NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL = 0.4;
35*cdf0e10cSrcweir
36*cdf0e10cSrcweir@implementation MultiClickRemoteBehavior
37*cdf0e10cSrcweir
38*cdf0e10cSrcweir- (id) init {
39*cdf0e10cSrcweir	if ( (self = [super init]) ) {
40*cdf0e10cSrcweir		maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE;
41*cdf0e10cSrcweir	}
42*cdf0e10cSrcweir	return self;
43*cdf0e10cSrcweir}
44*cdf0e10cSrcweir
45*cdf0e10cSrcweir// Delegates are not retained!
46*cdf0e10cSrcweir// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html
47*cdf0e10cSrcweir// Delegating objects do not (and should not) retain their delegates.
48*cdf0e10cSrcweir// However, clients of delegating objects (applications, usually) are responsible for ensuring that their delegates are around
49*cdf0e10cSrcweir// to receive delegation messages. To do this, they may have to retain the delegate.
50*cdf0e10cSrcweir- (void) setDelegate: (id) _delegate {
51*cdf0e10cSrcweir	if ( _delegate && ( [_delegate respondsToSelector:@selector(remoteButton:pressedDown:clickCount:)] == NO )) return; // return what ?
52*cdf0e10cSrcweir
53*cdf0e10cSrcweir	delegate = _delegate;
54*cdf0e10cSrcweir}
55*cdf0e10cSrcweir- (id) delegate {
56*cdf0e10cSrcweir	return delegate;
57*cdf0e10cSrcweir}
58*cdf0e10cSrcweir
59*cdf0e10cSrcweir- (BOOL) simulateHoldEvent {
60*cdf0e10cSrcweir	return simulateHoldEvents;
61*cdf0e10cSrcweir}
62*cdf0e10cSrcweir- (void) setSimulateHoldEvent: (BOOL) value {
63*cdf0e10cSrcweir	simulateHoldEvents = value;
64*cdf0e10cSrcweir}
65*cdf0e10cSrcweir
66*cdf0e10cSrcweir- (BOOL) simulatesHoldForButtonIdentifier: (RemoteControlEventIdentifier) identifier remoteControl: (RemoteControl*) remoteControl {
67*cdf0e10cSrcweir	// we do that check only for the normal button identifiers as we would check for hold support for hold events instead
68*cdf0e10cSrcweir	if (identifier > (1 << EVENT_TO_HOLD_EVENT_OFFSET)) return NO;
69*cdf0e10cSrcweir
70*cdf0e10cSrcweir	return [self simulateHoldEvent] && [remoteControl sendsEventForButtonIdentifier: (identifier << EVENT_TO_HOLD_EVENT_OFFSET)]==NO;
71*cdf0e10cSrcweir}
72*cdf0e10cSrcweir
73*cdf0e10cSrcweir- (BOOL) clickCountingEnabled {
74*cdf0e10cSrcweir	return clickCountEnabledButtons != 0;
75*cdf0e10cSrcweir}
76*cdf0e10cSrcweir- (void) setClickCountingEnabled: (BOOL) value {
77*cdf0e10cSrcweir	if (value) {
78*cdf0e10cSrcweir		[self setClickCountEnabledButtons: kRemoteButtonPlus | kRemoteButtonMinus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu];
79*cdf0e10cSrcweir	} else {
80*cdf0e10cSrcweir		[self setClickCountEnabledButtons: 0];
81*cdf0e10cSrcweir	}
82*cdf0e10cSrcweir}
83*cdf0e10cSrcweir
84*cdf0e10cSrcweir- (unsigned int) clickCountEnabledButtons {
85*cdf0e10cSrcweir	return clickCountEnabledButtons;
86*cdf0e10cSrcweir}
87*cdf0e10cSrcweir- (void) setClickCountEnabledButtons: (unsigned int)value {
88*cdf0e10cSrcweir	clickCountEnabledButtons = value;
89*cdf0e10cSrcweir}
90*cdf0e10cSrcweir
91*cdf0e10cSrcweir- (NSTimeInterval) maximumClickCountTimeDifference {
92*cdf0e10cSrcweir	return maxClickTimeDifference;
93*cdf0e10cSrcweir}
94*cdf0e10cSrcweir- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff {
95*cdf0e10cSrcweir	maxClickTimeDifference = timeDiff;
96*cdf0e10cSrcweir}
97*cdf0e10cSrcweir
98*cdf0e10cSrcweir- (void) sendSimulatedHoldEvent: (id) time {
99*cdf0e10cSrcweir	BOOL startSimulateHold = NO;
100*cdf0e10cSrcweir	RemoteControlEventIdentifier event = lastHoldEvent;
101*cdf0e10cSrcweir	@synchronized(self) {
102*cdf0e10cSrcweir		startSimulateHold = (lastHoldEvent>0 && lastHoldEventTime == [time doubleValue]);
103*cdf0e10cSrcweir	}
104*cdf0e10cSrcweir	if (startSimulateHold) {
105*cdf0e10cSrcweir		lastEventSimulatedHold = YES;
106*cdf0e10cSrcweir		event = (event << EVENT_TO_HOLD_EVENT_OFFSET);
107*cdf0e10cSrcweir		[delegate remoteButton:event pressedDown: YES clickCount: 1];
108*cdf0e10cSrcweir	}
109*cdf0e10cSrcweir}
110*cdf0e10cSrcweir
111*cdf0e10cSrcweir- (void) executeClickCountEvent: (NSArray*) values {
112*cdf0e10cSrcweir	RemoteControlEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue];
113*cdf0e10cSrcweir	NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue];
114*cdf0e10cSrcweir
115*cdf0e10cSrcweir	BOOL finishedClicking = NO;
116*cdf0e10cSrcweir	int finalClickCount = eventClickCount;
117*cdf0e10cSrcweir
118*cdf0e10cSrcweir	@synchronized(self) {
119*cdf0e10cSrcweir		finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime);
120*cdf0e10cSrcweir		if (finishedClicking) {
121*cdf0e10cSrcweir			eventClickCount = 0;
122*cdf0e10cSrcweir			lastClickCountEvent = 0;
123*cdf0e10cSrcweir			lastClickCountEventTime = 0;
124*cdf0e10cSrcweir		}
125*cdf0e10cSrcweir	}
126*cdf0e10cSrcweir
127*cdf0e10cSrcweir	if (finishedClicking) {
128*cdf0e10cSrcweir		[delegate remoteButton:event pressedDown: YES clickCount:finalClickCount];
129*cdf0e10cSrcweir		// trigger a button release event, too
130*cdf0e10cSrcweir		[NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]];
131*cdf0e10cSrcweir		[delegate remoteButton:event pressedDown: NO clickCount:finalClickCount];
132*cdf0e10cSrcweir	}
133*cdf0e10cSrcweir}
134*cdf0e10cSrcweir
135*cdf0e10cSrcweir- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl {
136*cdf0e10cSrcweir	if (!delegate)  return;
137*cdf0e10cSrcweir
138*cdf0e10cSrcweir	BOOL clickCountingForEvent = ([self clickCountEnabledButtons] & event) == event;
139*cdf0e10cSrcweir
140*cdf0e10cSrcweir	if ([self simulatesHoldForButtonIdentifier: event remoteControl: remoteControl] && lastClickCountEvent==0) {
141*cdf0e10cSrcweir		if (pressedDown) {
142*cdf0e10cSrcweir			// wait to see if it is a hold
143*cdf0e10cSrcweir			lastHoldEvent = event;
144*cdf0e10cSrcweir			lastHoldEventTime = [NSDate timeIntervalSinceReferenceDate];
145*cdf0e10cSrcweir			[self performSelector:@selector(sendSimulatedHoldEvent:)
146*cdf0e10cSrcweir					   withObject:[NSNumber numberWithDouble:lastHoldEventTime]
147*cdf0e10cSrcweir					   afterDelay:HOLD_RECOGNITION_TIME_INTERVAL];
148*cdf0e10cSrcweir			return;
149*cdf0e10cSrcweir		} else {
150*cdf0e10cSrcweir			if (lastEventSimulatedHold) {
151*cdf0e10cSrcweir				// it was a hold
152*cdf0e10cSrcweir				// send an event for "hold release"
153*cdf0e10cSrcweir				event = (event << EVENT_TO_HOLD_EVENT_OFFSET);
154*cdf0e10cSrcweir				lastHoldEvent = 0;
155*cdf0e10cSrcweir				lastEventSimulatedHold = NO;
156*cdf0e10cSrcweir
157*cdf0e10cSrcweir				[delegate remoteButton:event pressedDown: pressedDown clickCount:1];
158*cdf0e10cSrcweir				return;
159*cdf0e10cSrcweir			} else {
160*cdf0e10cSrcweir				RemoteControlEventIdentifier previousEvent = lastHoldEvent;
161*cdf0e10cSrcweir				@synchronized(self) {
162*cdf0e10cSrcweir					lastHoldEvent = 0;
163*cdf0e10cSrcweir				}
164*cdf0e10cSrcweir
165*cdf0e10cSrcweir				// in case click counting is enabled we have to setup the state for that, too
166*cdf0e10cSrcweir				if (clickCountingForEvent) {
167*cdf0e10cSrcweir					lastClickCountEvent = previousEvent;
168*cdf0e10cSrcweir					lastClickCountEventTime = lastHoldEventTime;
169*cdf0e10cSrcweir					NSNumber* eventNumber;
170*cdf0e10cSrcweir					NSNumber* timeNumber;
171*cdf0e10cSrcweir					eventClickCount = 1;
172*cdf0e10cSrcweir					timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime];
173*cdf0e10cSrcweir					eventNumber= [NSNumber numberWithUnsignedInt:previousEvent];
174*cdf0e10cSrcweir					NSTimeInterval diffTime = maxClickTimeDifference-([NSDate timeIntervalSinceReferenceDate]-lastHoldEventTime);
175*cdf0e10cSrcweir					[self performSelector: @selector(executeClickCountEvent:)
176*cdf0e10cSrcweir							   withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil]
177*cdf0e10cSrcweir							   afterDelay: diffTime];
178*cdf0e10cSrcweir					// we do not return here because we are still in the press-release event
179*cdf0e10cSrcweir					// that will be consumed below
180*cdf0e10cSrcweir				} else {
181*cdf0e10cSrcweir					// trigger the pressed down event that we consumed first
182*cdf0e10cSrcweir					[delegate remoteButton:event pressedDown: YES clickCount:1];
183*cdf0e10cSrcweir				}
184*cdf0e10cSrcweir			}
185*cdf0e10cSrcweir		}
186*cdf0e10cSrcweir	}
187*cdf0e10cSrcweir
188*cdf0e10cSrcweir	if (clickCountingForEvent) {
189*cdf0e10cSrcweir		if (pressedDown == NO) return;
190*cdf0e10cSrcweir
191*cdf0e10cSrcweir		NSNumber* eventNumber;
192*cdf0e10cSrcweir		NSNumber* timeNumber;
193*cdf0e10cSrcweir		@synchronized(self) {
194*cdf0e10cSrcweir			lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate];
195*cdf0e10cSrcweir			if (lastClickCountEvent == event) {
196*cdf0e10cSrcweir				eventClickCount = eventClickCount + 1;
197*cdf0e10cSrcweir			} else {
198*cdf0e10cSrcweir				eventClickCount = 1;
199*cdf0e10cSrcweir			}
200*cdf0e10cSrcweir			lastClickCountEvent = event;
201*cdf0e10cSrcweir			timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime];
202*cdf0e10cSrcweir			eventNumber= [NSNumber numberWithUnsignedInt:event];
203*cdf0e10cSrcweir		}
204*cdf0e10cSrcweir		[self performSelector: @selector(executeClickCountEvent:)
205*cdf0e10cSrcweir				   withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil]
206*cdf0e10cSrcweir				   afterDelay: maxClickTimeDifference];
207*cdf0e10cSrcweir	} else {
208*cdf0e10cSrcweir		[delegate remoteButton:event pressedDown: pressedDown clickCount:1];
209*cdf0e10cSrcweir	}
210*cdf0e10cSrcweir
211*cdf0e10cSrcweir}
212*cdf0e10cSrcweir
213*cdf0e10cSrcweir@end
214