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