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