]> git.sesse.net Git - vlc/blobdiff - modules/gui/qt4/components/playlist/playlist_model.cpp
qt4: move item removal from PLItem to PLModel...
[vlc] / modules / gui / qt4 / components / playlist / playlist_model.cpp
index 3dfaf18b08cec96c5cbec7764895a24ed9ad3958..30f4c6023ed27475a488d983e9189784461d4105 100644 (file)
@@ -5,6 +5,7 @@
  * $Id$
  *
  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
+ *          Ilkka Ollakkka <ileoo (at) 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
@@ -49,8 +50,6 @@ static int PlaylistChanged( vlc_object_t *, const char *,
                             vlc_value_t, vlc_value_t, void * );
 static int PlaylistNext( vlc_object_t *, const char *,
                          vlc_value_t, vlc_value_t, void * );
-static int ItemChanged( vlc_object_t *, const char *,
-                        vlc_value_t, vlc_value_t, void * );
 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
                          vlc_value_t oval, vlc_value_t nval, void *param );
 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
@@ -79,32 +78,45 @@ PLModel::PLModel( playlist_t *_p_playlist,  /* THEPL */
     i_cached_id       = -1;
     i_cached_input_id = -1;
     i_popup_item      = i_popup_parent = -1;
+    currentItem       = NULL;
 
     rootItem          = NULL; /* PLItem rootItem, will be set in rebuild( ) */
 
+    if( i_depth == DEPTH_SEL )
+        i_showflags = 0;
+    else
+    {
+        i_showflags = getSettings()->value( "qt-pl-showflags", COLUMN_DEFAULT ).toInt();
+        if( i_showflags < 1)
+            i_showflags = COLUMN_DEFAULT; /* reasonable default to show something */
+        else if ( i_showflags >= COLUMN_END )
+            i_showflags = COLUMN_END - 1; /* show everything */
+    }
+
     /* Icons initialization */
 #define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( QPixmap( x ) )
     ADD_ICON( UNKNOWN , type_unknown_xpm );
-    ADD_ICON( FILE, ":/type_file" );
-    ADD_ICON( DIRECTORY, ":/type_directory" );
-    ADD_ICON( DISC, ":/disc" );
-    ADD_ICON( CDDA, ":/cdda" );
-    ADD_ICON( CARD, ":/capture-card" );
-    ADD_ICON( NET, ":/type_net" );
-    ADD_ICON( PLAYLIST, ":/type_playlist" );
-    ADD_ICON( NODE, ":/type_node" );
+    ADD_ICON( FILE, ":/type/file" );
+    ADD_ICON( DIRECTORY, ":/type/directory" );
+    ADD_ICON( DISC, ":/type/disc" );
+    ADD_ICON( CDDA, ":/type/cdda" );
+    ADD_ICON( CARD, ":/type/capture-card" );
+    ADD_ICON( NET, ":/type/net" );
+    ADD_ICON( PLAYLIST, ":/type/playlist" );
+    ADD_ICON( NODE, ":/type/node" );
 #undef ADD_ICON
 
     rebuild( p_root );
-    CONNECT( THEMIM->getIM(), metaChanged( in),
-            this, ProcessInputItemUpdate( in) );
+    CONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
+            this, ProcessInputItemUpdate( input_item_t *) );
     CONNECT( THEMIM, inputChanged( input_thread_t * ),
             this, ProcessInputItemUpdate( input_thread_t* ) );
 }
 
 PLModel::~PLModel()
 {
-    getSettings()->setValue( "qt-pl-showflags", rootItem->i_showflags );
+    if(i_depth == -1)
+        getSettings()->setValue( "qt-pl-showflags", i_showflags );
     delCallbacks();
     delete rootItem;
 }
