]> git.sesse.net Git - vlc/blob - modules/gui/qt4/playlist_model.cpp
Typo
[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
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
37 QIcon PLModel::icons[ITEM_TYPE_NUMBER];
38
39 static int PlaylistChanged( vlc_object_t *, const char *,
40                             vlc_value_t, vlc_value_t, void * );
41 static int PlaylistNext( vlc_object_t *, const char *,
42                          vlc_value_t, vlc_value_t, void * );
43 static int ItemChanged( vlc_object_t *, const char *,
44                         vlc_value_t, vlc_value_t, void * );
45 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
46                          vlc_value_t oval, vlc_value_t nval, void *param );
47 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
48                         vlc_value_t oval, vlc_value_t nval, void *param );
49
50 /*************************************************************************
51  * Playlist item implementation
52  *************************************************************************/
53
54 /**
55  * Column strings
56  *      Title
57  *      Artist
58  *      Duration
59  */
60
61 void PLItem::init( int _i_id, int _i_input_id, PLItem *parent, PLModel *m )
62 {
63     parentItem = parent;
64     i_id = _i_id; i_input_id = _i_input_id;
65     model = m;
66     i_type = -1; /* Avoid segfault */
67
68     if( parentItem == NULL )
69     {
70         i_showflags = config_GetInt( model->p_intf , "qt-pl-showflags" );
71         updateview();
72     } else {
73         i_showflags = parentItem->i_showflags;
74         //Add empty string and update() handles data appending
75         strings.append( "" );
76     }
77 }
78
79 void PLItem::updateview( void )
80 {
81     strings.clear();
82
83     if( model->i_depth == 1 )  //left window for playlist etc.
84     {
85         strings.append( "" );
86         return;
87     }
88
89     for( int i_index=1; i_index <= VLC_META_ENGINE_ART_URL; 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
174     i_type = p_item->p_input->i_type;
175     current = iscurrent;
176
177     strings.clear();
178
179     if( model->i_depth == 1 )  //left window for playlist etc.
180     {
181         strings.append( qfu( p_item->p_input->psz_name ) );
182         return;
183     }
184
185     char *psz_meta;
186 #define ADD_META( item, meta ) \
187       psz_meta = input_item_Get ## meta ( item->p_input ); \
188       strings.append( qfu( psz_meta ) ); \
189       free( psz_meta );
190
191     for( int i_index=1; i_index <= VLC_META_ENGINE_ART_URL; i_index = i_index * 2 )
192     {
193         if( parentItem->i_showflags & i_index )
194         {
195             switch( i_index )
196             {
197             case VLC_META_ENGINE_ARTIST:
198                 ADD_META( p_item, Artist );
199                 break;
200             case VLC_META_ENGINE_TITLE:
201                 char *psz_title, *psz_name;
202                 psz_title = input_item_GetTitle( p_item->p_input );
203                 psz_name = input_item_GetName( p_item->p_input );
204                 if( psz_title )
205                 {
206                     ADD_META( p_item, Title );
207                 }
208                 else if( psz_name )
209                 {
210                     strings.append( qfu( psz_name ) );
211                 }
212                 free( psz_title );
213                 free( psz_name );
214                 break;
215             case VLC_META_ENGINE_DESCRIPTION:
216                 ADD_META( p_item, Description );
217                 break;
218             case VLC_META_ENGINE_DURATION:
219                 secstotimestr( psz_duration,
220                     input_item_GetDuration( p_item->p_input ) / 1000000 );
221                 strings.append( QString( psz_duration ) );
222                 break;
223             case VLC_META_ENGINE_GENRE:
224                 ADD_META( p_item, Genre );
225                 break;
226             case VLC_META_ENGINE_COLLECTION:
227                 ADD_META( p_item, Album );
228                 break;
229             case VLC_META_ENGINE_SEQ_NUM:
230                 ADD_META( p_item, TrackNum );
231                 break;
232             case VLC_META_ENGINE_RATING:
233                 ADD_META( p_item, Rating );
234             default:
235                 break;
236             }
237         }
238
239     }
240 #undef ADD_META
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( x ) )
262     ADD_ICON( UNKNOWN , type_unknown_xpm );
263     ADD_ICON( FILE, ":/pixmaps/type_file.png" );
264     ADD_ICON( DIRECTORY, ":/pixmaps/type_directory.png" );
265     ADD_ICON( DISC, ":/pixmaps/disc_16px.png" );
266     ADD_ICON( CDDA, ":/pixmaps/cdda_16px.png" );
267     ADD_ICON( CARD, ":/pixmaps/capture-card_16px.png" );
268     ADD_ICON( NET, ":/pixmaps/type_net.png" );
269     ADD_ICON( PLAYLIST, ":/pixmaps/type_playlist.png" );
270     ADD_ICON( NODE, ":/pixmaps/type_node.png" );
271 #undef ADD_ICON
272
273     rootItem = NULL;
274     addCallbacks();
275     rebuild( p_root );
276 }
277
278 PLModel::~PLModel()
279 {
280     delCallbacks();
281     delete rootItem;
282 }
283
284 Qt::DropActions PLModel::supportedDropActions() const
285 {
286     return Qt::CopyAction;
287 }
288
289 Qt::ItemFlags PLModel::flags( const QModelIndex &index ) const
290 {
291     Qt::ItemFlags defaultFlags = QAbstractItemModel::flags( index );
292     if( index.isValid() )
293         return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
294     else
295         return Qt::ItemIsDropEnabled | defaultFlags;
296 }
297
298 QStringList PLModel::mimeTypes() const
299 {
300     QStringList types;
301     types << "vlc/playlist-item-id";
302     return types;
303 }
304
305 QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
306 {
307     QMimeData *mimeData = new QMimeData();
308     QByteArray encodedData;
309     QDataStream stream( &encodedData, QIODevice::WriteOnly );
310
311     foreach( QModelIndex index, indexes ) {
312         if( index.isValid() && index.column() == 0 )
313             stream << itemId( index );
314     }
315     mimeData->setData( "vlc/playlist-item-id", encodedData );
316     return mimeData;
317 }
318
319 bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
320                            int row, int column, const QModelIndex &target )
321 {
322     if( data->hasFormat( "vlc/playlist-item-id" ) )
323     {
324         if( action == Qt::IgnoreAction )
325             return true;
326
327         PLItem *targetItem;
328         if( target.isValid() )
329             targetItem = static_cast<PLItem*>( target.internalPointer() );
330         else
331             targetItem = rootItem;
332
333         QByteArray encodedData = data->data( "vlc/playlist-item-id" );
334         QDataStream stream( &encodedData, QIODevice::ReadOnly );
335
336         PLItem *newParentItem;
337         while( !stream.atEnd() )
338         {
339             int i;
340             int srcId;
341             stream >> srcId;
342
343             PL_LOCK;
344             playlist_item_t *p_target =
345                         playlist_ItemGetById( p_playlist, targetItem->i_id,
346                                               VLC_TRUE );
347             playlist_item_t *p_src = playlist_ItemGetById( p_playlist, srcId,
348                                                            VLC_TRUE );
349
350             if( !p_target || !p_src )
351             {
352                 PL_UNLOCK;
353                 return false;
354             }
355             if( p_target->i_children == -1 ) /* A leaf */
356             {
357                 PLItem *parentItem = targetItem->parent();
358                 assert( parentItem );
359                 playlist_item_t *p_parent =
360                          playlist_ItemGetById( p_playlist, parentItem->i_id,
361                                                VLC_TRUE );
362                 if( !p_parent )
363                 {
364                     PL_UNLOCK;
365                     return false;
366                 }
367                 for( i = 0 ; i< p_parent->i_children ; i++ )
368                     if( p_parent->pp_children[i] == p_target ) break;
369                 playlist_TreeMove( p_playlist, p_src, p_parent, i );
370                 newParentItem = parentItem;
371             }
372             else
373             {
374                 /* \todo: if we drop on a top-level node, use copy instead ? */
375                 playlist_TreeMove( p_playlist, p_src, p_target, 0 );
376                 i = 0;
377                 newParentItem = targetItem;
378             }
379             /* Remove from source */
380             PLItem *srcItem = FindById( rootItem, p_src->i_id );
381             // We dropped on the source selector. Ask the dialog to forward
382             // to the main view
383             if( !srcItem )
384             {
385                 emit shouldRemove( p_src->i_id );
386             }
387             else
388                 srcItem->remove( srcItem );
389
390             /* Display at new destination */
391             PLItem *newItem = new PLItem( p_src, newParentItem, this );
392             newParentItem->insertChild( newItem, i, true );
393             UpdateTreeItem( p_src, newItem, true );
394             if( p_src->i_children != -1 )
395                 UpdateNodeChildren( newItem );
396             PL_UNLOCK;
397         }
398     }
399     return true;
400 }
401
402 void PLModel::removeItem( int i_id )
403 {
404     PLItem *item = FindById( rootItem,i_id );
405     if( item ) item->remove( item );
406 }
407
408 void PLModel::addCallbacks()
409 {
410     /* Some global changes happened -> Rebuild all */
411     var_AddCallback( p_playlist, "intf-change", PlaylistChanged, this );
412     /* We went to the next item */
413     var_AddCallback( p_playlist, "playlist-current", PlaylistNext, this );
414     /* One item has been updated */
415     var_AddCallback( p_playlist, "item-change", ItemChanged, this );
416     var_AddCallback( p_playlist, "item-append", ItemAppended, this );
417     var_AddCallback( p_playlist, "item-deleted", ItemDeleted, this );
418 }
419
420 void PLModel::delCallbacks()
421 {
422     var_DelCallback( p_playlist, "item-change", ItemChanged, this );
423     var_DelCallback( p_playlist, "playlist-current", PlaylistNext, this );
424     var_DelCallback( p_playlist, "intf-change", PlaylistChanged, this );
425     var_DelCallback( p_playlist, "item-append", ItemAppended, this );
426     var_DelCallback( p_playlist, "item-deleted", ItemDeleted, this );
427 }
428
429 void PLModel::activateItem( const QModelIndex &index )
430 {
431     assert( index.isValid() );
432     PLItem *item = static_cast<PLItem*>(index.internalPointer());
433     assert( item );
434     PL_LOCK;
435     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id,
436                                                     VLC_TRUE);
437     activateItem( p_item );
438     PL_UNLOCK;
439 }
440 /* Must be entered with lock */
441 void PLModel::activateItem( playlist_item_t *p_item )
442 {
443     if( !p_item ) return;
444     playlist_item_t *p_parent = p_item;
445     while( p_parent )
446     {
447         if( p_parent->i_id == rootItem->i_id ) break;
448         p_parent = p_parent->p_parent;
449     }
450     if( p_parent )
451         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE, p_parent, p_item );
452 }
453
454 /****************** Base model mandatory implementations *****************/
455 QVariant PLModel::data( const QModelIndex &index, int role ) const
456 {
457     if( !index.isValid() ) return QVariant();
458     PLItem *item = static_cast<PLItem*>(index.internalPointer());
459     if( role == Qt::DisplayRole )
460     {
461         return QVariant( item->columnString( index.column() ) );
462     }
463     else if( role == Qt::DecorationRole && index.column() == 0  )
464     {
465         /* Use to segfault here because i_type wasn't always initialized */
466         if( item->i_type >= 0 )
467             return QVariant( PLModel::icons[item->i_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" ); 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     return rootItem->strings.count();
547 }
548
549 int PLModel::childrenCount( const QModelIndex &parent ) const
550 {
551     return rowCount( parent );
552 }
553
554 int PLModel::rowCount( const QModelIndex &parent ) const
555 {
556     PLItem *parentItem;
557
558     if( !parent.isValid() )
559         parentItem = rootItem;
560     else
561         parentItem = static_cast<PLItem*>(parent.internalPointer());
562
563     return parentItem->childCount();
564 }
565
566 /************************* General playlist status ***********************/
567
568 bool PLModel::hasRandom()
569 {
570     if( var_GetBool( p_playlist, "random" ) ) return true;
571     return false;
572 }
573 bool PLModel::hasRepeat()
574 {
575     if( var_GetBool( p_playlist, "repeat" ) ) return true;
576     return false;
577 }
578 bool PLModel::hasLoop()
579 {
580     if( var_GetBool( p_playlist, "loop" ) ) return true;
581     return false;
582 }
583 void PLModel::setLoop( bool on )
584 {
585     var_SetBool( p_playlist, "loop", on ? VLC_TRUE:VLC_FALSE );
586     config_PutInt( p_playlist, "loop", on ? 1: 0 );
587 }
588 void PLModel::setRepeat( bool on )
589 {
590     var_SetBool( p_playlist, "repeat", on ? VLC_TRUE:VLC_FALSE );
591     config_PutInt( p_playlist, "repeat", on ? 1: 0 );
592 }
593 void PLModel::setRandom( bool on )
594 {
595     var_SetBool( p_playlist, "random", on ? VLC_TRUE:VLC_FALSE );
596     config_PutInt( p_playlist, "random", on ? 1: 0 );
597 }
598
599 /************************* Lookups *****************************/
600
601 PLItem *PLModel::FindById( PLItem *root, int i_id )
602 {
603     return FindInner( root, i_id, false );
604 }
605
606 PLItem *PLModel::FindByInput( PLItem *root, int i_id )
607 {
608     return FindInner( root, i_id, true );
609 }
610
611 #define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
612 #define ICACHE( i, p ) { i_cached_input_id = i; p_cached_item_bi = p; }
613
614 PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
615 {
616     if( ( !b_input && i_cached_id == i_id) ||
617         ( b_input && i_cached_input_id ==i_id ) )
618     {
619         return b_input ? p_cached_item_bi : p_cached_item;
620     }
621
622     if( !b_input && root->i_id == i_id )
623     {
624         CACHE( i_id, root );
625         return root;
626     }
627     else if( b_input && root->i_input_id == i_id )
628     {
629         ICACHE( i_id, root );
630         return root;
631     }
632
633     QList<PLItem *>::iterator it = root->children.begin();
634     while ( it != root->children.end() )
635     {
636         if( !b_input && (*it)->i_id == i_id )
637         {
638             CACHE( i_id, (*it) );
639             return p_cached_item;
640         }
641         else if( b_input && (*it)->i_input_id == i_id )
642         {
643             ICACHE( i_id, (*it) );
644             return p_cached_item_bi;
645         }
646         if( (*it)->children.size() )
647         {
648             PLItem *childFound = FindInner( (*it), i_id, b_input );
649             if( childFound )
650             {
651                 if( b_input )
652                     ICACHE( i_id, childFound )
653                 else
654                     CACHE( i_id, childFound )
655                 return childFound;
656             }
657         }
658         it++;
659     }
660     return NULL;
661 }
662 #undef CACHE
663 #undef ICACHE
664
665
666 /************************* Updates handling *****************************/
667 void PLModel::customEvent( QEvent *event )
668 {
669     int type = event->type();
670     if( type != ItemUpdate_Type && type != ItemAppend_Type &&
671         type != ItemDelete_Type && type != PLUpdate_Type )
672         return;
673
674     PLEvent *ple = static_cast<PLEvent *>(event);
675
676     if( type == ItemUpdate_Type )
677         ProcessInputItemUpdate( ple->i_id );
678     else if( type == ItemAppend_Type )
679         ProcessItemAppend( ple->p_add );
680     else if( type == ItemDelete_Type )
681         ProcessItemRemoval( ple->i_id );
682     else
683         rebuild();
684 }
685
686 /**** Events processing ****/
687 void PLModel::ProcessInputItemUpdate( int i_input_id )
688 {
689     if( i_input_id <= 0 ) return;
690     PLItem *item = FindByInput( rootItem, i_input_id );
691     if( item )
692         UpdateTreeItem( item, true );
693 }
694
695 void PLModel::ProcessItemRemoval( int i_id )
696 {
697     if( i_id <= 0 ) return;
698     if( i_id == i_cached_id ) i_cached_id = -1;
699     i_cached_input_id = -1;
700     PLItem *item = FindById( rootItem, i_id );
701     if( item )
702         item->remove( item );
703 }
704
705 void PLModel::ProcessItemAppend( playlist_add_t *p_add )
706 {
707     playlist_item_t *p_item = NULL;
708     PLItem *newItem = NULL;
709     i_items_to_append--;
710     if( b_need_update ) return;
711
712     PLItem *nodeItem = FindById( rootItem, p_add->i_node );
713     PL_LOCK;
714     if( !nodeItem ) goto end;
715
716     p_item = playlist_ItemGetById( p_playlist, p_add->i_item, VLC_TRUE );
717     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
718     if( i_depth == 1 && p_item->p_parent &&
719                         p_item->p_parent->i_id != rootItem->i_id )
720         goto end;
721
722     newItem = new PLItem( p_item, nodeItem, this );
723     nodeItem->appendChild( newItem );
724     UpdateTreeItem( p_item, newItem, true );
725 end:
726     PL_UNLOCK;
727     return;
728 }
729
730
731 void PLModel::rebuild()
732 {
733     rebuild( NULL );
734 }
735
736 void PLModel::rebuild( playlist_item_t *p_root )
737 {
738     /* Remove callbacks before locking to avoid deadlocks */
739     delCallbacks();
740     /* Invalidate cache */
741     i_cached_id = i_cached_input_id = -1;
742
743     PL_LOCK;
744     /* Clear the tree */
745     if( rootItem )
746     {
747         if( rootItem->children.size() )
748         {
749             beginRemoveRows( index( rootItem, 0 ), 0,
750                     rootItem->children.size() -1 );
751             qDeleteAll( rootItem->children );
752             rootItem->children.clear();
753             endRemoveRows();
754         }
755     }
756     if( p_root )
757     {
758         //if( rootItem ) delete rootItem;
759         rootItem = new PLItem( p_root, NULL, this );
760     }
761     assert( rootItem );
762     /* Recreate from root */
763     UpdateNodeChildren( rootItem );
764     if( p_playlist->status.p_item )
765     {
766         PLItem *currentItem = FindByInput( rootItem,
767                                      p_playlist->status.p_item->p_input->i_id );
768         if( currentItem )
769         {
770             UpdateTreeItem( p_playlist->status.p_item, currentItem,
771                             true, false );
772         }
773     }
774     PL_UNLOCK;
775
776     /* And signal the view */
777     emit layoutChanged();
778     addCallbacks();
779 }
780
781 /* This function must be entered WITH the playlist lock */
782 void PLModel::UpdateNodeChildren( PLItem *root )
783 {
784     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id,
785                                                     VLC_TRUE );
786     UpdateNodeChildren( p_node, root );
787 }
788
789 /* This function must be entered WITH the playlist lock */
790 void PLModel::UpdateNodeChildren( playlist_item_t *p_node, PLItem *root )
791 {
792     for( int i = 0; i < p_node->i_children ; i++ )
793     {
794         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
795         PLItem *newItem =  new PLItem( p_node->pp_children[i], root, this );
796         root->appendChild( newItem, false );
797         UpdateTreeItem( newItem, false, true );
798         if( i_depth != 1 && p_node->pp_children[i]->i_children != -1 )
799             UpdateNodeChildren( p_node->pp_children[i], newItem );
800     }
801 }
802
803 /* This function must be entered WITH the playlist lock */
804 void PLModel::UpdateTreeItem( PLItem *item, bool signal, bool force )
805 {
806     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id,
807                                                     VLC_TRUE );
808     UpdateTreeItem( p_item, item, signal, force );
809 }
810
811 /* This function must be entered WITH the playlist lock */
812 void PLModel::UpdateTreeItem( playlist_item_t *p_item, PLItem *item,
813                               bool signal, bool force )
814 {
815     if ( !p_item )
816         return;
817     if( !force && i_depth == 1 && p_item->p_parent &&
818                                  p_item->p_parent->i_id != rootItem->i_id )
819         return;
820     item->update( p_item, p_item == p_playlist->status.p_item );
821     if( signal )
822         emit dataChanged( index( item, 0 ) , index( item, 1 ) );
823 }
824
825 /************************* Actions ******************************/
826
827 /**
828  * Deletion, here we have to do a ugly slow hack as we retrieve the full
829  * list of indexes to delete at once: when we delete a node and all of
830  * its children, we need to update the list.
831  * Todo: investigate whethere we can use ranges to be sure to delete all items?
832  */
833 void PLModel::doDelete( QModelIndexList selected )
834 {
835     for( int i = selected.size() -1 ; i >= 0; i-- )
836     {
837         QModelIndex index = selected[i];
838         if( index.column() != 0 ) continue;
839         PLItem *item = static_cast<PLItem*>(index.internalPointer());
840         if( item )
841         {
842             if( item->children.size() )
843                 recurseDelete( item->children, &selected );
844             doDeleteItem( item, &selected );
845         }
846     }
847 }
848
849 void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
850 {
851     for( int i = children.size() - 1; i >= 0 ; i-- )
852     {
853         PLItem *item = children[i];
854         if( item->children.size() )
855             recurseDelete( item->children, fullList );
856         doDeleteItem( item, fullList );
857     }
858 }
859
860 void PLModel::doDeleteItem( PLItem *item, QModelIndexList *fullList )
861 {
862     QModelIndex deleteIndex = index( item, 0 );
863     fullList->removeAll( deleteIndex );
864
865     PL_LOCK;
866     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id,
867                                                     VLC_TRUE );
868     if( !p_item )
869     {
870         PL_UNLOCK; return;
871     }
872     if( p_item->i_children == -1 )
873         playlist_DeleteFromInput( p_playlist, item->i_input_id, VLC_TRUE );
874     else
875         playlist_NodeDelete( p_playlist, p_item, VLC_TRUE, VLC_FALSE );
876     /* And finally, remove it from the tree */
877     item->remove( item );
878     PL_UNLOCK;
879 }
880
881 /******* Volume III: Sorting and searching ********/
882 void PLModel::sort( int column, Qt::SortOrder order )
883 {
884     PL_LOCK;
885     {
886         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
887                                                         rootItem->i_id,
888                                                         VLC_TRUE );
889         int i_mode;
890         switch( column )
891         {
892         case 0: i_mode = SORT_TITLE_NODES_FIRST;break;
893         case 1: i_mode = SORT_ARTIST;break;
894         case 2: i_mode = SORT_DURATION; break;
895         default: i_mode = SORT_TITLE_NODES_FIRST; break;
896         }
897         if( p_root )
898             playlist_RecursiveNodeSort( p_playlist, p_root, i_mode,
899                                         order == Qt::AscendingOrder ?
900                                             ORDER_NORMAL : ORDER_REVERSE );
901     }
902     PL_UNLOCK;
903     rebuild();
904 }
905
906 void PLModel::search( QString search_text )
907 {
908     /** \todo Fire the search with a small delay ? */
909     PL_LOCK;
910     {
911         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
912                                                         rootItem->i_id,
913                                                         VLC_TRUE );
914         assert( p_root );
915         char *psz_name = search_text.toUtf8().data();
916         playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
917     }
918     PL_UNLOCK;
919     rebuild();
920 }
921
922 /*********** Popup *********/
923 void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
924 {
925     assert( index.isValid() );
926     PL_LOCK;
927     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
928                                                     itemId( index ), VLC_TRUE );
929     if( p_item )
930     {
931         i_popup_item = p_item->i_id;
932         i_popup_parent = p_item->p_parent ? p_item->p_parent->i_id : -1;
933         PL_UNLOCK;
934         current_selection = list;
935         QMenu *menu = new QMenu;
936         menu->addAction( qfu(I_POP_PLAY), this, SLOT( popupPlay() ) );
937         menu->addAction( qfu(I_POP_DEL), this, SLOT( popupDel() ) );
938         menu->addSeparator();
939         menu->addAction( qfu(I_POP_STREAM), this, SLOT( popupStream() ) );
940         menu->addAction( qfu(I_POP_SAVE), this, SLOT( popupSave() ) );
941         menu->addSeparator();
942         menu->addAction( qfu(I_POP_INFO), this, SLOT( popupInfo() ) );
943         if( p_item->i_children > -1 )
944         {
945             menu->addSeparator();
946             menu->addAction( qfu(I_POP_SORT), this, SLOT( popupSort() ) );
947             menu->addAction( qfu(I_POP_ADD), this, SLOT( popupAdd() ) );
948         }
949 #ifdef WIN32
950         menu->addSeparator();
951         menu->addAction( qfu( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
952 #endif
953         menu->popup( point );
954     }
955     else
956         PL_UNLOCK;
957 }
958
959
960 void PLModel::viewchanged( int meta )
961 {
962    if( rootItem )
963    {
964        int index=0;
965        switch( meta )
966        {
967        case VLC_META_ENGINE_TITLE:
968            index=0; break;
969        case VLC_META_ENGINE_DURATION:
970            index=1; break;
971        case VLC_META_ENGINE_ARTIST:
972            index=2; break;
973        case VLC_META_ENGINE_GENRE:
974            index=3; break;
975        case VLC_META_ENGINE_COPYRIGHT:
976            index=4; break;
977        case VLC_META_ENGINE_COLLECTION:
978            index=5; break;
979        case VLC_META_ENGINE_SEQ_NUM:
980            index=6; break;
981        case VLC_META_ENGINE_DESCRIPTION:
982            index=7; break;
983        default:
984            break;
985        }
986        /* UNUSED        emit layoutAboutToBeChanged(); */
987        index = __MIN( index , rootItem->strings.count() );
988        QModelIndex parent = createIndex( 0, 0, rootItem );
989
990        if( rootItem->i_showflags & meta )
991            /* Removing columns */
992        {
993            beginRemoveColumns( parent, index, index+1 );
994            rootItem->i_showflags &= ~( meta );
995            rootItem->updateview();
996            endRemoveColumns();
997        }
998        else
999        {
1000            /* Adding columns */
1001            beginInsertColumns( createIndex( 0, 0, rootItem), index, index+1 );
1002            rootItem->i_showflags |= meta;
1003            rootItem->updateview();
1004            endInsertColumns();
1005        }
1006        rebuild();
1007        config_PutInt( p_intf, "qt-pl-showflags", rootItem->i_showflags );
1008        config_SaveConfigFile( p_intf, NULL );
1009    }
1010 }
1011
1012 void PLModel::popupDel()
1013 {
1014     doDelete( current_selection );
1015 }
1016 void PLModel::popupPlay()
1017 {
1018     PL_LOCK;
1019     {
1020         playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1021                                                         i_popup_item,VLC_TRUE );
1022         activateItem( p_item );
1023     }
1024     PL_UNLOCK;
1025 }
1026
1027 void PLModel::popupInfo()
1028 {
1029     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1030                                                     i_popup_item,
1031                                                     VLC_TRUE );
1032     if( p_item )
1033     {
1034         MediaInfoDialog *mid = new MediaInfoDialog( p_intf, p_item->p_input );
1035         mid->show();
1036     }
1037 }
1038
1039 void PLModel::popupStream()
1040 {
1041      msg_Err( p_playlist, "Stream not implemented" );
1042 }
1043
1044 void PLModel::popupSave()
1045 {
1046      msg_Err( p_playlist, "Save not implemented" );
1047 }
1048
1049 #ifdef WIN32
1050 #include <shellapi.h>
1051 void PLModel::popupExplore()
1052 {
1053     ShellExecuteW( NULL, L"explore", L"C:\\", NULL, NULL, SW_SHOWNORMAL );
1054 }
1055 #endif
1056
1057 /**********************************************************************
1058  * Playlist callbacks
1059  **********************************************************************/
1060 static int PlaylistChanged( vlc_object_t *p_this, const char *psz_variable,
1061                             vlc_value_t oval, vlc_value_t nval, void *param )
1062 {
1063     PLModel *p_model = (PLModel *) param;
1064     PLEvent *event = new PLEvent( PLUpdate_Type, 0 );
1065     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1066     return VLC_SUCCESS;
1067 }
1068
1069 static int PlaylistNext( vlc_object_t *p_this, const char *psz_variable,
1070                          vlc_value_t oval, vlc_value_t nval, void *param )
1071 {
1072     PLModel *p_model = (PLModel *) param;
1073     PLEvent *event = new PLEvent( ItemUpdate_Type, oval.i_int );
1074     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1075     event = new PLEvent( ItemUpdate_Type, nval.i_int );
1076     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1077     return VLC_SUCCESS;
1078 }
1079
1080 static int ItemChanged( vlc_object_t *p_this, const char *psz_variable,
1081                         vlc_value_t oval, vlc_value_t nval, void *param )
1082 {
1083     PLModel *p_model = (PLModel *) param;
1084     PLEvent *event = new PLEvent( ItemUpdate_Type, nval.i_int );
1085     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1086     return VLC_SUCCESS;
1087 }
1088
1089 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
1090                         vlc_value_t oval, vlc_value_t nval, void *param )
1091 {
1092     PLModel *p_model = (PLModel *) param;
1093     PLEvent *event = new PLEvent( ItemDelete_Type, nval.i_int );
1094     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1095     return VLC_SUCCESS;
1096 }
1097
1098 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
1099                          vlc_value_t oval, vlc_value_t nval, void *param )
1100 {
1101     PLModel *p_model = (PLModel *) param;
1102     playlist_add_t *p_add = (playlist_add_t *)malloc( sizeof( playlist_add_t));
1103     memcpy( p_add, nval.p_address, sizeof( playlist_add_t ) );
1104
1105     if( ++p_model->i_items_to_append >= 50 )
1106     {
1107 //        p_model->b_need_update = VLC_TRUE;
1108 //        return VLC_SUCCESS;
1109     }
1110     PLEvent *event = new PLEvent(  p_add );
1111     QApplication::postEvent( p_model, static_cast<QEvent*>(event) );
1112     return VLC_SUCCESS;
1113 }
1114