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