]> git.sesse.net Git - vlc/commitdiff
macosx: Add new playlist model
authorDavid Fuhrmann <dfuhrmann@videolan.org>
Sat, 15 Nov 2014 11:33:30 +0000 (12:33 +0100)
committerDavid Fuhrmann <dfuhrmann@videolan.org>
Tue, 30 Dec 2014 15:10:48 +0000 (16:10 +0100)
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. :-)

modules/gui/macosx/CoreInteraction.m
modules/gui/macosx/MainWindow.m
modules/gui/macosx/Modules.am
modules/gui/macosx/PLItem.h [new file with mode: 0644]
modules/gui/macosx/PLItem.m [new file with mode: 0644]
modules/gui/macosx/PLModel.h [new file with mode: 0644]
modules/gui/macosx/PLModel.m [new file with mode: 0644]
modules/gui/macosx/intf.m
modules/gui/macosx/playlist.h
modules/gui/macosx/playlist.m

index 8233022bbfb9ff0867e4498843616cee5919e0dc..ffc71ea67e135675b9666da35d675bef8cfccbe4 100644 (file)
@@ -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];
index 2079b289f3ba08d0b4ca53ad7763ebe246a72778..1ff023e727402dd4dc0c847b7c61dd00d3b652ee 100644 (file)
@@ -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];
index 6a6f73fee4f5adee602f7dce40431fd2d7be91b5..c796624bc6f3962c23aa88479ee188afc1b2fd23 100644 (file)
@@ -95,4 +95,8 @@ SOURCES_macosx = \
        BWQuincyUI.m \
        iTunes.h \
        Spotify.h \
+       PLItem.h \
+       PLItem.m \
+       PLModel.h \
+       PLModel.m \
        $(NULL)
