You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
936 lines
33 KiB
936 lines
33 KiB
/*****************************************************************************
|
|
* VLCLibraryWindow.m: MacOS X interface module
|
|
*****************************************************************************
|
|
* Copyright (C) 2019 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Felix Paul Kühne <fkuehne # videolan -dot- org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#import "VLCLibraryWindow.h"
|
|
#include "VLCLibraryDataTypes.h"
|
|
#import "extensions/NSString+Helpers.h"
|
|
#import "extensions/NSFont+VLCAdditions.h"
|
|
#import "extensions/NSColor+VLCAdditions.h"
|
|
#import "extensions/NSView+VLCAdditions.h"
|
|
#import "main/VLCMain.h"
|
|
|
|
#import "playlist/VLCPlayerController.h"
|
|
#import "playlist/VLCPlaylistController.h"
|
|
#import "playlist/VLCPlaylistDataSource.h"
|
|
#import "playlist/VLCPlaylistSortingMenuController.h"
|
|
|
|
#import "library/VLCLibraryController.h"
|
|
#import "library/VLCLibraryCollectionViewItem.h"
|
|
#import "library/VLCLibraryModel.h"
|
|
#import "library/VLCLibraryCollectionViewSupplementaryElementView.h"
|
|
#import "library/VLCLibrarySortingMenuController.h"
|
|
#import "library/VLCLibraryNavigationStack.h"
|
|
#import "library/VLCLibraryUIUnits.h"
|
|
|
|
#import "library/video-library/VLCLibraryVideoCollectionViewsStackViewController.h"
|
|
#import "library/video-library/VLCLibraryVideoTableViewDataSource.h"
|
|
#import "library/video-library/VLCLibraryVideoViewController.h"
|
|
|
|
#import "library/audio-library/VLCLibraryAlbumTableCellView.h"
|
|
#import "library/audio-library/VLCLibraryAudioViewController.h"
|
|
|
|
#import "media-source/VLCMediaSourceBaseDataSource.h"
|
|
#import "media-source/VLCLibraryMediaSourceViewController.h"
|
|
|
|
#import "views/VLCCustomWindowButton.h"
|
|
#import "views/VLCDragDropView.h"
|
|
#import "views/VLCRoundedCornerTextField.h"
|
|
|
|
#import "windows/mainwindow/VLCControlsBarCommon.h"
|
|
|
|
#import "windows/video/VLCVoutView.h"
|
|
#import "windows/video/VLCVideoOutputProvider.h"
|
|
#import "windows/video/VLCMainVideoViewController.h"
|
|
|
|
#import "windows/VLCOpenWindowController.h"
|
|
#import "windows/VLCOpenInputMetadata.h"
|
|
|
|
#import <vlc_common.h>
|
|
#import <vlc_url.h>
|
|
|
|
const CGFloat VLCLibraryWindowMinimalWidth = 604.;
|
|
const CGFloat VLCLibraryWindowMinimalHeight = 307.;
|
|
const CGFloat VLCLibraryWindowDefaultPlaylistWidth = 340.;
|
|
const CGFloat VLCLibraryWindowMinimalPlaylistWidth = 170.;
|
|
const NSUserInterfaceItemIdentifier VLCLibraryWindowIdentifier = @"VLCLibraryWindow";
|
|
|
|
@interface VLCLibraryWindow () <VLCDragDropTarget, NSSplitViewDelegate>
|
|
{
|
|
CGFloat _lastPlaylistWidthBeforeCollaps;
|
|
|
|
NSInteger _currentSelectedSegment;
|
|
NSInteger _currentSelectedViewModeSegment;
|
|
|
|
BOOL _autohideTitlebar;
|
|
NSTimer *_hideTitlebarTimer;
|
|
}
|
|
|
|
- (IBAction)goToBrowseSection:(id)sender;
|
|
|
|
@end
|
|
|
|
static int ShowFullscreenController(vlc_object_t *p_this, const char *psz_variable,
|
|
vlc_value_t old_val, vlc_value_t new_val, void *param)
|
|
{
|
|
@autoreleasepool {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:VLCVideoWindowShouldShowFullscreenController
|
|
object:nil];
|
|
});
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
}
|
|
|
|
static int ShowController(vlc_object_t *p_this, const char *psz_variable,
|
|
vlc_value_t old_val, vlc_value_t new_val, void *param)
|
|
{
|
|
@autoreleasepool {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:VLCWindowShouldShowController
|
|
object:nil];
|
|
});
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
}
|
|
|
|
static void addShadow(NSImageView *__unsafe_unretained imageView)
|
|
{
|
|
NSShadow *buttonShadow = [[NSShadow alloc] init];
|
|
|
|
buttonShadow.shadowBlurRadius = 15.0f;
|
|
buttonShadow.shadowOffset = CGSizeMake(0.0f, -5.0f);
|
|
buttonShadow.shadowColor = [NSColor blackColor];
|
|
|
|
imageView.wantsLayer = YES;
|
|
imageView.shadow = buttonShadow;
|
|
}
|
|
|
|
@implementation VLCLibraryWindow
|
|
|
|
- (void)awakeFromNib
|
|
{
|
|
[super awakeFromNib];
|
|
self.identifier = VLCLibraryWindowIdentifier;
|
|
|
|
if(@available(macOS 10.12, *)) {
|
|
self.tabbingMode = NSWindowTabbingModeDisallowed;
|
|
}
|
|
|
|
self.toolbar.allowsUserCustomization = NO;
|
|
|
|
VLCMain *mainInstance = [VLCMain sharedInstance];
|
|
_playlistController = [mainInstance playlistController];
|
|
|
|
libvlc_int_t *libvlc = vlc_object_instance(getIntf());
|
|
var_AddCallback(libvlc, "intf-toggle-fscontrol", ShowFullscreenController, (__bridge void *)self);
|
|
var_AddCallback(libvlc, "intf-show", ShowController, (__bridge void *)self);
|
|
|
|
self.navigationStack = [[VLCLibraryNavigationStack alloc] init];
|
|
self.navigationStack.delegate = self;
|
|
|
|
self.videoViewController.view.frame = self.mainSplitView.frame;
|
|
self.videoViewController.view.hidden = YES;
|
|
self.videoViewController.displayLibraryControls = YES;
|
|
[self hideControlsBar];
|
|
|
|
[self.gridVsListSegmentedControl setToolTip: _NS("Grid View or List View")];
|
|
[self.librarySortButton setToolTip: _NS("Select Sorting Mode")];
|
|
[self.playQueueToggle setToolTip: _NS("Toggle Playqueue")];
|
|
|
|
[self.gridVsListSegmentedControl setHidden:NO];
|
|
[self.librarySortButton setHidden:NO];
|
|
[self.librarySearchField setEnabled:YES];
|
|
|
|
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
|
[notificationCenter addObserver:self
|
|
selector:@selector(shouldShowFullscreenController:)
|
|
name:VLCVideoWindowShouldShowFullscreenController
|
|
object:nil];
|
|
[notificationCenter addObserver:self
|
|
selector:@selector(shouldShowController:)
|
|
name:VLCWindowShouldShowController
|
|
object:nil];
|
|
[notificationCenter addObserver:self
|
|
selector:@selector(shuffleStateUpdated:)
|
|
name:VLCPlaybackOrderChanged
|
|
object:nil];
|
|
[notificationCenter addObserver:self
|
|
selector:@selector(repeatStateUpdated:)
|
|
name:VLCPlaybackRepeatChanged
|
|
object:nil];
|
|
[notificationCenter addObserver:self
|
|
selector:@selector(updateViewCellDimensionsBasedOnSetting:)
|
|
name:VLCConfigurationChangedNotification
|
|
object:nil];
|
|
[notificationCenter addObserver:self
|
|
selector:@selector(playerStateChanged:)
|
|
name:VLCPlayerCurrentMediaItemChanged
|
|
object:nil];
|
|
[notificationCenter addObserver:self
|
|
selector:@selector(playerStateChanged:)
|
|
name:VLCPlayerStateChanged
|
|
object:nil];
|
|
|
|
if (@available(macOS 10.14, *)) {
|
|
[[NSApplication sharedApplication] addObserver:self
|
|
forKeyPath:@"effectiveAppearance"
|
|
options:NSKeyValueObservingOptionNew
|
|
context:nil];
|
|
|
|
_mediaToolBar.centeredItemIdentifier = _segmentedTitleControlToolbarItem.itemIdentifier;
|
|
}
|
|
|
|
_currentSelectedSegment = -1; // To enforce action on the selected segment
|
|
_segmentedTitleControl.segmentCount = 4;
|
|
[_segmentedTitleControl setTarget:self];
|
|
[_segmentedTitleControl setLabel:_NS("Video") forSegment:VLCLibraryVideoSegment];
|
|
[_segmentedTitleControl setLabel:_NS("Music") forSegment:VLCLibraryMusicSegment];
|
|
[_segmentedTitleControl setLabel:_NS("Browse") forSegment:VLCLibraryBrowseSegment];
|
|
[_segmentedTitleControl setLabel:_NS("Streams") forSegment:VLCLibraryStreamsSegment];
|
|
[_segmentedTitleControl sizeToFit];
|
|
|
|
_playlistDragDropView.dropTarget = self;
|
|
_playlistCounterTextField.useStrongRounding = YES;
|
|
_playlistCounterTextField.font = [NSFont VLCplaylistSelectedItemLabelFont];
|
|
_playlistCounterTextField.textColor = [NSColor VLClibraryAnnotationColor];
|
|
_playlistCounterTextField.hidden = YES;
|
|
|
|
_playlistDataSource = [[VLCPlaylistDataSource alloc] init];
|
|
_playlistDataSource.playlistController = _playlistController;
|
|
_playlistDataSource.tableView = _playlistTableView;
|
|
_playlistDataSource.dragDropView = _playlistDragDropView;
|
|
_playlistDataSource.counterTextField = _playlistCounterTextField;
|
|
[_playlistDataSource prepareForUse];
|
|
_playlistController.playlistDataSource = _playlistDataSource;
|
|
|
|
_playlistTableView.dataSource = _playlistDataSource;
|
|
_playlistTableView.delegate = _playlistDataSource;
|
|
[self updateViewCellDimensionsBasedOnSetting:nil];
|
|
[_playlistTableView reloadData];
|
|
|
|
_libraryVideoViewController = [[VLCLibraryVideoViewController alloc] initWithLibraryWindow:self];
|
|
_libraryAudioViewController = [[VLCLibraryAudioViewController alloc] initWithLibraryWindow:self];
|
|
_libraryMediaSourceViewController = [[VLCLibraryMediaSourceViewController alloc] initWithLibraryWindow:self];
|
|
|
|
self.upNextLabel.font = [NSFont VLClibrarySectionHeaderFont];
|
|
self.upNextLabel.stringValue = _NS("Playlist");
|
|
self.openMediaButton.title = _NS("Open media...");
|
|
self.dragDropImageBackgroundBox.fillColor = [NSColor VLClibrarySeparatorLightColor];
|
|
|
|
[self updateColorsBasedOnAppearance:self.effectiveAppearance];
|
|
|
|
_mainSplitView.delegate = self;
|
|
_lastPlaylistWidthBeforeCollaps = VLCLibraryWindowDefaultPlaylistWidth;
|
|
|
|
[self setViewForSelectedSegment];
|
|
[self repeatStateUpdated:nil];
|
|
[self shuffleStateUpdated:nil];
|
|
|
|
// HACK: The size of the segmented title buttons is not always correctly calculated
|
|
// especially when the text we are setting differs from what is set in the storyboard.
|
|
// Hiding and showing the toolbar again must trigger something that causes the width
|
|
// of the buttons to be correctly recalculated, working around this issue
|
|
[self toggleToolbarShown:self];
|
|
[self toggleToolbarShown:self];
|
|
|
|
[self updatePlayqueueToggleState];
|
|
|
|
_autohideTitlebar = NO;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
if (@available(macOS 10.14, *)) {
|
|
[[NSApplication sharedApplication] removeObserver:self forKeyPath:@"effectiveAppearance"];
|
|
}
|
|
|
|
libvlc_int_t *libvlc = vlc_object_instance(getIntf());
|
|
var_DelCallback(libvlc, "intf-toggle-fscontrol", ShowFullscreenController, (__bridge void *)self);
|
|
var_DelCallback(libvlc, "intf-show", ShowController, (__bridge void *)self);
|
|
}
|
|
|
|
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
|
|
{
|
|
[super encodeRestorableStateWithCoder:coder];
|
|
[coder encodeInteger:_segmentedTitleControl.selectedSegment forKey:@"macosx-library-selected-segment"];
|
|
[coder encodeInteger:_gridVsListSegmentedControl.selectedSegment forKey:@"macosx-library-view-mode-selected-segment"];
|
|
[coder encodeInteger:_audioSegmentedControl.selectedSegment forKey:@"macosx-library-audio-view-selected-segment"];
|
|
}
|
|
|
|
#pragma mark - appearance setters
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
ofObject:(id)object
|
|
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
|
|
context:(void *)context
|
|
{
|
|
if ([keyPath isEqualToString:@"effectiveAppearance"]) {
|
|
NSAppearance *effectiveAppearance = change[NSKeyValueChangeNewKey];
|
|
[self updateColorsBasedOnAppearance:effectiveAppearance];
|
|
}
|
|
}
|
|
|
|
- (void)updateColorsBasedOnAppearance:(NSAppearance*)appearance
|
|
{
|
|
NSParameterAssert(appearance);
|
|
BOOL isDark = NO;
|
|
if (@available(macOS 10.14, *)) {
|
|
isDark = [appearance.name isEqualToString:NSAppearanceNameDarkAqua] || [appearance.name isEqualToString:NSAppearanceNameVibrantDark];
|
|
}
|
|
|
|
// If we try to pull the view's effectiveAppearance we are going to get the previous appearance's name despite
|
|
// responding to the effectiveAppearance change (???) so it is a better idea to pull from the general system
|
|
// theme preference, which is always up-to-date
|
|
if (isDark) {
|
|
self.upNextLabel.textColor = [NSColor VLClibraryDarkTitleColor];
|
|
self.upNextSeparator.borderColor = [NSColor VLClibrarySeparatorDarkColor];
|
|
self.clearPlaylistSeparator.borderColor = [NSColor VLClibrarySeparatorDarkColor];
|
|
self.dragDropImageBackgroundBox.hidden = NO;
|
|
} else {
|
|
self.upNextLabel.textColor = [NSColor VLClibraryLightTitleColor];
|
|
self.upNextSeparator.borderColor = [NSColor VLClibrarySeparatorLightColor];
|
|
self.clearPlaylistSeparator.borderColor = [NSColor VLClibrarySeparatorLightColor];
|
|
self.dragDropImageBackgroundBox.hidden = YES;
|
|
}
|
|
}
|
|
|
|
- (void)updateViewCellDimensionsBasedOnSetting:(NSNotification *)aNotification
|
|
{
|
|
_playlistTableView.rowHeight = config_GetInt("macosx-large-text") ?
|
|
[VLCLibraryUIUnits largeTableViewRowHeight] :
|
|
[VLCLibraryUIUnits mediumTableViewRowHeight];
|
|
}
|
|
|
|
#pragma mark - playmode state display and interaction
|
|
|
|
- (IBAction)shuffleAction:(id)sender
|
|
{
|
|
if (_playlistController.playbackOrder == VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL) {
|
|
_playlistController.playbackOrder = VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM;
|
|
} else {
|
|
_playlistController.playbackOrder = VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL;
|
|
}
|
|
}
|
|
|
|
- (void)shuffleStateUpdated:(NSNotification *)aNotification
|
|
{
|
|
if (_playlistController.playbackOrder == VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL) {
|
|
self.shufflePlaylistButton.image = [NSImage imageNamed:@"shuffleOff"];
|
|
} else {
|
|
self.shufflePlaylistButton.image = [NSImage imageNamed:@"shuffleOn"];
|
|
}
|
|
}
|
|
|
|
- (IBAction)repeatAction:(id)sender
|
|
{
|
|
enum vlc_playlist_playback_repeat currentRepeatState = _playlistController.playbackRepeat;
|
|
switch (currentRepeatState) {
|
|
case VLC_PLAYLIST_PLAYBACK_REPEAT_ALL:
|
|
_playlistController.playbackRepeat = VLC_PLAYLIST_PLAYBACK_REPEAT_NONE;
|
|
break;
|
|
case VLC_PLAYLIST_PLAYBACK_REPEAT_CURRENT:
|
|
_playlistController.playbackRepeat = VLC_PLAYLIST_PLAYBACK_REPEAT_ALL;
|
|
break;
|
|
|
|
default:
|
|
_playlistController.playbackRepeat = VLC_PLAYLIST_PLAYBACK_REPEAT_CURRENT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)repeatStateUpdated:(NSNotification *)aNotification
|
|
{
|
|
enum vlc_playlist_playback_repeat currentRepeatState = _playlistController.playbackRepeat;
|
|
switch (currentRepeatState) {
|
|
case VLC_PLAYLIST_PLAYBACK_REPEAT_ALL:
|
|
self.repeatPlaylistButton.image = [NSImage imageNamed:@"repeatAll"];
|
|
break;
|
|
case VLC_PLAYLIST_PLAYBACK_REPEAT_CURRENT:
|
|
self.repeatPlaylistButton.image = [NSImage imageNamed:@"repeatOne"];
|
|
break;
|
|
|
|
default:
|
|
self.repeatPlaylistButton.image = [NSImage imageNamed:@"repeatOff"];
|
|
break;
|
|
}
|
|
}
|
|
|
|
#pragma mark - misc. user interactions
|
|
|
|
- (void)setViewForSelectedSegment
|
|
{
|
|
_currentSelectedSegment = _segmentedTitleControl.selectedSegment;
|
|
_currentSelectedViewModeSegment = _gridVsListSegmentedControl.selectedSegment;
|
|
|
|
VLCLibrarySegment selectedLibrarySegment = _segmentedTitleControl.selectedSegment;
|
|
switch (selectedLibrarySegment) {
|
|
case VLCLibraryVideoSegment:
|
|
[self showVideoLibrary];
|
|
break;
|
|
case VLCLibraryMusicSegment:
|
|
[self showAudioLibrary];
|
|
break;
|
|
case VLCLibraryBrowseSegment:
|
|
case VLCLibraryStreamsSegment:
|
|
[self showMediaSourceLibraryWithSegment:selectedLibrarySegment];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (IBAction)segmentedControlAction:(id)sender
|
|
{
|
|
if (_segmentedTitleControl.selectedSegment == _currentSelectedSegment &&
|
|
_gridVsListSegmentedControl.selectedSegment == _currentSelectedViewModeSegment) {
|
|
return;
|
|
}
|
|
|
|
[self setViewForSelectedSegment];
|
|
[self invalidateRestorableState];
|
|
}
|
|
|
|
- (void)hideToolbarItem:(NSToolbarItem *)toolbarItem
|
|
{
|
|
NSInteger toolbarItemIndex = [[self.toolbar items] indexOfObject:toolbarItem];
|
|
if (toolbarItemIndex != NSNotFound) {
|
|
[self.toolbar removeItemAtIndex:toolbarItemIndex];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Try to insert the toolbar item ahead of a group of possible toolbar items.
|
|
* "items" should contain items sorted from the trailing edge of the toolbar to leading edge.
|
|
* "toolbarItem" will be inserted as close to the trailing edge as possible.
|
|
*
|
|
* If you have: | item1 | item2 | item3 | item4 |
|
|
* and the "items" parameter is an array containing @[item6, item5, item2, item1]
|
|
* then the "toolbarItem" provided to this function will place toolbarItem thus:
|
|
* | item1 | item2 | toolbarItem | item3 | item4 |
|
|
*/
|
|
|
|
- (void)insertToolbarItem:(NSToolbarItem *)toolbarItem inFrontOf:(NSArray<NSToolbarItem *> *)items
|
|
{
|
|
NSParameterAssert(toolbarItem != nil && items != nil && toolbarItem.itemIdentifier.length > 0);
|
|
|
|
NSInteger toolbarItemIndex = [[self.toolbar items] indexOfObject:toolbarItem];
|
|
if (toolbarItemIndex != NSNotFound) {
|
|
return;
|
|
}
|
|
|
|
for (NSToolbarItem *item in items) {
|
|
NSInteger itemIndex = [[self.toolbar items] indexOfObject:item];
|
|
|
|
if (itemIndex != NSNotFound) {
|
|
[self.toolbar insertItemWithItemIdentifier:toolbarItem.itemIdentifier atIndex:itemIndex + 1];
|
|
return;
|
|
}
|
|
}
|
|
|
|
[self.toolbar insertItemWithItemIdentifier:toolbarItem.itemIdentifier atIndex:0];
|
|
}
|
|
|
|
- (void)setForwardsBackwardsToolbarItemsVisible:(BOOL)visible
|
|
{
|
|
if (!visible) {
|
|
[self hideToolbarItem:_forwardsToolbarItem];
|
|
[self hideToolbarItem:_backwardsToolbarItem];
|
|
return;
|
|
}
|
|
|
|
[self insertToolbarItem:_backwardsToolbarItem inFrontOf:@[]];
|
|
[self insertToolbarItem:_forwardsToolbarItem inFrontOf:@[_backwardsToolbarItem]];
|
|
}
|
|
|
|
- (void)setSortOrderToolbarItemVisible:(BOOL)visible
|
|
{
|
|
if (!visible) {
|
|
[self hideToolbarItem:_sortOrderToolbarItem];
|
|
return;
|
|
}
|
|
|
|
[self insertToolbarItem:_sortOrderToolbarItem
|
|
inFrontOf:@[_libraryViewModeToolbarItem, _forwardsToolbarItem, _backwardsToolbarItem]];
|
|
}
|
|
|
|
- (void)setLibrarySearchToolbarItemVisible:(BOOL)visible
|
|
{
|
|
if (!visible) {
|
|
[self hideToolbarItem:_librarySearchToolbarItem];
|
|
_librarySearchField.stringValue = @"";
|
|
[VLCMain.sharedInstance.libraryController filterByString:@""];
|
|
return;
|
|
}
|
|
|
|
// Display as far to the right as possible, but not in front of the playlist toggle button
|
|
NSMutableArray<NSToolbarItem *> *currentToolbarItems = [NSMutableArray arrayWithArray:self.toolbar.items];
|
|
if (currentToolbarItems.lastObject == _togglePlaylistToolbarItem) {
|
|
[currentToolbarItems removeLastObject];
|
|
}
|
|
|
|
NSArray *reversedCurrentToolbarItems = [[currentToolbarItems reverseObjectEnumerator] allObjects];
|
|
[self insertToolbarItem:_librarySearchToolbarItem inFrontOf:reversedCurrentToolbarItems];
|
|
}
|
|
|
|
- (void)updatePlayqueueToggleState
|
|
{
|
|
_playQueueToggle.state = [_mainSplitView isSubviewCollapsed:_playlistView] ?
|
|
NSControlStateValueOff : NSControlStateValueOn;
|
|
}
|
|
|
|
- (void)showVideoLibrary
|
|
{
|
|
[self setForwardsBackwardsToolbarItemsVisible:NO];
|
|
[self setSortOrderToolbarItemVisible:YES];
|
|
[self setLibrarySearchToolbarItemVisible:YES];
|
|
_optionBarView.hidden = YES;
|
|
|
|
_gridVsListSegmentedControl.target = self;
|
|
_gridVsListSegmentedControl.action = @selector(segmentedControlAction:);
|
|
|
|
[_libraryVideoViewController presentVideoView];
|
|
}
|
|
|
|
- (void)showAudioLibrary
|
|
{
|
|
[self setForwardsBackwardsToolbarItemsVisible:NO];
|
|
[self setSortOrderToolbarItemVisible:YES];
|
|
[self setLibrarySearchToolbarItemVisible:YES];
|
|
_optionBarView.hidden = NO;
|
|
|
|
_gridVsListSegmentedControl.target = self;
|
|
_gridVsListSegmentedControl.action = @selector(segmentedControlAction:);
|
|
|
|
[_libraryAudioViewController presentAudioView];
|
|
}
|
|
|
|
- (void)showMediaSourceLibraryWithSegment:(VLCLibrarySegment)segment
|
|
{
|
|
NSParameterAssert(segment == VLCLibraryBrowseSegment || segment == VLCLibraryStreamsSegment);
|
|
|
|
[self.navigationStack clear];
|
|
[self setForwardsBackwardsToolbarItemsVisible:YES];
|
|
[self setSortOrderToolbarItemVisible:NO];
|
|
[self setLibrarySearchToolbarItemVisible:NO];
|
|
_optionBarView.hidden = YES;
|
|
|
|
if (segment == VLCLibraryBrowseSegment) {
|
|
[_libraryMediaSourceViewController presentBrowseView];
|
|
} else if (segment == VLCLibraryStreamsSegment) {
|
|
[_libraryMediaSourceViewController presentStreamsView];
|
|
}
|
|
}
|
|
|
|
- (IBAction)playlistDoubleClickAction:(id)sender
|
|
{
|
|
NSInteger selectedRow = self.playlistTableView.selectedRow;
|
|
if (selectedRow == -1)
|
|
return;
|
|
|
|
[[[VLCMain sharedInstance] playlistController] playItemAtIndex:selectedRow];
|
|
}
|
|
|
|
- (IBAction)clearPlaylist:(id)sender
|
|
{
|
|
[_playlistController clearPlaylist];
|
|
}
|
|
|
|
- (IBAction)sortPlaylist:(id)sender
|
|
{
|
|
if (!_playlistSortingMenuController) {
|
|
_playlistSortingMenuController = [[VLCPlaylistSortingMenuController alloc] init];
|
|
}
|
|
[NSMenu popUpContextMenu:_playlistSortingMenuController.playlistSortingMenu withEvent:[NSApp currentEvent] forView:sender];
|
|
}
|
|
|
|
- (IBAction)sortLibrary:(id)sender
|
|
{
|
|
if (!_librarySortingMenuController) {
|
|
_librarySortingMenuController = [[VLCLibrarySortingMenuController alloc] init];
|
|
}
|
|
[NSMenu popUpContextMenu:_librarySortingMenuController.librarySortingMenu withEvent:[NSApp currentEvent] forView:sender];
|
|
}
|
|
|
|
- (IBAction)filterLibrary:(id)sender
|
|
{
|
|
[[[VLCMain sharedInstance] libraryController] filterByString:_librarySearchField.stringValue];
|
|
}
|
|
|
|
- (void)clearLibraryFilterString
|
|
{
|
|
_librarySearchField.stringValue = @"";
|
|
[self filterLibrary:self];
|
|
}
|
|
|
|
- (IBAction)openMedia:(id)sender
|
|
{
|
|
[[[VLCMain sharedInstance] open] openFileGeneric];
|
|
}
|
|
|
|
- (BOOL)handlePasteBoardFromDragSession:(NSPasteboard *)paste
|
|
{
|
|
id propertyList = [paste propertyListForType:NSFilenamesPboardType];
|
|
if (propertyList == nil) {
|
|
return NO;
|
|
}
|
|
|
|
NSArray *values = [propertyList sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
|
|
NSUInteger valueCount = [values count];
|
|
if (valueCount > 0) {
|
|
NSMutableArray *metadataArray = [NSMutableArray arrayWithCapacity:valueCount];
|
|
|
|
for (NSString *filepath in values) {
|
|
VLCOpenInputMetadata *inputMetadata;
|
|
|
|
inputMetadata = [VLCOpenInputMetadata inputMetaWithPath:filepath];
|
|
if (!inputMetadata)
|
|
continue;
|
|
|
|
[metadataArray addObject:inputMetadata];
|
|
}
|
|
[_playlistController addPlaylistItems:metadataArray];
|
|
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (IBAction)goToBrowseSection:(id)sender
|
|
{
|
|
[_segmentedTitleControl setSelected:YES forSegment:2];
|
|
[self segmentedControlAction:_segmentedTitleControl];
|
|
}
|
|
|
|
#pragma mark - split view delegation
|
|
|
|
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex
|
|
{
|
|
switch (dividerIndex) {
|
|
case 0:
|
|
return VLCLibraryWindowMinimalWidth;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return proposedMinimumPosition;
|
|
}
|
|
|
|
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex
|
|
{
|
|
switch (dividerIndex) {
|
|
case 0:
|
|
return splitView.frame.size.width - VLCLibraryWindowMinimalPlaylistWidth;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return proposedMaximumPosition;
|
|
}
|
|
|
|
- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview
|
|
{
|
|
return [subview isEqual:_playlistView];
|
|
}
|
|
|
|
- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex
|
|
{
|
|
return [subview isEqual:_playlistView];
|
|
}
|
|
|
|
- (void)splitViewDidResizeSubviews:(NSNotification *)notification
|
|
{
|
|
_lastPlaylistWidthBeforeCollaps = [_playlistView frame].size.width;
|
|
|
|
if (![_mainSplitView isSubviewCollapsed:_playlistView]) {
|
|
_playQueueToggle.state = NSControlStateValueOn;
|
|
} else {
|
|
_playQueueToggle.state = NSControlStateValueOff;
|
|
}
|
|
}
|
|
|
|
- (void)togglePlaylist
|
|
{
|
|
[_mainSplitView adjustSubviews];
|
|
CGFloat splitViewWidth = _mainSplitView.frame.size.width;
|
|
if ([_mainSplitView isSubviewCollapsed:_playlistView]) {
|
|
[_mainSplitView setPosition:splitViewWidth - _lastPlaylistWidthBeforeCollaps ofDividerAtIndex:0];
|
|
_playQueueToggle.state = NSControlStateValueOn;
|
|
self.videoViewController.playlistButton.state = NSControlStateValueOn;
|
|
} else {
|
|
[_mainSplitView setPosition:splitViewWidth ofDividerAtIndex:0];
|
|
_playQueueToggle.state = NSControlStateValueOff;
|
|
self.videoViewController.playlistButton.state = NSControlStateValueOff;
|
|
}
|
|
}
|
|
|
|
- (IBAction)showAndHidePlaylist:(id)sender
|
|
{
|
|
[self togglePlaylist];
|
|
}
|
|
|
|
- (IBAction)backwardsNavigationAction:(id)sender
|
|
{
|
|
self.videoViewController.view.hidden ? [_navigationStack backwards] : [self disableVideoPlaybackAppearance];
|
|
}
|
|
|
|
- (IBAction)forwardsNavigationAction:(id)sender
|
|
{
|
|
[_navigationStack forwards];
|
|
}
|
|
|
|
#pragma mark - video output controlling
|
|
|
|
- (void)setHasActiveVideo:(BOOL)hasActiveVideo
|
|
{
|
|
[super setHasActiveVideo:hasActiveVideo];
|
|
hasActiveVideo ? [self enableVideoPlaybackAppearance] : [self disableVideoPlaybackAppearance];
|
|
}
|
|
|
|
- (void)playerStateChanged:(NSNotification *)notification
|
|
{
|
|
if (_playlistController.playerController.playerState == VLC_PLAYER_STATE_STOPPED) {
|
|
[self hideControlsBar];
|
|
return;
|
|
}
|
|
|
|
if(_playlistController.playerController.playerState == VLC_PLAYER_STATE_PLAYING) {
|
|
[self reopenVideoView];
|
|
}
|
|
|
|
if (self.videoViewController.view.isHidden) {
|
|
[self showControlsBar];
|
|
}
|
|
}
|
|
|
|
// This handles reopening the video view when the user has closed it.
|
|
- (void)reopenVideoView
|
|
{
|
|
if(!self.hasActiveVideo) {
|
|
return;
|
|
}
|
|
|
|
VLCMediaLibraryMediaItem *mediaItem = [VLCMediaLibraryMediaItem mediaItemForURL:_playlistController.playerController.URLOfCurrentMediaItem];
|
|
|
|
if(mediaItem == nil || mediaItem.mediaType != VLC_ML_MEDIA_TYPE_VIDEO) {
|
|
return;
|
|
}
|
|
|
|
[self enableVideoPlaybackAppearance];
|
|
}
|
|
|
|
- (void)hideControlsBar
|
|
{
|
|
[super hideControlsBar];
|
|
_splitViewBottomConstraintToBottomBar.priority = 1;
|
|
_splitViewBottomConstraintToSuperView.priority = 999;
|
|
|
|
}
|
|
|
|
- (void)showControlsBar
|
|
{
|
|
[super showControlsBar];
|
|
_splitViewBottomConstraintToBottomBar.priority = 999;
|
|
_splitViewBottomConstraintToSuperView.priority = 1;
|
|
}
|
|
|
|
- (void)stopTitlebarAutohideTimer
|
|
{
|
|
[_hideTitlebarTimer invalidate];
|
|
}
|
|
|
|
- (void)startTitlebarAutohideTimer
|
|
{
|
|
/* Do nothing if timer is already in place */
|
|
if (_hideTitlebarTimer.valid) {
|
|
return;
|
|
}
|
|
|
|
/* Get timeout and make sure it is not lower than 1 second */
|
|
long long timeToKeepVisibleInSec = MAX(var_CreateGetInteger(getIntf(), "mouse-hide-timeout") / 1000, 1);
|
|
|
|
_hideTitlebarTimer = [NSTimer scheduledTimerWithTimeInterval:timeToKeepVisibleInSec
|
|
target:self
|
|
selector:@selector(hideTitleBar:)
|
|
userInfo:nil
|
|
repeats:NO];
|
|
}
|
|
|
|
- (void)showTitleBar
|
|
{
|
|
[self stopTitlebarAutohideTimer];
|
|
|
|
NSView *titlebarView = [self standardWindowButton:NSWindowCloseButton].superview;
|
|
if (!titlebarView.hidden && !_autohideTitlebar) {
|
|
return;
|
|
}
|
|
|
|
titlebarView.hidden = NO;
|
|
|
|
if (_autohideTitlebar) {
|
|
[self startTitlebarAutohideTimer];
|
|
}
|
|
}
|
|
|
|
- (void)hideTitleBar:(id)sender
|
|
{
|
|
[self stopTitlebarAutohideTimer];
|
|
[self standardWindowButton:NSWindowCloseButton].superview.hidden = YES;
|
|
}
|
|
|
|
- (void)enableVideoTitleBarMode
|
|
{
|
|
self.toolbar.visible = NO;
|
|
self.styleMask |= NSWindowStyleMaskFullSizeContentView;
|
|
self.titlebarAppearsTransparent = YES;
|
|
|
|
_autohideTitlebar = YES;
|
|
[self showTitleBar];
|
|
}
|
|
|
|
- (void)disableVideoTitleBarMode
|
|
{
|
|
self.toolbar.visible = YES;
|
|
self.styleMask &= ~NSWindowStyleMaskFullSizeContentView;
|
|
self.titlebarAppearsTransparent = NO;
|
|
|
|
_autohideTitlebar = NO;
|
|
[self showTitleBar];
|
|
}
|
|
|
|
- (void)presentVideoView
|
|
{
|
|
for (NSView *subview in _libraryTargetView.subviews) {
|
|
[subview removeFromSuperview];
|
|
}
|
|
|
|
NSLog(@"Presenting video view in main library window.");
|
|
|
|
NSView *videoView = self.videoViewController.view;
|
|
videoView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
videoView.hidden = NO;
|
|
|
|
[_libraryTargetView addSubview:videoView];
|
|
NSDictionary *dict = NSDictionaryOfVariableBindings(videoView);
|
|
[_libraryTargetView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[videoView(>=572.)]|"
|
|
options:0
|
|
metrics:0
|
|
views:dict]];
|
|
[_libraryTargetView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[videoView(>=444.)]|"
|
|
options:0
|
|
metrics:0
|
|
views:dict]];
|
|
}
|
|
|
|
- (void)enableVideoPlaybackAppearance
|
|
{
|
|
[self presentVideoView];
|
|
|
|
[self.segmentedTitleControl setHidden:YES];
|
|
[self.optionBarView setHidden:YES];
|
|
[self.forwardsNavigationButton setHidden:YES];
|
|
[self.gridVsListSegmentedControl setHidden:YES];
|
|
[self.librarySortButton setHidden:YES];
|
|
[self.librarySearchField setEnabled:NO];
|
|
[self clearLibraryFilterString];
|
|
|
|
// Make sure the back button is visible...
|
|
[self insertToolbarItem:_backwardsToolbarItem inFrontOf:@[]];
|
|
// And repurpose it to hide the video view
|
|
[self.backwardsNavigationButton setEnabled:YES];
|
|
|
|
if (self.isInNativeFullscreen && [self hasActiveVideo] && [self fullscreen]) {
|
|
[self hideControlsBar];
|
|
[self.videoViewController showControls];
|
|
}
|
|
|
|
[self enableVideoTitleBarMode];
|
|
[self hideControlsBar];
|
|
[self.videoViewController showControls];
|
|
}
|
|
|
|
- (void)disableVideoPlaybackAppearance
|
|
{
|
|
[self makeFirstResponder: _playlistTableView];
|
|
[[[VLCMain sharedInstance] voutProvider] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
|
|
|
|
// restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
|
|
[self setAlphaValue:1.0];
|
|
self.videoViewController.view.hidden = YES;
|
|
|
|
[self.segmentedTitleControl setHidden:NO];
|
|
[self.forwardsNavigationButton setHidden:NO];
|
|
[self.gridVsListSegmentedControl setHidden:NO];
|
|
[self.librarySortButton setHidden:NO];
|
|
[self.librarySearchField setEnabled:YES];
|
|
|
|
// Reset the back button to navigation state
|
|
[self.backwardsNavigationButton setEnabled:_navigationStack.backwardsAvailable];
|
|
|
|
[self setViewForSelectedSegment];
|
|
|
|
if (self.isInNativeFullscreen) {
|
|
[self showControlsBar];
|
|
}
|
|
|
|
[self disableVideoTitleBarMode];
|
|
[self showControlsBar];
|
|
}
|
|
|
|
- (void)mouseMoved:(NSEvent *)o_event
|
|
{
|
|
if (!self.videoViewController.view.hidden) {
|
|
NSPoint mouseLocation = [o_event locationInWindow];
|
|
NSRect windowRectWithFrame = [self frameRectForContentRect:self.contentView.frame];
|
|
|
|
if ([self.contentView mouse:mouseLocation inRect:windowRectWithFrame]) {
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:VLCVideoWindowShouldShowFullscreenController
|
|
object:self];
|
|
}
|
|
}
|
|
|
|
[super mouseMoved:o_event];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark respond to core events
|
|
|
|
- (void)shouldShowController:(NSNotification *)aNotification
|
|
{
|
|
[self makeKeyAndOrderFront:nil];
|
|
|
|
if (self.videoViewController.view.isHidden) {
|
|
[self showControlsBar];
|
|
NSView *standardWindowButtonsSuperView = [self standardWindowButton:NSWindowCloseButton].superview;
|
|
standardWindowButtonsSuperView.hidden = NO;
|
|
}
|
|
}
|
|
|
|
- (void)shouldShowFullscreenController:(NSNotification *)aNotification
|
|
{
|
|
[self showTitleBar];
|
|
}
|
|
|
|
@end
|
|
|