MacOSX/VLC_app: port the fullscreen zoom and the fspanel from gui/macosx to VLC_app.
[vlc.git] / extras / MacOSX / VLC_app / Sources / VLCMainWindow.m
1 /*****************************************************************************
2  * VLCMainWindow.m: VLCMainWindow implementation
3  *****************************************************************************
4  * Copyright (C) 2007 Pierre d'Herbemont
5  * Copyright (C) 2007 the VideoLAN team
6  * $Id$
7  *
8  * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #import "VLCMainWindow.h"
26 #import "ImageAndTextCell.h"
27 #import "VLCMediaArrayController.h"
28 #import "VLCBrowsableVideoView.h"
29 #import "VLCAppAdditions.h"
30 #import "VLCFullScreenControllerWindow.h"
31
32 @interface VLCMainWindow (NavigatorViewHidingShowing)
33 @property float contentHeight; /* animatable, keep the mainSplitView cursor at the same place, enabling playlist(navigator) togling */
34 @end
35
36 /******************************************************************************
37  * VLCMainWindow (CategoriesListDelegate)
38  */
39 @implementation VLCMainWindow (CategoriesListDelegate)
40 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
41 {
42     return [[item representedObject] isKindOfClass:[NSDictionary class]];
43 }
44 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
45 {
46     return !([[item representedObject] isKindOfClass:[NSDictionary class]]);
47 }
48 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
49 {
50     [cell setRepresentedObject:[item representedObject]];
51 }
52 @end
53
54 /******************************************************************************
55  * VLCMainWindow (CategoriesListDataSource)
56  */
57 @implementation VLCMainWindow (CategoriesListDataSource)
58 /* Drag and drop */
59 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
60 {
61     int i;
62
63     if(![item respondsToSelector:@selector(representedObject)])
64         return NO;
65     
66     NSArray *droppedItems = [[info draggingPasteboard] propertyListForType:@"VLCMediaURLType"];
67     if( !droppedItems )
68         droppedItems = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
69     if( !droppedItems )
70         droppedItems = [[info draggingPasteboard] propertyListForType:NSURLPboardType];
71
72     NSAssert( droppedItems, @"Dropped an unsupported object type on the outline View" );
73
74     VLCMediaList * mediaList = [(VLCMedia *)[item representedObject] subitems];
75
76     for (i = 0; i < [droppedItems count]; i++)
77     {
78         NSString * filename = [droppedItems objectAtIndex:i];
79                 VLCMedia *media = [VLCMedia mediaWithPath:filename];
80         [mediaList lock];
81                 [mediaList insertMedia:media atIndex:index+1];
82         [mediaList unlock];
83     }
84     return YES;
85 }
86
87 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
88 {
89     NSArray *droppedItems = [[info draggingPasteboard] propertyListForType:@"VLCMediaURLType"];
90     if( !droppedItems )
91         droppedItems = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
92     if( !droppedItems )
93         droppedItems = [[info draggingPasteboard] propertyListForType:NSURLPboardType];
94
95     if(! droppedItems ||
96        ![item respondsToSelector:@selector(representedObject)] ||
97        ![[item representedObject] isKindOfClass:[VLCMedia class]] )
98     {
99         return NSDragOperationNone;
100     }
101
102     return NSDragOperationMove;
103 }
104 @end
105
106 /******************************************************************************
107  * VLCMainWindow
108  */
109 @implementation VLCMainWindow
110
111 @synthesize mediaPlayer;
112 @synthesize videoView;
113
114 - (void)awakeFromNib;
115 {
116     NSTableColumn * tableColumn;
117
118     /* Check ib outlets */
119     NSAssert( mainSplitView, @"No split view or wrong split view");
120     NSAssert( fullScreenButton, @"No fullscreen button");
121
122     /***********************************
123      * Init the media player
124      */
125     [mediaPlayer setVideoView:videoView];
126
127     /***********************************
128      * CategoriesList OutlineView content
129      */
130     /* categoriesTreeController */ 
131     categoriesTreeController = [[NSTreeController alloc] init];
132     [categoriesTreeController setContent:controller.categories];
133   
134     [categoriesTreeController setChildrenKeyPath:@"childrenInCategoriesList"];
135     //[categoriesTreeController bind:@"contentArray" toObject:controller withKeyPath:@"arrayOfMasters" options:nil];
136
137     /* Bind the "name" table column */
138     tableColumn = [categoriesListView tableColumnWithIdentifier:@"name"];
139         [tableColumn bind:@"value" toObject: categoriesTreeController withKeyPath:@"arrangedObjects.descriptionInCategoriesList" options:nil];
140     [tableColumn setEditable:YES];
141     /* FIXME: this doesn't work obviously. */
142         [tableColumn bind:@"editable" toObject: categoriesTreeController withKeyPath:@"arrangedObjects.editableInCategoriesList" options:nil];
143
144     /* Use an ImageAndTextCell in the "name" table column */
145     ImageAndTextCell * cell = [[ImageAndTextCell alloc] init];
146     [cell setFont:[[tableColumn dataCell] font]];
147     [cell setImageKeyPath:@"image"];
148
149     [tableColumn setDataCell:cell];
150
151     /* Other setup */
152     [categoriesListView setIndentationMarkerFollowsCell:YES];
153     [categoriesListView setAutoresizesOutlineColumn:NO];
154     [categoriesListView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
155     [categoriesListView setDelegate:self];
156
157     [categoriesListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, @"VLCMediaURLType", nil]];
158     [categoriesListView setDataSource: self];
159
160     /***********************************
161      * mediaListView setup
162      */
163
164     mediaArrayController = [[VLCMediaArrayController alloc] init];
165
166     /* 1- Drag and drop */
167     [mediaListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]];
168     [mediaListView setDataSource:mediaArrayController];
169
170     /* 2- Double click */
171     [mediaListView setTarget:self];
172     [mediaListView setDoubleAction:@selector(mediaListViewItemDoubleClicked:)];
173
174         /* 3- binding for "title" column */
175     tableColumn = [mediaListView tableColumnWithIdentifier:@"title"];
176         [tableColumn bind:@"value" toObject: mediaArrayController withKeyPath:@"arrangedObjects.metaDictionary.title" options:nil];
177
178         /* 4- binding for "state" column */
179     tableColumn = [mediaListView tableColumnWithIdentifier:@"state"];
180         [tableColumn bind:@"value" toObject: mediaArrayController withKeyPath:@"arrangedObjects.stateAsImage" options:nil];
181
182     /* 5- Search & Predicate */
183     NSMutableDictionary * bindingOptions = [NSMutableDictionary dictionary];
184     [bindingOptions setObject:@"metaDictionary.title contains[c] $value" forKey:NSPredicateFormatBindingOption];
185     [bindingOptions setObject:@"No Title" forKey:NSDisplayNameBindingOption];
186     [mediaListSearchField bind:@"predicate" toObject: mediaArrayController withKeyPath:@"filterPredicate" options:bindingOptions];
187     
188     /* 6- Bind the @"contentArray" and contentMediaList of the mediaArrayController */
189     [mediaArrayController bind:@"contentArray" toObject:categoriesTreeController withKeyPath:@"selection.childrenInCategoriesListForDetailView.media" options:nil];
190     [mediaArrayController bind:@"contentMediaList" toObject:categoriesTreeController withKeyPath:@"selection.childrenInCategoriesListForDetailView.parentMediaList" options:nil];
191     
192     /* 7- Aspect */
193     [mediaListView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
194     [mediaListView setAllowsTypeSelect:YES];
195
196     /***********************************
197      * videoView setup
198      */
199     [videoView setItemsTree:controller.categories];
200     [videoView setNodeKeyPath:@"childrenInVideoView"];
201     [videoView setContentKeyPath:@"descriptionInVideoView"];
202     [videoView setTarget:self];
203     [videoView setAction:@selector(videoViewItemClicked:)];
204     
205     /***********************************
206      * Toolbar setup
207      */
208     [[self toolbar] setDelegate:self];
209
210     /***********************************
211      * Other interface element setup
212      */
213
214     [mediaListItemsCount bind:@"displayPatternValue1" toObject:mediaArrayController withKeyPath:@"arrangedObjects.@count" options:[NSDictionary dictionaryWithObject:@"%{value1}@ items" forKey:NSDisplayPatternBindingOption]];
215     [mediaListItemFetchedStatus bind:@"animate" toObject:categoriesTreeController withKeyPath:@"selection.currentlyFetchingItems" options:[NSDictionary dictionaryWithObject:@"%{value1}@ items" forKey:NSDisplayPatternBindingOption]];
216
217     [fillScreenButton bind:@"value" toObject:videoView withKeyPath:@"fillScreen" options: nil];
218     [fullScreenButton bind:@"value" toObject:videoView withKeyPath:@"fullScreen" options: nil];
219     [fullScreenButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
220     [fillScreenButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
221
222     [mediaReadingProgressSlider bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
223     [mediaReadingProgressSlider bind:@"enabled2" toObject:mediaPlayer withKeyPath:@"seekable" options: nil];
224
225     [mediaReadingProgressSlider bind:@"value" toObject:mediaPlayer withKeyPath:@"position" options:
226         [NSDictionary dictionaryWithObjectsAndKeys:@"Float10000FoldTransformer", NSValueTransformerNameBindingOption,
227                                                   [NSNumber numberWithBool:NO], NSConditionallySetsEnabledBindingOption, nil ]];
228     [mediaReadingProgressText bind:@"value" toObject:mediaPlayer withKeyPath:@"time.stringValue" options: nil];
229     [mediaDescriptionText bind:@"value" toObject:mediaPlayer withKeyPath:@"description" options: nil];
230     [self bind:@"representedFilename" toObject:mediaPlayer withKeyPath:@"media.url" options: [NSDictionary dictionaryWithObject:@"URLToRepresentedFileNameTransformer" forKey:NSValueTransformerNameBindingOption]];
231     [self bind:@"title" toObject:mediaPlayer withKeyPath:@"description" options: nil];
232
233     [navigatorViewToggleButton bind:@"value" toObject:self withKeyPath:@"navigatorViewVisible" options: nil];
234
235     /* Playlist buttons */
236     [removePlaylistButton bind:@"enabled" toObject:categoriesTreeController withKeyPath:@"selection.editableInCategoriesList" options: nil];
237     [removePlaylistButton setTarget:categoriesTreeController];
238     [removePlaylistButton setAction:@selector(remove:)];
239     [addPlaylistButton setTarget:controller];
240     [addPlaylistButton setAction:@selector(addPlaylist:)];
241
242     [mainSplitView setDelegate:self];
243
244     /* Sound */
245     [mediaSoundVolume bind:@"value" toObject:[VLCLibrary sharedLibrary] withKeyPath:@"audio.volume" options: nil];
246
247     /* mediaPlayer */
248     [mediaPlayerPlayPauseStopButton bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
249     [mediaPlayerPlayPauseStopButton bind:@"state"   toObject:mediaPlayer withKeyPath:@"playing" options: nil];
250     [mediaPlayerPlayPauseStopButton bind:@"alternateImage" toObject:mediaPlayer withKeyPath:@"stateAsButtonAlternateImage" options: nil];
251     [mediaPlayerPlayPauseStopButton bind:@"image"   toObject:mediaPlayer withKeyPath:@"stateAsButtonImage" options: nil];
252     [mediaPlayerBackwardPrevButton  bind:@"enabled" toObject:mediaPlayer withKeyPath:@"playing" options: nil];
253     [mediaPlayerForwardNextButton   bind:@"enabled" toObject:mediaPlayer withKeyPath:@"playing" options: nil];
254     [mediaPlayerForwardNextButton   setTarget:mediaPlayer];
255     [mediaPlayerForwardNextButton   setAction:@selector(fastForward)];
256     [mediaPlayerBackwardPrevButton  setTarget:mediaPlayer];
257     [mediaPlayerBackwardPrevButton  setAction:@selector(rewind)];
258     [mediaPlayerPlayPauseStopButton setTarget:mediaPlayer];
259     [mediaPlayerPlayPauseStopButton setAction:@selector(pause)];
260
261     /* Last minute setup */
262     [categoriesListView expandItem:nil expandChildren:YES];
263     [categoriesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[categoriesListView numberOfRows] > 0 ? [categoriesListView numberOfRows]-1 : 0] byExtendingSelection:NO];
264 }
265
266 - (void)dealloc
267 {
268     [navigatorView release];
269     [mediaPlayer release];
270     [categoriesTreeController release];
271     [mediaArrayController release];
272     [super dealloc];
273 }
274
275 - (void)mediaListViewItemDoubleClicked:(id)sender
276 {
277     if([[mediaArrayController selectedObjects] count] <= 0 )
278         return;
279     [mediaPlayer setMedia:[[mediaArrayController selectedObjects] objectAtIndex:0]];
280     [mediaPlayer play];
281 }
282
283 - (void)videoViewItemClicked:(id)sender
284 {
285     id object = [sender selectedObject];
286     NSAssert( [object isKindOfClass:[VLCMedia class]], @"Object is not a VLCMedia" );
287
288     [mediaPlayer setMedia:object];
289     [mediaPlayer play];
290 }
291
292 - (BOOL)videoViewVisible
293 {
294     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
295     return  ([[[mainSplitView subviews] objectAtIndex:0] frame].size.height > 50.);
296 }
297
298 - (BOOL)navigatorViewVisible
299 {
300     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
301     return  ([[[mainSplitView subviews] objectAtIndex:1] frame].size.height > 6.);
302 }
303
304
305 - (void)setNavigatorViewVisible:(BOOL)visible
306 {
307     NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
308     if(!([self navigatorViewVisible] ^ visible))
309         return; /* Nothing to do */
310     
311     if(visible)
312     {
313         /* Show the navigator view (playlist view) */
314         if( navigatorHeight < 100.f ) navigatorHeight = 100.f;
315         if( ![self videoViewVisible] && ![self navigatorViewVisible] )
316         {
317             /* Nothing is visible, only our toolbar */
318             NSRect frame = [self frame];
319             frame.origin.y += navigatorHeight;
320             frame.size.height += navigatorHeight;
321             [[self animator] setFrame:frame display:YES];
322         }
323         else
324             [[self animator] setContentHeight:[[self contentView] frame].size.height + navigatorHeight ];
325         /* Hack, because sliding cause some glitches */
326         [navigatorView moveSubviewsToVisible];
327     }
328     else
329     {
330         /* Hide the navigator view (playlist view) */
331         navigatorHeight = [navigatorView bounds].size.height;
332         [[self animator] setContentHeight:[[self contentView] frame].size.height - navigatorHeight];
333         /* Hack, because sliding cause some glitches */
334         [navigatorView moveSubviewsToVisible];
335     }
336 }
337 @end
338
339 @implementation VLCMainWindow (SplitViewDelegating)
340 - (CGFloat)splitView:(NSSplitView *)sender constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)offset
341 {
342     CGFloat minHeight = 34.;
343
344     /* Hack, because sliding cause some glitches */
345     [navigatorView moveSubviewsToVisible];
346
347     /* Make a stuck point at the bottom of the nav view */
348     if( [sender bounds].size.height - proposedPosition < minHeight )
349          return [sender bounds].size.height;
350
351     return proposedPosition;
352 }
353
354 - (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize
355 {
356     [sender adjustSubviews];
357
358     /* Hack, because sliding cause some glitches */
359     [navigatorView setFrame:[[navigatorView superview] bounds]];
360     [navigatorView moveSubviewsToVisible];
361 }
362
363 - (void)splitViewWillResizeSubviews:(NSNotification *)aNotification
364 {
365     /* Hack, because sliding cause some glitches */
366     [navigatorView moveSubviewsToVisible];
367
368     /* This could be changed from now on, so post a KVO notification */
369     [self willChangeValueForKey:@"navigatorViewVisible"];
370 }
371 - (void)splitViewDidResizeSubviews:(NSNotification *)aNotification
372 {
373     [self didChangeValueForKey:@"navigatorViewVisible"];
374 }
375 @end
376
377 @implementation VLCMainWindow (NSWindowDelegating)
378 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
379 {
380     if( proposedFrameSize.height < 120.f)
381         proposedFrameSize.height = [self minSize].height;
382     return proposedFrameSize;
383 }
384 @end
385
386 @implementation VLCMainWindow (NavigatorViewHidingShowing)
387 - (float)contentHeight
388 {
389     return [self contentRectForFrameRect:[self frame]].size.height;
390 }
391
392 - (void)setContentHeight:(float)height
393 {
394     /* Set the Height while keeping the mainSplitView at his current position */
395     [mainSplitView setFixedCursorDuringResize:YES];
396     NSRect contentRect = [self contentRectForFrameRect:[self frame]];
397     float delta = height - contentRect.size.height;
398     contentRect.size.height = height;
399         NSRect windowFrame = [self frameRectForContentRect:contentRect];
400     windowFrame.origin.y -= delta;
401     windowFrame = [self constrainFrameRect:windowFrame toScreen:[self screen]];
402     [self setFrame:windowFrame display:YES];
403     [mainSplitView setFixedCursorDuringResize:NO];
404 }
405
406 + (id)defaultAnimationForKey:(NSString *)key
407 {
408     if([key isEqualToString:@"contentHeight"])
409     {
410         return [CABasicAnimation animation];
411     }
412     return [super defaultAnimationForKey: key];
413 }
414 @end
415
416 @implementation VLCMainWindow (NSToolbarDelegating)
417 /* Our item identifiers */
418 static NSString * VLCToolbarMediaControl     = @"VLCToolbarMediaControl";
419 static NSString * VLCToolbarMediaAudioVolume = @"VLCToolbarMediaAudioVolume";
420 static NSString * VLCToolbarMediaDescription = @"VLCToolbarMediaDescription";
421
422 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
423 {
424     return [NSArray arrayWithObjects:
425                         NSToolbarCustomizeToolbarItemIdentifier,
426                         NSToolbarFlexibleSpaceItemIdentifier,
427                         NSToolbarSpaceItemIdentifier,
428                         NSToolbarSeparatorItemIdentifier,
429                         VLCToolbarMediaControl,
430                         VLCToolbarMediaAudioVolume,
431                         VLCToolbarMediaDescription,
432                         nil ];
433 }
434
435 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
436 {
437     return [NSArray arrayWithObjects:
438                         VLCToolbarMediaControl,
439                         VLCToolbarMediaAudioVolume,
440                         VLCToolbarMediaDescription,
441                         nil ];
442 }
443
444 - (NSToolbarItem *) toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
445 {
446     NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
447  
448     if( [itemIdentifier isEqual: VLCToolbarMediaControl] )
449     {
450         [toolbarItem setLabel:@"Media Controls"];
451         [toolbarItem setPaletteLabel:@"Media Controls"];
452      
453         [toolbarItem setView:toolbarMediaControl];
454         [toolbarItem setMinSize:[[toolbarItem view] frame].size];
455         [toolbarItem setMaxSize:[[toolbarItem view] frame].size];
456
457         /* TODO: setup a menu */
458     }
459     else if( [itemIdentifier isEqual: VLCToolbarMediaAudioVolume] )
460     {
461         [toolbarItem setLabel:@"Audio Volume"];
462         [toolbarItem setPaletteLabel:@"Audio Volume"];
463      
464         [toolbarItem setView:toolbarMediaAudioVolume];
465         [toolbarItem setMinSize:[[toolbarItem view] frame].size];
466         [toolbarItem setMaxSize:[[toolbarItem view] frame].size];
467
468         /* TODO: setup a menu */
469     }
470     else  if( [itemIdentifier isEqual: VLCToolbarMediaDescription] )
471     {
472         [toolbarItem setLabel:@"Media Description"];
473         [toolbarItem setPaletteLabel:@"Media Description"];
474      
475         [toolbarItem setView:toolbarMediaDescription];
476         [toolbarItem setMinSize:[[toolbarItem view] frame].size];
477         [toolbarItem setMaxSize:NSMakeSize(10000 /* Can be really big */, NSHeight([[toolbarItem view] frame]))];
478
479         /* TODO: setup a menu */
480     }
481     else
482     {
483         /* itemIdentifier referred to a toolbar item that is not
484          * provided or supported by us or Cocoa
485          * Returning nil will inform the toolbar
486          * that this kind of item is not supported */
487         toolbarItem = nil;
488     }
489     return toolbarItem;
490 }
491 @end