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