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