diff --git a/modules/gui/macosx/PLItem.h b/modules/gui/macosx/PLItem.h
new file mode 100644 (file)
index 0000000..5cc3872
--- /dev/null
@@ -0,0 +1,50 @@
+/*****************************************************************************
+ * 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
+
diff --git a/modules/gui/macosx/PLItem.m b/modules/gui/macosx/PLItem.m
new file mode 100644 (file)
index 0000000..d072937
--- /dev/null
@@ -0,0 +1,86 @@
+/*****************************************************************************
+ * 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
diff --git a/modules/gui/macosx/PLModel.h b/modules/gui/macosx/PLModel.h
new file mode 100644 (file)
index 0000000..3a1b36c
--- /dev/null
@@ -0,0 +1,64 @@
+/*****************************************************************************
+ * 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
+
diff --git a/modules/gui/macosx/PLModel.m b/modules/gui/macosx/PLModel.m
new file mode 100644 (file)
index 0000000..a9222d2
--- /dev/null
@@ -0,0 +1,359 @@
+/*****************************************************************************
+ * 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 = input_item_GetGenre(p_input);
+        if (psz_value) {
+            o_value = [NSString stringWithUTF8String:psz_value];
+            free(psz_value);
+        }
+    } else if ([o_identifier isEqualToString:ALBUM_COLUMN]) {
+        psz_value = input_item_GetAlbum(p_input);
+        if (psz_value) {
+            o_value = [NSString stringWithUTF8String:psz_value];
+            free(psz_value);
+        }
+    } else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN]) {
+        psz_value = input_item_GetDescription(p_input);
+        if (psz_value) {
+            o_value = [NSString stringWithUTF8String:psz_value];
+            free(psz_value);
+        }
+    } else if ([o_identifier isEqualToString:DATE_COLUMN]) {
+        psz_value = input_item_GetDate(p_input);
+        if (psz_value) {
+            o_value = [NSString stringWithUTF8String:psz_value];
+            free(psz_value);
+        }
+    } else if ([o_identifier isEqualToString:LANGUAGE_COLUMN]) {
+        psz_value = input_item_GetLanguage(p_input);
+        if (psz_value) {
+            o_value = [NSString stringWithUTF8String:psz_value];
+            free(psz_value);
+        }
+    }
+    else if ([o_identifier isEqualToString:URI_COLUMN]) {
+        psz_value = decode_URI(input_item_GetURI(p_input));
+        if (psz_value) {
+            o_value = [NSString stringWithUTF8String:psz_value];
+            free(psz_value);
+        }
+    }
+    else if ([o_identifier isEqualToString:FILESIZE_COLUMN]) {
+        psz_value = input_item_GetURI(p_item->p_input);
+        o_value = @"";
+        if (psz_value) {
+            NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:psz_value]];
+            if ([url isFileURL]) {
+                NSFileManager *fileManager = [NSFileManager defaultManager];
+                if ([fileManager fileExistsAtPath:[url path]]) {
+                    NSError *error;
+                    NSDictionary *attributes = [fileManager attributesOfItemAtPath:[url path] error:&error];
+                    o_value = [VLCByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleDecimal];
+                }
+            }
+            free(psz_value);
+        }
+
+    }
+    else if ([o_identifier isEqualToString:@"status"]) {
+        if (input_item_HasErrorWhenReading(p_input)) {
+            o_value = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertCautionIcon)];
+            [o_value setSize: NSMakeSize(16,16)];
+        }
+    }
+
+    return o_value;
+}
+
+@end
index 9d10bd136fb2eaaa2a5557d9115acb4ac8168735..0284c60461d9b6cd70cc4323da3dd5bb67d44936 100644 (file)
@@ -89,6 +89,7 @@ static int PLItemUpdated(vlc_object_t *, const char *,
                          vlc_value_t, vlc_value_t, void *);
 static int PlaylistUpdated(vlc_object_t *, const char *,
                            vlc_value_t, vlc_value_t, void *);
+
 static int PlaybackModeUpdated(vlc_object_t *, const char *,
                                vlc_value_t, vlc_value_t, void *);
 static int VolumeUpdated(vlc_object_t *, const char *,
@@ -413,6 +414,31 @@ static int PLItemUpdated(vlc_object_t *p_this, const char *psz_var,
     return VLC_SUCCESS;
 }
 
+static int PLItemAppended(vlc_object_t *p_this, const char *psz_var,
+                           vlc_value_t oldval, vlc_value_t new_val, void *param)
+{
+    NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
+
+    playlist_add_t *p_add = new_val.p_address;
+    NSArray *o_val = [NSArray arrayWithObjects:[NSNumber numberWithInt:p_add->i_node], [NSNumber numberWithInt:p_add->i_item], nil];
+    [[VLCMain sharedInstance] performSelectorOnMainThread:@selector(plItemAppended:) withObject:o_val waitUntilDone:NO];
+
+    [o_pool release];
+    return VLC_SUCCESS;
+}
+
+static int PLItemRemoved(vlc_object_t *p_this, const char *psz_var,
+                           vlc_value_t oldval, vlc_value_t new_val, void *param)
+{
+    NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
+
+    NSNumber *o_val = [NSNumber numberWithInt:new_val.i_int];
+    [[VLCMain sharedInstance] performSelectorOnMainThread:@selector(plItemRemoved:) withObject:o_val waitUntilDone:NO];
+
+    [o_pool release];
+    return VLC_SUCCESS;
+}
+
 static int PlaylistUpdated(vlc_object_t *p_this, const char *psz_var,
                          vlc_value_t oldval, vlc_value_t new_val, void *param)
 {
@@ -682,8 +708,8 @@ static VLCMain *_o_sharedMainInstance = nil;
     var_AddCallback(p_playlist, "item-change", PLItemUpdated, self);
     var_AddCallback(p_playlist, "activity", PLItemChanged, self);
     var_AddCallback(p_playlist, "leaf-to-parent", PlaylistUpdated, self);
-    var_AddCallback(p_playlist, "playlist-item-append", PlaylistUpdated, self);
-    var_AddCallback(p_playlist, "playlist-item-deleted", PlaylistUpdated, self);
+    var_AddCallback(p_playlist, "playlist-item-append", PLItemAppended, self);
+    var_AddCallback(p_playlist, "playlist-item-deleted", PLItemRemoved, self);
     var_AddCallback(p_playlist, "random", PlaybackModeUpdated, self);
     var_AddCallback(p_playlist, "repeat", PlaybackModeUpdated, self);
     var_AddCallback(p_playlist, "loop", PlaybackModeUpdated, self);
@@ -856,8 +882,8 @@ static bool f_appExit = false;
     var_DelCallback(p_playlist, "item-change", PLItemUpdated, self);
     var_DelCallback(p_playlist, "activity", PLItemChanged, self);
     var_DelCallback(p_playlist, "leaf-to-parent", PlaylistUpdated, self);
-    var_DelCallback(p_playlist, "playlist-item-append", PlaylistUpdated, self);
-    var_DelCallback(p_playlist, "playlist-item-deleted", PlaylistUpdated, self);
+    var_DelCallback(p_playlist, "playlist-item-append", PLItemAppended, self);
+    var_DelCallback(p_playlist, "playlist-item-deleted", PLItemRemoved, self);
     var_DelCallback(p_playlist, "random", PlaybackModeUpdated, self);
     var_DelCallback(p_playlist, "repeat", PlaybackModeUpdated, self);
     var_DelCallback(p_playlist, "loop", PlaybackModeUpdated, self);
@@ -1289,6 +1315,23 @@ static bool f_appExit = false;
 
 #pragma mark -
 #pragma mark Interface updaters
+
+- (void)plItemAppended:(NSArray *)o_val
+{
+    int i_node = [[o_val objectAtIndex:0] intValue];
+    int i_item = [[o_val objectAtIndex:1] intValue];
+
+    [[[self playlist] model] addItem:i_item withParentNode:i_node];
+}
+
+- (void)plItemRemoved:(NSNumber *)o_val
+{
+    int i_item = [o_val intValue];
+
+    [[[self playlist] model] removeItem:i_item];
+}
+
+
 // This must be called on main thread
 - (void)PlaylistItemChanged
 {
@@ -1848,7 +1891,7 @@ static const int kCurrentPreferencesVersion = 3;
     if (b_mediaKeySupport && !o_mediaKeyController)
         o_mediaKeyController = [[SPMediaKeyTap alloc] initWithDelegate:self];
 
-    if (b_mediaKeySupport && ([[[VLCMain sharedInstance] playlist] currentPlaylistRoot]->i_children > 0 ||
+    if (b_mediaKeySupport && ([[[[VLCMain sharedInstance] playlist] model] hasChildren] ||
                               p_current_input)) {
         if (!b_mediaKeyTrapEnabled) {
             b_mediaKeyTrapEnabled = YES;
index a8ed62077478136803d8f5e4493ed7f9fdefed9b..c6f833258c256df0fbe84e6a54f79e429cf0dbf4 100644 (file)
@@ -53,6 +53,8 @@
 
 @end
 
+#import "PLModel.h"
+
 /*****************************************************************************
  * VLCPlaylist interface
  *****************************************************************************/
     IBOutlet id o_playlist_header;
 
     int currentResumeTimeout;
