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