]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/playlist_model.cpp
qt4: cosmetics and consistence
[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  *          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::rowCount( const QModelIndex &parent ) const
505 {
506     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
507     return parentItem->childCount();
508 }
509
510 QStringList PLModel::selectedURIs()
511 {
512     QStringList lst;
513     for( int i = 0; i < current_selection.size(); i++ )
514     {
515         PLItem *item = getItem( current_selection[i] );
516         if( item )
517         {
518             PL_LOCK;
519             playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
520             if( p_item )
521             {
522                 char *psz = input_item_GetURI( p_item->p_input );
523                 if( psz )
524                 {
525                     lst.append( psz );
526                     free( psz );
527                 }
528             }
529             PL_UNLOCK;
530         }
531     }
532     return lst;
533 }
534
535 /************************* General playlist status ***********************/
536
537 bool PLModel::hasRandom()
538 {
539     return var_GetBool( p_playlist, "random" );
540 }
541 bool PLModel::hasRepeat()
542 {
543     return var_GetBool( p_playlist, "repeat" );
544 }
545 bool PLModel::hasLoop()
546 {
547     return var_GetBool( p_playlist, "loop" );
548 }
549 void PLModel::setLoop( bool on )
550 {
551     var_SetBool( p_playlist, "loop", on ? true:false );
552     config_PutInt( p_playlist, "loop", on ? 1: 0 );
553 }
554 void PLModel::setRepeat( bool on )
555 {
556     var_SetBool( p_playlist, "repeat", on ? true:false );
557     config_PutInt( p_playlist, "repeat", on ? 1: 0 );
558 }
559 void PLModel::setRandom( bool on )
560 {
561     var_SetBool( p_playlist, "random", on ? true:false );
562     config_PutInt( p_playlist, "random", on ? 1: 0 );
563 }
564
565 /************************* Lookups *****************************/
566
567 PLItem *PLModel::findById( PLItem *root, int i_id )
568 {
569     return findInner( root, i_id, false );
570 }
571
572 PLItem *PLModel::findByInput( PLItem *root, int i_id )
573 {
574     PLItem *result = findInner( root, i_id, true );
575     return result;
576 }
577
578 #define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
579 #define ICACHE( i, p ) { i_cached_input_id = i; p_cached_item_bi = p; }
580
581 PLItem * PLModel::findInner( PLItem *root, int i_id, bool b_input )
582 {
583     if( ( !b_input && i_cached_id == i_id) ||
584         ( b_input && i_cached_input_id ==i_id ) )
585     {
586         return b_input ? p_cached_item_bi : p_cached_item;
587     }
588
589     if( !b_input && root->i_id == i_id )
590     {
591         CACHE( i_id, root );
592         return root;
593     }
594     else if( b_input && root->p_input->i_id == i_id )
595     {
596         ICACHE( i_id, root );
597         return root;
598     }
599
600     QList<PLItem *>::iterator it = root->children.begin();
601     while ( it != root->children.end() )
602     {
603         if( !b_input && (*it)->i_id == i_id )
604         {
605             CACHE( i_id, (*it) );
606             return p_cached_item;
607         }
608         else if( b_input && (*it)->p_input->i_id == i_id )
609         {
610             ICACHE( i_id, (*it) );
611             return p_cached_item_bi;
612         }
613         if( (*it)->children.size() )
614         {
615             PLItem *childFound = findInner( (*it), i_id, b_input );
616             if( childFound )
617             {
618                 if( b_input )
619                     ICACHE( i_id, childFound )
620                 else
621                     CACHE( i_id, childFound )
622                 return childFound;
623             }
624         }
625         it++;
626     }
627     return NULL;
628 }
629 #undef CACHE
630 #undef ICACHE
631
632 PLItem *PLModel::getItem( QModelIndex index )
633 {
634     assert( index.isValid() );
635     return static_cast<PLItem*>( index.internalPointer() );
636 }
637
638 /* computes column id of meta data from visible column index */
639 int PLModel::metaColumn( int column ) const
640 {
641     int metadata = 1;
642     int running_index = -1;
643
644     while( metadata < COLUMN_END )
645     {
646         if( metadata & i_showflags )
647             running_index++;
648         if( running_index == column )
649             break;
650         metadata <<= 1;
651     }
652
653     if( running_index != column ) return COLUMN_END;
654     return metadata;
655 }
656
657 /************************* Updates handling *****************************/
658 void PLModel::customEvent( QEvent *event )
659 {
660     int type = event->type();
661     if( type != ItemAppend_Type &&
662         type != ItemDelete_Type && type != PLUpdate_Type )
663         return;
664
665     PLEvent *ple = static_cast<PLEvent *>(event);
666
667     if( type == ItemAppend_Type )
668         processItemAppend( &ple->add );
669     else if( type == ItemDelete_Type )
670         processItemRemoval( ple->i_id );
671     else
672         rebuild();
673 }
674
675 /**** Events processing ****/
676 void PLModel::processInputItemUpdate( input_thread_t *p_input )
677 {
678     if( !p_input ) return;
679     processInputItemUpdate( input_GetItem( p_input ) );
680     if( p_input && !( p_input->b_dead || !vlc_object_alive( p_input ) ) )
681     {
682         PLItem *item = findByInput( rootItem, input_GetItem( p_input )->i_id );
683         currentItem = item;
684         emit currentChanged( index( item, 0 ) );
685     }
686     else
687     {
688         currentItem = NULL;
689     }
690 }
691 void PLModel::processInputItemUpdate( input_item_t *p_item )
692 {
693     if( !p_item ||  p_item->i_id <= 0 ) return;
694     PLItem *item = findByInput( rootItem, p_item->i_id );
695     if( item )
696         updateTreeItem( item, true, true);
697 }
698
699 void PLModel::processItemRemoval( int i_id )
700 {
701     if( i_id <= 0 ) return;
702     if( i_id == i_cached_id ) i_cached_id = -1;
703     i_cached_input_id = -1;
704
705     removeItem( i_id );
706 }
707
708 void PLModel::processItemAppend( const playlist_add_t *p_add )
709 {
710     playlist_item_t *p_item = NULL;
711     PLItem *newItem = NULL;
712
713     PLItem *nodeItem = findById( rootItem, p_add->i_node );
714     if( !nodeItem ) return;
715
716     PL_LOCK;
717     p_item = playlist_ItemGetById( p_playlist, p_add->i_item );
718     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
719     if( i_depth == DEPTH_SEL && p_item->p_parent &&
720                         p_item->p_parent->i_id != rootItem->i_id )
721         goto end;
722
723     newItem = new PLItem( p_item, nodeItem );
724     PL_UNLOCK;
725
726     beginInsertRows( index( nodeItem, 0 ), nodeItem->childCount(), nodeItem->childCount() );
727     nodeItem->appendChild( newItem );
728     endInsertRows();
729     updateTreeItem( newItem, true );
730     return;
731 end:
732     PL_UNLOCK;
733     return;
734 }
735
736
737 void PLModel::rebuild()
738 {
739     rebuild( NULL );
740 }
741
742 void PLModel::rebuild( playlist_item_t *p_root )
743 {
744     playlist_item_t* p_item;
745     /* Remove callbacks before locking to avoid deadlocks */
746     delCallbacks();
747     /* Invalidate cache */
748     i_cached_id = i_cached_input_id = -1;
749
750     if( rootItem ) rootItem->removeChildren();
751
752     PL_LOCK;
753     if( p_root )
754     {
755         delete rootItem;
756         rootItem = new PLItem( p_root );
757     }
758     assert( rootItem );
759     /* Recreate from root */
760     updateChildren( rootItem );
761     if( (p_item = playlist_CurrentPlayingItem(p_playlist)) )
762         currentItem = findByInput( rootItem, p_item->p_input->i_id );
763     else
764         currentItem = NULL;
765     PL_UNLOCK;
766
767     /* And signal the view */
768     reset();
769     emit currentChanged( index( currentItem, 0 ) );
770
771     addCallbacks();
772 }
773
774 void PLModel::takeItem( PLItem *item )
775 {
776     assert( item );
777     PLItem *parent = item->parentItem;
778     assert( parent );
779     int i_index = parent->children.indexOf( item );
780
781     beginRemoveRows( index( parent, 0 ), i_index, i_index );
782     parent->takeChildAt( i_index );
783     endRemoveRows();
784 }
785
786 void PLModel::insertChildren( PLItem *node, QList<PLItem*>& items, int i_pos )
787 {
788     assert( node );
789     int count = items.size();
790     if( !count ) return;
791     beginInsertRows( index( node, 0 ), i_pos, i_pos + count - 1 );
792     for( int i = 0; i < count; i++ )
793     {
794         node->children.insert( i_pos + i, items[i] );
795         items[i]->parentItem = node;
796     }
797     endInsertRows();
798 }
799
800 void PLModel::removeItem( PLItem *item )
801 {
802     if( !item ) return;
803     if( currentItem == item )
804     {
805         currentItem = NULL;
806         emit currentChanged( QModelIndex() );
807     }
808     PLItem *parent = item->parentItem;
809     assert( parent );
810     parent->removeChild( item );
811 }
812
813 /* This function must be entered WITH the playlist lock */
814 void PLModel::updateChildren( PLItem *root )
815 {
816     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
817     updateChildren( p_node, root );
818 }
819
820 /* This function must be entered WITH the playlist lock */
821 void PLModel::updateChildren( playlist_item_t *p_node, PLItem *root )
822 {
823     playlist_item_t *p_item = playlist_CurrentPlayingItem(p_playlist);
824     for( int i = 0; i < p_node->i_children ; i++ )
825     {
826         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
827         PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
828         root->appendChild( newItem );
829         if( p_item && newItem->p_input == p_item->p_input )
830         {
831             currentItem = newItem;
832             emit currentChanged( index( currentItem, 0 ) );
833         }
834         if( i_depth == DEPTH_PL && p_node->pp_children[i]->i_children != -1 )
835             updateChildren( p_node->pp_children[i], newItem );
836     }
837 }
838
839 /* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
840 void PLModel::updateTreeItem( PLItem *item, bool signal, bool force )
841 {
842     if ( !item || !item->p_input )
843         return;
844     if( !force && i_depth == DEPTH_SEL && item->parentItem &&
845                                  item->parentItem->p_input != rootItem->p_input )
846         return;
847     if( signal )
848         emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) ) );
849 }
850
851 /************************* Actions ******************************/
852
853 /**
854  * Deletion, here we have to do a ugly slow hack as we retrieve the full
855  * list of indexes to delete at once: when we delete a node and all of
856  * its children, we need to update the list.
857  * Todo: investigate whethere we can use ranges to be sure to delete all items?
858  */
859 void PLModel::doDelete( QModelIndexList selected )
860 {
861     for( int i = selected.size() -1 ; i >= 0; i-- )
862     {
863         QModelIndex index = selected[i];
864         if( index.column() != 0 ) continue;
865         PLItem *item = getItem( index );
866         if( item )
867         {
868             if( item->children.size() )
869                 recurseDelete( item->children, &selected );
870             doDeleteItem( item, &selected );
871         }
872         if( i > selected.size() ) i = selected.size();
873     }
874 }
875
876 void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
877 {
878     for( int i = children.size() - 1; i >= 0 ; i-- )
879     {
880         PLItem *item = children[i];
881         if( item->children.size() )
882             recurseDelete( item->children, fullList );
883         doDeleteItem( item, fullList );
884     }
885 }
886
887 void PLModel::doDeleteItem( PLItem *item, QModelIndexList *fullList )
888 {
889     QModelIndex deleteIndex = index( item, 0 );
890     fullList->removeAll( deleteIndex );
891
892     PL_LOCK;
893     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
894     if( !p_item )
895     {
896         PL_UNLOCK;
897         return;
898     }
899     if( p_item->i_children == -1 )
900         playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
901     else
902         playlist_NodeDelete( p_playlist, p_item, true, false );
903     PL_UNLOCK;
904     /* And finally, remove it from the tree */
905     int itemIndex = item->parentItem->children.indexOf( item );
906     beginRemoveRows( index( item->parentItem, 0), itemIndex, itemIndex );
907     removeItem( item );
908     endRemoveRows();
909 }
910
911 /******* Volume III: Sorting and searching ********/
912 void PLModel::sort( int column, Qt::SortOrder order )
913 {
914     sort( rootItem->i_id, column, order );
915 }
916
917 void PLModel::sort( int i_root_id, int column, Qt::SortOrder order )
918 {
919     int i_index = -1;
920     int i_flag = 0;
921
922     int i_column = 1;
923     for( i_column = 1; i_column != COLUMN_END; i_column<<=1 )
924     {
925         if( ( shownFlags() & i_column ) )
926             i_index++;
927         if( column == i_index )
928         {
929             i_flag = i_column;
930             break;
931         }
932     }
933
934     PLItem *item = findById( rootItem, i_root_id );
935     if( !item ) return;
936     QModelIndex qIndex = index( item, 0 );
937     int count = item->children.size();
938     if( count )
939     {
940         beginRemoveRows( qIndex, 0, count - 1 );
941         item->removeChildren();
942         endRemoveRows( );
943     }
944
945     PL_LOCK;
946     {
947         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
948                                                         i_root_id );
949         if( p_root && i_flag )
950         {
951             playlist_RecursiveNodeSort( p_playlist, p_root,
952                                         i_column_sorting( i_flag ),
953                                         order == Qt::AscendingOrder ?
954                                             ORDER_NORMAL : ORDER_REVERSE );
955         }
956     }
957     if( count )
958     {
959         beginInsertRows( qIndex, 0, count - 1 );
960         updateChildren( item );
961         endInsertRows( );
962     }
963     PL_UNLOCK;
964 }
965
966 void PLModel::search( const QString& search_text )
967 {
968     /** \todo Fire the search with a small delay ? */
969     PL_LOCK;
970     {
971         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
972                                                         rootItem->i_id );
973         assert( p_root );
974         const char *psz_name = search_text.toUtf8().data();
975         playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
976     }
977     PL_UNLOCK;
978     rebuild();
979 }
980
981 /*********** Popup *********/
982 void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
983 {
984     int i_id = index.isValid() ? itemId( index ) : rootItem->i_id;
985
986     PL_LOCK;
987     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
988     if( !p_item )
989     {
990         PL_UNLOCK; return;
991     }
992     i_popup_item = index.isValid() ? p_item->i_id : -1;
993     i_popup_parent = index.isValid() ?
994         ( p_item->p_parent ? p_item->p_parent->i_id : -1 ) :
995         ( p_item->i_id );
996     i_popup_column = index.column();
997     /* check whether we are in tree view */
998     bool tree = false;
999     playlist_item_t *p_up = p_item;
1000     while( p_up )
1001     {
1002         if ( p_up == p_playlist->p_root_category ) tree = true;
1003         p_up = p_up->p_parent;
1004     }
1005     PL_UNLOCK;
1006
1007     current_selection = list;
1008     QMenu *menu = new QMenu;
1009     if( i_popup_item > -1 )
1010     {
1011         menu->addAction( qtr(I_POP_PLAY), this, SLOT( popupPlay() ) );
1012         menu->addAction( qtr(I_POP_DEL), this, SLOT( popupDel() ) );
1013         menu->addSeparator();
1014         menu->addAction( qtr(I_POP_STREAM), this, SLOT( popupStream() ) );
1015         menu->addAction( qtr(I_POP_SAVE), this, SLOT( popupSave() ) );
1016         menu->addSeparator();
1017         menu->addAction( qtr(I_POP_INFO), this, SLOT( popupInfo() ) );
1018         menu->addSeparator();
1019         QMenu *sort_menu = menu->addMenu( qtr( "Sort by ") +
1020             qfu( psz_column_title( metaColumn( index.column() ) ) ) );
1021         sort_menu->addAction( qtr( "Ascending" ),
1022             this, SLOT( popupSortAsc() ) );
1023         sort_menu->addAction( qtr( "Descending" ),
1024             this, SLOT( popupSortDesc() ) );
1025     }
1026     if( tree )
1027         menu->addAction( qtr(I_POP_ADD), this, SLOT( popupAddNode() ) );
1028     if( i_popup_item > -1 )
1029     {
1030         menu->addSeparator();
1031         menu->addAction( qtr( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
1032     }
1033     menu->popup( point );
1034 }
1035
1036
1037 void PLModel::viewchanged( int meta )
1038 {
1039     assert( meta );
1040     int _meta = meta;
1041     if( rootItem )
1042     {
1043         int index=-1;
1044         while( _meta )
1045         {
1046             index++;
1047             _meta >>= 1;
1048         }
1049
1050         index = __MIN( index, columnCount() );
1051         QModelIndex parent = createIndex( 0, 0, rootItem );
1052
1053         if( i_showflags & meta )
1054             /* Removing columns */
1055         {
1056             beginRemoveColumns( parent, index, index+1 );
1057             i_showflags &= ~( meta );
1058             getSettings()->setValue( "qt-pl-showflags", i_showflags );
1059             endRemoveColumns();
1060         }
1061         else
1062         {
1063             /* Adding columns */
1064             beginInsertColumns( parent, index, index+1 );
1065             i_showflags |= meta;
1066             getSettings()->setValue( "qt-pl-showflags", i_showflags );
1067             endInsertColumns();
1068         }
1069
1070         emit columnsChanged( meta );
1071     }
1072 }
1073
1074 void PLModel::popupDel()
1075 {
1076     doDelete( current_selection );
1077 }
1078
1079 void PLModel::popupPlay()
1080 {
1081     PL_LOCK;
1082     {
1083         playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1084                                                         i_popup_item );
1085         activateItem( p_item );
1086     }
1087     PL_UNLOCK;
1088 }
1089
1090 void PLModel::popupInfo()
1091 {
1092     PL_LOCK;
1093     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1094                                                     i_popup_item );
1095     if( p_item )
1096     {
1097         input_item_t* p_input = p_item->p_input;
1098         vlc_gc_incref( p_input );
1099         PL_UNLOCK;
1100         MediaInfoDialog *mid = new MediaInfoDialog( p_intf, p_input );
1101         vlc_gc_decref( p_input );
1102         mid->setParent( PlaylistDialog::getInstance( p_intf ),
1103                         Qt::Dialog );
1104         mid->show();
1105     } else
1106         PL_UNLOCK;
1107 }
1108
1109 void PLModel::popupStream()
1110 {
1111     QStringList mrls = selectedURIs();
1112     if( !mrls.isEmpty() )
1113         THEDP->streamingDialog( NULL, mrls[0], false );
1114
1115 }
1116
1117 void PLModel::popupSave()
1118 {
1119     QStringList mrls = selectedURIs();
1120     if( !mrls.isEmpty() )
1121         THEDP->streamingDialog( NULL, mrls[0] );
1122 }
1123
1124 #include <QUrl>
1125 #include <QFileInfo>
1126 #include <QDesktopServices>
1127 void PLModel::popupExplore()
1128 {
1129     PL_LOCK;
1130     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1131                                                     i_popup_item );
1132     if( p_item )
1133     {
1134        input_item_t *p_input = p_item->p_input;
1135        char *psz_meta = input_item_GetURI( p_input );
1136        PL_UNLOCK;
1137        if( psz_meta )
1138        {
1139            const char *psz_access;
1140            const char *psz_demux;
1141            char  *psz_path;
1142            input_SplitMRL( &psz_access, &psz_demux, &psz_path, psz_meta );
1143
1144            if( EMPTY_STR( psz_access ) ||
1145                !strncasecmp( psz_access, "file", 4 ) ||
1146                !strncasecmp( psz_access, "dire", 4 ) )
1147            {
1148                QFileInfo info( qfu( psz_meta ) );
1149                QDesktopServices::openUrl(
1150                                QUrl::fromLocalFile( info.absolutePath() ) );
1151            }
1152            free( psz_meta );
1153        }
1154     }
1155     else
1156         PL_UNLOCK;
1157 }
1158
1159 #include <QInputDialog>
1160 void PLModel::popupAddNode()
1161 {
1162     bool ok;
1163     QString name = QInputDialog::getText( PlaylistDialog::getInstance( p_intf ),
1164         qtr( I_POP_ADD ), qtr( "Enter name for new node:" ),
1165         QLineEdit::Normal, QString(), &ok);
1166     if( !ok || name.isEmpty() ) return;
1167     PL_LOCK;
1168     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1169                                                     i_popup_parent );
1170     if( p_item )
1171     {
1172         playlist_NodeCreate( p_playlist, qtu( name ), p_item, 0, NULL );
1173     }
1174     PL_UNLOCK;
1175 }
1176
1177 void PLModel::popupSortAsc()
1178 {
1179     sort( i_popup_parent, i_popup_column, Qt::AscendingOrder );
1180 }
1181
1182 void PLModel::popupSortDesc()
1183 {
1184     sort( i_popup_parent, i_popup_column, Qt::DescendingOrder );
1185 }
1186 /**********************************************************************
1187  * Playlist callbacks
1188  **********************************************************************/
1189 static int PlaylistChanged( vlc_object_t *p_this, const char *psz_variable,
1190                             vlc_value_t oval, vlc_value_t nval, void *param )
1191 {
1192     PLModel *p_model = (PLModel *) param;
1193     PLEvent *event = new PLEvent( PLUpdate_Type, 0 );
1194     QApplication::postEvent( p_model, event );
1195     return VLC_SUCCESS;
1196 }
1197
1198 static int PlaylistNext( vlc_object_t *p_this, const char *psz_variable,
1199                          vlc_value_t oval, vlc_value_t nval, void *param )
1200 {
1201     PLModel *p_model = (PLModel *) param;
1202     PLEvent *event = new PLEvent( ItemUpdate_Type, oval.i_int );
1203     QApplication::postEvent( p_model, event );
1204     event = new PLEvent( ItemUpdate_Type, nval.i_int );
1205     QApplication::postEvent( p_model, event );
1206     return VLC_SUCCESS;
1207 }
1208
1209 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
1210                         vlc_value_t oval, vlc_value_t nval, void *param )
1211 {
1212     PLModel *p_model = (PLModel *) param;
1213     PLEvent *event = new PLEvent( ItemDelete_Type, nval.i_int );
1214     QApplication::postEvent( p_model, event );
1215     return VLC_SUCCESS;
1216 }
1217
1218 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
1219                          vlc_value_t oval, vlc_value_t nval, void *param )
1220 {
1221     PLModel *p_model = (PLModel *) param;
1222     const playlist_add_t *p_add = (playlist_add_t *)nval.p_address;
1223     PLEvent *event = new PLEvent( p_add );
1224     QApplication::postEvent( p_model, event );
1225     return VLC_SUCCESS;
1226 }
1227