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