1 /****************************************************************************************
2  * Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com>                  *
3  * Copyright (c) 2007 Ian Monroe <ian@monroe.nu>                                        *
4  *                                                                                      *
5  * This program is free software; you can redistribute it and/or modify it under        *
6  * the terms of the GNU General Public License as published by the Free Software        *
7  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
8  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
9  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
10  * version 3 of the license.                                                            *
11  *                                                                                      *
12  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
13  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
14  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
15  *                                                                                      *
16  * You should have received a copy of the GNU General Public License along with         *
17  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
18  ****************************************************************************************/
19 
20 #define DEBUG_PREFIX "CollectionTreeView"
21 
22 #include "CollectionTreeView.h"
23 
24 #include "AmarokMimeData.h"
25 #include "GlobalCollectionActions.h"
26 #include "PopupDropperFactory.h"
27 #include "SvgHandler.h"
28 #include "browsers/CollectionSortFilterProxyModel.h"
29 #include "browsers/CollectionTreeItemModel.h"
30 #include "context/ContextView.h"
31 #include "core/capabilities/ActionsCapability.h"
32 #include "core/capabilities/BookmarkThisCapability.h"
33 #include "core/collections/CollectionLocation.h"
34 #include "core/collections/MetaQueryMaker.h"
35 #include "core/collections/QueryMaker.h"
36 #include "core/meta/Meta.h"
37 #include "core/support/Amarok.h"
38 #include "core/support/Debug.h"
39 #include "core-impl/collections/support/CollectionManager.h"
40 #include "core-impl/collections/support/TextualQueryFilter.h"
41 #include "core-impl/collections/support/TrashCollectionLocation.h"
42 #include "dialogs/TagDialog.h"
43 #include "playlist/PlaylistModelStack.h"
44 #include "scripting/scriptengine/AmarokCollectionViewScript.h"
45 
46 #include <QAction>
47 #include <QIcon>
48 #include <QComboBox>
49 #include <QMenu>
50 
51 #include <QContextMenuEvent>
52 #include <QHash>
53 #include <QMouseEvent>
54 #include <QQueue>
55 #include <QSortFilterProxyModel>
56 #include <QScrollBar>
57 
58 using namespace Collections;
59 
60 /**
61  * RAII class to perform restoring of the scroll position once all queries are
62  * finished. DelayedScroller auto-deletes itself once its job is over (ot if it finds
63  * it is useless).
64  */
65 class DelayedScroller : public QObject
66 {
67     Q_OBJECT
68 
69     public:
DelayedScroller(CollectionTreeView * treeView,CollectionTreeItemModelBase * treeModel,const QModelIndex & treeModelScrollToIndex,int topOffset)70         DelayedScroller( CollectionTreeView *treeView,
71                          CollectionTreeItemModelBase *treeModel,
72                          const QModelIndex &treeModelScrollToIndex, int topOffset )
73             : QObject( treeView )
74             , m_treeView( treeView )
75             , m_treeModel( treeModel )
76             , m_topOffset( topOffset )
77         {
78             connect( treeModel, &CollectionTreeItemModelBase::destroyed,
79                      this, &DelayedScroller::deleteLater );
80             connect( treeModel, &CollectionTreeItemModelBase::allQueriesFinished,
81                      this, &DelayedScroller::slotScroll );
82 
83             m_scrollToItem = m_treeModel->treeItem( treeModelScrollToIndex );
84             if( m_scrollToItem )
85                 connect( m_scrollToItem, &CollectionTreeItem::destroyed, this, &DelayedScroller::deleteLater );
86             else
87                 deleteLater(); // nothing to do
88         }
89 
90     private Q_SLOTS:
slotScroll()91         void slotScroll()
92         {
93             deleteLater();
94             QModelIndex idx = m_treeModel->itemIndex( m_scrollToItem );
95             QSortFilterProxyModel *filterModel = m_treeView->filterModel();
96             idx = filterModel ? filterModel->mapFromSource( idx ) : QModelIndex();
97             QScrollBar *scrollBar = m_treeView->verticalScrollBar();
98             if( !idx.isValid() || !scrollBar )
99                 return;
100 
101             int newTopOffset = m_treeView->visualRect( idx ).top();
102             scrollBar->setValue( scrollBar->value() + (newTopOffset - m_topOffset) );
103         }
104 
105     private:
106         CollectionTreeView *m_treeView;
107         CollectionTreeItemModelBase *m_treeModel;
108         CollectionTreeItem *m_scrollToItem;
109         int m_topOffset;
110 };
111 
112 /**
113  * RAII class to auto-expand collection tree entries after filtering.
114  * AutoExpander auto-deletes itself once its job is over (or if it finds
115  * it is useless).
116  */
117 class AutoExpander : public QObject
118 {
119     Q_OBJECT
120 
121     public:
AutoExpander(CollectionTreeView * treeView,CollectionTreeItemModelBase * treeModel,QAbstractItemModel * filterModel)122         AutoExpander( CollectionTreeView *treeView,
123                       CollectionTreeItemModelBase *treeModel,
124                       QAbstractItemModel *filterModel)
125             : QObject( treeView )
126             , m_treeView( treeView )
127             , m_filterModel( filterModel )
128         {
129             connect( filterModel, &QObject::destroyed, this, &QObject::deleteLater );
130             connect( treeModel, &CollectionTreeItemModelBase::allQueriesFinished, this, &AutoExpander::slotExpandMore );
131 
132             // start with the root index
133             m_indicesToCheck.enqueue( QModelIndex() );
134             slotExpandMore();
135         }
136 
137     private Q_SLOTS:
slotExpandMore()138         void slotExpandMore()
139         {
140             const int maxChildrenToExpand = 3;
141 
142             QQueue<QModelIndex> pendingIndices;
143             while( !m_indicesToCheck.isEmpty() )
144             {
145                 if( !m_filterModel )
146                     return;
147 
148                 QModelIndex current = m_indicesToCheck.dequeue();
149 
150                 if( m_filterModel->canFetchMore( current ) )
151                 {
152                     m_filterModel->fetchMore( current );
153                     pendingIndices.enqueue( current );
154                     continue;
155                 }
156 
157                 if( m_filterModel->rowCount( current ) <= maxChildrenToExpand )
158                 {
159                     m_treeView->expand( current );
160                     for( int i = 0; i < m_filterModel->rowCount( current ); i++ )
161                         m_indicesToCheck.enqueue( m_filterModel->index( i, 0, current ) );
162                 }
163             }
164 
165             if( pendingIndices.isEmpty() )
166                 // nothing left to do
167                 deleteLater();
168             else
169                 // process pending indices when queries finish
170                 m_indicesToCheck.swap( pendingIndices );
171         }
172 
173     private:
174         Q_DISABLE_COPY(AutoExpander)
175 
176         CollectionTreeView *m_treeView;
177         QPointer<QAbstractItemModel> m_filterModel;
178         QQueue<QModelIndex> m_indicesToCheck;
179 };
180 
CollectionTreeView(QWidget * parent)181 CollectionTreeView::CollectionTreeView( QWidget *parent)
182     : Amarok::PrettyTreeView( parent )
183     , m_filterModel( 0 )
184     , m_treeModel( 0 )
185     , m_pd( 0 )
186     , m_appendAction( 0 )
187     , m_loadAction( 0 )
188     , m_editAction( 0 )
189     , m_organizeAction( 0 )
190     , m_ongoingDrag( false )
191 {
192     setSortingEnabled( true );
193     setFocusPolicy( Qt::StrongFocus );
194     sortByColumn( 0, Qt::AscendingOrder );
195     setSelectionMode( QAbstractItemView::ExtendedSelection );
196     setSelectionBehavior( QAbstractItemView::SelectRows );
197     setEditTriggers( EditKeyPressed );
198 
199     setDragDropMode( QAbstractItemView::DragDrop );
200 
201     connect( this, &CollectionTreeView::collapsed,
202              this, &CollectionTreeView::slotCollapsed );
203     connect( this, &CollectionTreeView::expanded,
204              this, &CollectionTreeView::slotExpanded );
205 }
206 
207 void
setModel(QAbstractItemModel * model)208 CollectionTreeView::setModel( QAbstractItemModel *model )
209 {
210     if( m_treeModel )
211         disconnect( m_treeModel, 0, this, 0);
212 
213     m_treeModel = qobject_cast<CollectionTreeItemModelBase *>( model );
214     if( !m_treeModel )
215         return;
216 
217     connect( m_treeModel, &CollectionTreeItemModelBase::allQueriesFinished,
218              this, &CollectionTreeView::slotCheckAutoExpand );
219     connect( m_treeModel, &CollectionTreeItemModelBase::expandIndex,
220              this, &CollectionTreeView::slotExpandIndex );
221 
222     if( m_filterModel )
223         m_filterModel->deleteLater();
224     m_filterModel = new CollectionSortFilterProxyModel( this );
225     m_filterModel->setSourceModel( model );
226 
227     QTreeView::setModel( m_filterModel );
228 
229     QTimer::singleShot( 0, this, &CollectionTreeView::slotCheckAutoExpandReally );
230 }
231 
~CollectionTreeView()232 CollectionTreeView::~CollectionTreeView()
233 {
234     // we don't own m_treeModel pointer
235     // m_filterModel will get deleted by QObject parentship
236 }
237 
238 void
setLevels(const QList<CategoryId::CatMenuId> & levels)239 CollectionTreeView::setLevels( const QList<CategoryId::CatMenuId> &levels )
240 {
241     if( m_treeModel )
242         m_treeModel->setLevels( levels );
243 }
244 
245 QList<CategoryId::CatMenuId>
levels() const246 CollectionTreeView::levels() const
247 {
248     if( m_treeModel )
249         return m_treeModel->levels();
250     return QList<CategoryId::CatMenuId>();
251 }
252 
253 void
setLevel(int level,CategoryId::CatMenuId type)254 CollectionTreeView::setLevel( int level, CategoryId::CatMenuId type )
255 {
256     if( !m_treeModel )
257         return;
258     QList<CategoryId::CatMenuId> levels = m_treeModel->levels();
259     if( type == CategoryId::None )
260     {
261         while( levels.count() >= level )
262             levels.removeLast();
263     }
264     else
265     {
266         levels.removeAll( type );
267         levels[level] = type;
268     }
269     setLevels( levels );
270 }
271 
272 QSortFilterProxyModel *
filterModel() const273 CollectionTreeView::filterModel() const
274 {
275     return m_filterModel;
276 }
277 
278 void
contextMenuEvent(QContextMenuEvent * event)279 CollectionTreeView::contextMenuEvent( QContextMenuEvent *event )
280 {
281     if( !m_treeModel )
282         return;
283 
284     QModelIndex index = indexAt( event->pos() );
285     if( !index.isValid() )
286     {
287         Amarok::PrettyTreeView::contextMenuEvent( event );
288         return;
289     }
290 
291     QModelIndexList indices = selectedIndexes();
292 
293     // if previously selected indices do not contain the index of the item
294     // currently under the mouse when context menu is invoked.
295     if( !indices.contains( index ) )
296     {
297         indices.clear();
298         indices << index;
299         setCurrentIndex( index );
300     }
301 
302     //TODO: get rid of this, it's a hack.
303     // Put remove actions in model so we don't need access to the internal pointer in view
304     if( m_filterModel )
305     {
306         QModelIndexList tmp;
307         foreach( const QModelIndex &idx, indices )
308         {
309             tmp.append( m_filterModel->mapToSource( idx ) );
310         }
311         indices = tmp;
312     }
313 
314     // Abort if nothing is selected
315     if( indices.isEmpty() )
316         return;
317 
318     m_currentItems.clear();
319     foreach( const QModelIndex &index, indices )
320     {
321         if( index.isValid() && index.internalPointer() )
322             m_currentItems.insert(
323                             static_cast<CollectionTreeItem *>( index.internalPointer() )
324                         );
325     }
326 
327     QMenu menu( this );
328 
329     // Destroy the menu when the model is reset (collection update), so that we don't
330     // operate on invalid data. see BUG 190056
331     connect( m_treeModel, &CollectionTreeItemModelBase::modelReset, &menu, &QMenu::deleteLater );
332 
333     // create basic actions
334     QActionList actions = createBasicActions( indices );
335     foreach( QAction *action, actions ) {
336         menu.addAction( action );
337     }
338     menu.addSeparator();
339     actions.clear();
340 
341     QActionList albumActions = createCustomActions( indices );
342     QMenu menuAlbum( i18n( "Album" )  );
343     foreach( QAction *action, albumActions )
344     {
345         if( !action->parent() )
346             action->setParent( &menuAlbum );
347     }
348 
349     if( albumActions.count() > 1 )
350     {
351         menuAlbum.addActions( albumActions );
352         menuAlbum.setIcon( QIcon::fromTheme( QStringLiteral("filename-album-amarok") ) );
353         menu.addMenu( &menuAlbum );
354         menu.addSeparator();
355     }
356     else if( albumActions.count() == 1 )
357     {
358         menu.addActions( albumActions );
359     }
360 
361     QActionList collectionActions = createCollectionActions( indices );
362     QMenu menuCollection( i18n( "Collection" ) );
363     foreach( QAction *action, collectionActions )
364     {
365         if( !action->parent() )
366             action->setParent( &menuCollection );
367     }
368 
369     if( collectionActions.count() > 1 )
370     {
371         menuCollection.setIcon( QIcon::fromTheme( QStringLiteral("drive-harddisk") ) );
372         menuCollection.addActions( collectionActions );
373         menu.addMenu( &menuCollection );
374         menu.addSeparator();
375     }
376     else if( collectionActions.count() == 1 )
377     {
378         menu.addActions( collectionActions );
379     }
380 
381     m_currentCopyDestination = getCopyActions( indices );
382     m_currentMoveDestination = getMoveActions( indices );
383 
384     if( !m_currentCopyDestination.empty() )
385     {
386         QMenu *copyMenu = new QMenu( i18n( "Copy to Collection" ), &menu );
387         copyMenu->setIcon( QIcon::fromTheme( QStringLiteral("edit-copy") ) );
388         copyMenu->addActions( m_currentCopyDestination.keys() );
389         menu.addMenu( copyMenu );
390     }
391 
392     //Move = copy + delete from source
393     if( !m_currentMoveDestination.empty() )
394     {
395         QMenu *moveMenu = new QMenu( i18n( "Move to Collection" ), &menu );
396         moveMenu->setIcon( QIcon::fromTheme( QStringLiteral("go-jump") ) );
397         moveMenu->addActions( m_currentMoveDestination.keys() );
398         menu.addMenu( moveMenu );
399     }
400 
401     // create trash and delete actions
402     if( onlyOneCollection( indices ) )
403     {
404         Collection *collection = getCollection( indices.first() );
405         if( collection && collection->isWritable() )
406         {
407             //TODO: don't recreate action
408             QAction *trashAction = new QAction( QIcon::fromTheme( QStringLiteral("user-trash") ),
409                                                 i18n( "Move Tracks to Trash" ),
410                                                 &menu );
411             trashAction->setProperty( "popupdropper_svg_id", "delete" );
412             // key shortcut is only for display purposes here, actual one is
413             // determined by View in Model/View classes
414             trashAction->setShortcut( Qt::Key_Delete );
415             connect( trashAction, &QAction::triggered,
416                      this, &CollectionTreeView::slotTrashTracks );
417             menu.addAction( trashAction );
418 
419             QAction *deleteAction = new QAction( QIcon::fromTheme( QStringLiteral("remove-amarok") ),
420                                                  i18n( "Delete Tracks" ),
421                                                  &menu );
422             deleteAction->setProperty( "popupdropper_svg_id", "delete" );
423             // key shortcut is only for display purposes here, actual one is
424             // determined by View in Model/View classes
425             deleteAction->setShortcut( Qt::SHIFT + Qt::Key_Delete );
426             connect( deleteAction, &QAction::triggered, this, &CollectionTreeView::slotDeleteTracks );
427             menu.addAction( deleteAction );
428         }
429     }
430 
431     // add extended actions
432     menu.addSeparator();
433     actions += createExtendedActions( indices );
434     foreach( QAction *action, actions ) {
435         menu.addAction( action );
436     }
437     AmarokScript::AmarokCollectionViewScript::createScriptedActions( menu, indices );
438 
439     menu.exec( event->globalPos() );
440 }
441 
442 void
mouseDoubleClickEvent(QMouseEvent * event)443 CollectionTreeView::mouseDoubleClickEvent( QMouseEvent *event )
444 {
445     if( event->button() == Qt::MidButton )
446     {
447         event->accept();
448         return;
449     }
450 
451     QModelIndex index = indexAt( event->pos() );
452     if( !index.isValid() )
453     {
454         event->accept();
455         return;
456     }
457 
458     // code copied in PlaylistBrowserView::mouseDoubleClickEvent(), keep in sync
459     // mind bug 279513
460     bool isExpandable = model()->hasChildren( index );
461     bool wouldExpand = !visualRect( index ).contains( event->pos() ) || // clicked outside item, perhaps on expander icon
462                        ( isExpandable && !style()->styleHint( QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this ) ); // we're in doubleClick
463     if( event->button() == Qt::LeftButton &&
464         event->modifiers() == Qt::NoModifier &&
465         !wouldExpand )
466     {
467         CollectionTreeItem *item = getItemFromIndex( index );
468         playChildTracks( item, Playlist::OnDoubleClickOnSelectedItems );
469         event->accept();
470         return;
471     }
472 
473     PrettyTreeView::mouseDoubleClickEvent( event );
474 }
475 
476 void
mouseReleaseEvent(QMouseEvent * event)477 CollectionTreeView::mouseReleaseEvent( QMouseEvent *event )
478 {
479     if( m_pd )
480     {
481         connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::deleteLater );
482         m_pd->hide();
483         m_pd = 0;
484     }
485 
486     QModelIndex index = indexAt( event->pos() );
487     if( !index.isValid() )
488     {
489         PrettyTreeView::mouseReleaseEvent( event );
490         return;
491     }
492 
493     if( event->button() == Qt::MidButton )
494     {
495         CollectionTreeItem *item = getItemFromIndex( index );
496         playChildTracks( item, Playlist::OnMiddleClickOnSelectedItems );
497         event->accept();
498         return;
499     }
500 
501     PrettyTreeView::mouseReleaseEvent( event );
502 }
503 
504 CollectionTreeItem *
getItemFromIndex(QModelIndex & index)505 CollectionTreeView::getItemFromIndex( QModelIndex &index )
506 {
507     QModelIndex filteredIndex;
508     if( m_filterModel )
509         filteredIndex = m_filterModel->mapToSource( index );
510     else
511         filteredIndex = index;
512 
513     if( !filteredIndex.isValid() )
514     {
515         return 0;
516     }
517 
518     return static_cast<CollectionTreeItem *>( filteredIndex.internalPointer() );
519 }
520 
521 void
keyPressEvent(QKeyEvent * event)522 CollectionTreeView::keyPressEvent( QKeyEvent *event )
523 {
524     QModelIndexList indices = selectedIndexes();
525     if( indices.isEmpty() )
526     {
527         Amarok::PrettyTreeView::keyPressEvent( event );
528         return;
529     }
530 
531     if( m_filterModel )
532     {
533         QModelIndexList tmp;
534         foreach( const QModelIndex &idx, indices )
535             tmp.append( m_filterModel->mapToSource( idx ) );
536         indices = tmp;
537     }
538 
539     m_currentItems.clear();
540     foreach( const QModelIndex &index, indices )
541     {
542         if( index.isValid() && index.internalPointer() )
543         {
544             m_currentItems.insert(
545                         static_cast<CollectionTreeItem *>( index.internalPointer() ) );
546         }
547     }
548 
549     QModelIndex current = currentIndex();
550     switch( event->key() )
551     {
552         case Qt::Key_Enter:
553         case Qt::Key_Return:
554             playChildTracks( m_currentItems, Playlist::OnReturnPressedOnSelectedItems );
555             return;
556         case Qt::Key_Delete:
557             if( !onlyOneCollection( indices ) )
558                 break;
559             removeTracks( m_currentItems, !( event->modifiers() & Qt::ShiftModifier ) );
560             return;
561         case Qt::Key_Up:
562             if( current.parent() == QModelIndex() && current.row() == 0 )
563             {
564                 Q_EMIT leavingTree();
565                 return;
566             }
567             break;
568         case Qt::Key_Down:
569             break;
570         // L and R should magically work when we get a patched version of qt
571         case Qt::Key_Right:
572         case Qt::Key_Direction_R:
573             expand( current );
574             return;
575         case Qt::Key_Left:
576         case Qt::Key_Direction_L:
577             collapse( current );
578             return;
579         default:
580             break;
581     }
582     Amarok::PrettyTreeView::keyPressEvent( event );
583 }
584 
585 void
dragEnterEvent(QDragEnterEvent * event)586 CollectionTreeView::dragEnterEvent( QDragEnterEvent *event )
587 {
588     // We want to indicate to the user that dropping to the same collection is not possible.
589     // CollectionTreeItemModel therefore needs to know what collection the drag originated
590     // so that is can play with Qt::ItemIsDropEnabled in flags()
591     const AmarokMimeData *mimeData =
592             qobject_cast<const AmarokMimeData *>( event->mimeData() );
593     if( mimeData ) // drag from within Amarok
594     {
595         QSet<Collection *> srcCollections;
596         foreach( Meta::TrackPtr track, mimeData->tracks() )
597         {
598             srcCollections.insert( track->collection() );
599         }
600         m_treeModel->setDragSourceCollections( srcCollections );
601     }
602     QAbstractItemView::dragEnterEvent( event );
603 }
604 
605 void
dragMoveEvent(QDragMoveEvent * event)606 CollectionTreeView::dragMoveEvent( QDragMoveEvent *event )
607 {
608     // this mangling is not needed for Copy/Move distinction to work, it is only needed
609     // for mouse cursor changing to work
610     if( (event->keyboardModifiers() & Qt::ShiftModifier)
611         && (event->possibleActions() & Qt::MoveAction) )
612     {
613         event->setDropAction( Qt::MoveAction );
614     }
615     else if( event->possibleActions() & Qt::CopyAction )
616     {
617         event->setDropAction( Qt::CopyAction );
618     }
619 
620     QTreeView::dragMoveEvent( event );
621 }
622 
623 void
startDrag(Qt::DropActions supportedActions)624 CollectionTreeView::startDrag(Qt::DropActions supportedActions)
625 {
626     DEBUG_BLOCK
627 
628     // Make sure that the left mouse button is actually pressed. Otherwise we're prone to
629     // mis-detecting clicks as dragging
630     if( !( QApplication::mouseButtons() & Qt::LeftButton ) )
631         return;
632 
633     QModelIndexList indices = selectedIndexes();
634     if( indices.isEmpty() )
635         return;
636 
637     // When a parent item is dragged, startDrag() is called a bunch of times. Here we
638     // prevent that:
639     if( m_ongoingDrag )
640         return;
641     m_ongoingDrag = true;
642 
643     if( !m_pd )
644         m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() );
645 
646     if( m_pd && m_pd->isHidden() )
647     {
648         if( m_filterModel )
649         {
650             QModelIndexList tmp;
651             foreach( const QModelIndex &idx, indices )
652             {
653                 tmp.append( m_filterModel->mapToSource( idx ) );
654             }
655             indices = tmp;
656         }
657 
658         QActionList actions = createBasicActions( indices );
659 
660         QFont font;
661         font.setPointSize( 16 );
662         font.setBold( true );
663 
664         foreach( QAction * action, actions )
665             m_pd->addItem( The::popupDropperFactory()->createItem( action ) );
666 
667         m_currentCopyDestination = getCopyActions( indices );
668         m_currentMoveDestination = getMoveActions( indices );
669 
670         m_currentItems.clear();
671         foreach( const QModelIndex &index, indices )
672         {
673             if( index.isValid() && index.internalPointer() )
674             {
675                 m_currentItems.insert(
676                         static_cast<CollectionTreeItem *>( index.internalPointer() ) );
677             }
678         }
679 
680         PopupDropperItem *subItem;
681 
682         actions = createExtendedActions( indices );
683 
684         PopupDropper *morePud = 0;
685         if( actions.count() > 1 )
686         {
687             morePud = The::popupDropperFactory()->createPopupDropper( 0, true );
688 
689             foreach( QAction *action, actions )
690                 morePud->addItem( The::popupDropperFactory()->createItem( action ) );
691         }
692         else
693             m_pd->addItem( The::popupDropperFactory()->createItem( actions[0] ) );
694 
695         //TODO: Keep bugging i18n team about problems with 3 dots
696         if ( actions.count() > 1 )
697         {
698             subItem = m_pd->addSubmenu( &morePud, i18n( "More..." )  );
699             The::popupDropperFactory()->adjustItem( subItem );
700         }
701 
702         m_pd->show();
703     }
704 
705     QTreeView::startDrag( supportedActions );
706     debug() << "After the drag!";
707 
708     if( m_pd )
709     {
710         debug() << "clearing PUD";
711         connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::clear );
712         m_pd->hide();
713     }
714 
715     m_ongoingDrag = false;
716 }
717 
718 void
selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)719 CollectionTreeView::selectionChanged( const QItemSelection &selected,
720                                       const QItemSelection &deselected )
721 {
722     QModelIndexList indexes = selected.indexes();
723 
724     QModelIndexList changedIndexes = indexes;
725     changedIndexes << deselected.indexes();
726     foreach( const QModelIndex &index, changedIndexes )
727         update( index );
728 
729     if( indexes.count() < 1 )
730         return;
731 
732     QModelIndex index;
733     if( m_filterModel )
734         index = m_filterModel->mapToSource( indexes[0] );
735     else
736         index = indexes[0];
737 
738     CollectionTreeItem *item =
739             static_cast<CollectionTreeItem *>( index.internalPointer() );
740     Q_EMIT( itemSelected ( item ) );
741 }
742 
743 void
slotCollapsed(const QModelIndex & index)744 CollectionTreeView::slotCollapsed( const QModelIndex &index )
745 {
746     if( !m_treeModel )
747         return;
748     if( m_filterModel )
749         m_treeModel->slotCollapsed( m_filterModel->mapToSource( index ) );
750     else
751         m_treeModel->slotCollapsed( index );
752 }
753 
754 void
slotExpanded(const QModelIndex & index)755 CollectionTreeView::slotExpanded( const QModelIndex &index )
756 {
757     if( !m_treeModel )
758         return;
759     if( m_filterModel )
760         m_treeModel->slotExpanded( m_filterModel->mapToSource( index ));
761     else
762         m_treeModel->slotExpanded( index );
763 }
764 
765 void
slotExpandIndex(const QModelIndex & index)766 CollectionTreeView::slotExpandIndex( const QModelIndex &index )
767 {
768     if( !m_treeModel )
769         return;
770     if( m_filterModel )
771         expand( m_filterModel->mapFromSource( index ) );
772 }
773 
774 void
slotCheckAutoExpand(bool reallyExpand)775 CollectionTreeView::slotCheckAutoExpand( bool reallyExpand )
776 {
777     if( !m_filterModel || !reallyExpand )
778         return;
779 
780     // auto-deletes itself:
781     new AutoExpander( this, m_treeModel, m_filterModel );
782 }
783 
784 void
playChildTracks(CollectionTreeItem * item,Playlist::AddOptions insertMode)785 CollectionTreeView::playChildTracks( CollectionTreeItem *item, Playlist::AddOptions insertMode )
786 {
787     QSet<CollectionTreeItem*> items;
788     items.insert( item );
789 
790     playChildTracks( items, insertMode );
791 }
792 
793 void
playChildTracks(const QSet<CollectionTreeItem * > & items,Playlist::AddOptions insertMode)794 CollectionTreeView::playChildTracks( const QSet<CollectionTreeItem *> &items,
795                                      Playlist::AddOptions insertMode )
796 {
797     if( !m_treeModel )
798         return;
799     //Ensure that if a parent and child are both selected we ignore the child
800     QSet<CollectionTreeItem *> parents( cleanItemSet( items ) );
801 
802     //Store the type of playlist insert to be done and cause a slot to be invoked when the tracklist has been generated.
803     AmarokMimeData *mime = dynamic_cast<AmarokMimeData*>(
804                 m_treeModel->mimeData( QList<CollectionTreeItem *>::fromSet( parents ) ) );
805     m_playChildTracksMode.insert( mime, insertMode );
806     connect( mime, &AmarokMimeData::trackListSignal,
807              this, &CollectionTreeView::playChildTracksSlot );
808     mime->getTrackListSignal();
809 }
810 
811 void
playChildTracksSlot(Meta::TrackList list)812 CollectionTreeView::playChildTracksSlot( Meta::TrackList list ) //slot
813 {
814     AmarokMimeData *mime = dynamic_cast<AmarokMimeData *>( sender() );
815 
816     Playlist::AddOptions insertMode = m_playChildTracksMode.take( mime );
817 
818     qStableSort( list.begin(), list.end(), Meta::Track::lessThan );
819     The::playlistController()->insertOptioned( list, insertMode );
820 
821     mime->deleteLater();
822 }
823 
824 void
organizeTracks(const QSet<CollectionTreeItem * > & items) const825 CollectionTreeView::organizeTracks( const QSet<CollectionTreeItem *> &items ) const
826 {
827     DEBUG_BLOCK
828     if( !items.count() )
829         return;
830 
831     //Create query based upon items, ensuring that if a parent and child are both
832     //selected we ignore the child
833     Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
834     if( !qm )
835         return;
836 
837     CollectionTreeItem *item = items.toList().first();
838     while( item->isDataItem() )
839         item = item->parent();
840 
841     Collection *coll = item->parentCollection();
842     CollectionLocation *location = coll->location();
843     if( !location->isOrganizable() )
844     {
845         debug() << "Collection not organizable";
846         //how did we get here??
847         delete location;
848         delete qm;
849         return;
850     }
851     location->prepareMove( qm, coll->location() );
852 }
853 
854 void
copySelectedToLocalCollection()855 CollectionTreeView::copySelectedToLocalCollection()
856 {
857     DEBUG_BLOCK
858 
859     // Get the local collection
860     Collections::Collection *collection = 0;
861     const QList<Collections::Collection*> collections = CollectionManager::instance()->collections().keys();
862 
863     foreach( collection, collections )
864     {
865         if ( collection->collectionId() == QLatin1String("localCollection") )
866             break;
867     }
868 
869     if( !collection )
870         return;
871 
872     // Get selected items
873     QModelIndexList indexes = selectedIndexes();
874     if( m_filterModel )
875     {
876         QModelIndexList tmp;
877         foreach( const QModelIndex &idx, indexes )
878             tmp.append( m_filterModel->mapToSource( idx ) );
879         indexes = tmp;
880     }
881 
882     m_currentItems.clear();
883     foreach( const QModelIndex &index, indexes )
884     {
885         if( index.isValid() && index.internalPointer() )
886             m_currentItems.insert( static_cast<CollectionTreeItem *>( index.internalPointer() ) );
887     }
888 
889     copyTracks( m_currentItems, collection, false );
890 }
891 
892 void
copyTracks(const QSet<CollectionTreeItem * > & items,Collection * destination,bool removeSources) const893 CollectionTreeView::copyTracks( const QSet<CollectionTreeItem *> &items,
894                                 Collection *destination, bool removeSources ) const
895 {
896     DEBUG_BLOCK
897 
898     if( !destination )
899     {
900         warning() << "collection is not writable (0-pointer)! Aborting";
901         return;
902     }
903     if( !destination->isWritable() )
904     {
905         warning() << "collection " << destination->prettyName() << " is not writable! Aborting";
906         return;
907     }
908     //copied from organizeTracks. create a method for this somewhere
909     if( !items.count() )
910     {
911         warning() << "No items to copy! Aborting";
912         return;
913     }
914 
915     //Create query based upon items, ensuring that if a parent and child are both selected we ignore the child
916     Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
917     if( !qm )
918     {
919         warning() << "could not get qm!";
920         return;
921     }
922 
923     CollectionTreeItem *item = items.toList().first();
924     while( item->isDataItem() )
925     {
926         item = item->parent();
927     }
928     Collection *coll = item->parentCollection();
929     CollectionLocation *source = coll->location();
930     CollectionLocation *dest = destination->location();
931     if( removeSources )
932     {
933         if( !source->isWritable() ) //error
934         {
935             warning() << "We can not write to ze source!!! OMGooses!";
936             delete dest;
937             delete source;
938             delete qm;
939             return;
940         }
941 
942         debug() << "starting source->prepareMove";
943         source->prepareMove( qm, dest );
944     }
945     else
946     {
947         debug() << "starting source->prepareCopy";
948         source->prepareCopy( qm, dest );
949     }
950 }
951 
952 void
removeTracks(const QSet<CollectionTreeItem * > & items,bool useTrash) const953 CollectionTreeView::removeTracks( const QSet<CollectionTreeItem *> &items,
954                                   bool useTrash ) const
955 {
956     DEBUG_BLOCK
957 
958     //copied from organizeTracks. create a method for this somewhere
959     if( !items.count() )
960         return;
961 
962     //Create query based upon items, ensuring that if a parent and child are both selected we ignore the child
963     Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
964     if( !qm )
965         return;
966 
967     CollectionTreeItem *item = items.toList().first();
968     while( item->isDataItem() )
969         item = item->parent();
970     Collection *coll = item->parentCollection();
971 
972     CollectionLocation *source = coll->location();
973     if( !source->isWritable() ) //error
974     {
975         warning() << "We can not write to ze source!!! OMGooses!";
976         delete source;
977         delete qm;
978         return;
979     }
980 
981     if( useTrash )
982     {
983         TrashCollectionLocation *trash = new TrashCollectionLocation();
984         source->prepareMove( qm, trash );
985     }
986     else
987         source->prepareRemove( qm );
988 }
989 
990 void
editTracks(const QSet<CollectionTreeItem * > & items) const991 CollectionTreeView::editTracks( const QSet<CollectionTreeItem *> &items ) const
992 {
993     //Create query based upon items, ensuring that if a parent and child are both
994     //selected we ignore the child
995     Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
996     if( !qm )
997         return;
998 
999     (void)new TagDialog( qm ); //the dialog will show itself automatically as soon as it is ready
1000 }
1001 
1002 void
slotSetFilter(const QString & filter)1003 CollectionTreeView::slotSetFilter( const QString &filter )
1004 {
1005     QString currentFilter = m_treeModel ? m_treeModel->currentFilter() : QString();
1006     if( !m_filterModel || !m_treeModel || filter == currentFilter )
1007         return;
1008 
1009     // special case: transitioning from non-empty to empty buffer
1010     // -> trigger later restoring of the scroll position
1011     if( filter.isEmpty() ) // currentFilter must not be empty then (see earlier check)
1012     {
1013         // take first item, descending to leaf ones if expanded. There may be better
1014         // ways to determine what item should stay "fixed".
1015         QModelIndex scrollToIndex = m_filterModel->index( 0, 0 );
1016         while( isExpanded( scrollToIndex ) && m_filterModel->rowCount( scrollToIndex ) > 0 )
1017             scrollToIndex = scrollToIndex.child( 0, 0 );
1018         int topOffset = visualRect( scrollToIndex ).top();
1019 
1020         QModelIndex bottomIndex = m_filterModel->mapToSource( scrollToIndex );
1021         // if we have somewhere to scroll to after filter is cleared...
1022         if( bottomIndex.isValid() )
1023             // auto-destroys itself
1024             new DelayedScroller( this, m_treeModel, bottomIndex, topOffset );
1025     }
1026     m_treeModel->setCurrentFilter( filter );
1027 }
1028 
1029 void
slotAddFilteredTracksToPlaylist()1030 CollectionTreeView::slotAddFilteredTracksToPlaylist()
1031 {
1032     if( !m_treeModel )
1033         return;
1034 
1035     // disconnect any possible earlier connection we've done
1036     disconnect( m_treeModel, &CollectionTreeItemModelBase::allQueriesFinished,
1037                 this, &CollectionTreeView::slotAddFilteredTracksToPlaylist );
1038 
1039     if( m_treeModel->hasRunningQueries() )
1040         // wait for the queries to finish
1041         connect( m_treeModel, &CollectionTreeItemModelBase::allQueriesFinished,
1042                  this, &CollectionTreeView::slotAddFilteredTracksToPlaylist );
1043     else
1044     {
1045         // yay, we can add the tracks now
1046         QSet<CollectionTreeItem *> items;
1047         for( int row = 0; row < m_treeModel->rowCount(); row++ )
1048         {
1049             QModelIndex idx = m_treeModel->index( row, 0 );
1050             CollectionTreeItem *item = idx.isValid()
1051                     ? static_cast<CollectionTreeItem *>( idx.internalPointer() ) : 0;
1052             if( item )
1053                 items.insert( item );
1054         }
1055         if( !items.isEmpty() )
1056             playChildTracks( items, Playlist::OnAppendToPlaylistAction );
1057         Q_EMIT addingFilteredTracksDone();
1058     }
1059 }
1060 
1061 QActionList
createBasicActions(const QModelIndexList & indices)1062 CollectionTreeView::createBasicActions( const QModelIndexList &indices )
1063 {
1064     QActionList actions;
1065 
1066     if( !indices.isEmpty() )
1067     {
1068         if( m_appendAction == 0 )
1069         {
1070             m_appendAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-add-amarok") ),
1071                                           i18n( "&Add to Playlist" ), this );
1072             m_appendAction->setProperty( "popupdropper_svg_id", "append" );
1073             connect( m_appendAction, &QAction::triggered, this, &CollectionTreeView::slotAppendChildTracks );
1074         }
1075 
1076         actions.append( m_appendAction );
1077 
1078         if( m_loadAction == 0 )
1079         {
1080             m_loadAction = new QAction(
1081                         i18nc( "Replace the currently loaded tracks with these",
1082                                "&Replace Playlist" ), this );
1083             m_loadAction->setProperty( "popupdropper_svg_id", "load" );
1084             connect( m_loadAction, &QAction::triggered,
1085                      this, &CollectionTreeView::slotReplacePlaylistWithChildTracks );
1086         }
1087 
1088         actions.append( m_loadAction );
1089     }
1090 
1091     return actions;
1092 }
1093 
1094 QActionList
createExtendedActions(const QModelIndexList & indices)1095 CollectionTreeView::createExtendedActions( const QModelIndexList &indices )
1096 {
1097     QActionList actions;
1098 
1099     if( !indices.isEmpty() )
1100     {
1101         {   //keep the scope of item minimal
1102             CollectionTreeItem *item =
1103                     static_cast<CollectionTreeItem *>( indices.first().internalPointer() );
1104             while( item->isDataItem() )
1105                 item = item->parent();
1106 
1107             Collection *collection = item->parentCollection();
1108             CollectionLocation* location = collection->location();
1109 
1110             if( location->isOrganizable() )
1111             {
1112                 bool onlyOneCollection = true;
1113                 foreach( const QModelIndex &index, indices )
1114                 {
1115                     Q_UNUSED( index )
1116                     CollectionTreeItem *item = static_cast<CollectionTreeItem *>(
1117                                 indices.first().internalPointer() );
1118                     while( item->isDataItem() )
1119                         item = item->parent();
1120 
1121                     onlyOneCollection = item->parentCollection() == collection;
1122                     if( !onlyOneCollection )
1123                         break;
1124                 }
1125 
1126                 if( onlyOneCollection )
1127                 {
1128                     if( m_organizeAction == 0 )
1129                     {
1130                         m_organizeAction = new QAction( QIcon::fromTheme(QStringLiteral("folder-open") ),
1131                                     i18nc( "Organize Files", "Organize Files" ), this );
1132                         m_organizeAction->setProperty( "popupdropper_svg_id", "organize" );
1133                         connect( m_organizeAction, &QAction::triggered,
1134                                  this, &CollectionTreeView::slotOrganize );
1135                     }
1136                     actions.append( m_organizeAction );
1137                 }
1138             }
1139             delete location;
1140         }
1141 
1142         //hmmm... figure out what kind of item we are dealing with....
1143 
1144         if( indices.size() == 1 )
1145         {
1146             debug() << "checking for global actions";
1147             CollectionTreeItem *item = static_cast<CollectionTreeItem *>(
1148                         indices.first().internalPointer() );
1149 
1150             QActionList gActions = The::globalCollectionActions()->actionsFor( item->data() );
1151             foreach( QAction *action, gActions )
1152             {
1153                 if( action ) // Can become 0-pointer, see https://bugs.kde.org/show_bug.cgi?id=183250
1154                 {
1155                     actions.append( action );
1156                     debug() << "Got global action: " << action->text();
1157                 }
1158             }
1159         }
1160 
1161         if( m_editAction == 0 )
1162         {
1163             m_editAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-edit-amarok") ),
1164                                         i18n( "&Edit Track Details" ), this );
1165             setProperty( "popupdropper_svg_id", "edit" );
1166             connect( m_editAction, &QAction::triggered, this, &CollectionTreeView::slotEditTracks );
1167         }
1168         actions.append( m_editAction );
1169     }
1170     else
1171         debug() << "invalid index or null internalPointer";
1172 
1173     return actions;
1174 }
1175 
1176 QActionList
createCustomActions(const QModelIndexList & indices)1177 CollectionTreeView::createCustomActions( const QModelIndexList &indices )
1178 {
1179     QActionList actions;
1180     if( indices.count() == 1 )
1181     {
1182         if( indices.first().isValid() && indices.first().internalPointer() )
1183         {
1184             Meta::DataPtr data = static_cast<CollectionTreeItem *>(
1185                         indices.first().internalPointer() )->data();
1186             if( data )
1187             {
1188                 QScopedPointer<Capabilities::ActionsCapability> ac(
1189                             data->create<Capabilities::ActionsCapability>() );
1190                 if( ac )
1191                 {
1192                     QActionList cActions = ac->actions();
1193 
1194                     foreach( QAction *action, cActions )
1195                     {
1196                         Q_ASSERT( action );
1197                         actions.append( action );
1198                         debug() << "Got custom action: " << action->text();
1199                     }
1200                 }
1201 
1202                 //check if this item can be bookmarked...
1203                 QScopedPointer<Capabilities::BookmarkThisCapability> btc(
1204                             data->create<Capabilities::BookmarkThisCapability>() );
1205                 if( btc && btc->isBookmarkable() && btc->bookmarkAction() )
1206                     actions.append( btc->bookmarkAction() );
1207             }
1208         }
1209     }
1210     return actions;
1211 }
1212 
1213 QActionList
createCollectionActions(const QModelIndexList & indices)1214 CollectionTreeView::createCollectionActions( const QModelIndexList &indices )
1215 {
1216     QActionList actions;
1217     // Extract collection whose constituent was selected
1218 
1219     CollectionTreeItem *item =
1220             static_cast<CollectionTreeItem *>( indices.first().internalPointer() );
1221 
1222     // Don't return any collection actions for non collection items
1223     if( item->isDataItem() )
1224         return actions;
1225 
1226     Collection *collection = item->parentCollection();
1227 
1228     // Generate CollectionCapability, test for existence
1229 
1230     QScopedPointer<Capabilities::ActionsCapability> cc(
1231                 collection->create<Capabilities::ActionsCapability>() );
1232 
1233     if( cc )
1234         actions = cc->actions();
1235 
1236     return actions;
1237 }
1238 
1239 
1240 QHash<QAction *, Collection *>
getCopyActions(const QModelIndexList & indices)1241 CollectionTreeView::getCopyActions( const QModelIndexList &indices )
1242 {
1243     QHash<QAction *, Collection *> currentCopyDestination;
1244 
1245     if( onlyOneCollection( indices ) )
1246     {
1247         Collection *collection = getCollection( indices.first() );
1248         QList<Collection *> writableCollections;
1249         QHash<Collection *, CollectionManager::CollectionStatus> hash =
1250                 CollectionManager::instance()->collections();
1251         QHash<Collection *, CollectionManager::CollectionStatus>::const_iterator it =
1252                 hash.constBegin();
1253         while( it != hash.constEnd() )
1254         {
1255             Collection *coll = it.key();
1256             if( coll && coll->isWritable() && coll != collection )
1257                 writableCollections.append( coll );
1258             ++it;
1259         }
1260         if( !writableCollections.isEmpty() )
1261         {
1262             foreach( Collection *coll, writableCollections )
1263             {
1264                 QAction *action = new QAction( coll->icon(), coll->prettyName(), 0 );
1265                 action->setProperty( "popupdropper_svg_id", "collection" );
1266                 connect( action, &QAction::triggered, this, &CollectionTreeView::slotCopyTracks );
1267 
1268                 currentCopyDestination.insert( action, coll );
1269             }
1270         }
1271     }
1272     return currentCopyDestination;
1273 }
1274 
1275 QHash<QAction *, Collection *>
getMoveActions(const QModelIndexList & indices)1276 CollectionTreeView::getMoveActions( const QModelIndexList &indices )
1277 {
1278     QHash<QAction *, Collection *> currentMoveDestination;
1279 
1280     if( onlyOneCollection( indices ) )
1281     {
1282         Collection *collection = getCollection( indices.first() );
1283         QList<Collection *> writableCollections;
1284         QHash<Collection *, CollectionManager::CollectionStatus> hash =
1285                 CollectionManager::instance()->collections();
1286         QHash<Collection *, CollectionManager::CollectionStatus>::const_iterator it =
1287                 hash.constBegin();
1288         while( it != hash.constEnd() )
1289         {
1290             Collection *coll = it.key();
1291             if( coll && coll->isWritable() && coll != collection )
1292                 writableCollections.append( coll );
1293             ++it;
1294         }
1295         if( !writableCollections.isEmpty() )
1296         {
1297             if( collection->isWritable() )
1298             {
1299                 foreach( Collection *coll, writableCollections )
1300                 {
1301                     QAction *action = new QAction( coll->icon(), coll->prettyName(), 0 );
1302                     action->setProperty( "popupdropper_svg_id", "collection" );
1303                     connect( action, &QAction::triggered, this, &CollectionTreeView::slotMoveTracks );
1304                     currentMoveDestination.insert( action, coll );
1305                 }
1306             }
1307         }
1308     }
1309     return currentMoveDestination;
1310 }
1311 
onlyOneCollection(const QModelIndexList & indices)1312 bool CollectionTreeView::onlyOneCollection( const QModelIndexList &indices )
1313 {
1314     if( !indices.isEmpty() )
1315     {
1316         Collection *collection = getCollection( indices.first() );
1317         foreach( const QModelIndex &index, indices )
1318         {
1319             Collection *currentCollection = getCollection( index );
1320             if( collection != currentCollection )
1321                 return false;
1322         }
1323     }
1324 
1325     return true;
1326 }
1327 
1328 Collection *
getCollection(const QModelIndex & index)1329 CollectionTreeView::getCollection( const QModelIndex &index )
1330 {
1331     Collection *collection = 0;
1332     if( index.isValid() )
1333     {
1334         CollectionTreeItem *item =
1335                 static_cast<CollectionTreeItem *>( index.internalPointer() );
1336         while( item->isDataItem() )
1337             item = item->parent();
1338         collection = item->parentCollection();
1339     }
1340 
1341     return collection;
1342 }
1343 
1344 void
slotReplacePlaylistWithChildTracks()1345 CollectionTreeView::slotReplacePlaylistWithChildTracks()
1346 {
1347     playChildTracks( m_currentItems, Playlist::OnReplacePlaylistAction );
1348 }
1349 
1350 void
slotAppendChildTracks()1351 CollectionTreeView::slotAppendChildTracks()
1352 {
1353     playChildTracks( m_currentItems, Playlist::OnAppendToPlaylistAction );
1354 }
1355 
1356 void
slotQueueChildTracks()1357 CollectionTreeView::slotQueueChildTracks()
1358 {
1359     playChildTracks( m_currentItems, Playlist::OnQueueToPlaylistAction );
1360 }
1361 
1362 void
slotEditTracks()1363 CollectionTreeView::slotEditTracks()
1364 {
1365     editTracks( m_currentItems );
1366 }
1367 
1368 void
slotCopyTracks()1369 CollectionTreeView::slotCopyTracks()
1370 {
1371     if( !sender() )
1372         return;
1373     if( QAction *action = dynamic_cast<QAction *>( sender() ) )
1374         copyTracks( m_currentItems, m_currentCopyDestination[ action ], false );
1375 }
1376 
1377 void
slotMoveTracks()1378 CollectionTreeView::slotMoveTracks()
1379 {
1380     if( !sender() )
1381         return;
1382     if ( QAction *action = dynamic_cast<QAction *>( sender() ) )
1383         copyTracks( m_currentItems, m_currentMoveDestination[ action ], true );
1384 }
1385 
1386 void
slotTrashTracks()1387 CollectionTreeView::slotTrashTracks()
1388 {
1389     removeTracks( m_currentItems, true );
1390 }
1391 
1392 void
slotDeleteTracks()1393 CollectionTreeView::slotDeleteTracks()
1394 {
1395     removeTracks( m_currentItems, false /* do not use trash */ );
1396 }
1397 
1398 void
slotOrganize()1399 CollectionTreeView::slotOrganize()
1400 {
1401     if( sender() )
1402     {
1403         if( QAction *action = dynamic_cast<QAction *>( sender() ) )
1404         {
1405             Q_UNUSED( action )
1406             organizeTracks( m_currentItems );
1407         }
1408     }
1409 }
1410 
1411 QSet<CollectionTreeItem *>
cleanItemSet(const QSet<CollectionTreeItem * > & items)1412 CollectionTreeView::cleanItemSet( const QSet<CollectionTreeItem *> &items )
1413 {
1414     QSet<CollectionTreeItem *> parents;
1415     foreach( CollectionTreeItem *item, items )
1416     {
1417         CollectionTreeItem *tmpItem = item;
1418         while( tmpItem )
1419         {
1420             if( items.contains( tmpItem->parent() ) )
1421                 tmpItem = tmpItem->parent();
1422             else
1423             {
1424                 parents.insert( tmpItem );
1425                 break;
1426             }
1427         }
1428     }
1429     return parents;
1430 }
1431 
1432 Collections::QueryMaker *
createMetaQueryFromItems(const QSet<CollectionTreeItem * > & items,bool cleanItems) const1433 CollectionTreeView::createMetaQueryFromItems( const QSet<CollectionTreeItem *> &items,
1434                                               bool cleanItems ) const
1435 {
1436     if( !m_treeModel )
1437         return 0;
1438 
1439     QSet<CollectionTreeItem*> parents = cleanItems ? cleanItemSet( items ) : items;
1440 
1441     QList<Collections::QueryMaker *> queryMakers;
1442     foreach( CollectionTreeItem *item, parents )
1443     {
1444         Collections::QueryMaker *qm = item->queryMaker();
1445         for( CollectionTreeItem *tmp = item; tmp; tmp = tmp->parent() )
1446             tmp->addMatch( qm, m_treeModel->levelCategory( tmp->level() - 1 ) );
1447         Collections::addTextualFilter( qm, m_treeModel->currentFilter() );
1448         queryMakers.append( qm );
1449     }
1450     return new Collections::MetaQueryMaker( queryMakers );
1451 }
1452 
1453 #include "CollectionTreeView.moc"  // Q_OBJECTs defined in CollectionTreeView.cpp
1454 #include "moc_CollectionTreeView.cpp"  // Q_OBJECTs defined in CollectionTreeView.h
1455