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