xref: /AOO41X/main/apple_remote/MultiClickRemoteBehavior.m (revision 5343d3eb23cbde9fbb3e990d8bec361773f68b14)
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) {
78*5343d3ebSEric 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