]> git.sesse.net Git - vlc/blob - modules/gui/qt4/playlist_model.cpp
qt4 - Playlist: move the column selection to a submenu.
[vlc] / modules / gui / qt4 / 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 #define PLI_NAME( p ) p ? p->p_input->psz_name : "null"
24
25 #include <assert.h>
26 #include <QIcon>
27 #include <QFont>
28 #include <QMenu>
29 #include <QApplication>
30
31 #include "qt4.hpp"
32 #include "playlist_model.hpp"
33 #include "dialogs/mediainfo.hpp"
34 #include <vlc_intf_strings.h>
35
36 #include "pixmaps/type_unknown.xpm"
37 #include "pixmaps/type_afile.xpm"
38 #include "pixmaps/type_vfile.xpm"
39 #include "pixmaps/type_net.xpm"
40 #include "pixmaps/type_card.xpm"
41 #include "pixmaps/type_disc.xpm"
42 #include "pixmaps/type_cdda.xpm"
43 #include "pixmaps/type_directory.xpm"
44 #include "pixmaps/type_playlist.xpm"
45 #include "pixmaps/type_node.xpm"
46
47 QIcon PLModel::icons[ITEM_TYPE_NUMBER];
48
49 static int PlaylistChanged( vlc_object_t *, const char *,
50                             vlc_value_t, vlc_value_t, void * );
51 static int PlaylistNext( vlc_object_t *, const char *,
52                          vlc_value_t, vlc_value_t, void * );
53 static int ItemChanged( vlc_object_t *, const char *,
54                         vlc_value_t, vlc_value_t, void * );
55 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
56                          vlc_value_t oval, vlc_value_t nval, void *param );
57 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
58                         vlc_value_t oval, vlc_value_t nval, void *param );
59
60 /*************************************************************************
61  * Playlist item implementation
62  *************************************************************************/
63
64 /**
65  * Column strings
66  *      Title
67  *      Artist
68  *      Duration
69  */
70
71 void PLItem::init( int _i_id, int _i_input_id, PLItem *parent, PLModel *m)
72 {
73     parentItem = parent;
74     i_id = _i_id; i_input_id = _i_input_id;
75     model = m;
76
77
78     if( parentItem == NULL )
79     {
80         i_showflags = config_GetInt( model->p_intf , "qt-pl-showflags" );
81         updateview();
82     }
83 }
84
85 void PLItem::updateview( void )
86 {
87     strings.clear();
88
89     for( int i_index=1; i_index <= VLC_META_ENGINE_MB_TRM_ID; i_index = i_index*2 )
90     {
91         if( i_showflags & i_index )
92         {
93             switch( i_index )
94             {
95                 case VLC_META_ENGINE_ARTIST:
96                     strings.append( qtr( VLC_META_ARTIST ) );
97                     break;
98                 case VLC_META_ENGINE_TITLE:
99                     strings.append( qtr( VLC_META_TITLE ) );
100                     break;
101                 case VLC_META_ENGINE_DESCRIPTION:
102                     strings.append( qtr( VLC_META_DESCRIPTION ) );
103                     break;
104                 case VLC_META_ENGINE_DURATION:
105                     strings.append( qtr( "Duration" ) );
106                     break;
107                 case VLC_META_ENGINE_GENRE:
108                     strings.append( qtr( VLC_META_GENRE ) );
109                     break;
110                 case VLC_META_ENGINE_COLLECTION:
111                     strings.append( qtr( VLC_META_COLLECTION ) );
112                     break;
113                 case VLC_META_ENGINE_SEQ_NUM:
114                     strings.append( qtr( VLC_META_SEQ_NUM ) );
115                     break;
116                 case VLC_META_ENGINE_RATING:
117                     strings.append( qtr( VLC_META_RATING ) );
118                     break;
119                 default:
120                     break;
121             }
122         }
123     }
124 }
125
126
127 PLItem::PLItem( int _i_id, int _i_input_id, PLItem *parent, PLModel *m)
128 {
129     init( _i_id, _i_input_id, parent, m );
130 }
131
132 PLItem::PLItem( playlist_item_t * p_item, PLItem *parent, PLModel *m )
133 {
134     init( p_item->i_id, p_item->p_input->i_id, parent, m );
135 }
136
137 PLItem::~PLItem()
138 {
139     qDeleteAll(children);
140     children.clear();
141 }
142
143 void PLItem::insertChild( PLItem *item, int i_pos, bool signal )
144 {
145     assert( model );
146     if( signal )
147         model->beginInsertRows( model->index( this , 0 ), i_pos, i_pos );
148     children.insert( i_pos, item );
149     if( signal )
150         model->endInsertRows();
151 }
152
153 void PLItem::remove( PLItem *removed )
154 {
155     assert( model && parentItem );
156     int i_index = parentItem->children.indexOf( removed );
157     model->beginRemoveRows( model->index( parentItem, 0 ), i_index, i_index );
158     parentItem->children.removeAt( i_index );
159     model->endRemoveRows();
160 }
161
162 int PLItem::row() const
163 {
164     if( parentItem )
165         return parentItem->children.indexOf(const_cast<PLItem*>(this));
166     return 0;
167 }
168
169 void PLItem::update( playlist_item_t *p_item, bool iscurrent )
170 {
171     char psz_duration[MSTRTIME_MAX_SIZE];
172     assert( p_item->p_input->i_id == i_input_id );
173     strings.clear();
174
175     type = p_item->p_input->i_type;
176     current = iscurrent;
177
178     if( current && p_item->p_input->p_meta &&
179         p_item->p_input->p_meta->psz_arturl &&
180         !strncmp( p_item->p_input->p_meta->psz_arturl, "file://", 7 ) )
181         model->sendArt( qfu( p_item->p_input->p_meta->psz_arturl ) );
182     else if( current )
183         model->removeArt();
184
185     if( model->i_depth == 1 )  //left window for playlist etc.
186     {
187         strings.append( p_item->p_input->psz_name );
188         return;
189     }
190
191
192 #define ADD_META( meta ) {             \
193    if( p_item->p_input->p_meta )       \
194       strings.append( qfu( meta ) );   \
195    else                                \
196       strings.append( qtr( "" ) ); }
197
198     for( int i_index=1; i_index <= VLC_META_ENGINE_MB_TRM_ID; i_index = i_index * 2 )
199     {
200         if( parentItem->i_showflags & i_index )
201         {
202             switch( i_index )
203             {
204                 case VLC_META_ENGINE_ARTIST:
205                     ADD_META( p_item->p_input->p_meta->psz_artist );
206                     break;
207                 case VLC_META_ENGINE_TITLE:
208                     if( p_item->p_input->p_meta && p_item->p_input->p_meta->psz_title )
209                     {
210                         ADD_META( p_item->p_input->p_meta->psz_title );
211                     } else {
212                         ADD_META( p_item->p_input->psz_name );
213                     }
214                     break;
215                 case VLC_META_ENGINE_DESCRIPTION:
216                     ADD_META( p_item->p_input->p_meta->psz_description );
217                     break;
218                 case VLC_META_ENGINE_DURATION:
219                     secstotimestr( psz_duration, p_item->p_input->i_duration / 1000000 );
220                     strings.append( QString( psz_duration ) );
221                     break;
222                 case VLC_META_ENGINE_GENRE:
223                     ADD_META( p_item->p_input->p_meta->psz_genre );
224                     break;
225                 case VLC_META_ENGINE_COLLECTION:
226                     ADD_META( p_item->p_input->p_meta->psz_album );
227                     break;
228                 case VLC_META_ENGINE_SEQ_NUM:
229                     ADD_META( p_item->p_input->p_meta->psz_tracknum);
230                     break;
231                 case VLC_META_ENGINE_RATING:
232                     ADD_META( p_item->p_input->p_meta->psz_rating );
233                 default:
234                     break;
235             }
236         }
237
238     }
239 #undef ADD_META
240
241 }
242
243 /*************************************************************************
244  * Playlist model implementation
245  *************************************************************************/
246
247 PLModel::PLModel( playlist_t *_p_playlist, intf_thread_t *_p_intf,
248                   playlist_item_t * p_root, int _i_depth, QObject *parent)
249                                     : QAbstractItemModel(parent)
250 {
251     i_depth = _i_depth;
252     assert( i_depth == 1 || i_depth == -1 );
253     p_intf = _p_intf;
254     p_playlist= _p_playlist;
255     i_items_to_append = 0;
256     b_need_update     = false;
257     i_cached_id       = -1;
258     i_cached_input_id = -1;
259     i_popup_item = i_popup_parent = -1;
260
261 #define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( QPixmap( type_##x##_xpm ) );
262     ADD_ICON( UNKNOWN , unknown );
263     ADD_ICON( AFILE,afile );
264     ADD_ICON( VFILE, vfile );
265     ADD_ICON( DIRECTORY, directory );
266     ADD_ICON( DISC, disc );
267     ADD_ICON( CDDA, cdda );
268     ADD_ICON( CARD, card );
269     ADD_ICON( NET, net );
270     ADD_ICON( PLAYLIST, playlist );
271     ADD_ICON( NODE, node );
272
273     rootItem = NULL;
274     addCallbacks();
275     rebuild( p_root );
276 }
277
278
279 PLModel::~PLModel()
280 {
281     delCallbacks();
282     delete rootItem;
283 }
284
285 Qt::DropActions PLModel::supportedDropActions() const
286 {
287     return Qt::CopyAction;
288 }
289
290 Qt::ItemFlags PLModel::flags(const QModelIndex &index) const
291 {
292     Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
293     if( index.isValid() )
294         return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
295     else
296         return Qt::ItemIsDropEnabled | defaultFlags;
297 }
298
299 QStringList PLModel::mimeTypes() const
300 {
301     QStringList types;
302     types << "vlc/playlist-item-id";
303     return types;
304 }
305
306 QMimeData *PLModel::mimeData(const QModelIndexList &indexes) const
307 {
308     QMimeData *mimeData = new QMimeData();
309     QByteArray encodedData;
310     QDataStream stream(&encodedData, QIODevice::WriteOnly);
311
312     foreach (QModelIndex index, indexes) {
313         if (index.isValid() && index.column() == 0 )
314             stream << itemId(index);
315     }
316     mimeData->setData("vlc/playlist-item-id", encodedData);
317     return mimeData;
318 }
319
320 bool PLModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
321                            int row, int column, const QModelIndex &target)
322 {
323     if ( data->hasFormat("vlc/playlist-item-id") )
324     {
325         if (action == Qt::IgnoreAction)
326             return true;
327
328         PLItem *targetItem;
329         if( target.isValid() )
330             targetItem = static_cast<PLItem*>( target.internalPointer() );
331         else
332             targetItem = rootItem;
333
334         QByteArray encodedData = data->data("vlc/playlist-item-id");
335         QDataStream stream(&encodedData, QIODevice::ReadOnly);
336
337         PLItem *newParentItem;
338         while (!stream.atEnd())
339         {
340             int i;
341             int srcId;
342             stream >> srcId;
343
344             PL_LOCK;
345             playlist_item_t *p_target =
346                         playlist_ItemGetById( p_playlist, targetItem->i_id,
347                                               VLC_TRUE );
348             playlist_item_t *p_src = playlist_ItemGetById( p_playlist, srcId,
349                                                            VLC_TRUE );
350
351             if( !p_target || !p_src )
352             {
353                 PL_UNLOCK;
354                 return false;
355             }
356             if( p_target->i_children == -1 ) /* A leaf */
357             {
358                 PLItem *parentItem = targetItem->parent();
359                 assert( parentItem );
360                 playlist_item_t *p_parent =
361                          playlist_ItemGetById( p_playlist, parentItem->i_id,
362                                                VLC_TRUE );
363                 if( !p_parent )
364                 {
365                     PL_UNLOCK;
366                     return false;
367                 }
368                 for( i = 0 ; i< p_parent->i_children ; i++ )
369                     if( p_parent->pp_children[i] == p_target ) break;
370                 playlist_TreeMove( p_playlist, p_src, p_parent, i );
371                 newParentItem = parentItem;
372             }
373             else
374             {
375                 /* \todo: if we drop on a top-level node, use copy instead ? */
376                 playlist_TreeMove( p_playlist, p_src, p_target, 0 );
377                 i = 0;
378                 newParentItem = targetItem;
379             }
380             /* Remove from source */
381             PLItem *srcItem = FindById( rootItem, p_src->i_id );
382             // We dropped on the source selector. Ask the dialog to forward
383             // to the main view
384             if( !srcItem )
385             {
386                 emit shouldRemove( p_src->i_id );
387             }
388             else
389                 srcItem->remove( srcItem );
390
391             /* Display at new destination */
392             PLItem *newItem = new PLItem( p_src, newParentItem, this );
393             newParentItem->insertChild( newItem, i, true );
394             UpdateTreeItem( p_src, newItem, true );
395             if( p_src->i_children != -1 )
396                 UpdateNodeChildren( newItem );
397             PL_UNLOCK;
398         }
399     }
400     return true;
401 }
402
403 void PLModel::removeItem( int i_id )
404 {
405     PLItem *item = FindById( rootItem,i_id );
406     if( item ) item->remove( item );
407 }
408
409 void PLModel::addCallbacks()
410 {
411     /* Some global changes happened -> Rebuild all */
412     var_AddCallback( p_playlist, "intf-change", PlaylistChanged, this );
413     /* We went to the next item */
414     var_AddCallback( p_playlist, "playlist-current", PlaylistNext, this );
415     /* One item has been updated */
416     var_AddCallback( p_playlist, "item-change", ItemChanged, this );
417     var_AddCallback( p_playlist, "item-append", ItemAppended, this );
418     var_AddCallback( p_playlist, "item-deleted", ItemDeleted, this );
419 }
420
421 void PLModel::delCallbacks()
422 {
423     var_DelCallback( p_playlist, "item-change", ItemChanged, this );
424     var_DelCallback( p_playlist, "playlist-current", PlaylistNext, this );
425     var_DelCallback( p_playlist, "intf-change", PlaylistChanged, this );
426     var_DelCallback( p_playlist, "item-append", ItemAppended, this );
427     var_DelCallback( p_playlist, "item-deleted", ItemDeleted, this );
428 }
429
430 void PLModel::activateItem( const QModelIndex &index )
431 {
432     assert( index.isValid() );
433     PLItem *item = static_cast<PLItem*>(index.internalPointer());
434     assert( item );
435     PL_LOCK;
436     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id,
437                                                     VLC_TRUE);
438     activateItem( p_item );
439     PL_UNLOCK;
440 }
441 /* Must be entered with lock */
442 void PLModel::activateItem( playlist_item_t *p_item )
443 {
444     if( !p_item ) return;
445     playlist_item_t *p_parent = p_item;
446     while( p_parent )
447     {
448         if( p_parent->i_id == rootItem->i_id ) break;
449         p_parent = p_parent->p_parent;
450     }
451     if( p_parent )
452         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE, p_parent, p_item );
453 }
454
455 /****************** Base model mandatory implementations *****************/
456 QVariant PLModel::data(const QModelIndex &index, int role) const
457 {
458     if(!index.isValid() ) return QVariant();
459     PLItem *item = static_cast<PLItem*>(index.internalPointer());
460     if( role == Qt::DisplayRole )
461     {
462         return QVariant( item->columnString( index.column() ) );
463     }
464     else if( role == Qt::DecorationRole && index.column() == 0  )
465     {
466         if( item->type >= 0 )
467             return QVariant( PLModel::icons[item->type] );
468     }
469     else if( role == Qt::FontRole )
470     {
471         if( item->current == true )
472         {
473             QFont f; f.setBold( true ); return QVariant( f );
474         }
475     }
476     return QVariant();
477 }
478
479 bool PLModel::isCurrent( const QModelIndex &index )
480 {
481     assert( index.isValid() );
482     return static_cast<PLItem*>(index.internalPointer())->current;
483 }
484
485 int PLModel::itemId( const QModelIndex &index ) const
486 {
487     assert( index.isValid() );
488     return static_cast<PLItem*>(index.internalPointer())->i_id;
489 }
490
491 QVariant PLModel::headerData( int section, Qt::Orientation orientation,
492                               int role) const
493 {
494     if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
495             return QVariant( rootItem->columnString( section ) );
496     return QVariant();
497 }
498
499 QModelIndex PLModel::index(int row, int column, const QModelIndex &parent)
500                   const
501 {
502     PLItem *parentItem;
503     if (!parent.isValid())
504         parentItem = rootItem;
505     else
506         parentItem = static_cast<PLItem*>(parent.internalPointer());
507
508     PLItem *childItem = parentItem->child(row);
509     if (childItem)
510         return createIndex(row, column, childItem);
511     else
512         return QModelIndex();
513 }
514
515 /* Return the index of a given item */
516 QModelIndex PLModel::index( PLItem *item, int column ) const
517 {
518     if( !item ) return QModelIndex();
519     const PLItem *parent = item->parent();
520     if( parent )
521         return createIndex( parent->children.lastIndexOf( item ),
522                             column, item );
523     return QModelIndex();
524 }
525
526 QModelIndex PLModel::parent(const QModelIndex &index) const
527 {
528     if( !index.isValid() ) return QModelIndex();
529
530     PLItem *childItem = static_cast<PLItem*>(index.internalPointer());
531     if( !childItem ) { msg_Err( p_playlist, "NULL CHILD \n" ); return QModelIndex(); }
532     PLItem *parentItem = childItem->parent();
533     if( !parentItem || parentItem == rootItem ) return QModelIndex();
534     if( ! parentItem->parentItem )
535     {
536         msg_Err( p_playlist, "No parent parent, trying row 0 " );
537         msg_Err( p_playlist, "----- PLEASE REPORT THIS ------" );
538         return createIndex( 0, 0, parentItem );
539     }
540     QModelIndex ind = createIndex(parentItem->row(), 0, parentItem);
541     return ind;
542 }
543
544 int PLModel::columnCount( const QModelIndex &i) const
545 {
546     if( i_depth == 1 ) return 1;
547     return rootItem->strings.count();
548 }
549
550 int PLModel::childrenCount(const QModelIndex &parent) const
551 {
552     return rowCount( parent );
553 }
554
555 int PLModel::rowCount(const QModelIndex &parent) const
556 {
557     PLItem *parentItem;
558
559     if (!parent.isValid())
560         parentItem = rootItem;
561     else
562         parentItem = static_cast<PLItem*>(parent.internalPointer());
563
564     return parentItem->childCount();
565 }
566
567 /************************* General playlist status ***********************/
568
569 bool PLModel::hasRandom()
570 {
571     if( var_GetBool( p_playlist, "random" ) ) return true;
572     return false;
573 }
574 bool PLModel::hasRepeat()
575 {
576     if( var_GetBool( p_playlist, "repeat" ) ) return true;
577     return false;
578 }
579 bool PLModel::hasLoop()
580 {
581     if( var_GetBool( p_playlist, "loop" ) ) return true;
582     return false;
583 }
584 void PLModel::setLoop( bool on )
585 {
586     var_SetBool( p_playlist, "loop", on ? VLC_TRUE:VLC_FALSE );
587     config_PutInt( p_playlist, "loop", on ? 1: 0 );
588 }
589 void PLModel::setRepeat( bool on )
590 {
591     var_SetBool( p_playlist, "repeat", on ? VLC_TRUE:VLC_FALSE );
592     config_PutInt( p_playlist, "repeat", on ? 1: 0 );
593 }
594 void PLModel::setRandom( bool on )
595 {
596     var_SetBool( p_playlist, "random", on ? VLC_TRUE:VLC_FALSE );
597     config_PutInt( p_playlist, "random", on ? 1: 0 );
598 }
599
600 /************************* Lookups *****************************/
601
602 PLItem *PLModel::FindById( PLItem *root, int i_id )
603 {
604     return FindInner( root, i_id, false );
605 }
606
607 PLItem *PLModel::FindByInput( PLItem *root, int i_id )
608 {
609     return FindInner( root, i_id, true );
610 }
611
612 #define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
613 #define ICACHE( i, p ) { i_cached_input_id = i; p_cached_item_bi = p; }
614
615 PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
616 {
617     if( ( !b_input && i_cached_id == i_id) ||
618         ( b_input && i_cached_input_id ==i_id ) )
619     {
620         return b_input ? p_cached_item_bi : p_cached_item;
621     }
622
623     if( !b_input && root->i_id == i_id )
624     {
625         CACHE( i_id, root );
626         return root;
627     }
628     else if( b_input && root->i_input_id == i_id )
629     {
630         ICACHE( i_id, root );
631         return root;
632     }
633
634     QList<PLItem *>::iterator it = root->children.begin();
635     while ( it != root->children.end() )
636     {
637         if( !b_input && (*it)->i_id == i_id )
638         {
639             CACHE( i_id, (*it) );
640             return p_cached_item;
641         }
642         else if( b_input && (*it)->i_input_id == i_id )
643         {
644             ICACHE( i_id, (*it) );
645             return p_cached_item_bi;
646         }
647         if( (*it)->children.size() )
648         {
649             PLItem *childFound = FindInner( (*it), i_id, b_input );
650             if( childFound )
651             {
652                 if( b_input )
653                     ICACHE( i_id, childFound )
654                 else
655                     CACHE( i_id, childFound )
656                 return childFound;
657             }
658         }
659         it++;
660     }
661     return NULL;
662 }
663 #undef CACHE
664 #undef ICACHE
665
666
667 /************************* Updates handling *****************************/
668 void PLModel::customEvent( QEvent *event )
669 {
670     int type = event->type();
671     if( type != ItemUpdate_Type && type != ItemAppend_Type &&
672         type != ItemDelete_Type && type != PLUpdate_Type )
673         return;
674
675     PLEvent *ple = static_cast<PLEvent *>(event);
676
677     if( type == ItemUpdate_Type )
678         ProcessInputItemUpdate( ple->i_id );
679     else if( type == ItemAppend_Type )
680         ProcessItemAppend( ple->p_add );
681     else if( type == ItemDelete_Type )
682         ProcessItemRemoval( ple->i_id );
683     else
684         rebuild();
685 }
686
687 /**** Events processing ****/
688 void PLModel::ProcessInputItemUpdate( int i_input_id )
689 {
690     if( i_input_id <= 0 ) return;
691     PLItem *item = FindByInput( rootItem, i_input_id );
692     if( item )
693         UpdateTreeItem( item, true );
694 }
695
696 void PLModel::ProcessItemRemoval( int i_id )
697 {
698     if( i_id <= 0 ) return;
699     if( i_id == i_cached_id ) i_cached_id = -1;
700     i_cached_input_id = -1;
701     PLItem *item = FindById( rootItem, i_id );
702     if( item )
703         item->remove( item );
704 }
705
706 void PLModel::ProcessItemAppend( playlist_add_t *p_add )
707 {
708     playlist_item_t *p_item = NULL;
709     PLItem *newItem = NULL;
710     i_items_to_append--;
711     if( b_need_update ) return;
712
713     PLItem *nodeItem = FindById( rootItem, p_add->i_node );
714     PL_LOCK;
715     if( !nodeItem ) goto end;
716
717     p_item = playlist_ItemGetById( p_playlist, p_add->i_item, VLC_TRUE );
718     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
719     if( i_depth == 1 && p_item->p_parent &&
720                         p_item->p_parent->i_id != rootItem->i_id )
721         goto end;
722
723     newItem = new PLItem( p_item, nodeItem, this );
724     nodeItem->appendChild( newItem );
725     UpdateTreeItem( p_item, newItem, true );
726 end:
727     PL_UNLOCK;
728     return;
729 }
730
731
732 void PLModel::rebuild()
733 {
734     rebuild( NULL );
735 }
736
737 void PLModel::rebuild( playlist_item_t *p_root )
738 {
739     /* Remove callbacks before locking to avoid deadlocks */
740     delCallbacks();
741     /* Invalidate cache */
742     i_cached_id = i_cached_input_id = -1;
743
744     PL_LOCK;
745     /* Clear the tree */
746     if( rootItem )
747     {
748         beginRemoveRows( index( rootItem, 0 ), 0,
749                          rootItem->children.size() -1 );
750         qDeleteAll( rootItem->children );
751         rootItem->children.clear();
752         endRemoveRows();
753     }
754     if( p_root )
755     {
756         //if( rootItem ) delete rootItem;
757         rootItem = new PLItem( p_root, NULL, this );
758     }
759     assert( rootItem );
760     /* Recreate from root */
761     UpdateNodeChildren( rootItem );
762     if( p_playlist->status.p_item )
763     {
764         PLItem *currentItem = FindByInput( rootItem,
765                                      p_playlist->status.p_item->p_input->i_id );
766         if( currentItem )
767         {
768             UpdateTreeItem( p_playlist->status.p_item, currentItem,
769                             true, false );
770         }
771     }
772     PL_UNLOCK;
773
774     /* And signal the view */
775     emit layoutChanged();
776     addCallbacks();
777 }
778
779 /* This function must be entered WITH the playlist lock */
780 void PLModel::UpdateNodeChildren( PLItem *root )
781 {
782     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id,
783                                                     VLC_TRUE );
784     UpdateNodeChildren( p_node, root );
785 }
786
787 /* This function must be entered WITH the playlist lock */
788 void PLModel::UpdateNodeChildren( playlist_item_t *p_node, PLItem *root )
789 {
790     for( int i = 0; i < p_node->i_children ; i++ )
791     {
792         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
793         PLItem *newItem =  new PLItem( p_node->pp_children[i], root, this );
794         root->appendChild( newItem, false );
795         UpdateTreeItem( newItem, false, true );
796         if( i_depth != 1 && p_node->pp_children[i]->i_children != -1 )
797             UpdateNodeChildren( p_node->pp_children[i], newItem );
798     }
799 }
800
801 /* This function must be entered WITH the playlist lock */
802 void PLModel::UpdateTreeItem( PLItem *item, bool signal, bool force )
803 {
804     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id,
805                                                     VLC_TRUE );
806     UpdateTreeItem( p_item, item, signal, force );
807 }
808
809 /* This function must be entered WITH the playlist lock */
810 void PLModel::UpdateTreeItem( playlist_item_t *p_item, PLItem *item,
811                               bool signal, bool force )
812 {
813     if ( !p_item )
814         return;
815     if( !force && i_depth == 1 && p_item->p_parent &&
816                                  p_item->p_parent->i_id != rootItem->i_id )
817         return;
818     item->update( p_item, p_item == p_playlist->status.p_item );
819     if( signal )
820         emit dataChanged( index( item, 0 ) , index( item, 1 ) );
821 }
822
823 /************************* Actions ******************************/
824
825 void PLModel::sendArt( QString url )
826 {
827     QString arturl = url.replace( "file://",QString("" ) );
828     emit artSet( arturl );
829 }
830
831 void PLModel::removeArt()
832 {
833     emit artSet( QString() );
834 }
835
836 /**
837  * Deletion, here we have to do a ugly slow hack as we retrieve the full
838  * list of indexes to delete at once: when we delete a node and all of
839  * its children, we need to update the list.
840  * Todo: investigate whethere we can use ranges to be sure to delete all items?
841  */
842 void PLModel::doDelete( QModelIndexList selected )
843 {
844     for( int i = selected.size() -1 ; i >= 0; i-- )
845     {
846         QModelIndex index = selected[i];
847         if( index.column() != 0 ) continue;
848         PLItem *item = static_cast<PLItem*>(index.internalPointer());
849         if( item )
850         {
851             if( item->children.size() )
852                 recurseDelete( item->children, &selected );
853             doDeleteItem( item, &selected );
854         }
855     }
856 }
857
858 void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList)
859 {
860     for( int i = children.size() - 1; i >= 0 ; i-- )
861     {
862         PLItem *item = children[i];
863         if( item->children.size() )
864             recurseDelete( item->children, fullList );
865         doDeleteItem( item, fullList );
866     }
867 }
868
869 void PLModel::doDeleteItem( PLItem *item, QModelIndexList *fullList )
870 {
871     QModelIndex deleteIndex = index( item, 0 );
872     fullList->removeAll( deleteIndex );
873
874     PL_LOCK;
875     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id,
876                                                     VLC_TRUE );
877     if( !p_item )
878     {
879         PL_UNLOCK; return;
880     }
881     if( p_item->i_children == -1 )
882         playlist_DeleteFromInput( p_playlist, item->i_input_id, VLC_TRUE );
883     else
884         playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
885     /* And finally, remove it from the tree */
886     item->remove( item );
887     PL_UNLOCK;
888 }
889
890 /******* Volume III: Sorting and searching ********/
891 void PLModel::sort( int column, Qt::SortOrder order )
892 {
893     PL_LOCK;
894     {
895         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
896                                                         rootItem->i_id,
897                                                         VLC_TRUE );
898         int i_mode;
899         switch( column )
900         {
901         case 0: i_mode = SORT_TITLE_NODES_FIRST;break;
902         case 1: i_mode = SORT_ARTIST;break;
903         case 2: i_mode = SORT_DURATION; break;
904         default: i_mode = SORT_TITLE_NODES_FIRST; break;
905         }
906         if( p_root )
907             playlist_RecursiveNodeSort( p_playlist, p_root, i_mode,
908                                         order == Qt::AscendingOrder ?
909                                             ORDER_NORMAL : ORDER_REVERSE );
910     }
911     PL_UNLOCK
912     rebuild();
913 }
914
915 void PLModel::search( QString search_text )
916 {
917     /** \todo Fire the search with a small delay ? */
918     PL_LOCK;
919     {
920         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
921                                                         rootItem->i_id,
922                                                         VLC_TRUE );
923         assert( p_root );
924         char *psz_name = search_text.toUtf8().data();
925         playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
926     }
927     PL_UNLOCK;
928     rebuild();
929 }
930
931 /*********** Popup *********/
932 void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
933 {
934     assert( index.isValid() );
935     PL_LOCK;
936     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
937                                                     itemId( index ), VLC_TRUE );
938     if( p_item )
939     {
940         i_popup_item = p_item->i_id;
941         i_popup_parent = p_item->p_parent ? p_item->p_parent->i_id : -1;
942         PL_UNLOCK;
943         current_selection = list;
944         QMenu *menu = new QMenu;
945         menu->addAction( qfu(I_POP_PLAY), this, SLOT( popupPlay() ) );
946         menu->addAction( qfu(I_POP_DEL), this, SLOT( popupDel() ) );
947         menu->addSeparator();
948         menu->addAction( qfu(I_POP_STREAM), this, SLOT( popupStream() ) );
949         menu->addAction( qfu(I_POP_SAVE), this, SLOT( popupSave() ) );
950         menu->addSeparator();
951         menu->addAction( qfu(I_POP_INFO), this, SLOT( popupInfo() ) );
952         if( p_item->i_children > -1 )
953         {
954             menu->addSeparator();
955             menu->addAction( qfu(I_POP_SORT), this, SLOT( popupSort() ) );
956             menu->addAction( qfu(I_POP_ADD), this, SLOT( popupAdd() ) );
957         }
958         menu->addSeparator();
959
960         ContextUpdateMapper = new QSignalMapper(this);
961
962         QMenu *selectColMenu = new QMenu( qtr("Show columns") );
963
964 #define ADD_META_ACTION( meta ) { \
965    QAction* option = selectColMenu->addAction( qfu(VLC_META_##meta) );     \
966    option->setCheckable( true );                                           \
967    option->setChecked( rootItem->i_showflags & VLC_META_ENGINE_##meta );   \
968    ContextUpdateMapper->setMapping( option, VLC_META_ENGINE_##meta );      \
969    CONNECT( option, triggered(), ContextUpdateMapper, map() );             \
970    }
971         CONNECT(ContextUpdateMapper, mapped( int ), this, viewchanged( int ) );
972
973         ADD_META_ACTION( TITLE );
974         ADD_META_ACTION( ARTIST );
975         ADD_META_ACTION( DURATION );
976         ADD_META_ACTION( COLLECTION );
977         ADD_META_ACTION( GENRE );
978         ADD_META_ACTION( SEQ_NUM );
979         ADD_META_ACTION( RATING );
980         ADD_META_ACTION( DESCRIPTION );
981
982 #undef ADD_META_ACTION
983         menu->addMenu( selectColMenu );
984         menu->popup( point );
985     }
986     else
987         PL_UNLOCK;
988 }
989
990
991 void PLModel::viewchanged( int meta )
992 {
993    if( rootItem )
994    {
995        int index=0;
996        switch( meta )
997        {
998            case VLC_META_ENGINE_TITLE:
999                index=0;
1000                break;
1001            case VLC_META_ENGINE_DURATION:
1002                index=1;
1003                break;
1004            case VLC_META_ENGINE_ARTIST:
1005                index=2;
1006                break;
1007            case VLC_META_ENGINE_GENRE:
1008                index=3;
1009                break;
1010            case VLC_META_ENGINE_COPYRIGHT:
1011                index=4;
1012                break;
1013            case VLC_META_ENGINE_COLLECTION:
1014                index=5;
1015                break;
1016            case VLC_META_ENGINE_SEQ_NUM:
1017                index=6;
1018                break;
1019            case VLC_META_ENGINE_DESCRIPTION:
1020                index=7;
1021                break;
1022            default:
1023                break;
1024        }
1025        emit layoutAboutToBeChanged();
1026        index = __MIN( index , rootItem->strings.count() );
1027        QModelIndex parent = createIndex( 0, 0, rootItem );
1028        if( rootItem->i_showflags & meta )
1029        {
1030            beginRemoveColumns( parent , index, index+1 );
1031            rootItem->i_showflags &= ~( meta );
1032            rootItem->updateview();
1033            endRemoveColumns();
1034        }
1035        else
1036        {
1037            beginInsertColumns( createIndex( 0, 0, rootItem), index, index+1 );
1038            rootItem->i_showflags |= meta;
1039            rootItem->updateview();
1040            endInsertColumns();
1041        }
1042        rebuild();
1043        config_PutInt( p_intf, "qt-pl-showflags", rootItem->i_showflags );
1044        config_SaveConfigFile( p_intf, NULL );
1045    }
1046 }
1047
1048 void PLModel::popupDel()
1049 {
1050     doDelete( current_selection );
1051 }
1052 void PLModel::popupPlay()
1053 {
1054     PL_LOCK;
1055     {
1056         playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1057                                                         i_popup_item,VLC_TRUE );
1058         activateItem( p_item );
1059     }
1060     PL_UNLOCK;
1061 }
1062
1063 void PLModel::popupInfo()
1064 {
1065     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1066                                                     i_popup_item,VLC_TRUE );
1067     if( p_item )
1068     {
1069         MediaInfoDialog *mid = new MediaInfoDialog( p_intf );
1070         mid->setInput( p_item->p_input );
1071         mid->show();
1072     }
1073 }
1074
1075 void PLModel::popupStream()
1076 {
1077     fprintf( stderr, "Stream not implemented\n" );
1078 }
1079 void PLModel::popupSave()
1080 {
1081     fprintf( stderr, "Save not implemented\n" );
1082 }
1083
1084 /**********************************************************************
1085  * Playlist callbacks
1086  **********************************************************************/
1087 static int PlaylistChanged( vlc_object_t *p_this, const char *psz_variable,
1088                             vlc_value_t oval, vlc_value_t nval, void *param )
1089 {
1090     PLModel *p_model = (PLModel *) param;
1091     PLEvent *event = new PLEvent( PLUpdate_Type, 0 );
1092     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1093     return VLC_SUCCESS;
1094 }
1095
1096 static int PlaylistNext( vlc_object_t *p_this, const char *psz_variable,
1097                          vlc_value_t oval, vlc_value_t nval, void *param )
1098 {
1099     PLModel *p_model = (PLModel *) param;
1100     PLEvent *event = new PLEvent( ItemUpdate_Type, oval.i_int );
1101     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1102     event = new PLEvent( ItemUpdate_Type, nval.i_int );
1103     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1104     return VLC_SUCCESS;
1105 }
1106
1107 static int ItemChanged( vlc_object_t *p_this, const char *psz_variable,
1108                         vlc_value_t oval, vlc_value_t nval, void *param )
1109 {
1110     PLModel *p_model = (PLModel *) param;
1111     PLEvent *event = new PLEvent( ItemUpdate_Type, nval.i_int );
1112     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1113     return VLC_SUCCESS;
1114 }
1115
1116 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
1117                         vlc_value_t oval, vlc_value_t nval, void *param )
1118 {
1119     PLModel *p_model = (PLModel *) param;
1120     PLEvent *event = new PLEvent( ItemDelete_Type, nval.i_int );
1121     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1122     return VLC_SUCCESS;
1123 }
1124
1125 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
1126                          vlc_value_t oval, vlc_value_t nval, void *param )
1127 {
1128     PLModel *p_model = (PLModel *) param;
1129     playlist_add_t *p_add = (playlist_add_t *)malloc( sizeof( playlist_add_t));
1130     memcpy( p_add, nval.p_address, sizeof( playlist_add_t ) );
1131
1132     if( ++p_model->i_items_to_append >= 50 )
1133     {
1134 //        p_model->b_need_update = VLC_TRUE;
1135 //        return VLC_SUCCESS;
1136     }
1137     PLEvent *event = new PLEvent(  p_add );
1138     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1139     return VLC_SUCCESS;
1140 }