]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/playlist_model.cpp
Qt4: let PLModel and PLSelector use MainInputManager instead of PlaylistEventManager
[vlc] / modules / gui / qt4 / components / playlist / playlist_model.cpp
1 /*****************************************************************************
2  * playlist_model.cpp : Manage playlist model
3  ****************************************************************************
4  * Copyright (C) 2006-2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *          Ilkka Ollakkka <ileoo (at) videolan dot org>
9  *          Jakob Leben <jleben@videolan.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include "qt4.hpp"
31 #include "dialogs_provider.hpp"
32 #include "components/playlist/playlist_model.hpp"
33 #include "dialogs/mediainfo.hpp"
34 #include "dialogs/playlist.hpp"
35 #include <vlc_intf_strings.h>
36
37 #include "pixmaps/types/type_unknown.xpm"
38
39 #include <assert.h>
40 #include <QIcon>
41 #include <QFont>
42 #include <QMenu>
43 #include <QApplication>
44 #include <QSettings>
45
46 #include "sorting.h"
47
48 QIcon PLModel::icons[ITEM_TYPE_NUMBER];
49
50 /*************************************************************************
51  * Playlist model implementation
52  *************************************************************************/
53
54 /*
55   This model is called two times, for the selector and the standard panel
56 */
57 PLModel::PLModel( playlist_t *_p_playlist,  /* THEPL */
58                   intf_thread_t *_p_intf,   /* main Qt p_intf */
59                   playlist_item_t * p_root,
60                   /*playlist_GetPreferredNode( THEPL, THEPL->p_local_category );
61                     and THEPL->p_root_category for SelectPL */
62                   QObject *parent )         /* Basic Qt parent */
63                   : QAbstractItemModel( parent )
64 {
65     p_intf            = _p_intf;
66     p_playlist        = _p_playlist;
67     i_cached_id       = -1;
68     i_cached_input_id = -1;
69     i_popup_item      = i_popup_parent = -1;
70     currentItem       = NULL;
71
72     rootItem          = NULL; /* PLItem rootItem, will be set in rebuild( ) */
73
74     /* Icons initialization */
75 #define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( x )
76     ADD_ICON( UNKNOWN , type_unknown_xpm );
77     ADD_ICON( FILE, ":/type/file" );
78     ADD_ICON( DIRECTORY, ":/type/directory" );
79     ADD_ICON( DISC, ":/type/disc" );
80     ADD_ICON( CDDA, ":/type/cdda" );
81     ADD_ICON( CARD, ":/type/capture-card" );
82     ADD_ICON( NET, ":/type/net" );
83     ADD_ICON( PLAYLIST, ":/type/playlist" );
84     ADD_ICON( NODE, ":/type/node" );
85 #undef ADD_ICON
86
87     rebuild( p_root, true );
88     CONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
89             this, processInputItemUpdate( input_item_t *) );
90     CONNECT( THEMIM, inputChanged( input_thread_t * ),
91             this, processInputItemUpdate( input_thread_t* ) );
92     CONNECT( THEMIM, playlistItemAppended( int, int ),
93              this, processItemAppend( int, int ) );
94     CONNECT( THEMIM, playlistItemRemoved( int ),
95              this, processItemRemoval( int ) );
96 }
97
98 PLModel::~PLModel()
99 {
100     delete rootItem;
101 }
102
103 Qt::DropActions PLModel::supportedDropActions() const
104 {
105     return Qt::CopyAction; /* Why not Qt::MoveAction */
106 }
107
108 Qt::ItemFlags PLModel::flags( const QModelIndex &index ) const
109 {
110     Qt::ItemFlags flags = QAbstractItemModel::flags( index );
111
112     PLItem *item = index.isValid() ? getItem( index ) : rootItem;
113
114     if( canEdit() )
115     {
116         PL_LOCK;
117         playlist_item_t *plItem =
118             playlist_ItemGetById( p_playlist, item->i_id );
119
120         if ( plItem && ( plItem->i_children > -1 ) )
121             flags |= Qt::ItemIsDropEnabled;
122
123         PL_UNLOCK;
124
125     }
126     flags |= Qt::ItemIsDragEnabled;
127
128     return flags;
129 }
130
131 QStringList PLModel::mimeTypes() const
132 {
133     QStringList types;
134     types << "vlc/qt-playlist-item";
135     return types;
136 }
137
138 QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
139 {
140     QMimeData *mimeData = new QMimeData();
141     QByteArray encodedData;
142     QDataStream stream( &encodedData, QIODevice::WriteOnly );
143     QModelIndexList list;
144
145     foreach( const QModelIndex &index, indexes ) {
146         if( index.isValid() && index.column() == 0 )
147             list.append(index);
148     }
149
150     qSort(list);
151
152     foreach( const QModelIndex &index, list ) {
153         PLItem *item = getItem( index );
154         stream.writeRawData( (char*) &item, sizeof( PLItem* ) );
155     }
156     mimeData->setData( "vlc/qt-playlist-item", encodedData );
157     return mimeData;
158 }
159
160 /* Drop operation */
161 bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
162                            int row, int column, const QModelIndex &parent )
163 {
164     if( data->hasFormat( "vlc/qt-playlist-item" ) )
165     {
166         if( action == Qt::IgnoreAction )
167             return true;
168
169         PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
170
171         PL_LOCK;
172         playlist_item_t *p_parent =
173             playlist_ItemGetById( p_playlist, parentItem->i_id );
174         if( !p_parent || p_parent->i_children == -1 )
175         {
176             PL_UNLOCK;
177             return false;
178         }
179
180         bool copy = false;
181         playlist_item_t *p_pl = p_playlist->p_local_category;
182         playlist_item_t *p_ml = p_playlist->p_ml_category;
183         if
184         (
185             row == -1 && (
186             ( p_pl && p_parent->p_input == p_pl->p_input ) ||
187             ( p_ml && p_parent->p_input == p_ml->p_input ) )
188         )
189             copy = true;
190         PL_UNLOCK;
191
192         QByteArray encodedData = data->data( "vlc/qt-playlist-item" );
193         if( copy )
194             dropAppendCopy( encodedData, parentItem );
195         else
196             dropMove( encodedData, parentItem, row );
197     }
198     return true;
199 }
200
201 void PLModel::dropAppendCopy( QByteArray& data, PLItem *target )
202 {
203     QDataStream stream( &data, QIODevice::ReadOnly );
204
205     PL_LOCK;
206     playlist_item_t *p_parent =
207             playlist_ItemGetById( p_playlist, target->i_id );
208     while( !stream.atEnd() )
209     {
210         PLItem *item;
211         stream.readRawData( (char*)&item, sizeof(PLItem*) );
212         playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
213         if( !p_item ) continue;
214         input_item_t *p_input = p_item->p_input;
215         playlist_AddExt ( p_playlist,
216             p_input->psz_uri, p_input->psz_name,
217             PLAYLIST_APPEND | PLAYLIST_SPREPARSE, PLAYLIST_END,
218             p_input->i_duration,
219             p_input->i_options, p_input->ppsz_options, p_input->optflagc,
220             ( p_parent == p_playlist->p_local_category ||
221             p_parent == p_playlist->p_local_onelevel ),
222             true );
223     }
224     PL_UNLOCK;
225 }
226
227 void PLModel::dropMove( QByteArray& data, PLItem *target, int row )
228 {
229     QDataStream stream( &data, QIODevice::ReadOnly );
230     QList<PLItem*> model_items;
231     QList<int> ids;
232     int new_pos = row == -1 ? target->children.size() : row;
233     int model_pos = new_pos;
234     while( !stream.atEnd() )
235     {
236         PLItem *item;
237         stream.readRawData( (char*)&item, sizeof(PLItem*) );
238
239         /* better not try to move a node into itself: */
240         PLItem *climber = target;
241         while( climber )
242         {
243             if( climber == item ) break;
244             climber = climber->parentItem;
245         }
246         if( climber ) continue;
247
248         if( item->parentItem == target &&
249             target->children.indexOf( item ) < model_pos )
250                 model_pos--;
251
252         ids.append( item->i_id );
253         model_items.append( item );
254
255         takeItem( item );
256     }
257     int count = ids.size();
258     if( count )
259     {
260         playlist_item_t *pp_items[count];
261
262         PL_LOCK;
263         for( int i = 0; i < count; i++ )
264         {
265             playlist_item_t *p_item = playlist_ItemGetById( p_playlist, ids[i] );
266             if( !p_item )
267             {
268                 PL_UNLOCK;
269                 return;
270             }
271             pp_items[i] = p_item;
272         }
273         playlist_item_t *p_parent =
274             playlist_ItemGetById( p_playlist, target->i_id );
275         playlist_TreeMoveMany( p_playlist, count, pp_items, p_parent,
276             new_pos );
277         PL_UNLOCK;
278
279         insertChildren( target, model_items, model_pos );
280     }
281 }
282
283 /* remove item with its id */
284 void PLModel::removeItem( int i_id )
285 {
286     PLItem *item = findById( rootItem, i_id );
287     removeItem( item );
288 }
289
290 void PLModel::activateItem( const QModelIndex &index )
291 {
292     assert( index.isValid() );
293     PLItem *item = getItem( index );
294     assert( item );
295     PL_LOCK;
296     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
297     activateItem( p_item );
298     PL_UNLOCK;
299 }
300
301 /* Must be entered with lock */
302 void PLModel::activateItem( playlist_item_t *p_item )
303 {
304     if( !p_item ) return;
305     playlist_item_t *p_parent = p_item;
306     while( p_parent )
307     {
308         if( p_parent->i_id == rootItem->i_id ) break;
309         p_parent = p_parent->p_parent;
310     }
311     if( p_parent )
312         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked,
313                           p_parent, p_item );
314 }
315
316 /****************** Base model mandatory implementations *****************/
317 QVariant PLModel::data( const QModelIndex &index, int role ) const
318 {
319     if( !index.isValid() ) return QVariant();
320     PLItem *item = getItem( index );
321     if( role == Qt::DisplayRole )
322     {
323         int metadata = columnToMeta( index.column() );
324         if( metadata == COLUMN_END ) return QVariant();
325
326         QString returninfo;
327         if( metadata == COLUMN_NUMBER )
328             returninfo = QString::number( index.row() + 1 );
329         else
330         {
331             char *psz = psz_column_meta( item->p_input, metadata );
332             returninfo = qfu( psz );
333             free( psz );
334         }
335         return QVariant( returninfo );
336     }
337     else if( role == Qt::DecorationRole && index.column() == 0  )
338     {
339         /* Use to segfault here because i_type wasn't always initialized */
340         if( item->p_input->i_type >= 0 )
341             return QVariant( PLModel::icons[item->p_input->i_type] );
342     }
343     else if( role == Qt::FontRole )
344     {
345         if( isCurrent( index ) )
346         {
347             QFont f; f.setBold( true ); return QVariant( f );
348         }
349     }
350     return QVariant();
351 }
352
353 bool PLModel::isCurrent( const QModelIndex &index ) const
354 {
355     if( !currentItem ) return false;
356     return getItem( index )->p_input == currentItem->p_input;
357 }
358
359 int PLModel::itemId( const QModelIndex &index ) const
360 {
361     return getItem( index )->i_id;
362 }
363
364 QVariant PLModel::headerData( int section, Qt::Orientation orientation,
365                               int role ) const
366 {
367     if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
368         return QVariant();
369
370     int meta_col = columnToMeta( section );
371
372     if( meta_col == COLUMN_END ) return QVariant();
373
374     return QVariant( qfu( psz_column_title( meta_col ) ) );
375 }
376
377 QModelIndex PLModel::index( int row, int column, const QModelIndex &parent )
378                   const
379 {
380     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
381
382     PLItem *childItem = parentItem->child( row );
383     if( childItem )
384         return createIndex( row, column, childItem );
385     else
386         return QModelIndex();
387 }
388
389 /* Return the index of a given item */
390 QModelIndex PLModel::index( PLItem *item, int column ) const
391 {
392     if( !item ) return QModelIndex();
393     const PLItem *parent = item->parent();
394     if( parent )
395         return createIndex( parent->children.lastIndexOf( item ),
396                             column, item );
397     return QModelIndex();
398 }
399
400 QModelIndex PLModel::parent( const QModelIndex &index ) const
401 {
402     if( !index.isValid() ) return QModelIndex();
403
404     PLItem *childItem = getItem( index );
405     if( !childItem )
406     {
407         msg_Err( p_playlist, "NULL CHILD" );
408         return QModelIndex();
409     }
410
411     PLItem *parentItem = childItem->parent();
412     if( !parentItem || parentItem == rootItem ) return QModelIndex();
413     if( !parentItem->parentItem )
414     {
415         msg_Err( p_playlist, "No parent parent, trying row 0 " );
416         msg_Err( p_playlist, "----- PLEASE REPORT THIS ------" );
417         return createIndex( 0, 0, parentItem );
418     }
419     QModelIndex ind = createIndex(parentItem->row(), 0, parentItem);
420     return ind;
421 }
422
423 int PLModel::columnCount( const QModelIndex &i) const
424 {
425     return columnFromMeta( COLUMN_END );
426 }
427
428 int PLModel::rowCount( const QModelIndex &parent ) const
429 {
430     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
431     return parentItem->childCount();
432 }
433
434 QStringList PLModel::selectedURIs()
435 {
436     QStringList lst;
437     for( int i = 0; i < current_selection.size(); i++ )
438     {
439         PLItem *item = getItem( current_selection[i] );
440         if( item )
441         {
442             PL_LOCK;
443             playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
444             if( p_item )
445             {
446                 char *psz = input_item_GetURI( p_item->p_input );
447                 if( psz )
448                 {
449                     lst.append( qfu(psz) );
450                     free( psz );
451                 }
452             }
453             PL_UNLOCK;
454         }
455     }
456     return lst;
457 }
458
459 /************************* General playlist status ***********************/
460
461 bool PLModel::hasRandom()
462 {
463     return var_GetBool( p_playlist, "random" );
464 }
465 bool PLModel::hasRepeat()
466 {
467     return var_GetBool( p_playlist, "repeat" );
468 }
469 bool PLModel::hasLoop()
470 {
471     return var_GetBool( p_playlist, "loop" );
472 }
473 void PLModel::setLoop( bool on )
474 {
475     var_SetBool( p_playlist, "loop", on ? true:false );
476     config_PutInt( p_playlist, "loop", on ? 1: 0 );
477 }
478 void PLModel::setRepeat( bool on )
479 {
480     var_SetBool( p_playlist, "repeat", on ? true:false );
481     config_PutInt( p_playlist, "repeat", on ? 1: 0 );
482 }
483 void PLModel::setRandom( bool on )
484 {
485     var_SetBool( p_playlist, "random", on ? true:false );
486     config_PutInt( p_playlist, "random", on ? 1: 0 );
487 }
488
489 /************************* Lookups *****************************/
490
491 PLItem *PLModel::findById( PLItem *root, int i_id )
492 {
493     return findInner( root, i_id, false );
494 }
495
496 PLItem *PLModel::findByInput( PLItem *root, int i_id )
497 {
498     PLItem *result = findInner( root, i_id, true );
499     return result;
500 }
501
502 #define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
503 #define ICACHE( i, p ) { i_cached_input_id = i; p_cached_item_bi = p; }
504
505 PLItem * PLModel::findInner( PLItem *root, int i_id, bool b_input )
506 {
507     if( !root ) return NULL;
508     if( ( !b_input && i_cached_id == i_id) ||
509         ( b_input && i_cached_input_id ==i_id ) )
510     {
511         return b_input ? p_cached_item_bi : p_cached_item;
512     }
513
514     if( !b_input && root->i_id == i_id )
515     {
516         CACHE( i_id, root );
517         return root;
518     }
519     else if( b_input && root->p_input->i_id == i_id )
520     {
521         ICACHE( i_id, root );
522         return root;
523     }
524
525     QList<PLItem *>::iterator it = root->children.begin();
526     while ( it != root->children.end() )
527     {
528         if( !b_input && (*it)->i_id == i_id )
529         {
530             CACHE( i_id, (*it) );
531             return p_cached_item;
532         }
533         else if( b_input && (*it)->p_input->i_id == i_id )
534         {
535             ICACHE( i_id, (*it) );
536             return p_cached_item_bi;
537         }
538         if( (*it)->children.size() )
539         {
540             PLItem *childFound = findInner( (*it), i_id, b_input );
541             if( childFound )
542             {
543                 if( b_input )
544                     ICACHE( i_id, childFound )
545                 else
546                     CACHE( i_id, childFound )
547                 return childFound;
548             }
549         }
550         it++;
551     }
552     return NULL;
553 }
554 #undef CACHE
555 #undef ICACHE
556
557 PLItem *PLModel::getItem( QModelIndex index )
558 {
559     assert( index.isValid() );
560     return static_cast<PLItem*>( index.internalPointer() );
561 }
562
563 int PLModel::columnToMeta( int _column ) const
564 {
565     int meta = 1;
566     int column = 0;
567
568     while( column != _column && meta != COLUMN_END )
569     {
570         meta <<= 1;
571         column++;
572     }
573
574     return meta;
575 }
576
577 int PLModel::columnFromMeta( int meta_col ) const
578 {
579     int meta = 1;
580     int column = 0;
581
582     while( meta != meta_col && meta != COLUMN_END )
583     {
584         meta <<= 1;
585         column++;
586     }
587
588     return column;
589 }
590
591 bool PLModel::canEdit() const
592 {
593   return (
594     rootItem != NULL &&
595     (
596       rootItem->p_input == p_playlist->p_local_category->p_input ||
597       (
598         p_playlist->p_ml_category &&
599         rootItem->p_input == p_playlist->p_ml_category->p_input
600       )
601     )
602   );
603 }
604 /************************* Updates handling *****************************/
605
606 /**** Events processing ****/
607 void PLModel::processInputItemUpdate( input_thread_t *p_input )
608 {
609     if( !p_input ) return;
610     processInputItemUpdate( input_GetItem( p_input ) );
611     if( p_input && !( p_input->b_dead || !vlc_object_alive( p_input ) ) )
612     {
613         PLItem *item = findByInput( rootItem, input_GetItem( p_input )->i_id );
614         currentItem = item;
615         emit currentChanged( index( item, 0 ) );
616     }
617     else
618     {
619         currentItem = NULL;
620     }
621 }
622 void PLModel::processInputItemUpdate( input_item_t *p_item )
623 {
624     if( !p_item ||  p_item->i_id <= 0 ) return;
625     PLItem *item = findByInput( rootItem, p_item->i_id );
626     if( item )
627         updateTreeItem( item );
628 }
629
630 void PLModel::processItemRemoval( int i_id )
631 {
632     if( i_id <= 0 ) return;
633     removeItem( i_id );
634 }
635
636 void PLModel::processItemAppend( int i_item, int i_parent )
637 {
638     playlist_item_t *p_item = NULL;
639     PLItem *newItem = NULL;
640
641     PLItem *nodeItem = findById( rootItem, i_parent );
642     if( !nodeItem ) return;
643
644     foreach( PLItem *existing, nodeItem->children )
645       if( existing->i_id == i_item ) return;
646
647     PL_LOCK;
648     p_item = playlist_ItemGetById( p_playlist, i_item );
649     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
650
651     newItem = new PLItem( p_item, nodeItem );
652     PL_UNLOCK;
653
654     beginInsertRows( index( nodeItem, 0 ), nodeItem->childCount(), nodeItem->childCount() );
655     nodeItem->appendChild( newItem );
656     endInsertRows();
657     updateTreeItem( newItem );
658     return;
659 end:
660     PL_UNLOCK;
661     return;
662 }
663
664
665 void PLModel::rebuild()
666 {
667     rebuild( NULL, false );
668 }
669
670 void PLModel::rebuild( playlist_item_t *p_root, bool b_first )
671 {
672     playlist_item_t* p_item;
673
674     /* Invalidate cache */
675     i_cached_id = i_cached_input_id = -1;
676
677     if( rootItem ) rootItem->removeChildren();
678
679     PL_LOCK;
680     if( p_root )
681     {
682         delete rootItem;
683         rootItem = new PLItem( p_root );
684     }
685     assert( rootItem );
686     /* Recreate from root */
687     updateChildren( rootItem );
688     if( (p_item = playlist_CurrentPlayingItem(p_playlist)) )
689         currentItem = findByInput( rootItem, p_item->p_input->i_id );
690     else
691         currentItem = NULL;
692     PL_UNLOCK;
693
694     /* And signal the view */
695     reset();
696
697     emit currentChanged( index( currentItem, 0 ) );
698 }
699
700 void PLModel::takeItem( PLItem *item )
701 {
702     assert( item );
703     PLItem *parent = item->parentItem;
704     assert( parent );
705     int i_index = parent->children.indexOf( item );
706
707     beginRemoveRows( index( parent, 0 ), i_index, i_index );
708     parent->takeChildAt( i_index );
709     endRemoveRows();
710 }
711
712 void PLModel::insertChildren( PLItem *node, QList<PLItem*>& items, int i_pos )
713 {
714     assert( node );
715     int count = items.size();
716     if( !count ) return;
717     beginInsertRows( index( node, 0 ), i_pos, i_pos + count - 1 );
718     for( int i = 0; i < count; i++ )
719     {
720         node->children.insert( i_pos + i, items[i] );
721         items[i]->parentItem = node;
722     }
723     endInsertRows();
724 }
725
726 void PLModel::removeItem( PLItem *item )
727 {
728     if( !item ) return;
729
730     if( item->i_id == i_cached_id ) i_cached_id = -1;
731     i_cached_input_id = -1;
732
733     if( currentItem == item || rootItem == item)
734     {
735         currentItem = NULL;
736         emit currentChanged( QModelIndex() );
737     }
738
739     if(item == rootItem)
740         rootItem = NULL;
741
742     if( item->parentItem ) {
743         int i = item->parentItem->children.indexOf( item );
744         beginRemoveRows( index( item->parentItem, 0), i, i );
745         item->parentItem->children.removeAt(i);
746         delete item;
747         endRemoveRows();
748     }
749     else delete item;
750
751 }
752
753 /* This function must be entered WITH the playlist lock */
754 void PLModel::updateChildren( PLItem *root )
755 {
756     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
757     updateChildren( p_node, root );
758 }
759
760 /* This function must be entered WITH the playlist lock */
761 void PLModel::updateChildren( playlist_item_t *p_node, PLItem *root )
762 {
763     playlist_item_t *p_item = playlist_CurrentPlayingItem(p_playlist);
764     for( int i = 0; i < p_node->i_children ; i++ )
765     {
766         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
767         PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
768         root->appendChild( newItem );
769         if( p_item && newItem->p_input == p_item->p_input )
770         {
771             currentItem = newItem;
772             emit currentChanged( index( currentItem, 0 ) );
773         }
774         if( p_node->pp_children[i]->i_children != -1 )
775             updateChildren( p_node->pp_children[i], newItem );
776     }
777 }
778
779 /* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
780 void PLModel::updateTreeItem( PLItem *item )
781 {
782     if( !item ) return;
783     emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) ) );
784 }
785
786 /************************* Actions ******************************/
787
788 /**
789  * Deletion, here we have to do a ugly slow hack as we retrieve the full
790  * list of indexes to delete at once: when we delete a node and all of
791  * its children, we need to update the list.
792  * Todo: investigate whethere we can use ranges to be sure to delete all items?
793  */
794 void PLModel::doDelete( QModelIndexList selected )
795 {
796     if( !canEdit() ) return;
797
798     for( int i = selected.size() -1 ; i >= 0; i-- )
799     {
800         QModelIndex index = selected[i];
801         if( index.column() != 0 ) continue;
802         PLItem *item = getItem( index );
803         if( item )
804         {
805             if( item->children.size() )
806                 recurseDelete( item->children, &selected );
807             doDeleteItem( item, &selected );
808         }
809         if( i > selected.size() ) i = selected.size();
810     }
811 }
812
813 void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
814 {
815     for( int i = children.size() - 1; i >= 0 ; i-- )
816     {
817         PLItem *item = children[i];
818         if( item->children.size() )
819             recurseDelete( item->children, fullList );
820         doDeleteItem( item, fullList );
821     }
822 }
823
824 void PLModel::doDeleteItem( PLItem *item, QModelIndexList *fullList )
825 {
826     QModelIndex deleteIndex = index( item, 0 );
827     fullList->removeAll( deleteIndex );
828
829     PL_LOCK;
830     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
831     if( !p_item )
832     {
833         PL_UNLOCK;
834         return;
835     }
836     if( p_item->i_children == -1 )
837         playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
838     else
839         playlist_NodeDelete( p_playlist, p_item, true, false );
840     PL_UNLOCK;
841
842     /* And finally, remove it from the tree */
843     removeItem( item );
844 }
845
846 /******* Volume III: Sorting and searching ********/
847 void PLModel::sort( int column, Qt::SortOrder order )
848 {
849     sort( rootItem->i_id, column, order );
850 }
851
852 void PLModel::sort( int i_root_id, int column, Qt::SortOrder order )
853 {
854     int meta = columnToMeta( column );
855     if( meta == COLUMN_END ) return;
856
857     PLItem *item = findById( rootItem, i_root_id );
858     if( !item ) return;
859     QModelIndex qIndex = index( item, 0 );
860     int count = item->children.size();
861     if( count )
862     {
863         beginRemoveRows( qIndex, 0, count - 1 );
864         item->removeChildren();
865         endRemoveRows( );
866     }
867
868     PL_LOCK;
869     {
870         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
871                                                         i_root_id );
872         if( p_root )
873         {
874             playlist_RecursiveNodeSort( p_playlist, p_root,
875                                         i_column_sorting( meta ),
876                                         order == Qt::AscendingOrder ?
877                                             ORDER_NORMAL : ORDER_REVERSE );
878         }
879     }
880     if( count )
881     {
882         beginInsertRows( qIndex, 0, count - 1 );
883         updateChildren( item );
884         endInsertRows( );
885     }
886     PL_UNLOCK;
887 }
888
889 void PLModel::search( const QString& search_text )
890 {
891     /** \todo Fire the search with a small delay ? */
892     PL_LOCK;
893     {
894         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
895                                                         rootItem->i_id );
896         assert( p_root );
897         const char *psz_name = search_text.toUtf8().data();
898         playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
899     }
900     PL_UNLOCK;
901     rebuild();
902 }
903
904 /*********** Popup *********/
905 void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
906 {
907     int i_id = index.isValid() ? itemId( index ) : rootItem->i_id;
908
909     PL_LOCK;
910     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
911     if( !p_item )
912     {
913         PL_UNLOCK; return;
914     }
915     i_popup_item = index.isValid() ? p_item->i_id : -1;
916     i_popup_parent = index.isValid() ?
917         ( p_item->p_parent ? p_item->p_parent->i_id : -1 ) :
918         ( p_item->i_id );
919     i_popup_column = index.column();
920     /* check whether we are in tree view */
921     bool tree = false;
922     playlist_item_t *p_up = p_item;
923     while( p_up )
924     {
925         if ( p_up == p_playlist->p_root_category ) tree = true;
926         p_up = p_up->p_parent;
927     }
928     PL_UNLOCK;
929
930     current_selection = list;
931
932     QMenu menu;
933     if( i_popup_item > -1 )
934     {
935         menu.addAction( qtr(I_POP_PLAY), this, SLOT( popupPlay() ) );
936         menu.addAction( qtr(I_POP_DEL), this, SLOT( popupDel() ) );
937         menu.addSeparator();
938         menu.addAction( qtr(I_POP_STREAM), this, SLOT( popupStream() ) );
939         menu.addAction( qtr(I_POP_SAVE), this, SLOT( popupSave() ) );
940         menu.addSeparator();
941         menu.addAction( qtr(I_POP_INFO), this, SLOT( popupInfo() ) );
942         menu.addSeparator();
943         QMenu *sort_menu = menu.addMenu( qtr( "Sort by ") +
944             qfu( psz_column_title( columnToMeta( index.column() ) ) ) );
945         sort_menu->addAction( qtr( "Ascending" ),
946             this, SLOT( popupSortAsc() ) );
947         sort_menu->addAction( qtr( "Descending" ),
948             this, SLOT( popupSortDesc() ) );
949     }
950     if( tree && canEdit() )
951         menu.addAction( qtr(I_POP_ADD), this, SLOT( popupAddNode() ) );
952     if( i_popup_item > -1 )
953     {
954         menu.addSeparator();
955         menu.addAction( qtr( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
956     }
957     if( !menu.isEmpty() ) menu.exec( point );
958 }
959
960 void PLModel::popupDel()
961 {
962     doDelete( current_selection );
963 }
964
965 void PLModel::popupPlay()
966 {
967     PL_LOCK;
968     {
969         playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
970                                                         i_popup_item );
971         activateItem( p_item );
972     }
973     PL_UNLOCK;
974 }
975
976 void PLModel::popupInfo()
977 {
978     PL_LOCK;
979     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
980                                                     i_popup_item );
981     if( p_item )
982     {
983         input_item_t* p_input = p_item->p_input;
984         vlc_gc_incref( p_input );
985         PL_UNLOCK;
986         MediaInfoDialog *mid = new MediaInfoDialog( p_intf, p_input );
987         vlc_gc_decref( p_input );
988         mid->setParent( PlaylistDialog::getInstance( p_intf ),
989                         Qt::Dialog );
990         mid->show();
991     } else
992         PL_UNLOCK;
993 }
994
995 void PLModel::popupStream()
996 {
997     QStringList mrls = selectedURIs();
998     if( !mrls.isEmpty() )
999         THEDP->streamingDialog( NULL, mrls[0], false );
1000
1001 }
1002
1003 void PLModel::popupSave()
1004 {
1005     QStringList mrls = selectedURIs();
1006     if( !mrls.isEmpty() )
1007         THEDP->streamingDialog( NULL, mrls[0] );
1008 }
1009
1010 #include <QUrl>
1011 #include <QFileInfo>
1012 #include <QDesktopServices>
1013 void PLModel::popupExplore()
1014 {
1015     PL_LOCK;
1016     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1017                                                     i_popup_item );
1018     if( p_item )
1019     {
1020        input_item_t *p_input = p_item->p_input;
1021        char *psz_meta = input_item_GetURI( p_input );
1022        PL_UNLOCK;
1023        if( psz_meta )
1024        {
1025            const char *psz_access;
1026            const char *psz_demux;
1027            char  *psz_path;
1028            input_SplitMRL( &psz_access, &psz_demux, &psz_path, psz_meta );
1029
1030            if( EMPTY_STR( psz_access ) ||
1031                !strncasecmp( psz_access, "file", 4 ) ||
1032                !strncasecmp( psz_access, "dire", 4 ) )
1033            {
1034                QFileInfo info( qfu( psz_meta ) );
1035                QDesktopServices::openUrl(
1036                                QUrl::fromLocalFile( info.absolutePath() ) );
1037            }
1038            free( psz_meta );
1039        }
1040     }
1041     else
1042         PL_UNLOCK;
1043 }
1044
1045 #include <QInputDialog>
1046 void PLModel::popupAddNode()
1047 {
1048     bool ok;
1049     QString name = QInputDialog::getText( PlaylistDialog::getInstance( p_intf ),
1050         qtr( I_POP_ADD ), qtr( "Enter name for new node:" ),
1051         QLineEdit::Normal, QString(), &ok);
1052     if( !ok || name.isEmpty() ) return;
1053     PL_LOCK;
1054     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1055                                                     i_popup_parent );
1056     if( p_item )
1057     {
1058         playlist_NodeCreate( p_playlist, qtu( name ), p_item, 0, NULL );
1059     }
1060     PL_UNLOCK;
1061 }
1062
1063 void PLModel::popupSortAsc()
1064 {
1065     sort( i_popup_parent, i_popup_column, Qt::AscendingOrder );
1066 }
1067
1068 void PLModel::popupSortDesc()
1069 {
1070     sort( i_popup_parent, i_popup_column, Qt::DescendingOrder );
1071 }