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