]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/playlist_model.cpp
Qt4: check that index.isValid too
[vlc] / modules / gui / qt4 / components / playlist / playlist_model.cpp
1 /*****************************************************************************
2  * playlist_model.cpp : Manage playlist model
3  ****************************************************************************
4  * Copyright (C) 2006-2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *          Ilkka Ollakkka <ileoo (at) videolan dot org>
9  *          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 "dialogs_provider.hpp"
32 #include "components/playlist/playlist_model.hpp"
33 #include "dialogs/mediainfo.hpp"
34 #include "dialogs/playlist.hpp"
35 #include <vlc_intf_strings.h>
36
37 #include "pixmaps/types/type_unknown.xpm"
38
39 #include <assert.h>
40 #include <QIcon>
41 #include <QFont>
42 #include <QMenu>
43 #include <QApplication>
44 #include <QSettings>
45 #include <QUrl>
46 #include <QFileInfo>
47 #include <QDesktopServices>
48 #include <QInputDialog>
49
50 #include "sorting.h"
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                   : QAbstractItemModel( parent )
69 {
70     p_intf            = _p_intf;
71     p_playlist        = _p_playlist;
72     i_cached_id       = -1;
73     i_cached_input_id = -1;
74     i_popup_item      = i_popup_parent = -1;
75     sortingMenu       = NULL;
76     current_index     = QModelIndex();
77
78     rootItem          = NULL; /* PLItem rootItem, will be set in rebuild( ) */
79
80     /* Icons initialization */
81 #define ADD_ICON(type, x) icons[ITEM_TYPE_##type] = QIcon( x )
82     ADD_ICON( UNKNOWN , type_unknown_xpm );
83     ADD_ICON( FILE, ":/type/file" );
84     ADD_ICON( DIRECTORY, ":/type/directory" );
85     ADD_ICON( DISC, ":/type/disc" );
86     ADD_ICON( CDDA, ":/type/cdda" );
87     ADD_ICON( CARD, ":/type/capture-card" );
88     ADD_ICON( NET, ":/type/net" );
89     ADD_ICON( PLAYLIST, ":/type/playlist" );
90     ADD_ICON( NODE, ":/type/node" );
91 #undef ADD_ICON
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     CONNECT( this, currentChanged( const QModelIndex &) ,
103              this, cacheCurrent( const QModelIndex &) );
104 }
105
106 PLModel::~PLModel()
107 {
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     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->p_input );
186     }
187
188     return plMimeData;
189 }
190
191 /* Drop operation */
192 bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
193                            int row, int column, 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->p_input );
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[inputItems.size()];
237
238     PL_LOCK;
239
240     playlist_item_t *p_parent =
241         playlist_ItemGetByInput( p_playlist, target->p_input );
242
243     if( !p_parent || row > p_parent->i_children )
244     {
245         PL_UNLOCK; return;
246     }
247
248     int new_pos = row == -1 ? p_parent->i_children : row;
249     int model_pos = new_pos;
250     int i = 0;
251
252     foreach( input_item_t *p_input, inputItems )
253     {
254         playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
255         if( !p_item ) continue;
256
257         PLItem *item = findByInput( rootItem, p_input->i_id );
258         if( !item ) continue;
259
260         /* Better not try to move a node into itself.
261            Abort the whole operation in that case,
262            because it is ambiguous. */
263         PLItem *climber = target;
264         while( climber )
265         {
266             if( climber == item )
267             {
268                 PL_UNLOCK; return;
269             }
270             climber = climber->parentItem;
271         }
272
273         if( item->parentItem == target &&
274             target->children.indexOf( item ) < new_pos )
275                 model_pos--;
276
277         model_items.append( item );
278         pp_items[i] = p_item;
279         i++;
280     }
281
282     if( model_items.isEmpty() )
283     {
284         PL_UNLOCK; return;
285     }
286
287     playlist_TreeMoveMany( p_playlist, i, pp_items, p_parent, new_pos );
288
289     PL_UNLOCK;
290
291     foreach( PLItem *item, model_items )
292         takeItem( item );
293
294     insertChildren( target, model_items, model_pos );
295 }
296
297 /* remove item with its id */
298 void PLModel::removeItem( int i_id )
299 {
300     PLItem *item = findById( rootItem, i_id );
301     removeItem( item );
302 }
303
304 void PLModel::activateItem( const QModelIndex &index )
305 {
306     assert( index.isValid() );
307     PLItem *item = getItem( index );
308     assert( item );
309     PL_LOCK;
310     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
311     activateItem( p_item );
312     PL_UNLOCK;
313 }
314
315 /* Must be entered with 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->i_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, int role ) const
332 {
333     if( !index.isValid() ) return QVariant();
334     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
344         {
345             char *psz = psz_column_meta( item->p_input, metadata );
346             returninfo = qfu( psz );
347             free( psz );
348         }
349         return QVariant( returninfo );
350     }
351     else if( role == Qt::DecorationRole && index.column() == 0  )
352     {
353         /* Used to segfault here because i_type wasn't always initialized */
354         return QVariant( PLModel::icons[item->p_input->i_type] );
355     }
356     else if( role == Qt::FontRole )
357     {
358         if( isCurrent( index ) )
359         {
360             QFont f; f.setBold( true ); return QVariant( f );
361         }
362     }
363     else if( role == Qt::BackgroundRole && isCurrent( index ) )
364     {
365         return QVariant( QBrush( Qt::gray ) );
366     }
367     else if( role == IsCurrentRole ) return QVariant( isCurrent( index ) );
368     else if( role == IsLeafNodeRole )
369     {
370         QVariant isLeaf;
371         PL_LOCK;
372         playlist_item_t *plItem =
373             playlist_ItemGetById( p_playlist, item->i_id );
374
375         if( plItem )
376             isLeaf = plItem->i_children == -1;
377
378         PL_UNLOCK;
379         return isLeaf;
380     }
381     else if( role == IsCurrentsParentNodeRole )
382     {
383         return QVariant( isParent( index, current_index ) );
384     }
385     return QVariant();
386 }
387
388 /* Seek from current index toward the top and see if index is one of parent nodes */
389 bool PLModel::isParent( const QModelIndex &index, const QModelIndex &current ) const
390 {
391     if( !index.isValid() )
392         return false;
393
394     if( index == current )
395         return true;
396
397     if( !current.isValid() || !current.parent().isValid() )
398         return false;
399
400     return isParent( index, current.parent() );
401 }
402
403 bool PLModel::isCurrent( const QModelIndex &index ) const
404 {
405     return index == current_index;
406 }
407
408 int PLModel::itemId( const QModelIndex &index ) const
409 {
410     return getItem( index )->i_id;
411 }
412
413 QVariant PLModel::headerData( int section, Qt::Orientation orientation,
414                               int role ) const
415 {
416     if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
417         return QVariant();
418
419     int meta_col = columnToMeta( section );
420
421     if( meta_col == COLUMN_END ) return QVariant();
422
423     return QVariant( qfu( psz_column_title( meta_col ) ) );
424 }
425
426 QModelIndex PLModel::index( int row, int column, const QModelIndex &parent )
427                   const
428 {
429     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
430
431     PLItem *childItem = parentItem->child( row );
432     if( childItem )
433         return createIndex( row, column, childItem );
434     else
435         return QModelIndex();
436 }
437
438 QModelIndex PLModel::index( int i_id, int c )
439 {
440   return index( findById( rootItem, i_id ), c );
441 }
442
443 /* Return the index of a given item */
444 QModelIndex PLModel::index( PLItem *item, int column ) const
445 {
446     if( !item ) return QModelIndex();
447     const PLItem *parent = item->parent();
448     if( parent )
449         return createIndex( parent->children.lastIndexOf( item ),
450                             column, item );
451     return QModelIndex();
452 }
453
454 void PLModel::cacheCurrent( const QModelIndex &current )
455 {
456     current_index = current;
457 }
458
459 QModelIndex PLModel::currentIndex()
460 {
461     return current_index;
462 }
463
464 QModelIndex PLModel::parent( const QModelIndex &index ) const
465 {
466     if( !index.isValid() ) return QModelIndex();
467
468     PLItem *childItem = getItem( index );
469     if( !childItem )
470     {
471         msg_Err( p_playlist, "NULL CHILD" );
472         return QModelIndex();
473     }
474
475     PLItem *parentItem = childItem->parent();
476     if( !parentItem || parentItem == rootItem ) return QModelIndex();
477     if( !parentItem->parentItem )
478     {
479         msg_Err( p_playlist, "No parent parent, trying row 0 " );
480         msg_Err( p_playlist, "----- PLEASE REPORT THIS ------" );
481         return createIndex( 0, 0, parentItem );
482     }
483     return createIndex(parentItem->row(), 0, parentItem);
484 }
485
486 int PLModel::columnCount( const QModelIndex &i) const
487 {
488     return columnFromMeta( COLUMN_END );
489 }
490
491 int PLModel::rowCount( const QModelIndex &parent ) const
492 {
493     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
494     return parentItem->childCount();
495 }
496
497 QStringList PLModel::selectedURIs()
498 {
499     QStringList lst;
500     for( int i = 0; i < current_selection.size(); i++ )
501     {
502         PLItem *item = getItem( current_selection[i] );
503         if( item )
504         {
505             PL_LOCK;
506             playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_id );
507             if( p_item )
508             {
509                 char *psz = input_item_GetURI( p_item->p_input );
510                 if( psz )
511                 {
512                     lst.append( qfu(psz) );
513                     free( psz );
514                 }
515             }
516             PL_UNLOCK;
517         }
518     }
519     return lst;
520 }
521
522
523 /************************* Lookups *****************************/
524
525 PLItem *PLModel::findById( PLItem *root, int i_id )
526 {
527     return findInner( root, i_id, false );
528 }
529
530 PLItem *PLModel::findByInput( PLItem *root, int i_id )
531 {
532     PLItem *result = findInner( root, i_id, true );
533     return result;
534 }
535
536 #define CACHE( i, p ) { i_cached_id = i; p_cached_item = p; }
537 #define ICACHE( i, p ) { i_cached_input_id = i; p_cached_item_bi = p; }
538
539 PLItem * PLModel::findInner( PLItem *root, int i_id, bool b_input )
540 {
541     if( !root ) return NULL;
542     if( ( !b_input && i_cached_id == i_id) ||
543         ( b_input && i_cached_input_id ==i_id ) )
544     {
545         return b_input ? p_cached_item_bi : p_cached_item;
546     }
547
548     if( !b_input && root->i_id == i_id )
549     {
550         CACHE( i_id, root );
551         return root;
552     }
553     else if( b_input && root->p_input->i_id == i_id )
554     {
555         ICACHE( i_id, root );
556         return root;
557     }
558
559     QList<PLItem *>::iterator it = root->children.begin();
560     while ( it != root->children.end() )
561     {
562         if( !b_input && (*it)->i_id == i_id )
563         {
564             CACHE( i_id, (*it) );
565             return p_cached_item;
566         }
567         else if( b_input && (*it)->p_input->i_id == i_id )
568         {
569             ICACHE( i_id, (*it) );
570             return p_cached_item_bi;
571         }
572         if( (*it)->children.size() )
573         {
574             PLItem *childFound = findInner( (*it), i_id, b_input );
575             if( childFound )
576             {
577                 if( b_input )
578                     ICACHE( i_id, childFound )
579                 else
580                     CACHE( i_id, childFound )
581                 return childFound;
582             }
583         }
584         it++;
585     }
586     return NULL;
587 }
588 #undef CACHE
589 #undef ICACHE
590
591 int PLModel::columnToMeta( int _column )
592 {
593     int meta = 1;
594     int column = 0;
595
596     while( column != _column && meta != COLUMN_END )
597     {
598         meta <<= 1;
599         column++;
600     }
601
602     return meta;
603 }
604
605 int PLModel::columnFromMeta( int meta_col )
606 {
607     int meta = 1;
608     int column = 0;
609
610     while( meta != meta_col && meta != COLUMN_END )
611     {
612         meta <<= 1;
613         column++;
614     }
615
616     return column;
617 }
618
619 bool PLModel::canEdit() const
620 {
621   return (
622     rootItem != NULL &&
623     (
624       rootItem->p_input == p_playlist->p_playing->p_input ||
625       (
626         p_playlist->p_media_library &&
627         rootItem->p_input == p_playlist->p_media_library->p_input
628       )
629     )
630   );
631 }
632 /************************* Updates handling *****************************/
633
634 /**** Events processing ****/
635 void PLModel::processInputItemUpdate( input_thread_t *p_input )
636 {
637     if( !p_input ) return;
638     if( p_input && !( p_input->b_dead || !vlc_object_alive( p_input ) ) )
639     {
640         PLItem *item = findByInput( rootItem, input_GetItem( p_input )->i_id );
641         if( item ) emit currentChanged( index( item, 0 ) );
642     }
643     processInputItemUpdate( input_GetItem( p_input ) );
644 }
645
646 void PLModel::processInputItemUpdate( input_item_t *p_item )
647 {
648     if( !p_item ||  p_item->i_id <= 0 ) return;
649     PLItem *item = findByInput( rootItem, p_item->i_id );
650     if( item )
651         updateTreeItem( item );
652 }
653
654 void PLModel::processItemRemoval( int i_id )
655 {
656     if( i_id <= 0 ) return;
657     removeItem( i_id );
658 }
659
660 void PLModel::processItemAppend( int i_item, int i_parent )
661 {
662     playlist_item_t *p_item = NULL;
663     PLItem *newItem = NULL;
664     input_thread_t *currentInputThread;
665     int pos;
666
667     PLItem *nodeItem = findById( rootItem, i_parent );
668     if( !nodeItem ) return;
669
670     foreach( PLItem *existing, nodeItem->children )
671       if( existing->i_id == i_item ) return;
672
673     PL_LOCK;
674     p_item = playlist_ItemGetById( p_playlist, i_item );
675     if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG )
676     {
677         PL_UNLOCK; return;
678     }
679
680     for( pos = 0; pos < p_item->p_parent->i_children; pos++ )
681         if( p_item->p_parent->pp_children[pos] == p_item ) break;
682
683     newItem = new PLItem( p_item, nodeItem );
684     PL_UNLOCK;
685
686     beginInsertRows( index( nodeItem, 0 ), pos, pos );
687     nodeItem->insertChild( newItem, pos );
688     endInsertRows();
689
690     if( newItem->p_input == THEMIM->currentInputItem() )
691         emit currentChanged( index( newItem, 0 ) );
692 }
693
694
695 void PLModel::rebuild()
696 {
697     rebuild( NULL );
698 }
699
700 void PLModel::rebuild( playlist_item_t *p_root )
701 {
702     playlist_item_t* p_item;
703
704     /* Invalidate cache */
705     i_cached_id = i_cached_input_id = -1;
706     current_index = QModelIndex();
707
708     if( rootItem ) rootItem->removeChildren();
709
710     PL_LOCK;
711     if( p_root )
712     {
713         delete rootItem;
714         rootItem = new PLItem( p_root );
715     }
716     assert( rootItem );
717     /* Recreate from root */
718     updateChildren( rootItem );
719     PL_UNLOCK;
720
721     /* And signal the view */
722     reset();
723
724     if( p_root ) emit rootChanged();
725 }
726
727 void PLModel::takeItem( PLItem *item )
728 {
729     assert( item );
730     PLItem *parent = item->parentItem;
731     assert( parent );
732     int i_index = parent->children.indexOf( item );
733
734     beginRemoveRows( index( parent, 0 ), i_index, i_index );
735     parent->takeChildAt( i_index );
736     endRemoveRows();
737 }
738
739 void PLModel::insertChildren( PLItem *node, QList<PLItem*>& items, int i_pos )
740 {
741     assert( node );
742     int count = items.size();
743     if( !count ) return;
744     beginInsertRows( index( node, 0 ), i_pos, i_pos + count - 1 );
745     for( int i = 0; i < count; i++ )
746     {
747         node->children.insert( i_pos + i, items[i] );
748         items[i]->parentItem = node;
749     }
750     endInsertRows();
751 }
752
753 void PLModel::removeItem( PLItem *item )
754 {
755     if( !item ) return;
756
757     i_cached_id = -1;
758     i_cached_input_id = -1;
759     current_index = QModelIndex();
760
761     if( item->parentItem ) {
762         int i = item->parentItem->children.indexOf( item );
763         beginRemoveRows( index( item->parentItem, 0), i, i );
764         item->parentItem->children.removeAt(i);
765         delete item;
766         endRemoveRows();
767     }
768     else delete item;
769
770     if(item == rootItem)
771     {
772         rootItem = NULL;
773         rebuild( p_playlist->p_playing );
774     }
775 }
776
777 /* This function must be entered WITH the playlist lock */
778 void PLModel::updateChildren( PLItem *root )
779 {
780     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->i_id );
781     updateChildren( p_node, root );
782 }
783
784 /* This function must be entered WITH the playlist lock */
785 void PLModel::updateChildren( playlist_item_t *p_node, PLItem *root )
786 {
787     for( int i = 0; i < p_node->i_children ; i++ )
788     {
789         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
790         PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
791         root->appendChild( newItem );
792         if( p_node->pp_children[i]->i_children != -1 )
793             updateChildren( p_node->pp_children[i], newItem );
794     }
795 }
796
797 /* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
798 void PLModel::updateTreeItem( PLItem *item )
799 {
800     if( !item ) return;
801     emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) ) );
802 }
803
804 /************************* Actions ******************************/
805
806 /**
807  * Deletion, don't delete items childrens if item is going to be
808  * delete allready, so we remove childrens from selection-list.
809  */
810 void PLModel::doDelete( QModelIndexList selected )
811 {
812     if( !canEdit() ) return;
813
814     while( !selected.isEmpty() )
815     {
816         QModelIndex index = selected[0];
817         selected.removeAt( 0 );
818
819         if( index.column() != 0 ) continue;
820
821         PLItem *item = getItem( index );
822         if( item->children.size() )
823             recurseDelete( item->children, &selected );
824
825         PL_LOCK;
826         playlist_DeleteFromInput( p_playlist, item->p_input, pl_Locked );
827         PL_UNLOCK;
828
829         removeItem( item );
830     }
831 }
832
833 void PLModel::recurseDelete( QList<PLItem*> children, QModelIndexList *fullList )
834 {
835     for( int i = children.size() - 1; i >= 0 ; i-- )
836     {
837         PLItem *item = children[i];
838         if( item->children.size() )
839             recurseDelete( item->children, fullList );
840         fullList->removeAll( index( item, 0 ) );
841     }
842 }
843
844 /******* Volume III: Sorting and searching ********/
845 void PLModel::sort( int column, Qt::SortOrder order )
846 {
847     sort( rootItem->i_id, column, order );
848 }
849
850 void PLModel::sort( int i_root_id, int column, Qt::SortOrder order )
851 {
852     msg_Dbg( p_intf, "Sorting by column %i, order %i", column, order );
853
854     int meta = columnToMeta( column );
855     if( meta == COLUMN_END ) return;
856
857     PLItem *item = findById( rootItem, i_root_id );
858     if( !item ) return;
859     QModelIndex qIndex = index( item, 0 );
860     int count = item->children.size();
861     if( count )
862     {
863         beginRemoveRows( qIndex, 0, count - 1 );
864         item->removeChildren();
865         endRemoveRows( );
866     }
867
868     PL_LOCK;
869     {
870         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
871                                                         i_root_id );
872         if( p_root )
873         {
874             playlist_RecursiveNodeSort( p_playlist, p_root,
875                                         i_column_sorting( meta ),
876                                         order == Qt::AscendingOrder ?
877                                             ORDER_NORMAL : ORDER_REVERSE );
878         }
879     }
880
881     i_cached_id = i_cached_input_id = -1;
882     current_index = QModelIndex();
883
884     if( count )
885     {
886         beginInsertRows( qIndex, 0, count - 1 );
887         updateChildren( item );
888         endInsertRows( );
889     }
890     PL_UNLOCK;
891     /* if we have popup item, try to make sure that you keep that item visible */
892     if( i_popup_item > -1 )
893     {
894         PLItem *popupitem = findById( rootItem, i_popup_item );
895         if( popupitem ) emit currentChanged( index( popupitem, 0 ) );
896         /* reset i_popup_item as we don't show it as selected anymore anyway */
897         i_popup_item = -1;
898     }
899     else if( currentIndex().isValid() ) emit currentChanged( currentIndex() );
900 }
901
902 void PLModel::search( const QString& search_text, const QModelIndex & idx, bool b_recursive )
903 {
904     /** \todo Fire the search with a small delay ? */
905     PL_LOCK;
906     {
907         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
908                                                         itemId( idx ) );
909         assert( p_root );
910         const char *psz_name = qtu( search_text );
911         playlist_LiveSearchUpdate( p_playlist , p_root, psz_name, b_recursive );
912
913         if( idx.isValid() )
914         {
915             PLItem *searchRoot = getItem( idx );
916
917             beginRemoveRows( idx, 0, searchRoot->children.size() - 1 );
918             searchRoot->removeChildren();
919             endRemoveRows( );
920
921             beginInsertRows( idx, 0, searchRoot->children.size() - 1 );
922             updateChildren( searchRoot );
923             endInsertRows();
924
925             PL_UNLOCK;
926             return;
927         }
928     }
929     PL_UNLOCK;
930     rebuild();
931 }
932
933 /*********** Popup *********/
934 bool PLModel::popup( const QModelIndex & index, const QPoint &point, const QModelIndexList &list )
935 {
936     int i_id = index.isValid() ? itemId( index ) : rootItem->i_id;
937
938     PL_LOCK;
939     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id );
940     if( !p_item )
941     {
942         PL_UNLOCK;
943         return false;
944     }
945
946     i_popup_item = index.isValid() ? p_item->i_id : -1;
947     i_popup_parent = index.isValid() ?
948         ( p_item->p_parent ? p_item->p_parent->i_id : -1 ) :
949         ( rootItem->i_id );
950     i_popup_column = index.column();
951
952     bool tree = ( rootItem && rootItem->i_id != p_playlist->p_playing->i_id ) ||
953                 var_InheritBool( p_intf, "playlist-tree" );
954
955     PL_UNLOCK;
956
957     current_selection = list;
958
959     QMenu menu;
960     if( i_popup_item > -1 )
961     {
962         menu.addAction( QIcon( ":/menu/play" ), qtr(I_POP_PLAY), this, SLOT( popupPlay() ) );
963         menu.addAction( QIcon( ":/menu/stream" ),
964                         qtr(I_POP_STREAM), this, SLOT( popupStream() ) );
965         menu.addAction( qtr(I_POP_SAVE), this, SLOT( popupSave() ) );
966         menu.addAction( QIcon( ":/menu/info" ), qtr(I_POP_INFO), this, SLOT( popupInfo() ) );
967         menu.addAction( QIcon( ":/type/folder-grey" ),
968                         qtr( I_POP_EXPLORE ), this, SLOT( popupExplore() ) );
969         menu.addSeparator();
970     }
971     if( canEdit() )
972     {
973         QIcon addIcon( ":/buttons/playlist/playlist_add" );
974         menu.addSeparator();
975         if( tree ) menu.addAction( addIcon, qtr(I_POP_NEWFOLDER), this, SLOT( popupAddNode() ) );
976         if( rootItem->i_id == THEPL->p_playing->i_id )
977         {
978             menu.addAction( addIcon, qtr(I_PL_ADDF), THEDP, SLOT( simplePLAppendDialog()) );
979             menu.addAction( addIcon, qtr(I_PL_ADDDIR), THEDP, SLOT( PLAppendDir()) );
980             menu.addAction( addIcon, qtr(I_OP_ADVOP), THEDP, SLOT( PLAppendDialog()) );
981         }
982         else if( THEPL->p_media_library &&
983                     rootItem->i_id == THEPL->p_media_library->i_id )
984         {
985             menu.addAction( addIcon, qtr(I_PL_ADDF), THEDP, SLOT( simpleMLAppendDialog()) );
986             menu.addAction( addIcon, qtr(I_PL_ADDDIR), THEDP, SLOT( MLAppendDir() ) );
987             menu.addAction( addIcon, qtr(I_OP_ADVOP), THEDP, SLOT( MLAppendDialog() ) );
988         }
989     }
990     if( i_popup_item > -1 )
991     {
992         menu.addAction( QIcon( ":/buttons/playlist/playlist_remove" ),
993                         qtr(I_POP_DEL), this, SLOT( popupDel() ) );
994         menu.addSeparator();
995         if( !sortingMenu )
996         {
997             sortingMenu = new QMenu( qtr( "Sort by" ) );
998             sortingMapper = new QSignalMapper( this );
999             int i, j;
1000             for( i = 1, j = 1; i < COLUMN_END; i <<= 1, j++ )
1001             {
1002                 if( i == COLUMN_NUMBER ) continue;
1003                 QMenu *m = sortingMenu->addMenu( qfu( psz_column_title( i ) ) );
1004                 QAction *asc = m->addAction( qtr("Ascending") );
1005                 QAction *desc = m->addAction( qtr("Descending") );
1006                 sortingMapper->setMapping( asc, j );
1007                 sortingMapper->setMapping( desc, -j );
1008                 CONNECT( asc, triggered(), sortingMapper, map() );
1009                 CONNECT( desc, triggered(), sortingMapper, map() );
1010             }
1011             CONNECT( sortingMapper, mapped( int ), this, popupSort( int ) );
1012         }
1013         menu.addMenu( sortingMenu );
1014     }
1015     if( !menu.isEmpty() )
1016     {
1017         menu.exec( point ); return true;
1018     }
1019     else return false;
1020 }
1021
1022 void PLModel::popupDel()
1023 {
1024     doDelete( current_selection );
1025 }
1026
1027 void PLModel::popupPlay()
1028 {
1029     PL_LOCK;
1030     {
1031         playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1032                                                         i_popup_item );
1033         activateItem( p_item );
1034     }
1035     PL_UNLOCK;
1036 }
1037
1038 void PLModel::popupInfo()
1039 {
1040     PL_LOCK;
1041     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1042                                                     i_popup_item );
1043     if( p_item )
1044     {
1045         input_item_t* p_input = p_item->p_input;
1046         vlc_gc_incref( p_input );
1047         PL_UNLOCK;
1048         MediaInfoDialog *mid = new MediaInfoDialog( p_intf, p_input );
1049         vlc_gc_decref( p_input );
1050         mid->setParent( PlaylistDialog::getInstance( p_intf ),
1051                         Qt::Dialog );
1052         mid->show();
1053     } else
1054         PL_UNLOCK;
1055 }
1056
1057 void PLModel::popupStream()
1058 {
1059     QStringList mrls = selectedURIs();
1060     if( !mrls.isEmpty() )
1061         THEDP->streamingDialog( NULL, mrls[0], false );
1062
1063 }
1064
1065 void PLModel::popupSave()
1066 {
1067     QStringList mrls = selectedURIs();
1068     if( !mrls.isEmpty() )
1069         THEDP->streamingDialog( NULL, mrls[0] );
1070 }
1071
1072 void PLModel::popupExplore()
1073 {
1074     PL_LOCK;
1075     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1076                                                     i_popup_item );
1077     if( p_item )
1078     {
1079        input_item_t *p_input = p_item->p_input;
1080        char *psz_meta = input_item_GetURI( p_input );
1081        PL_UNLOCK;
1082        if( psz_meta )
1083        {
1084            const char *psz_access;
1085            const char *psz_demux;
1086            char  *psz_path;
1087            input_SplitMRL( &psz_access, &psz_demux, &psz_path, psz_meta );
1088
1089            if( !EMPTY_STR( psz_access ) && (
1090                    !strncasecmp( psz_access, "file", 4 ) ||
1091                    !strncasecmp( psz_access, "dire", 4 ) ))
1092            {
1093                QFileInfo info( qfu( decode_URI( psz_path ) ) );
1094                QDesktopServices::openUrl(
1095                                QUrl::fromLocalFile( info.absolutePath() ) );
1096            }
1097            free( psz_meta );
1098        }
1099     }
1100     else
1101         PL_UNLOCK;
1102 }
1103
1104 void PLModel::popupAddNode()
1105 {
1106     bool ok;
1107     QString name = QInputDialog::getText( PlaylistDialog::getInstance( p_intf ),
1108         qtr( I_NEW_DIR ), qtr( I_NEW_DIR_NAME ),
1109         QLineEdit::Normal, QString(), &ok);
1110     if( !ok || name.isEmpty() ) return;
1111     PL_LOCK;
1112     playlist_item_t *p_item = playlist_ItemGetById( p_playlist,
1113                                                     i_popup_parent );
1114     if( p_item )
1115     {
1116         playlist_NodeCreate( p_playlist, qtu( name ), p_item, PLAYLIST_END, 0, NULL );
1117     }
1118     PL_UNLOCK;
1119 }
1120
1121 void PLModel::popupSort( int column )
1122 {
1123     sort( i_popup_parent,
1124           column > 0 ? column - 1 : -column - 1,
1125           column > 0 ? Qt::AscendingOrder : Qt::DescendingOrder );
1126 }
1127
1128 /******************* Drag and Drop helper class ******************/
1129
1130 PlMimeData::PlMimeData( )
1131 { }
1132
1133 PlMimeData::~PlMimeData()
1134 {
1135     foreach( input_item_t *p_item, _inputItems )
1136         vlc_gc_decref( p_item );
1137 }
1138
1139 void PlMimeData::appendItem( input_item_t *p_item )
1140 {
1141     vlc_gc_incref( p_item );
1142     _inputItems.append( p_item );
1143 }
1144
1145 QList<input_item_t*> PlMimeData::inputItems() const
1146 {
1147     return _inputItems;
1148 }
1149
1150 QStringList PlMimeData::formats () const
1151 {
1152     QStringList fmts;
1153     fmts << "vlc/qt-input-items";
1154     return fmts;
1155 }