Qt4 - Split PLItem and PLModel in two different files.
[vlc.git] / 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  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <assert.h>
29 #include <QIcon>
30 #include <QFont>
31 #include <QMenu>
32 #include <QApplication>
33
34 #include "qt4.hpp"
35 #include "components/playlist/playlist_model.hpp"
36 #include "dialogs/mediainfo.hpp"
37 #include <vlc_intf_strings.h>
38
39 #include "pixmaps/type_unknown.xpm"
40
41 QIcon PLModel::icons[ITEM_TYPE_NUMBER];
42
43 static int PlaylistChanged( vlc_object_t *, const char *,
44                             vlc_value_t, vlc_value_t, void * );
45 static int PlaylistNext( vlc_object_t *, const char *,
46                          vlc_value_t, vlc_value_t, void * );
47 static int ItemChanged( vlc_object_t *, const char *,
48                         vlc_value_t, vlc_value_t, void * );
49 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
50                          vlc_value_t oval, vlc_value_t nval, void *param );
51 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
52                         vlc_value_t oval, vlc_value_t nval, void *param );
53
54 /*************************************************************************
55  * Playlist model implementation
56  *************************************************************************/
57
58 /*
59   This model is called two times, for the selector and the standard panel
60 */
61 PLModel::PLModel( playlist_t *_p_playlist,  /* THEPL */
62                   intf_thread_t *_p_intf,   /* main Qt p_intf */
63                   playlist_item_t * p_root,
64                   /*playlist_GetPreferredNode( THEPL, THEPL->p_local_category );
65                     and THEPL->p_root_category for SelectPL */
66                   int _i_depth,             /* -1 for StandPL, 1 for SelectPL */
67                   QObject *parent )         /* Basic Qt parent */
68                   : QAbstractItemModel( parent )
69 {
70     i_depth = _i_depth;
71     assert( i_depth == DEPTH_SEL || i_depth == DEPTH_PL );
72     p_intf            = _p_intf;
73     p_playlist        = _p_playlist;
74     i_items_to_append = 0;
75     b_need_update     = false;
76     i_cached_id       = -1;
77     i_cached_input_id = -1;
78     i_popup_item      = i_popup_parent = -1;
79
80     rootItem          = NULL; /* PLItem rootItem, will be set in rebuild( ) */
81
82     /* Icons initialization */
83 #define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( QPixmap( x ) )
84     ADD_ICON( UNKNOWN , type_unknown_xpm );
85     ADD_ICON( FILE, ":/pixmaps/type_file.png" );
86     ADD_ICON( DIRECTORY, ":/pixmaps/type_directory.png" );
87     ADD_ICON( DISC, ":/pixmaps/disc_16px.png" );
88     ADD_ICON( CDDA, ":/pixmaps/cdda_16px.png" );
89     ADD_ICON( CARD, ":/pixmaps/capture-card_16px.png" );
90     ADD_ICON( NET, ":/pixmaps/type_net.png" );
91     ADD_ICON( PLAYLIST, ":/pixmaps/type_playlist.png" );
92     ADD_ICON( NODE, ":/pixmaps/type_node.png" );
93 #undef ADD_ICON
94
95     addCallbacks();
96     rebuild( p_root );
97 }
98
99 PLModel::~PLModel()
100 {
101     delCallbacks();
102     delete rootItem;
103 }
104
105 Qt::DropActions PLModel::supportedDropActions() const
106 {
107     return Qt::CopyAction; /* Why not Qt::MoveAction */
108 }
109
110 Qt::ItemFlags PLModel::flags( const QModelIndex &index ) const
111 {
112     Qt::ItemFlags defaultFlags = QAbstractItemModel::flags( index );
113     if( index.isValid() )
114         return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
115     else
116         return Qt::ItemIsDropEnabled | defaultFlags;
117 }
118
119 /* A list of model indexes are a playlist */
120 QStringList PLModel::mimeTypes() const
121 {
122     QStringList types;
123     types << "vlc/playlist-item-id";
124     return types;
125 }
126
127 QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
128 {
129     QMimeData *mimeData = new QMimeData();
130     QByteArray encodedData;
131     QDataStream stream( &encodedData, QIODevice::WriteOnly );
132
133     foreach( QModelIndex index, indexes ) {
134         if( index.isValid() && index.column() == 0 )
135             stream << itemId( index );
136     }
137     mimeData->setData( "vlc/playlist-item-id", encodedData );
138     return mimeData;
139 }
140
141 /* Drop operation */
142 bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
143                            int row, int column, const QModelIndex &target )
144 {
145     if( data->hasFormat( "vlc/playlist-item-id" ) )
146     {
147         if( action == Qt::IgnoreAction )
148             return true;
149
150         PLItem *targetItem;
151         if( target.isValid() )
152             targetItem = static_cast<PLItem*>( target.internalPointer() );
153         else
154             targetItem = rootItem;
155
156         QByteArray encodedData = data->data( "vlc/playlist-item-id" );
157         QDataStream stream( &encodedData, QIODevice::ReadOnly );
158
159         PLItem *newParentItem;
160         while( !stream.atEnd() )
161         {
162             int i;
163             int srcId;
164             stream >> srcId;
165
166             PL_LOCK;
167             playlist_item_t *p_target =
168                         playlist_ItemGetById( p_playlist, targetItem->i_id,
169                                               VLC_TRUE );
170             playlist_item_t *p_src = playlist_ItemGetById( p_playlist, srcId,
171                                                            VLC_TRUE );
172
173             if( !p_target || !p_src )
174             {
175                 PL_UNLOCK;
176                 return false;
177             }
178             if( p_target->i_children == -1 ) /* A leaf */
179             {
180                 PLItem *parentItem = targetItem->parent();
181                 assert( parentItem );
182                 playlist_item_t *p_parent =
183                          playlist_ItemGetById( p_playlist, parentItem->i_id,
184                                                VLC_TRUE );
185                 if( !p_parent )
186                 {
187                     PL_UNLOCK;
188                     return false;
189                 }
190                 for( i = 0 ; i< p_parent->i_children ; i++ )
191                     if( p_parent->pp_children[i] == p_target ) break;
192                 playlist_TreeMove( p_playlist, p_src, p_parent, i );
193                 newParentItem = parentItem;
194             }
195             else
196             {
197                 /* \todo: if we drop on a top-level node, use copy instead ? */
198                 playlist_TreeMove( p_playlist, p_src, p_target, 0 );
199                 i = 0;
200                 newParentItem = targetItem;
201             }
202             /* Remove from source */
203             PLItem *srcItem = FindById( rootItem, p_src->i_id );
204             // We dropped on the source selector. Ask the dialog to forward
205             // to the main view
206             if( !srcItem )
207             {
208                 emit shouldRemove( p_src->i_id );
209             }
210             else
211                 srcItem->remove( srcItem );
212
213             /* Display at new destination */
214             PLItem *newItem = new PLItem( p_src, newParentItem, this );
215             newParentItem->insertChild( newItem, i, true );
216             UpdateTreeItem( p_src, newItem, true );
217             if( p_src->i_children != -1 )
218                 UpdateNodeChildren( newItem );
219             PL_UNLOCK;
220         }
221     }
222     return true;
223 }
224
225 /* remove item with its id */
226 void PLModel::removeItem( int i_id )
227 {
228     PLItem *item = FindById( rootItem, i_id );
229     if( item ) item->remove( item );
230 }
231
232 /* callbacks and slots */
233 void PLModel::addCallbacks()
234 {
235     /* Some global changes happened -> Rebuild all */
236     var_AddCallback( p_playlist, "intf-change", PlaylistChanged, this );
237     /* We went to the next item */
238     var_AddCallback( p_playlist, "playlist-current", PlaylistNext, this );
239     /* One item has been updated */
240     var_AddCallback( p_playlist, "item-change", ItemChanged, this );
241     var_AddCallback( p_playlist, "item-append", ItemAppended, this );
242     var_AddCallback( p_playlist, "item-deleted", ItemDeleted, this );
243 }
244
245 void PLModel::delCallbacks()
246 {
247     var_DelCallback( p_playlist, "item-change", ItemChanged, this );
248     var_DelCallback( p_playlist, "playlist-current", PlaylistNext, this );
249     var_DelCallback( p_playlist, "intf-change", PlaylistChanged, this );
250     var_DelCallback( p_playlist, "item-append", ItemAppended, this );
251     var_DelCallback( p_playlist, "item-deleted", ItemDeleted, this );
252 }
253
254 void PLModel::activateItem( const QModelIndex &index )
255 {
256     assert( index.isValid() );
257     PLItem *item = static_cast<PLItem*>(index.internalPointer());
258     assert( item );
259     PL_LOCK;
260     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id,
261                                                     VLC_TRUE);
262     activateItem( p_item );
263     PL_UNLOCK;
264 }
265
266 /* Must be entered with lock */
267 void PLModel::activateItem( playlist_item_t *p_item )
268 {
269     if( !p_item ) return;
270     playlist_item_t *p_parent = p_item;
271     while( p_parent )
272     {
273         if( p_parent->i_id == rootItem->i_id ) break;
274         p_parent = p_parent->p_parent;
275     }
276     if( p_parent )
277         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE,
278                           p_parent, p_item );
279 }
280
281 /****************** Base model mandatory implementations *****************/
282 QVariant PLModel::data( const QModelIndex &index, int role ) const
283 {
284     if( !index.isValid() ) return QVariant();
285     PLItem *item = static_cast<PLItem*>(index.internalPointer());
286     if( role == Qt::DisplayRole )
287     {
288         return QVariant( item->columnString( index.column() ) );
289     }
290     else if( role == Qt::DecorationRole && index.column() == 0  )
291     {
292         /* Use to segfault here because i_type wasn't always initialized */
293         if( item->i_type >= 0 )
294             return QVariant( PLModel::icons[item->i_type] );
295     }
296     else if( role == Qt::FontRole )
297     {
298         if( item->b_current == true )
299         {
300             QFont f; f.setBold( true ); return QVariant( f );
301         }
302     }
303     return QVariant();
304 }
305
306 bool PLModel::isCurrent( const QModelIndex &index )
307 {
308     assert( index.isValid() );
309     return static_cast<PLItem*>(index.internalPointer())->b_current;
310 }
311
312 int PLModel::itemId( const QModelIndex &index ) const
313 {
314     assert( index.isValid() );
315     return static_cast<PLItem*>(index.internalPointer())->i_id;
316 }
317
318 QVariant PLModel::headerData( int section, Qt::Orientation orientation,
319                               int role ) const
320 {
321     if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
322             return QVariant( rootItem->columnString( section ) );
323     return QVariant();
324 }
325
326 QModelIndex PLModel::index( int row, int column, const QModelIndex &parent )
327                   const
328 {
329     PLItem *parentItem;
330     if( !parent.isValid() )
331         parentItem = rootItem;
332     else
333         parentItem = static_cast<PLItem*>(parent.internalPointer());
334
335     PLItem *childItem = parentItem->child( row );
336     if( childItem )
337         return createIndex( row, column, childItem );
338     else
339         return QModelIndex();
340 }
341
342 /* Return the index of a given item */
343 QModelIndex PLModel::index( PLItem *item, int column ) const
344 {
345     if( !item ) return QModelIndex();
346     const PLItem *parent = item->parent();
347     if( parent )
348         return createIndex( parent->children.lastIndexOf( item ),
349                             column, item );
350     return QModelIndex();
351 }
352
353 QModelIndex PLModel::parent( const QModelIndex &index ) const
354 {
355     if( !index.isValid() ) return QModelIndex();
356
357     PLItem *childItem = static_cast<PLItem*>(index.internalPointer());
358     if( !childItem )
359     {
360         msg_Err( p_playlist, "NULL CHILD" );
361         return QModelIndex();
362     }
363
364     PLItem *parentItem = childItem->parent();
365     if( !parentItem || parentItem == rootItem ) return QModelIndex();
366     if( !parentItem->parentItem )
367     {
368         msg_Err( p_playlist, "No parent parent, trying row 0 " );
369         msg_Err( p_playlist, "----- PLEASE REPORT THIS ------" );
370         return createIndex( 0, 0, parentItem );
371     }
372     QModelIndex ind = createIndex(parentItem->row(), 0, parentItem);
373     return ind;
374 }
375
376 int PLModel::columnCount( const QModelIndex &i) const
377 {
378     return rootItem->item_col_strings.count();
379 }
380
381 int PLModel::childrenCount( const QModelIndex &parent ) const
382 {
383     return rowCount( parent );
384 }
385
386 int PLModel::rowCount( const QModelIndex &parent ) const
387 {
388     PLItem *parentItem;
389
390     if( !parent.isValid() )
391         parentItem = rootItem;
392     else
393         parentItem = static_cast<PLItem*>(parent.internalPointer());
394
395     return parentItem->childCount();
396 }
397
398 /************************* General playlist status ***********************/
399
400 bool PLModel::hasRandom()
401 {
402     if( var_GetBool( p_playlist, "random" ) ) return true;
403     return false;
404 }
405 bool PLModel::hasRepeat()
406 {
407     if( var_GetBool( p_playlist, "repeat" ) ) return true;
408     return false;
409 }
410 bool PLModel::hasLoop()
411 {
412     if( var_GetBool( p_playlist, "loop" ) ) return true;
413     return false;
414 }
415 void PLModel::setLoop( bool on )
416 {
417     var_SetBool( p_playlist, "loop", on ? VLC_TRUE:VLC_FALSE );
418     config_PutInt( p_playlist, "loop", on ? 1: 0 );
419 }
420 void PLModel::setRepeat( bool on )
421 {
422     var_SetBool( p_playlist, "repeat", on ? VLC_TRUE:VLC_FALSE );
423     config_PutInt( p_playlist, "repeat", on ? 1: 0 );
424 }
425 void PLModel::setRandom( bool on )
426 {
427     var_SetBool( p_playlist, "random", on ? VLC_TRUE:VLC_FALSE );
428     config_PutInt( p_playlist, "random", on ? 1: 0 );
429 }
430
431 /************************* Lookups *****************************/
432
433 PLItem *PLModel::FindById( PLItem *root, int i_id )
434 {
435     return FindInner( root, i_id, false );
436 }
437
438 PLItem *PLModel::FindByInput( PLItem *root, int i_id )
439 {
440     return FindInner( root, i_id, true );
441 }
442
443 #define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
444 #define ICACHE( i, p ) { i_cached_input_id = i; p_cached_item_bi = p; }
445
446 PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
447 {
448     if( ( !b_input && i_cached_id == i_id) ||
449         ( b_input && i_cached_input_id ==i_id ) )
450     {
451         return b_input ? p_cached_item_bi : p_cached_item;
452     }
453
454     if( !b_input && root->i_id == i_id )
455     {
456         CACHE( i_id, root );
457         return root;
458     }
459     else if( b_input && root->i_input_id == i_id )
460     {
461         ICACHE( i_id, root );
462         return root;
463     }
464
465     QList<PLItem *>::iterator it = root->children.begin();
466     while ( it != root->children.end() )
467     {
468         if( !b_input && (*it)->i_id == i_id )
469         {
470             CACHE( i_id, (*it) );
471             return p_cached_item;
472         }
473         else if( b_input && (*it)->i_input_id == i_id )
474         {
475             ICACHE( i_id, (*it) );
476             return p_cached_item_bi;
477         }
478         if( (*it)->children.size() )
479         {
480             PLItem *childFound = FindInner( (*it), i_id, b_input );
481             if( childFound )
482             {
483                 if( b_input )
484                     ICACHE( i_id, childFound )
485                 else
486                     CACHE( i_id, childFound )
487                 return childFound;
488             }
489         }
490         it++;
491     }
492     return NULL;
493 }
494 #undef CACHE
495 #undef ICACHE
496
497
498 /************************* Updates handling *****************************/
499 void PLModel::customEvent( QEvent *event )
500 {
501     int type = event->type();
502     if( type != ItemUpdate_Type && type != ItemAppend_Type &&
503         type != ItemDelete_Type && type != PLUpdate_Type )
504         return;
505
506     PLEvent *ple = static_cast<PLEvent *>(event);
507
508     if( type == ItemUpdate_Type )
509         ProcessInputItemUpdate( ple->i_id );
510     else if( type == ItemAppend_Type )
511         ProcessItemAppend( ple->p_add );
512     else if( type == ItemDelete_Type )
513         ProcessItemRemoval( ple->i_id );
514     else
515         rebuild();
516 }
517
518 /**** Events processing ****/
519 void PLModel::ProcessInputItemUpdate( int i_input_id )
520 {
521     if( i_input_id <= 0 ) return;
522     PLItem *item = FindByInput( rootItem, i_input_id );
523     if( item )
524         UpdateTreeItem( item, true );
525 }
526
527 void PLModel::ProcessItemRemoval( int i_id )
528 {
529     if( i_id <= 0 ) return;
530     if( i_id == i_cached_id ) i_cached_id = -1;
531     i_cached_input_id = -1;
532
533     removeItem( i_id );
534 }
535
536 void PLModel::ProcessItemAppend( playlist_add_t *p_add )
537 {
538     playlist_item_t *p_item = NULL;
539     PLItem *newItem = NULL;
540     i_items_to_append--;
541     if( b_need_update ) return;
542
543     PLItem *nodeItem = FindById( rootItem, p_add->i_node );
544     PL_LOCK;
545     if( !nodeItem ) goto end;
546
547     p_item = playlist_ItemGetById( p_playlist, p_add->i_item, VLC_TRUE );
548     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
549     if( i_depth == DEPTH_SEL && p_item->p_parent &&
550                         p_item->p_parent->i_id != rootItem->i_id )
551         goto end;
552
553     newItem = new PLItem( p_item, nodeItem, this );
554     nodeItem->appendChild( newItem );
555     UpdateTreeItem( p_item, newItem, true );
556 end:
557     PL_UNLOCK;
558     return;
559 }
560
561
562 void PLModel::rebuild()
563 {
564     rebuild( NULL );
565 }
566
567 void PLModel::rebuild( playlist_item_t *p_root )
568 {
569     /* Remove callbacks before locking to avoid deadlocks */
570     delCallbacks();
571     /* Invalidate cache */
572     i_cached_id = i_cached_input_id = -1;
573
574     PL_LOCK;
575     /* Clear the tree */
576     if( rootItem )
577     {
578         if( rootItem->children.size() )
579         {
580             beginRemoveRows( index( rootItem, 0 ), 0,
581                     rootItem->children.size() -1 );
582             qDeleteAll( rootItem->children );
583             rootItem->children.clear();
584             endRemoveRows();
585         }
586     }
587     if( p_root )
588     {
589         //if( rootItem ) delete rootItem;
590         rootItem = new PLItem( p_root, NULL, this );
591     }
592     assert( rootItem );
593     /* Recreate from root */
594     UpdateNodeChildren( rootItem );
595     if( p_playlist->status.p_item )
596     {
597         PLItem *currentItem = FindByInput( rootItem,
598                                      p_playlist->status.p_item->p_input->i_id );
599         if( currentItem )
600         {
601             UpdateTreeItem( p_playlist->status.p_item, currentItem,
602                             true, false );
603         }
604     }
605     PL_UNLOCK;
606
607     /* And signal the view */
608     emit layoutChanged();
609     addCallbacks();
610 }
611
612 /* This function must be entered WITH the playlist lock */
613 void PLModel::UpdateNodeChildren( PLItem *root )
614 {
615     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id,
616                                                     VLC_TRUE );
617     UpdateNodeChildren( p_node, root );
618 }
619
620 /* This function must be entered WITH the playlist lock */
621 void PLModel::UpdateNodeChildren( playlist_item_t *p_node, PLItem *root )
622 {
623     for( int i = 0; i < p_node->i_children ; i++ )
624     {
625         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
626         PLItem *newItem =  new PLItem( p_node->pp_children[i], root, this );
627         root->appendChild( newItem, false );
628         UpdateTreeItem( newItem, false, true );
629         if( i_depth == DEPTH_PL && p_node->pp_children[i]->i_children != -1 )
630             UpdateNodeChildren( p_node->pp_children[i], newItem );
631     }
632 }
633
634 /* This function must be entered WITH the playlist lock */
635 void PLModel::UpdateTreeItem( PLItem *item, bool signal, bool force )
636 {
637     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id,
638                                                     VLC_TRUE );
639     UpdateTreeItem( p_item, item, signal, force );
640 }
641
642 /* This function must be entered WITH the playlist lock */
643 void PLModel::UpdateTreeItem( playlist_item_t *p_item, PLItem *item,
644                               bool signal, bool force )
645 {
646     if ( !p_item )
647         return;
648     if( !force && i_depth == DEPTH_SEL && p_item->p_parent &&
649                                  p_item->p_parent->i_id != rootItem->i_id )
650         return;
651     item->update( p_item, p_item == p_playlist->status.p_item );
652     if( signal )
653         emit dataChanged( index( item, 0 ) , index( item, 1 ) );
654 }
655
656 /************************* Actions ******************************/
657
658 /**
659  * Deletion, here we have to do a ugly slow hack as we retrieve the full
660  * list of indexes to delete at once: when we delete a node and all of
661  * its children, we need to update the list.
662  * Todo: investigate whethere we can use ranges to be sure to delete all items?
663  */
664 void PLModel::doDelete( QModelIndexList selected )
665 {
666     for( int i = selected.size() -1 ; i >= 0; i-- )
667     {
668         QModelIndex index = selected[i];
669         if( index.column() != 0 ) continue;
670         PLItem *item = static_cast<PLItem*>(index.internalPointer());
671         if( item )
672         {
673             if( item->children.size() )
674                 recurseDelete( item->children, &selected );
675             doDeleteItem( item, &selected );
676         }
677     }
678 }
679
680 void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
681 {
682     for( int i = children.size() - 1; i >= 0 ; i-- )
683     {
684         PLItem *item = children[i];
685         if( item->children.size() )
686             recurseDelete( item->children, fullList );
687         doDeleteItem( item, fullList );
688     }
689 }
690
691 void PLModel::doDeleteItem( PLItem *item, QModelIndexList *fullList )
692 {
693     QModelIndex deleteIndex = index( item, 0 );
694     fullList->removeAll( deleteIndex );
695
696     PL_LOCK;
697     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id,
698                                                     VLC_TRUE );
699     if( !p_item )
700     {
701         PL_UNLOCK; return;
702     }
703     if( p_item->i_children == -1 )
704         playlist_DeleteFromInput( p_playlist, item->i_input_id, VLC_TRUE );
705     else
706         playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
707     /* And finally, remove it from the tree */
708     item->remove( item );
709     PL_UNLOCK;
710 }
711
712 /******* Volume III: Sorting and searching ********/
713 void PLModel::sort( int column, Qt::SortOrder order )
714 {
715     PL_LOCK;
716     {
717         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
718                                                         rootItem->i_id,
719                                                         VLC_TRUE );
720         int i_mode;
721         switch( column )
722         {
723         case 0: i_mode = SORT_TITLE_NODES_FIRST;break;
724         case 1: i_mode = SORT_ARTIST;break;
725         case 2: i_mode = SORT_DURATION; break;
726         default: i_mode = SORT_TITLE_NODES_FIRST; break;
727         }
728         if( p_root )
729             playlist_RecursiveNodeSort( p_playlist, p_root, i_mode,
730                                         order == Qt::AscendingOrder ?
731                                             ORDER_NORMAL : ORDER_REVERSE );
732     }
733     PL_UNLOCK;
734     rebuild();
735 }
736
737 void PLModel::search( QString search_text )
738 {
739     /** \todo Fire the search with a small delay ? */
740     PL_LOCK;
741     {
742         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
743                                                         rootItem->i_id,
744                                                         VLC_TRUE );
745         assert( p_root );
746         char *psz_name = search_text.toUtf8().data();
747         playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
748     }
749     PL_UNLOCK;
750     rebuild();
751 }
752
753 /*********** Popup *********/
754 void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
755 {
756     assert( index.isValid() );
757     PL_LOCK;
758     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
759                                                     itemId( index ), VLC_TRUE );
760     if( p_item )
761     {
762         i_popup_item = p_item->i_id;
763         i_popup_parent = p_item->p_parent ? p_item->p_parent->i_id : -1;
764         PL_UNLOCK;
765         current_selection = list;
766         QMenu *menu = new QMenu;
767         menu->addAction( qfu(I_POP_PLAY), this, SLOT( popupPlay() ) );
768         menu->addAction( qfu(I_POP_DEL), this, SLOT( popupDel() ) );
769         menu->addSeparator();
770         menu->addAction( qfu(I_POP_STREAM), this, SLOT( popupStream() ) );
771         menu->addAction( qfu(I_POP_SAVE), this, SLOT( popupSave() ) );
772         menu->addSeparator();
773         menu->addAction( qfu(I_POP_INFO), this, SLOT( popupInfo() ) );
774         if( p_item->i_children > -1 )
775         {
776             menu->addSeparator();
777             menu->addAction( qfu(I_POP_SORT), this, SLOT( popupSort() ) );
778             menu->addAction( qfu(I_POP_ADD), this, SLOT( popupAdd() ) );
779         }
780 #ifdef WIN32
781         menu->addSeparator();
782         menu->addAction( qfu( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
783 #endif
784         menu->popup( point );
785     }
786     else
787         PL_UNLOCK;
788 }
789
790
791 void PLModel::viewchanged( int meta )
792 {
793    if( rootItem )
794    {
795        int index=0;
796        switch( meta )
797        {
798        case VLC_META_ENGINE_TITLE:
799            index=0; break;
800        case VLC_META_ENGINE_DURATION:
801            index=1; break;
802        case VLC_META_ENGINE_ARTIST:
803            index=2; break;
804        case VLC_META_ENGINE_GENRE:
805            index=3; break;
806        case VLC_META_ENGINE_COPYRIGHT:
807            index=4; break;
808        case VLC_META_ENGINE_COLLECTION:
809            index=5; break;
810        case VLC_META_ENGINE_SEQ_NUM:
811            index=6; break;
812        case VLC_META_ENGINE_DESCRIPTION:
813            index=7; break;
814        default:
815            break;
816        }
817        /* UNUSED        emit layoutAboutToBeChanged(); */
818        index = __MIN( index , rootItem->item_col_strings.count() );
819        QModelIndex parent = createIndex( 0, 0, rootItem );
820
821        if( rootItem->i_showflags & meta )
822            /* Removing columns */
823        {
824            beginRemoveColumns( parent, index, index+1 );
825            rootItem->i_showflags &= ~( meta );
826            rootItem->updateColumnHeaders();
827            endRemoveColumns();
828        }
829        else
830        {
831            /* Adding columns */
832            beginInsertColumns( createIndex( 0, 0, rootItem), index, index+1 );
833            rootItem->i_showflags |= meta;
834            rootItem->updateColumnHeaders();
835            endInsertColumns();
836        }
837        rebuild();
838        config_PutInt( p_intf, "qt-pl-showflags", rootItem->i_showflags );
839        config_SaveConfigFile( p_intf, NULL );
840    }
841 }
842
843 void PLModel::popupDel()
844 {
845     doDelete( current_selection );
846 }
847 void PLModel::popupPlay()
848 {
849     PL_LOCK;
850     {
851         playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
852                                                         i_popup_item,VLC_TRUE );
853         activateItem( p_item );
854     }
855     PL_UNLOCK;
856 }
857
858 void PLModel::popupInfo()
859 {
860     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
861                                                     i_popup_item,
862                                                     VLC_TRUE );
863     if( p_item )
864     {
865         MediaInfoDialog *mid = new MediaInfoDialog( p_intf, p_item->p_input );
866         mid->show();
867     }
868 }
869
870 void PLModel::popupStream()
871 {
872      msg_Err( p_playlist, "Stream not implemented" );
873 }
874
875 void PLModel::popupSave()
876 {
877      msg_Err( p_playlist, "Save not implemented" );
878 }
879
880 #ifdef WIN32
881 #include <shellapi.h>
882 void PLModel::popupExplore()
883 {
884     ShellExecuteW( NULL, L"explore", L"C:\\", NULL, NULL, SW_SHOWNORMAL );
885 }
886 #endif
887
888 /**********************************************************************
889  * Playlist callbacks
890  **********************************************************************/
891 static int PlaylistChanged( vlc_object_t *p_this, const char *psz_variable,
892                             vlc_value_t oval, vlc_value_t nval, void *param )
893 {
894     PLModel *p_model = (PLModel *) param;
895     PLEvent *event = new PLEvent( PLUpdate_Type, 0 );
896     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
897     return VLC_SUCCESS;
898 }
899
900 static int PlaylistNext( vlc_object_t *p_this, const char *psz_variable,
901                          vlc_value_t oval, vlc_value_t nval, void *param )
902 {
903     PLModel *p_model = (PLModel *) param;
904     PLEvent *event = new PLEvent( ItemUpdate_Type, oval.i_int );
905     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
906     event = new PLEvent( ItemUpdate_Type, nval.i_int );
907     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
908     return VLC_SUCCESS;
909 }
910
911 static int ItemChanged( vlc_object_t *p_this, const char *psz_variable,
912                         vlc_value_t oval, vlc_value_t nval, void *param )
913 {
914     PLModel *p_model = (PLModel *) param;
915     PLEvent *event = new PLEvent( ItemUpdate_Type, nval.i_int );
916     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
917     return VLC_SUCCESS;
918 }
919
920 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
921                         vlc_value_t oval, vlc_value_t nval, void *param )
922 {
923     PLModel *p_model = (PLModel *) param;
924     PLEvent *event = new PLEvent( ItemDelete_Type, nval.i_int );
925     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
926     return VLC_SUCCESS;
927 }
928
929 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
930                          vlc_value_t oval, vlc_value_t nval, void *param )
931 {
932     PLModel *p_model = (PLModel *) param;
933     playlist_add_t *p_add = (playlist_add_t *)malloc( sizeof( playlist_add_t));
934     memcpy( p_add, nval.p_address, sizeof( playlist_add_t ) );
935
936     if( ++p_model->i_items_to_append >= 50 )
937     {
938 //        p_model->b_need_update = VLC_TRUE;
939 //        return VLC_SUCCESS;
940     }
941     PLEvent *event = new PLEvent(  p_add );
942     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
943     return VLC_SUCCESS;
944 }
945