@@ -116,11 +128,38 @@ Qt::DropActions PLModel::supportedDropActions() const
 
 Qt::ItemFlags PLModel::flags( const QModelIndex &index ) const
 {
-    Qt::ItemFlags defaultFlags = QAbstractItemModel::flags( index );
-    if( index.isValid() )
-        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
-    else
-        return Qt::ItemIsDropEnabled | defaultFlags;
+    Qt::ItemFlags flags = QAbstractItemModel::flags( index );
+
+    PLItem *item = index.isValid() ?
+        static_cast<PLItem*>( index.internalPointer() ) :
+        rootItem;
+
+    input_item_t *pl_input = p_playlist->p_local_category->p_input;
+    input_item_t *ml_input = p_playlist->p_ml_category->p_input;
+
+    if( rootItem->i_id == p_playlist->p_root_onelevel->i_id
+          || rootItem->i_id == p_playlist->p_root_category->i_id )
+    {
+        if( item->p_input == pl_input
+            || item->p_input == ml_input)
+                flags |= Qt::ItemIsDropEnabled;
+    }
+    else if( rootItem->p_input == pl_input ||
+            rootItem->p_input == ml_input )
+    {
+        PL_LOCK;
+        playlist_item_t *plItem =
+            playlist_ItemGetById( p_playlist, item->i_id );
+
+        if ( plItem && ( plItem->i_children > -1 ) )
+            flags |= Qt::ItemIsDropEnabled;
+
+        PL_UNLOCK;
+
+    }
+    flags |= Qt::ItemIsDragEnabled;
+
+    return flags;
 }
 
 /* A list of model indexes are a playlist */
@@ -136,10 +175,17 @@ QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
     QMimeData *mimeData = new QMimeData();
     QByteArray encodedData;
     QDataStream stream( &encodedData, QIODevice::WriteOnly );
+    QModelIndexList list;
 
     foreach( const QModelIndex &index, indexes ) {
         if( index.isValid() && index.column() == 0 )
-            stream << itemId( index );
+            list.append(index);
+    }
+
+    qSort(list);
+
+    foreach( const QModelIndex &index, list ) {
+        stream << itemId( index );
     }
     mimeData->setData( "vlc/playlist-item-id", encodedData );
     return mimeData;
@@ -147,65 +193,92 @@ QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
 
 /* Drop operation */
 bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
