63cac682eefba371e3194c8ffcbaed897c168061
[vlc.git] / modules / gui / macosx / VLCControlsBarCommon.m
1 /*****************************************************************************
2  * VLCControlsBarCommon.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2012-2016 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul K├╝hne <fkuehne -at- videolan -dot- org>
8  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #import "VLCControlsBarCommon.h"
26 #import "VLCMain.h"
27 #import "VLCCoreInteraction.h"
28 #import "VLCMainMenu.h"
29 #import "VLCPlaylist.h"
30 #import "CompatibilityFixes.h"
31
32 /*****************************************************************************
33  * VLCControlsBarCommon
34  *
35  *  Holds all outlets, actions and code common for controls bar in detached
36  *  and in main window.
37  *****************************************************************************/
38
39 @interface VLCControlsBarCommon ()
40 {
41     NSImage * _pauseImage;
42     NSImage * _pressedPauseImage;
43     NSImage * _playImage;
44     NSImage * _pressedPlayImage;
45
46     NSTimeInterval last_fwd_event;
47     NSTimeInterval last_bwd_event;
48     BOOL just_triggered_next;
49     BOOL just_triggered_previous;
50 }
51 @end
52
53 @implementation VLCControlsBarCommon
54
55 - (void)awakeFromNib
56 {
57     _darkInterface = var_InheritBool(getIntf(), "macosx-interfacestyle");
58     _nativeFullscreenMode = var_InheritBool(getIntf(), "macosx-nativefullscreenmode");
59
60     [self.dropView setDrawBorder: NO];
61
62     [self.playButton setToolTip: _NS("Play/Pause")];
63     [[self.playButton cell] accessibilitySetOverrideValue:_NS("Click to play or pause the current media.") forAttribute:NSAccessibilityDescriptionAttribute];
64     [[self.playButton cell] accessibilitySetOverrideValue:[self.playButton toolTip] forAttribute:NSAccessibilityTitleAttribute];
65
66     [self.backwardButton setToolTip: _NS("Backward")];
67     [[self.backwardButton cell] accessibilitySetOverrideValue:_NS("Click to go to the previous playlist item. Hold to skip backward through the current media.") forAttribute:NSAccessibilityDescriptionAttribute];
68     [[self.backwardButton cell] accessibilitySetOverrideValue:[self.backwardButton toolTip] forAttribute:NSAccessibilityTitleAttribute];
69
70     [self.forwardButton setToolTip: _NS("Forward")];
71     [[self.forwardButton cell] accessibilitySetOverrideValue:_NS("Click to go to the next playlist item. Hold to skip forward through the current media.") forAttribute:NSAccessibilityDescriptionAttribute];
72     [[self.forwardButton cell] accessibilitySetOverrideValue:[self.forwardButton toolTip] forAttribute:NSAccessibilityTitleAttribute];
73
74     [self.timeSlider setToolTip: _NS("Position")];
75     [[self.timeSlider cell] accessibilitySetOverrideValue:_NS("Click and move the mouse while keeping the button pressed to use this slider to change current playback position.") forAttribute:NSAccessibilityDescriptionAttribute];
76     [[self.timeSlider cell] accessibilitySetOverrideValue:[self.timeSlider toolTip] forAttribute:NSAccessibilityTitleAttribute];
77
78     [self.fullscreenButton setToolTip: _NS("Toggle Fullscreen mode")];
79     [[self.fullscreenButton cell] accessibilitySetOverrideValue:_NS("Click to enable fullscreen video playback.") forAttribute:NSAccessibilityDescriptionAttribute];
80     [[self.fullscreenButton cell] accessibilitySetOverrideValue:[self.fullscreenButton toolTip] forAttribute:NSAccessibilityTitleAttribute];
81
82     if (!_darkInterface) {
83         [self.bottomBarView setImagesLeft: imageFromRes(@"bottom-background") middle: imageFromRes(@"bottom-background") right: imageFromRes(@"bottom-background")];
84
85         [self.backwardButton setImage: imageFromRes(@"backward-3btns")];
86         [self.backwardButton setAlternateImage: imageFromRes(@"backward-3btns-pressed")];
87         _playImage = imageFromRes(@"play");
88         _pressedPlayImage = imageFromRes(@"play-pressed");
89         _pauseImage = imageFromRes(@"pause");
90         _pressedPauseImage = imageFromRes(@"pause-pressed");
91         [self.forwardButton setImage: imageFromRes(@"forward-3btns")];
92         [self.forwardButton setAlternateImage: imageFromRes(@"forward-3btns-pressed")];
93
94         [self.fullscreenButton setImage: imageFromRes(@"fullscreen-one-button")];
95         [self.fullscreenButton setAlternateImage: imageFromRes(@"fullscreen-one-button-pressed")];
96     } else {
97         [self.bottomBarView setImagesLeft: imageFromRes(@"bottomdark-left") middle: imageFromRes(@"bottom-background_dark") right: imageFromRes(@"bottomdark-right")];
98
99         [self.backwardButton setImage: imageFromRes(@"backward-3btns-dark")];
100         [self.backwardButton setAlternateImage: imageFromRes(@"backward-3btns-dark-pressed")];
101         _playImage = imageFromRes(@"play_dark");
102         _pressedPlayImage = imageFromRes(@"play-pressed_dark");
103         _pauseImage = imageFromRes(@"pause_dark");
104         _pressedPauseImage = imageFromRes(@"pause-pressed_dark");
105         [self.forwardButton setImage: imageFromRes(@"forward-3btns-dark")];
106         [self.forwardButton setAlternateImage: imageFromRes(@"forward-3btns-dark-pressed")];
107
108         [self.fullscreenButton setImage: imageFromRes(@"fullscreen-one-button-pressed_dark")];
109         [self.fullscreenButton setAlternateImage: imageFromRes(@"fullscreen-one-button-pressed_dark")];
110     }
111
112     [self.playButton setImage: _playImage];
113     [self.playButton setAlternateImage: _pressedPlayImage];
114
115     NSColor *timeFieldTextColor;
116     if (!var_InheritBool(getIntf(), "macosx-interfacestyle"))
117         timeFieldTextColor = [NSColor colorWithCalibratedRed:0.229 green:0.229 blue:0.229 alpha:100.0];
118     else
119         timeFieldTextColor = [NSColor colorWithCalibratedRed:0.64 green:0.64 blue:0.64 alpha:100.0];
120     [self.timeField setTextColor: timeFieldTextColor];
121     [self.timeField setFont:[NSFont titleBarFontOfSize:10.0]];
122     [self.timeField setAlignment: NSCenterTextAlignment];
123     [self.timeField setNeedsDisplay:YES];
124     [self.timeField setRemainingIdentifier:@"DisplayTimeAsTimeRemaining"];
125
126     // remove fullscreen button for lion fullscreen
127     if (_nativeFullscreenMode) {
128         [self.fullscreenButton removeFromSuperview];
129     }
130
131     if (config_GetInt(getIntf(), "macosx-show-playback-buttons"))
132         [self toggleForwardBackwardMode: YES];
133
134 }
135
136 - (CGFloat)height
137 {
138     return [self.bottomBarView frame].size.height;
139 }
140
141 - (void)toggleForwardBackwardMode:(BOOL)b_alt
142 {
143     if (b_alt == YES) {
144         /* change the accessibility help for the backward/forward buttons accordingly */
145         [[self.backwardButton cell] accessibilitySetOverrideValue:_NS("Click and hold to skip backward through the current media.") forAttribute:NSAccessibilityDescriptionAttribute];
146         [[self.forwardButton cell] accessibilitySetOverrideValue:_NS("Click and hold to skip forward through the current media.") forAttribute:NSAccessibilityDescriptionAttribute];
147
148         [self.forwardButton setAction:@selector(alternateForward:)];
149         [self.backwardButton setAction:@selector(alternateBackward:)];
150
151     } else {
152         /* change the accessibility help for the backward/forward buttons accordingly */
153         [[self.backwardButton cell] accessibilitySetOverrideValue:_NS("Click to go to the previous playlist item. Hold to skip backward through the current media.") forAttribute:NSAccessibilityDescriptionAttribute];
154         [[self.forwardButton cell] accessibilitySetOverrideValue:_NS("Click to go to the next playlist item. Hold to skip forward through the current media.") forAttribute:NSAccessibilityDescriptionAttribute];
155
156         [self.forwardButton setAction:@selector(fwd:)];
157         [self.backwardButton setAction:@selector(bwd:)];
158     }
159 }
160
161 #pragma mark -
162 #pragma mark Button Actions
163
164 - (IBAction)play:(id)sender
165 {
166     [[VLCCoreInteraction sharedInstance] playOrPause];
167 }
168
169 - (void)resetPreviousButton
170 {
171     if (([NSDate timeIntervalSinceReferenceDate] - last_bwd_event) >= 0.35) {
172         // seems like no further event occurred, so let's switch the playback item
173         [[VLCCoreInteraction sharedInstance] previous];
174         just_triggered_previous = NO;
175     }
176 }
177
178 - (void)resetBackwardSkip
179 {
180     // the user stopped skipping, so let's allow him to change the item
181     if (([NSDate timeIntervalSinceReferenceDate] - last_bwd_event) >= 0.35)
182         just_triggered_previous = NO;
183 }
184
185 - (IBAction)bwd:(id)sender
186 {
187     if (!just_triggered_previous) {
188         just_triggered_previous = YES;
189         [self performSelector:@selector(resetPreviousButton)
190                    withObject: NULL
191                    afterDelay:0.40];
192     } else {
193         if (([NSDate timeIntervalSinceReferenceDate] - last_fwd_event) > 0.16) {
194             // we just skipped 4 "continous" events, otherwise we are too fast
195             [[VLCCoreInteraction sharedInstance] backwardExtraShort];
196             last_bwd_event = [NSDate timeIntervalSinceReferenceDate];
197             [self performSelector:@selector(resetBackwardSkip)
198                        withObject: NULL
199                        afterDelay:0.40];
200         }
201     }
202 }
203
204 - (void)resetNextButton
205 {
206     if (([NSDate timeIntervalSinceReferenceDate] - last_fwd_event) >= 0.35) {
207         // seems like no further event occurred, so let's switch the playback item
208         [[VLCCoreInteraction sharedInstance] next];
209         just_triggered_next = NO;
210     }
211 }
212
213 - (void)resetForwardSkip
214 {
215     // the user stopped skipping, so let's allow him to change the item
216     if (([NSDate timeIntervalSinceReferenceDate] - last_fwd_event) >= 0.35)
217         just_triggered_next = NO;
218 }
219
220 - (IBAction)fwd:(id)sender
221 {
222     if (!just_triggered_next) {
223         just_triggered_next = YES;
224         [self performSelector:@selector(resetNextButton)
225                    withObject: NULL
226                    afterDelay:0.40];
227     } else {
228         if (([NSDate timeIntervalSinceReferenceDate] - last_fwd_event) > 0.16) {
229             // we just skipped 4 "continous" events, otherwise we are too fast
230             [[VLCCoreInteraction sharedInstance] forwardExtraShort];
231             last_fwd_event = [NSDate timeIntervalSinceReferenceDate];
232             [self performSelector:@selector(resetForwardSkip)
233                        withObject: NULL
234                        afterDelay:0.40];
235         }
236     }
237 }
238
239 // alternative actions for forward / backward buttons when next / prev are activated
240 - (IBAction)alternateForward:(id)sender
241 {
242     [[VLCCoreInteraction sharedInstance] forwardExtraShort];
243 }
244
245 - (IBAction)alternateBackward:(id)sender
246 {
247     [[VLCCoreInteraction sharedInstance] backwardExtraShort];
248 }
249
250 - (IBAction)timeSliderAction:(id)sender
251 {
252     float f_updated;
253     input_thread_t * p_input;
254
255     switch([[NSApp currentEvent] type]) {
256         case NSLeftMouseUp:
257             /* Ignore mouse up, as this is a continous slider and
258              * when the user does a single click to a position on the slider,
259              * the action is called twice, once for the mouse down and once
260              * for the mouse up event. This results in two short seeks one
261              * after another to the same position, which results in weird
262              * audio quirks.
263              */
264             return;
265         case NSLeftMouseDown:
266         case NSLeftMouseDragged:
267             f_updated = [sender floatValue];
268             break;
269
270         default:
271             return;
272     }
273     p_input = pl_CurrentInput(getIntf());
274     if (p_input != NULL) {
275         vlc_value_t pos;
276         NSString * o_time;
277
278         pos.f_float = f_updated / 10000.;
279         var_Set(p_input, "position", pos);
280         [self.timeSlider setFloatValue: f_updated];
281
282         o_time = [[VLCStringUtility sharedInstance] getCurrentTimeAsString: p_input negative:[self.timeField timeRemaining]];
283         [self.timeField setStringValue: o_time];
284         vlc_object_release(p_input);
285     }
286 }
287
288 - (IBAction)fullscreen:(id)sender
289 {
290     [[VLCCoreInteraction sharedInstance] toggleFullscreen];
291 }
292
293 #pragma mark -
294 #pragma mark Updaters
295
296 - (void)updateTimeSlider
297 {
298     input_thread_t * p_input;
299     p_input = pl_CurrentInput(getIntf());
300
301     [self.timeSlider setHidden:NO];
302
303     if (!p_input) {
304         // Nothing playing
305         [self.timeSlider setFloatValue: 0.0];
306         [self.timeField setStringValue: @"00:00"];
307         [self.timeSlider setIndefinite:NO];
308         [self.timeSlider setEnabled:NO];
309         return;
310     }
311
312     vlc_value_t pos;
313     var_Get(p_input, "position", &pos);
314     [self.timeSlider setFloatValue:(10000. * pos.f_float)];
315
316     mtime_t dur = input_item_GetDuration(input_GetItem(p_input));
317     if (dur == -1) {
318         // No duration, disable slider
319         [self.timeSlider setEnabled:NO];
320     } else {
321         input_state_e inputState = input_GetState(p_input);
322         bool buffering = (inputState == INIT_S || inputState == OPENING_S);
323         [self.timeSlider setIndefinite:buffering];
324     }
325
326     NSString *time = [[VLCStringUtility sharedInstance] getCurrentTimeAsString:p_input
327                                                                       negative:[self.timeField timeRemaining]];
328     [self.timeField setStringValue:time];
329     [self.timeField setNeedsDisplay:YES];
330
331     vlc_object_release(p_input);
332 }
333
334 - (void)updateControls
335 {
336     bool b_plmul = false;
337     bool b_seekable = false;
338     bool b_chapters = false;
339     bool b_buffering = false;
340
341     playlist_t * p_playlist = pl_Get(getIntf());
342
343     PL_LOCK;
344     b_plmul = playlist_CurrentSize(p_playlist) > 1;
345     PL_UNLOCK;
346
347     input_thread_t * p_input = playlist_CurrentInput(p_playlist);
348
349     if (p_input) {
350         input_state_e inputState = input_GetState(p_input);
351         if (inputState == INIT_S || inputState == OPENING_S)
352             b_buffering = YES;
353
354         /* seekable streams */
355         b_seekable = var_GetBool(p_input, "can-seek");
356
357         /* chapters & titles */
358         //FIXME! b_chapters = p_input->stream.i_area_nb > 1;
359
360         vlc_object_release(p_input);
361     }
362
363     [self.timeSlider setEnabled: b_seekable];
364
365     [self.forwardButton setEnabled: (b_seekable || b_plmul || b_chapters)];
366     [self.backwardButton setEnabled: (b_seekable || b_plmul || b_chapters)];
367 }
368
369 - (void)setPause
370 {
371     [self.playButton setImage: _pauseImage];
372     [self.playButton setAlternateImage: _pressedPauseImage];
373     [self.playButton setToolTip: _NS("Pause")];
374 }
375
376 - (void)setPlay
377 {
378     [self.playButton setImage: _playImage];
379     [self.playButton setAlternateImage: _pressedPlayImage];
380     [self.playButton setToolTip: _NS("Play")];
381 }
382
383 - (void)setFullscreenState:(BOOL)b_fullscreen
384 {
385     if (!self.nativeFullscreenMode)
386         [self.fullscreenButton setState:b_fullscreen];
387 }
388
389 @end