]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/playlist_model.cpp
3a9f550b1d2dc710ede59dbe578f4d6187d3b0ab
[vlc] / modules / gui / qt4 / components / playlist / playlist_model.cpp
1 /*****************************************************************************
2  * playlist_model.cpp : Manage playlist model
3  ****************************************************************************
4  * Copyright (C) 2006-2011 the VideoLAN team
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *          Ilkka Ollakkka <ileoo (at) videolan dot org>
9  *          Jakob Leben <jleben@videolan.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include "qt4.hpp"
31 #include "components/playlist/playlist_model.hpp"
32 #include "dialogs_provider.hpp"                         /* THEDP */
33 #include "input_manager.hpp"                            /* THEMIM */
34 #include "dialogs/mediainfo.hpp"                        /* MediaInfo Dialog */
35 #include "dialogs/playlist.hpp"                         /* Playlist Dialog */
36
37 #include <vlc_intf_strings.h>                           /* I_DIR */
38
39 #include "pixmaps/types/type_unknown.xpm"
40 #include "sorting.h"
41
42 #include <assert.h>
43 #include <QIcon>
44 #include <QFont>
45 #include <QMenu>
46 #include <QUrl>
47 #include <QFileInfo>
48 #include <QDesktopServices>
49 #include <QInputDialog>
50 #include <QSignalMapper>
51
52 #define I_NEW_DIR \
53     I_DIR_OR_FOLDER( N_("Create Directory"), N_( "Create Folder" ) )
54 #define I_NEW_DIR_NAME \
55     I_DIR_OR_FOLDER( N_( "Enter name for new directory:" ), \
56                      N_( "Enter name for new folder:" ) )
57
58 QIcon PLModel::icons[ITEM_TYPE_NUMBER];
59
60 /*************************************************************************
61  * Playlist model implementation
62  *************************************************************************/
63
64 PLModel::PLModel( playlist_t *_p_playlist,  /* THEPL */
65                   intf_thread_t *_p_intf,   /* main Qt p_intf */
66                   playlist_item_t * p_root,
67                   QObject *parent )         /* Basic Qt parent */
68                   : VLCModel( _p_intf, parent )
69 {
70     p_playlist        = _p_playlist;
71     i_cached_id       = -1;
72     i_cached_input_id = -1;
73     i_popup_item      = i_popup_parent = -1;
74     sortingMenu       = NULL;
75
76     rootItem          = NULL; /* PLItem rootItem, will be set in rebuild( ) */
77     latestSearch      = QString();
78
79     /* Icons initialization */
80 #define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( x )
81     ADD_ICON( UNKNOWN , type_unknown_xpm );
82     ADD_ICON( FILE, ":/type/file" );
83     ADD_ICON( DIRECTORY, ":/type/directory" );
84     ADD_ICON( DISC, ":/type/disc" );
85     ADD_ICON( CDDA, ":/type/cdda" );
86     ADD_ICON( CARD, ":/type/capture-card" );
87     ADD_ICON( NET, ":/type/net" );
88     ADD_ICON( PLAYLIST, ":/type/playlist" );
89     ADD_ICON( NODE, ":/type/node" );
90 #undef ADD_ICON
91
92     i_zoom = getSettings()->value( "Playlist/zoom", 0 ).toInt();
93
94     rebuild( p_root );
95     DCONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
96               this, processInputItemUpdate( input_item_t *) );
97     DCONNECT( THEMIM, inputChanged( input_thread_t * ),
98               this, processInputItemUpdate( input_thread_t* ) );
99     CONNECT( THEMIM, playlistItemAppended( int, int ),
100              this, processItemAppend( int, int ) );
101     CONNECT( THEMIM, playlistItemRemoved( int ),
102              this, processItemRemoval( int ) );
103 }
104
105 PLModel::~PLModel()
106 {
107     getSettings()->setValue( "Playlist/zoom", i_zoom );
108     delete rootItem;
109     delete sortingMenu;
110 }
111
112 Qt::DropActions PLModel::supportedDropActions() const
113 {
114     return Qt::CopyAction | Qt::MoveAction;
115 }
116
117 Qt::ItemFlags PLModel::flags( const QModelIndex &index ) const
118 {
119     Qt::ItemFlags flags = QAbstractItemModel::flags( index );
120
121     const PLItem *item = index.isValid() ? getItem( index ) : rootItem;
122
123     if( canEdit() )
124     {
125         PL_LOCK;
126         playlist_item_t *plItem =
127             playlist_ItemGetById( p_playlist, item->i_id );
128
129         if ( plItem && ( plItem->i_children > -1 ) )
130             flags |= Qt::ItemIsDropEnabled;
131
132         PL_UNLOCK;
133
134     }
135     flags |= Qt::ItemIsDragEnabled;
136
137     return flags;
138 }
139
140 QStringList PLModel::mimeTypes() const
141 {
142     QStringList types;
143     types << "vlc/qt-input-items";
144     return types;
145 }
146
147 bool modelIndexLessThen( const QModelIndex &i1, const QModelIndex &i2 )
148 {
149     if( !i1.isValid() || !i2.isValid() ) return false;
150     PLItem *item1 = static_cast<PLItem*>( i1.internalPointer() );
151     PLItem *item2 = static_cast<PLItem*>( i2.internalPointer() );
152     if( item1->parent() == item2->parent() ) return i1.row() < i2.row();
153     else return *item1 < *item2;
154 }
155
156 QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
157 {
158     PlMimeData *plMimeData = new PlMimeData();
159     QModelIndexList list;
160
161     foreach( const QModelIndex &index, indexes ) {
162         if( index.isValid() && index.column() == 0 )
163             list.append(index);
164     }
165
166     qSort(list.begin(), list.end(), modelIndexLessThen);
167
168     PLItem *item = NULL;
169     foreach( const QModelIndex &index, list ) {
170         if( item )
171         {
172             PLItem *testee = getItem( index );
173             while( testee->parent() )
174             {
175                 if( testee->parent() == item ||
176                     testee->parent() == item->parent() ) break;
177                 testee = testee->parent();
178             }
179             if( testee->parent() == item ) continue;
180             item = getItem( index );
181         }
182         else
183             item = getItem( index );
184
185         plMimeData->appendItem( item->inputItem() );
186     }
187
188     return plMimeData;
189 }
190
191 /* Drop operation */
192 bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
193         int row, int, const QModelIndex &parent )
194 {
195     bool copy = action == Qt::CopyAction;
196     if( !copy && action != Qt::MoveAction )
197         return true;
198
199     const PlMimeData *plMimeData = qobject_cast<const PlMimeData*>( data );
200     if( plMimeData )
201     {
202         if( copy )
203             dropAppendCopy( plMimeData, getItem( parent ), row );
204         else
205             dropMove( plMimeData, getItem( parent ), row );
206     }
207     return true;
208 }
209
210 void PLModel::dropAppendCopy( const PlMimeData *plMimeData, PLItem *target, int pos )
211 {
212     PL_LOCK;
213
214     playlist_item_t *p_parent =
215         playlist_ItemGetByInput( p_playlist, target->inputItem() );
216     if( !p_parent ) return;
217
218     if( pos == -1 ) pos = PLAYLIST_END;
219
220     QList<input_item_t*> inputItems = plMimeData->inputItems();
221
222     foreach( input_item_t* p_input, inputItems )
223     {
224         playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
225         if( !p_item ) continue;
226         pos = playlist_NodeAddCopy( p_playlist, p_item, p_parent, pos );
227     }
228
229     PL_UNLOCK;
230 }
231
232 void PLModel::dropMove( const PlMimeData * plMimeData, PLItem *target, int row )
233 {
234     QList<input_item_t*> inputItems = plMimeData->inputItems();
235     QList<PLItem*> model_items;
236     playlist_item_t **pp_items;
237     pp_items = (playlist_item_t **)
238                calloc( inputItems.count(), sizeof( playlist_item_t* ) );
239     if ( !pp_items ) return;
240
241     PL_LOCK;
242
243     playlist_item_t *p_parent =
244         playlist_ItemGetByInput( p_playlist, target->inputItem() );
245
246     if( !p_parent || row > p_parent->i_children )
247     {
248         PL_UNLOCK;
249         free( pp_items );
250         return;
251     }
252
253     int new_pos = row == -1 ? p_parent->i_children : row;
254     int model_pos = new_pos;
255     int i = 0;
256
257     foreach( input_item_t *p_input, inputItems )
258     {
259         playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
260         if( !p_item ) continue;
261
262         PLItem *item = findByInput( rootItem, p_input->i_id );
263         if( !item ) continue;
264
265         /* Better not try to move a node into itself.
266            Abort the whole operation in that case,
267            because it is ambiguous. */
268         PLItem *climber = target;
269         while( climber )
270         {
271             if( climber == item )
272             {
273                 PL_UNLOCK;
274                 free( pp_items );
275                 return;
276             }
277             climber = climber->parent();
278         }
279
280         if( item->parent() == target &&
281             target->children.indexOf( item ) < new_pos )
282             model_pos--;
283
284         model_items.append( item );
285         pp_items[i] = p_item;
286         i++;
287     }
288
289     if( model_items.isEmpty() )
290     {
291         PL_UNLOCK;
292         free( pp_items );
293         return;
294     }
295
296     playlist_TreeMoveMany( p_playlist, i, pp_items, p_parent, new_pos );
297
298     PL_UNLOCK;
299
300     foreach( PLItem *item, model_items )
301         takeItem( item );
302
303     insertChildren( target, model_items, model_pos );
304     free( pp_items );
305 }
306
307 /* remove item with its id */
308 void PLModel::removeItem( int i_id )
309 {
310     PLItem *item = findById( rootItem, i_id );
311     removeItem( item );
312 }
313
314 void PLModel::activateItem( const QModelIndex &index )
315 {
316     assert( index.isValid() );
317     const PLItem *item = getItem( index );
318     assert( item );
319     PL_LOCK;
320     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
321     activateItem( p_item );
322     PL_UNLOCK;
323 }
324
325 /* Convenient overloaded private version of activateItem
326  * Must be entered with PL lock */
327 void PLModel::activateItem( playlist_item_t *p_item )
328 {
329     if( !p_item ) return;
330     playlist_item_t *p_parent = p_item;
331     while( p_parent )
332     {
333         if( p_parent->i_id == rootItem->id() ) break;
334         p_parent = p_parent->p_parent;
335     }
336     if( p_parent )
337         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked,
338                 p_parent, p_item );
339 }
340
341 /****************** Base model mandatory implementations *****************/
342 QVariant PLModel::data( const QModelIndex &index, const int role ) const
343 {
344     if( !index.isValid() ) return QVariant();
345     const PLItem *item = getItem( index );
346     if( role == Qt::DisplayRole )
347     {
348         int metadata = columnToMeta( index.column() );
349         if( metadata == COLUMN_END ) return QVariant();
350
351         QString returninfo;
352         if( metadata == COLUMN_NUMBER )
353             returninfo = QString::number( index.row() + 1 );
354         else if( metadata == COLUMN_COVER )
355         {
356             QString artUrl;
357             artUrl = InputManager::decodeArtURL( item->inputItem() );
358             if( artUrl.isEmpty() )
359             {
360                 for( int i = 0; i < item->childCount(); i++ )
361                 {
362                     artUrl = InputManager::decodeArtURL( item->child( i )->inputItem() );
363                     if( !artUrl.isEmpty() )
364                         break;
365                 }
366             }
367             return QVariant( artUrl );
368         }
369         else
370         {
371             char *psz = psz_column_meta( item->inputItem(), metadata );
372             returninfo = qfu( psz );
373             free( psz );
374         }
375         return QVariant( returninfo );
376     }
377     else if( role == Qt::DecorationRole && index.column() == 0  )
378     {
379         /* Used to segfault here because i_type wasn't always initialized */
380         return QVariant( PLModel::icons[item->inputItem()->i_type] );
381     }
382     else if( role == Qt::FontRole )
383     {
384         QFont f;
385         f.setPointSize( __MAX( f.pointSize() + i_zoom, 4 ) );
386         if( isCurrent( index ) )
387             f.setBold( true );
388         return QVariant( f );
389     }
390     else if( role == Qt::BackgroundRole && isCurrent( index ) )
391     {
392         return QVariant( QBrush( Qt::gray ) );
393     }
394     else if( role == IsCurrentRole )
395     {
396         return QVariant( isCurrent( index ) );
397     }
398     else if( role == IsLeafNodeRole )
399     {
400         QVariant isLeaf;
401         PL_LOCK;
402         playlist_item_t *plItem =
403             playlist_ItemGetById( p_playlist, item->i_id );
404
405         if( plItem )
406             isLeaf = plItem->i_children == -1;
407
408         PL_UNLOCK;
409         return isLeaf;
410     }
411     else if( role == IsCurrentsParentNodeRole )
412     {
413         return QVariant( isParent( index, currentIndex() ) );
414     }
415     return QVariant();
416 }
417
418 /* Seek from current index toward the top and see if index is one of parent nodes */
419 bool PLModel::isParent( const QModelIndex &index, const QModelIndex &current ) const
420 {
421     if( !index.isValid() )
422         return false;
423
424     if( index == current )
425         return true;
426
427     if( !current.isValid() || !current.parent().isValid() )
428         return false;
429
430     return isParent( index, current.parent() );
431 }
432
433 bool PLModel::isCurrent( const QModelIndex &index ) const
434 {
435     return getItem( index )->inputItem() == THEMIM->currentInputItem();
436 }
437
438 int PLModel::itemId( const QModelIndex &index ) const
439 {
440     return getItem( index )->id();
441 }
442
443 QVariant PLModel::headerData( int section, Qt::Orientation orientation,
444                               int role ) const
445 {
446     if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
447         return QVariant();
448
449     int meta_col = columnToMeta( section );
450
451     if( meta_col == COLUMN_END ) return QVariant();
452
453     return QVariant( qfu( psz_column_title( meta_col ) ) );
454 }
455
456 QModelIndex PLModel::index( const int row, const int column, const QModelIndex &parent )
457                   const
458 {
459     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
460
461     PLItem *childItem = parentItem->child( row );
462     if( childItem )
463         return createIndex( row, column, childItem );
464     else
465         return QModelIndex();
466 }
467
468 QModelIndex PLModel::index( const int i_id, const int c )
469 {
470     return index( findById( rootItem, i_id ), c );
471 }
472
473 /* Return the index of a given item */
474 QModelIndex PLModel::index( PLItem *item, int column ) const
475 {
476     if( !item ) return QModelIndex();
477     const PLItem *parent = item->parent();
478     if( parent )
479         return createIndex( parent->children.lastIndexOf( item ),
480                             column, item );
481     return QModelIndex();
482 }
483
484 QModelIndex PLModel::currentIndex() const
485 {
486     input_thread_t *p_input_thread = THEMIM->getInput();
487     if( !p_input_thread ) return QModelIndex();
488     PLItem *item = findByInput( rootItem, input_GetItem( p_input_thread )->i_id );
489     return index( item, 0 );
490 }
491
492 QModelIndex PLModel::parent( const QModelIndex &index ) const
493 {
494     if( !index.isValid() ) return QModelIndex();
495
496     PLItem *childItem = getItem( index );
497     if( !childItem )
498     {
499         msg_Err( p_playlist, "Item not found" );
500         return QModelIndex();
501     }
502
503     PLItem *parentItem = childItem->parent();
504     if( !parentItem || parentItem == rootItem ) return QModelIndex();
505     if( !parentItem->parent() )
506     {
507         msg_Err( p_playlist, "No parent found, trying row 0. Please report this" );
508         return createIndex( 0, 0, parentItem );
509     }
510     return createIndex(parentItem->row(), 0, parentItem);
511 }
512
513 int PLModel::columnCount( const QModelIndex &) const
514 {
515     return columnFromMeta( COLUMN_END );
516 }
517
518 int PLModel::rowCount( const QModelIndex &parent ) const
519 {
520     const PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
521     return parentItem->childCount();
522 }
523
524 QStringList PLModel::selectedURIs()
525 {
526     QStringList lst;
527     for( int i = 0; i < current_selection.count(); i++ )
528     {
529         const PLItem *item = getItem( current_selection[i] );
530         if( item )
531         {
532             PL_LOCK;
533             playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
534             if( p_item )
535             {
536                 char *psz = input_item_GetURI( p_item->p_input );
537                 if( psz )
538                 {
539                     lst.append( qfu(psz) );
540                     free( psz );
541                 }
542             }
543             PL_UNLOCK;
544         }
545     }
546     return lst;
547 }
548
549 /************************* Lookups *****************************/
550 PLItem *PLModel::findById( PLItem *root, int i_id ) const
551 {
552     return findInner( root, i_id, false );
553 }
554
555 PLItem *PLModel::findByInput( PLItem *root, int i_id ) const
556 {
557     PLItem *result = findInner( root, i_id, true );
558     return result;
559 }
560
561 PLItem * PLModel::findInner( PLItem *root, int i_id, bool b_input ) const
562 {
563     if( !root ) return NULL;
564
565     if( !b_input && root->id() == i_id )
566         return root;
567
568     else if( b_input && root->inputItem()->i_id == i_id )
569         return root;
570
571     QList<PLItem *>::iterator it = root->children.begin();
572     while ( it != root->children.end() )
573     {
574         if( !b_input && (*it)->id() == i_id )
575             return (*it);
576
577         else if( b_input && (*it)->inputItem()->i_id == i_id )
578             return (*it);
579
580         if( (*it)->childCount() )
581         {
582             PLItem *childFound = findInner( (*it), i_id, b_input );
583             if( childFound )
584                 return childFound;
585         }
586         ++it;
587     }
588     return NULL;
589 }
590
591 bool PLModel::canEdit() const
592 {
593     return (
594             rootItem != NULL &&
595             (
596              rootItem->inputItem() == p_playlist->p_playing->p_input ||
597              ( p_playlist->p_media_library &&
598               rootItem->inputItem() == p_playlist->p_media_library->p_input )
599             )
600            );
601 }
602
603 /************************* Updates handling *****************************/
604
605 /**** Events processing ****/
606 void PLModel::processInputItemUpdate( input_thread_t *p_input )
607 {
608     if( !p_input ) return;
609     if( p_input && !( p_input->b_dead || !vlc_object_alive( p_input ) ) )
610     {
611         PLItem *item = findByInput( rootItem, input_GetItem( p_input )->i_id );
612         if( item ) emit currentIndexChanged( index( item, 0 ) );
613     }
614     processInputItemUpdate( input_GetItem( p_input ) );
615 }
616
617 void PLModel::processInputItemUpdate( input_item_t *p_item )
618 {
619     if( !p_item ||  p_item->i_id <= 0 ) return;
620     PLItem *item = findByInput( rootItem, p_item->i_id );
621     if( item )
622         updateTreeItem( item );
623 }
624
625 void PLModel::processItemRemoval( int i_id )
626 {
627     if( i_id <= 0 ) return;
628     removeItem( i_id );
629 }
630
631 void PLModel::processItemAppend( int i_item, int i_parent )
632 {
633     playlist_item_t *p_item = NULL;
634     PLItem *newItem = NULL;
635     int pos;
636
637     /* Find the Parent */
638     PLItem *nodeParentItem = findById( rootItem, i_parent );
639     if( !nodeParentItem ) return;
640
641     /* Search for an already matching children */
642     foreach( const PLItem *existing, nodeParentItem->children )
643         if( existing->i_id == i_item ) return;
644
645     /* Find the child */
646     PL_LOCK;
647     p_item = playlist_ItemGetById( p_playlist, i_item );
648     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG )
649     {
650         PL_UNLOCK; return;
651     }
652
653     for( pos = p_item->p_parent->i_children - 1; pos >= 0; pos-- )
654         if( p_item->p_parent->pp_children[pos] == p_item ) break;
655
656     newItem = new PLItem( p_item, nodeParentItem );
657     PL_UNLOCK;
658
659     /* We insert the newItem (children) inside the parent */
660     beginInsertRows( index( nodeParentItem, 0 ), pos, pos );
661     nodeParentItem->insertChild( newItem, pos );
662     endInsertRows();
663
664     if( newItem->inputItem() == THEMIM->currentInputItem() )
665         emit currentIndexChanged( index( newItem, 0 ) );
666
667     if( latestSearch.isEmpty() ) return;
668     search( latestSearch, index( rootItem, 0), false /*FIXME*/ );
669 }
670
671 void PLModel::rebuild( playlist_item_t *p_root )
672 {
673     /* Invalidate cache */
674     i_cached_id = i_cached_input_id = -1;
675
676     if( rootItem ) rootItem->removeChildren();
677
678     PL_LOCK;
679     if( p_root ) // Can be NULL
680     {
681         delete rootItem;
682         rootItem = new PLItem( p_root );
683     }
684     assert( rootItem );
685     /* Recreate from root */
686     updateChildren( rootItem );
687     PL_UNLOCK;
688
689     /* And signal the view */
690     reset();
691     if( p_root ) emit rootIndexChanged();
692 }
693
694 void PLModel::takeItem( PLItem *item )
695 {
696     assert( item );
697     PLItem *parent = item->parent();
698     assert( parent );
699     int i_index = parent->children.indexOf( item );
700
701     beginRemoveRows( index( parent, 0 ), i_index, i_index );
702     parent->takeChildAt( i_index );
703     endRemoveRows();
704 }
705
706 void PLModel::insertChildren( PLItem *node, QList<PLItem*>& items, int i_pos )
707 {
708     assert( node );
709     int count = items.count();
710     if( !count ) return;
711     beginInsertRows( index( node, 0 ), i_pos, i_pos + count - 1 );
712     for( int i = 0; i < count; i++ )
713     {
714         node->children.insert( i_pos + i, items[i] );
715         items[i]->parentItem = node;
716     }
717     endInsertRows();
718 }
719
720 void PLModel::removeItem( PLItem *item )
721 {
722     if( !item ) return;
723
724     i_cached_id = -1;
725     i_cached_input_id = -1;
726
727     if( item->parent() ) {
728         int i = item->parent()->children.indexOf( item );
729         beginRemoveRows( index( item->parent(), 0), i, i );
730         item->parent()->children.removeAt(i);
731         delete item;
732         endRemoveRows();
733     }
734     else delete item;
735
736     if(item == rootItem)
737     {
738         rootItem = NULL;
739         rebuild( p_playlist->p_playing );
740     }
741 }
742
743 /* This function must be entered WITH the playlist lock */
744 void PLModel::updateChildren( PLItem *root )
745 {
746     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->id() );
747     updateChildren( p_node, root );
748 }
749
750 /* This function must be entered WITH the playlist lock */
751 void PLModel::updateChildren( playlist_item_t *p_node, PLItem *root )
752 {
753     for( int i = 0; i < p_node->i_children ; i++ )
754     {
755         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
756         PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
757         root->appendChild( newItem );
758         if( p_node->pp_children[i]->i_children != -1 )
759             updateChildren( p_node->pp_children[i], newItem );
760     }
761 }
762
763 /* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
764 void PLModel::updateTreeItem( PLItem *item )
765 {
766     if( !item ) return;
767     emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) ) );
768 }
769
770 /************************* Actions ******************************/
771
772 /**
773  * Deletion, don't delete items childrens if item is going to be
774  * delete allready, so we remove childrens from selection-list.
775  */
776 void PLModel::doDelete( QModelIndexList selected )
777 {
778     if( !canEdit() ) return;
779
780     while( !selected.isEmpty() )
781     {
782         QModelIndex index = selected[0];
783         selected.removeAt( 0 );
784
785         if( index.column() != 0 ) continue;
786
787         PLItem *item = getItem( index );
788         if( item->childCount() )
789             recurseDelete( item->children, &selected );
790
791         PL_LOCK;
792         playlist_DeleteFromInput( p_playlist, item->inputItem(), pl_Locked );
793         PL_UNLOCK;
794
795         removeItem( item );
796     }
797 }
798
799 void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
800 {
801     for( int i = children.count() - 1; i >= 0 ; i-- )
802     {
803         PLItem *item = children[i];
804         if( item->childCount() )
805             recurseDelete( item->children, fullList );
806         fullList->removeAll( index( item, 0 ) );
807     }
808 }
809
810 /******* Volume III: Sorting and searching ********/
811 void PLModel::sort( const int column, Qt::SortOrder order )
812 {
813     sort( rootItem->id(), column, order );
814 }
815
816 void PLModel::sort( const int i_root_id, const int column, Qt::SortOrder order )
817 {
818     msg_Dbg( p_intf, "Sorting by column %i, order %i", column, order );
819
820     int meta = columnToMeta( column );
821     if( meta == COLUMN_END ) return;
822
823     PLItem *item = findById( rootItem, i_root_id );
824     if( !item ) return;
825     QModelIndex qIndex = index( item, 0 );
826     int count = item->childCount();
827     if( count )
828     {
829         beginRemoveRows( qIndex, 0, count - 1 );
830         item->removeChildren();
831         endRemoveRows( );
832     }
833
834     PL_LOCK;
835     {
836         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
837                                                         i_root_id );
838         if( p_root )
839         {
840             playlist_RecursiveNodeSort( p_playlist, p_root,
841                                         i_column_sorting( meta ),
842                                         order == Qt::AscendingOrder ?
843                                             ORDER_NORMAL : ORDER_REVERSE );
844         }
845     }
846
847     i_cached_id = i_cached_input_id = -1;
848
849     if( count )
850     {
851         beginInsertRows( qIndex, 0, count - 1 );
852         updateChildren( item );
853         endInsertRows( );
854     }
855     PL_UNLOCK;
856     /* if we have popup item, try to make sure that you keep that item visible */
857     if( i_popup_item > -1 )
858     {
859         PLItem *popupitem = findById( rootItem, i_popup_item );
860         if( popupitem ) emit currentIndexChanged( index( popupitem, 0 ) );
861         /* reset i_popup_item as we don't show it as selected anymore anyway */
862         i_popup_item = -1;
863     }
864     else if( currentIndex().isValid() ) emit currentIndexChanged( currentIndex() );
865 }
866
867 void PLModel::search( const QString& search_text, const QModelIndex & idx, bool b_recursive )
868 {
869     latestSearch = search_text;
870
871     /** \todo Fire the search with a small delay ? */
872     PL_LOCK;
873     {
874         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
875                                                         itemId( idx ) );
876         assert( p_root );
877         playlist_LiveSearchUpdate( p_playlist, p_root, qtu( search_text ),
878                                    b_recursive );
879         if( idx.isValid() )
880         {
881             PLItem *searchRoot = getItem( idx );
882
883             beginRemoveRows( idx, 0, searchRoot->childCount() - 1 );
884             searchRoot->removeChildren();
885             endRemoveRows();
886
887             beginInsertRows( idx, 0, searchRoot->childCount() - 1 );
888             updateChildren( searchRoot ); // The PL_LOCK is needed here
889             endInsertRows();
890
891             PL_UNLOCK;
892             return;
893         }
894     }
895     PL_UNLOCK;
896     rebuild();
897 }
898
899 void PLModel::clearPlaylist()
900 {
901     if( rowCount() < 1 ) return;
902
903     QModelIndexList l;
904     for( int i = 0; i < rowCount(); i++)
905     {
906         QModelIndex indexrecord = index( i, 0, QModelIndex() );
907         l.append( indexrecord );
908     }
909     doDelete(l);
910 }
911
912 void PLModel::ensureArtRequested( const QModelIndex &index )
913 {
914     if ( index.isValid() && hasChildren( index ) )
915     {
916         int nbnodes = rowCount( index );
917         QModelIndex child;
918         for( int row = 0 ; row < nbnodes ; row++ )
919         {
920             child = index.child( row, 0 );
921             if ( child.isValid() && getArtUrl( child ).isEmpty() )
922                 THEMIM->getIM()->requestArtUpdate( getItem( child )->inputItem() );
923         }
924     }
925 }
926
927 /*********** Popup *********/
928 bool PLModel::popup( const QModelIndex & index, const QPoint &point, const QModelIndexList &list )
929 {
930     int i_id = index.isValid() ? itemId( index ) : rootItem->id();
931
932     PL_LOCK;
933     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
934     if( !p_item )
935     {
936         PL_UNLOCK;
937         return false;
938     }
939
940     i_popup_item   = index.isValid() ? p_item->i_id : -1;
941     i_popup_parent = index.isValid() ?
942         ( p_item->p_parent ? p_item->p_parent->i_id : -1 ) :
943         ( rootItem->id() );
944
945     bool tree = ( rootItem && rootItem->id() != p_playlist->p_playing->i_id ) ||
946                 var_InheritBool( p_intf, "playlist-tree" );
947
948     input_item_t *p_input = p_item->p_input;
949     vlc_gc_incref( p_input );
950     PL_UNLOCK;
951
952     /* */
953     QMenu menu;
954
955     /* Play/Stream/Info static actions */
956     if( i_popup_item > -1 )
957     {
958         menu.addAction( QIcon( ":/menu/play" ), qtr(I_POP_PLAY), this, SLOT( popupPlay() ) );
959         menu.addAction( QIcon( ":/menu/stream" ),
960                         qtr(I_POP_STREAM), this, SLOT( popupStream() ) );
961         menu.addAction( qtr(I_POP_SAVE), this, SLOT( popupSave() ) );
962         menu.addAction( QIcon( ":/menu/info" ), qtr(I_POP_INFO), this, SLOT( popupInfo() ) );
963         if( p_input->psz_uri && !strncasecmp( p_input->psz_uri, "file://", 7 ) )
964         {
965             menu.addAction( QIcon( ":/type/folder-grey" ),
966                             qtr( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
967         }
968         menu.addSeparator();
969     }
970     vlc_gc_decref( p_input );
971
972     /* In PL or ML, allow to add a file/folder */
973     if( canEdit() )
974     {
975         QIcon addIcon( ":/buttons/playlist/playlist_add" );
976         menu.addSeparator();
977         if( tree ) menu.addAction( addIcon, qtr(I_POP_NEWFOLDER), this, SLOT( popupAddNode() ) );
978         if( rootItem->id() == THEPL->p_playing->i_id )
979         {
980             menu.addAction( addIcon, qtr(I_PL_ADDF), THEDP, SLOT( simplePLAppendDialog()) );
981             menu.addAction( addIcon, qtr(I_PL_ADDDIR), THEDP, SLOT( PLAppendDir()) );
982             menu.addAction( addIcon, qtr(I_OP_ADVOP), THEDP, SLOT( PLAppendDialog()) );
983         }
984         else if( THEPL->p_media_library &&
985                     rootItem->id() == THEPL->p_media_library->i_id )
986         {
987             menu.addAction( addIcon, qtr(I_PL_ADDF), THEDP, SLOT( simpleMLAppendDialog()) );
988             menu.addAction( addIcon, qtr(I_PL_ADDDIR), THEDP, SLOT( MLAppendDir() ) );
989             menu.addAction( addIcon, qtr(I_OP_ADVOP), THEDP, SLOT( MLAppendDialog() ) );
990         }
991     }
992
993     /* Item removal */
994     if( i_popup_item > -1 )
995     {
996         if( rootItem->id() != THEPL->p_playing->i_id )
997             menu.addAction( qtr( "Add to playlist"), this, SLOT( popupAddToPlaylist() ) );
998         menu.addAction( QIcon( ":/buttons/playlist/playlist_remove" ),
999                         qtr(I_POP_DEL), this, SLOT( popupDel() ) );
1000     }
1001
1002     menu.addSeparator();
1003
1004     if( canEdit() ) {
1005         menu.addAction( QIcon( ":/toolbar/clear" ), qtr("Clear playlist"),
1006                         this, SLOT( clearPlaylist() ) );
1007     }
1008
1009     /* Playlist sorting */
1010     if( !sortingMenu )
1011     {
1012         sortingMenu = new QMenu( qtr( "Sort by" ) );
1013         sortingMapper = new QSignalMapper( this );
1014         /* Choose what columns to show in sorting menu, not sure if this should be configurable*/
1015         QList<int> sortingColumns;
1016         sortingColumns << COLUMN_TITLE << COLUMN_ARTIST << COLUMN_ALBUM << COLUMN_TRACK_NUMBER << COLUMN_URI;
1017         foreach( int Column, sortingColumns )
1018         {
1019             QAction *asc  = sortingMenu->addAction( qfu( psz_column_title( Column ) ) + " " + qtr("Ascending") );
1020             QAction *desc = sortingMenu->addAction( qfu( psz_column_title( Column ) ) + " " + qtr("Descending") );
1021             sortingMapper->setMapping( asc, columnFromMeta(Column) + 1 );
1022             sortingMapper->setMapping( desc, -1 * (columnFromMeta(Column)+1) );
1023             CONNECT( asc, triggered(), sortingMapper, map() );
1024             CONNECT( desc, triggered(), sortingMapper, map() );
1025         }
1026         CONNECT( sortingMapper, mapped( int ), this, popupSort( int ) );
1027     }
1028     menu.addMenu( sortingMenu );
1029
1030     /* Zoom */
1031     QMenu *zoomMenu = new QMenu( qtr( "Display size" ) );
1032     zoomMenu->addAction( qtr( "Increase" ), this, SLOT( increaseZoom() ) );
1033     zoomMenu->addAction( qtr( "Decrease" ), this, SLOT( decreaseZoom() ) );
1034     menu.addMenu( zoomMenu );
1035
1036     /* Store the current selected item for popup*() methods */
1037     current_selection = list;
1038
1039     /* Display and forward the result */
1040     if( !menu.isEmpty() )
1041     {
1042         menu.exec( point ); return true;
1043     }
1044     else return false;
1045 }
1046
1047 void PLModel::popupDel()
1048 {
1049     doDelete( current_selection );
1050 }
1051
1052 void PLModel::popupPlay()
1053 {
1054     PL_LOCK;
1055     {
1056         playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1057                                                         i_popup_item );
1058         activateItem( p_item );
1059     }
1060     PL_UNLOCK;
1061 }
1062
1063 void PLModel::popupAddToPlaylist()
1064 {
1065     playlist_Lock( THEPL );
1066
1067     foreach( QModelIndex currentIndex, current_selection )
1068     {
1069         /* Don't add item multiple times when there are more columns in the model */
1070         if( currentIndex.column() )
1071             continue;
1072
1073         playlist_item_t *p_item = playlist_ItemGetById( THEPL, itemId( currentIndex ) );
1074         if( !p_item ) continue;
1075
1076         playlist_NodeAddCopy( THEPL, p_item,
1077                               THEPL->p_playing,
1078                               PLAYLIST_END );
1079     }
1080     playlist_Unlock( THEPL );
1081 }
1082
1083 void PLModel::popupInfo()
1084 {
1085     PL_LOCK;
1086     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1087                                                     i_popup_item );
1088     if( p_item )
1089     {
1090         input_item_t* p_input = p_item->p_input;
1091         vlc_gc_incref( p_input );
1092         PL_UNLOCK;
1093         MediaInfoDialog *mid = new MediaInfoDialog( p_intf, p_input );
1094         vlc_gc_decref( p_input );
1095         mid->setParent( PlaylistDialog::getInstance( p_intf ),
1096                         Qt::Dialog );
1097         mid->show();
1098     } else
1099         PL_UNLOCK;
1100 }
1101
1102 void PLModel::popupStream()
1103 {
1104     QStringList mrls = selectedURIs();
1105     if( !mrls.isEmpty() )
1106         THEDP->streamingDialog( NULL, mrls[0], false );
1107 }
1108
1109 void PLModel::popupSave()
1110 {
1111     QStringList mrls = selectedURIs();
1112     if( !mrls.isEmpty() )
1113         THEDP->streamingDialog( NULL, mrls[0] );
1114 }
1115
1116 void PLModel::popupExplore()
1117 {
1118     char *uri = NULL, *path = NULL;
1119
1120     PL_LOCK;
1121     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_popup_item );
1122     if( p_item )
1123     {
1124         input_item_t *p_input = p_item->p_input;
1125         uri = input_item_GetURI( p_input );
1126     }
1127     PL_UNLOCK;
1128
1129     if( uri != NULL )
1130     {
1131         path = make_path( uri );
1132         free( uri );
1133     }
1134     if( path == NULL )
1135         return;
1136
1137     QFileInfo info( qfu( path ) );
1138     free( path );
1139
1140     QDesktopServices::openUrl( QUrl::fromLocalFile( info.absolutePath() ) );
1141 }
1142
1143 void PLModel::popupAddNode()
1144 {
1145     bool ok;
1146     QString name = QInputDialog::getText( PlaylistDialog::getInstance( p_intf ),
1147         qtr( I_NEW_DIR ), qtr( I_NEW_DIR_NAME ),
1148         QLineEdit::Normal, QString(), &ok);
1149     if( !ok || name.isEmpty() ) return;
1150
1151     PL_LOCK;
1152     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1153                                                     i_popup_parent );
1154     if( p_item )
1155         playlist_NodeCreate( p_playlist, qtu( name ), p_item, PLAYLIST_END, 0, NULL );
1156     PL_UNLOCK;
1157 }
1158
1159 void PLModel::popupSort( int column )
1160 {
1161     sort( i_popup_parent,
1162           column > 0 ? column - 1 : -column - 1,
1163           column > 0 ? Qt::AscendingOrder : Qt::DescendingOrder );
1164 }
1165
1166 /* */
1167 void PLModel::increaseZoom()
1168 {
1169     i_zoom++;
1170     emit layoutChanged();
1171 }
1172
1173 void PLModel::decreaseZoom()
1174 {
1175     i_zoom--;
1176     emit layoutChanged();
1177 }
1178
1179 /******************* Drag and Drop helper class ******************/
1180 PlMimeData::~PlMimeData()
1181 {
1182     foreach( input_item_t *p_item, _inputItems )
1183         vlc_gc_decref( p_item );
1184 }
1185
1186 void PlMimeData::appendItem( input_item_t *p_item )
1187 {
1188     vlc_gc_incref( p_item );
1189     _inputItems.append( p_item );
1190 }
1191
1192 QList<input_item_t*> PlMimeData::inputItems() const
1193 {
1194     return _inputItems;
1195 }
1196
1197 QStringList PlMimeData::formats () const
1198 {
1199     QStringList fmts;
1200     fmts << "vlc/qt-input-items";
1201     return fmts;
1202 }