-                           int row, int column, const QModelIndex &target )
+                           int row, int column, const QModelIndex &parent )
 {
     if( data->hasFormat( "vlc/playlist-item-id" ) )
     {
         if( action == Qt::IgnoreAction )
             return true;
 
-        if( !target.isValid() )
-            /* We don't want to move on an invalid position */
-            return true;
-
-        PLItem *targetItem = static_cast<PLItem*>( target.internalPointer() );
+        PL_LOCK;
 
-        QByteArray encodedData = data->data( "vlc/playlist-item-id" );
-        QDataStream stream( &encodedData, QIODevice::ReadOnly );
+        playlist_item_t *p_parent;
 
-        PLItem *newParentItem;
-        while( !stream.atEnd() )
+        if( !parent.isValid())
         {
-            int i;
-            int srcId;
-            stream >> srcId;
-
-            PL_LOCK;
-            playlist_item_t *p_target =
-                        playlist_ItemGetById( p_playlist, targetItem->i_id );
-            playlist_item_t *p_src = playlist_ItemGetById( p_playlist, srcId );
-
-            if( !p_target || !p_src )
+            if( row > -1)
+                p_parent = playlist_ItemGetById( p_playlist, rootItem->i_id );
+            else
             {
                 PL_UNLOCK;
-                return false;
+                return true;
             }
-            if( p_target->i_children == -1 ) /* A leaf */
+        }
+        else
+            p_parent = playlist_ItemGetById( p_playlist, itemId ( parent ) );
+
+        if( !p_parent || p_parent->i_children == -1 )
+        {
+            PL_UNLOCK;
+            return false;
+        }
+
+        bool copy = false;
+        if( row == -1 &&
+            ( p_parent->p_input == p_playlist->p_local_category->p_input
+            || p_parent->p_input == p_playlist->p_ml_category->p_input ) )
+                copy = true;
+
+        QByteArray encodedData = data->data( "vlc/playlist-item-id" );
+        QDataStream stream( &encodedData, QIODevice::ReadOnly );
+
+        if( copy )
+        {
+            while( !stream.atEnd() )
             {
-                PLItem *parentItem = targetItem->parent();
-                assert( parentItem );
-                playlist_item_t *p_parent =
-                         playlist_ItemGetById( p_playlist, parentItem->i_id );
-                if( !p_parent )
+                int i_id;
+                stream >> i_id;
+                playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
+                if( !p_item )
                 {
                     PL_UNLOCK;
                     return false;
                 }
-                for( i = 0 ; i< p_parent->i_children ; i++ )
-                    if( p_parent->pp_children[i] == p_target ) break;
-                // Move the item to the element after i
-                playlist_TreeMove( p_playlist, p_src, p_parent, i + 1 );
-                newParentItem = parentItem;
+                input_item_t *p_input = p_item->p_input;
+                playlist_AddExt ( p_playlist,
+                    p_input->psz_uri, p_input->psz_name,
+                    PLAYLIST_APPEND | PLAYLIST_SPREPARSE, PLAYLIST_END,
+                    p_input->i_duration,
+                    p_input->i_options, p_input->ppsz_options, p_input->optflagc,
+                    p_parent == p_playlist->p_local_category, true );
             }
-            else
+        }
+        else
+        {
+            QList<int> ids;
+            while( !stream.atEnd() )
             {
-                /* \todo: if we drop on a top-level node, use copy instead ? */
-                playlist_TreeMove( p_playlist, p_src, p_target, 0 );
-                i = 0;
-                newParentItem = targetItem;
+                int id;
+                stream >> id;
+                ids.append(id);
             }
-            PL_UNLOCK;
+            int count = ids.size();
+            playlist_item_t *items[count];
+            for( int i = 0; i < count; i++ )
+            {
+                playlist_item_t *item = playlist_ItemGetById( p_playlist, ids[i] );
+                if( !item )
+                {
+                    PL_UNLOCK;
+                    return false;
+                }
+                items[i] = item;
+            }
+            playlist_TreeMoveMany( p_playlist, count, items, p_parent,
+                (row == -1 ? p_parent->i_children : row) );
         }
+
+        PL_UNLOCK;
         /*TODO: That's not a good idea to rebuild the playlist */
         rebuild();
     }
@@ -216,7 +289,7 @@ bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
 void PLModel::removeItem( int i_id )
 {
     PLItem *item = FindById( rootItem, i_id );
-    if( item ) item->remove( item );
+    RemoveItem( item );
 }
 
 /* callbacks and slots */
@@ -224,7 +297,7 @@ void PLModel::addCallbacks()
 {
     /* Some global changes happened -> Rebuild all */
     var_AddCallback( p_playlist, "intf-change", PlaylistChanged, this );
-    /* We went to the next item 
+    /* We went to the next item
     var_AddCallback( p_playlist, "item-current", PlaylistNext, this );
     */
     /* One item has been updated */
@@ -234,7 +307,6 @@ void PLModel::addCallbacks()
 
 void PLModel::delCallbacks()
 {
-    var_DelCallback( p_playlist, "item-change", ItemChanged, this );
     /*
     var_DelCallback( p_playlist, "item-current", PlaylistNext, this );
     */
@@ -276,17 +348,37 @@ QVariant PLModel::data( const QModelIndex &index, int role ) const
     PLItem *item = static_cast<PLItem*>(index.internalPointer());
     if( role == Qt::DisplayRole )
     {
-        return QVariant( item->columnString( index.column() ) );
+        if( i_depth == DEPTH_SEL )
+        {
+            vlc_mutex_lock( &item->p_input->lock );
+            QString returninfo = QString( qfu( item->p_input->psz_name ) );
+            vlc_mutex_unlock( &item->p_input->lock );
+            return QVariant(returninfo);
+        }
+
+        int metadata = metaColumn( index.column() );
+        if( metadata == COLUMN_END ) return QVariant();
+
+        QString returninfo;
+        if( metadata == COLUMN_NUMBER )
+            returninfo = QString::number( index.row() + 1 );
+        else
+        {
+            char *psz = psz_column_meta( item->p_input, metadata );
+            returninfo = qfu( psz );
+            free( psz );
+        }
+        return QVariant( returninfo );
     }
     else if( role == Qt::DecorationRole && index.column() == 0  )
     {
         /* Use to segfault here because i_type wasn't always initialized */
-        if( item->i_type >= 0 )
-            return QVariant( PLModel::icons[item->i_type] );
+        if( item->p_input->i_type >= 0 )
+            return QVariant( PLModel::icons[item->p_input->i_type] );
     }
     else if( role == Qt::FontRole )
     {
-        if( item->b_current == true )
+        if( isCurrent( index ) )
         {
             QFont f; f.setBold( true ); return QVariant( f );
         }
@@ -294,10 +386,11 @@ QVariant PLModel::data( const QModelIndex &index, int role ) const
     return QVariant();
 }
 
-bool PLModel::isCurrent( const QModelIndex &index )
+bool PLModel::isCurrent( const QModelIndex &index ) const
 {
     assert( index.isValid() );
-    return static_cast<PLItem*>(index.internalPointer())->b_current;
+    if( !currentItem ) return false;
+    return static_cast<PLItem*>(index.internalPointer())->p_input == currentItem->p_input;
 }
 
 int PLModel::itemId( const QModelIndex &index ) const
@@ -309,9 +402,16 @@ int PLModel::itemId( const QModelIndex &index ) const
 QVariant PLModel::headerData( int section, Qt::Orientation orientation,
                               int role ) const
 {
-    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
-            return QVariant( rootItem->columnString( section ) );
-    return QVariant();
+    if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
+        return QVariant();
+
+    if( i_depth == DEPTH_SEL ) return QVariant( QString("") );
+
+    int meta_col = metaColumn( section );
+
+    if( meta_col == COLUMN_END ) return QVariant();
+
+    return QVariant( qfu( psz_column_title( meta_col ) ) );
 }
 
 QModelIndex PLModel::index( int row, int column, const QModelIndex &parent )
@@ -366,7 +466,17 @@ QModelIndex PLModel::parent( const QModelIndex &index ) const
 
 int PLModel::columnCount( const QModelIndex &i) const
 {
-    return rootItem->item_col_strings.count();
+    int columnCount=0;
+    int metadata=1;
+    if( i_depth == DEPTH_SEL ) return 1;
+
+    while( metadata < COLUMN_END )
+    {
+        if( metadata & i_showflags )
+            columnCount++;
+        metadata <<= 1;
+    }
+    return columnCount;
 }
 
 int PLModel::childrenCount( const QModelIndex &parent ) const
@@ -391,23 +501,23 @@ QStringList PLModel::selectedURIs()
     QStringList lst;
     for( int i = 0; i < current_selection.size(); i++ )
     {
-        PL_LOCK;
         PLItem *item = static_cast<PLItem*>
                     (current_selection[i].internalPointer());
         if( item )
         {
+            PL_LOCK;
             playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
             if( p_item )
             {
                 char *psz = input_item_GetURI( p_item->p_input );
                 if( psz )
                 {
-                    lst.append( QString( psz ) );
+                    lst.append( psz );
                     free( psz );
                 }
             }
+            PL_UNLOCK;
         }
-        PL_UNLOCK;
     }
     return lst;
 }
@@ -416,18 +526,15 @@ QStringList PLModel::selectedURIs()
 
 bool PLModel::hasRandom()
 {
-    if( var_GetBool( p_playlist, "random" ) ) return true;
-    return false;
+    return var_GetBool( p_playlist, "random" );
 }
 bool PLModel::hasRepeat()
 {
-    if( var_GetBool( p_playlist, "repeat" ) ) return true;
-    return false;
+    return var_GetBool( p_playlist, "repeat" );
 }
 bool PLModel::hasLoop()
 {
-    if( var_GetBool( p_playlist, "loop" ) ) return true;
-    return false;
+    return var_GetBool( p_playlist, "loop" );
 }
 void PLModel::setLoop( bool on )
 {
@@ -454,7 +561,8 @@ PLItem *PLModel::FindById( PLItem *root, int i_id )
 
 PLItem *PLModel::FindByInput( PLItem *root, int i_id )
 {
-    return FindInner( root, i_id, true );
+    PLItem *result = FindInner( root, i_id, true );
+    return result;
 }
 
 #define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
@@ -473,7 +581,7 @@ PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
         CACHE( i_id, root );
         return root;
     }
-    else if( b_input && root->i_input_id == i_id )
+    else if( b_input && root->p_input->i_id == i_id )
     {
         ICACHE( i_id, root );
         return root;
@@ -487,7 +595,7 @@ PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
             CACHE( i_id, (*it) );
             return p_cached_item;
         }
-        else if( b_input && (*it)->i_input_id == i_id )
+        else if( b_input && (*it)->p_input->i_id == i_id )
         {
             ICACHE( i_id, (*it) );
             return p_cached_item_bi;
@@ -511,6 +619,24 @@ PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
 #undef CACHE
 #undef ICACHE
 
+/* computes column id of meta data from visible column index */
+int PLModel::metaColumn( int column ) const
+{
+    int metadata = 1;
+    int running_index = -1;
+
+    while( metadata < COLUMN_END )
+    {
+        if( metadata & i_showflags )
+            running_index++;
+        if( running_index == column )
+            break;
+        metadata <<= 1;
+    }
+
+    if( running_index != column ) return COLUMN_END;
+    return metadata;
+}
 
 /************************* Updates handling *****************************/
 void PLModel::customEvent( QEvent *event )
@@ -534,18 +660,24 @@ void PLModel::customEvent( QEvent *event )
 void PLModel::ProcessInputItemUpdate( input_thread_t *p_input )
 {
     if( !p_input ) return;
-    ProcessInputItemUpdate( input_GetItem( p_input )->i_id );
+    ProcessInputItemUpdate( input_GetItem( p_input ) );
+    if( p_input && !( p_input->b_dead || !vlc_object_alive( p_input ) ) )
+    {
+        PLItem *item = FindByInput( rootItem, input_GetItem( p_input )->i_id );
+        currentItem = item;
+        emit currentChanged( index( item, 0 ) );
+    }
+    else
+    {
+        currentItem = NULL;
+    }
 }
-void PLModel::ProcessInputItemUpdate( int i_input_id )
+void PLModel::ProcessInputItemUpdate( input_item_t *p_item )
 {
-    if( i_input_id <= 0 ) return;
-    PLItem *item = FindByInput( rootItem, i_input_id );
+    if( !p_item ||  p_item->i_id <= 0 ) return;
+    PLItem *item = FindByInput( rootItem, p_item->i_id );
     if( item )
-    {
-        QPL_LOCK;
-        UpdateTreeItem( item, true );
-        QPL_UNLOCK;
-    }
+        UpdateTreeItem( item, true, true);
 }
 
 void PLModel::ProcessItemRemoval( int i_id )
@@ -563,18 +695,23 @@ void PLModel::ProcessItemAppend( const playlist_add_t *p_add )
     PLItem *newItem = NULL;
 
     PLItem *nodeItem = FindById( rootItem, p_add->i_node );
-    PL_LOCK;
-    if( !nodeItem ) goto end;
+    if( !nodeItem ) return;
 
+    PL_LOCK;
     p_item = playlist_ItemGetById( p_playlist, p_add->i_item );
     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
     if( i_depth == DEPTH_SEL && p_item->p_parent &&
                         p_item->p_parent->i_id != rootItem->i_id )
         goto end;
 
-    newItem = new PLItem( p_item, nodeItem, this );
+    newItem = new PLItem( p_item, nodeItem );
+    PL_UNLOCK;
+
+    beginInsertRows( index( nodeItem, 0 ), nodeItem->childCount(), nodeItem->childCount() );
     nodeItem->appendChild( newItem );
-    UpdateTreeItem( p_item, newItem, true );
+    endInsertRows();
+    UpdateTreeItem( newItem, true );
+    return;
 end:
     PL_UNLOCK;
     return;
@@ -594,84 +731,83 @@ void PLModel::rebuild( playlist_item_t *p_root )
     /* Invalidate cache */
     i_cached_id = i_cached_input_id = -1;
 
+    if( rootItem ) RemoveChildren( rootItem );
+
     PL_LOCK;
-    /* Clear the tree */
-    if( rootItem )
-    {
-        if( rootItem->children.size() )
-        {
-            beginRemoveRows( index( rootItem, 0 ), 0,
-                    rootItem->children.size() -1 );
-            qDeleteAll( rootItem->children );
-            rootItem->children.clear();
-            endRemoveRows();
-        }
-    }
     if( p_root )
     {
         delete rootItem;
-        rootItem = new PLItem( p_root, getSettings(), this );
+        rootItem = new PLItem( p_root );
     }
     assert( rootItem );
     /* Recreate from root */
-    UpdateNodeChildren( rootItem );
+    UpdateChildren( rootItem );
     if( (p_item = playlist_CurrentPlayingItem(p_playlist)) )
-    {
-        PLItem *currentItem = FindByInput( rootItem,
-                                           p_item->p_input->i_id );
-        if( currentItem )
-        {
-            UpdateTreeItem( p_item, currentItem,
-                            true, false );
-        }
-    }
+        currentItem = FindByInput( rootItem, p_item->p_input->i_id );
+    else
+        currentItem = NULL;
     PL_UNLOCK;
 
     /* And signal the view */
-    emit layoutChanged();
+    reset();
+    emit currentChanged( index( currentItem, 0 ) );
+
     addCallbacks();
 }
 
+void PLModel::RemoveItem( PLItem *item )
+{
+    if( !item ) return;
+    if( currentItem && currentItem->p_input == item->p_input )
+    {
+        currentItem = NULL;
+        emit currentChanged( QModelIndex() );
+    }
+    PLItem *parent = item->parentItem;
+    assert( parent );
+    int i_index = parent->children.indexOf( item );
+    parent->children.removeAt( i_index );
+    delete item;
+}
+void PLModel::RemoveChildren( PLItem *root )
+{
+  if( root->children.size() )
+  {
+      qDeleteAll( root->children );
+      root->children.clear();
+  }
+}
+
 /* This function must be entered WITH the playlist lock */
-void PLModel::UpdateNodeChildren( PLItem *root )
+void PLModel::UpdateChildren( PLItem *root )
 {
     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
-    UpdateNodeChildren( p_node, root );
+    UpdateChildren( p_node, root );
 }
 
 /* This function must be entered WITH the playlist lock */
-void PLModel::UpdateNodeChildren( playlist_item_t *p_node, PLItem *root )
+void PLModel::UpdateChildren( playlist_item_t *p_node, PLItem *root )
 {
     for( int i = 0; i < p_node->i_children ; i++ )
     {
         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
-        PLItem *newItem =  new PLItem( p_node->pp_children[i], root, this );
-        root->appendChild( newItem, false );
-        UpdateTreeItem( newItem, false, true );
+        PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
+        root->appendChild( newItem );
         if( i_depth == DEPTH_PL && p_node->pp_children[i]->i_children != -1 )
-            UpdateNodeChildren( p_node->pp_children[i], newItem );
+            UpdateChildren( p_node->pp_children[i], newItem );
     }
 }
 
-/* This function must be entered WITH the playlist lock */
+/* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
 void PLModel::UpdateTreeItem( PLItem *item, bool signal, bool force )
 {
-    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
-    UpdateTreeItem( p_item, item, signal, force );
-}
-
-/* This function must be entered WITH the playlist lock */
-void PLModel::UpdateTreeItem( playlist_item_t *p_item, PLItem *item,
-                              bool signal, bool force )
-{
-    if ( !p_item )
+    if ( !item || !item->p_input )
         return;
-    if( !force && i_depth == DEPTH_SEL && p_item->p_parent &&
-                                 p_item->p_parent->i_id != rootItem->i_id )
+    if( !force && i_depth == DEPTH_SEL && item->parentItem &&
+                                 item->parentItem->p_input != rootItem->p_input )
         return;
-    item->update( p_item, p_item == playlist_CurrentPlayingItem( p_playlist ) );
     if( signal )
-        emit dataChanged( index( item, 0 ) , index( item, 1 ) );
+        emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) ) );
 }
 
 /************************* Actions ******************************/
