| /***************************************************************************** |
| * MultiClickRemoteBehavior.m |
| * RemoteControlWrapper |
| * |
| * Created by Martin Kahr on 11.03.06 under a MIT-style license. |
| * Copyright (c) 2006 martinkahr.com. All rights reserved. |
| * |
| * Code modified and adapted to OpenOffice.org |
| * by Eric Bachard on 11.08.2008 under the same License |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included |
| * in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| * |
| *****************************************************************************/ |
| |
| #import "MultiClickRemoteBehavior.h" |
| |
| const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE = 0.35; |
| const NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL = 0.4; |
| |
| @implementation MultiClickRemoteBehavior |
| |
| - (id) init { |
| if ( (self = [super init]) ) { |
| maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE; |
| } |
| return self; |
| } |
| |
| // Delegates are not retained! |
| // http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html |
| // Delegating objects do not (and should not) retain their delegates. |
| // However, clients of delegating objects (applications, usually) are responsible for ensuring that their delegates are around |
| // to receive delegation messages. To do this, they may have to retain the delegate. |
| - (void) setDelegate: (id) _delegate { |
| if ( _delegate && ( [_delegate respondsToSelector:@selector(remoteButton:pressedDown:clickCount:)] == NO )) return; // return what ? |
| |
| delegate = _delegate; |
| } |
| - (id) delegate { |
| return delegate; |
| } |
| |
| - (BOOL) simulateHoldEvent { |
| return simulateHoldEvents; |
| } |
| - (void) setSimulateHoldEvent: (BOOL) value { |
| simulateHoldEvents = value; |
| } |
| |
| - (BOOL) simulatesHoldForButtonIdentifier: (RemoteControlEventIdentifier) identifier remoteControl: (RemoteControl*) remoteControl { |
| // we do that check only for the normal button identifiers as we would check for hold support for hold events instead |
| if (identifier > (1 << EVENT_TO_HOLD_EVENT_OFFSET)) return NO; |
| |
| return [self simulateHoldEvent] && [remoteControl sendsEventForButtonIdentifier: (identifier << EVENT_TO_HOLD_EVENT_OFFSET)]==NO; |
| } |
| |
| - (BOOL) clickCountingEnabled { |
| return clickCountEnabledButtons != 0; |
| } |
| - (void) setClickCountingEnabled: (BOOL) value { |
| if (value) { |
| [self setClickCountEnabledButtons: kRemoteButtonPlus | kRemoteButtonMinus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu | kMetallicRemote2009ButtonPlay | kMetallicRemote2009ButtonMiddlePlay]; |
| } else { |
| [self setClickCountEnabledButtons: 0]; |
| } |
| } |
| |
| - (unsigned int) clickCountEnabledButtons { |
| return clickCountEnabledButtons; |
| } |
| - (void) setClickCountEnabledButtons: (unsigned int)value { |
| clickCountEnabledButtons = value; |
| } |
| |
| - (NSTimeInterval) maximumClickCountTimeDifference { |
| return maxClickTimeDifference; |
| } |
| - (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff { |
| maxClickTimeDifference = timeDiff; |
| } |
| |
| - (void) sendSimulatedHoldEvent: (id) time { |
| BOOL startSimulateHold = NO; |
| RemoteControlEventIdentifier event = lastHoldEvent; |
| @synchronized(self) { |
| startSimulateHold = (lastHoldEvent>0 && lastHoldEventTime == [time doubleValue]); |
| } |
| if (startSimulateHold) { |
| lastEventSimulatedHold = YES; |
| event = (event << EVENT_TO_HOLD_EVENT_OFFSET); |
| [delegate remoteButton:event pressedDown: YES clickCount: 1]; |
| } |
| } |
| |
| - (void) executeClickCountEvent: (NSArray*) values { |
| RemoteControlEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue]; |
| NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue]; |
| |
| BOOL finishedClicking = NO; |
| int finalClickCount = eventClickCount; |
| |
| @synchronized(self) { |
| finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime); |
| if (finishedClicking) { |
| eventClickCount = 0; |
| lastClickCountEvent = 0; |
| lastClickCountEventTime = 0; |
| } |
| } |
| |
| if (finishedClicking) { |
| [delegate remoteButton:event pressedDown: YES clickCount:finalClickCount]; |
| // trigger a button release event, too |
| [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]]; |
| [delegate remoteButton:event pressedDown: NO clickCount:finalClickCount]; |
| } |
| } |
| |
| - (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl { |
| if (!delegate) return; |
| |
| BOOL clickCountingForEvent = ([self clickCountEnabledButtons] & event) == event; |
| |
| if ([self simulatesHoldForButtonIdentifier: event remoteControl: remoteControl] && lastClickCountEvent==0) { |
| if (pressedDown) { |
| // wait to see if it is a hold |
| lastHoldEvent = event; |
| lastHoldEventTime = [NSDate timeIntervalSinceReferenceDate]; |
| [self performSelector:@selector(sendSimulatedHoldEvent:) |
| withObject:[NSNumber numberWithDouble:lastHoldEventTime] |
| afterDelay:HOLD_RECOGNITION_TIME_INTERVAL]; |
| return; |
| } else { |
| if (lastEventSimulatedHold) { |
| // it was a hold |
| // send an event for "hold release" |
| event = (event << EVENT_TO_HOLD_EVENT_OFFSET); |
| lastHoldEvent = 0; |
| lastEventSimulatedHold = NO; |
| |
| [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; |
| return; |
| } else { |
| RemoteControlEventIdentifier previousEvent = lastHoldEvent; |
| @synchronized(self) { |
| lastHoldEvent = 0; |
| } |
| |
| // in case click counting is enabled we have to setup the state for that, too |
| if (clickCountingForEvent) { |
| lastClickCountEvent = previousEvent; |
| lastClickCountEventTime = lastHoldEventTime; |
| NSNumber* eventNumber; |
| NSNumber* timeNumber; |
| eventClickCount = 1; |
| timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; |
| eventNumber= [NSNumber numberWithUnsignedInt:previousEvent]; |
| NSTimeInterval diffTime = maxClickTimeDifference-([NSDate timeIntervalSinceReferenceDate]-lastHoldEventTime); |
| [self performSelector: @selector(executeClickCountEvent:) |
| withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil] |
| afterDelay: diffTime]; |
| // we do not return here because we are still in the press-release event |
| // that will be consumed below |
| } else { |
| // trigger the pressed down event that we consumed first |
| [delegate remoteButton:event pressedDown: YES clickCount:1]; |
| } |
| } |
| } |
| } |
| |
| if (clickCountingForEvent) { |
| if (pressedDown == NO) return; |
| |
| NSNumber* eventNumber; |
| NSNumber* timeNumber; |
| @synchronized(self) { |
| lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate]; |
| if (lastClickCountEvent == event) { |
| eventClickCount = eventClickCount + 1; |
| } else { |
| eventClickCount = 1; |
| } |
| lastClickCountEvent = event; |
| timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; |
| eventNumber= [NSNumber numberWithUnsignedInt:event]; |
| } |
| [self performSelector: @selector(executeClickCountEvent:) |
| withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil] |
| afterDelay: maxClickTimeDifference]; |
| } else { |
| [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; |
| } |
| |
| } |
| |
| @end |