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