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