Commit c39aaf28 authored by David's avatar David
Browse files

macosx: Add new playlist model

The current playlist model directly operates on the core playlist
datastructures without proper locking for a complete playlist table
reload/update. This resulted in various ugly hacks and workarounds.

The new playlist model encapsulates the data in own objects like
in the qt interface. This allows a much easier integration with
the table view and proper updates from the core playlist.

This way, the previous playlist objects, stored in an ugly map with
pointer strings as keys, pointing to the same pointer inside a
NSValue, is obsolete finally. :-)
parent da0b9bbc
......@@ -84,7 +84,8 @@ static VLCCoreInteraction *_o_sharedInstance = nil;
empty = playlist_IsEmpty(p_playlist);
PL_UNLOCK;
if ([[[VLCMain sharedInstance] playlist] isSelectionEmpty] && ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] == p_playlist->p_local_category || [[[VLCMain sharedInstance] playlist] currentPlaylistRoot] == p_playlist->p_ml_category))
PLRootType root = [[[[VLCMain sharedInstance] playlist] model] currentRootType];
if ([[[VLCMain sharedInstance] playlist] isSelectionEmpty] && (root == ROOT_TYPE_PLAYLIST || root == ROOT_TYPE_MEDIALIBRARY))
[[[VLCMain sharedInstance] open] openFileGeneric];
else
[[[VLCMain sharedInstance] playlist] playItem:nil];
......
......@@ -769,7 +769,8 @@ static VLCMainWindow *_o_sharedInstance = nil;
[o_fspanel setSeekable: b_seekable];
PL_LOCK;
if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
[[[[VLCMain sharedInstance] playlist] model] hasChildren])
[self hideDropZone];
else
[self showDropZone];
......@@ -947,15 +948,16 @@ static VLCMainWindow *_o_sharedInstance = nil;
#pragma mark private playlist magic
- (void)_updatePlaylistTitle
{
playlist_t * p_playlist = pl_Get(VLCIntf);
PL_LOCK;
playlist_item_t *currentPlaylistRoot = [[[VLCMain sharedInstance] playlist] currentPlaylistRoot];
PL_UNLOCK;
PLRootType root = [[[[VLCMain sharedInstance] playlist] model] currentRootType];
playlist_t *p_playlist = pl_Get(VLCIntf);
if (currentPlaylistRoot == p_playlist->p_local_category)
PL_LOCK;
if (root == ROOT_TYPE_PLAYLIST)
[o_chosen_category_lbl setStringValue: [_NS("Playlist") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
else if (currentPlaylistRoot == p_playlist->p_ml_category)
else if (root == ROOT_TYPE_MEDIALIBRARY)
[o_chosen_category_lbl setStringValue: [_NS("Media Library") stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
PL_UNLOCK;
}
- (NSString *)_playbackDurationOfNode:(playlist_item_t*)node
......@@ -964,9 +966,9 @@ static VLCMainWindow *_o_sharedInstance = nil;
return @"";
playlist_t * p_playlist = pl_Get(VLCIntf);
PL_LOCK;
PL_ASSERT_LOCKED;
mtime_t mt_duration = playlist_GetNodeDuration( node );
PL_UNLOCK;
if (mt_duration < 1)
return @"";
......@@ -1132,19 +1134,29 @@ static VLCMainWindow *_o_sharedInstance = nil;
[o_chosen_category_lbl setStringValue:[item title]];
if ([[item identifier] isEqualToString:@"playlist"]) {
[[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_local_category];
[o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_local_category]]];
PL_LOCK;
[[[[VLCMain sharedInstance] playlist] model] changeRootItem:p_playlist->p_playing];
PL_UNLOCK;
[self _updatePlaylistTitle];
} else if ([[item identifier] isEqualToString:@"medialibrary"]) {
if (p_playlist->p_ml_category) {
[[[VLCMain sharedInstance] playlist] setPlaylistRoot:p_playlist->p_ml_category];
[o_chosen_category_lbl setStringValue: [[o_chosen_category_lbl stringValue] stringByAppendingString:[self _playbackDurationOfNode:p_playlist->p_ml_category]]];
PL_LOCK;
[[[[VLCMain sharedInstance] playlist] model] changeRootItem:p_playlist->p_media_library];
PL_UNLOCK;
[self _updatePlaylistTitle];
}
} else {
playlist_item_t * pl_item;
PL_LOCK;
pl_item = playlist_ChildSearchName(p_playlist->p_root, [[item untranslatedTitle] UTF8String]);
playlist_item_t *pl_item = playlist_ChildSearchName(p_playlist->p_root, [[item untranslatedTitle] UTF8String]);
[[[[VLCMain sharedInstance] playlist] model] changeRootItem:pl_item];
PL_UNLOCK;
[[[VLCMain sharedInstance] playlist] setPlaylistRoot: pl_item];
}
// Note the order: first hide the podcast controls, then show the drop zone
......@@ -1154,7 +1166,8 @@ static VLCMainWindow *_o_sharedInstance = nil;
[self hidePodcastControls];
PL_LOCK;
if ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot] != p_playlist->p_local_category || p_playlist->p_local_category->i_children > 0)
if ([[[[VLCMain sharedInstance] playlist] model] currentRootType] != ROOT_TYPE_PLAYLIST ||
[[[[VLCMain sharedInstance] playlist] model] hasChildren])
[self hideDropZone];
else
[self showDropZone];
......
......@@ -95,4 +95,8 @@ SOURCES_macosx = \
BWQuincyUI.m \
iTunes.h \
Spotify.h \
PLItem.h \
PLItem.m \
PLModel.h \
PLModel.m \
$(NULL)
/*****************************************************************************
* PLItem.h: MacOS X interface module
*****************************************************************************
* Copyright (C) 2014 VLC authors and VideoLAN
* $Id$
*
* 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 <Cocoa/Cocoa.h>
#include <vlc_common.h>
@interface PLItem : NSObject
{
input_item_t *p_input;
int _playlistId;
NSMutableArray *_children;
PLItem *_parent;
}
@property(readonly, copy) NSMutableArray *children;
@property(readonly) int plItemId;
@property(readonly) input_item_t *input;
@property(readonly) PLItem *parent;
- (id)initWithPlaylistItem:(playlist_item_t *)p_item parent:(PLItem *)parent;
- (BOOL)isLeaf;
- (void)clear;
- (void)addChild:(PLItem *)item atPos:(int)pos;
- (void)deleteChild:(PLItem *)child;
@end
/*****************************************************************************
* PLItem.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2014 VLC authors and VideoLAN
* $Id$
*
* 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 "PLItem.h"
#include <vlc_playlist.h>
#include <vlc_input_item.h>
#pragma mark -
;
@implementation PLItem
@synthesize children=_children;
@synthesize plItemId=_playlistId;
@synthesize input=p_input;
@synthesize parent=_parent;
- (id)initWithPlaylistItem:(playlist_item_t *)p_item parent:(PLItem *)parent;
{
self = [super init];
if(self) {
_playlistId = p_item->i_id;
p_input = p_item->p_input;
input_item_Hold(p_input);
_children = [[NSMutableArray alloc] init];
[parent retain];
_parent = parent;
}
return self;
}
- (void)dealloc
{
input_item_Release(p_input);
[_children release];
[_parent release];
[super dealloc];
}
- (BOOL)isLeaf
{
return [_children count] == 0;
}
- (void)clear
{
[_children removeAllObjects];
}
- (void)addChild:(PLItem *)item atPos:(int)pos
{
// if ([o_children count] > pos) {
// NSLog(@"invalid position %d", pos);
// }
[_children insertObject:item atIndex:pos];
}
- (void)deleteChild:(PLItem *)child
{
[_children removeObject:child];
}
@end
/*****************************************************************************
* PLItem.h: MacOS X interface module
*****************************************************************************
* Copyright (C) 2014 VLC authors and VideoLAN
* $Id$
*
* 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 <Cocoa/Cocoa.h>
#import "PLItem.h"
#include <vlc_common.h>
@interface PLModel : NSObject<NSOutlineViewDataSource>
{
PLItem *_rootItem;
playlist_t *p_playlist;
NSOutlineView *_outlineView;
}
@property(readonly) PLItem *rootItem;
- (id)initWithOutlineView:(NSOutlineView *)outlineView playlist:(playlist_t *)pl rootItem:(playlist_item_t *)root;
- (void)changeRootItem:(playlist_item_t *)p_root;
- (BOOL)hasChildren;
typedef enum {
ROOT_TYPE_PLAYLIST,
ROOT_TYPE_MEDIALIBRARY,
ROOT_TYPE_OTHER
} PLRootType;
- (PLRootType)currentRootType;
- (BOOL)editAllowed;
- (void)addItem:(int)i_item withParentNode:(int)i_node;
- (void)removeItem:(int)i_item;
- (void)sortForColumn:(NSString *)o_column withMode:(int)i_mode;
@end
/*****************************************************************************
* PLItem.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2014 VLC authors and VideoLAN
* $Id$
*
* 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 "PLModel.h"
#import "misc.h"
#include <vlc_playlist.h>
#include <vlc_input_item.h>
#include <vlc_url.h>
#define TRACKNUM_COLUMN @"tracknumber"
#define TITLE_COLUMN @"name"
#define ARTIST_COLUMN @"artist"
#define DURATION_COLUMN @"duration"
#define GENRE_COLUMN @"genre"
#define ALBUM_COLUMN @"album"
#define DESCRIPTION_COLUMN @"description"
#define DATE_COLUMN @"date"
#define LANGUAGE_COLUMN @"language"
#define URI_COLUMN @"uri"
#define FILESIZE_COLUMN @"file-size"
#pragma mark -
@implementation PLModel
@synthesize rootItem=_rootItem;
- (id)initWithOutlineView:(NSOutlineView *)outlineView playlist:(playlist_t *)pl rootItem:(playlist_item_t *)root;
{
self = [super init];
if(self) {
p_playlist = pl;
_outlineView = [outlineView retain];
PL_LOCK;
_rootItem = [[PLItem alloc] initWithPlaylistItem:root parent:nil];
[self rebuildPLItem:_rootItem];
PL_UNLOCK;
}
return self;
}
- (void)changeRootItem:(playlist_item_t *)p_root;
{
NSLog(@"change root item to %p", p_root);
PL_ASSERT_LOCKED;
[_rootItem release];
_rootItem = [[PLItem alloc] initWithPlaylistItem:p_root parent:nil];
[self rebuildPLItem:_rootItem];
[_outlineView reloadData];
}
- (BOOL)hasChildren
{
return [[_rootItem children] count] > 0;
}
- (PLRootType)currentRootType
{
int i_root_id = [_rootItem plItemId];
if (i_root_id == p_playlist->p_playing->i_id)
return ROOT_TYPE_PLAYLIST;
if (p_playlist->p_media_library && i_root_id == p_playlist->p_media_library->i_id)
return ROOT_TYPE_MEDIALIBRARY;
return ROOT_TYPE_OTHER;
}
- (BOOL)editAllowed
{
return [self currentRootType] == ROOT_TYPE_MEDIALIBRARY ||
[self currentRootType] == ROOT_TYPE_PLAYLIST;
}
- (void)rebuildPLItem:(PLItem *)o_item
{
[o_item clear];
playlist_item_t *p_item = playlist_ItemGetById(p_playlist, [o_item plItemId]);
if (p_item) {
for(int i = 0; i < p_item->i_children; ++i) {
PLItem *o_child = [[[PLItem alloc] initWithPlaylistItem:p_item->pp_children[i] parent:o_item] autorelease];
[o_item addChild:o_child atPos:i];
if (p_item->pp_children[i]->i_children >= 0) {
[self rebuildPLItem:o_child];
}
}
}
}
- (PLItem *)findItemByPlaylistId:(int)i_pl_id
{
return [self findItemInnerByPlaylistId:i_pl_id node:_rootItem];
}
- (PLItem *)findItemInnerByPlaylistId:(int)i_pl_id node:(PLItem *)node
{
if ([node plItemId] == i_pl_id) {
return node;
}
for (NSUInteger i = 0; i < [[node children] count]; ++i) {
PLItem *o_sub_item = [[node children] objectAtIndex:i];
if ([o_sub_item plItemId] == i_pl_id) {
return o_sub_item;
}
if (![o_sub_item isLeaf]) {
PLItem *o_returned = [self findItemInnerByPlaylistId:i_pl_id node:o_sub_item];
if (o_returned)
return o_returned;
}
}
return nil;
}
- (void)addItem:(int)i_item withParentNode:(int)i_node
{
NSLog(@"add item with index %d, parent: %d", i_item, i_node);
PLItem *o_parent = [self findItemByPlaylistId:i_node];
if (!o_parent) {
return;
}
PL_LOCK;
playlist_item_t *p_item = playlist_ItemGetById(p_playlist, i_item);
if (!p_item || p_item->i_flags & PLAYLIST_DBL_FLAG)
{
PL_UNLOCK; return;
}
int pos;
for(pos = p_item->p_parent->i_children - 1; pos >= 0; pos--)
if(p_item->p_parent->pp_children[pos] == p_item)
break;
PLItem *o_new_item = [[[PLItem alloc] initWithPlaylistItem:p_item parent:o_parent] autorelease];
PL_UNLOCK;
if (pos < 0)
return;
[o_parent addChild:o_new_item atPos:pos];
if ([o_parent plItemId] == [_rootItem plItemId])
[_outlineView reloadData];
else // only reload leafs this way, doing it with nil collapses width of title column
[_outlineView reloadItem:o_parent reloadChildren:YES];
}
- (void)removeItem:(int)i_item
{
NSLog(@"remove item with index %d", i_item);
PLItem *o_item = [self findItemByPlaylistId:i_item];
if (!o_item) {
return;
}
PLItem *o_parent = [o_item parent];
[o_parent deleteChild:o_item];
if ([o_parent plItemId] == [_rootItem plItemId])
[_outlineView reloadData];
else
[_outlineView reloadItem:o_parent reloadChildren:YES];
}
- (void)sortForColumn:(NSString *)o_column withMode:(int)i_mode
{
int i_column = 0;
if ([o_column isEqualToString:TRACKNUM_COLUMN])
i_column = SORT_TRACK_NUMBER;
else if ([o_column isEqualToString:TITLE_COLUMN])
i_column = SORT_TITLE;
else if ([o_column isEqualToString:ARTIST_COLUMN])
i_column = SORT_ARTIST;
else if ([o_column isEqualToString:GENRE_COLUMN])
i_column = SORT_GENRE;
else if ([o_column isEqualToString:DURATION_COLUMN])
i_column = SORT_DURATION;
else if ([o_column isEqualToString:ALBUM_COLUMN])
i_column = SORT_ALBUM;
else if ([o_column isEqualToString:DESCRIPTION_COLUMN])
i_column = SORT_DESCRIPTION;
else if ([o_column isEqualToString:URI_COLUMN])
i_column = SORT_URI;
else
return;
PL_LOCK;
playlist_item_t *p_root = playlist_ItemGetById(p_playlist, [_rootItem plItemId]);
if (!p_root) {
PL_UNLOCK;
return;
}
playlist_RecursiveNodeSort(p_playlist, p_root, i_column, i_mode);
[self rebuildPLItem:_rootItem];
[_outlineView reloadData];
PL_UNLOCK;
}
@end
#pragma mark -
#pragma mark Outline view data source
@implementation PLModel(NSOutlineViewDataSource)
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
return !item ? [[_rootItem children] count] : [[item children] count];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
return !item ? YES : [[item children] count] > 0;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
id obj = !item ? _rootItem : item;
return [[obj children] objectAtIndex:index];
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
id o_value = nil;
char * psz_value;
playlist_item_t *p_item;
input_item_t *p_input = [item input];
NSString * o_identifier = [tableColumn identifier];
if ([o_identifier isEqualToString:TRACKNUM_COLUMN]) {
psz_value = input_item_GetTrackNumber(p_input);
if (psz_value) {
o_value = [NSString stringWithUTF8String:psz_value];
free(psz_value);
}
} else if ([o_identifier isEqualToString:TITLE_COLUMN]) {
/* sanity check to prevent the NSString class from crashing */
char *psz_title = input_item_GetTitleFbName(p_input);
if (psz_title) {
o_value = [NSString stringWithUTF8String:psz_title];
free(psz_title);
}
} else if ([o_identifier isEqualToString:ARTIST_COLUMN]) {
psz_value = input_item_GetArtist(p_input);
if (psz_value) {
o_value = [NSString stringWithUTF8String:psz_value];
free(psz_value);
}
} else if ([o_identifier isEqualToString:@"duration"]) {
char psz_duration[MSTRTIME_MAX_SIZE];
mtime_t dur = input_item_GetDuration(p_input);
if (dur != -1) {
secstotimestr(psz_duration, dur/1000000);
o_value = [NSString stringWithUTF8String:psz_duration];
}
else
o_value = @"--:--";
} else if ([o_identifier isEqualToString:GENRE_COLUMN]) {
psz_value = inp