@@ -695,6 +831,7 @@ void PLModel::doDelete( QModelIndexList selected )
                 recurseDelete( item->children, &selected );
             doDeleteItem( item, &selected );
         }
+        if( i > selected.size() ) i = selected.size();
     }
 }
 
@@ -722,16 +859,24 @@ void PLModel::doDeleteItem( PLItem *item, QModelIndexList *fullList )
         return;
     }
     if( p_item->i_children == -1 )
-        playlist_DeleteFromInput( p_playlist, item->i_input_id, pl_Locked );
+        playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
     else
         playlist_NodeDelete( p_playlist, p_item, true, false );
-    /* And finally, remove it from the tree */
-    item->remove( item );
     PL_UNLOCK;
+    /* And finally, remove it from the tree */
+    int itemIndex = item->parentItem->children.indexOf( item );
+    beginRemoveRows( index( item->parentItem, 0), itemIndex, itemIndex );
+    RemoveItem( item );
+    endRemoveRows();
 }
 
 /******* Volume III: Sorting and searching ********/
 void PLModel::sort( int column, Qt::SortOrder order )
+{
+    sort( rootItem->i_id, column, order );
+}
+
+void PLModel::sort( int i_root_id, int column, Qt::SortOrder order )
 {
     int i_index = -1;
     int i_flag = 0;
@@ -744,17 +889,26 @@ void PLModel::sort( int column, Qt::SortOrder order )
         if( column == i_index )
         {
             i_flag = i_column;
-            goto next;
+            break;
         }
     }
 
