1/***************************************************************************** 2 * MultiClickRemoteBehavior.m 3 * RemoteControlWrapper 4 * 5 * Created by Martin Kahr on 11.03.06 under a MIT-style license. 6 * Copyright (c) 2006 martinkahr.com. All rights reserved. 7 * 8 * Code modified and adapted to OpenOffice.org 9 * by Eric Bachard on 11.08.2008 under the same License 10 * 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: 17 * 18 * The above copyright notice and this permission notice shall be included 19 * in all copies or substantial portions of the Software. 20 * 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 27 * THE SOFTWARE. 28 * 29 *****************************************************************************/ 30 31#import "MultiClickRemoteBehavior.h" 32 33const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE = 0.35; 34const NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL = 0.4; 35 36@implementation MultiClickRemoteBehavior 37 38- (id) init { 39 if ( (self = [super init]) ) { 40 maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE; 41 } 42 return self; 43} 44 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 ? 52 53 delegate = _delegate; 54} 55- (id) delegate { 56 return delegate; 57} 58 59- (BOOL) simulateHoldEvent { 60 return simulateHoldEvents; 61} 62- (void) setSimulateHoldEvent: (BOOL) value { 63 simulateHoldEvents = value; 64} 65 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; 69 70 return [self simulateHoldEvent] && [remoteControl sendsEventForButtonIdentifier: (identifier << EVENT_TO_HOLD_EVENT_OFFSET)]==NO; 71} 72 73- (BOOL) clickCountingEnabled { 74 return clickCountEnabledButtons != 0; 75} 76- (void) setClickCountingEnabled: (BOOL) value { 77 if (value) { 78 [self setClickCountEnabledButtons: kRemoteButtonPlus | kRemoteButtonMinus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu | kMetallicRemote2009ButtonPlay | kMetallicRemote2009ButtonMiddlePlay]; 79 } else { 80 [self setClickCountEnabledButtons: 0]; 81 } 82} 83 84- (unsigned int) clickCountEnabledButtons { 85 return clickCountEnabledButtons; 86} 87- (void) setClickCountEnabledButtons: (unsigned int)value { 88 clickCountEnabledButtons = value; 89} 90 91- (NSTimeInterval) maximumClickCountTimeDifference { 92 return maxClickTimeDifference; 93} 94- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff { 95 maxClickTimeDifference = timeDiff; 96} 97 98- (void) sendSimulatedHoldEvent: (id) time { 99 BOOL startSimulateHold = NO; 100 RemoteControlEventIdentifier event = lastHoldEvent; 101 @synchronized(self) { 102 startSimulateHold = (lastHoldEvent>0 && lastHoldEventTime == [time doubleValue]); 103 } 104 if (startSimulateHold) { 105 lastEventSimulatedHold = YES; 106 event = (event << EVENT_TO_HOLD_EVENT_OFFSET); 107 [delegate remoteButton:event pressedDown: YES clickCount: 1]; 108 } 109} 110 111- (void) executeClickCountEvent: (NSArray*) values { 112 RemoteControlEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue]; 113 NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue]; 114 115 BOOL finishedClicking = NO; 116 int finalClickCount = eventClickCount; 117 118 @synchronized(self) { 119 finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime); 120 if (finishedClicking) { 121 eventClickCount = 0; 122 lastClickCountEvent = 0; 123 lastClickCountEventTime = 0; 124 } 125 } 126 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]; 132 } 133} 134 135- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl { 136 if (!delegate) return; 137 138 BOOL clickCountingForEvent = ([self clickCountEnabledButtons] & event) == event; 139 140 if ([self simulatesHoldForButtonIdentifier: event remoteControl: remoteControl] && lastClickCountEvent==0) { 141 if (pressedDown) { 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]; 148 return; 149 } else { 150 if (lastEventSimulatedHold) { 151 // it was a hold 152 // send an event for "hold release" 153 event = (event << EVENT_TO_HOLD_EVENT_OFFSET); 154 lastHoldEvent = 0; 155 lastEventSimulatedHold = NO; 156 157 [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; 158 return; 159 } else { 160 RemoteControlEventIdentifier previousEvent = lastHoldEvent; 161 @synchronized(self) { 162 lastHoldEvent = 0; 163 } 164 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; 171 eventClickCount = 1; 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 180 } else { 181 // trigger the pressed down event that we consumed first 182 [delegate remoteButton:event pressedDown: YES clickCount:1]; 183 } 184 } 185 } 186 } 187 188 if (clickCountingForEvent) { 189 if (pressedDown == NO) return; 190 191 NSNumber* eventNumber; 192 NSNumber* timeNumber; 193 @synchronized(self) { 194 lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate]; 195 if (lastClickCountEvent == event) { 196 eventClickCount = eventClickCount + 1; 197 } else { 198 eventClickCount = 1; 199 } 200 lastClickCountEvent = event; 201 timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; 202 eventNumber= [NSNumber numberWithUnsignedInt:event]; 203 } 204 [self performSelector: @selector(executeClickCountEvent:) 205 withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil] 206 afterDelay: maxClickTimeDifference]; 207 } else { 208 [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; 209 } 210 211} 212 213@end 214