1 /*****************************************************************************
2 * VLCMainWindow.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2002-2013 VLC authors and VideoLAN
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>
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.
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.
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"
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"
39 #import <vlc_playlist.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;
57 BOOL splitViewRemoved;
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;
82 static const float f_min_window_height = 307.;
84 @implementation VLCMainWindow
87 #pragma mark Initialization
89 - (BOOL)isEvent:(NSEvent *)o_event forKey:(const char *)keyString
94 key = config_GetPsz(keyString);
95 o_key = [NSString stringWithFormat:@"%s", 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);
111 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
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])
130 VLCCoreInteraction *coreInteraction = [VLCCoreInteraction sharedInstance];
131 return [coreInteraction hasDefinedShortcutKey:o_event force:b_force] ||
132 [coreInteraction keyEvent:o_event];
137 [[NSNotificationCenter defaultCenter] removeObserver: self];
142 [super awakeFromNib];
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;
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];
174 * Set up translatable strings for the UI elements
178 [self setTitle:_NS("VLC media player")];
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];
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];
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);
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;
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;
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]];
268 #pragma mark appearance management
270 - (void)reloadSidebar
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);
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];
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];
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];
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")];
325 [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
326 [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
329 msg_Warn(getIntf(), "unknown SD type found, skipping (%s)", *ppsz_name);
334 free(*ppsz_longname);
336 [mycompItem setChildren: [NSArray arrayWithArray: mycompItems]];
337 [devicesItem setChildren: [NSArray arrayWithArray: devicesItems]];
338 [lanItem setChildren: [NSArray arrayWithArray: lanItems]];
339 [internetItem setChildren: [NSArray arrayWithArray: internetItems]];
341 free(ppsz_longnames);
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];
367 [_sidebarView expandItem:nil expandChildren:YES];
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];
386 [self.videoView setHidden:YES];
387 [_splitView setHidden:NO];
388 if (self.nativeFullscreenMode && [self fullscreen]) {
389 [self showControlsBar];
390 [self.fspanel setNonActive];
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];
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];
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)
430 // eUserMenuEvent is now handled same as eUserEvent
431 if(event == psUserMenuEvent)
434 if (b_dropzone_active && b_have_alt_key) {
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;
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;
455 b_minimized_view = NO;
461 if (!self.nonembedded) {
462 if (([self.videoView isHidden] && b_activeVideo) || b_restored || (b_activeVideo && event != psUserEvent))
463 [self makeSplitViewHidden];
465 [self makeSplitViewVisible];
467 [_splitView setHidden: NO];
468 [_playlistScrollView setHidden: NO];
469 [self.videoView setHidden: YES];
470 [self showControlsBar];
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];
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];
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];
520 #pragma mark Update interface and respond to foreign events
523 b_dropzone_active = YES;
524 [_dropzoneView setHidden:NO];
525 [_playlistScrollView setHidden:YES];
530 b_dropzone_active = NO;
531 [_dropzoneView setHidden:YES];
532 [_playlistScrollView setHidden:NO];
535 - (void)hideSplitView:(BOOL)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];
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
554 [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
555 [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
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];
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];
577 [[VLCCoreInteraction sharedInstance] updateAtoB];
582 input_thread_t *p_input;
583 p_input = pl_CurrentInput(getIntf());
585 NSString *aString = @"";
587 if (!config_GetPsz("video-title")) {
588 char *format = var_InheritString(getIntf(), "input-title-format");
590 char *formated = vlc_strfinput(p_input, NULL, format);
592 aString = toNSStr(formated);
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];
607 [self setRepresentedURL: nil];
608 [[[VLCMain sharedInstance] voutProvider] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
609 [o_window setRepresentedURL:nil];
614 if ([aString isEqualToString:@""]) {
615 if ([o_url isFileURL])
616 aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
618 aString = [o_url absoluteString];
621 if ([aString length] > 0) {
622 [self setTitle: aString];
623 [[[VLCMain sharedInstance] voutProvider] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
624 [o_window setTitle:aString];
627 [self.fspanel setStreamTitle: aString];
629 [self setTitle: _NS("VLC media player")];
630 [self setRepresentedURL: nil];
633 vlc_object_release(p_input);
635 [self setTitle: _NS("VLC media player")];
636 [self setRepresentedURL: nil];
642 [self.controlsBar updateControls];
643 [[[VLCMain sharedInstance] voutProvider] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
644 [controlsBar updateControls];
647 bool b_seekable = false;
649 playlist_t *p_playlist = pl_Get(getIntf());
650 input_thread_t *p_input = playlist_CurrentInput(p_playlist);
652 /* seekable streams */
653 b_seekable = var_GetBool(p_input, "can-seek");
655 vlc_object_release(p_input);
658 [self updateTimeSlider];
659 if ([self.fspanel respondsToSelector:@selector(setSeekable:)])
660 [self.fspanel setSeekable: b_seekable];
663 if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
664 [[[[VLCMain sharedInstance] playlist] model] hasChildren])
669 [_sidebarView setNeedsDisplay:YES];
671 [self _updatePlaylistTitle];
676 [self.controlsBar setPause];
677 [self.fspanel setPause];
679 [[[VLCMain sharedInstance] voutProvider] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
680 [controlsBar setPause];
686 [self.controlsBar setPlay];
687 [self.fspanel setPlay];
689 [[[VLCMain sharedInstance] voutProvider] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
690 [controlsBar setPlay];
694 - (void)updateVolumeSlider
696 [(VLCMainWindowControlsBar *)[self controlsBar] updateVolumeSlider];
697 [self.fspanel setVolumeLevel:[[VLCCoreInteraction sharedInstance] volume]];
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];
723 [[self animator] setFrame:frameBeforePlayback display:YES];
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];
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];
742 if (self.nativeFullscreenMode) {
743 if ([self hasActiveVideo] && [self fullscreen] && b_videoPlayback) {
744 [self hideControlsBar];
745 [self.fspanel setActive];
747 [self showControlsBar];
748 [self.fspanel setNonActive];
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];
770 #pragma mark split view delegate
771 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
773 if (dividerIndex == 0)
779 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
781 if (dividerIndex == 0)
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];
809 [_splitView setPosition:[_splitView minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
811 [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem: ![_splitView isSubviewCollapsed:_splitViewLeft]];
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());
822 if (root == ROOT_TYPE_PLAYLIST)
823 [_categoryLabel setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_playing]]];
828 - (NSString *)_playbackDurationOfNode:(playlist_item_t*)node
833 playlist_t * p_playlist = pl_Get(getIntf());
836 vlc_tick_t mt_duration = playlist_GetNodeDuration( node );
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];
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
866 return [o_sidebaritems count];
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
876 return [o_sidebaritems objectAtIndex:index];
878 return [[item children] objectAtIndex:index];
882 - (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
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"])
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"]) {
914 i_playlist_size = p_playlist->p_playing->i_children;
917 return i_playlist_size;
920 return [item badgeValue];
924 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
926 return [item hasIcon];
930 - (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
935 - (NSMenu*)sourceList:(PXSourceList*)aSourceList menuForEvent:(NSEvent*)theEvent item:(id)item
937 if ([theEvent type] == NSRightMouseDown || ([theEvent type] == NSLeftMouseDown && ([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)) {
939 if ([item sdtype] > 0)
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]);
945 [m addItemWithTitle:_NS("Enable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
947 [m addItemWithTitle:_NS("Disable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
948 [[m itemAtIndex:0] setRepresentedObject: [item identifier]];
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]);
965 playlist_ServicesDiscoveryAdd(p_playlist, [identifier UTF8String]);
967 playlist_ServicesDiscoveryRemove(p_playlist, [identifier UTF8String]);
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"])
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]);
993 playlist_ServicesDiscoveryAdd(p_playlist, [[item identifier] UTF8String]);
996 [_categoryLabel setStringValue:[item title]];
998 if ([[item identifier] isEqualToString:@"playlist"]) {
1000 [[[[VLCMain sharedInstance] playlist] model] changeRootItem:p_playlist->p_playing];
1003 [self _updatePlaylistTitle];
1007 const char *title = [[item title] UTF8String];
1008 playlist_item_t *pl_item = playlist_ChildSearchName(&p_playlist->root, title);
1010 [[[[VLCMain sharedInstance] playlist] model] changeRootItem:pl_item];
1012 msg_Err(getIntf(), "Could not find playlist entry with name %s", title);
1017 // Note the order: first hide the podcast controls, then show the drop zone
1018 if ([[item identifier] isEqualToString:@"podcast"])
1019 [self showPodcastControls];
1021 [self hidePodcastControls];
1024 if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
1025 [[[[VLCMain sharedInstance] playlist] model] hasChildren])
1026 [self hideDropZone];
1028 [self showDropZone];
1031 [[NSNotificationCenter defaultCenter] postNotificationName: VLCMediaKeySupportSettingChangedNotification
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;
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];
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);
1072 // check if dropped item is a file
1073 NSArray *items = [[[VLCMain sharedInstance] playlist] createItemsFromExternalPasteboard:o_pasteboard];
1074 if (items.count == 0)
1077 [[[VLCMain sharedInstance] playlist] addPlaylistItems:items
1078 withParentItemId:p_node->i_id
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];
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])
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]);
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];
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);
1160 /* update playlist table */
1161 if (playlist_IsServicesDiscoveryLoaded(p_playlist, "podcast")) {
1162 [[[VLCMain sharedInstance] playlist] playlistUpdated];
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;
1187 @interface VLCDetachedVideoWindow ()
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])];