+    PLItem *item = FindById( rootItem, i_root_id );
+    if( !item ) return;
+    QModelIndex qIndex = index( item, 0 );
+    int count = item->children.size();
+    if( count )
+    {
+        beginRemoveRows( qIndex, 0, count - 1 );
+        RemoveChildren( item );
+        endRemoveRows( );
+    }
 
-next:
     PL_LOCK;
     {
         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
-                                                        rootItem->i_id );
-        if( p_root )
+                                                        i_root_id );
+        if( p_root && i_flag )
         {
             playlist_RecursiveNodeSort( p_playlist, p_root,
                                         i_column_sorting( i_flag ),
@@ -762,11 +916,16 @@ next:
                                             ORDER_NORMAL : ORDER_REVERSE );
         }
     }
+    if( count )
+    {
+        beginInsertRows( qIndex, 0, count - 1 );
+        UpdateChildren( item );
+        endInsertRows( );
+    }
     PL_UNLOCK;
-    rebuild();
 }
 
-void PLModel::search( QString search_text )
+void PLModel::search( const QString& search_text )
 {
     /** \todo Fire the search with a small delay ? */
     PL_LOCK;
@@ -774,7 +933,7 @@ void PLModel::search( QString search_text )
         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
                                                         rootItem->i_id );
         assert( p_root );
