1 /*****************************************************************************
2 * MainWindow.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 *****************************************************************************/
29 #import "CompatibilityFixes.h"
30 #import "MainWindow.h"
31 #import "CoreInteraction.h"
32 #import "AudioEffects.h"
36 #import "SideBarItem.h"
38 #import <vlc_playlist.h>
40 #import <vlc_strings.h>
41 #import <vlc_services_discovery.h>
44 #import "ControlsBar.h"
46 #import "VLCVoutWindowController.h"
49 @interface VLCMainWindow() <PXSourceListDataSource, PXSourceListDelegate, NSWindowDelegate, NSAnimationDelegate, NSSplitViewDelegate>
51 BOOL b_video_playback_enabled;
52 BOOL b_dropzone_active;
53 BOOL b_splitview_removed;
54 BOOL b_minimized_view;
56 CGFloat f_lastSplitViewHeight;
57 CGFloat f_lastLeftSplitViewWidth;
59 NSMutableArray *o_sidebaritems;
61 /* this is only true, when we have NO video playing inside the main window */
63 BOOL b_podcastView_displayed;
65 VLCColorView * o_color_backdrop;
67 NSRect frameBeforePlayback;
69 - (void)resizePlaylistAfterCollapse;
70 - (void)makeSplitViewVisible;
71 - (void)makeSplitViewHidden;
72 - (void)showPodcastControls;
73 - (void)hidePodcastControls;
76 static const float f_min_window_height = 307.;
78 @implementation VLCMainWindow
81 #pragma mark Initialization
83 - (BOOL)isEvent:(NSEvent *)o_event forKey:(const char *)keyString
88 key = config_GetPsz(VLCIntf, keyString);
89 o_key = [NSString stringWithFormat:@"%s", key];
92 unsigned int i_keyModifiers = [[VLCStringUtility sharedInstance] VLCModifiersToCocoa:o_key];
94 NSString * characters = [o_event charactersIgnoringModifiers];
95 if ([characters length] > 0) {
96 return [[characters lowercaseString] isEqualToString: [[VLCStringUtility sharedInstance] VLCKeyToString: o_key]] &&
97 (i_keyModifiers & NSShiftKeyMask) == ([o_event modifierFlags] & NSShiftKeyMask) &&
98 (i_keyModifiers & NSControlKeyMask) == ([o_event modifierFlags] & NSControlKeyMask) &&
99 (i_keyModifiers & NSAlternateKeyMask) == ([o_event modifierFlags] & NSAlternateKeyMask) &&
100 (i_keyModifiers & NSCommandKeyMask) == ([o_event modifierFlags] & NSCommandKeyMask);
105 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
108 // these are key events which should be handled by vlc core, but are attached to a main menu item
109 if (![self isEvent: o_event forKey: "key-vol-up"] &&
110 ![self isEvent: o_event forKey: "key-vol-down"] &&
111 ![self isEvent: o_event forKey: "key-vol-mute"] &&
112 ![self isEvent: o_event forKey: "key-prev"] &&
113 ![self isEvent: o_event forKey: "key-next"] &&
114 ![self isEvent: o_event forKey: "key-jump+short"] &&
115 ![self isEvent: o_event forKey: "key-jump-short"]) {
116 /* We indeed want to prioritize some Cocoa key equivalent against libvlc,
117 so we perform the menu equivalent now. */
118 if ([[NSApp mainMenu] performKeyEquivalent:o_event])
124 VLCCoreInteraction *coreInteraction = [VLCCoreInteraction sharedInstance];
125 return [coreInteraction hasDefinedShortcutKey:o_event force:b_force] ||
126 [coreInteraction keyEvent:o_event];
131 [[NSNotificationCenter defaultCenter] removeObserver: self];
136 // sets lion fullscreen behaviour
137 [super awakeFromNib];
139 BOOL b_splitviewShouldBeHidden = NO;
141 [self setRestorable: NO];
142 [self setFrameAutosaveName:@"mainwindow"];
144 /* setup the styled interface */
145 _nativeFullscreenMode = var_InheritBool(VLCIntf, "macosx-nativefullscreenmode");
146 [self useOptimizedDrawing: YES];
148 [[o_search_fld cell] setPlaceholderString: _NS("Search")];
149 [[o_search_fld cell] accessibilitySetOverrideValue:_NS("Enter a term to search the playlist. Results will be selected in the table.") forAttribute:NSAccessibilityDescriptionAttribute];
150 [o_search_fld setToolTip: _NS("Search in Playlist")];
152 VLCPlaylist *playlist = [[VLCMain sharedInstance] playlist];
153 playlist.outlineView = o_outline_view;
154 playlist.playlistHeaderView = [o_outline_view headerView];
156 [o_dropzone_btn setTitle: _NS("Open media...")];
157 [[o_dropzone_btn cell] accessibilitySetOverrideValue:_NS("Click to open an advanced dialog to select the media to play. You can also drop files here to play.") forAttribute:NSAccessibilityDescriptionAttribute];
158 [o_dropzone_lbl setStringValue: _NS("Drop media here")];
159 [o_dropzone_img setImage: imageFromRes(@"dropzone")];
161 [o_podcast_add_btn setTitle: _NS("Subscribe")];
162 [o_podcast_remove_btn setTitle: _NS("Unsubscribe")];
163 [o_podcast_subscribe_title_lbl setStringValue: _NS("Subscribe to a podcast")];
164 [o_podcast_subscribe_subtitle_lbl setStringValue: _NS("Enter URL of the podcast to subscribe to:")];
165 [o_podcast_subscribe_cancel_btn setTitle: _NS("Cancel")];
166 [o_podcast_subscribe_ok_btn setTitle: _NS("Subscribe")];
167 [o_podcast_unsubscribe_title_lbl setStringValue: _NS("Unsubscribe from a podcast")];
168 [o_podcast_unsubscribe_subtitle_lbl setStringValue: _NS("Select the podcast you would like to unsubscribe from:")];
169 [o_podcast_unsubscribe_ok_btn setTitle: _NS("Unsubscribe")];
170 [o_podcast_unsubscribe_cancel_btn setTitle: _NS("Cancel")];
172 /* interface builder action */
173 CGFloat f_threshold_height = f_min_video_height + [self.controlsBar height];
174 if (self.darkInterface)
175 f_threshold_height += [self.titlebarView frame].size.height;
176 if ([[self contentView] frame].size.height < f_threshold_height)
177 b_splitviewShouldBeHidden = YES;
179 [self setDelegate: self];
180 [self setExcludedFromWindowsMenu: YES];
181 [self setAcceptsMouseMovedEvents: YES];
182 // Set that here as IB seems to be buggy
183 if (self.darkInterface)
184 [self setContentMinSize:NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
186 [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
188 [self setTitle: _NS("VLC media player")];
190 b_dropzone_active = YES;
191 [o_dropzone_view setFrame: [o_playlist_table frame]];
192 [o_left_split_view setFrame: [o_sidebar_view frame]];
194 /* the default small size of the search field is slightly different on Lion, let's work-around that */
196 frame = [o_search_fld frame];
197 frame.origin.y = frame.origin.y + 2.0;
198 frame.size.height = frame.size.height - 1.0;
199 [o_search_fld setFrame: frame];
201 /* reload the sidebar */
202 [self reloadSidebar];
204 _fspanel = [[VLCFSPanel alloc] initWithContentRect:NSMakeRect(110.,267.,549.,87.)
205 styleMask:NSTexturedBackgroundWindowMask
206 backing:NSBackingStoreBuffered
209 /* make sure we display the desired default appearance when VLC launches for the first time */
210 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
211 if (![defaults objectForKey:@"VLCFirstRun"]) {
212 [defaults setObject:[NSDate date] forKey:@"VLCFirstRun"];
214 [o_sidebar_view expandItem:nil expandChildren:YES];
217 NSAlert *albumArtAlert = [NSAlert alertWithMessageText:_NS("Check for album art and metadata?") defaultButton:_NS("Enable Metadata Retrieval") alternateButton:_NS("No, Thanks") otherButton:nil informativeTextWithFormat:@"%@",_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.")];
218 NSInteger returnValue = [albumArtAlert runModal];
219 config_PutInt(VLCIntf, "metadata-network-access", returnValue == NSAlertDefaultReturn);
222 // select playlist item by default
223 [o_sidebar_view selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
225 NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
226 if (self.darkInterface) {
227 [defaultCenter addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidResizeNotification object: nil];
228 [defaultCenter addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidMoveNotification object: nil];
230 [self setBackgroundColor: [NSColor clearColor]];
231 [self setOpaque: NO];
233 [self setHasShadow:NO];
234 [self setHasShadow:YES];
236 NSRect winrect = [self frame];
237 CGFloat f_titleBarHeight = [self.titlebarView frame].size.height;
239 [self.titlebarView setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
240 winrect.size.width, f_titleBarHeight)];
241 [[self contentView] addSubview: self.titlebarView positioned: NSWindowAbove relativeTo: o_split_view];
243 if (winrect.size.height > 100) {
244 [self setFrame: winrect display:YES animate:YES];
245 self.previousSavedFrame = winrect;
248 winrect = [o_split_view frame];
249 winrect.size.height = winrect.size.height - f_titleBarHeight;
250 [o_split_view setFrame: winrect];
251 [self.videoView setFrame: winrect];
253 o_color_backdrop = [[VLCColorView alloc] initWithFrame: [o_split_view frame]];
254 [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: o_split_view];
255 [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
257 [self.videoView setFrame: [o_split_view frame]];
258 [o_playlist_table setBorderType: NSNoBorder];
259 [o_sidebar_scrollview setBorderType: NSNoBorder];
262 [defaultCenter addObserver: self selector: @selector(someWindowWillClose:) name: NSWindowWillCloseNotification object: nil];
263 [defaultCenter addObserver: self selector: @selector(someWindowWillMiniaturize:) name: NSWindowWillMiniaturizeNotification object:nil];
264 [defaultCenter addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
265 [defaultCenter addObserver: self selector: @selector(mainSplitViewDidResizeSubviews:) name: NSSplitViewDidResizeSubviewsNotification object:o_split_view];
267 if (b_splitviewShouldBeHidden) {
268 [self hideSplitView: YES];
269 f_lastSplitViewHeight = 300;
272 /* sanity check for the window size */
273 frame = [self frame];
274 NSSize screenSize = [[self screen] frame].size;
275 if (screenSize.width <= frame.size.width || screenSize.height <= frame.size.height) {
276 self.nativeVideoSize = screenSize;
280 /* update fs button to reflect state for next startup */
281 if (var_InheritBool(pl_Get(VLCIntf), "fullscreen"))
282 [self.controlsBar setFullscreenState:YES];
284 /* restore split view */
285 f_lastLeftSplitViewWidth = 200;
286 /* trick NSSplitView implementation, which pretends to know better than us */
287 if (!config_GetInt(VLCIntf, "macosx-show-sidebar"))
288 [self performSelector:@selector(toggleLeftSubSplitView) withObject:nil afterDelay:0.05];
292 #pragma mark appearance management
294 - (void)reloadSidebar
300 o_sidebaritems = [[NSMutableArray alloc] init];
301 SideBarItem *libraryItem = [SideBarItem itemWithTitle:_NS("LIBRARY") identifier:@"library"];
302 SideBarItem *playlistItem = [SideBarItem itemWithTitle:_NS("Playlist") identifier:@"playlist"];
303 [playlistItem setIcon: imageFromRes(@"sidebar-playlist")];
304 SideBarItem *medialibraryItem = [SideBarItem itemWithTitle:_NS("Media Library") identifier:@"medialibrary"];
305 [medialibraryItem setIcon: imageFromRes(@"sidebar-playlist")];
306 SideBarItem *mycompItem = [SideBarItem itemWithTitle:_NS("MY COMPUTER") identifier:@"mycomputer"];
307 SideBarItem *devicesItem = [SideBarItem itemWithTitle:_NS("DEVICES") identifier:@"devices"];
308 SideBarItem *lanItem = [SideBarItem itemWithTitle:_NS("LOCAL NETWORK") identifier:@"localnetwork"];
309 SideBarItem *internetItem = [SideBarItem itemWithTitle:_NS("INTERNET") identifier:@"internet"];
311 /* SD subnodes, inspired by the Qt4 intf */
312 char **ppsz_longnames = NULL;
313 int *p_categories = NULL;
314 char **ppsz_names = vlc_sd_GetNames(pl_Get(VLCIntf), &ppsz_longnames, &p_categories);
316 msg_Err(VLCIntf, "no sd item found"); //TODO
317 char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
318 int *p_category = p_categories;
319 NSMutableArray *internetItems = [[NSMutableArray alloc] init];
320 NSMutableArray *devicesItems = [[NSMutableArray alloc] init];
321 NSMutableArray *lanItems = [[NSMutableArray alloc] init];
322 NSMutableArray *mycompItems = [[NSMutableArray alloc] init];
323 NSString *o_identifier;
324 for (; ppsz_name && *ppsz_name; ppsz_name++, ppsz_longname++, p_category++) {
325 o_identifier = toNSStr(*ppsz_name);
326 switch (*p_category) {
327 case SD_CAT_INTERNET:
328 [internetItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
329 [[internetItems lastObject] setIcon: imageFromRes(@"sidebar-podcast")];
330 [[internetItems lastObject] setSdtype: SD_CAT_INTERNET];
331 [[internetItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
334 [devicesItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
335 [[devicesItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
336 [[devicesItems lastObject] setSdtype: SD_CAT_DEVICES];
337 [[devicesItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
340 [lanItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
341 [[lanItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
342 [[lanItems lastObject] setSdtype: SD_CAT_LAN];
343 [[lanItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
345 case SD_CAT_MYCOMPUTER:
346 [mycompItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
347 if (!strncmp(*ppsz_name, "video_dir", 9))
348 [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-movie")];
349 else if (!strncmp(*ppsz_name, "audio_dir", 9))
350 [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-music")];
351 else if (!strncmp(*ppsz_name, "picture_dir", 11))
352 [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-pictures")];
354 [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
355 [[mycompItems lastObject] setUntranslatedTitle: toNSStr(*ppsz_longname)];
356 [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
359 msg_Warn(VLCIntf, "unknown SD type found, skipping (%s)", *ppsz_name);
364 free(*ppsz_longname);
366 [mycompItem setChildren: [NSArray arrayWithArray: mycompItems]];
367 [devicesItem setChildren: [NSArray arrayWithArray: devicesItems]];
368 [lanItem setChildren: [NSArray arrayWithArray: lanItems]];
369 [internetItem setChildren: [NSArray arrayWithArray: internetItems]];
371 free(ppsz_longnames);
374 [libraryItem setChildren: [NSArray arrayWithObjects:playlistItem, medialibraryItem, nil]];
375 [o_sidebaritems addObject: libraryItem];
376 if ([mycompItem hasChildren])
377 [o_sidebaritems addObject: mycompItem];
378 if ([devicesItem hasChildren])
379 [o_sidebaritems addObject: devicesItem];
380 if ([lanItem hasChildren])
381 [o_sidebaritems addObject: lanItem];
382 if ([internetItem hasChildren])
383 [o_sidebaritems addObject: internetItem];
385 [o_sidebar_view reloadData];
386 [o_sidebar_view setDropItem:playlistItem dropChildIndex:NSOutlineViewDropOnItemIndex];
387 [o_sidebar_view registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
389 [o_sidebar_view setAutosaveName:@"mainwindow-sidebar"];
390 [(PXSourceList *)o_sidebar_view setDataSource:self];
391 [o_sidebar_view setDelegate:self];
392 [o_sidebar_view setAutosaveExpandedItems:YES];
394 [o_sidebar_view expandItem: libraryItem expandChildren: YES];
397 NSUInteger i_sidebaritem_count = [o_sidebaritems count];
398 for (NSUInteger x = 0; x < i_sidebaritem_count; x++)
399 [o_sidebar_view expandItem: [o_sidebaritems objectAtIndex:x] expandChildren: YES];
403 - (void)resizePlaylistAfterCollapse
405 // no animation here since we might be in the middle of another resize animation
406 NSRect rightSplitRect = [o_right_split_view frame];
409 plrect.size.height = rightSplitRect.size.height - 20.0; // actual pl top bar height, which differs from its frame
410 plrect.size.width = rightSplitRect.size.width;
411 plrect.origin.x = plrect.origin.y = 0.;
413 NSRect dropzoneboxRect = [o_dropzone_box frame];
414 dropzoneboxRect.origin.x = (plrect.size.width - dropzoneboxRect.size.width) / 2;
415 dropzoneboxRect.origin.y = (plrect.size.height - dropzoneboxRect.size.height) / 2;
417 [o_dropzone_view setFrame: plrect];
418 [o_dropzone_box setFrame: dropzoneboxRect];
420 if (b_podcastView_displayed) {
421 plrect.size.height -= [o_podcast_view frame].size.height;
422 plrect.origin.y = [o_podcast_view frame].size.height;
424 [o_playlist_table setFrame: plrect];
426 [o_dropzone_view setNeedsDisplay: YES];
427 [o_playlist_table setNeedsDisplay: YES];
430 - (void)makeSplitViewVisible
432 if (self.darkInterface)
433 [self setContentMinSize: NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
435 [self setContentMinSize: NSMakeSize(604., f_min_window_height)];
437 NSRect old_frame = [self frame];
438 CGFloat newHeight = [self minSize].height;
439 if (old_frame.size.height < newHeight) {
440 NSRect new_frame = old_frame;
441 new_frame.origin.y = old_frame.origin.y + old_frame.size.height - newHeight;
442 new_frame.size.height = newHeight;
444 [[self animator] setFrame: new_frame display: YES animate: YES];
447 [self.videoView setHidden: YES];
448 [o_split_view setHidden: NO];
449 if (self.nativeFullscreenMode && [self fullscreen]) {
450 [[self.controlsBar bottomBarView] setHidden: NO];
451 [self.fspanel setNonActive:nil];
454 [self makeFirstResponder: o_playlist_table];
457 - (void)makeSplitViewHidden
459 if (self.darkInterface)
460 [self setContentMinSize: NSMakeSize(604., f_min_video_height + [self.titlebarView frame].size.height)];
462 [self setContentMinSize: NSMakeSize(604., f_min_video_height)];
464 [o_split_view setHidden: YES];
465 [self.videoView setHidden: NO];
466 if (self.nativeFullscreenMode && [self fullscreen]) {
467 [[self.controlsBar bottomBarView] setHidden: YES];
468 [self.fspanel setActive:nil];
471 if ([[self.videoView subviews] count] > 0)
472 [self makeFirstResponder: [[self.videoView subviews] firstObject]];
476 - (void)changePlaylistState:(VLCPlaylistStateEvent)event
478 // Beware, this code is really ugly
480 msg_Dbg(VLCIntf, "toggle playlist from state: removed splitview %i, minimized view %i. Event %i", b_splitview_removed, b_minimized_view, event);
481 if (![self isVisible] && event == psUserMenuEvent) {
482 [self makeKeyAndOrderFront: nil];
486 BOOL b_activeVideo = [[VLCMain sharedInstance] activeVideoPlayback];
487 BOOL b_restored = NO;
489 // ignore alt if triggered through main menu shortcut
490 BOOL b_have_alt_key = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0;
491 if (event == psUserMenuEvent)
494 // eUserMenuEvent is now handled same as eUserEvent
495 if(event == psUserMenuEvent)
498 if (b_dropzone_active && b_have_alt_key) {
503 if (!(self.nativeFullscreenMode && self.fullscreen) && !b_splitview_removed && ((b_have_alt_key && b_activeVideo)
504 || (self.nonembedded && event == psUserEvent)
505 || (!b_activeVideo && event == psUserEvent)
506 || (b_minimized_view && event == psVideoStartedOrStoppedEvent))) {
507 // for starting playback, window is resized through resized events
508 // for stopping playback, resize through reset to previous frame
509 [self hideSplitView: event != psVideoStartedOrStoppedEvent];
510 b_minimized_view = NO;
512 if (b_splitview_removed) {
513 if (!self.nonembedded || (event == psUserEvent && self.nonembedded))
514 [self showSplitView: event != psVideoStartedOrStoppedEvent];
516 if (event != psUserEvent)
517 b_minimized_view = YES;
519 b_minimized_view = NO;
525 if (!self.nonembedded) {
526 if (([self.videoView isHidden] && b_activeVideo) || b_restored || (b_activeVideo && event != psUserEvent))
527 [self makeSplitViewHidden];
529 [self makeSplitViewVisible];
531 [o_split_view setHidden: NO];
532 [o_playlist_table setHidden: NO];
533 [self.videoView setHidden: YES];
537 msg_Dbg(VLCIntf, "toggle playlist to state: removed splitview %i, minimized view %i", b_splitview_removed, b_minimized_view);
540 - (IBAction)dropzoneButtonAction:(id)sender
542 [[[VLCMain sharedInstance] open] openFileGeneric];
546 #pragma mark overwritten default functionality
548 - (void)windowResizedOrMoved:(NSNotification *)notification
550 [self saveFrameUsingName: [self frameAutosaveName]];
553 - (void)applicationWillTerminate:(NSNotification *)notification
555 config_PutInt(VLCIntf, "macosx-show-sidebar", ![o_split_view isSubviewCollapsed:o_left_split_view]);
557 [self saveFrameUsingName: [self frameAutosaveName]];
561 - (void)someWindowWillClose:(NSNotification *)notification
563 id obj = [notification object];
565 // hasActiveVideo is defined for VLCVideoWindowCommon and subclasses
566 if ([obj respondsToSelector:@selector(hasActiveVideo)] && [obj hasActiveVideo]) {
567 if ([[VLCMain sharedInstance] activeVideoPlayback])
568 [[VLCCoreInteraction sharedInstance] stop];
572 - (void)someWindowWillMiniaturize:(NSNotification *)notification
574 if (config_GetInt(VLCIntf, "macosx-pause-minimized")) {
575 id obj = [notification object];
577 if ([obj class] == [VLCVideoWindowCommon class] || [obj class] == [VLCDetachedVideoWindow class] || ([obj class] == [VLCMainWindow class] && !self.nonembedded)) {
578 if ([[VLCMain sharedInstance] activeVideoPlayback])
579 [[VLCCoreInteraction sharedInstance] pause];
585 #pragma mark Update interface and respond to foreign events
588 b_dropzone_active = YES;
589 [o_right_split_view addSubview: o_dropzone_view positioned:NSWindowAbove relativeTo:o_playlist_table];
590 [o_dropzone_view setFrame: [o_playlist_table frame]];
591 [o_playlist_table setHidden:YES];
596 b_dropzone_active = NO;
597 [o_dropzone_view removeFromSuperview];
598 [o_playlist_table setHidden: NO];
601 - (void)hideSplitView:(BOOL)b_with_resize
603 // cancel pending pl resizes, in case of fast toggle between both modes
604 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];
607 NSRect winrect = [self frame];
608 f_lastSplitViewHeight = [o_split_view frame].size.height;
609 winrect.size.height = winrect.size.height - f_lastSplitViewHeight;
610 winrect.origin.y = winrect.origin.y + f_lastSplitViewHeight;
611 [self setFrame: winrect display: YES animate: YES];
614 if (self.darkInterface) {
615 [self setContentMinSize: NSMakeSize(604., [self.controlsBar height] + [self.titlebarView frame].size.height)];
616 [self setContentMaxSize: NSMakeSize(FLT_MAX, [self.controlsBar height] + [self.titlebarView frame].size.height)];
618 [self setContentMinSize: NSMakeSize(604., [self.controlsBar height])];
619 [self setContentMaxSize: NSMakeSize(FLT_MAX, [self.controlsBar height])];
622 b_splitview_removed = YES;
625 - (void)showSplitView:(BOOL)b_with_resize
628 if (self.darkInterface)
629 [self setContentMinSize:NSMakeSize(604., f_min_window_height + [self.titlebarView frame].size.height)];
631 [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
632 [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
636 winrect = [self frame];
637 winrect.size.height = winrect.size.height + f_lastSplitViewHeight;
638 winrect.origin.y = winrect.origin.y - f_lastSplitViewHeight;
639 [self setFrame: winrect display: YES animate: YES];
642 // cancel pending pl resizes, in case of fast toggle between both modes
643 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];
644 [self performSelector:@selector(resizePlaylistAfterCollapse) withObject: nil afterDelay:0.75];
646 b_splitview_removed = NO;
649 - (void)updateTimeSlider
651 [self.controlsBar updateTimeSlider];
652 [self.fspanel updatePositionAndTime];
654 [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
655 [controlsBar updateTimeSlider];
658 [[VLCCoreInteraction sharedInstance] updateAtoB];
663 input_thread_t * p_input;
664 p_input = pl_CurrentInput(VLCIntf);
666 NSString *aString = @"";
668 if (!config_GetPsz(VLCIntf, "video-title")) {
669 char *format = var_InheritString(VLCIntf, "input-title-format");
671 char *formated = str_format_meta(p_input, format);
673 aString = toNSStr(formated);
677 aString = toNSStr(config_GetPsz(VLCIntf, "video-title"));
679 char *uri = input_item_GetURI(input_GetItem(p_input));
681 NSURL * o_url = [NSURL URLWithString:toNSStr(uri)];
682 if ([o_url isFileURL]) {
683 [self setRepresentedURL: o_url];
684 [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
685 [o_window setRepresentedURL:o_url];
688 [self setRepresentedURL: nil];
689 [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
690 [o_window setRepresentedURL:nil];
695 if ([aString isEqualToString:@""]) {
696 if ([o_url isFileURL])
697 aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
699 aString = [o_url absoluteString];
702 if ([aString length] > 0) {
703 [self setTitle: aString];
704 [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
705 [o_window setTitle:aString];
708 [self.fspanel setStreamTitle: aString];
710 [self setTitle: _NS("VLC media player")];
711 [self setRepresentedURL: nil];
714 vlc_object_release(p_input);
716 [self setTitle: _NS("VLC media player")];
717 [self setRepresentedURL: nil];
723 [self.controlsBar updateControls];
724 [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
725 [controlsBar updateControls];
728 bool b_seekable = false;
730 playlist_t * p_playlist = pl_Get(VLCIntf);
731 input_thread_t * p_input = playlist_CurrentInput(p_playlist);
733 /* seekable streams */
734 b_seekable = var_GetBool(p_input, "can-seek");
736 vlc_object_release(p_input);
739 [self updateTimeSlider];
740 if ([self.fspanel respondsToSelector:@selector(setSeekable:)])
741 [self.fspanel setSeekable: b_seekable];
744 if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
745 [[[[VLCMain sharedInstance] playlist] model] hasChildren])
750 [o_sidebar_view setNeedsDisplay:YES];
752 [self _updatePlaylistTitle];
757 [self.controlsBar setPause];
758 [self.fspanel setPause];
760 [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
761 [controlsBar setPause];
767 [self.controlsBar setPlay];
768 [self.fspanel setPlay];
770 [[[VLCMain sharedInstance] voutController] updateControlsBarsUsingBlock:^(VLCControlsBarCommon *controlsBar) {
771 [controlsBar setPlay];
775 - (void)updateVolumeSlider
777 [(VLCMainWindowControlsBar *)[self controlsBar] updateVolumeSlider];
778 [self.fspanel setVolumeLevel: [[VLCCoreInteraction sharedInstance] volume]];
782 #pragma mark Video Output handling
784 - (void)videoplayWillBeStarted
786 if (!self.fullscreen)
787 frameBeforePlayback = [self frame];
790 - (void)setVideoplayEnabled
792 BOOL b_videoPlayback = [[VLCMain sharedInstance] activeVideoPlayback];
794 if (!b_videoPlayback) {
795 if (!self.nonembedded && (!self.nativeFullscreenMode || (self.nativeFullscreenMode && !self.fullscreen)) && frameBeforePlayback.size.width > 0 && frameBeforePlayback.size.height > 0) {
797 // only resize back to minimum view of this is still desired final state
798 CGFloat f_threshold_height = f_min_video_height + [self.controlsBar height];
799 if(frameBeforePlayback.size.height > f_threshold_height || b_minimized_view) {
801 if ([[VLCMain sharedInstance] isTerminating])
802 [self setFrame:frameBeforePlayback display:YES];
804 [[self animator] setFrame:frameBeforePlayback display:YES];
809 frameBeforePlayback = NSMakeRect(0, 0, 0, 0);
811 // update fs button to reflect state for next startup
812 if (var_InheritBool(VLCIntf, "fullscreen") || var_GetBool(pl_Get(VLCIntf), "fullscreen")) {
813 [self.controlsBar setFullscreenState:YES];
816 [self makeFirstResponder: o_playlist_table];
817 [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
819 // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
820 [self setAlphaValue:1.0];
823 if (self.nativeFullscreenMode) {
824 if ([self hasActiveVideo] && [self fullscreen]) {
825 [[self.controlsBar bottomBarView] setHidden: b_videoPlayback];
826 [self.fspanel setActive: nil];
828 [[self.controlsBar bottomBarView] setHidden: NO];
829 [self.fspanel setNonActive: nil];
835 #pragma mark Lion native fullscreen handling
836 - (void)windowWillEnterFullScreen:(NSNotification *)notification
838 [super windowWillEnterFullScreen:notification];
840 // update split view frame after removing title bar
841 if (self.darkInterface) {
842 NSRect frame = [[self contentView] frame];
843 frame.origin.y += [self.controlsBar height];
844 frame.size.height -= [self.controlsBar height];
845 [o_split_view setFrame:frame];
849 - (void)windowWillExitFullScreen:(NSNotification *)notification
851 [super windowWillExitFullScreen: notification];
853 // update split view frame after readding title bar
854 if (self.darkInterface) {
855 NSRect frame = [o_split_view frame];
856 frame.size.height -= [self.titlebarView frame].size.height;
857 [o_split_view setFrame:frame];
861 #pragma mark Fullscreen support
863 - (void)showFullscreenController
865 id currentWindow = [NSApp keyWindow];
866 if ([currentWindow respondsToSelector:@selector(hasActiveVideo)] && [currentWindow hasActiveVideo]) {
867 if ([currentWindow respondsToSelector:@selector(fullscreen)] && [currentWindow fullscreen] && ![[currentWindow videoView] isHidden]) {
869 if ([[VLCMain sharedInstance] activeVideoPlayback])
870 [self.fspanel fadeIn];
877 #pragma mark split view delegate
878 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
880 if (dividerIndex == 0)
886 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
888 if (dividerIndex == 0)
894 - (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
896 return ([subview isEqual:o_left_split_view]);
899 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
901 if ([subview isEqual:o_left_split_view])
906 - (void)mainSplitViewDidResizeSubviews:(id)object
908 f_lastLeftSplitViewWidth = [o_left_split_view frame].size.width;
909 config_PutInt(VLCIntf, "macosx-show-sidebar", ![o_split_view isSubviewCollapsed:o_left_split_view]);
910 [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
913 - (void)toggleLeftSubSplitView
915 [o_split_view adjustSubviews];
916 if ([o_split_view isSubviewCollapsed:o_left_split_view])
917 [o_split_view setPosition:f_lastLeftSplitViewWidth ofDividerAtIndex:0];
919 [o_split_view setPosition:[o_split_view minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
920 [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
924 #pragma mark private playlist magic
925 - (void)_updatePlaylistTitle
927 PLRootType root = [[[[VLCMain sharedInstance] playlist] model] currentRootType];
928 playlist_t *p_playlist = pl_Get(VLCIntf);
931 if (root == ROOT_TYPE_PLAYLIST)
932 [o_chosen_category_lbl setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
933 else if (root == ROOT_TYPE_MEDIALIBRARY)
934 [o_chosen_category_lbl setStringValue: [_NS("Media Library") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
939 - (NSString *)_playbackDurationOfNode:(playlist_item_t*)node
944 playlist_t * p_playlist = pl_Get(VLCIntf);
947 mtime_t mt_duration = playlist_GetNodeDuration( node );
952 mt_duration = mt_duration / 1000000;
954 NSDate *date = [NSDate dateWithTimeIntervalSince1970:mt_duration];
955 NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
956 [formatter setDateFormat:@"HH:mm:ss"];
957 [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
959 return [NSString stringWithFormat:@" — %@",[formatter stringFromDate:date]];
962 - (IBAction)searchItem:(id)sender
964 [[[[VLCMain sharedInstance] playlist] model] searchUpdate:[o_search_fld stringValue]];
968 #pragma mark Side Bar Data handling
969 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
970 - (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
972 //Works the same way as the NSOutlineView data source: `nil` means a parent item
974 return [o_sidebaritems count];
976 return [[item children] count];
980 - (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
982 //Works the same way as the NSOutlineView data source: `nil` means a parent item
984 return [o_sidebaritems objectAtIndex:index];
986 return [[item children] objectAtIndex:index];
990 - (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
995 - (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
997 [item setTitle:object];
1000 - (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
1002 return [item hasChildren];
1006 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
1008 if ([[item identifier] isEqualToString: @"playlist"] || [[item identifier] isEqualToString: @"medialibrary"])
1011 return [item hasBadge];
1015 - (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item
1017 playlist_t * p_playlist = pl_Get(VLCIntf);
1018 NSInteger i_playlist_size = 0;
1020 if ([[item identifier] isEqualToString: @"playlist"]) {
1022 i_playlist_size = p_playlist->p_local_category->i_children;
1025 return i_playlist_size;
1027 if ([[item identifier] isEqualToString: @"medialibrary"]) {
1029 if (p_playlist->p_ml_category)
1030 i_playlist_size = p_playlist->p_ml_category->i_children;
1033 return i_playlist_size;
1036 return [item badgeValue];
1040 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
1042 return [item hasIcon];
1046 - (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
1051 - (NSMenu*)sourceList:(PXSourceList*)aSourceList menuForEvent:(NSEvent*)theEvent item:(id)item
1053 if ([theEvent type] == NSRightMouseDown || ([theEvent type] == NSLeftMouseDown && ([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)) {
1055 if ([item sdtype] > 0)
1057 NSMenu *m = [[NSMenu alloc] init];
1058 playlist_t * p_playlist = pl_Get(VLCIntf);
1059 BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1061 [m addItemWithTitle:_NS("Enable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1063 [m addItemWithTitle:_NS("Disable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1064 [[m itemAtIndex:0] setRepresentedObject: [item identifier]];
1073 - (IBAction)sdmenuhandler:(id)sender
1075 NSString * identifier = [sender representedObject];
1076 if ([identifier length] > 0 && ![identifier isEqualToString:@"lua{sd='freebox',longname='Freebox TV'}"]) {
1077 playlist_t * p_playlist = pl_Get(VLCIntf);
1078 BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [identifier UTF8String]);
1081 playlist_ServicesDiscoveryAdd(p_playlist, [identifier UTF8String]);
1083 playlist_ServicesDiscoveryRemove(p_playlist, [identifier UTF8String]);
1088 #pragma mark Side Bar Delegate Methods
1089 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
1090 - (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group
1092 if ([[group identifier] isEqualToString:@"library"])
1098 - (void)sourceListSelectionDidChange:(NSNotification *)notification
1100 playlist_t * p_playlist = pl_Get(VLCIntf);
1102 NSIndexSet *selectedIndexes = [o_sidebar_view selectedRowIndexes];
1103 id item = [o_sidebar_view itemAtRow:[selectedIndexes firstIndex]];
1105 //Set the label text to represent the new selection
1106 if ([item sdtype] > -1 && [[item identifier] length] > 0) {
1107 BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1109 playlist_ServicesDiscoveryAdd(p_playlist, [[item identifier] UTF8String]);
1112 [o_chosen_category_lbl setStringValue:[item title]];
1114 if ([[item identifier] isEqualToString:@"playlist"]) {
1116 [[[[VLCMain sharedInstance] playlist] model] changeRootItem:p_playlist->p_playing];
1119 [self _updatePlaylistTitle];
1121 } else if ([[item identifier] isEqualToString:@"medialibrary"]) {
1122 if (p_playlist->p_ml_category) {
1125 [[[[VLCMain sharedInstance] playlist] model] changeRootItem:p_playlist->p_media_library];
1129 [self _updatePlaylistTitle];
1133 playlist_item_t *pl_item = playlist_ChildSearchName(p_playlist->p_root, [[item untranslatedTitle] UTF8String]);
1134 [[[[VLCMain sharedInstance] playlist] model] changeRootItem:pl_item];
1139 // Note the order: first hide the podcast controls, then show the drop zone
1140 if ([[item identifier] isEqualToString:@"podcast{longname=\"Podcasts\"}"])
1141 [self showPodcastControls];
1143 [self hidePodcastControls];
1146 if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
1147 [[[[VLCMain sharedInstance] playlist] model] hasChildren])
1148 [self hideDropZone];
1150 [self showDropZone];
1153 [[NSNotificationCenter defaultCenter] postNotificationName: @"VLCMediaKeySupportSettingChanged"
1158 - (NSDragOperation)sourceList:(PXSourceList *)aSourceList validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1160 if ([[item identifier] isEqualToString:@"playlist"] || [[item identifier] isEqualToString:@"medialibrary"]) {
1161 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1162 if ([[o_pasteboard types] containsObject: VLCPLItemPasteboadType] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
1163 return NSDragOperationGeneric;
1165 return NSDragOperationNone;
1168 - (BOOL)sourceList:(PXSourceList *)aSourceList acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1170 NSPasteboard *o_pasteboard = [info draggingPasteboard];
1172 playlist_t * p_playlist = pl_Get(VLCIntf);
1173 playlist_item_t *p_node;
1175 if ([[item identifier] isEqualToString:@"playlist"])
1176 p_node = p_playlist->p_playing;
1178 p_node = p_playlist->p_media_library;
1180 if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1181 NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1182 NSUInteger count = [o_values count];
1183 NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1185 for(NSUInteger i = 0; i < count; i++) {
1186 NSDictionary *o_dic;
1187 char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1191 o_dic = [NSDictionary dictionaryWithObject:toNSStr(psz_uri) forKey:@"ITEM_URL"];
1195 [o_array addObject: o_dic];
1198 [[[VLCMain sharedInstance] playlist] addPlaylistItems:o_array withParentItemId:p_node->i_id atPos:-1 startPlayback:NO];
1201 else if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1202 NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
1204 NSUInteger count = [array count];
1207 for(NSUInteger i = 0; i < count; i++) {
1208 playlist_item_t *p_item = playlist_ItemGetById(p_playlist, [[array objectAtIndex:i] plItemId]);
1209 if (!p_item) continue;
1210 playlist_NodeAddCopy(p_playlist, p_item, p_node, PLAYLIST_END);
1219 - (id)sourceList:(PXSourceList *)aSourceList persistentObjectForItem:(id)item
1221 return [item identifier];
1224 - (id)sourceList:(PXSourceList *)aSourceList itemForPersistentObject:(id)object
1226 /* the following code assumes for sakes of simplicity that only the top level
1227 * items are allowed to have children */
1229 NSArray * array = [NSArray arrayWithArray: o_sidebaritems]; // read-only arrays are noticebly faster
1230 NSUInteger count = [array count];
1234 for (NSUInteger x = 0; x < count; x++) {
1235 id item = [array objectAtIndex:x]; // save one objc selector call
1236 if ([[item identifier] isEqualToString:object])
1244 #pragma mark Podcast
1246 - (IBAction)addPodcast:(id)sender
1248 [NSApp beginSheet:o_podcast_subscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1251 - (IBAction)addPodcastWindowAction:(id)sender
1253 [o_podcast_subscribe_window orderOut:sender];
1254 [NSApp endSheet: o_podcast_subscribe_window];
1256 if (sender == o_podcast_subscribe_ok_btn && [[o_podcast_subscribe_url_fld stringValue] length] > 0) {
1257 NSMutableString * podcastConf = [[NSMutableString alloc] init];
1258 if (config_GetPsz(VLCIntf, "podcast-urls") != NULL)
1259 [podcastConf appendFormat:@"%s|", config_GetPsz(VLCIntf, "podcast-urls")];
1261 [podcastConf appendString: [o_podcast_subscribe_url_fld stringValue]];
1262 config_PutPsz(VLCIntf, "podcast-urls", [podcastConf UTF8String]);
1263 var_SetString(pl_Get(VLCIntf), "podcast-urls", [podcastConf UTF8String]);
1267 - (IBAction)removePodcast:(id)sender
1269 char *psz_urls = var_InheritString(pl_Get(VLCIntf), "podcast-urls");
1270 if (psz_urls != NULL) {
1271 [o_podcast_unsubscribe_pop removeAllItems];
1272 [o_podcast_unsubscribe_pop addItemsWithTitles:[toNSStr(psz_urls) componentsSeparatedByString:@"|"]];
1273 [NSApp beginSheet:o_podcast_unsubscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1278 - (IBAction)removePodcastWindowAction:(id)sender
1280 [o_podcast_unsubscribe_window orderOut:sender];
1281 [NSApp endSheet: o_podcast_unsubscribe_window];
1283 if (sender == o_podcast_unsubscribe_ok_btn) {
1284 playlist_t * p_playlist = pl_Get(VLCIntf);
1285 char *psz_urls = var_InheritString(p_playlist, "podcast-urls");
1287 NSMutableArray * urls = [[NSMutableArray alloc] initWithArray:[toNSStr(config_GetPsz(VLCIntf, "podcast-urls")) componentsSeparatedByString:@"|"]];
1288 [urls removeObjectAtIndex: [o_podcast_unsubscribe_pop indexOfSelectedItem]];
1289 const char *psz_new_urls = [[urls componentsJoinedByString:@"|"] UTF8String];
1290 var_SetString(pl_Get(VLCIntf), "podcast-urls", psz_new_urls);
1291 config_PutPsz(VLCIntf, "podcast-urls", psz_new_urls);
1295 /* update playlist table */
1296 if (playlist_IsServicesDiscoveryLoaded(p_playlist, "podcast{longname=\"Podcasts\"}")) {
1297 [[[VLCMain sharedInstance] playlist] playlistUpdated];
1302 - (void)showPodcastControls
1304 NSRect podcastViewDimensions = [o_podcast_view frame];
1305 NSRect rightSplitRect = [o_right_split_view frame];
1306 NSRect playlistTableRect = [o_playlist_table frame];
1308 podcastViewDimensions.size.width = rightSplitRect.size.width;
1309 podcastViewDimensions.origin.x = podcastViewDimensions.origin.y = .0;
1310 [o_podcast_view setFrame:podcastViewDimensions];
1312 playlistTableRect.origin.y = playlistTableRect.origin.y + podcastViewDimensions.size.height;
1313 playlistTableRect.size.height = playlistTableRect.size.height - podcastViewDimensions.size.height;
1314 [o_playlist_table setFrame:playlistTableRect];
1315 [o_playlist_table setNeedsDisplay:YES];
1317 [o_right_split_view addSubview: o_podcast_view positioned: NSWindowAbove relativeTo: o_right_split_view];
1318 b_podcastView_displayed = YES;
1321 - (void)hidePodcastControls
1323 if (b_podcastView_displayed) {
1324 NSRect podcastViewDimensions = [o_podcast_view frame];
1325 NSRect playlistTableRect = [o_playlist_table frame];
1327 playlistTableRect.origin.y = playlistTableRect.origin.y - podcastViewDimensions.size.height;
1328 playlistTableRect.size.height = playlistTableRect.size.height + podcastViewDimensions.size.height;
1330 [o_podcast_view removeFromSuperviewWithoutNeedingDisplay];
1331 [o_playlist_table setFrame: playlistTableRect];
1332 b_podcastView_displayed = NO;
1338 @interface VLCDetachedVideoWindow ()
1340 VLCColorView * o_color_backdrop;
1344 @implementation VLCDetachedVideoWindow
1346 - (void)awakeFromNib
1348 // sets lion fullscreen behaviour
1349 [super awakeFromNib];
1350 [self setAcceptsMouseMovedEvents: YES];
1352 BOOL darkInterface = config_GetInt(VLCIntf, "macosx-interfacestyle");
1354 if (darkInterface) {
1355 [self setBackgroundColor: [NSColor clearColor]];
1357 [self setOpaque: NO];
1359 [self setHasShadow:NO];
1360 [self setHasShadow:YES];
1362 NSRect winrect = [self frame];
1363 CGFloat f_titleBarHeight = [self.titlebarView frame].size.height;
1365 [self setTitle: _NS("VLC media player")];
1366 [self.titlebarView setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight, winrect.size.width, f_titleBarHeight)];
1367 [[self contentView] addSubview: self.titlebarView positioned: NSWindowAbove relativeTo: nil];
1370 [self setBackgroundColor: [NSColor blackColor]];
1373 NSRect videoViewRect = [[self contentView] bounds];
1375 videoViewRect.size.height -= [self.titlebarView frame].size.height;
1376 CGFloat f_bottomBarHeight = [[self controlsBar] height];
1377 videoViewRect.size.height -= f_bottomBarHeight;
1378 videoViewRect.origin.y = f_bottomBarHeight;
1379 [self.videoView setFrame: videoViewRect];
1381 if (darkInterface) {
1382 o_color_backdrop = [[VLCColorView alloc] initWithFrame: [self.videoView frame]];
1383 [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: self.videoView];
1384 [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
1386 [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height] + [self.titlebarView frame].size.height)];
1388 [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height])];