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