-        char *psz_name = search_text.toUtf8().data();
+        const char *psz_name = search_text.toUtf8().data();
         playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
     }
     PL_UNLOCK;
@@ -784,16 +943,33 @@ void PLModel::search( QString search_text )
 /*********** Popup *********/
 void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
 {
-    assert( index.isValid() );
+    int i_id = index.isValid() ? itemId( index ) : rootItem->i_id;
+
     PL_LOCK;
-    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, itemId( index ) );
-    if( p_item )
+    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
+    if( !p_item )
+    {
+        PL_UNLOCK; return;
+    }
+    i_popup_item = index.isValid() ? p_item->i_id : -1;
+    i_popup_parent = index.isValid() ?
+        ( p_item->p_parent ? p_item->p_parent->i_id : -1 ) :
+        ( p_item->i_id );
+    i_popup_column = index.column();
+    /* check whether we are in tree view */
+    bool tree = false;
+    playlist_item_t *p_up = p_item;
+    while( p_up )
+    {
+        if ( p_up == p_playlist->p_root_category ) tree = true;
+        p_up = p_up->p_parent;
+    }
+    PL_UNLOCK;
+
+    current_selection = list;
+    QMenu *menu = new QMenu;
+    if( i_popup_item > -1 )
     {
-        i_popup_item = p_item->i_id;
-        i_popup_parent = p_item->p_parent ? p_item->p_parent->i_id : -1;
-        PL_UNLOCK;
-        current_selection = list;
-        QMenu *menu = new QMenu;
         menu->addAction( qtr(I_POP_PLAY), this, SLOT( popupPlay() ) );
         menu->addAction( qtr(I_POP_DEL), this, SLOT( popupDel() ) );
         menu->addSeparator();
@@ -801,18 +977,22 @@ void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
         menu->addAction( qtr(I_POP_SAVE), this, SLOT( popupSave() ) );
         menu->addSeparator();
         menu->addAction( qtr(I_POP_INFO), this, SLOT( popupInfo() ) );
-        if( p_item->i_children > -1 )
-        {
-            menu->addSeparator();
-            menu->addAction( qtr(I_POP_SORT), this, SLOT( popupSort() ) );
-            menu->addAction( qtr(I_POP_ADD), this, SLOT( popupAdd() ) );
-        }
+        menu->addSeparator();
+        QMenu *sort_menu = menu->addMenu( qtr( "Sort by ") +
+            qfu( psz_column_title( metaColumn( index.column() ) ) ) );
+        sort_menu->addAction( qtr( "Ascending" ),
+            this, SLOT( popupSortAsc() ) );
+        sort_menu->addAction( qtr( "Descending" ),
+            this, SLOT( popupSortDesc() ) );
+    }
+    if( tree )
+        menu->addAction( qtr(I_POP_ADD), this, SLOT( popupAddNode() ) );
+    if( i_popup_item > -1 )
+    {
         menu->addSeparator();
         menu->addAction( qtr( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
-        menu->popup( point );
     }
-    else
-        PL_UNLOCK;
+    menu->popup( point );
 }
 
 
@@ -829,29 +1009,27 @@ void PLModel::viewchanged( int meta )
             _meta >>= 1;
         }
 
