]> git.sesse.net Git - vlc/blob - modules/gui/macosx/MainWindow.m
macosx: fix wrong size of playlist when using podcast and minimal view
[vlc] / modules / gui / macosx / MainWindow.m
1 /*****************************************************************************
2  * MainWindow.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  *****************************************************************************/
27
28 #import "intf.h"
29 #import "CompatibilityFixes.h"
30 #import "MainWindow.h"
31 #import "CoreInteraction.h"
32 #import "AudioEffects.h"
33 #import "MainMenu.h"
34 #import "open.h"
35 #import "controls.h" // TODO: remove me
36 #import "playlist.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
44 #import "ControlsBar.h"
45 #import "VideoView.h"
46 #import "VLCVoutWindowController.h"
47
48
49 @interface VLCMainWindow (Internal)
50 - (void)resizePlaylistAfterCollapse;
51 - (void)makeSplitViewVisible;
52 - (void)makeSplitViewHidden;
53 - (void)showPodcastControls;
54 - (void)hidePodcastControls;
55 @end
56
57 static const float f_min_window_height = 307.;
58
59 @implementation VLCMainWindow
60
61 @synthesize nativeFullscreenMode=b_nativeFullscreenMode;
62 @synthesize nonembedded=b_nonembedded;
63 @synthesize fsPanel=o_fspanel;
64
65 static VLCMainWindow *_o_sharedInstance = nil;
66
67 + (VLCMainWindow *)sharedInstance
68 {
69     return _o_sharedInstance ? _o_sharedInstance : [[self alloc] init];
70 }
71
72 #pragma mark -
73 #pragma mark Initialization
74
75 - (id)init
76 {
77     if (_o_sharedInstance) {
78         [self dealloc];
79         return _o_sharedInstance;
80     } else
81         _o_sharedInstance = [super init];
82
83     return _o_sharedInstance;
84 }
85
86 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
87                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
88 {
89     self = [super initWithContentRect:contentRect styleMask:styleMask
90                               backing:backingType defer:flag];
91     _o_sharedInstance = self;
92
93     [[VLCMain sharedInstance] updateTogglePlaylistState];
94
95     return self;
96 }
97
98 - (BOOL)isEvent:(NSEvent *)o_event forKey:(const char *)keyString
99 {
100     char *key;
101     NSString *o_key;
102
103     key = config_GetPsz(VLCIntf, keyString);
104     o_key = [NSString stringWithFormat:@"%s", key];
105     FREENULL(key);
106
107     unsigned int i_keyModifiers = [[VLCStringUtility sharedInstance] VLCModifiersToCocoa:o_key];
108
109     NSString * characters = [o_event charactersIgnoringModifiers];
110     if ([characters length] > 0) {
111         return [[characters lowercaseString] isEqualToString: [[VLCStringUtility sharedInstance] VLCKeyToString: o_key]] &&
112                 (i_keyModifiers & NSShiftKeyMask)     == ([o_event modifierFlags] & NSShiftKeyMask) &&
113                 (i_keyModifiers & NSControlKeyMask)   == ([o_event modifierFlags] & NSControlKeyMask) &&
114                 (i_keyModifiers & NSAlternateKeyMask) == ([o_event modifierFlags] & NSAlternateKeyMask) &&
115                 (i_keyModifiers & NSCommandKeyMask)   == ([o_event modifierFlags] & NSCommandKeyMask);
116     }
117     return NO;
118 }
119
120 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
121 {
122     BOOL b_force = NO;
123     // these are key events which should be handled by vlc core, but are attached to a main menu item
124     if (![self isEvent: o_event forKey: "key-vol-up"] &&
125         ![self isEvent: o_event forKey: "key-vol-down"] &&
126         ![self isEvent: o_event forKey: "key-vol-mute"] &&
127         ![self isEvent: o_event forKey: "key-prev"] &&
128         ![self isEvent: o_event forKey: "key-next"] &&
129         ![self isEvent: o_event forKey: "key-jump+short"] &&
130         ![self isEvent: o_event forKey: "key-jump-short"]) {
131         /* We indeed want to prioritize some Cocoa key equivalent against libvlc,
132          so we perform the menu equivalent now. */
133         if ([[NSApp mainMenu] performKeyEquivalent:o_event])
134             return TRUE;
135     }
136     else
137         b_force = YES;
138
139     return [[VLCMain sharedInstance] hasDefinedShortcutKey:o_event force:b_force] ||
140            [(VLCControls *)[[VLCMain sharedInstance] controls] keyEvent:o_event];
141 }
142
143 - (void)dealloc
144 {
145     if (b_dark_interface)
146         [o_color_backdrop release];
147
148     [[NSNotificationCenter defaultCenter] removeObserver: self];
149     [o_sidebaritems release];
150     [o_fspanel release];
151
152     [super dealloc];
153 }
154
155 - (void)awakeFromNib
156 {
157     // sets lion fullscreen behaviour
158     [super awakeFromNib];
159
160     BOOL b_splitviewShouldBeHidden = NO;
161
162     if (!OSX_SNOW_LEOPARD)
163         [self setRestorable: NO];
164     [self setFrameAutosaveName:@"mainwindow"];
165
166     /* setup the styled interface */
167     b_nativeFullscreenMode = NO;
168 #ifdef MAC_OS_X_VERSION_10_7
169     if (!OSX_SNOW_LEOPARD)
170         b_nativeFullscreenMode = var_InheritBool(VLCIntf, "macosx-nativefullscreenmode");
171 #endif
172     [self useOptimizedDrawing: YES];
173
174     [[o_search_fld cell] setPlaceholderString: _NS("Search")];
175     [[o_search_fld cell] accessibilitySetOverrideValue:_NS("Enter a term to search the playlist. Results will be selected in the table.") forAttribute:NSAccessibilityDescriptionAttribute];
176
177     [o_dropzone_btn setTitle: _NS("Open media...")];
178     [[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];
179     [o_dropzone_lbl setStringValue: _NS("Drop media here")];
180     [o_dropzone_img setImage: imageFromRes(@"dropzone")];
181
182     [o_podcast_add_btn setTitle: _NS("Subscribe")];
183     [o_podcast_remove_btn setTitle: _NS("Unsubscribe")];
184     [o_podcast_subscribe_title_lbl setStringValue: _NS("Subscribe to a podcast")];
185     [o_podcast_subscribe_subtitle_lbl setStringValue: _NS("Enter URL of the podcast to subscribe to:")];
186     [o_podcast_subscribe_cancel_btn setTitle: _NS("Cancel")];
187     [o_podcast_subscribe_ok_btn setTitle: _NS("Subscribe")];
188     [o_podcast_unsubscribe_title_lbl setStringValue: _NS("Unsubscribe from a podcast")];
189     [o_podcast_unsubscribe_subtitle_lbl setStringValue: _NS("Select the podcast you would like to unsubscribe from:")];
190     [o_podcast_unsubscribe_ok_btn setTitle: _NS("Unsubscribe")];
191     [o_podcast_unsubscribe_cancel_btn setTitle: _NS("Cancel")];
192
193     /* interface builder action */
194     CGFloat f_threshold_height = f_min_video_height + [o_controls_bar height];
195     if (b_dark_interface)
196         f_threshold_height += [o_titlebar_view frame].size.height;
197     if ([[self contentView] frame].size.height < f_threshold_height)
198         b_splitviewShouldBeHidden = YES;
199
200     [self setDelegate: self];
201     [self setExcludedFromWindowsMenu: YES];
202     [self setAcceptsMouseMovedEvents: YES];
203     // Set that here as IB seems to be buggy
204     if (b_dark_interface)
205         [self setContentMinSize:NSMakeSize(604., f_min_window_height + [o_titlebar_view frame].size.height)];
206     else
207         [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
208
209     [self setTitle: _NS("VLC media player")];
210
211     b_dropzone_active = YES;
212     [o_dropzone_view setFrame: [o_playlist_table frame]];
213     [o_left_split_view setFrame: [o_sidebar_view frame]];
214
215     if (!OSX_SNOW_LEOPARD) {
216         /* the default small size of the search field is slightly different on Lion, let's work-around that */
217         NSRect frame;
218         frame = [o_search_fld frame];
219         frame.origin.y = frame.origin.y + 2.0;
220         frame.size.height = frame.size.height - 1.0;
221         [o_search_fld setFrame: frame];
222     }
223
224     /* reload the sidebar */
225     [self reloadSidebar];
226
227     o_fspanel = [[VLCFSPanel alloc] initWithContentRect:NSMakeRect(110.,267.,549.,87.)
228                                               styleMask:NSTexturedBackgroundWindowMask
229                                                 backing:NSBackingStoreBuffered
230                                                   defer:YES];
231
232     /* make sure we display the desired default appearance when VLC launches for the first time */
233     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
234     if (![defaults objectForKey:@"VLCFirstRun"]) {
235         [defaults setObject:[NSDate date] forKey:@"VLCFirstRun"];
236
237         NSUInteger i_sidebaritem_count = [o_sidebaritems count];
238         for (NSUInteger x = 0; x < i_sidebaritem_count; x++)
239             [o_sidebar_view expandItem: [o_sidebaritems objectAtIndex:x] expandChildren: YES];
240
241         [o_fspanel center];
242
243         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.")];
244         NSInteger returnValue = [albumArtAlert runModal];
245         config_PutInt(VLCIntf, "metadata-network-access", returnValue == NSAlertDefaultReturn);
246     }
247
248     // select playlist item by default
249     [o_sidebar_view selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
250
251     if (b_dark_interface) {
252         [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidResizeNotification object: nil];
253         [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(windowResizedOrMoved:) name: NSWindowDidMoveNotification object: nil];
254
255         [self setBackgroundColor: [NSColor clearColor]];
256         [self setOpaque: NO];
257         [self display];
258         [self setHasShadow:NO];
259         [self setHasShadow:YES];
260
261         NSRect winrect = [self frame];
262         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
263
264         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
265                                               winrect.size.width, f_titleBarHeight)];
266         [[self contentView] addSubview: o_titlebar_view positioned: NSWindowAbove relativeTo: o_split_view];
267
268         if (winrect.size.height > 100) {
269             [self setFrame: winrect display:YES animate:YES];
270             previousSavedFrame = winrect;
271         }
272
273         winrect = [o_split_view frame];
274         winrect.size.height = winrect.size.height - f_titleBarHeight;
275         [o_split_view setFrame: winrect];
276         [o_video_view setFrame: winrect];
277
278         o_color_backdrop = [[VLCColorView alloc] initWithFrame: [o_split_view frame]];
279         [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: o_split_view];
280         [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
281     } else {
282         [o_video_view setFrame: [o_split_view frame]];
283         [o_playlist_table setBorderType: NSNoBorder];
284         [o_sidebar_scrollview setBorderType: NSNoBorder];
285     }
286
287     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(someWindowWillClose:) name: NSWindowWillCloseNotification object: nil];
288     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(someWindowWillMiniaturize:) name: NSWindowWillMiniaturizeNotification object:nil];
289     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationWillTerminate:) name: NSApplicationWillTerminateNotification object: nil];
290     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(mainSplitViewDidResizeSubviews:) name: NSSplitViewDidResizeSubviewsNotification object:o_split_view];
291
292     if (b_splitviewShouldBeHidden) {
293         [self hideSplitView: YES];
294         f_lastSplitViewHeight = 300;
295     }
296
297     /* sanity check for the window size */
298     NSRect frame = [self frame];
299     NSSize screenSize = [[self screen] frame].size;
300     if (screenSize.width <= frame.size.width || screenSize.height <= frame.size.height) {
301         nativeVideoSize = screenSize;
302         [self resizeWindow];
303     }
304
305     /* update fs button to reflect state for next startup */
306     if (var_InheritBool(pl_Get(VLCIntf), "fullscreen"))
307         [o_controls_bar setFullscreenState:YES];
308
309     /* restore split view */
310     f_lastLeftSplitViewWidth = 200;
311     /* trick NSSplitView implementation, which pretends to know better than us */
312     if (!config_GetInt(VLCIntf, "macosx-show-sidebar"))
313         [self performSelector:@selector(toggleLeftSubSplitView) withObject:nil afterDelay:0.05];
314 }
315
316 #pragma mark -
317 #pragma mark appearance management
318
319 - (void)reloadSidebar
320 {
321     BOOL isAReload = NO;
322     if (o_sidebaritems) {
323         [o_sidebaritems release];
324         isAReload = YES;
325     }
326
327     o_sidebaritems = [[NSMutableArray alloc] init];
328     SideBarItem *libraryItem = [SideBarItem itemWithTitle:_NS("LIBRARY") identifier:@"library"];
329     SideBarItem *playlistItem = [SideBarItem itemWithTitle:_NS("Playlist") identifier:@"playlist"];
330     [playlistItem setIcon: imageFromRes(@"sidebar-playlist")];
331     SideBarItem *medialibraryItem = [SideBarItem itemWithTitle:_NS("Media Library") identifier:@"medialibrary"];
332     [medialibraryItem setIcon: imageFromRes(@"sidebar-playlist")];
333     SideBarItem *mycompItem = [SideBarItem itemWithTitle:_NS("MY COMPUTER") identifier:@"mycomputer"];
334     SideBarItem *devicesItem = [SideBarItem itemWithTitle:_NS("DEVICES") identifier:@"devices"];
335     SideBarItem *lanItem = [SideBarItem itemWithTitle:_NS("LOCAL NETWORK") identifier:@"localnetwork"];
336     SideBarItem *internetItem = [SideBarItem itemWithTitle:_NS("INTERNET") identifier:@"internet"];
337
338     /* SD subnodes, inspired by the Qt4 intf */
339     char **ppsz_longnames = NULL;
340     int *p_categories = NULL;
341     char **ppsz_names = vlc_sd_GetNames(pl_Get(VLCIntf), &ppsz_longnames, &p_categories);
342     if (!ppsz_names)
343         msg_Err(VLCIntf, "no sd item found"); //TODO
344     char **ppsz_name = ppsz_names, **ppsz_longname = ppsz_longnames;
345     int *p_category = p_categories;
346     NSMutableArray *internetItems = [[NSMutableArray alloc] init];
347     NSMutableArray *devicesItems = [[NSMutableArray alloc] init];
348     NSMutableArray *lanItems = [[NSMutableArray alloc] init];
349     NSMutableArray *mycompItems = [[NSMutableArray alloc] init];
350     NSString *o_identifier;
351     for (; ppsz_name && *ppsz_name; ppsz_name++, ppsz_longname++, p_category++) {
352         o_identifier = [NSString stringWithCString: *ppsz_name encoding: NSUTF8StringEncoding];
353         switch (*p_category) {
354             case SD_CAT_INTERNET:
355                 [internetItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
356                 if (!strncmp(*ppsz_name, "podcast", 7))
357                     [[internetItems lastObject] setIcon: imageFromRes(@"sidebar-podcast")];
358                 else
359                     [[internetItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
360                 [[internetItems lastObject] setSdtype: SD_CAT_INTERNET];
361                 [[internetItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
362                 break;
363             case SD_CAT_DEVICES:
364                 [devicesItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
365                 [[devicesItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
366                 [[devicesItems lastObject] setSdtype: SD_CAT_DEVICES];
367                 [[devicesItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
368                 break;
369             case SD_CAT_LAN:
370                 [lanItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
371                 [[lanItems lastObject] setIcon: imageFromRes(@"sidebar-local")];
372                 [[lanItems lastObject] setSdtype: SD_CAT_LAN];
373                 [[lanItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
374                 break;
375             case SD_CAT_MYCOMPUTER:
376                 [mycompItems addObject: [SideBarItem itemWithTitle: _NS(*ppsz_longname) identifier: o_identifier]];
377                 if (!strncmp(*ppsz_name, "video_dir", 9))
378                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-movie")];
379                 else if (!strncmp(*ppsz_name, "audio_dir", 9))
380                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-music")];
381                 else if (!strncmp(*ppsz_name, "picture_dir", 11))
382                     [[mycompItems lastObject] setIcon: imageFromRes(@"sidebar-pictures")];
383                 else
384                     [[mycompItems lastObject] setIcon: [NSImage imageNamed:@"NSApplicationIcon"]];
385                 [[mycompItems lastObject] setUntranslatedTitle: [NSString stringWithUTF8String:*ppsz_longname]];
386                 [[mycompItems lastObject] setSdtype: SD_CAT_MYCOMPUTER];
387                 break;
388             default:
389                 msg_Warn(VLCIntf, "unknown SD type found, skipping (%s)", *ppsz_name);
390                 break;
391         }
392
393         free(*ppsz_name);
394         free(*ppsz_longname);
395     }
396     [mycompItem setChildren: [NSArray arrayWithArray: mycompItems]];
397     [devicesItem setChildren: [NSArray arrayWithArray: devicesItems]];
398     [lanItem setChildren: [NSArray arrayWithArray: lanItems]];
399     [internetItem setChildren: [NSArray arrayWithArray: internetItems]];
400     [mycompItems release];
401     [devicesItems release];
402     [lanItems release];
403     [internetItems release];
404     free(ppsz_names);
405     free(ppsz_longnames);
406     free(p_categories);
407
408     [libraryItem setChildren: [NSArray arrayWithObjects:playlistItem, medialibraryItem, nil]];
409     [o_sidebaritems addObject: libraryItem];
410     if ([mycompItem hasChildren])
411         [o_sidebaritems addObject: mycompItem];
412     if ([devicesItem hasChildren])
413         [o_sidebaritems addObject: devicesItem];
414     if ([lanItem hasChildren])
415         [o_sidebaritems addObject: lanItem];
416     if ([internetItem hasChildren])
417         [o_sidebaritems addObject: internetItem];
418
419     [o_sidebar_view reloadData];
420     [o_sidebar_view setDropItem:playlistItem dropChildIndex:NSOutlineViewDropOnItemIndex];
421     [o_sidebar_view registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
422
423     [o_sidebar_view setAutosaveName:@"mainwindow-sidebar"];
424     [(PXSourceList *)o_sidebar_view setDataSource:self];
425     [o_sidebar_view setDelegate:self];
426     [o_sidebar_view setAutosaveExpandedItems:YES];
427
428     [o_sidebar_view expandItem: libraryItem expandChildren: YES];
429
430     if (isAReload) {
431         NSUInteger i_sidebaritem_count = [o_sidebaritems count];
432         for (NSUInteger x = 0; x < i_sidebaritem_count; x++)
433             [o_sidebar_view expandItem: [o_sidebaritems objectAtIndex:x] expandChildren: YES];
434     }
435 }
436
437 - (VLCMainWindowControlsBar *)controlsBar;
438 {
439     return (VLCMainWindowControlsBar *)o_controls_bar;
440 }
441
442 - (void)resizePlaylistAfterCollapse
443 {
444     // no animation here since we might be in the middle of another resize animation
445     NSRect rightSplitRect = [o_right_split_view frame];
446
447     NSRect plrect;
448     plrect.size.height = rightSplitRect.size.height - 20.0; // actual pl top bar height, which differs from its frame
449     plrect.size.width = rightSplitRect.size.width;
450     plrect.origin.x = plrect.origin.y = 0.;
451
452     NSRect dropzoneboxRect = [o_dropzone_box frame];
453     dropzoneboxRect.origin.x = (plrect.size.width - dropzoneboxRect.size.width) / 2;
454     dropzoneboxRect.origin.y = (plrect.size.height - dropzoneboxRect.size.height) / 2;
455
456     [o_dropzone_view setFrame: plrect];
457     [o_dropzone_box setFrame: dropzoneboxRect];
458
459     if (b_podcastView_displayed) {
460         plrect.size.height -= [o_podcast_view frame].size.height;
461         plrect.origin.y = [o_podcast_view frame].size.height;
462     }
463     [o_playlist_table setFrame: plrect];
464
465     [o_dropzone_view setNeedsDisplay: YES];
466     [o_playlist_table setNeedsDisplay: YES];
467 }
468
469 - (void)makeSplitViewVisible
470 {
471     if (b_dark_interface)
472         [self setContentMinSize: NSMakeSize(604., f_min_window_height + [o_titlebar_view frame].size.height)];
473     else
474         [self setContentMinSize: NSMakeSize(604., f_min_window_height)];
475
476     NSRect old_frame = [self frame];
477     CGFloat newHeight = [self minSize].height;
478     if (old_frame.size.height < newHeight) {
479         NSRect new_frame = old_frame;
480         new_frame.origin.y = old_frame.origin.y + old_frame.size.height - newHeight;
481         new_frame.size.height = newHeight;
482
483         [[self animator] setFrame: new_frame display: YES animate: YES];
484     }
485
486     [o_video_view setHidden: YES];
487     [o_split_view setHidden: NO];
488     if (b_nativeFullscreenMode && [self fullscreen]) {
489         [[o_controls_bar bottomBarView] setHidden: NO];
490         [o_fspanel setNonActive:nil];
491     }
492
493     [self makeFirstResponder: o_playlist_table];
494 }
495
496 - (void)makeSplitViewHidden
497 {
498     if (b_dark_interface)
499         [self setContentMinSize: NSMakeSize(604., f_min_video_height + [o_titlebar_view frame].size.height)];
500     else
501         [self setContentMinSize: NSMakeSize(604., f_min_video_height)];
502
503     [o_split_view setHidden: YES];
504     [o_video_view setHidden: NO];
505     if (b_nativeFullscreenMode && [self fullscreen]) {
506         [[o_controls_bar bottomBarView] setHidden: YES];
507         [o_fspanel setActive:nil];
508     }
509
510     if ([[o_video_view subviews] count] > 0)
511         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
512 }
513
514
515 - (void)changePlaylistState:(VLCPlaylistStateEvent)event
516 {
517     // Beware, this code is really ugly
518
519     msg_Dbg(VLCIntf, "toggle playlist from state: removed splitview %i, minimized view %i. Event %i", b_splitview_removed, b_minimized_view, event);
520     if (![self isVisible] && event == psUserMenuEvent) {
521         [self makeKeyAndOrderFront: nil];
522         return;
523     }
524
525     BOOL b_activeVideo = [[VLCMain sharedInstance] activeVideoPlayback];
526     BOOL b_restored = NO;
527
528     // ignore alt if triggered through main menu shortcut
529     BOOL b_have_alt_key = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0;
530     if (event == psUserMenuEvent)
531         b_have_alt_key = NO;
532
533     // eUserMenuEvent is now handled same as eUserEvent
534     if(event == psUserMenuEvent)
535         event = psUserEvent;
536
537     if (b_dropzone_active && b_have_alt_key) {
538         [self hideDropZone];
539         return;
540     }
541
542     if (!(b_nativeFullscreenMode && b_fullscreen) && !b_splitview_removed && ((b_have_alt_key && b_activeVideo)
543                                                                               || (b_nonembedded && event == psUserEvent)
544                                                                               || (!b_activeVideo && event == psUserEvent)
545                                                                               || (b_minimized_view && event == psVideoStartedOrStoppedEvent))) {
546         // for starting playback, window is resized through resized events
547         // for stopping playback, resize through reset to previous frame
548         [self hideSplitView: event != psVideoStartedOrStoppedEvent];
549         b_minimized_view = NO;
550     } else {
551         if (b_splitview_removed) {
552             if (!b_nonembedded || (event == psUserEvent && b_nonembedded))
553                 [self showSplitView: event != psVideoStartedOrStoppedEvent];
554
555             if (event != psUserEvent)
556                 b_minimized_view = YES;
557             else
558                 b_minimized_view = NO;
559
560             if (b_activeVideo)
561                 b_restored = YES;
562         }
563
564         if (!b_nonembedded) {
565             if (([o_video_view isHidden] && b_activeVideo) || b_restored || (b_activeVideo && event != psUserEvent))
566                 [self makeSplitViewHidden];
567             else
568                 [self makeSplitViewVisible];
569         } else {
570             [o_split_view setHidden: NO];
571             [o_playlist_table setHidden: NO];
572             [o_video_view setHidden: YES];
573         }
574     }
575
576     msg_Dbg(VLCIntf, "toggle playlist to state: removed splitview %i, minimized view %i", b_splitview_removed, b_minimized_view);
577 }
578
579 - (IBAction)dropzoneButtonAction:(id)sender
580 {
581     [[[VLCMain sharedInstance] open] openFileGeneric];
582 }
583
584 #pragma mark -
585 #pragma mark overwritten default functionality
586
587 - (void)windowResizedOrMoved:(NSNotification *)notification
588 {
589     [self saveFrameUsingName: [self frameAutosaveName]];
590 }
591
592 - (void)applicationWillTerminate:(NSNotification *)notification
593 {
594     config_PutInt(VLCIntf, "macosx-show-sidebar", ![o_split_view isSubviewCollapsed:o_left_split_view]);
595
596     [self saveFrameUsingName: [self frameAutosaveName]];
597 }
598
599
600 - (void)someWindowWillClose:(NSNotification *)notification
601 {
602     id obj = [notification object];
603
604     // hasActiveVideo is defined for VLCVideoWindowCommon and subclasses
605     if ([obj respondsToSelector:@selector(hasActiveVideo)] && [obj hasActiveVideo]) {
606         if ([[VLCMain sharedInstance] activeVideoPlayback])
607             [[VLCCoreInteraction sharedInstance] stop];
608     }
609 }
610
611 - (void)someWindowWillMiniaturize:(NSNotification *)notification
612 {
613     if (config_GetInt(VLCIntf, "macosx-pause-minimized")) {
614         id obj = [notification object];
615
616         if ([obj class] == [VLCVideoWindowCommon class] || [obj class] == [VLCDetachedVideoWindow class] || ([obj class] == [VLCMainWindow class] && !b_nonembedded)) {
617             if ([[VLCMain sharedInstance] activeVideoPlayback])
618                 [[VLCCoreInteraction sharedInstance] pause];
619         }
620     }
621 }
622
623 #pragma mark -
624 #pragma mark Update interface and respond to foreign events
625 - (void)showDropZone
626 {
627     b_dropzone_active = YES;
628     [o_right_split_view addSubview: o_dropzone_view positioned:NSWindowAbove relativeTo:o_playlist_table];
629     [o_dropzone_view setFrame: [o_playlist_table frame]];
630     [o_playlist_table setHidden:YES];
631 }
632
633 - (void)hideDropZone
634 {
635     b_dropzone_active = NO;
636     [o_dropzone_view removeFromSuperview];
637     [o_playlist_table setHidden: NO];
638 }
639
640 - (void)hideSplitView:(BOOL)b_with_resize
641 {
642     // cancel pending pl resizes, in case of fast toggle between both modes
643     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];
644
645     if (b_with_resize) {
646         NSRect winrect = [self frame];
647         f_lastSplitViewHeight = [o_split_view frame].size.height;
648         winrect.size.height = winrect.size.height - f_lastSplitViewHeight;
649         winrect.origin.y = winrect.origin.y + f_lastSplitViewHeight;
650         [self setFrame: winrect display: YES animate: YES];
651     }
652
653     if (b_dark_interface) {
654         [self setContentMinSize: NSMakeSize(604., [o_controls_bar height] + [o_titlebar_view frame].size.height)];
655         [self setContentMaxSize: NSMakeSize(FLT_MAX, [o_controls_bar height] + [o_titlebar_view frame].size.height)];
656     } else {
657         [self setContentMinSize: NSMakeSize(604., [o_controls_bar height])];
658         [self setContentMaxSize: NSMakeSize(FLT_MAX, [o_controls_bar height])];
659     }
660
661     b_splitview_removed = YES;
662 }
663
664 - (void)showSplitView:(BOOL)b_with_resize
665 {
666     [self updateWindow];
667     if (b_dark_interface)
668         [self setContentMinSize:NSMakeSize(604., f_min_window_height + [o_titlebar_view frame].size.height)];
669     else
670         [self setContentMinSize:NSMakeSize(604., f_min_window_height)];
671     [self setContentMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
672
673     if (b_with_resize) {
674         NSRect winrect;
675         winrect = [self frame];
676         winrect.size.height = winrect.size.height + f_lastSplitViewHeight;
677         winrect.origin.y = winrect.origin.y - f_lastSplitViewHeight;
678         [self setFrame: winrect display: YES animate: YES];
679     }
680
681     // cancel pending pl resizes, in case of fast toggle between both modes
682     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resizePlaylistAfterCollapse) object:nil];
683     [self performSelector:@selector(resizePlaylistAfterCollapse) withObject: nil afterDelay:0.75];
684
685     b_splitview_removed = NO;
686 }
687
688 - (void)updateTimeSlider
689 {
690     [o_controls_bar updateTimeSlider];
691     [o_fspanel updatePositionAndTime];
692
693     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(updateTimeSlider)];
694 }
695
696 - (void)updateName
697 {
698     input_thread_t * p_input;
699     p_input = pl_CurrentInput(VLCIntf);
700     if (p_input) {
701         NSString *aString;
702
703         if (!config_GetPsz(VLCIntf, "video-title")) {
704             char *format = var_InheritString(VLCIntf, "input-title-format");
705             char *formated = str_format_meta(p_input, format);
706             free(format);
707             aString = [NSString stringWithUTF8String:formated];
708             free(formated);
709         } else
710             aString = [NSString stringWithUTF8String:config_GetPsz(VLCIntf, "video-title")];
711
712         char *uri = input_item_GetURI(input_GetItem(p_input));
713
714         NSURL * o_url = [NSURL URLWithString:[NSString stringWithUTF8String:uri]];
715         if ([o_url isFileURL]) {
716             [self setRepresentedURL: o_url];
717             [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
718                 [o_window setRepresentedURL:o_url];
719             }];
720         } else {
721             [self setRepresentedURL: nil];
722             [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
723                 [o_window setRepresentedURL:nil];
724             }];
725         }
726         free(uri);
727
728         if ([aString isEqualToString:@""]) {
729             if ([o_url isFileURL])
730                 aString = [[NSFileManager defaultManager] displayNameAtPath: [o_url path]];
731             else
732                 aString = [o_url absoluteString];
733         }
734
735         if ([aString length] > 0) {
736             [self setTitle: aString];
737             [[[VLCMain sharedInstance] voutController] updateWindowsUsingBlock:^(VLCVideoWindowCommon *o_window) {
738                 [o_window setTitle:aString];
739             }];
740
741             [o_fspanel setStreamTitle: aString];
742         } else {
743             [self setTitle: _NS("VLC media player")];
744             [self setRepresentedURL: nil];
745         }
746
747         vlc_object_release(p_input);
748     } else {
749         [self setTitle: _NS("VLC media player")];
750         [self setRepresentedURL: nil];
751     }
752 }
753
754 - (void)updateWindow
755 {
756     [o_controls_bar updateControls];
757     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(updateControls)];
758
759     bool b_seekable = false;
760
761     playlist_t * p_playlist = pl_Get(VLCIntf);
762     input_thread_t * p_input = playlist_CurrentInput(p_playlist);
763     if (p_input) {
764         /* seekable streams */
765         b_seekable = var_GetBool(p_input, "can-seek");
766
767         vlc_object_release(p_input);
768     }
769
770     [self updateTimeSlider];
771     if ([o_fspanel respondsToSelector:@selector(setSeekable:)])
772         [o_fspanel setSeekable: b_seekable];
773
774     PL_LOCK;
775     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
776         [self hideDropZone];
777     else
778         [self showDropZone];
779     PL_UNLOCK;
780     [o_sidebar_view setNeedsDisplay:YES];
781
782     [self _updatePlaylistTitle];
783 }
784
785 - (void)setPause
786 {
787     [o_controls_bar setPause];
788     [o_fspanel setPause];
789
790     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(setPause)];
791 }
792
793 - (void)setPlay
794 {
795     [o_controls_bar setPlay];
796     [o_fspanel setPlay];
797
798     [[[VLCMain sharedInstance] voutController] updateWindowsControlsBarWithSelector:@selector(setPlay)];
799 }
800
801 - (void)updateVolumeSlider
802 {
803     [[self controlsBar] updateVolumeSlider];
804     [o_fspanel setVolumeLevel: [[VLCCoreInteraction sharedInstance] volume]];
805 }
806
807 #pragma mark -
808 #pragma mark Video Output handling
809
810 - (void)videoplayWillBeStarted
811 {
812     if (!b_fullscreen)
813         frameBeforePlayback = [self frame];
814 }
815
816 - (void)setVideoplayEnabled
817 {
818     BOOL b_videoPlayback = [[VLCMain sharedInstance] activeVideoPlayback];
819         
820     if (!b_videoPlayback) {
821         if (!b_nonembedded && (!b_nativeFullscreenMode || (b_nativeFullscreenMode && !b_fullscreen)) && frameBeforePlayback.size.width > 0 && frameBeforePlayback.size.height > 0) {
822
823             // only resize back to minimum view of this is still desired final state
824             CGFloat f_threshold_height = f_min_video_height + [o_controls_bar height];
825             if(frameBeforePlayback.size.height > f_threshold_height || b_minimized_view) {
826
827                 if ([[VLCMain sharedInstance] isTerminating])
828                     [self setFrame:frameBeforePlayback display:YES];
829                 else
830                     [[self animator] setFrame:frameBeforePlayback display:YES];
831
832             }
833         }
834
835         frameBeforePlayback = NSMakeRect(0, 0, 0, 0);
836
837         // update fs button to reflect state for next startup
838         if (var_InheritBool(VLCIntf, "fullscreen") || var_GetBool(pl_Get(VLCIntf), "fullscreen")) {
839             [o_controls_bar setFullscreenState:YES];
840         }
841
842         [self makeFirstResponder: o_playlist_table];
843         [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
844
845         // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
846         [self setAlphaValue:1.0];
847     }
848
849     if (b_nativeFullscreenMode) {
850         if ([self hasActiveVideo] && [self fullscreen]) {
851             [[o_controls_bar bottomBarView] setHidden: b_videoPlayback];
852             [o_fspanel setActive: nil];
853         } else {
854             [[o_controls_bar bottomBarView] setHidden: NO];
855             [o_fspanel setNonActive: nil];
856         }
857     }
858 }
859
860 #pragma mark -
861 #pragma mark Lion native fullscreen handling
862 - (void)windowWillEnterFullScreen:(NSNotification *)notification
863 {
864     [super windowWillEnterFullScreen:notification];
865
866     // update split view frame after removing title bar
867     if (b_dark_interface) {
868         NSRect frame = [[self contentView] frame];
869         frame.origin.y += [o_controls_bar height];
870         frame.size.height -= [o_controls_bar height];
871         [o_split_view setFrame:frame];
872     }
873 }
874
875 - (void)windowWillExitFullScreen:(NSNotification *)notification
876 {
877     [super windowWillExitFullScreen: notification];
878
879     // update split view frame after readding title bar
880     if (b_dark_interface) {
881         NSRect frame = [o_split_view frame];
882         frame.size.height -= [o_titlebar_view frame].size.height;
883         [o_split_view setFrame:frame];
884     }
885 }
886 #pragma mark -
887 #pragma mark Fullscreen support
888
889 - (void)showFullscreenController
890 {
891     id currentWindow = [NSApp keyWindow];
892     if ([currentWindow respondsToSelector:@selector(hasActiveVideo)] && [currentWindow hasActiveVideo]) {
893         if ([currentWindow respondsToSelector:@selector(fullscreen)] && [currentWindow fullscreen] && ![[currentWindow videoView] isHidden]) {
894
895             if ([[VLCMain sharedInstance] activeVideoPlayback])
896                 [o_fspanel fadeIn];
897         }
898     }
899
900 }
901
902 #pragma mark -
903 #pragma mark split view delegate
904 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
905 {
906     if (dividerIndex == 0)
907         return 300.;
908     else
909         return proposedMax;
910 }
911
912 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
913 {
914     if (dividerIndex == 0)
915         return 100.;
916     else
917         return proposedMin;
918 }
919
920 - (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
921 {
922     return ([subview isEqual:o_left_split_view]);
923 }
924
925 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview
926 {
927     if ([subview isEqual:o_left_split_view])
928         return NO;
929     return YES;
930 }
931
932 - (void)mainSplitViewDidResizeSubviews:(id)object
933 {
934     f_lastLeftSplitViewWidth = [o_left_split_view frame].size.width;
935     config_PutInt(VLCIntf, "macosx-show-sidebar", ![o_split_view isSubviewCollapsed:o_left_split_view]);
936     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
937 }
938
939 - (void)toggleLeftSubSplitView
940 {
941     [o_split_view adjustSubviews];
942     if ([o_split_view isSubviewCollapsed:o_left_split_view])
943         [o_split_view setPosition:f_lastLeftSplitViewWidth ofDividerAtIndex:0];
944     else
945         [o_split_view setPosition:[o_split_view minPossiblePositionOfDividerAtIndex:0] ofDividerAtIndex:0];
946     [[[VLCMain sharedInstance] mainMenu] updateSidebarMenuItem];
947 }
948
949 #pragma mark -
950 #pragma mark private playlist magic
951 - (void)_updatePlaylistTitle
952 {
953     playlist_t * p_playlist = pl_Get(VLCIntf);
954     PL_LOCK;
955     playlist_item_t * currentPlaylistRoot = [[[VLCMain sharedInstance] playlist] currentPlaylistRoot];
956     PL_UNLOCK;
957     if (currentPlaylistRoot == p_playlist->p_local_category || currentPlaylistRoot == p_playlist->p_ml_category) {
958         if (currentPlaylistRoot == p_playlist->p_local_category)
959             [o_chosen_category_lbl setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
960         else
961             [o_chosen_category_lbl setStringValue: [_NS("Media Library") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
962     }
963 }
964
965 - (NSString *)_playbackDurationOfNode:(playlist_item_t*)node
966 {
967     if (!node)
968         return @"";
969
970     playlist_t * p_playlist = pl_Get(VLCIntf);
971     PL_LOCK;
972     mtime_t mt_duration = playlist_GetNodeDuration( node );
973     PL_UNLOCK;
974
975     if (mt_duration < 1)
976         return @"";
977
978     mt_duration = mt_duration / 1000000;
979
980     NSDate *date = [NSDate dateWithTimeIntervalSince1970:mt_duration];
981     NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
982     [formatter setDateFormat:@"HH:mm:ss"];
983     [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
984
985     NSString *playbackDuration = [NSString stringWithFormat:@" — %@",[formatter stringFromDate:date]];
986     [formatter release];
987     return playbackDuration;
988 }
989
990 #pragma mark -
991 #pragma mark Side Bar Data handling
992 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
993 - (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
994 {
995     //Works the same way as the NSOutlineView data source: `nil` means a parent item
996     if (item==nil)
997         return [o_sidebaritems count];
998     else
999         return [[item children] count];
1000 }
1001
1002
1003 - (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
1004 {
1005     //Works the same way as the NSOutlineView data source: `nil` means a parent item
1006     if (item==nil)
1007         return [o_sidebaritems objectAtIndex:index];
1008     else
1009         return [[item children] objectAtIndex:index];
1010 }
1011
1012
1013 - (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
1014 {
1015     return [item title];
1016 }
1017
1018 - (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
1019 {
1020     [item setTitle:object];
1021 }
1022
1023 - (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
1024 {
1025     return [item hasChildren];
1026 }
1027
1028
1029 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
1030 {
1031     if ([[item identifier] isEqualToString: @"playlist"] || [[item identifier] isEqualToString: @"medialibrary"])
1032         return YES;
1033
1034     return [item hasBadge];
1035 }
1036
1037
1038 - (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item
1039 {
1040     playlist_t * p_playlist = pl_Get(VLCIntf);
1041     NSInteger i_playlist_size = 0;
1042
1043     if ([[item identifier] isEqualToString: @"playlist"]) {
1044         PL_LOCK;
1045         i_playlist_size = p_playlist->p_local_category->i_children;
1046         PL_UNLOCK;
1047
1048         return i_playlist_size;
1049     }
1050     if ([[item identifier] isEqualToString: @"medialibrary"]) {
1051         PL_LOCK;
1052         if (p_playlist->p_ml_category)
1053             i_playlist_size = p_playlist->p_ml_category->i_children;
1054         PL_UNLOCK;
1055
1056         return i_playlist_size;
1057     }
1058
1059     return [item badgeValue];
1060 }
1061
1062
1063 - (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
1064 {
1065     return [item hasIcon];
1066 }
1067
1068
1069 - (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
1070 {
1071     return [item icon];
1072 }
1073
1074 - (NSMenu*)sourceList:(PXSourceList*)aSourceList menuForEvent:(NSEvent*)theEvent item:(id)item
1075 {
1076     if ([theEvent type] == NSRightMouseDown || ([theEvent type] == NSLeftMouseDown && ([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)) {
1077         if (item != nil) {
1078             if ([item sdtype] > 0)
1079             {
1080                 NSMenu *m = [[NSMenu alloc] init];
1081                 playlist_t * p_playlist = pl_Get(VLCIntf);
1082                 BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1083                 if (!sd_loaded)
1084                     [m addItemWithTitle:_NS("Enable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1085                 else
1086                     [m addItemWithTitle:_NS("Disable") action:@selector(sdmenuhandler:) keyEquivalent:@""];
1087                 [[m itemAtIndex:0] setRepresentedObject: [item identifier]];
1088
1089                 return [m autorelease];
1090             }
1091         }
1092     }
1093
1094     return nil;
1095 }
1096
1097 - (IBAction)sdmenuhandler:(id)sender
1098 {
1099     NSString * identifier = [sender representedObject];
1100     if ([identifier length] > 0 && ![identifier isEqualToString:@"lua{sd='freebox',longname='Freebox TV'}"]) {
1101         playlist_t * p_playlist = pl_Get(VLCIntf);
1102         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [identifier UTF8String]);
1103
1104         if (!sd_loaded)
1105             playlist_ServicesDiscoveryAdd(p_playlist, [identifier UTF8String]);
1106         else
1107             playlist_ServicesDiscoveryRemove(p_playlist, [identifier UTF8String]);
1108     }
1109 }
1110
1111 #pragma mark -
1112 #pragma mark Side Bar Delegate Methods
1113 /* taken under BSD-new from the PXSourceList sample project, adapted for VLC */
1114 - (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group
1115 {
1116     if ([[group identifier] isEqualToString:@"library"])
1117         return YES;
1118
1119     return NO;
1120 }
1121
1122 - (void)sourceListSelectionDidChange:(NSNotification *)notification
1123 {
1124     playlist_t * p_playlist = pl_Get(VLCIntf);
1125
1126     NSIndexSet *selectedIndexes = [o_sidebar_view selectedRowIndexes];
1127     id item = [o_sidebar_view itemAtRow:[selectedIndexes firstIndex]];
1128
1129     //Set the label text to represent the new selection
1130     if ([item sdtype] > -1 && [[item identifier] length] > 0) {
1131         BOOL sd_loaded = playlist_IsServicesDiscoveryLoaded(p_playlist, [[item identifier] UTF8String]);
1132         if (!sd_loaded)
1133             playlist_ServicesDiscoveryAdd(p_playlist, [[item identifier] UTF8String]);
1134     }
1135
1136     [o_chosen_category_lbl setStringValue:[item title]];
1137
1138     if ([[item identifier] isEqualToString:@"playlist"]) {
1139         [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_local_category];
1140         [o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
1141     } else if ([[item identifier] isEqualToString:@"medialibrary"]) {
1142         if (p_playlist->p_ml_category) {
1143             [[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_ml_category];
1144             [o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
1145         }
1146     } else {
1147         playlist_item_t * pl_item;
1148         PL_LOCK;
1149         pl_item = playlist_ChildSearchName(p_playlist->p_root, [[item untranslatedTitle] UTF8String]);
1150         PL_UNLOCK;
1151         [[[VLCMain sharedInstance] playlist] setPlaylistRoot: pl_item];
1152     }
1153
1154     // Note the order: first hide the podcast controls, then show the drop zone
1155     if ([[item identifier] isEqualToString:@"podcast{longname=\"Podcasts\"}"])
1156         [self showPodcastControls];
1157     else
1158         [self hidePodcastControls];
1159
1160     PL_LOCK;
1161     if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
1162         [self hideDropZone];
1163     else
1164         [self showDropZone];
1165     PL_UNLOCK;
1166
1167     [[NSNotificationCenter defaultCenter] postNotificationName: @"VLCMediaKeySupportSettingChanged"
1168                                                         object: nil
1169                                                       userInfo: nil];
1170 }
1171
1172 - (NSDragOperation)sourceList:(PXSourceList *)aSourceList validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1173 {
1174     if ([[item identifier] isEqualToString:@"playlist"] || [[item identifier] isEqualToString:@"medialibrary"]) {
1175         NSPasteboard *o_pasteboard = [info draggingPasteboard];
1176         if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"] || [[o_pasteboard types] containsObject: NSFilenamesPboardType])
1177             return NSDragOperationGeneric;
1178     }
1179     return NSDragOperationNone;
1180 }
1181
1182 - (BOOL)sourceList:(PXSourceList *)aSourceList acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1183 {
1184     NSPasteboard *o_pasteboard = [info draggingPasteboard];
1185
1186     playlist_t * p_playlist = pl_Get(VLCIntf);
1187     playlist_item_t *p_node;
1188
1189     if ([[item identifier] isEqualToString:@"playlist"])
1190         p_node = p_playlist->p_local_category;
1191     else
1192         p_node = p_playlist->p_ml_category;
1193
1194     if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
1195         NSArray *o_values = [[o_pasteboard propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
1196         NSUInteger count = [o_values count];
1197         NSMutableArray *o_array = [NSMutableArray arrayWithCapacity:count];
1198
1199         for(NSUInteger i = 0; i < count; i++) {
1200             NSDictionary *o_dic;
1201             char *psz_uri = vlc_path2uri([[o_values objectAtIndex:i] UTF8String], NULL);
1202             if (!psz_uri)
1203                 continue;
1204
1205             o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
1206
1207             free(psz_uri);
1208
1209             [o_array addObject: o_dic];
1210         }
1211
1212         [[[VLCMain sharedInstance] playlist] appendNodeArray:o_array inNode: p_node atPos:-1 enqueue:YES];
1213         return YES;
1214     }
1215     else if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
1216         NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
1217
1218         NSUInteger count = [array count];
1219         playlist_item_t * p_item = NULL;
1220
1221         PL_LOCK;
1222         for(NSUInteger i = 0; i < count; i++) {
1223             p_item = [[array objectAtIndex:i] pointerValue];
1224             if (!p_item) continue;
1225             playlist_NodeAddCopy(p_playlist, p_item, p_node, PLAYLIST_END);
1226         }
1227         PL_UNLOCK;
1228
1229         return YES;
1230     }
1231     return NO;
1232 }
1233
1234 - (id)sourceList:(PXSourceList *)aSourceList persistentObjectForItem:(id)item
1235 {
1236     return [item identifier];
1237 }
1238
1239 - (id)sourceList:(PXSourceList *)aSourceList itemForPersistentObject:(id)object
1240 {
1241     /* the following code assumes for sakes of simplicity that only the top level
1242      * items are allowed to have children */
1243
1244     NSArray * array = [NSArray arrayWithArray: o_sidebaritems]; // read-only arrays are noticebly faster
1245     NSUInteger count = [array count];
1246     if (count < 1)
1247         return nil;
1248
1249     for (NSUInteger x = 0; x < count; x++) {
1250         id item = [array objectAtIndex:x]; // save one objc selector call
1251         if ([[item identifier] isEqualToString:object])
1252             return item;
1253     }
1254
1255     return nil;
1256 }
1257
1258 #pragma mark -
1259 #pragma mark Podcast
1260
1261 - (IBAction)addPodcast:(id)sender
1262 {
1263     [NSApp beginSheet:o_podcast_subscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1264 }
1265
1266 - (IBAction)addPodcastWindowAction:(id)sender
1267 {
1268     [o_podcast_subscribe_window orderOut:sender];
1269     [NSApp endSheet: o_podcast_subscribe_window];
1270
1271     if (sender == o_podcast_subscribe_ok_btn && [[o_podcast_subscribe_url_fld stringValue] length] > 0) {
1272         NSMutableString * podcastConf = [[NSMutableString alloc] init];
1273         if (config_GetPsz(VLCIntf, "podcast-urls") != NULL)
1274             [podcastConf appendFormat:@"%s|", config_GetPsz(VLCIntf, "podcast-urls")];
1275
1276         [podcastConf appendString: [o_podcast_subscribe_url_fld stringValue]];
1277         config_PutPsz(VLCIntf, "podcast-urls", [podcastConf UTF8String]);
1278         var_SetString(pl_Get(VLCIntf), "podcast-urls", [podcastConf UTF8String]);
1279         [podcastConf release];
1280     }
1281 }
1282
1283 - (IBAction)removePodcast:(id)sender
1284 {
1285     if (config_GetPsz(VLCIntf, "podcast-urls") != NULL) {
1286         [o_podcast_unsubscribe_pop removeAllItems];
1287         [o_podcast_unsubscribe_pop addItemsWithTitles:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1288         [NSApp beginSheet:o_podcast_unsubscribe_window modalForWindow:self modalDelegate:self didEndSelector:NULL contextInfo:nil];
1289     }
1290 }
1291
1292 - (IBAction)removePodcastWindowAction:(id)sender
1293 {
1294     [o_podcast_unsubscribe_window orderOut:sender];
1295     [NSApp endSheet: o_podcast_unsubscribe_window];
1296
1297     if (sender == o_podcast_unsubscribe_ok_btn) {
1298         NSMutableArray * urls = [[NSMutableArray alloc] initWithArray:[[NSString stringWithUTF8String:config_GetPsz(VLCIntf, "podcast-urls")] componentsSeparatedByString:@"|"]];
1299         [urls removeObjectAtIndex: [o_podcast_unsubscribe_pop indexOfSelectedItem]];
1300         config_PutPsz(VLCIntf, "podcast-urls", [[urls componentsJoinedByString:@"|"] UTF8String]);
1301         var_SetString(pl_Get(VLCIntf), "podcast-urls", config_GetPsz(VLCIntf, "podcast-urls"));
1302         [urls release];
1303
1304         /* reload the podcast module, since it won't update its list when removing podcasts */
1305         playlist_t * p_playlist = pl_Get(VLCIntf);
1306         if (playlist_IsServicesDiscoveryLoaded(p_playlist, "podcast{longname=\"Podcasts\"}")) {
1307             playlist_ServicesDiscoveryRemove(p_playlist, "podcast{longname=\"Podcasts\"}");
1308             playlist_ServicesDiscoveryAdd(p_playlist, "podcast{longname=\"Podcasts\"}");
1309             [[[VLCMain sharedInstance] playlist] playlistUpdated];
1310         }
1311     }
1312 }
1313
1314 - (void)showPodcastControls
1315 {
1316     NSRect podcastViewDimensions = [o_podcast_view frame];
1317     NSRect rightSplitRect = [o_right_split_view frame];
1318     NSRect playlistTableRect = [o_playlist_table frame];
1319
1320     podcastViewDimensions.size.width = rightSplitRect.size.width;
1321     podcastViewDimensions.origin.x = podcastViewDimensions.origin.y = .0;
1322     [o_podcast_view setFrame:podcastViewDimensions];
1323
1324     playlistTableRect.origin.y = playlistTableRect.origin.y + podcastViewDimensions.size.height;
1325     playlistTableRect.size.height = playlistTableRect.size.height - podcastViewDimensions.size.height;
1326     [o_playlist_table setFrame:playlistTableRect];
1327     [o_playlist_table setNeedsDisplay:YES];
1328
1329     [o_right_split_view addSubview: o_podcast_view positioned: NSWindowAbove relativeTo: o_right_split_view];
1330     b_podcastView_displayed = YES;
1331 }
1332
1333 - (void)hidePodcastControls
1334 {
1335     if (b_podcastView_displayed) {
1336         NSRect podcastViewDimensions = [o_podcast_view frame];
1337         NSRect playlistTableRect = [o_playlist_table frame];
1338
1339         playlistTableRect.origin.y = playlistTableRect.origin.y - podcastViewDimensions.size.height;
1340         playlistTableRect.size.height = playlistTableRect.size.height + podcastViewDimensions.size.height;
1341
1342         [o_podcast_view removeFromSuperviewWithoutNeedingDisplay];
1343         [o_playlist_table setFrame: playlistTableRect];
1344         b_podcastView_displayed = NO;
1345     }
1346 }
1347
1348 @end
1349
1350 @implementation VLCDetachedVideoWindow
1351
1352 - (void)awakeFromNib
1353 {
1354     // sets lion fullscreen behaviour
1355     [super awakeFromNib];
1356     [self setAcceptsMouseMovedEvents: YES];
1357
1358     if (b_dark_interface) {
1359         [self setBackgroundColor: [NSColor clearColor]];
1360
1361         [self setOpaque: NO];
1362         [self display];
1363         [self setHasShadow:NO];
1364         [self setHasShadow:YES];
1365
1366         NSRect winrect = [self frame];
1367         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
1368
1369         [self setTitle: _NS("VLC media player")];
1370         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight, winrect.size.width, f_titleBarHeight)];
1371         [[self contentView] addSubview: o_titlebar_view positioned: NSWindowAbove relativeTo: nil];
1372
1373     } else {
1374         [self setBackgroundColor: [NSColor blackColor]];
1375     }
1376
1377     NSRect videoViewRect = [[self contentView] bounds];
1378     if (b_dark_interface)
1379         videoViewRect.size.height -= [o_titlebar_view frame].size.height;
1380     CGFloat f_bottomBarHeight = [[self controlsBar] height];
1381     videoViewRect.size.height -= f_bottomBarHeight;
1382     videoViewRect.origin.y = f_bottomBarHeight;
1383     [o_video_view setFrame: videoViewRect];
1384
1385     if (b_dark_interface) {
1386         o_color_backdrop = [[VLCColorView alloc] initWithFrame: [o_video_view frame]];
1387         [[self contentView] addSubview: o_color_backdrop positioned: NSWindowBelow relativeTo: o_video_view];
1388         [o_color_backdrop setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
1389
1390         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height] + [o_titlebar_view frame].size.height)];
1391     } else {
1392         [self setContentMinSize: NSMakeSize(363., f_min_video_height + [[self controlsBar] height])];
1393     }
1394 }
1395
1396 - (void)dealloc
1397 {
1398     if (b_dark_interface)
1399         [o_color_backdrop release];
1400
1401     [super dealloc];
1402 }
1403
1404 @end