+
+    PLModel *o_model;
 }
 
-- (void)setPlaylistRoot: (playlist_item_t *)root_item;
+- (PLModel *)model;
+
 - (playlist_item_t *)currentPlaylistRoot;
 - (void)reloadStyles;
 
index d609fcd0fc0de43f7610d1fe9801d51db376856f..e9273969cece12488bac2cf983e7dc5bef2be217 100644 (file)
 
 @end
 
-
 /*****************************************************************************
  * VLCPlaylistWizard implementation
  *****************************************************************************/
     [o_columnArray release];
 }
 
-- (void)setPlaylistRoot: (playlist_item_t *)root_item
+- (playlist_item_t *)currentPlaylistRoot
 {
-    p_current_root_item = root_item;
-    [o_outline_view reloadData];
+    // TODO remove
+    playlist_t *p_playlist = pl_Get(VLCIntf);
+    return p_playlist->p_playing;
 }
 
-- (playlist_item_t *)currentPlaylistRoot
+- (PLModel *)model
 {
-    return p_current_root_item;
+    return o_model;
 }
 
 - (void)reloadStyles
     playlist_t * p_playlist = pl_Get(VLCIntf);
     [o_outline_view setTarget: self];
     [o_outline_view setDelegate: self];
-    [o_outline_view setDataSource: self];
     [o_outline_view setAllowsEmptySelection: NO];
     [o_outline_view expandItem: [o_outline_view itemAtRow:0]];
 
     [self reloadStyles];
     [self initStrings];
 
