strings: add an input_item_t arg to vlc_strfinput()
[vlc.git] / modules / gui / macosx / VLCMainWindow.m
blobbcf03eca5ff3068357b756b156f735da0abac7ee
1 /*****************************************************************************
2  * VLCMainWindow.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2002-2013 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8  *          Jon Lech Johansen <jon-vl@nanocrew.net>
9  *          Christophe Massiot <massiot@via.ecp.fr>
10  *          Derk-Jan Hartman <hartman at videolan.org>
11  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
28 #import "VLCMainWindow.h"
30 #import "VLCMain.h"
31 #import "CompatibilityFixes.h"
32 #import "VLCCoreInteraction.h"
33 #import "VLCAudioEffectsWindowController.h"
34 #import "VLCMainMenu.h"
35 #import "VLCOpenWindowController.h"
36 #import "VLCPlaylist.h"
37 #import "SideBarItem.h"
38 #import <math.h>
39 #import <vlc_playlist.h>
40 #import <vlc_url.h>
41 #import <vlc_strings.h>
42 #import <vlc_services_discovery.h>
43 #import "VLCPLModel.h"
45 #import "PXSourceList.h"
46 #import "PXSourceListDataSource.h"
48 #import "VLCMainWindowControlsBar.h"
49 #import "VLCVoutView.h"
50 #import "VLCVideoOutputProvider.h"
53 @interface VLCMainWindow() <PXSourceListDataSource, PXSourceListDelegate, NSOutlineViewDataSource, NSOutlineViewDelegate, NSWindowDelegate, NSAnimationDelegate, NSSplitViewDelegate>
55     BOOL videoPlaybackEnabled;
56     BOOL dropzoneActive;
57     BOOL splitViewRemoved;
58     BOOL minimizedView;
60     BOOL b_video_playback_enabled;
61     BOOL b_dropzone_active;
62     BOOL b_splitview_removed;
63     BOOL b_minimized_view;
65     CGFloat f_lastSplitViewHeight;
66     CGFloat f_lastLeftSplitViewWidth;
68     NSMutableArray *o_sidebaritems;
70     /* this is only true, when we have NO video playing inside the main window */
72     BOOL b_podcastView_displayed;
74     NSRect frameBeforePlayback;
76 - (void)makeSplitViewVisible;
77 - (void)makeSplitViewHidden;
78 - (void)showPodcastControls;
79 - (void)hidePodcastControls;
80 @end
82 static const float f_min_window_height = 307.;
84 @implementation VLCMainWindow
86 #pragma mark -
87 #pragma mark Initialization
89 - (BOOL)isEvent:(NSEvent *)o_event forKey:(const char *)keyString
91     char *key;
92     NSString *o_key;
94     key = config_GetPsz(keyString);
95     o_key = [NSString stringWithFormat:@"%s", key];
96     FREENULL(key);
98     unsigned int i_keyModifiers = [[VLCStringUtility sharedInstance] VLCModifiersToCocoa:o_key];
100     NSString * characters = [o_event charactersIgnoringModifiers];
101     if ([characters length] > 0) {
102         return [[characters lowercaseString] isEqualToString: [[VLCStringUtility sharedInstance] VLCKeyToString: o_key]] &&
103                 (i_keyModifiers & NSShiftKeyMask)     == ([o_event modifierFlags] & NSShiftKeyMask) &&
104                 (i_keyModifiers & NSControlKeyMask)   == ([o_event modifierFlags] & NSControlKeyMask) &&
105                 (i_keyModifiers & NSAlternateKeyMask) == ([o_event modifierFlags] & NSAlternateKeyMask) &&
106                 (i_keyModifiers & NSCommandKeyMask)   == ([o_event modifierFlags] & NSCommandKeyMask);
107     }
108     return NO;
111 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
113     BOOL b_force = NO;
114     // these are key events which should be handled by vlc core, but are attached to a main menu item
115     if (![self isEvent: o_event forKey: "key-vol-up"] &&
116         ![self isEvent: o_event forKey: "key-vol-down"] &&
117         ![self isEvent: o_event forKey: "key-vol-mute"] &&
118         ![self isEvent: o_event forKey: "key-prev"] &&
119         ![self isEvent: o_event forKey: "key-next"] &&
120         ![self isEvent: o_event forKey: "key-jump+short"] &&
121         ![self isEvent: o_event forKey: "key-jump-short"]) {
122         /* We indeed want to prioritize some Cocoa key equivalent against libvlc,
123          so we perform the menu equivalent now. */
124         if ([[NSApp mainMenu] performKeyEquivalent:o_event])
125             return TRUE;
126     }
127     else
128         b_force = YES;
130     VLCCoreInteraction *coreInteraction = [VLCCoreInteraction sharedInstance];
131     return [coreInteraction hasDefinedShortcutKey:o_event force:b_force] ||
132            [coreInteraction keyEvent:o_event];
135 - (void)dealloc
137     [[NSNotificationCenter defaultCenter] removeObserver: self];
140 - (void)awakeFromNib
142     [super awakeFromNib];
144     /*
145      * General setup
146      */
148     NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
149     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
151     BOOL splitViewShouldBeHidden = NO;
153     [self setDelegate:self];
154     [self setRestorable:NO];
155     [self setExcludedFromWindowsMenu:YES];
156     [self setAcceptsMouseMovedEvents:YES];
157     [self setFrameAutosaveName:@"mainwindow"];
159     _nativeFullscreenMode = var_InheritBool(getIntf(), "macosx-nativefullscreenmode");
160     b_dropzone_active = YES;
162     // Playlist setup
163     VLCPlaylist *playlist = [[VLCMain sharedInstance] playlist];
164     [playlist setOutlineView:(VLCPlaylistView *)_outlineView];
165     [playlist setPlaylistHeaderView:_outlineView.headerView];
166     [self setNextResponder:playlist];
168     // (Re)load sidebar for the first time and select first item
169     [self reloadSidebar];
170     [_sidebarView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
173     /*
174      * Set up translatable strings for the UI elements
175      */
177     // Window title
178     [self setTitle:_NS("VLC media player")];
180     // Search Field
181     [_searchField setToolTip:_NS("Search in Playlist")];
182     [_searchField.cell setPlaceholderString:_NS("Search")];
183     [_searchField.cell accessibilitySetOverrideValue:_NS("Search the playlist. Results will be selected in the table.")
184                                         forAttribute:NSAccessibilityDescriptionAttribute];
186     // Dropzone
187     [_dropzoneLabel setStringValue:_NS("Drop media here")];
188     [_dropzoneImageView setImage:imageFromRes(@"dropzone")];
189     [_dropzoneButton setTitle:_NS("Open media...")];
190     [_dropzoneButton.cell accessibilitySetOverrideValue:_NS("Open a dialog to select the media to play")
191                                            forAttribute:NSAccessibilityDescriptionAttribute];
193     // Podcast view
194     [_podcastAddButton setTitle:_NS("Subscribe")];
195     [_podcastRemoveButton setTitle:_NS("Unsubscribe")];
197     // Podcast subscribe window
198     [_podcastSubscribeTitle setStringValue:_NS("Subscribe to a podcast")];
199     [_podcastSubscribeSubtitle setStringValue:_NS("Enter URL of the podcast to subscribe to:")];
200     [_podcastSubscribeOkButton setTitle:_NS("Subscribe")];
201     [_podcastSubscribeCancelButton setTitle:_NS("Cancel")];
203     // Podcast unsubscribe window
204     [_podcastUnsubscirbeTitle setStringValue:_NS("Unsubscribe from a podcast")];
205     [_podcastUnsubscribeSubtitle setStringValue:_NS("Select the podcast you would like to unsubscribe from:")];
206     [_podcastUnsubscribeOkButton setTitle:_NS("Unsubscribe")];
207     [_podcastUnsubscribeCancelButton setTitle:_NS("Cancel")];
209     /* interface builder action */
210     CGFloat f_threshold_height = f_min_video_height + [self.controlsBar height];
212     if ([[self contentView] frame].size.height < f_threshold_height)
213         splitViewShouldBeHidden = YES;
215     // Set that here as IB seems to be buggy
216     [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
218     _fspanel = [[VLCFSPanelController alloc] init];
219     [_fspanel showWindow:self];
221     /* make sure we display the desired default appearance when VLC launches for the first time */
222     if (![defaults objectForKey:@"VLCFirstRun"]) {
223         [defaults setObject:[NSDate date] forKey:@"VLCFirstRun"];
225         [_sidebarView expandItem:nil expandChildren:YES];
227         NSAlert *albumArtAlert = [[NSAlert alloc] init];
228         [albumArtAlert setMessageText:_NS("Check for album art and metadata?")];
229         [albumArtAlert setInformativeText:_NS("VLC can check online for album art and metadata to enrich your playback experience, e.g. by providing track information when playing Audio CDs. To provide this functionality, VLC will send information about your contents to trusted services in an anonymized form.")];
230         [albumArtAlert addButtonWithTitle:_NS("Enable Metadata Retrieval")];
231         [albumArtAlert addButtonWithTitle:_NS("No, Thanks")];
233         NSInteger returnValue = [albumArtAlert runModal];
234         config_PutInt("metadata-network-access", returnValue == NSAlertFirstButtonReturn);
235     }
237     [_playlistScrollView setBorderType:NSNoBorder];
238     [_sidebarScrollView setBorderType:NSNoBorder];
240     [defaultCenter addObserver: self selector: @selector(someWindowWillClose:) name: NSWindowWillCloseNotification object: nil];
241     [defaultCenter addObserver: self selector: @selector(someWindowWillMiniaturize:) name: NSWindowWillMiniaturizeNotification object:nil];
242     [defaultCenter addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
243     [defaultCenter addObserver: self selector: @selector(mainSplitViewDidResizeSubviews:) name: NSSplitViewDidResizeSubviewsNotification object:_splitView];
245     if (splitViewShouldBeHidden) {
246         [self hideSplitView:YES];
247         f_lastSplitViewHeight = 300;
248     }
250     /* sanity check for the window size */
251     NSRect frame = [self frame];
252     NSSize screenSize = [[self screen] frame].size;
253     if (screenSize.width <= frame.size.width || screenSize.height <= frame.size.height) {
254         self.nativeVideoSize = screenSize;
255         [self resizeWindow];
256     }
258     /* update fs button to reflect state for next startup */
259     if (var_InheritBool(pl_Get(getIntf()), "fullscreen"))
260         [self.controlsBar setFullscreenState:YES];
262     /* restore split view */
263     f_lastLeftSplitViewWidth = 200;
264     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem: ![_splitView isSubviewCollapsed:_splitViewLeft]];
267 #pragma mark -
268 #pragma mark appearance management
270 - (void)reloadSidebar
272     BOOL isAReload = NO;
273     if (o_sidebaritems)
274         isAReload = YES;
276     o_sidebaritems = [[NSMutableArray alloc] init];
277     SideBarItem *libraryItem = [SideBarItem itemWithTitle:_NS("LIBRARY") identifier:@"library"];
278     SideBarItem *playlistItem = [SideBarItem itemWithTitle:_NS("Playlist") identifier:@"playlist"];
279     [playlistItem setIcon: imageFromRes(@"sidebar-playlist")];
280     SideBarItem *mycompItem = [SideBarItem itemWithTitle:_NS("MY COMPUTER") identifier:@"mycomputer"];
281     SideBarItem *devicesItem = [SideBarItem itemWithTitle:_NS("DEVICES") identifier:@"devices"];
282     SideBarItem *lanItem = [SideBarItem itemWithTitle:_NS("LOCAL NETWORK") identifier:@"localnetwork"];
283     SideBarItem *internetItem = [SideBarItem itemWithTitle:_NS("INTERNET") identifier:@"internet"];
285     /* SD subnodes, inspired by the Qt intf */
286     char **ppsz_longnames = NULL;
287     int *p_categories = NULL;
288     char **ppsz_names = vlc_sd_GetNames(pl_Get(getIntf()), &ppsz_longnames, &p_categories);
289     if (!ppsz_names)
290         msg_Err(getIntf(), "no sd item found"); //TODO
291     char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
292     int *p_category = p_categories;
293     NSMutableArray *internetItems = [[NSMutableArray alloc] init];
294     NSMutableArray *devicesItems = [[NSMutableArray alloc] init];
295     NSMutableArray *lanItems = [[NSMutableArray alloc] init];
296     NSMutableArray *mycompItems = [[NSMutableArray alloc] init];
297     NSString *o_identifier;
298     for (; ppsz_name && *ppsz_name; ppsz_name++, ppsz_longname++, p_category++) {
299         o_identifier = toNSStr(*ppsz_name);
300         switch (*p_category) {
301             case SD_CAT_INTERNET:
302                 [internetItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
303                 [[internetItems lastObject] setIcon: imageFromRes(@"sidebar-podcast")];
304                 [[internetItems lastObject] setSdtype: SD_CAT_INTERNET];
305                 break;
306             case SD_CAT_DEVICES:
307                 [devicesItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
308                 [[devicesItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
309                 [[devicesItems lastObject] setSdtype: SD_CAT_DEVICES];
310                 break;
311             case SD_CAT_LAN:
312                 [lanItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
313                 [[lanItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
314                 [[lanItems lastObject] setSdtype: SD_CAT_LAN];
315                 break;
316             case SD_CAT_MYCOMPUTER:
317                 [mycompItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
318                 if (!strncmp(*ppsz_name, "video_dir", 9))
319                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-movie")];
320                 else if (!strncmp(*ppsz_name, "audio_dir", 9))
321                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-music")];
322                 else if (!strncmp(*ppsz_name, "picture_dir", 11))
323                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-pictures")];
324                 else
325                     [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
326                 [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
327                 break;
328             default:
329                 msg_Warn(getIntf(), "unknown SD type found, skipping (%s)", *ppsz_name);
330                 break;
331         }
333         free(*ppsz_name);
334         free(*ppsz_longname);
335     }
336     [mycompItem setChildren: [NSArray arrayWithArray: mycompItems]];
337     [devicesItem setChildren: [NSArray arrayWithArray: devicesItems]];
338     [lanItem setChildren: [NSArray arrayWithArray: lanItems]];
339     [internetItem setChildren: [NSArray arrayWithArray: internetItems]];
340     free(ppsz_names);
341     free(ppsz_longnames);
342     free(p_categories);
344     [libraryItem setChildren: [NSArray arrayWithObjects:playlistItem, nil]];
345     [o_sidebaritems addObject: libraryItem];
346     if ([mycompItem hasChildren])
347         [o_sidebaritems addObject: mycompItem];
348     if ([devicesItem hasChildren])
349         [o_sidebaritems addObject: devicesItem];
350     if ([lanItem hasChildren])
351         [o_sidebaritems addObject: lanItem];
352     if ([internetItem hasChildren])
353         [o_sidebaritems addObject: internetItem];
355     [_sidebarView reloadData];
356     [_sidebarView setDropItem:playlistItem dropChildIndex:NSOutlineViewDropOnItemIndex];
357     [_sidebarView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
359     [_sidebarView setAutosaveName:@"mainwindow-sidebar"];
360     [_sidebarView setDataSource:self];
361     [_sidebarView setDelegate:self];
362     [_sidebarView setAutosaveExpandedItems:YES];
364     [_sidebarView expandItem:libraryItem expandChildren:YES];
366     if (isAReload) {
367         [_sidebarView expandItem:nil expandChildren:YES];
368     }
371 // Show split view and hide the video view
372 - (void)makeSplitViewVisible
374     [self setContentMinSize: NSMakeSize(604., f_min_window_height)];
376     NSRect old_frame = [self frame];
377     CGFloat newHeight = [self minSize].height;
378     if (old_frame.size.height < newHeight) {
379         NSRect new_frame = old_frame;
380         new_frame.origin.y = old_frame.origin.y + old_frame.size.height - newHeight;
381         new_frame.size.height = newHeight;
383         [[self animator] setFrame:new_frame display:YES animate:YES];
384     }
386     [self.videoView setHidden:YES];
387     [_splitView setHidden:NO];
388     if (self.nativeFullscreenMode && [self fullscreen]) {
389         [self showControlsBar];
390         [self.fspanel setNonActive];
391     }
393     [self makeFirstResponder:_playlistScrollView];
396 // Hides the split view and makes the vout view in foreground
397 - (void)makeSplitViewHidden
399     [self setContentMinSize: NSMakeSize(604., f_min_video_height)];
401     [_splitView setHidden:YES];
402     [self.videoView setHidden:NO];
403     if (self.nativeFullscreenMode && [self fullscreen]) {
404         [self hideControlsBar];
405         [self.fspanel setActive];
406     }
408     if ([[self.videoView subviews] count] > 0)
409         [self makeFirstResponder: [[self.videoView subviews] firstObject]];
412 - (void)changePlaylistState:(VLCPlaylistStateEvent)event
414     // Beware, this code is really ugly
416     msg_Dbg(getIntf(), "toggle playlist from state: removed splitview %i, minimized view %i. Event %i", b_splitview_removed, b_minimized_view, event);
417     if (![self isVisible] && event == psUserMenuEvent) {
418         [self makeKeyAndOrderFront: nil];
419         return;
420     }
422     BOOL b_activeVideo = [[VLCMain sharedInstance] activeVideoPlayback];
423     BOOL b_restored = NO;
425     // ignore alt if triggered through main menu shortcut
426     BOOL b_have_alt_key = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0;
427     if (event == psUserMenuEvent)
428         b_have_alt_key = NO;
430     // eUserMenuEvent is now handled same as eUserEvent
431     if(event == psUserMenuEvent)
432         event = psUserEvent;
434     if (b_dropzone_active && b_have_alt_key) {
435         [self hideDropZone];
436         return;
437     }
439     if (!(self.nativeFullscreenMode && self.fullscreen) && !b_splitview_removed && ((b_have_alt_key && b_activeVideo)
440                                                                               || (self.nonembedded && event == psUserEvent)
441                                                                               || (!b_activeVideo && event == psUserEvent)
442                                                                               || (b_minimized_view && event == psVideoStartedOrStoppedEvent))) {
443         // for starting playback, window is resized through resized events
444         // for stopping playback, resize through reset to previous frame
445         [self hideSplitView: event != psVideoStartedOrStoppedEvent];
446         b_minimized_view = NO;
447     } else {
448         if (b_splitview_removed) {
449             if (!self.nonembedded || (event == psUserEvent && self.nonembedded))
450                 [self showSplitView: event != psVideoStartedOrStoppedEvent];
452             if (event != psUserEvent)
453                 b_minimized_view = YES;
454             else
455                 b_minimized_view = NO;
457             if (b_activeVideo)
458                 b_restored = YES;
459         }
461         if (!self.nonembedded) {
462             if (([self.videoView isHidden] && b_activeVideo) || b_restored || (b_activeVideo && event != psUserEvent))
463                 [self makeSplitViewHidden];
464             else
465                 [self makeSplitViewVisible];
466         } else {
467             [_splitView setHidden: NO];
468             [_playlistScrollView setHidden: NO];
469             [self.videoView setHidden: YES];
470             [self showControlsBar];
471         }
472     }
474     msg_Dbg(getIntf(), "toggle playlist to state: removed splitview %i, minimized view %i", b_splitview_removed, b_minimized_view);
477 - (IBAction)dropzoneButtonAction:(id)sender
479     [[[VLCMain sharedInstance] open] openFileGeneric];
482 #pragma mark -
483 #pragma mark overwritten default functionality
485 - (void)windowResizedOrMoved:(NSNotification *)notification
487     [self saveFrameUsingName:[self frameAutosaveName]];
490 - (void)applicationWillTerminate:(NSNotification *)notification
492     [self saveFrameUsingName:[self frameAutosaveName]];
496 - (void)someWindowWillClose:(NSNotification *)notification
498     id obj = [notification object];
500     // hasActiveVideo is defined for VLCVideoWindowCommon and subclasses
501     if ([obj respondsToSelector:@selector(hasActiveVideo)] && [obj hasActiveVideo]) {
502         if ([[VLCMain sharedInstance] activeVideoPlayback])
503             [[VLCCoreInteraction sharedInstance] stop];
504     }
507 - (void)someWindowWillMiniaturize:(NSNotification *)notification
509     if (config_GetInt("macosx-pause-minimized")) {
510         id obj = [notification object];
512         if ([obj class] == [VLCVideoWindowCommon class] || [obj class] == [VLCDetachedVideoWindow class] || ([obj class] == [VLCMainWindow class] && !self.nonembedded)) {
513             if ([[VLCMain sharedInstance] activeVideoPlayback])
514                 [[VLCCoreInteraction sharedInstance] pause];
515         }
516     }
519 #pragma mark -
520 #pragma mark Update interface and respond to foreign events
521 - (void)showDropZone
523     b_dropzone_active = YES;
524     [_dropzoneView setHidden:NO];
525     [_playlistScrollView setHidden:YES];
528 - (void)hideDropZone
530     b_dropzone_active = NO;
531     [_dropzoneView setHidden:YES];
532     [_playlistScrollView setHidden:NO];
535 - (void)hideSplitView:(BOOL)resize
537     if (resize) {
538         NSRect winrect = [self frame];
539         f_lastSplitViewHeight = [_splitView frame].size.height;
540         winrect.size.height = winrect.size.height - f_lastSplitViewHeight;
541         winrect.origin.y = winrect.origin.y + f_lastSplitViewHeight;
542         [self setFrame:winrect display:YES animate:YES];
543     }
545     [self setContentMinSize: NSMakeSize(604., [self.controlsBar height])];
546     [self setContentMaxSize: NSMakeSize(FLT_MAX, [self.controlsBar height])];
548     b_splitview_removed = YES;
551 - (void)showSplitView:(BOOL)resize
553     [self updateWindow];
554     [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
555     [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
557     if (resize) {
558         NSRect winrect;
559         winrect = [self frame];
560         winrect.size.height = winrect.size.height + f_lastSplitViewHeight;
561         winrect.origin.y = winrect.origin.y - f_lastSplitViewHeight;
562         [self setFrame:winrect display:YES animate:YES];
563     }
565     b_splitview_removed = NO;
568 - (void)updateTimeSlider
570     [self.controlsBar updateTimeSlider];
571     [self.fspanel updatePositionAndTime];
573     [[[VLCMain sharedInstance] voutProvider] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
574         [controlsBar updateTimeSlider];
575     }];
577     [[VLCCoreInteraction sharedInstance] updateAtoB];
580 - (void)updateName
582     input_thread_t *p_input;
583     p_input = pl_CurrentInput(getIntf());
584     if (p_input) {
585         NSString *aString = @"";
587         if (!config_GetPsz("video-title")) {
588             char *format = var_InheritString(getIntf(), "input-title-format");
589             if (format) {
590                 char *formated = vlc_strfinput(p_input, NULL, format);
591                 free(format);
592                 aString = toNSStr(formated);
593                 free(formated);
594             }
595         } else
596             aString = toNSStr(config_GetPsz("video-title"));
598         char *uri = input_item_GetURI(input_GetItem(p_input));
600         NSURL * o_url = [NSURL URLWithString:toNSStr(uri)];
601         if ([o_url isFileURL]) {
602             [self setRepresentedURL: o_url];
603             [[[VLCMain sharedInstance] voutProvider] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
604                 [o_window setRepresentedURL:o_url];
605             }];
606         } else {
607             [self setRepresentedURL: nil];
608             [[[VLCMain sharedInstance] voutProvider] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
609                 [o_window setRepresentedURL:nil];
610             }];
611         }
612         free(uri);
614         if ([aString isEqualToString:@""]) {
615             if ([o_url isFileURL])
616                 aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
617             else
618                 aString = [o_url absoluteString];
619         }
621         if ([aString length] > 0) {
622             [self setTitle: aString];
623             [[[VLCMain sharedInstance] voutProvider] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
624                 [o_window setTitle:aString];
625             }];
627             [self.fspanel setStreamTitle: aString];
628         } else {
629             [self setTitle: _NS("VLC media player")];
630             [self setRepresentedURL: nil];
631         }
633         vlc_object_release(p_input);
634     } else {
635         [self setTitle: _NS("VLC media player")];
636         [self setRepresentedURL: nil];
637     }
640 - (void)updateWindow
642     [self.controlsBar updateControls];
643     [[[VLCMain sharedInstance] voutProvider] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
644         [controlsBar updateControls];
645     }];
647     bool b_seekable = false;
649     playlist_t *p_playlist = pl_Get(getIntf());
650     input_thread_t *p_input = playlist_CurrentInput(p_playlist);
651     if (p_input) {
652         /* seekable streams */
653         b_seekable = var_GetBool(p_input, "can-seek");
655         vlc_object_release(p_input);
656     }
658     [self updateTimeSlider];
659     if ([self.fspanel respondsToSelector:@selector(setSeekable:)])
660         [self.fspanel setSeekable: b_seekable];
662     PL_LOCK;
663     if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
664         [[[[VLCMain sharedInstance] playlist] model] hasChildren])
665         [self hideDropZone];
666     else
667         [self showDropZone];
668     PL_UNLOCK;
669     [_sidebarView setNeedsDisplay:YES];
671     [self _updatePlaylistTitle];
674 - (void)setPause
676     [self.controlsBar setPause];
677     [self.fspanel setPause];
679     [[[VLCMain sharedInstance] voutProvider] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
680         [controlsBar setPause];
681     }];
684 - (void)setPlay
686     [self.controlsBar setPlay];
687     [self.fspanel setPlay];
689     [[[VLCMain sharedInstance] voutProvider] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
690         [controlsBar setPlay];
691     }];
694 - (void)updateVolumeSlider
696     [(VLCMainWindowControlsBar *)[self controlsBar] updateVolumeSlider];
697     [self.fspanel setVolumeLevel:[[VLCCoreInteraction sharedInstance] volume]];
700 #pragma mark -
701 #pragma mark Video Output handling
703 - (void)videoplayWillBeStarted
705     if (!self.fullscreen)
706         frameBeforePlayback = [self frame];
709 - (void)setVideoplayEnabled
711     BOOL b_videoPlayback = [[VLCMain sharedInstance] activeVideoPlayback];
713     if (!b_videoPlayback) {
714         if (!self.nonembedded && (!self.nativeFullscreenMode || (self.nativeFullscreenMode && !self.fullscreen)) && frameBeforePlayback.size.width > 0 && frameBeforePlayback.size.height > 0) {
716             // only resize back to minimum view of this is still desired final state
717             CGFloat f_threshold_height = f_min_video_height + [self.controlsBar height];
718             if(frameBeforePlayback.size.height > f_threshold_height || b_minimized_view) {
720                 if ([[VLCMain sharedInstance] isTerminating])
721                     [self setFrame:frameBeforePlayback display:YES];
722                 else
723                     [[self animator] setFrame:frameBeforePlayback display:YES];
725             }
726         }
728         frameBeforePlayback = NSMakeRect(0, 0, 0, 0);
730         // update fs button to reflect state for next startup
731         if (var_InheritBool(getIntf(), "fullscreen") || var_GetBool(pl_Get(getIntf()), "fullscreen")) {
732             [self.controlsBar setFullscreenState:YES];
733         }
735         [self makeFirstResponder: _playlistScrollView];
736         [[[VLCMain sharedInstance] voutProvider] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
738         // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
739         [self setAlphaValue:1.0];
740     }
742     if (self.nativeFullscreenMode) {
743         if ([self hasActiveVideo] && [self fullscreen] && b_videoPlayback) {
744             [self hideControlsBar];
745             [self.fspanel setActive];
746         } else {
747             [self showControlsBar];
748             [self.fspanel setNonActive];
749         }
750     }
753 #pragma mark -
754 #pragma mark Fullscreen support
756 - (void)showFullscreenController
758     id currentWindow = [NSApp keyWindow];
759     if ([currentWindow respondsToSelector:@selector(hasActiveVideo)] && [currentWindow hasActiveVideo]) {
760         if ([currentWindow respondsToSelector:@selector(fullscreen)] && [currentWindow fullscreen] && ![[currentWindow videoView] isHidden]) {
762             if ([[VLCMain sharedInstance] activeVideoPlayback])
763                 [self.fspanel fadeIn];
764         }
765     }
769 #pragma mark -
770 #pragma mark split view delegate
771 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
773     if (dividerIndex == 0)
774         return 300.;
775     else
776         return proposedMax;
779 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
781     if (dividerIndex == 0)
782         return 100.;
783     else
784         return proposedMin;
787 - (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
789     return ([subview isEqual:_splitViewLeft]);
792 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
794     return (![subview isEqual:_splitViewLeft]);
797 - (void)mainSplitViewDidResizeSubviews:(id)object
799     f_lastLeftSplitViewWidth = [_splitViewLeft frame].size.width;
800     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem: ![_splitView isSubviewCollapsed:_splitViewLeft]];
803 - (void)toggleLeftSubSplitView
805     [_splitView adjustSubviews];
806     if ([_splitView isSubviewCollapsed:_splitViewLeft])
807         [_splitView setPosition:f_lastLeftSplitViewWidth ofDividerAtIndex:0];
808     else
809         [_splitView setPosition:[_splitView minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
811     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem: ![_splitView isSubviewCollapsed:_splitViewLeft]];
814 #pragma mark -
815 #pragma mark private playlist magic
816 - (void)_updatePlaylistTitle
818     PLRootType root = [[[[VLCMain sharedInstance] playlist] model] currentRootType];
819     playlist_t *p_playlist = pl_Get(getIntf());
821     PL_LOCK;
822     if (root == ROOT_TYPE_PLAYLIST)
823         [_categoryLabel setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_playing]]];
825     PL_UNLOCK;
828 - (NSString *)_playbackDurationOfNode:(playlist_item_t*)node
830     if (!node)
831         return @"";
833     playlist_t * p_playlist = pl_Get(getIntf());
834     PL_ASSERT_LOCKED;
836     vlc_tick_t mt_duration = playlist_GetNodeDuration( node );
838     if (mt_duration < 1)
839         return @"";
841     NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
842     formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated;
844     NSString* outputString = [formatter stringFromTimeInterval:SEC_FROM_VLC_TICK(mt_duration)];
846     return [NSString stringWithFormat:@" — %@", outputString];
849 - (IBAction)searchItem:(id)sender
851     [[[[VLCMain sharedInstance] playlist] model] searchUpdate:[_searchField stringValue]];
854 - (IBAction)highlightSearchField:(id)sender
856     [_searchField selectText:sender];
859 #pragma mark -
860 #pragma mark Side Bar Data handling
861 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
862 - (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
864     //Works the same way as the NSOutlineView data source: `nil` means a parent item
865     if (item==nil)
866         return [o_sidebaritems count];
867     else
868         return [[item children] count];
872 - (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
874     //Works the same way as the NSOutlineView data source: `nil` means a parent item
875     if (item==nil)
876         return [o_sidebaritems objectAtIndex:index];
877     else
878         return [[item children] objectAtIndex:index];
882 - (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
884     return [item title];
887 - (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
889     [item setTitle:object];
892 - (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
894     return [item hasChildren];
898 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
900     if ([[item identifier] isEqualToString: @"playlist"])
901         return YES;
903     return [item hasBadge];
907 - (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item
909     playlist_t * p_playlist = pl_Get(getIntf());
910     NSInteger i_playlist_size = 0;
912     if ([[item identifier] isEqualToString: @"playlist"]) {
913         PL_LOCK;
914         i_playlist_size = p_playlist->p_playing->i_children;
915         PL_UNLOCK;
917         return i_playlist_size;
918     }
920     return [item badgeValue];
924 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
926     return [item hasIcon];
930 - (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
932     return [item icon];
935 - (NSMenu*)sourceList:(PXSourceList*)aSourceList menuForEvent:(NSEvent*)theEvent item:(id)item
937     if ([theEvent type] == NSRightMouseDown || ([theEvent type] == NSLeftMouseDown && ([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)) {
938         if (item != nil) {
939             if ([item sdtype] > 0)
940             {
941                 NSMenu *m = [[NSMenu alloc] init];
942                 playlist_t * p_playlist = pl_Get(getIntf());
943                 BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
944                 if (!sd_loaded)
945                     [m addItemWithTitle:_NS("Enable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
946                 else
947                     [m addItemWithTitle:_NS("Disable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
948                 [[m itemAtIndex:0] setRepresentedObject: [item identifier]];
949                 return m;
950             }
951         }
952     }
954     return nil;
957 - (IBAction)sdmenuhandler:(id)sender
959     NSString * identifier = [sender representedObject];
960     if ([identifier length] > 0 && ![identifier isEqualToString:@"lua{sd='freebox',longname='Freebox TV'}"]) {
961         playlist_t * p_playlist = pl_Get(getIntf());
962         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [identifier UTF8String]);
964         if (!sd_loaded)
965             playlist_ServicesDiscoveryAdd(p_playlist, [identifier UTF8String]);
966         else
967             playlist_ServicesDiscoveryRemove(p_playlist, [identifier UTF8String]);
968     }
971 #pragma mark -
972 #pragma mark Side Bar Delegate Methods
973 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
974 - (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group
976     if ([[group identifier] isEqualToString:@"library"])
977         return YES;
979     return NO;
982 - (void)sourceListSelectionDidChange:(NSNotification *)notification
984     playlist_t * p_playlist = pl_Get(getIntf());
986     NSIndexSet *selectedIndexes = [_sidebarView selectedRowIndexes];
987     id item = [_sidebarView itemAtRow:[selectedIndexes firstIndex]];
989     //Set the label text to represent the new selection
990     if ([item sdtype] > -1 && [[item identifier] length] > 0) {
991         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
992         if (!sd_loaded)
993             playlist_ServicesDiscoveryAdd(p_playlist, [[item identifier] UTF8String]);
994     }
996     [_categoryLabel setStringValue:[item title]];
998     if ([[item identifier] isEqualToString:@"playlist"]) {
999         PL_LOCK;
1000         [[[[VLCMain sharedInstance] playlist] model] changeRootItem:p_playlist->p_playing];
1001         PL_UNLOCK;
1003         [self _updatePlaylistTitle];
1005     } else {
1006         PL_LOCK;
1007         const char *title = [[item title] UTF8String];
1008         playlist_item_t *pl_item = playlist_ChildSearchName(&p_playlist->root, title);
1009         if (pl_item)
1010             [[[[VLCMain sharedInstance] playlist] model] changeRootItem:pl_item];
1011         else
1012             msg_Err(getIntf(), "Could not find playlist entry with name %s", title);
1014         PL_UNLOCK;
1015     }
1017     // Note the order: first hide the podcast controls, then show the drop zone
1018     if ([[item identifier] isEqualToString:@"podcast"])
1019         [self showPodcastControls];
1020     else
1021         [self hidePodcastControls];
1023     PL_LOCK;
1024     if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
1025         [[[[VLCMain sharedInstance] playlist] model] hasChildren])
1026         [self hideDropZone];
1027     else
1028         [self showDropZone];
1029     PL_UNLOCK;
1031     [[NSNotificationCenter defaultCenter] postNotificationName: VLCMediaKeySupportSettingChangedNotification
1032                                                         object: nil
1033                                                       userInfo: nil];
1036 - (NSDragOperation)sourceList:(PXSourceList *)aSourceList validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1038     if ([[item identifier] isEqualToString:@"playlist"]) {
1039         NSPasteboard *o_pasteboard = [info draggingPasteboard];
1040         if ([[o_pasteboard types] containsObject: VLCPLItemPasteboadType] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
1041             return NSDragOperationGeneric;
1042     }
1043     return NSDragOperationNone;
1046 - (BOOL)sourceList:(PXSourceList *)aSourceList acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1048     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1050     playlist_t * p_playlist = pl_Get(getIntf());
1051     playlist_item_t *p_node;
1053     if ([[item identifier] isEqualToString:@"playlist"])
1054         p_node = p_playlist->p_playing;
1056     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1057         NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
1059         NSUInteger count = [array count];
1061         PL_LOCK;
1062         for(NSUInteger i = 0; i < count; i++) {
1063             playlist_item_t *p_item = playlist_ItemGetById(p_playlist, [[array objectAtIndex:i] plItemId]);
1064             if (!p_item) continue;
1065             playlist_NodeAddCopy(p_playlist, p_item, p_node, PLAYLIST_END);
1066         }
1067         PL_UNLOCK;
1069         return YES;
1070     }
1072     // check if dropped item is a file
1073     NSArray *items = [[[VLCMain sharedInstance] playlist] createItemsFromExternalPasteboard:o_pasteboard];
1074     if (items.count == 0)
1075         return NO;
1077     [[[VLCMain sharedInstance] playlist] addPlaylistItems:items
1078                                          withParentItemId:p_node->i_id
1079                                                     atPos:-1
1080                                             startPlayback:NO];
1081     return YES;
1084 - (id)sourceList:(PXSourceList *)aSourceList persistentObjectForItem:(id)item
1086     return [item identifier];
1089 - (id)sourceList:(PXSourceList *)aSourceList itemForPersistentObject:(id)object
1091     /* the following code assumes for sakes of simplicity that only the top level
1092      * items are allowed to have children */
1094     NSArray * array = [NSArray arrayWithArray: o_sidebaritems]; // read-only arrays are noticebly faster
1095     NSUInteger count = [array count];
1096     if (count < 1)
1097         return nil;
1099     for (NSUInteger x = 0; x < count; x++) {
1100         id item = [array objectAtIndex:x]; // save one objc selector call
1101         if ([[item identifier] isEqualToString:object])
1102             return item;
1103     }
1105     return nil;
1108 #pragma mark -
1109 #pragma mark Podcast
1111 - (IBAction)addPodcast:(id)sender
1113     [NSApp beginSheet:_podcastSubscribeWindow modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1116 - (IBAction)addPodcastWindowAction:(id)sender
1118     [_podcastSubscribeWindow orderOut:sender];
1119     [NSApp endSheet:_podcastSubscribeWindow];
1121     if (sender == _podcastSubscribeOkButton && [[_podcastSubscribeUrlField stringValue] length] > 0) {
1122         NSMutableString *podcastConf = [[NSMutableString alloc] init];
1123         if (config_GetPsz("podcast-urls") != NULL)
1124             [podcastConf appendFormat:@"%s|", config_GetPsz("podcast-urls")];
1126         [podcastConf appendString: [_podcastSubscribeUrlField stringValue]];
1127         config_PutPsz("podcast-urls", [podcastConf UTF8String]);
1128         var_SetString(pl_Get(getIntf()), "podcast-urls", [podcastConf UTF8String]);
1129     }
1132 - (IBAction)removePodcast:(id)sender
1134     char *psz_urls = var_InheritString(pl_Get(getIntf()), "podcast-urls");
1135     if (psz_urls != NULL) {
1136         [_podcastUnsubscribePopUpButton removeAllItems];
1137         [_podcastUnsubscribePopUpButton addItemsWithTitles:[toNSStr(psz_urls) componentsSeparatedByString:@"|"]];
1138         [NSApp beginSheet:_podcastUnsubscribeWindow modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1139     }
1140     free(psz_urls);
1143 - (IBAction)removePodcastWindowAction:(id)sender
1145     [_podcastUnsubscribeWindow orderOut:sender];
1146     [NSApp endSheet:_podcastUnsubscribeWindow];
1148     if (sender == _podcastUnsubscribeOkButton) {
1149         playlist_t * p_playlist = pl_Get(getIntf());
1150         char *psz_urls = var_InheritString(p_playlist, "podcast-urls");
1152         NSMutableArray * urls = [[NSMutableArray alloc] initWithArray:[toNSStr(config_GetPsz("podcast-urls")) componentsSeparatedByString:@"|"]];
1153         [urls removeObjectAtIndex: [_podcastUnsubscribePopUpButton indexOfSelectedItem]];
1154         const char *psz_new_urls = [[urls componentsJoinedByString:@"|"] UTF8String];
1155         var_SetString(pl_Get(getIntf()), "podcast-urls", psz_new_urls);
1156         config_PutPsz("podcast-urls", psz_new_urls);
1158         free(psz_urls);
1160         /* update playlist table */
1161         if (playlist_IsServicesDiscoveryLoaded(p_playlist, "podcast")) {
1162             [[[VLCMain sharedInstance] playlist] playlistUpdated];
1163         }
1164     }
1167 - (void)showPodcastControls
1169     _tableViewToPodcastConstraint.priority = 999;
1170     _podcastView.hidden = NO;
1172     b_podcastView_displayed = YES;
1175 - (void)hidePodcastControls
1177     if (b_podcastView_displayed) {
1178         _tableViewToPodcastConstraint.priority = 1;
1179         _podcastView.hidden = YES;
1181         b_podcastView_displayed = NO;
1182     }
1185 @end
1187 @interface VLCDetachedVideoWindow ()
1188 @end
1190 @implementation VLCDetachedVideoWindow
1192 - (void)awakeFromNib
1194     // sets lion fullscreen behaviour
1195     [super awakeFromNib];
1196     [self setAcceptsMouseMovedEvents: YES];
1198     [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height])];
1201 @end