-        /* UNUSED        emit layoutAboutToBeChanged(); */
-        index = __MIN( index, rootItem->item_col_strings.count() );
+        index = __MIN( index, columnCount() );
         QModelIndex parent = createIndex( 0, 0, rootItem );
 
-        if( rootItem->i_showflags & meta )
+        if( i_showflags & meta )
             /* Removing columns */
         {
             beginRemoveColumns( parent, index, index+1 );
-            rootItem->i_showflags &= ~( meta );
-            getSettings()->setValue( "qt-pl-showflags", rootItem->i_showflags );
-            rootItem->updateColumnHeaders();
+            i_showflags &= ~( meta );
+            getSettings()->setValue( "qt-pl-showflags", i_showflags );
             endRemoveColumns();
         }
         else
         {
             /* Adding columns */
             beginInsertColumns( parent, index, index+1 );
-            rootItem->i_showflags |= meta;
-            getSettings()->setValue( "qt-pl-showflags", rootItem->i_showflags );
-            rootItem->updateColumnHeaders();
+            i_showflags |= meta;
+            getSettings()->setValue( "qt-pl-showflags", i_showflags );
             endInsertColumns();
         }
-        rebuild();
+
+        emit columnsChanged( meta );
     }
 }
 
@@ -886,7 +1064,8 @@ void PLModel::popupInfo()
         mid->setParent( PlaylistDialog::getInstance( p_intf ),
                         Qt::Dialog );
         mid->show();