+    o_model = [[PLModel alloc] initWithOutlineView:o_outline_view playlist:p_playlist rootItem:p_current_root_item];
+    [o_outline_view setDataSource:o_model];
+    [o_outline_view reloadData];
+
     [o_outline_view setDoubleAction: @selector(playItem:)];
 
     [o_outline_view registerForDraggedTypes: [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
 
 - (void)searchfieldChanged:(NSNotification *)o_notification
 {
+    assert(0);
     [o_search_field setStringValue:[[o_notification object] stringValue]];
 }
 
 
 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
 {
-    // FIXME: unsafe
-    playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
-
-    if (p_item) {
-        /* update the state of our Reveal-in-Finder menu items */
-        NSMutableString *o_mrl;
-        char *psz_uri = input_item_GetURI(p_item->p_input);
-
-        [o_mi_revealInFinder setEnabled: NO];
-        [o_mm_mi_revealInFinder setEnabled: NO];
-        if (psz_uri) {
-            o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
-
-            /* perform some checks whether it is a file and if it is local at all... */
-            NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
-            if (prefix_range.location != NSNotFound)
-                [o_mrl deleteCharactersInRange: prefix_range];
-
-            if ([o_mrl characterAtIndex:0] == '/') {
-                [o_mi_revealInFinder setEnabled: YES];
-                [o_mm_mi_revealInFinder setEnabled: YES];
-            }
-            free(psz_uri);
-        }
-
-        /* update our info-panel to reflect the new item */
-        [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
-    }
+//    // FIXME: unsafe
+//    playlist_item_t * p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
+//
+//    if (p_item) {
+//        /* update the state of our Reveal-in-Finder menu items */
+//        NSMutableString *o_mrl;
+//        char *psz_uri = input_item_GetURI(p_item->p_input);
+//
+//        [o_mi_revealInFinder setEnabled: NO];
+//        [o_mm_mi_revealInFinder setEnabled: NO];
+//        if (psz_uri) {
+//            o_mrl = [NSMutableString stringWithUTF8String: psz_uri];
+//
+//            /* perform some checks whether it is a file and if it is local at all... */
+//            NSRange prefix_range = [o_mrl rangeOfString: @"file:"];
+//            if (prefix_range.location != NSNotFound)
+//                [o_mrl deleteCharactersInRange: prefix_range];
+//
+//            if ([o_mrl characterAtIndex:0] == '/') {
+//                [o_mi_revealInFinder setEnabled: YES];
+//                [o_mm_mi_revealInFinder setEnabled: YES];
+//            }
+//            free(psz_uri);
+//        }
+//
+//        /* update our info-panel to reflect the new item */
+//        [[[VLCMain sharedInstance] info] updatePanelWithItem:p_item->p_input];
+//    }
 }
 
 - (BOOL)isSelectionEmpty
    deleted. We don't do it when not required since this verification takes
    quite a long time on big playlists (yes, pretty hacky). */
 
-- (BOOL)isItem: (playlist_item_t *)p_item inNode: (playlist_item_t *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
+// todo remove useless parameters
+- (BOOL)isItem: (PLItem *)p_item inNode: (PLItem *)p_node checkItemExistence:(BOOL)b_check locked:(BOOL)b_locked
 {
-    playlist_t * p_playlist = pl_Get(VLCIntf);
-    playlist_item_t *p_temp_item = p_item;
-
-    if (!p_node)
-        return NO;
+    PLItem *p_temp_item = p_item;
 
-    if (p_node == p_item)
+    if ([p_node plItemId] == [p_item plItemId])
         return YES;
 
-    if (p_node->i_children < 1)
-        return NO;
-
-    if (p_temp_item) {
-        int i;
-        if (!b_locked) PL_LOCK;
-
-        if (b_check) {
-        /* Since outlineView: willDisplayCell:... may call this function with
-           p_items that don't exist anymore, first check if the item is still
-           in the playlist. Any cleaner solution welcomed. */
-            for (i = 0; i < p_playlist->all_items.i_size; i++) {
-                if (ARRAY_VAL(p_playlist->all_items, i) == p_item)
-                    break;
-                else if (i == p_playlist->all_items.i_size - 1)
-                {
-                    if (!b_locked) PL_UNLOCK;
-                    return NO;
-                }
-            }
-        }
-
-        while(p_temp_item) {
-            p_temp_item = p_temp_item->p_parent;
-            if (p_temp_item == p_node) {
-                if (!b_locked) PL_UNLOCK;
-                return YES;
-            }
+    while(p_temp_item) {
+        p_temp_item = [p_temp_item parent];
+        if ([p_temp_item plItemId] == [p_node plItemId]) {
+            return YES;
         }
-        if (!b_locked) PL_UNLOCK;
     }
+
     return NO;
 }
 
             if (o_items == o_nodes) {
                 if (j == i) continue;
             }
-            if ([self isItem: [[o_items objectAtIndex:i] pointerValue]
-                    inNode: [[o_nodes objectAtIndex:j] pointerValue]
+            if ([self isItem: [o_items objectAtIndex:i]
+                    inNode: [o_nodes objectAtIndex:j]
                     checkItemExistence: NO locked:NO]) {
                 [o_items removeObjectAtIndex:i];
                 /* We need to execute the next iteration with the same index
     if (sender != nil && [o_outline_view clickedRow] == -1 && sender != o_mi_play)
         return;
 
-    p_item = [[o_outline_view itemAtRow:[o_outline_view selectedRow]] pointerValue];
-
     PL_LOCK;
+    PLItem *o_item = [o_outline_view itemAtRow:[o_outline_view selectedRow]];
+    p_item = playlist_ItemGetById(p_playlist, [o_item plItemId]);
+
     if (p_item) {
         if (p_item->i_children == -1) {
             p_node = p_item->p_parent;
     [selectedRows getIndexes:indexes maxCount:count inIndexRange:nil];
 
     NSMutableString * o_mrl;
-    playlist_item_t *p_item;
     for (NSUInteger i = 0; i < count; i++) {
-        p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
+        PLItem *o_item = [o_outline_view itemAtRow:indexes[i]];
 
-        if (! p_item || !p_item->p_input)
-            continue;
-
-        char * psz_url = decode_URI(input_item_GetURI(p_item->p_input));
+        char * psz_url = decode_URI(input_item_GetURI([o_item input]));
         o_mrl = [[NSMutableString alloc] initWithString: [NSString stringWithUTF8String:psz_url ? psz_url : ""]];
         if (psz_url != NULL)
             free( psz_url );
     NSUInteger indexes[i_count];
     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
     for (int i = 0; i < i_count; i++) {
-        p_item = [[o_outline_view itemAtRow:indexes[i]] pointerValue];
+        PLItem *o_item = [o_outline_view itemAtRow:indexes[i]];
         [o_outline_view deselectRow: indexes[i]];
 
-        if (p_item) {
-            if (p_item->i_children == -1)
-                libvlc_MetaRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
-            else
-                msg_Dbg(p_intf, "preparsing nodes not implemented");
+        if (![o_item isLeaf]) {
+            msg_Dbg(p_intf, "preparsing nodes not implemented");
+            continue;
         }
+
+        libvlc_MetaRequest(p_intf->p_libvlc, [o_item input], META_REQUEST_OPTION_NONE);
+
     }
     [self playlistUpdated];
 }
     NSUInteger indexes[i_count];
     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
     for (int i = 0; i < i_count; i++) {
-        p_item = [[o_outline_view itemAtRow: indexes[i]] pointerValue];
+        PLItem *o_item = [o_outline_view itemAtRow: indexes[i]];
 
-        if (p_item && p_item->i_children == -1)
-            libvlc_ArtRequest(p_intf->p_libvlc, p_item->p_input, META_REQUEST_OPTION_NONE);
+        if (![o_item isLeaf])
+            continue;
+
+        libvlc_ArtRequest(p_intf->p_libvlc, [o_item input], META_REQUEST_OPTION_NONE);
     }
     [self playlistUpdated];
 }
     playlist_t * p_playlist = pl_Get(p_intf);
 
     // check if deletion is allowed
-    if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
+    if (![[self model] editAllowed])
         return;
 
     o_selected_indexes = [o_outline_view selectedRowIndexes];
 
 
     NSUInteger indexes[i_count];
-    if (i_count == [o_outline_view numberOfRows]) {
-        PL_LOCK;
-        playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
-        PL_UNLOCK;
-        [self playlistUpdated];
-        return;
-    }
+//    if (i_count == [o_outline_view numberOfRows]) {
+//        PL_LOCK;
+//        playlist_NodeDelete(p_playlist, [self currentPlaylistRoot], true, false);
+//        PL_UNLOCK;
+//        [self playlistUpdated];
+//        return;
+//    }
     [o_selected_indexes getIndexes:indexes maxCount:i_count inIndexRange:nil];
     for (int i = 0; i < i_count; i++) {
-        id o_item = [o_outline_view itemAtRow: indexes[i]];
+        PLItem *o_item = [o_outline_view itemAtRow: indexes[i]];
         [o_outline_view deselectRow: indexes[i]];
 
-        PL_LOCK;
-        playlist_item_t *p_item = [o_item pointerValue];
-        if (!p_item || !p_item->p_input) {
-            PL_UNLOCK;
-            continue;
-        }
-
-        if (p_item->i_children != -1) {
-        //is a node and not an item
-            if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
-                [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
-                        checkItemExistence: NO locked:YES] == YES)
-                // if current item is in selected node and is playing then stop playlist
-                playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
-
-                playlist_NodeDelete(p_playlist, p_item, true, false);
-        } else
-            playlist_DeleteFromInput(p_playlist, p_item->p_input, pl_Locked);
-
-        PL_UNLOCK;
-        [o_outline_dict removeObjectForKey:[NSString stringWithFormat:@"%p", [o_item pointerValue]]];
-        [o_item release];
+        /// TODO
+//        if (p_item->i_children != -1) {
+//        //is a node and not an item
+//            if (playlist_Status(p_playlist) != PLAYLIST_STOPPED &&
+//                [self isItem: playlist_CurrentPlayingItem(p_playlist) inNode: ((playlist_item_t *)[o_item pointerValue])
+//                        checkItemExistence: NO locked:YES] == YES)
+//                // if current item is in selected node and is playing then stop playlist
+//                playlist_Control(p_playlist, PLAYLIST_STOP, pl_Locked);
+//
+//                playlist_NodeDelete(p_playlist, p_item, true, false);
+//        } else
+
+            playlist_DeleteFromInput(p_playlist, [o_item input], pl_Unlocked);
+//        [[o_item parent] deleteChild:o_item];
+//
+//        [o_outline_view reloadData];
     }
 
-    [self playlistUpdated];
+//    [self playlistUpdated];
 }
 
 - (IBAction)sortNodeByName:(id)sender
     playlist_t * p_playlist = pl_Get(VLCIntf);
     playlist_item_t * p_item;
 
-    if ([o_outline_view selectedRow] > -1) {
-        p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
-        if (!p_item)
-            return;
-    } else
-        p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
-
-    PL_LOCK;
-    if (p_item->i_children > -1) // the item is a node
-        playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
-    else
-        playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
-
-    PL_UNLOCK;
-    [self playlistUpdated];
+    // TODO why do we need this kind of sort? It looks crap and confusing...
+
+//    if ([o_outline_view selectedRow] > -1) {
+//        p_item = [[o_outline_view itemAtRow: [o_outline_view selectedRow]] pointerValue];
+//        if (!p_item)
+//            return;
+//    } else
+//        p_item = [self currentPlaylistRoot]; // If no item is selected, sort the whole playlist
+//
+//    PL_LOCK;
+//    if (p_item->i_children > -1) // the item is a node
+//        playlist_RecursiveNodeSort(p_playlist, p_item, i_mode, ORDER_NORMAL);
+//    else
+//        playlist_RecursiveNodeSort(p_playlist, p_item->p_parent, i_mode, ORDER_NORMAL);
+//
+//    PL_UNLOCK;
+//    [self playlistUpdated];
 }
 
 - (input_item_t *)createItem:(NSDictionary *)o_one_item
 {
     playlist_t * p_playlist = pl_Get(VLCIntf);
     NSUInteger count = [o_array count];
-    BOOL b_usingPlaylist;
-    if ([self currentPlaylistRoot] == p_playlist->p_ml_category)
-        b_usingPlaylist = NO;
-    else
-        b_usingPlaylist = YES;
+    BOOL b_usingPlaylist = [[self model] currentRootType] == ROOT_TYPE_PLAYLIST;
 
     PL_LOCK;
     for (NSUInteger i_item = 0; i_item < count; i_item++) {
         PL_UNLOCK;
         vlc_gc_decref(p_input);
     }
-    [self playlistUpdated];
+//    [self playlistUpdated];
 }
 
 - (NSMutableArray *)subSearchItem:(playlist_item_t *)p_item
     b_rows = [o_outline_view numberOfRows] != 0;
 
     playlist_t *p_playlist = pl_Get(VLCIntf);
-    bool b_del_allowed = [self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category;
+    bool b_del_allowed = [[self model] editAllowed];
 
     [o_mi_play setEnabled: b_item_sel];
     [o_mi_delete setEnabled: b_item_sel && b_del_allowed];
 
     playlist_t *p_playlist = pl_Get(p_intf);
 
-    if ([o_identifier isEqualToString:TRACKNUM_COLUMN])
-        i_mode = SORT_TRACK_NUMBER;
-    else if ([o_identifier isEqualToString:TITLE_COLUMN])
-        i_mode = SORT_TITLE;
-    else if ([o_identifier isEqualToString:ARTIST_COLUMN])
-        i_mode = SORT_ARTIST;
-    else if ([o_identifier isEqualToString:GENRE_COLUMN])
-        i_mode = SORT_GENRE;
-    else if ([o_identifier isEqualToString:DURATION_COLUMN])
-        i_mode = SORT_DURATION;
-    else if ([o_identifier isEqualToString:ALBUM_COLUMN])
-        i_mode = SORT_ALBUM;
-    else if ([o_identifier isEqualToString:DESCRIPTION_COLUMN])
-        i_mode = SORT_DESCRIPTION;
-    else if ([o_identifier isEqualToString:URI_COLUMN])
-        i_mode = SORT_URI;
-    else
-        return;
-
     if (o_tc_sortColumn == o_tc)
         b_isSortDescending = !b_isSortDescending;
     else
     else
         i_type = ORDER_NORMAL;
 
-    PL_LOCK;
-    playlist_RecursiveNodeSort(p_playlist, [self currentPlaylistRoot], i_mode, i_type);
-    PL_UNLOCK;
+    [[self model] sortForColumn:o_identifier withMode:i_type];
+
+    // TODO rework, why do we need a full call here?
+//    [self playlistUpdated];
+
+    /* Clear indications of any existing column sorting */
+    NSUInteger count = [[o_outline_view tableColumns] count];
+    for (NSUInteger i = 0 ; i < count ; i++)
+        [o_outline_view setIndicatorImage:nil inTableColumn: [[o_outline_view tableColumns] objectAtIndex:i]];
+
+    [o_outline_view setHighlightedTableColumn:nil];
+    o_tc_sortColumn = nil;
 
-    [self playlistUpdated];
 
     o_tc_sortColumn = o_tc;
     [o_outline_view setHighlightedTableColumn:o_tc];
 
 
 - (void)outlineView:(NSOutlineView *)outlineView
-                                willDisplayCell:(id)cell
-                                forTableColumn:(NSTableColumn *)tableColumn
-                                item:(id)item
+    willDisplayCell:(id)cell
+     forTableColumn:(NSTableColumn *)tableColumn
+               item:(id)item
 {
     /* this method can be called when VLC is already dead, hence the extra checks */
     intf_thread_t * p_intf = VLCIntf;
     if (!p_intf)
         return;
     playlist_t *p_playlist = pl_Get(p_intf);
-    if (!p_playlist)
-        return;
 
     id o_playing_item;
 
     else
         fontToUse = [NSFont systemFontOfSize:11.];
 
-    if ([self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
-                        || [o_playing_item isEqual: item])
+    BOOL b_is_playing = NO;
+    PL_LOCK;
+    playlist_item_t *p_current_item = playlist_CurrentPlayingItem(p_playlist);
+    if (p_current_item) {
+        b_is_playing = p_current_item->i_id == [item plItemId];
+    }
+    PL_UNLOCK;
+
+    /*
+     TODO: repaint all items bold:
+     [self isItem: [o_playing_item pointerValue] inNode: [item pointerValue] checkItemExistence:YES locked:NO]
+     || [o_playing_item isEqual: item]
+     */
+
+    if (b_is_playing)
         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toHaveTrait:NSBoldFontMask]];
     else
         [cell setFont: [[NSFontManager sharedFontManager] convertFont:fontToUse toNotHaveTrait:NSBoldFontMask]];
         id o_item = [items objectAtIndex:i];
 
         /* Fill the items and nodes to move in 2 different arrays */
-        if (((playlist_item_t *)[o_item pointerValue])->i_children > 0)
+        if (![o_item isLeaf])
             [o_nodes_array addObject: o_item];
         else
             [o_items_array addObject: o_item];
     /* We refuse to drop an item in anything else than a child of the General
        Node. We still accept items that would be root nodes of the outlineview
        however, to allow drop in an empty playlist. */
-    if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
-            (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
-        return NSDragOperationNone;
-    }
+    /// todo
+    //    if (!(([self isItem: [item pointerValue] inNode: p_playlist->p_local_category checkItemExistence: NO locked: NO] ||
+//            (var_CreateGetBool(p_playlist, "media-library") && [self isItem: [item pointerValue] inNode: p_playlist->p_ml_category checkItemExistence: NO locked: NO])) || item == nil)) {
+//        return NSDragOperationNone;
+//    }
 
     /* Drop from the Playlist */
     if ([[o_pasteboard types] containsObject: @"VLCPlaylistItemPboardType"]) {
         NSUInteger count = [o_nodes_array count];
         for (NSUInteger i = 0 ; i < count ; i++) {
             /* We refuse to Drop in a child of an item we are moving */
-            if ([self isItem: [item pointerValue] inNode: [[o_nodes_array objectAtIndex:i] pointerValue] checkItemExistence: NO locked:NO]) {
+            if ([self isItem: item inNode: [o_nodes_array objectAtIndex:i] checkItemExistence: NO locked:NO]) {
                 return NSDragOperationNone;
             }
         }
            child of the respective general node, if is either the pl or the ml
            Else, choose the proposed parent as parent. */
         if (item == nil) {
-            if ([self currentPlaylistRoot] == p_playlist->p_local_category || [self currentPlaylistRoot] == p_playlist->p_ml_category)
-                p_new_parent = [self currentPlaylistRoot];
+            // TODO edit allowed / no drop in other types
+            if ([[self model] currentRootType] == ROOT_TYPE_PLAYLIST ||
+                [[self model] currentRootType] == ROOT_TYPE_MEDIALIBRARY)
+                item = [[self model] rootItem];
             else
                 return NO;
         }
-        else
-            p_new_parent = [item pointerValue];
 
         /* Make sure the proposed parent is a node.
            (This should never be true) */
             return NO;
 
         PL_LOCK;
+        p_new_parent = playlist_ItemGetById(p_playlist, [item plItemId]);
+        if (!p_new_parent) {
+            PL_UNLOCK;
+            return NO;
+        }
+
         NSUInteger j = 0;
         for (NSUInteger i = 0; i < count; i++) {
-            p_item = [[o_all_items objectAtIndex:i] pointerValue];
+            p_item = playlist_ItemGetById(p_playlist, [[o_all_items objectAtIndex:i] plItemId]);
             if (p_item)
                 pp_items[j++] = p_item;
         }
         free(pp_items);
 
         [self playlistUpdated];
-        i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", [[o_all_items objectAtIndex:0] pointerValue]]]];
+        i_row = [o_outline_view rowForItem:[o_all_items objectAtIndex:0]];
 
         if (i_row == -1)
-            i_row = [o_outline_view rowForItem:[o_outline_dict objectForKey:[NSString stringWithFormat: @"%p", p_new_parent]]];
+            i_row = [o_outline_view rowForItem:item];
 
         [o_outline_view deselectAll: self];
         [o_outline_view selectRowIndexes:[NSIndexSet indexSetWithIndex:i_row] byExtendingSelection:NO];
     }
 
     else if ([[o_pasteboard types] containsObject: NSFilenamesPboardType]) {
-        if ([self currentPlaylistRoot] != p_playlist->p_local_category && [self currentPlaylistRoot] != p_playlist->p_ml_category)
+        // TODO: can this already checked in drop validation?
+        if (![[self model] editAllowed])
             return NO;
 
         playlist_item_t *p_node = [item pointerValue];