1 /*****************************************************************************
2  * playlist_model.cpp : Manage playlist model
3  ****************************************************************************
4  * Copyright (C) 2006-2011 the VideoLAN team
5  * $Id: 99e4c8f942db79ad8f043d33a26b4927cc96397f $
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 "qt.hpp"
31 #include "components/playlist/playlist_model.hpp"
32 #include "input_manager.hpp"                            /* THEMIM */
33 #include "util/qt_dirs.hpp"
34 #include "recents.hpp"                                  /* Open:: */
35 
36 #include <vlc_intf_strings.h>                           /* I_DIR */
37 #include <vlc_url.h>
38 
39 #include "sorting.h"
40 
41 #include <assert.h>
42 #include <QFont>
43 #include <QAction>
44 #include <QStack>
45 
46 /*************************************************************************
47  * Playlist model implementation
48  *************************************************************************/
49 
PLModel(playlist_t * _p_playlist,intf_thread_t * _p_intf,playlist_item_t * p_root,QObject * parent)50 PLModel::PLModel( playlist_t *_p_playlist,  /* THEPL */
51                   intf_thread_t *_p_intf,   /* main Qt p_intf */
52                   playlist_item_t * p_root,
53                   QObject *parent )         /* Basic Qt parent */
54                   : VLCModel( _p_intf, parent )
55 {
56     p_playlist        = _p_playlist;
57 
58     rootItem          = NULL; /* PLItem rootItem, will be set in rebuild( ) */
59     latestSearch      = QString();
60 
61     rebuild( p_root );
62     DCONNECT( THEMIM->getIM(), metaChanged( input_item_t *),
63               this, processInputItemUpdate( input_item_t *) );
64     DCONNECT( THEMIM, inputChanged( bool ),
65               this, processInputItemUpdate( ) );
66     CONNECT( THEMIM, playlistItemAppended( int, int ),
67              this, processItemAppend( int, int ) );
68     CONNECT( THEMIM, playlistItemRemoved( int ),
69              this, processItemRemoval( int ) );
70 }
71 
~PLModel()72 PLModel::~PLModel()
73 {
74     delete rootItem;
75 }
76 
supportedDropActions() const77 Qt::DropActions PLModel::supportedDropActions() const
78 {
79     return Qt::CopyAction | Qt::MoveAction;
80 }
81 
flags(const QModelIndex & index) const82 Qt::ItemFlags PLModel::flags( const QModelIndex &index ) const
83 {
84     Qt::ItemFlags flags = QAbstractItemModel::flags( index );
85 
86     const PLItem *item = index.isValid() ? getItem( index ) : rootItem;
87 
88     if( canEdit() )
89     {
90         vlc_playlist_locker pl_lock ( THEPL );
91 
92         playlist_item_t *plItem =
93             playlist_ItemGetById( p_playlist, item->i_playlist_id );
94 
95         if ( plItem && ( plItem->i_children > -1 ) )
96             flags |= Qt::ItemIsDropEnabled;
97     }
98     flags |= Qt::ItemIsDragEnabled;
99 
100     return flags;
101 }
102 
mimeTypes() const103 QStringList PLModel::mimeTypes() const
104 {
105     QStringList types;
106     types << "vlc/qt-input-items";
107     return types;
108 }
109 
modelIndexLessThen(const QModelIndex & i1,const QModelIndex & i2)110 bool modelIndexLessThen( const QModelIndex &i1, const QModelIndex &i2 )
111 {
112     if( !i1.isValid() || !i2.isValid() ) return false;
113     PLItem *item1 = static_cast<PLItem*>( i1.internalPointer() );
114     PLItem *item2 = static_cast<PLItem*>( i2.internalPointer() );
115     if( item1->hasSameParent( item2 ) ) return i1.row() < i2.row();
116     else return *item1 < *item2;
117 }
118 
mimeData(const QModelIndexList & indexes) const119 QMimeData *PLModel::mimeData( const QModelIndexList &indexes ) const
120 {
121     PlMimeData *plMimeData = new PlMimeData();
122     QModelIndexList list;
123 
124     foreach( const QModelIndex &index, indexes ) {
125         if( index.isValid() && index.column() == 0 )
126             list.append(index);
127     }
128 
129     qSort(list.begin(), list.end(), modelIndexLessThen);
130 
131     AbstractPLItem *item = NULL;
132     foreach( const QModelIndex &index, list ) {
133         if( item )
134         {
135             AbstractPLItem *testee = getItem( index );
136             while( testee->parent() )
137             {
138                 if( testee->parent() == item ||
139                     testee->parent() == item->parent() ) break;
140                 testee = testee->parent();
141             }
142             if( testee->parent() == item ) continue;
143             item = getItem( index );
144         }
145         else
146             item = getItem( index );
147 
148         plMimeData->appendItem( static_cast<PLItem*>(item)->inputItem() );
149     }
150 
151     return plMimeData;
152 }
153 
154 /* Drop operation */
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int,const QModelIndex & parent)155 bool PLModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
156         int row, int, const QModelIndex &parent )
157 {
158     bool copy = action == Qt::CopyAction;
159     if( !copy && action != Qt::MoveAction )
160         return true;
161 
162     const PlMimeData *plMimeData = qobject_cast<const PlMimeData*>( data );
163     if( plMimeData )
164     {
165         if( copy )
166             dropAppendCopy( plMimeData, getItem( parent ), row );
167         else
168             dropMove( plMimeData, getItem( parent ), row );
169     }
170     return true;
171 }
172 
dropAppendCopy(const PlMimeData * plMimeData,PLItem * target,int pos)173 void PLModel::dropAppendCopy( const PlMimeData *plMimeData, PLItem *target, int pos )
174 {
175     vlc_playlist_locker pl_lock ( THEPL );
176 
177     playlist_item_t *p_parent =
178         playlist_ItemGetByInput( p_playlist, target->inputItem() );
179     if( !p_parent ) return;
180 
181     if( pos == -1 ) pos = PLAYLIST_END;
182 
183     QList<input_item_t*> inputItems = plMimeData->inputItems();
184 
185     foreach( input_item_t* p_input, inputItems )
186     {
187         playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
188         if( !p_item ) continue;
189         pos = playlist_NodeAddCopy( p_playlist, p_item, p_parent, pos );
190     }
191 }
192 
dropMove(const PlMimeData * plMimeData,PLItem * target,int row)193 void PLModel::dropMove( const PlMimeData * plMimeData, PLItem *target, int row )
194 {
195     QList<input_item_t*> inputItems = plMimeData->inputItems();
196     QList<PLItem*> model_items;
197     playlist_item_t **pp_items;
198     pp_items = (playlist_item_t **)
199                calloc( inputItems.count(), sizeof( playlist_item_t* ) );
200     if ( !pp_items ) return;
201 
202     int model_pos;
203 
204     {
205         vlc_playlist_locker pl_lock ( THEPL );
206 
207         playlist_item_t *p_parent =
208             playlist_ItemGetByInput( p_playlist, target->inputItem() );
209 
210         if( !p_parent || row > p_parent->i_children )
211         {
212             free( pp_items );
213             return;
214         }
215 
216         int new_pos = model_pos = row == -1 ? p_parent->i_children : row;
217         int i = 0;
218 
219         foreach( input_item_t *p_input, inputItems )
220         {
221             playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
222             if( !p_item ) continue;
223 
224             PLItem *item = findByInputLocked( rootItem, p_input );
225             if( !item ) continue;
226 
227             /* Better not try to move a node into itself.
228                Abort the whole operation in that case,
229                because it is ambiguous. */
230             AbstractPLItem *climber = target;
231             while( climber )
232             {
233                 if( climber == item )
234                 {
235                     free( pp_items );
236                     return;
237                 }
238                 climber = climber->parent();
239             }
240 
241             if( item->parent() == target &&
242                 target->children.indexOf( item ) < new_pos )
243                 model_pos--;
244 
245             model_items.append( item );
246             pp_items[i] = p_item;
247             i++;
248         }
249 
250         if( model_items.isEmpty() )
251         {
252             free( pp_items );
253             return;
254         }
255 
256         playlist_TreeMoveMany( p_playlist, i, pp_items, p_parent, new_pos );
257     }
258 
259     foreach( PLItem *item, model_items )
260         takeItem( item );
261 
262     insertChildren( target, model_items, model_pos );
263     free( pp_items );
264 }
265 
activateItem(const QModelIndex & index)266 void PLModel::activateItem( const QModelIndex &index )
267 {
268     assert( index.isValid() );
269     const PLItem *item = getItem( index );
270     assert( item );
271 
272     vlc_playlist_locker pl_lock( THEPL );
273 
274     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, item->i_playlist_id );
275     activateItem( p_item );
276 }
277 
278 /* Convenient overloaded private version of activateItem
279  * Must be entered with PL lock */
activateItem(playlist_item_t * p_item)280 void PLModel::activateItem( playlist_item_t *p_item )
281 {
282     if( !p_item ) return;
283     playlist_item_t *p_parent = p_item;
284     while( p_parent )
285     {
286         if( p_parent->i_id == rootItem->id() ) break;
287         p_parent = p_parent->p_parent;
288     }
289     if( p_parent )
290         playlist_ViewPlay( p_playlist, p_parent, p_item );
291 }
292 
293 /****************** Base model mandatory implementations *****************/
data(const QModelIndex & index,const int role) const294 QVariant PLModel::data( const QModelIndex &index, const int role ) const
295 {
296     if( !index.isValid() )
297         return QVariant();
298 
299     switch( role )
300     {
301 
302         case Qt::FontRole:
303             return customFont;
304 
305         case Qt::DisplayRole:
306         {
307             PLItem *item = getItem( index );
308             int metadata = columnToMeta( index.column() );
309             if( metadata == COLUMN_END )
310                 return QVariant();
311 
312             QString returninfo;
313             if( metadata == COLUMN_NUMBER )
314             {
315                 returninfo = QString::number( index.row() + 1 );
316             }
317             else if( metadata == COLUMN_COVER )
318             {
319                 QString artUrl;
320                 artUrl = InputManager::decodeArtURL( item->inputItem() );
321                 if( artUrl.isEmpty() )
322                 {
323                     for( int i = 0; i < item->childCount(); i++ )
324                     {
325                         artUrl = InputManager::decodeArtURL( item->child( i )->inputItem() );
326                         if( !artUrl.isEmpty() )
327                             break;
328                     }
329                 }
330                 return artUrl;
331             }
332             else
333             {
334                 char *psz = psz_column_meta( item->inputItem(), metadata );
335                 returninfo = qfu( psz );
336                 free( psz );
337             }
338 
339             return QVariant( returninfo );
340         }
341 
342         case Qt::DecorationRole:
343         {
344             switch( columnToMeta(index.column()) )
345             {
346                 case COLUMN_TITLE:
347                 {
348                     PLItem *item = getItem( index );
349                     /* Used to segfault here because i_type wasn't always initialized */
350                     int idx = item->inputItem()->i_type;
351                     if( item->inputItem()->b_net && item->inputItem()->i_type == ITEM_TYPE_FILE )
352                         idx = ITEM_TYPE_STREAM;
353                     return QVariant( icons[idx] );
354                 }
355                 case COLUMN_COVER:
356                     /* !warn: changes tree item line height. Otherwise, override
357                      * delegate's sizehint */
358                     return getArtPixmap( index, QSize(16,16) );
359                 default:
360                     break;
361             }
362             break;
363         }
364 
365         case Qt::BackgroundRole:
366             if( isCurrent( index ) )
367                 return QVariant( QBrush( Qt::gray ) );
368             break;
369 
370         case CURRENT_ITEM_ROLE:
371             return QVariant( isCurrent( index ) );
372 
373         case CURRENT_ITEM_CHILD_ROLE:
374             return QVariant( isParent( index, currentIndex() ) );
375 
376         case LEAF_NODE_ROLE:
377             return QVariant( isLeaf( index ) );
378 
379         default:
380             break;
381     }
382 
383     return QVariant();
384 }
385 
setData(const QModelIndex & index,const QVariant & value,int role)386 bool PLModel::setData( const QModelIndex &index, const QVariant & value, int role )
387 {
388     switch( role )
389     {
390     case Qt::FontRole:
391         customFont = value.value<QFont>();
392         return true;
393     default:
394         return VLCModel::setData( index, value, role );
395     }
396 }
397 
398 /* Seek from current index toward the top and see if index is one of parent nodes */
isParent(const QModelIndex & index,const QModelIndex & current) const399 bool PLModel::isParent( const QModelIndex &index, const QModelIndex &current ) const
400 {
401     if( !index.isValid() )
402         return false;
403 
404     if( index == current )
405         return true;
406 
407     if( !current.isValid() || !current.parent().isValid() )
408         return false;
409 
410     return isParent( index, current.parent() );
411 }
412 
isLeaf(const QModelIndex & index) const413 bool PLModel::isLeaf( const QModelIndex &index ) const
414 {
415     bool b_isLeaf = false;
416 
417     vlc_playlist_locker pl_lock ( THEPL );
418 
419     playlist_item_t *plItem =
420         playlist_ItemGetById( p_playlist, itemId( index ) );
421 
422     if( plItem )
423         b_isLeaf = plItem->i_children == -1;
424 
425     return b_isLeaf;
426 }
427 
getItem(const QModelIndex & index) const428 PLItem* PLModel::getItem( const QModelIndex & index ) const
429 {
430     PLItem *item = static_cast<PLItem *>( VLCModel::getItem( index ) );
431     if ( item == NULL ) item = rootItem;
432     return item;
433 }
434 
index(const int row,const int column,const QModelIndex & parent) const435 QModelIndex PLModel::index( const int row, const int column, const QModelIndex &parent )
436                   const
437 {
438     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
439 
440     PLItem *childItem = static_cast<PLItem*>(parentItem->child( row ));
441     if( childItem )
442         return createIndex( row, column, childItem );
443     else
444         return QModelIndex();
445 }
446 
indexByPLID(const int i_plid,const int c) const447 QModelIndex PLModel::indexByPLID( const int i_plid, const int c ) const
448 {
449     return index( findByPLId( rootItem, i_plid ), c );
450 }
451 
indexByInputItem(const input_item_t * item,const int c) const452 QModelIndex PLModel::indexByInputItem( const input_item_t *item, const int c ) const
453 {
454     return index( findByInput( rootItem, item ), c );
455 }
456 
rootIndex() const457 QModelIndex PLModel::rootIndex() const
458 {
459     return index( findByPLId( rootItem, rootItem->id() ), 0 );
460 }
461 
isTree() const462 bool PLModel::isTree() const
463 {
464     return ( ( rootItem && rootItem->id() != p_playlist->p_playing->i_id )
465              || var_InheritBool( p_intf, "playlist-tree" ) );
466 }
467 
468 /* Return the index of a given item */
index(PLItem * item,int column) const469 QModelIndex PLModel::index( PLItem *item, int column ) const
470 {
471     if( !item ) return QModelIndex();
472     AbstractPLItem *parent = item->parent();
473     if( parent )
474         return createIndex( parent->lastIndexOf( item ),
475                             column, item );
476     return QModelIndex();
477 }
478 
currentIndex() const479 QModelIndex PLModel::currentIndex() const
480 {
481     input_thread_t *p_input_thread = THEMIM->getInput();
482     if( !p_input_thread ) return QModelIndex();
483     PLItem *item = findByInput( rootItem, input_GetItem( p_input_thread ) );
484     return index( item, 0 );
485 }
486 
parent(const QModelIndex & index) const487 QModelIndex PLModel::parent( const QModelIndex &index ) const
488 {
489     if( !index.isValid() ) return QModelIndex();
490 
491     PLItem *childItem = getItem( index );
492     if( !childItem )
493     {
494         msg_Err( p_playlist, "Item not found" );
495         return QModelIndex();
496     }
497 
498     PLItem *parentItem = static_cast<PLItem*>(childItem->parent());
499     if( !parentItem || parentItem == rootItem ) return QModelIndex();
500     if( !parentItem->parent() )
501     {
502         msg_Err( p_playlist, "No parent found, trying row 0. Please report this" );
503         return createIndex( 0, 0, parentItem );
504     }
505     return createIndex(parentItem->row(), 0, parentItem);
506 }
507 
rowCount(const QModelIndex & parent) const508 int PLModel::rowCount( const QModelIndex &parent ) const
509 {
510     PLItem *parentItem = parent.isValid() ? getItem( parent ) : rootItem;
511     return parentItem->childCount();
512 }
513 
514 /************************* Lookups *****************************/
findByPLId(PLItem * root,int i_id) const515 PLItem *PLModel::findByPLId( PLItem *root, int i_id ) const
516 {
517     if( !root ) return NULL;
518 
519     if( root->id() == i_id )
520         return root;
521 
522     /* traverse the tree (in depth first) iteratively to avoid stack overflow */
523 
524     struct RemainingChildren {
525         QList<AbstractPLItem *>::const_iterator next;
526         QList<AbstractPLItem *>::const_iterator end;
527     };
528 
529     QStack<RemainingChildren> stack;
530     if( root->childCount() )
531         stack.push( {root->children.cbegin(), root->children.cend()} );
532 
533     while ( !stack.isEmpty() )
534     {
535         RemainingChildren &remainingChildren = stack.top();
536 
537         PLItem *item = static_cast<PLItem *>( *remainingChildren.next );
538         if( item->id() == i_id )
539             return item;
540 
541         if( ++remainingChildren.next == remainingChildren.end )
542             /* there are no more children at this depth level */
543             stack.pop();
544 
545         if( item->childCount() )
546             stack.push( {item->children.cbegin(), item->children.cend()} );
547     }
548     return NULL;
549 }
550 
findByInput(PLItem * root,const input_item_t * input) const551 PLItem *PLModel::findByInput( PLItem *root, const input_item_t *input ) const
552 {
553     int i_id;
554     {
555         playlist_item_t *item;
556 
557         vlc_playlist_locker pl_lock ( THEPL );
558         item = playlist_ItemGetByInput( THEPL, input );
559         if( item == NULL )
560             return NULL;
561         i_id = item->i_id;
562     }
563     return findByPLId( root, i_id );
564 }
565 
findByInputLocked(PLItem * root,const input_item_t * input) const566 PLItem *PLModel::findByInputLocked( PLItem *root, const input_item_t *input ) const
567 {
568     PL_ASSERT_LOCKED;
569 
570     playlist_item_t* item = playlist_ItemGetByInput( THEPL, input );
571     if( item == NULL )
572         return NULL;
573     return findByPLId( root, item->i_id );
574 }
575 
getPLRootType() const576 PLModel::pl_nodetype PLModel::getPLRootType() const
577 {
578     vlc_playlist_locker pl_lock ( THEPL );
579 
580     /* can't rely on rootitem as it depends on view / rebuild() */
581     AbstractPLItem *plitem = rootItem;
582     while( plitem->parent() )plitem = plitem->parent();
583 
584     if( plitem->id() == p_playlist->p_playing->i_id )
585         return ROOTTYPE_CURRENT_PLAYING;
586 
587     if( p_playlist->p_media_library &&
588         plitem->id() == p_playlist->p_media_library->i_id )
589         return ROOTTYPE_MEDIA_LIBRARY;
590 
591     return ROOTTYPE_OTHER;
592 }
593 
canEdit() const594 bool PLModel::canEdit() const
595 {
596     return ( getPLRootType() != ROOTTYPE_OTHER );
597 }
598 
599 /************************* Updates handling *****************************/
600 
601 /**** Events processing ****/
processInputItemUpdate()602 void PLModel::processInputItemUpdate( )
603 {
604     input_thread_t *p_input = THEMIM->getInput();
605     if( !p_input ) return;
606 
607     PLItem *item = findByInput( rootItem, input_GetItem( p_input ) );
608     if( item ) emit currentIndexChanged( index( item, 0 ) );
609 
610     processInputItemUpdate( input_GetItem( p_input ) );
611 }
612 
processInputItemUpdate(input_item_t * p_item)613 void PLModel::processInputItemUpdate( input_item_t *p_item )
614 {
615     if( !p_item ) return;
616     PLItem *item = findByInput( rootItem, p_item );
617     if( item )
618         updateTreeItem( item );
619 }
620 
processItemRemoval(int i_pl_itemid)621 void PLModel::processItemRemoval( int i_pl_itemid )
622 {
623     if( i_pl_itemid <= 0 ) return;
624     removeItem( findByPLId( rootItem, i_pl_itemid ) );
625 }
626 
processItemAppend(int i_pl_itemid,int i_pl_itemidparent)627 void PLModel::processItemAppend( int i_pl_itemid, int i_pl_itemidparent )
628 {
629     playlist_item_t *p_item = NULL;
630     PLItem *newItem = NULL;
631     int pos;
632 
633     /* Find the Parent */
634     PLItem *nodeParentItem = findByPLId( rootItem, i_pl_itemidparent );
635     if( !nodeParentItem ) return;
636 
637     /* Search for an already matching children */
638     foreach( AbstractPLItem *existing, nodeParentItem->children )
639         if( existing->id() == i_pl_itemid ) return;
640 
641     /* Find the child */
642     {
643         vlc_playlist_locker pl_lock ( THEPL );
644 
645         p_item = playlist_ItemGetById( p_playlist, i_pl_itemid );
646         if( !p_item || p_item->i_flags & PLAYLIST_DBL_FLAG )
647             return;
648 
649         for( pos = p_item->p_parent->i_children - 1; pos >= 0; pos-- )
650             if( p_item->p_parent->pp_children[pos] == p_item ) break;
651 
652         newItem = new PLItem( p_item, nodeParentItem );
653     }
654 
655     /* We insert the newItem (children) inside the parent */
656     beginInsertRows( index( nodeParentItem, 0 ), pos, pos );
657     nodeParentItem->insertChild( newItem, pos );
658     endInsertRows();
659     if ( newItem->inputItem() == THEMIM->currentInputItem() )
660         emit currentIndexChanged( index( newItem, 0 ) );
661 
662     if( latestSearch.isEmpty() ) return;
663     filter( latestSearch, index( rootItem, 0), false /*FIXME*/ );
664 }
665 
rebuild(playlist_item_t * p_root)666 void PLModel::rebuild( playlist_item_t *p_root )
667 {
668     beginResetModel();
669 
670     {
671         vlc_playlist_locker pl_lock ( THEPL );
672 
673         if( rootItem ) rootItem->clearChildren();
674         if( p_root ) // Can be NULL
675         {
676             if ( rootItem ) delete rootItem;
677             rootItem = new PLItem( p_root );
678         }
679         assert( rootItem );
680         /* Recreate from root */
681         updateChildren( rootItem );
682     }
683 
684     /* And signal the view */
685     endResetModel();
686     if( p_root ) emit rootIndexChanged();
687 }
688 
takeItem(PLItem * item)689 void PLModel::takeItem( PLItem *item )
690 {
691     assert( item );
692     PLItem *parent = static_cast<PLItem*>(item->parent());
693     assert( parent );
694     int i_index = parent->indexOf( item );
695 
696     beginRemoveRows( index( parent, 0 ), i_index, i_index );
697     parent->takeChildAt( i_index );
698     endRemoveRows();
699 }
700 
insertChildren(PLItem * node,QList<PLItem * > & items,int i_pos)701 void PLModel::insertChildren( PLItem *node, QList<PLItem*>& items, int i_pos )
702 {
703     assert( node );
704     int count = items.count();
705     if( !count ) return;
706     beginInsertRows( index( node, 0 ), i_pos, i_pos + count - 1 );
707     for( int i = 0; i < count; i++ )
708     {
709         node->children.insert( i_pos + i, items[i] );
710         items[i]->parentItem = node;
711     }
712     endInsertRows();
713 }
714 
removeItem(PLItem * item)715 void PLModel::removeItem( PLItem *item )
716 {
717     if( !item ) return;
718 
719     if( item->parent() ) {
720         int i = item->parent()->indexOf( item );
721         beginRemoveRows( index( static_cast<PLItem*>(item->parent()), 0), i, i );
722         item->parent()->children.removeAt(i);
723         delete item;
724         endRemoveRows();
725     }
726     else delete item;
727 
728     if(item == rootItem)
729     {
730         rootItem = NULL;
731         rebuild( p_playlist->p_playing );
732     }
733 }
734 
735 /* This function must be entered WITH the playlist lock */
updateChildren(PLItem * root)736 void PLModel::updateChildren( PLItem *root )
737 {
738     playlist_item_t *p_node = playlist_ItemGetById( p_playlist, root->id() );
739     updateChildren( p_node, root );
740 }
741 
742 /* This function must be entered WITH the playlist lock */
updateChildren(playlist_item_t * p_node,PLItem * root)743 void PLModel::updateChildren( playlist_item_t *p_node, PLItem *root )
744 {
745     for( int i = 0; i < p_node->i_children ; i++ )
746     {
747         if( p_node->pp_children[i]->i_flags & PLAYLIST_DBL_FLAG ) continue;
748         PLItem *newItem =  new PLItem( p_node->pp_children[i], root );
749         root->appendChild( newItem );
750         if( p_node->pp_children[i]->i_children != -1 )
751             updateChildren( p_node->pp_children[i], newItem );
752     }
753 }
754 
755 /* Function doesn't need playlist-lock, as we don't touch playlist_item_t stuff here*/
updateTreeItem(PLItem * item)756 void PLModel::updateTreeItem( PLItem *item )
757 {
758     if( !item ) return;
759     emit dataChanged( index( item, 0 ) , index( item, columnCount( QModelIndex() ) - 1 ) );
760 }
761 
762 /************************* Actions ******************************/
763 
764 /**
765  * Deletion, don't delete items childrens if item is going to be
766  * delete allready, so we remove childrens from selection-list.
767  */
doDelete(QModelIndexList selected)768 void PLModel::doDelete( QModelIndexList selected )
769 {
770     if( !canEdit() ) return;
771 
772     while( !selected.isEmpty() )
773     {
774         QModelIndex index = selected[0];
775         selected.removeAt( 0 );
776 
777         if( index.column() != 0 ) continue;
778 
779         PLItem *item = getItem( index );
780         if( item->childCount() )
781             recurseDelete( item->children, &selected );
782 
783         PL_LOCK;
784         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
785                                                         item->id() );
786         if( p_root != NULL )
787             playlist_NodeDelete( p_playlist, p_root );
788         PL_UNLOCK;
789 
790         if( p_root != NULL )
791             removeItem( item );
792     }
793 }
794 
recurseDelete(QList<AbstractPLItem * > children,QModelIndexList * fullList)795 void PLModel::recurseDelete( QList<AbstractPLItem*> children, QModelIndexList *fullList )
796 {
797     for( int i = children.count() - 1; i >= 0 ; i-- )
798     {
799         PLItem *item = static_cast<PLItem *>(children[i]);
800         if( item->childCount() )
801             recurseDelete( item->children, fullList );
802         fullList->removeAll( index( item, 0 ) );
803     }
804 }
805 
806 /******* Volume III: Sorting and searching ********/
sort(const int column,Qt::SortOrder order)807 void PLModel::sort( const int column, Qt::SortOrder order )
808 {
809     sort( QModelIndex(), indexByPLID( rootItem->id(), 0 ) , column, order );
810 }
811 
sort(QModelIndex caller,QModelIndex rootIndex,const int column,Qt::SortOrder order)812 void PLModel::sort( QModelIndex caller, QModelIndex rootIndex, const int column, Qt::SortOrder order )
813 {
814     msg_Dbg( p_intf, "Sorting by column %i, order %i", column, order );
815 
816     int meta = columnToMeta( column );
817     if( meta == COLUMN_END || meta == COLUMN_COVER ) return;
818 
819     PLItem *item = ( rootIndex.isValid() ) ? getItem( rootIndex )
820                                            : rootItem;
821     if( !item ) return;
822 
823     input_item_t* p_caller_item = caller.isValid()
824         ? static_cast<AbstractPLItem*>( caller.internalPointer() )->inputItem()
825         : NULL;
826 
827     int i_root_id = item->id();
828 
829     QModelIndex qIndex = index( item, 0 );
830     int count = item->childCount();
831     if( count )
832     {
833         beginRemoveRows( qIndex, 0, count - 1 );
834         item->clearChildren();
835         endRemoveRows( );
836     }
837 
838     {
839         vlc_playlist_locker pl_lock ( THEPL );
840 
841         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
842                                                         i_root_id );
843         if( p_root )
844         {
845             playlist_RecursiveNodeSort( p_playlist, p_root,
846                                         i_column_sorting( meta ),
847                                         order == Qt::AscendingOrder ?
848                                             ORDER_NORMAL : ORDER_REVERSE );
849         }
850 
851         if( count )
852         {
853             beginInsertRows( qIndex, 0, count - 1 );
854             updateChildren( item );
855             endInsertRows( );
856         }
857     }
858 
859     /* if we have popup item, try to make sure that you keep that item visible */
860     if( p_caller_item )
861     {
862         QModelIndex idx = indexByInputItem( p_caller_item, 0 );
863 
864         emit currentIndexChanged( idx );
865     }
866     else if( currentIndex().isValid() )
867         emit currentIndexChanged( currentIndex() );
868 }
869 
filter(const QString & search_text,const QModelIndex & idx,bool b_recursive)870 void PLModel::filter( const QString& search_text, const QModelIndex & idx, bool b_recursive )
871 {
872     latestSearch = search_text;
873 
874     /** \todo Fire the search with a small delay ? */
875     {
876         vlc_playlist_locker pl_lock ( THEPL );
877 
878         playlist_item_t *p_root = playlist_ItemGetById( p_playlist,
879                                                         itemId( idx ) );
880         assert( p_root );
881         playlist_LiveSearchUpdate( p_playlist, p_root, qtu( search_text ),
882                                    b_recursive );
883         if( idx.isValid() )
884         {
885             PLItem *searchRoot = getItem( idx );
886 
887             beginRemoveRows( idx, 0, searchRoot->childCount() - 1 );
888             searchRoot->clearChildren();
889             endRemoveRows();
890 
891             beginInsertRows( idx, 0, searchRoot->childCount() - 1 );
892             updateChildren( searchRoot ); // The PL_LOCK is needed here
893             endInsertRows();
894 
895             return;
896         }
897     }
898 
899     rebuild();
900 }
901 
removeAll()902 void PLModel::removeAll()
903 {
904     if( rowCount() < 1 ) return;
905 
906     QModelIndexList l;
907     for( int i = 0; i < rowCount(); i++)
908     {
909         QModelIndex indexrecord = index( i, 0, QModelIndex() );
910         l.append( indexrecord );
911     }
912     doDelete(l);
913 }
914 
createNode(QModelIndex index,QString name)915 void PLModel::createNode( QModelIndex index, QString name )
916 {
917     if( name.isEmpty() )
918         return;
919 
920     vlc_playlist_locker pl_lock ( THEPL );
921 
922     index = index.parent();
923     if ( !index.isValid() ) index = rootIndex();
924     playlist_item_t *p_item = playlist_ItemGetById( p_playlist, itemId( index ) );
925     if( p_item )
926         playlist_NodeCreate( p_playlist, qtu( name ), p_item, PLAYLIST_END, 0 );
927 }
928 
renameNode(QModelIndex index,QString name)929 void PLModel::renameNode( QModelIndex index, QString name )
930 {
931     if( name.isEmpty() || !index.isValid() ) return;
932 
933     vlc_playlist_locker pl_lock ( THEPL );
934 
935     if ( !index.isValid() ) index = rootIndex();
936     input_item_t* p_input = this->getInputItem( index );
937     input_item_SetName( p_input, qtu( name ) );
938     playlist_t *p_playlist = THEPL;
939     input_item_WriteMeta( VLC_OBJECT(p_playlist), p_input );
940 }
941 
action(QAction * action,const QModelIndexList & indexes)942 bool PLModel::action( QAction *action, const QModelIndexList &indexes )
943 {
944     QModelIndex index;
945     actionsContainerType a = action->data().value<actionsContainerType>();
946 
947     switch ( a.action )
948     {
949 
950     case ACTION_PLAY:
951         if ( !indexes.empty() && indexes.first().isValid() )
952         {
953             if( isCurrent( indexes.first() ) )
954                 playlist_Resume(THEPL);
955             else
956                 activateItem( indexes.first() );
957             return true;
958         }
959         break;
960 
961     case ACTION_PAUSE:
962         if ( !indexes.empty() && indexes.first().isValid() )
963         {
964             playlist_Pause(THEPL);
965             return true;
966         }
967         break;
968 
969     case ACTION_ADDTOPLAYLIST:
970     {
971         vlc_playlist_locker pl_lock ( THEPL );
972 
973         foreach( const QModelIndex &currentIndex, indexes )
974         {
975             playlist_item_t *p_item = playlist_ItemGetById( THEPL, itemId( currentIndex ) );
976             if( !p_item ) continue;
977 
978             playlist_NodeAddCopy( THEPL, p_item,
979                                   THEPL->p_playing,
980                                   PLAYLIST_END );
981         }
982         return true;
983     }
984 
985     case ACTION_REMOVE:
986         doDelete( indexes );
987         return true;
988 
989     case ACTION_SORT:
990         if ( !indexes.empty() )
991             index = indexes.first();
992 
993         sort( index, rootIndex(),
994               a.column > 0 ? a.column - 1 : -a.column - 1,
995               a.column > 0 ? Qt::AscendingOrder : Qt::DescendingOrder );
996         return true;
997 
998     case ACTION_CLEAR:
999         removeAll();
1000         return true;
1001 
1002     case ACTION_ENQUEUEFILE:
1003         foreach( const QString &uri, a.uris )
1004             Open::openMRL( p_intf, uri.toLatin1().constData(),
1005                            false, getPLRootType() == ROOTTYPE_CURRENT_PLAYING );
1006         return true;
1007 
1008     case ACTION_ENQUEUEDIR:
1009         if( a.uris.isEmpty() ) break;
1010 
1011         Open::openMRL( p_intf, a.uris.first().toLatin1().constData(),
1012                        false, getPLRootType() == ROOTTYPE_CURRENT_PLAYING );
1013 
1014         return true;
1015 
1016     case ACTION_ENQUEUEGENERIC:
1017         foreach( const QString &uri, a.uris )
1018         {
1019             QStringList options = a.options.split( " :" );
1020             Open::openMRLwithOptions( p_intf, uri, &options, false );
1021         }
1022         return true;
1023 
1024     default:
1025         break;
1026     }
1027     return false;
1028 }
1029 
isSupportedAction(actions action,const QModelIndex & index) const1030 bool PLModel::isSupportedAction( actions action, const QModelIndex &index ) const
1031 {
1032     AbstractPLItem const* item = VLCModel::getItem( index );
1033 
1034     switch ( action )
1035     {
1036     case ACTION_ADDTOPLAYLIST:
1037         /* Only if we are not already in Current Playing */
1038         return getPLRootType() != ROOTTYPE_CURRENT_PLAYING;
1039     case ACTION_SORT:
1040         return rowCount();
1041     case ACTION_PLAY:
1042     {
1043         if( !item )
1044             return false;
1045 
1046         {
1047             vlc_playlist_locker pl_lock ( THEPL );
1048 
1049             if( playlist_Status( THEPL ) != PLAYLIST_RUNNING )
1050                 return true;
1051         }
1052 
1053         return !isCurrent( index );
1054     }
1055     case ACTION_PAUSE:
1056     {
1057         if( !isCurrent( index ) )
1058             return false;
1059 
1060         vlc_playlist_locker pl_lock ( THEPL );
1061 
1062         return playlist_Status( THEPL ) == PLAYLIST_RUNNING;
1063     }
1064     case ACTION_STREAM:
1065     case ACTION_SAVE:
1066     case ACTION_INFO:
1067         return item;
1068     case ACTION_REMOVE:
1069         return item && !item->readOnly();
1070     case ACTION_EXPLORE:
1071     {
1072         if( !item )
1073             return false;
1074 
1075         char*  psz_path = vlc_uri2path( qtu( item->getURI() ) );
1076         free(  psz_path );
1077         return psz_path != NULL;
1078     }
1079     case ACTION_CREATENODE:
1080             return canEdit() && isTree() && ( !item || !item->readOnly() );
1081     case ACTION_RENAMENODE:
1082             return item && !isLeaf( index ) && !item->readOnly();
1083     case ACTION_CLEAR:
1084             return canEdit() && rowCount();
1085     case ACTION_ENQUEUEFILE:
1086     case ACTION_ENQUEUEDIR:
1087     case ACTION_ENQUEUEGENERIC:
1088         return canEdit();
1089     case ACTION_SAVETOPLAYLIST:
1090         return getPLRootType() == ROOTTYPE_CURRENT_PLAYING && rowCount();
1091     default:
1092         return false;
1093     }
1094     return false;
1095 }
1096 
1097 /******************* Drag and Drop helper class ******************/
~PlMimeData()1098 PlMimeData::~PlMimeData()
1099 {
1100     foreach( input_item_t *p_item, _inputItems )
1101         input_item_Release( p_item );
1102 }
1103 
appendItem(input_item_t * p_item)1104 void PlMimeData::appendItem( input_item_t *p_item )
1105 {
1106     input_item_Hold( p_item );
1107     _inputItems.append( p_item );
1108 }
1109 
inputItems() const1110 QList<input_item_t*> PlMimeData::inputItems() const
1111 {
1112     return _inputItems;
1113 }
1114 
formats() const1115 QStringList PlMimeData::formats () const
1116 {
1117     QStringList fmts;
1118     fmts << "vlc/qt-input-items";
1119     return fmts;
1120 }
1121