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