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 ¤t ) 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 ¤tIndex, 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