-    }
+    } else
+        PL_UNLOCK;
 }
 
 void PLModel::popupStream()
@@ -939,6 +1118,33 @@ void PLModel::popupExplore()
         PL_UNLOCK;
 }
 
+#include <QInputDialog>
+void PLModel::popupAddNode()
+{
+    bool ok;
+    QString name = QInputDialog::getText( PlaylistDialog::getInstance( p_intf ),
+        qtr( I_POP_ADD ), qtr( "Enter name for new node:" ),
+        QLineEdit::Normal, QString(), &ok);
+    if( !ok || name.isEmpty() ) return;
+    PL_LOCK;
+    playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
+                                                    i_popup_parent );
+    if( p_item )
+    {
+        playlist_NodeCreate( p_playlist, qtu( name ), p_item, 0, NULL );
+    }
+    PL_UNLOCK;
+}
+
+void PLModel::popupSortAsc()
+{
+    sort( i_popup_parent, i_popup_column, Qt::AscendingOrder );
+}
+
+void PLModel::popupSortDesc()
+{
+    sort( i_popup_parent, i_popup_column, Qt::DescendingOrder );
+}
 /**********************************************************************
  * Playlist callbacks
  **********************************************************************/
@@ -962,15 +1168,6 @@ static int PlaylistNext( vlc_object_t *p_this, const char *psz_variable,
     return VLC_SUCCESS;
 }
 
-static int ItemChanged( vlc_object_t *p_this, const char *psz_variable,
-                        vlc_value_t oval, vlc_value_t nval, void *param )
-{
-    PLModel *p_model = (PLModel *) param;
-    PLEvent *event = new PLEvent( ItemUpdate_Type, nval.i_int );
-    QApplication::postEvent( p_model, event );
-    return VLC_SUCCESS;
-}
-
 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
                         vlc_value_t oval, vlc_value_t nval, void *param )
 {