1 /*****************************************************************************
2 * VLCMainWindow.m: VLCMainWindow implementation
3 *****************************************************************************
4 * Copyright (C) 2007 Pierre d'Herbemont
5 * Copyright (C) 2007 the VideoLAN team
8 * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
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.
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.
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 *****************************************************************************/
25 #import "VLCMainWindow.h"
26 #import "ImageAndTextCell.h"
27 #import "VLCMediaArrayController.h"
28 #import "VLCBrowsableVideoView.h"
29 #import "VLCAppAdditions.h"
30 #import "VLCFullScreenControllerWindow.h"
32 @interface VLCMainWindow (NavigatorViewHidingShowing)
33 @property float contentHeight; /* animatable, keep the mainSplitView cursor at the same place, enabling playlist(navigator) togling */
36 /******************************************************************************
37 * VLCMainWindow (CategoriesListDelegate)
39 @implementation VLCMainWindow (CategoriesListDelegate)
40 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
42 return [[item representedObject] isKindOfClass:[NSDictionary class]];
44 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
46 return !([[item representedObject] isKindOfClass:[NSDictionary class]]);
48 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
50 [cell setRepresentedObject:[item representedObject]];
54 /******************************************************************************
55 * VLCMainWindow (CategoriesListDataSource)
57 @implementation VLCMainWindow (CategoriesListDataSource)
59 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
63 if(![item respondsToSelector:@selector(representedObject)])
66 NSArray *droppedItems = [[info draggingPasteboard] propertyListForType:@"VLCMediaURLType"];
68 droppedItems = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
70 droppedItems = [[info draggingPasteboard] propertyListForType:NSURLPboardType];
72 NSAssert( droppedItems, @"Dropped an unsupported object type on the outline View" );
74 VLCMediaList * mediaList = [(VLCMedia *)[item representedObject] subitems];
76 for (i = 0; i < [droppedItems count]; i++)
78 NSString * filename = [droppedItems objectAtIndex:i];
79 VLCMedia *media = [VLCMedia mediaWithPath:filename];
81 [mediaList insertMedia:media atIndex:index+1];
87 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
89 NSArray *droppedItems = [[info draggingPasteboard] propertyListForType:@"VLCMediaURLType"];
91 droppedItems = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
93 droppedItems = [[info draggingPasteboard] propertyListForType:NSURLPboardType];
96 ![item respondsToSelector:@selector(representedObject)] ||
97 ![[item representedObject] isKindOfClass:[VLCMedia class]] )
99 return NSDragOperationNone;
102 return NSDragOperationMove;
106 /******************************************************************************
109 @implementation VLCMainWindow
111 @synthesize mediaPlayer;
112 @synthesize videoView;
114 - (void)awakeFromNib;
116 NSTableColumn * tableColumn;
118 /* Check ib outlets */
119 NSAssert( mainSplitView, @"No split view or wrong split view");
120 NSAssert( fullScreenButton, @"No fullscreen button");
122 /***********************************
123 * Init the media player
125 [mediaPlayer setVideoView:videoView];
127 /***********************************
128 * CategoriesList OutlineView content
130 /* categoriesTreeController */
131 categoriesTreeController = [[NSTreeController alloc] init];
132 [categoriesTreeController setContent:controller.categories];
134 [categoriesTreeController setChildrenKeyPath:@"childrenInCategoriesList"];
135 //[categoriesTreeController bind:@"contentArray" toObject:controller withKeyPath:@"arrayOfMasters" options:nil];
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];
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"];
149 [tableColumn setDataCell:cell];
152 [categoriesListView setIndentationMarkerFollowsCell:YES];
153 [categoriesListView setAutoresizesOutlineColumn:NO];
154 [categoriesListView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
155 [categoriesListView setDelegate:self];
157 [categoriesListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, @"VLCMediaURLType", nil]];
158 [categoriesListView setDataSource: self];
160 /***********************************
161 * mediaListView setup
164 mediaArrayController = [[VLCMediaArrayController alloc] init];
166 /* 1- Drag and drop */
167 [mediaListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]];
168 [mediaListView setDataSource:mediaArrayController];
170 /* 2- Double click */
171 [mediaListView setTarget:self];
172 [mediaListView setDoubleAction:@selector(mediaListViewItemDoubleClicked:)];
174 /* 3- binding for "title" column */
175 tableColumn = [mediaListView tableColumnWithIdentifier:@"title"];
176 [tableColumn bind:@"value" toObject: mediaArrayController withKeyPath:@"arrangedObjects.metaDictionary.title" options:nil];
178 /* 4- binding for "state" column */
179 tableColumn = [mediaListView tableColumnWithIdentifier:@"state"];
180 [tableColumn bind:@"value" toObject: mediaArrayController withKeyPath:@"arrangedObjects.stateAsImage" options:nil];
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];
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];
193 [mediaListView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
194 [mediaListView setAllowsTypeSelect:YES];
196 /***********************************
199 [videoView setItemsTree:controller.categories];
200 [videoView setNodeKeyPath:@"childrenInVideoView"];
201 [videoView setContentKeyPath:@"descriptionInVideoView"];
202 [videoView setTarget:self];
203 [videoView setAction:@selector(videoViewItemClicked:)];
205 /***********************************
208 [[self toolbar] setDelegate:self];
210 /***********************************
211 * Other interface element setup
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]];
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]];
222 [mediaReadingProgressSlider bind:@"enabled" toObject:mediaPlayer withKeyPath:@"media" options: [NSDictionary dictionaryWithObject:@"NonNilAsBoolTransformer" forKey:NSValueTransformerNameBindingOption]];
223 [mediaReadingProgressSlider bind:@"enabled2" toObject:mediaPlayer withKeyPath:@"seekable" options: nil];
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];
233 [navigatorViewToggleButton bind:@"value" toObject:self withKeyPath:@"navigatorViewVisible" options: nil];
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:)];
242 [mainSplitView setDelegate:self];
245 [mediaSoundVolume bind:@"value" toObject:[VLCLibrary sharedLibrary] withKeyPath:@"audio.volume" options: nil];
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)];
261 /* Last minute setup */
262 [categoriesListView expandItem:nil expandChildren:YES];
263 [categoriesListView selectRowIndexes:[NSIndexSet indexSetWithIndex:[categoriesListView numberOfRows] > 0 ? [categoriesListView numberOfRows]-1 : 0] byExtendingSelection:NO];
268 [navigatorView release];
269 [mediaPlayer release];
270 [categoriesTreeController release];
271 [mediaArrayController release];
275 - (void)mediaListViewItemDoubleClicked:(id)sender
277 if([[mediaArrayController selectedObjects] count] <= 0 )
279 [mediaPlayer setMedia:[[mediaArrayController selectedObjects] objectAtIndex:0]];
283 - (void)videoViewItemClicked:(id)sender
285 id object = [sender selectedObject];
286 NSAssert( [object isKindOfClass:[VLCMedia class]], @"Object is not a VLCMedia" );
288 [mediaPlayer setMedia:object];
292 - (BOOL)videoViewVisible
294 NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
295 return ([[[mainSplitView subviews] objectAtIndex:0] frame].size.height > 50.);
298 - (BOOL)navigatorViewVisible
300 NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
301 return ([[[mainSplitView subviews] objectAtIndex:1] frame].size.height > 6.);
305 - (void)setNavigatorViewVisible:(BOOL)visible
307 NSAssert( mainSplitView && [[mainSplitView subviews] count] == 2, @"No split view or wrong split view");
308 if(!([self navigatorViewVisible] ^ visible))
309 return; /* Nothing to do */
313 /* Show the navigator view (playlist view) */
314 if( navigatorHeight < 100.f ) navigatorHeight = 100.f;
315 if( ![self videoViewVisible] && ![self navigatorViewVisible] )
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];
324 [[self animator] setContentHeight:[[self contentView] frame].size.height + navigatorHeight ];
325 /* Hack, because sliding cause some glitches */
326 [navigatorView moveSubviewsToVisible];
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];
339 @implementation VLCMainWindow (SplitViewDelegating)
340 - (CGFloat)splitView:(NSSplitView *)sender constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)offset
342 CGFloat minHeight = 34.;
344 /* Hack, because sliding cause some glitches */
345 [navigatorView moveSubviewsToVisible];
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;
351 return proposedPosition;
354 - (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize
356 [sender adjustSubviews];
358 /* Hack, because sliding cause some glitches */
359 [navigatorView setFrame:[[navigatorView superview] bounds]];
360 [navigatorView moveSubviewsToVisible];
363 - (void)splitViewWillResizeSubviews:(NSNotification *)aNotification
365 /* Hack, because sliding cause some glitches */
366 [navigatorView moveSubviewsToVisible];
368 /* This could be changed from now on, so post a KVO notification */
369 [self willChangeValueForKey:@"navigatorViewVisible"];
371 - (void)splitViewDidResizeSubviews:(NSNotification *)aNotification
373 [self didChangeValueForKey:@"navigatorViewVisible"];
377 @implementation VLCMainWindow (NSWindowDelegating)
378 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
380 if( proposedFrameSize.height < 120.f)
381 proposedFrameSize.height = [self minSize].height;
382 return proposedFrameSize;
386 @implementation VLCMainWindow (NavigatorViewHidingShowing)
387 - (float)contentHeight
389 return [self contentRectForFrameRect:[self frame]].size.height;
392 - (void)setContentHeight:(float)height
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];
406 + (id)defaultAnimationForKey:(NSString *)key
408 if([key isEqualToString:@"contentHeight"])
410 return [CABasicAnimation animation];
412 return [super defaultAnimationForKey: key];
416 @implementation VLCMainWindow (NSToolbarDelegating)
417 /* Our item identifiers */
418 static NSString * VLCToolbarMediaControl = @"VLCToolbarMediaControl";
419 static NSString * VLCToolbarMediaAudioVolume = @"VLCToolbarMediaAudioVolume";
420 static NSString * VLCToolbarMediaDescription = @"VLCToolbarMediaDescription";
422 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
424 return [NSArray arrayWithObjects:
425 NSToolbarCustomizeToolbarItemIdentifier,
426 NSToolbarFlexibleSpaceItemIdentifier,
427 NSToolbarSpaceItemIdentifier,
428 NSToolbarSeparatorItemIdentifier,
429 VLCToolbarMediaControl,
430 VLCToolbarMediaAudioVolume,
431 VLCToolbarMediaDescription,
435 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
437 return [NSArray arrayWithObjects:
438 VLCToolbarMediaControl,
439 VLCToolbarMediaAudioVolume,
440 VLCToolbarMediaDescription,
444 - (NSToolbarItem *) toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
446 NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
448 if( [itemIdentifier isEqual: VLCToolbarMediaControl] )
450 [toolbarItem setLabel:@"Media Controls"];
451 [toolbarItem setPaletteLabel:@"Media Controls"];
453 [toolbarItem setView:toolbarMediaControl];
454 [toolbarItem setMinSize:[[toolbarItem view] frame].size];
455 [toolbarItem setMaxSize:[[toolbarItem view] frame].size];
457 /* TODO: setup a menu */
459 else if( [itemIdentifier isEqual: VLCToolbarMediaAudioVolume] )
461 [toolbarItem setLabel:@"Audio Volume"];
462 [toolbarItem setPaletteLabel:@"Audio Volume"];
464 [toolbarItem setView:toolbarMediaAudioVolume];
465 [toolbarItem setMinSize:[[toolbarItem view] frame].size];
466 [toolbarItem setMaxSize:[[toolbarItem view] frame].size];
468 /* TODO: setup a menu */
470 else if( [itemIdentifier isEqual: VLCToolbarMediaDescription] )
472 [toolbarItem setLabel:@"Media Description"];
473 [toolbarItem setPaletteLabel:@"Media Description"];
475 [toolbarItem setView:toolbarMediaDescription];
476 [toolbarItem setMinSize:[[toolbarItem view] frame].size];
477 [toolbarItem setMaxSize:NSMakeSize(10000 /* Can be really big */, NSHeight([[toolbarItem view] frame]))];
479 /* TODO: setup a menu */
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 */