qt4: helper function PLItem * getItem( const QModelIndex ) const
[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 = p_playlist->p_local_category->p_input;
136     input_item_t *ml_input = p_playlist->p_ml_category->p_input;
137
138     if( rootItem->i_id == p_playlist->p_root_onelevel->i_id
139           || rootItem->i_id == p_playlist->p_root_category->i_id )
140     {
141         if( item->p_input == pl_input
142             || item->p_input == ml_input)
143                 flags |= Qt::ItemIsDropEnabled;
144     }
145     else if( rootItem->p_input == pl_input ||
146             rootItem->p_input == ml_input )
147     {
148         PL_LOCK;
149         playlist_item_t *plItem =
150             playlist_ItemGetById( p_playlist, item->i_id );
151
152         if ( plItem && ( plItem->i_children > -1 ) )
153             flags |= Qt::ItemIsDropEnabled;
154
155         PL_UNLOCK;
156
157     }
158     flags |= Qt::ItemIsDragEnabled;
159
160     return flags;
161 }
162
163 /* A list of model indexes are a playlist */
164 QStringList PLModel::mimeTypes() const
165 {
166     QStringList types;
167     types << "vlc/playlist-item-id";
168     return types;
169 }
170
171 QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
172 {
173     QMimeData *mimeData = new QMimeData();
174     QByteArray encodedData;
175     QDataStream stream( &encodedData, QIODevice::WriteOnly );
176     QModelIndexList list;
177
178     foreach( const QModelIndex &index, indexes ) {
179         if( index.isValid() && index.column() == 0 )
180             list.append(index);
181     }
182
183     qSort(list);
184
185     foreach( const QModelIndex &index, list ) {
186         stream << itemId( index );
187     }
188     mimeData->setData( "vlc/playlist-item-id", encodedData );
189     return mimeData;
190 }
191
192 /* Drop operation */
193 bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
194                            int row, int column, const QModelIndex &parent )
195 {
196     if( data->hasFormat( "vlc/playlist-item-id" ) )
197     {
198         if( action == Qt::IgnoreAction )
199             return true;
200
201         PL_LOCK;
202
203         playlist_item_t *p_parent;
204
205         if( !parent.isValid())
206         {
207             if( row > -1)
208                 p_parent = playlist_ItemGetById( p_playlist, rootItem->i_id );
209             else
210             {
211                 PL_UNLOCK;
212                 return true;
213             }
214         }
215         else
216             p_parent = playlist_ItemGetById( p_playlist, itemId ( parent ) );
217
218         if( !p_parent || p_parent->i_children == -1 )
219         {
220             PL_UNLOCK;
221             return false;
222         }
223
224         bool copy = false;
225         if( row == -1 &&
226             ( p_parent->p_input == p_playlist->p_local_category->p_input
227             || p_parent->p_input == p_playlist->p_ml_category->p_input ) )
228                 copy = true;
229
230         QByteArray encodedData = data->data( "vlc/playlist-item-id" );
231         QDataStream stream( &encodedData, QIODevice::ReadOnly );
232
233         if( copy )
234         {
235             while( !stream.atEnd() )
236             {
237                 int i_id;
238                 stream >> i_id;
239                 playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
240                 if( !p_item )
241                 {
242                     PL_UNLOCK;
243                     return false;
244                 }
245                 input_item_t *p_input = p_item->p_input;
246                 playlist_AddExt ( p_playlist,
247                     p_input->psz_uri, p_input->psz_name,
248                     PLAYLIST_APPEND | PLAYLIST_SPREPARSE, PLAYLIST_END,
249                     p_input->i_duration,
250                     p_input->i_options, p_input->ppsz_options, p_input->optflagc,
251                     p_parent == p_playlist->p_local_category, true );
252             }
253         }
254         else
255         {
256             QList<playlist_item_t*> items;
257             while( !stream.atEnd() )
258             {
259                 int id;
260                 stream >> id;
261                 playlist_item_t *item = playlist_ItemGetById( p_playlist, id );
262                 if( !item ) continue;
263                 /* better not try to move a node into itself: */
264                 if( item->i_children > 0 )
265                 {
266                     playlist_item_t *climber = p_parent;
267                     while( climber )
268                     {
269                         if( climber == item ) break;
270                         climber = climber->p_parent;
271                     }
272                     if( climber ) continue;
273                 }
274                 items.append( item );
275             }
276             int count = items.size();
277             if( count )
278             {
279                 playlist_item_t *pp_items[count];
280                 for( int i = 0; i < count; i++ ) pp_items[i] = items[i];
281                 playlist_TreeMoveMany( p_playlist, count, pp_items, p_parent,
282                     (row == -1 ? p_parent->i_children : row) );
283             }
284         }
285
286         PL_UNLOCK;
287         /*TODO: That's not a good idea to rebuild the playlist */
288         rebuild();
289     }
290     return true;
291 }
292
293 /* remove item with its id */
294 void PLModel::removeItem( int i_id )
295 {
296     PLItem *item = FindById( rootItem, i_id );
297     RemoveItem( item );
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 = getItem( index );
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 = getItem( index );
354     if( role == Qt::DisplayRole )
355     {
356         if( i_depth == DEPTH_SEL )
357         {
358             vlc_mutex_lock( &item->p_input->lock );
359             QString returninfo = QString( qfu( item->p_input->psz_name ) );
360             vlc_mutex_unlock( &item->p_input->lock );
361             return QVariant(returninfo);
362         }
363
364         int metadata = metaColumn( index.column() );
365         if( metadata == COLUMN_END ) return QVariant();
366
367         QString returninfo;
368         if( metadata == COLUMN_NUMBER )
369             returninfo = QString::number( index.row() + 1 );
370         else
371         {
372             char *psz = psz_column_meta( item->p_input, metadata );
373             returninfo = qfu( psz );
374             free( psz );
375         }
376         return QVariant( returninfo );
377     }
378     else if( role == Qt::DecorationRole && index.column() == 0  )
379     {
380         /* Use to segfault here because i_type wasn't always initialized */
381         if( item->p_input->i_type >= 0 )
382             return QVariant( PLModel::icons[item->p_input->i_type] );
383     }
384     else if( role == Qt::FontRole )
385     {
386         if( isCurrent( index ) )
387         {
388             QFont f; f.setBold( true ); return QVariant( f );
389         }
390     }
391     return QVariant();
392 }
393
394 bool PLModel::isCurrent( const QModelIndex &index ) const
395 {
396     if( !currentItem ) return false;
397     return getItem( index )->p_input == currentItem->p_input;
398 }
399
400 int PLModel::itemId( const QModelIndex &index ) const
401 {
402     return getItem( index )->i_id;
403 }
404
405 QVariant PLModel::headerData( int section, Qt::Orientation orientation,
406                               int role ) const
407 {
408     if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
409         return QVariant();
410
411     if( i_depth == DEPTH_SEL ) return QVariant( QString("") );
412
413     int meta_col = metaColumn( section );
414
415     if( meta_col == COLUMN_END ) return QVariant();
416
417     return QVariant( qfu( psz_column_title( meta_col ) ) );
418 }
419
420 QModelIndex PLModel::index( int row, int column, const QModelIndex &parent )
421                   const
422 {
423     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
424
425     PLItem *childItem = parentItem->child( row );
426     if( childItem )
427         return createIndex( row, column, childItem );
428     else
429         return QModelIndex();
430 }
431
432 /* Return the index of a given item */
433 QModelIndex PLModel::index( PLItem *item, int column ) const
434 {
435     if( !item ) return QModelIndex();
436     const PLItem *parent = item->parent();
437     if( parent )
438         return createIndex( parent->children.lastIndexOf( item ),
439                             column, item );
440     return QModelIndex();
441 }
442
443 QModelIndex PLModel::parent( const QModelIndex &index ) const
444 {
445     if( !index.isValid() ) return QModelIndex();
446
447     PLItem *childItem = getItem( index );
448     if( !childItem )
449     {
450         msg_Err( p_playlist, "NULL CHILD" );
451         return QModelIndex();
452     }
453
454     PLItem *parentItem = childItem->parent();
455     if( !parentItem || parentItem == rootItem ) return QModelIndex();
456     if( !parentItem->parentItem )
457     {
458         msg_Err( p_playlist, "No parent parent, trying row 0 " );
459         msg_Err( p_playlist, "----- PLEASE REPORT THIS ------" );
460         return createIndex( 0, 0, parentItem );
461     }
462     QModelIndex ind = createIndex(parentItem->row(), 0, parentItem);
463     return ind;
464 }
465
466 int PLModel::columnCount( const QModelIndex &i) const
467 {
468     int columnCount=0;
469     int metadata=1;
470     if( i_depth == DEPTH_SEL ) return 1;
471
472     while( metadata < COLUMN_END )
473     {
474         if( metadata & i_showflags )
475             columnCount++;
476         metadata <<= 1;
477     }
478     return columnCount;
479 }
480
481 int PLModel::childrenCount( const QModelIndex &parent ) const
482 {
483     return rowCount( parent );
484 }
485
486 int PLModel::rowCount( const QModelIndex &parent ) const
487 {
488     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
489     return parentItem->childCount();
490 }
491
492 QStringList PLModel::selectedURIs()
493 {
494     QStringList lst;
495     for( int i = 0; i < current_selection.size(); i++ )
496     {
497         PLItem *item = getItem( current_selection[i] );
498         if( item )
499         {
500             PL_LOCK;
501             playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
502             if( p_item )
503             {
504                 char *psz = input_item_GetURI( p_item->p_input );
505                 if( psz )
506                 {
507                     lst.append( psz );
508                     free( psz );
509                 }
510             }
511             PL_UNLOCK;
512         }
513     }
514     return lst;
515 }
516
517 /************************* General playlist status ***********************/
518
519 bool PLModel::hasRandom()
520 {
521     return var_GetBool( p_playlist, "random" );
522 }
523 bool PLModel::hasRepeat()
524 {
525     return var_GetBool( p_playlist, "repeat" );
526 }
527 bool PLModel::hasLoop()
528 {
529     return var_GetBool( p_playlist, "loop" );
530 }
531 void PLModel::setLoop( bool on )
532 {
533     var_SetBool( p_playlist, "loop", on ? true:false );
534     config_PutInt( p_playlist, "loop", on ? 1: 0 );
535 }
536 void PLModel::setRepeat( bool on )
537 {
538     var_SetBool( p_playlist, "repeat", on ? true:false );
539     config_PutInt( p_playlist, "repeat", on ? 1: 0 );
540 }
541 void PLModel::setRandom( bool on )
542 {
543     var_SetBool( p_playlist, "random", on ? true:false );
544     config_PutInt( p_playlist, "random", on ? 1: 0 );
545 }
546
547 /************************* Lookups *****************************/
548
549 PLItem *PLModel::FindById( PLItem *root, int i_id )
550 {
551     return FindInner( root, i_id, false );
552 }
553
554 PLItem *PLModel::FindByInput( PLItem *root, int i_id )
555 {
556     PLItem *result = FindInner( root, i_id, true );
557     return result;
558 }
559
560 #define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
561 #define ICACHE( i, p ) { i_cached_input_id = i; p_cached_item_bi = p; }
562
563 PLItem * PLModel::FindInner( PLItem *root, int i_id, bool b_input )
564 {
565     if( ( !b_input && i_cached_id == i_id) ||
566         ( b_input && i_cached_input_id ==i_id ) )
567     {
568         return b_input ? p_cached_item_bi : p_cached_item;
569     }
570
571     if( !b_input && root->i_id == i_id )
572     {
573         CACHE( i_id, root );
574         return root;
575     }
576     else if( b_input && root->p_input->i_id == i_id )
577     {
578         ICACHE( i_id, root );
579         return root;
580     }
581
582     QList<PLItem *>::iterator it = root->children.begin();
583     while ( it != root->children.end() )
584     {
585         if( !b_input && (*it)->i_id == i_id )
586         {
587             CACHE( i_id, (*it) );
588             return p_cached_item;
589         }
590         else if( b_input && (*it)->p_input->i_id == i_id )
591         {
592             ICACHE( i_id, (*it) );
593             return p_cached_item_bi;
594         }
595         if( (*it)->children.size() )
596         {
597             PLItem *childFound = FindInner( (*it), i_id, b_input );
598             if( childFound )
599             {
600                 if( b_input )
601                     ICACHE( i_id, childFound )
602                 else
603                     CACHE( i_id, childFound )
604                 return childFound;
605             }
606         }
607         it++;
608     }
609     return NULL;
610 }
611 #undef CACHE
612 #undef ICACHE
613
614 PLItem *PLModel::getItem( const QModelIndex& index ) const
615 {
616     assert( index.isValid() );
617     return static_cast<PLItem*>( index.internalPointer() );
618 }
619
620 /* computes column id of meta data from visible column index */
621 int PLModel::metaColumn( int column ) const
622 {
623     int metadata = 1;
624     int running_index = -1;
625
626     while( metadata < COLUMN_END )
627     {
628         if( metadata & i_showflags )
629             running_index++;
630         if( running_index == column )
631             break;
632         metadata <<= 1;
633     }
634
635     if( running_index != column ) return COLUMN_END;
636     return metadata;
637 }
638
639 /************************* Updates handling *****************************/
640 void PLModel::customEvent( QEvent *event )
641 {
642     int type = event->type();
643     if( type != ItemAppend_Type &&
644         type != ItemDelete_Type && type != PLUpdate_Type )
645         return;
646
647     PLEvent *ple = static_cast<PLEvent *>(event);
648
649     if( type == ItemAppend_Type )
650         ProcessItemAppend( &ple->add );
651     else if( type == ItemDelete_Type )
652         ProcessItemRemoval( ple->i_id );
653     else
654         rebuild();
655 }
656
657 /**** Events processing ****/
658 void PLModel::ProcessInputItemUpdate( input_thread_t *p_input )
659 {
660     if( !p_input ) return;
661     ProcessInputItemUpdate( input_GetItem( p_input ) );
662     if( p_input && !( p_input->b_dead || !vlc_object_alive( p_input ) ) )
663     {
664         PLItem *item = FindByInput( rootItem, input_GetItem( p_input )->i_id );
665         currentItem = item;
666         emit currentChanged( index( item, 0 ) );
667     }
668     else
669     {
670         currentItem = NULL;
671     }
672 }
673 void PLModel::ProcessInputItemUpdate( input_item_t *p_item )
674 {
675     if( !p_item ||  p_item->i_id <= 0 ) return;
676     PLItem *item = FindByInput( rootItem, p_item->i_id );
677     if( item )
678         UpdateTreeItem( item, true, true);
679 }
680
681 void PLModel::ProcessItemRemoval( int i_id )
682 {
683     if( i_id <= 0 ) return;
684     if( i_id == i_cached_id ) i_cached_id = -1;
685     i_cached_input_id = -1;
686
687     removeItem( i_id );
688 }
689
690 void PLModel::ProcessItemAppend( const playlist_add_t *p_add )
691 {
692     playlist_item_t *p_item = NULL;
693     PLItem *newItem = NULL;
694
695     PLItem *nodeItem = FindById( rootItem, p_add->i_node );
696     if( !nodeItem ) return;
697
698     PL_LOCK;
699     p_item = playlist_ItemGetById( p_playlist, p_add->i_item );
700     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG ) goto end;
701     if( i_depth == DEPTH_SEL && p_item->p_parent &&
702                         p_item->p_parent->i_id != rootItem->i_id )
703         goto end;
704
705     newItem = new PLItem( p_item, nodeItem );
706     PL_UNLOCK;
707
708     beginInsertRows( index( nodeItem, 0 ), nodeItem->childCount(), nodeItem->childCount() );
709     nodeItem->appendChild( newItem );
710     endInsertRows();
711     UpdateTreeItem( newItem, true );
712     return;
713 end:
714     PL_UNLOCK;
715     return;
716 }
717
718
719 void PLModel::rebuild()
720 {
721     rebuild( NULL );
722 }
723
724 void PLModel::rebuild( playlist_item_t *p_root )
725 {
726     playlist_item_t* p_item;
727     /* Remove callbacks before locking to avoid deadlocks */
728     delCallbacks();
729     /* Invalidate cache */
730     i_cached_id = i_cached_input_id = -1;
731
732     if( rootItem ) RemoveChildren( rootItem );
733
734     PL_LOCK;
735     if( p_root )
736     {
737         delete rootItem;
738         rootItem = new PLItem( p_root );
739     }
740     assert( rootItem );
741     /* Recreate from root */
742     UpdateChildren( rootItem );
743     if( (p_item = playlist_CurrentPlayingItem(p_playlist)) )
744         currentItem = FindByInput( rootItem, p_item->p_input->i_id );
745     else
746         currentItem = NULL;
747     PL_UNLOCK;
748
749     /* And signal the view */
750     reset();
751     emit currentChanged( index( currentItem, 0 ) );
752
753     addCallbacks();
754 }
755
756 void PLModel::RemoveItem( PLItem *item )
757 {
758     if( !item ) return;
759     if( currentItem && currentItem->p_input == item->p_input )
760     {
761         currentItem = NULL;
762         emit currentChanged( QModelIndex() );
763     }
764     PLItem *parent = item->parentItem;
765     assert( parent );
766     int i_index = parent->children.indexOf( item );
767     parent->children.removeAt( i_index );
768     delete item;
769 }
770 void PLModel::RemoveChildren( PLItem *root )
771 {
772   if( root->children.size() )
773   {
774       qDeleteAll( root->children );
775       root->children.clear();
776   }
777 }
778
779 /* This function must be entered WITH the playlist lock */
780 void PLModel::UpdateChildren( PLItem *root )
781 {
782     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
783     UpdateChildren( p_node, root );
784 }
785
786 /* This function must be entered WITH the playlist lock */
787 void PLModel::UpdateChildren( playlist_item_t *p_node, PLItem *root )
788 {
789     for( int i = 0; i < p_node->i_children ; i++ )
790     {
791         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
792         PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
793         root->appendChild( newItem );
794         if( i_depth == DEPTH_PL && p_node->pp_children[i]->i_children != -1 )
795             UpdateChildren( p_node->pp_children[i], newItem );
796     }
797 }
798
799 /* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
800 void PLModel::UpdateTreeItem( PLItem *item, bool signal, bool force )
801 {
802     if ( !item || !item->p_input )
803         return;
804     if( !force && i_depth == DEPTH_SEL && item->parentItem &&
805                                  item->parentItem->p_input != rootItem->p_input )
806         return;
807     if( signal )
808         emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) ) );
809 }
810
811 /************************* Actions ******************************/
812
813 /**
814  * Deletion, here we have to do a ugly slow hack as we retrieve the full
815  * list of indexes to delete at once: when we delete a node and all of
816  * its children, we need to update the list.
817  * Todo: investigate whethere we can use ranges to be sure to delete all items?
818  */
819 void PLModel::doDelete( QModelIndexList selected )
820 {
821     for( int i = selected.size() -1 ; i >= 0; i-- )
822     {
823         QModelIndex index = selected[i];
824         if( index.column() != 0 ) continue;
825         PLItem *item = getItem( index );
826         if( item )
827         {
828             if( item->children.size() )
829                 recurseDelete( item->children, &selected );
830             doDeleteItem( item, &selected );
831         }
832         if( i > selected.size() ) i = selected.size();
833     }
834 }
835
836 void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
837 {
838     for( int i = children.size() - 1; i >= 0 ; i-- )
839     {
840         PLItem *item = children[i];
841         if( item->children.size() )
842             recurseDelete( item->children, fullList );
843         doDeleteItem( item, fullList );
844     }
845 }
846
847 void PLModel::doDeleteItem( PLItem *item, QModelIndexList *fullList )
848 {
849     QModelIndex deleteIndex = index( item, 0 );
850     fullList->removeAll( deleteIndex );
851
852     PL_LOCK;
853     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
854     if( !p_item )
855     {
856         PL_UNLOCK;
857         return;
858     }
859     if( p_item->i_children == -1 )
860         playlist_DeleteFromInput( p_playlist, p_item->p_input, pl_Locked );
861     else
862         playlist_NodeDelete( p_playlist, p_item, true, false );
863     PL_UNLOCK;
864     /* And finally, remove it from the tree */
865     int itemIndex = item->parentItem->children.indexOf( item );
866     beginRemoveRows( index( item->parentItem, 0), itemIndex, itemIndex );
867     RemoveItem( item );
868     endRemoveRows();
869 }
870
871 /******* Volume III: Sorting and searching ********/
872 void PLModel::sort( int column, Qt::SortOrder order )
873 {
874     sort( rootItem->i_id, column, order );
875 }
876
877 void PLModel::sort( int i_root_id, int column, Qt::SortOrder order )
878 {
879     int i_index = -1;
880     int i_flag = 0;
881
882     int i_column = 1;
883     for( i_column = 1; i_column != COLUMN_END; i_column<<=1 )
884     {
885         if( ( shownFlags() & i_column ) )
886             i_index++;
887         if( column == i_index )
888         {
889             i_flag = i_column;
890             break;
891         }
892     }
893
894     PLItem *item = FindById( rootItem, i_root_id );
895     if( !item ) return;
896     QModelIndex qIndex = index( item, 0 );
897     int count = item->children.size();
898     if( count )
899     {
900         beginRemoveRows( qIndex, 0, count - 1 );
901         RemoveChildren( item );
902         endRemoveRows( );
903     }
904
905     PL_LOCK;
906     {
907         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
908                                                         i_root_id );
909         if( p_root && i_flag )
910         {
911             playlist_RecursiveNodeSort( p_playlist, p_root,
912                                         i_column_sorting( i_flag ),
913                                         order == Qt::AscendingOrder ?
914                                             ORDER_NORMAL : ORDER_REVERSE );
915         }
916     }
917     if( count )
918     {
919         beginInsertRows( qIndex, 0, count - 1 );
920         UpdateChildren( item );
921         endInsertRows( );
922     }
923     PL_UNLOCK;
924 }
925
926 void PLModel::search( const QString& search_text )
927 {
928     /** \todo Fire the search with a small delay ? */
929     PL_LOCK;
930     {
931         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
932                                                         rootItem->i_id );
933         assert( p_root );
934         const char *psz_name = search_text.toUtf8().data();
935         playlist_LiveSearchUpdate( p_playlist , p_root, psz_name );
936     }
937     PL_UNLOCK;
938     rebuild();
939 }
940
941 /*********** Popup *********/
942 void PLModel::popup( QModelIndex & index, QPoint &point, QModelIndexList list )
943 {
944     int i_id = index.isValid() ? itemId( index ) : rootItem->i_id;
945
946     PL_LOCK;
947     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
948     if( !p_item )
949     {
950         PL_UNLOCK; return;
951     }
952     i_popup_item = index.isValid() ? p_item->i_id : -1;
953     i_popup_parent = index.isValid() ?
954         ( p_item->p_parent ? p_item->p_parent->i_id : -1 ) :
955         ( p_item->i_id );
956     i_popup_column = index.column();
957     /* check whether we are in tree view */
958     bool tree = false;
959     playlist_item_t *p_up = p_item;
960     while( p_up )
961     {
962         if ( p_up == p_playlist->p_root_category ) tree = true;
963         p_up = p_up->p_parent;
964     }
965     PL_UNLOCK;
966
967     current_selection = list;
968     QMenu *menu = new QMenu;
969     if( i_popup_item > -1 )
970     {
971         menu->addAction( qtr(I_POP_PLAY), this, SLOT( popupPlay() ) );
972         menu->addAction( qtr(I_POP_DEL), this, SLOT( popupDel() ) );
973         menu->addSeparator();
974         menu->addAction( qtr(I_POP_STREAM), this, SLOT( popupStream() ) );
975         menu->addAction( qtr(I_POP_SAVE), this, SLOT( popupSave() ) );
976         menu->addSeparator();
977         menu->addAction( qtr(I_POP_INFO), this, SLOT( popupInfo() ) );
978         menu->addSeparator();
979         QMenu *sort_menu = menu->addMenu( qtr( "Sort by ") +
980             qfu( psz_column_title( metaColumn( index.column() ) ) ) );
981         sort_menu->addAction( qtr( "Ascending" ),
982             this, SLOT( popupSortAsc() ) );
983         sort_menu->addAction( qtr( "Descending" ),
984             this, SLOT( popupSortDesc() ) );
985     }
986     if( tree )
987         menu->addAction( qtr(I_POP_ADD), this, SLOT( popupAddNode() ) );
988     if( i_popup_item > -1 )
989     {
990         menu->addSeparator();
991         menu->addAction( qtr( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
992     }
993     menu->popup( point );
994 }
995
996
997 void PLModel::viewchanged( int meta )
998 {
999     assert( meta );
1000     int _meta = meta;
1001     if( rootItem )
1002     {
1003         int index=-1;
1004         while( _meta )
1005         {
1006             index++;
1007             _meta >>= 1;
1008         }
1009
1010         index = __MIN( index, columnCount() );
1011         QModelIndex parent = createIndex( 0, 0, rootItem );
1012
1013         if( i_showflags & meta )
1014             /* Removing columns */
1015         {
1016             beginRemoveColumns( parent, index, index+1 );
1017             i_showflags &= ~( meta );
1018             getSettings()->setValue( "qt-pl-showflags", i_showflags );
1019             endRemoveColumns();
1020         }
1021         else
1022         {
1023             /* Adding columns */
1024             beginInsertColumns( parent, index, index+1 );
1025             i_showflags |= meta;
1026             getSettings()->setValue( "qt-pl-showflags", i_showflags );
1027             endInsertColumns();
1028         }
1029
1030         emit columnsChanged( meta );
1031     }
1032 }
1033
1034 void PLModel::popupDel()
1035 {
1036     doDelete( current_selection );
1037 }
1038
1039 void PLModel::popupPlay()
1040 {
1041     PL_LOCK;
1042     {
1043         playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1044                                                         i_popup_item );
1045         activateItem( p_item );
1046     }
1047     PL_UNLOCK;
1048 }
1049
1050 void PLModel::popupInfo()
1051 {
1052     PL_LOCK;
1053     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1054                                                     i_popup_item );
1055     if( p_item )
1056     {
1057         input_item_t* p_input = p_item->p_input;
1058         vlc_gc_incref( p_input );
1059         PL_UNLOCK;
1060         MediaInfoDialog *mid = new MediaInfoDialog( p_intf, p_input );
1061         vlc_gc_decref( p_input );
1062         mid->setParent( PlaylistDialog::getInstance( p_intf ),
1063                         Qt::Dialog );
1064         mid->show();
1065     } else
1066         PL_UNLOCK;
1067 }
1068
1069 void PLModel::popupStream()
1070 {
1071     QStringList mrls = selectedURIs();
1072     if( !mrls.isEmpty() )
1073         THEDP->streamingDialog( NULL, mrls[0], false );
1074
1075 }
1076
1077 void PLModel::popupSave()
1078 {
1079     QStringList mrls = selectedURIs();
1080     if( !mrls.isEmpty() )
1081         THEDP->streamingDialog( NULL, mrls[0] );
1082 }
1083
1084 #include <QUrl>
1085 #include <QFileInfo>
1086 #include <QDesktopServices>
1087 void PLModel::popupExplore()
1088 {
1089     PL_LOCK;
1090     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1091                                                     i_popup_item );
1092     if( p_item )
1093     {
1094        input_item_t *p_input = p_item->p_input;
1095        char *psz_meta = input_item_GetURI( p_input );
1096        PL_UNLOCK;
1097        if( psz_meta )
1098        {
1099            const char *psz_access;
1100            const char *psz_demux;
1101            char  *psz_path;
1102            input_SplitMRL( &psz_access, &psz_demux, &psz_path, psz_meta );
1103
1104            if( EMPTY_STR( psz_access ) ||
1105                !strncasecmp( psz_access, "file", 4 ) ||
1106                !strncasecmp( psz_access, "dire", 4 ) )
1107            {
1108                QFileInfo info( qfu( psz_meta ) );
1109                QDesktopServices::openUrl(
1110                                QUrl::fromLocalFile( info.absolutePath() ) );
1111            }
1112            free( psz_meta );
1113        }
1114     }
1115     else
1116         PL_UNLOCK;
1117 }
1118
1119 #include <QInputDialog>
1120 void PLModel::popupAddNode()
1121 {
1122     bool ok;
1123     QString name = QInputDialog::getText( PlaylistDialog::getInstance( p_intf ),
1124         qtr( I_POP_ADD ), qtr( "Enter name for new node:" ),
1125         QLineEdit::Normal, QString(), &ok);
1126     if( !ok || name.isEmpty() ) return;
1127     PL_LOCK;
1128     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1129                                                     i_popup_parent );
1130     if( p_item )
1131     {
1132         playlist_NodeCreate( p_playlist, qtu( name ), p_item, 0, NULL );
1133     }
1134     PL_UNLOCK;
1135 }
1136
1137 void PLModel::popupSortAsc()
1138 {
1139     sort( i_popup_parent, i_popup_column, Qt::AscendingOrder );
1140 }
1141
1142 void PLModel::popupSortDesc()
1143 {
1144     sort( i_popup_parent, i_popup_column, Qt::DescendingOrder );
1145 }
1146 /**********************************************************************
1147  * Playlist callbacks
1148  **********************************************************************/
1149 static int PlaylistChanged( vlc_object_t *p_this, const char *psz_variable,
1150                             vlc_value_t oval, vlc_value_t nval, void *param )
1151 {
1152     PLModel *p_model = (PLModel *) param;
1153     PLEvent *event = new PLEvent( PLUpdate_Type, 0 );
1154     QApplication::postEvent( p_model, event );
1155     return VLC_SUCCESS;
1156 }
1157
1158 static int PlaylistNext( vlc_object_t *p_this, const char *psz_variable,
1159                          vlc_value_t oval, vlc_value_t nval, void *param )
1160 {
1161     PLModel *p_model = (PLModel *) param;
1162     PLEvent *event = new PLEvent( ItemUpdate_Type, oval.i_int );
1163     QApplication::postEvent( p_model, event );
1164     event = new PLEvent( ItemUpdate_Type, nval.i_int );
1165     QApplication::postEvent( p_model, event );
1166     return VLC_SUCCESS;
1167 }
1168
1169 static int ItemDeleted( vlc_object_t *p_this, const char *psz_variable,
1170                         vlc_value_t oval, vlc_value_t nval, void *param )
1171 {
1172     PLModel *p_model = (PLModel *) param;
1173     PLEvent *event = new PLEvent( ItemDelete_Type, nval.i_int );
1174     QApplication::postEvent( p_model, event );
1175     return VLC_SUCCESS;
1176 }
1177
1178 static int ItemAppended( vlc_object_t *p_this, const char *psz_variable,
1179                          vlc_value_t oval, vlc_value_t nval, void *param )
1180 {
1181     PLModel *p_model = (PLModel *) param;
1182     const playlist_add_t *p_add = (playlist_add_t *)nval.p_address;
1183     PLEvent *event = new PLEvent( p_add );
1184     QApplication::postEvent( p_model, event );
1185     return VLC_SUCCESS;
1186 }
1187