Warning, file /multimedia/amarok/src/browsers/CollectionTreeView.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /**************************************************************************************** 0002 * Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com> * 0003 * Copyright (c) 2007 Ian Monroe <ian@monroe.nu> * 0004 * * 0005 * This program is free software; you can redistribute it and/or modify it under * 0006 * the terms of the GNU General Public License as published by the Free Software * 0007 * Foundation; either version 2 of the License, or (at your option) version 3 or * 0008 * any later version accepted by the membership of KDE e.V. (or its successor approved * 0009 * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * 0010 * version 3 of the license. * 0011 * * 0012 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0013 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0014 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License along with * 0017 * this program. If not, see <http://www.gnu.org/licenses/>. * 0018 ****************************************************************************************/ 0019 0020 #define DEBUG_PREFIX "CollectionTreeView" 0021 0022 #include "CollectionTreeView.h" 0023 0024 #include "AmarokMimeData.h" 0025 #include "GlobalCollectionActions.h" 0026 #include "PopupDropperFactory.h" 0027 #include "SvgHandler.h" 0028 #include "browsers/CollectionSortFilterProxyModel.h" 0029 #include "browsers/CollectionTreeItemModel.h" 0030 #include "context/ContextView.h" 0031 #include "core/capabilities/ActionsCapability.h" 0032 #include "core/capabilities/BookmarkThisCapability.h" 0033 #include "core/collections/CollectionLocation.h" 0034 #include "core/collections/MetaQueryMaker.h" 0035 #include "core/collections/QueryMaker.h" 0036 #include "core/meta/Meta.h" 0037 #include "core/support/Amarok.h" 0038 #include "core/support/Debug.h" 0039 #include "core-impl/collections/support/CollectionManager.h" 0040 #include "core-impl/collections/support/TextualQueryFilter.h" 0041 #include "core-impl/collections/support/TrashCollectionLocation.h" 0042 #include "dialogs/TagDialog.h" 0043 #include "playlist/PlaylistModelStack.h" 0044 #include "scripting/scriptengine/AmarokCollectionViewScript.h" 0045 0046 #include <QAction> 0047 #include <QIcon> 0048 #include <QComboBox> 0049 #include <QMenu> 0050 0051 #include <QContextMenuEvent> 0052 #include <QHash> 0053 #include <QMouseEvent> 0054 #include <QQueue> 0055 #include <QSortFilterProxyModel> 0056 #include <QScrollBar> 0057 0058 #include <algorithm> 0059 0060 using namespace Collections; 0061 0062 /** 0063 * RAII class to perform restoring of the scroll position once all queries are 0064 * finished. DelayedScroller auto-deletes itself once its job is over (ot if it finds 0065 * it is useless). 0066 */ 0067 class DelayedScroller : public QObject 0068 { 0069 Q_OBJECT 0070 0071 public: 0072 DelayedScroller( CollectionTreeView *treeView, 0073 CollectionTreeItemModelBase *treeModel, 0074 const QModelIndex &treeModelScrollToIndex, int topOffset ) 0075 : QObject( treeView ) 0076 , m_treeView( treeView ) 0077 , m_treeModel( treeModel ) 0078 , m_topOffset( topOffset ) 0079 { 0080 connect( treeModel, &CollectionTreeItemModelBase::destroyed, 0081 this, &DelayedScroller::deleteLater ); 0082 connect( treeModel, &CollectionTreeItemModelBase::allQueriesFinished, 0083 this, &DelayedScroller::slotScroll ); 0084 0085 m_scrollToItem = m_treeModel->treeItem( treeModelScrollToIndex ); 0086 if( m_scrollToItem ) 0087 connect( m_scrollToItem, &CollectionTreeItem::destroyed, this, &DelayedScroller::deleteLater ); 0088 else 0089 deleteLater(); // nothing to do 0090 } 0091 0092 private Q_SLOTS: 0093 void slotScroll() 0094 { 0095 deleteLater(); 0096 QModelIndex idx = m_treeModel->itemIndex( m_scrollToItem ); 0097 QSortFilterProxyModel *filterModel = m_treeView->filterModel(); 0098 idx = filterModel ? filterModel->mapFromSource( idx ) : QModelIndex(); 0099 QScrollBar *scrollBar = m_treeView->verticalScrollBar(); 0100 if( !idx.isValid() || !scrollBar ) 0101 return; 0102 0103 int newTopOffset = m_treeView->visualRect( idx ).top(); 0104 scrollBar->setValue( scrollBar->value() + (newTopOffset - m_topOffset) ); 0105 } 0106 0107 private: 0108 CollectionTreeView *m_treeView; 0109 CollectionTreeItemModelBase *m_treeModel; 0110 CollectionTreeItem *m_scrollToItem; 0111 int m_topOffset; 0112 }; 0113 0114 /** 0115 * RAII class to auto-expand collection tree entries after filtering. 0116 * AutoExpander auto-deletes itself once its job is over (or if it finds 0117 * it is useless). 0118 */ 0119 class AutoExpander : public QObject 0120 { 0121 Q_OBJECT 0122 0123 public: 0124 AutoExpander( CollectionTreeView *treeView, 0125 CollectionTreeItemModelBase *treeModel, 0126 QAbstractItemModel *filterModel) 0127 : QObject( treeView ) 0128 , m_treeView( treeView ) 0129 , m_filterModel( filterModel ) 0130 { 0131 connect( filterModel, &QObject::destroyed, this, &QObject::deleteLater ); 0132 connect( treeModel, &CollectionTreeItemModelBase::allQueriesFinished, this, &AutoExpander::slotExpandMore ); 0133 0134 // start with the root index 0135 m_indicesToCheck.enqueue( QModelIndex() ); 0136 slotExpandMore(); 0137 } 0138 0139 private Q_SLOTS: 0140 void slotExpandMore() 0141 { 0142 const int maxChildrenToExpand = 3; 0143 0144 QQueue<QModelIndex> pendingIndices; 0145 while( !m_indicesToCheck.isEmpty() ) 0146 { 0147 if( !m_filterModel ) 0148 return; 0149 0150 QModelIndex current = m_indicesToCheck.dequeue(); 0151 0152 if( m_filterModel->canFetchMore( current ) ) 0153 { 0154 m_filterModel->fetchMore( current ); 0155 pendingIndices.enqueue( current ); 0156 continue; 0157 } 0158 0159 if( m_filterModel->rowCount( current ) <= maxChildrenToExpand ) 0160 { 0161 m_treeView->expand( current ); 0162 for( int i = 0; i < m_filterModel->rowCount( current ); i++ ) 0163 m_indicesToCheck.enqueue( m_filterModel->index( i, 0, current ) ); 0164 } 0165 } 0166 0167 if( pendingIndices.isEmpty() ) 0168 // nothing left to do 0169 deleteLater(); 0170 else 0171 // process pending indices when queries finish 0172 m_indicesToCheck.swap( pendingIndices ); 0173 } 0174 0175 private: 0176 Q_DISABLE_COPY(AutoExpander) 0177 0178 CollectionTreeView *m_treeView; 0179 QPointer<QAbstractItemModel> m_filterModel; 0180 QQueue<QModelIndex> m_indicesToCheck; 0181 }; 0182 0183 CollectionTreeView::CollectionTreeView( QWidget *parent) 0184 : Amarok::PrettyTreeView( parent ) 0185 , m_filterModel( nullptr ) 0186 , m_treeModel( nullptr ) 0187 , m_pd( nullptr ) 0188 , m_appendAction( nullptr ) 0189 , m_loadAction( nullptr ) 0190 , m_editAction( nullptr ) 0191 , m_organizeAction( nullptr ) 0192 , m_ongoingDrag( false ) 0193 { 0194 setSortingEnabled( true ); 0195 setFocusPolicy( Qt::StrongFocus ); 0196 sortByColumn( 0, Qt::AscendingOrder ); 0197 setSelectionMode( QAbstractItemView::ExtendedSelection ); 0198 setSelectionBehavior( QAbstractItemView::SelectRows ); 0199 setEditTriggers( EditKeyPressed ); 0200 0201 setDragDropMode( QAbstractItemView::DragDrop ); 0202 0203 connect( this, &CollectionTreeView::collapsed, 0204 this, &CollectionTreeView::slotCollapsed ); 0205 connect( this, &CollectionTreeView::expanded, 0206 this, &CollectionTreeView::slotExpanded ); 0207 } 0208 0209 void 0210 CollectionTreeView::setModel( QAbstractItemModel *model ) 0211 { 0212 if( m_treeModel ) 0213 disconnect( m_treeModel, nullptr, this, nullptr); 0214 0215 m_treeModel = qobject_cast<CollectionTreeItemModelBase *>( model ); 0216 if( !m_treeModel ) 0217 return; 0218 0219 connect( m_treeModel, &CollectionTreeItemModelBase::allQueriesFinished, 0220 this, &CollectionTreeView::slotCheckAutoExpand ); 0221 connect( m_treeModel, &CollectionTreeItemModelBase::expandIndex, 0222 this, &CollectionTreeView::slotExpandIndex ); 0223 0224 if( m_filterModel ) 0225 m_filterModel->deleteLater(); 0226 m_filterModel = new CollectionSortFilterProxyModel( this ); 0227 m_filterModel->setSourceModel( model ); 0228 0229 QTreeView::setModel( m_filterModel ); 0230 0231 QTimer::singleShot( 0, this, &CollectionTreeView::slotCheckAutoExpandReally ); 0232 } 0233 0234 CollectionTreeView::~CollectionTreeView() 0235 { 0236 // we don't own m_treeModel pointer 0237 // m_filterModel will get deleted by QObject parentship 0238 } 0239 0240 void 0241 CollectionTreeView::setLevels( const QList<CategoryId::CatMenuId> &levels ) 0242 { 0243 if( m_treeModel ) 0244 m_treeModel->setLevels( levels ); 0245 } 0246 0247 QList<CategoryId::CatMenuId> 0248 CollectionTreeView::levels() const 0249 { 0250 if( m_treeModel ) 0251 return m_treeModel->levels(); 0252 return QList<CategoryId::CatMenuId>(); 0253 } 0254 0255 void 0256 CollectionTreeView::setLevel( int level, CategoryId::CatMenuId type ) 0257 { 0258 if( !m_treeModel ) 0259 return; 0260 QList<CategoryId::CatMenuId> levels = m_treeModel->levels(); 0261 if( type == CategoryId::None ) 0262 { 0263 while( levels.count() >= level ) 0264 levels.removeLast(); 0265 } 0266 else 0267 { 0268 levels.removeAll( type ); 0269 levels[level] = type; 0270 } 0271 setLevels( levels ); 0272 } 0273 0274 QSortFilterProxyModel * 0275 CollectionTreeView::filterModel() const 0276 { 0277 return m_filterModel; 0278 } 0279 0280 void 0281 CollectionTreeView::contextMenuEvent( QContextMenuEvent *event ) 0282 { 0283 if( !m_treeModel ) 0284 return; 0285 0286 QModelIndex index = indexAt( event->pos() ); 0287 if( !index.isValid() ) 0288 { 0289 Amarok::PrettyTreeView::contextMenuEvent( event ); 0290 return; 0291 } 0292 0293 QModelIndexList indices = selectedIndexes(); 0294 0295 // if previously selected indices do not contain the index of the item 0296 // currently under the mouse when context menu is invoked. 0297 if( !indices.contains( index ) ) 0298 { 0299 indices.clear(); 0300 indices << index; 0301 setCurrentIndex( index ); 0302 } 0303 0304 //TODO: get rid of this, it's a hack. 0305 // Put remove actions in model so we don't need access to the internal pointer in view 0306 if( m_filterModel ) 0307 { 0308 QModelIndexList tmp; 0309 foreach( const QModelIndex &idx, indices ) 0310 { 0311 tmp.append( m_filterModel->mapToSource( idx ) ); 0312 } 0313 indices = tmp; 0314 } 0315 0316 // Abort if nothing is selected 0317 if( indices.isEmpty() ) 0318 return; 0319 0320 m_currentItems.clear(); 0321 foreach( const QModelIndex &index, indices ) 0322 { 0323 if( index.isValid() && index.internalPointer() ) 0324 m_currentItems.insert( 0325 static_cast<CollectionTreeItem *>( index.internalPointer() ) 0326 ); 0327 } 0328 0329 QMenu menu( this ); 0330 0331 // Destroy the menu when the model is reset (collection update), so that we don't 0332 // operate on invalid data. see BUG 190056 0333 connect( m_treeModel, &CollectionTreeItemModelBase::modelReset, &menu, &QMenu::deleteLater ); 0334 0335 // create basic actions 0336 QActionList actions = createBasicActions( indices ); 0337 foreach( QAction *action, actions ) { 0338 menu.addAction( action ); 0339 } 0340 menu.addSeparator(); 0341 actions.clear(); 0342 0343 QActionList albumActions = createCustomActions( indices ); 0344 QMenu menuAlbum( i18n( "Album" ) ); 0345 foreach( QAction *action, albumActions ) 0346 { 0347 if( !action->parent() ) 0348 action->setParent( &menuAlbum ); 0349 } 0350 0351 if( albumActions.count() > 1 ) 0352 { 0353 menuAlbum.addActions( albumActions ); 0354 menuAlbum.setIcon( QIcon::fromTheme( QStringLiteral("filename-album-amarok") ) ); 0355 menu.addMenu( &menuAlbum ); 0356 menu.addSeparator(); 0357 } 0358 else if( albumActions.count() == 1 ) 0359 { 0360 menu.addActions( albumActions ); 0361 } 0362 0363 QActionList collectionActions = createCollectionActions( indices ); 0364 QMenu menuCollection( i18n( "Collection" ) ); 0365 foreach( QAction *action, collectionActions ) 0366 { 0367 if( !action->parent() ) 0368 action->setParent( &menuCollection ); 0369 } 0370 0371 if( collectionActions.count() > 1 ) 0372 { 0373 menuCollection.setIcon( QIcon::fromTheme( QStringLiteral("drive-harddisk") ) ); 0374 menuCollection.addActions( collectionActions ); 0375 menu.addMenu( &menuCollection ); 0376 menu.addSeparator(); 0377 } 0378 else if( collectionActions.count() == 1 ) 0379 { 0380 menu.addActions( collectionActions ); 0381 } 0382 0383 m_currentCopyDestination = getCopyActions( indices ); 0384 m_currentMoveDestination = getMoveActions( indices ); 0385 0386 if( !m_currentCopyDestination.empty() ) 0387 { 0388 QMenu *copyMenu = new QMenu( i18n( "Copy to Collection" ), &menu ); 0389 copyMenu->setIcon( QIcon::fromTheme( QStringLiteral("edit-copy") ) ); 0390 copyMenu->addActions( m_currentCopyDestination.keys() ); 0391 menu.addMenu( copyMenu ); 0392 } 0393 0394 //Move = copy + delete from source 0395 if( !m_currentMoveDestination.empty() ) 0396 { 0397 QMenu *moveMenu = new QMenu( i18n( "Move to Collection" ), &menu ); 0398 moveMenu->setIcon( QIcon::fromTheme( QStringLiteral("go-jump") ) ); 0399 moveMenu->addActions( m_currentMoveDestination.keys() ); 0400 menu.addMenu( moveMenu ); 0401 } 0402 0403 // create trash and delete actions 0404 if( onlyOneCollection( indices ) ) 0405 { 0406 Collection *collection = getCollection( indices.first() ); 0407 if( collection && collection->isWritable() ) 0408 { 0409 //TODO: don't recreate action 0410 QAction *trashAction = new QAction( QIcon::fromTheme( QStringLiteral("user-trash") ), 0411 i18n( "Move Tracks to Trash" ), 0412 &menu ); 0413 trashAction->setProperty( "popupdropper_svg_id", "delete" ); 0414 // key shortcut is only for display purposes here, actual one is 0415 // determined by View in Model/View classes 0416 trashAction->setShortcut( Qt::Key_Delete ); 0417 connect( trashAction, &QAction::triggered, 0418 this, &CollectionTreeView::slotTrashTracks ); 0419 menu.addAction( trashAction ); 0420 0421 QAction *deleteAction = new QAction( QIcon::fromTheme( QStringLiteral("remove-amarok") ), 0422 i18n( "Delete Tracks" ), 0423 &menu ); 0424 deleteAction->setProperty( "popupdropper_svg_id", "delete" ); 0425 // key shortcut is only for display purposes here, actual one is 0426 // determined by View in Model/View classes 0427 deleteAction->setShortcut( Qt::SHIFT + Qt::Key_Delete ); 0428 connect( deleteAction, &QAction::triggered, this, &CollectionTreeView::slotDeleteTracks ); 0429 menu.addAction( deleteAction ); 0430 } 0431 } 0432 0433 // add extended actions 0434 menu.addSeparator(); 0435 actions += createExtendedActions( indices ); 0436 foreach( QAction *action, actions ) { 0437 menu.addAction( action ); 0438 } 0439 AmarokScript::AmarokCollectionViewScript::createScriptedActions( menu, indices ); 0440 0441 menu.exec( event->globalPos() ); 0442 } 0443 0444 void 0445 CollectionTreeView::mouseDoubleClickEvent( QMouseEvent *event ) 0446 { 0447 if( event->button() == Qt::MidButton ) 0448 { 0449 event->accept(); 0450 return; 0451 } 0452 0453 QModelIndex index = indexAt( event->pos() ); 0454 if( !index.isValid() ) 0455 { 0456 event->accept(); 0457 return; 0458 } 0459 0460 // code copied in PlaylistBrowserView::mouseDoubleClickEvent(), keep in sync 0461 // mind bug 279513 0462 bool isExpandable = model()->hasChildren( index ); 0463 bool wouldExpand = !visualRect( index ).contains( event->pos() ) || // clicked outside item, perhaps on expander icon 0464 ( isExpandable && !style()->styleHint( QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this ) ); // we're in doubleClick 0465 if( event->button() == Qt::LeftButton && 0466 event->modifiers() == Qt::NoModifier && 0467 !wouldExpand ) 0468 { 0469 CollectionTreeItem *item = getItemFromIndex( index ); 0470 playChildTracks( item, Playlist::OnDoubleClickOnSelectedItems ); 0471 event->accept(); 0472 return; 0473 } 0474 0475 PrettyTreeView::mouseDoubleClickEvent( event ); 0476 } 0477 0478 void 0479 CollectionTreeView::mouseReleaseEvent( QMouseEvent *event ) 0480 { 0481 if( m_pd ) 0482 { 0483 connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::deleteLater ); 0484 m_pd->hide(); 0485 m_pd = nullptr; 0486 } 0487 0488 QModelIndex index = indexAt( event->pos() ); 0489 if( !index.isValid() ) 0490 { 0491 PrettyTreeView::mouseReleaseEvent( event ); 0492 return; 0493 } 0494 0495 if( event->button() == Qt::MidButton ) 0496 { 0497 CollectionTreeItem *item = getItemFromIndex( index ); 0498 playChildTracks( item, Playlist::OnMiddleClickOnSelectedItems ); 0499 event->accept(); 0500 return; 0501 } 0502 0503 PrettyTreeView::mouseReleaseEvent( event ); 0504 } 0505 0506 CollectionTreeItem * 0507 CollectionTreeView::getItemFromIndex( QModelIndex &index ) 0508 { 0509 QModelIndex filteredIndex; 0510 if( m_filterModel ) 0511 filteredIndex = m_filterModel->mapToSource( index ); 0512 else 0513 filteredIndex = index; 0514 0515 if( !filteredIndex.isValid() ) 0516 { 0517 return nullptr; 0518 } 0519 0520 return static_cast<CollectionTreeItem *>( filteredIndex.internalPointer() ); 0521 } 0522 0523 void 0524 CollectionTreeView::keyPressEvent( QKeyEvent *event ) 0525 { 0526 QModelIndexList indices = selectedIndexes(); 0527 if( indices.isEmpty() ) 0528 { 0529 Amarok::PrettyTreeView::keyPressEvent( event ); 0530 return; 0531 } 0532 0533 if( m_filterModel ) 0534 { 0535 QModelIndexList tmp; 0536 foreach( const QModelIndex &idx, indices ) 0537 tmp.append( m_filterModel->mapToSource( idx ) ); 0538 indices = tmp; 0539 } 0540 0541 m_currentItems.clear(); 0542 foreach( const QModelIndex &index, indices ) 0543 { 0544 if( index.isValid() && index.internalPointer() ) 0545 { 0546 m_currentItems.insert( 0547 static_cast<CollectionTreeItem *>( index.internalPointer() ) ); 0548 } 0549 } 0550 0551 QModelIndex current = currentIndex(); 0552 switch( event->key() ) 0553 { 0554 case Qt::Key_Enter: 0555 case Qt::Key_Return: 0556 playChildTracks( m_currentItems, Playlist::OnReturnPressedOnSelectedItems ); 0557 return; 0558 case Qt::Key_Delete: 0559 if( !onlyOneCollection( indices ) ) 0560 break; 0561 removeTracks( m_currentItems, !( event->modifiers() & Qt::ShiftModifier ) ); 0562 return; 0563 case Qt::Key_Up: 0564 if( current.parent() == QModelIndex() && current.row() == 0 ) 0565 { 0566 Q_EMIT leavingTree(); 0567 return; 0568 } 0569 break; 0570 case Qt::Key_Down: 0571 break; 0572 // L and R should magically work when we get a patched version of qt 0573 case Qt::Key_Right: 0574 case Qt::Key_Direction_R: 0575 expand( current ); 0576 return; 0577 case Qt::Key_Left: 0578 case Qt::Key_Direction_L: 0579 collapse( current ); 0580 return; 0581 default: 0582 break; 0583 } 0584 Amarok::PrettyTreeView::keyPressEvent( event ); 0585 } 0586 0587 void 0588 CollectionTreeView::dragEnterEvent( QDragEnterEvent *event ) 0589 { 0590 // We want to indicate to the user that dropping to the same collection is not possible. 0591 // CollectionTreeItemModel therefore needs to know what collection the drag originated 0592 // so that is can play with Qt::ItemIsDropEnabled in flags() 0593 const AmarokMimeData *mimeData = 0594 qobject_cast<const AmarokMimeData *>( event->mimeData() ); 0595 if( mimeData ) // drag from within Amarok 0596 { 0597 QSet<Collection *> srcCollections; 0598 foreach( Meta::TrackPtr track, mimeData->tracks() ) 0599 { 0600 srcCollections.insert( track->collection() ); 0601 } 0602 m_treeModel->setDragSourceCollections( srcCollections ); 0603 } 0604 QAbstractItemView::dragEnterEvent( event ); 0605 } 0606 0607 void 0608 CollectionTreeView::dragMoveEvent( QDragMoveEvent *event ) 0609 { 0610 // this mangling is not needed for Copy/Move distinction to work, it is only needed 0611 // for mouse cursor changing to work 0612 if( (event->keyboardModifiers() & Qt::ShiftModifier) 0613 && (event->possibleActions() & Qt::MoveAction) ) 0614 { 0615 event->setDropAction( Qt::MoveAction ); 0616 } 0617 else if( event->possibleActions() & Qt::CopyAction ) 0618 { 0619 event->setDropAction( Qt::CopyAction ); 0620 } 0621 0622 QTreeView::dragMoveEvent( event ); 0623 } 0624 0625 void 0626 CollectionTreeView::startDrag(Qt::DropActions supportedActions) 0627 { 0628 DEBUG_BLOCK 0629 0630 // Make sure that the left mouse button is actually pressed. Otherwise we're prone to 0631 // mis-detecting clicks as dragging 0632 if( !( QApplication::mouseButtons() & Qt::LeftButton ) ) 0633 return; 0634 0635 QModelIndexList indices = selectedIndexes(); 0636 if( indices.isEmpty() ) 0637 return; 0638 0639 // When a parent item is dragged, startDrag() is called a bunch of times. Here we 0640 // prevent that: 0641 if( m_ongoingDrag ) 0642 return; 0643 m_ongoingDrag = true; 0644 0645 if( !m_pd ) 0646 m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() ); 0647 0648 if( m_pd && m_pd->isHidden() ) 0649 { 0650 if( m_filterModel ) 0651 { 0652 QModelIndexList tmp; 0653 foreach( const QModelIndex &idx, indices ) 0654 { 0655 tmp.append( m_filterModel->mapToSource( idx ) ); 0656 } 0657 indices = tmp; 0658 } 0659 0660 QActionList actions = createBasicActions( indices ); 0661 0662 QFont font; 0663 font.setPointSize( 16 ); 0664 font.setBold( true ); 0665 0666 foreach( QAction * action, actions ) 0667 m_pd->addItem( The::popupDropperFactory()->createItem( action ) ); 0668 0669 m_currentCopyDestination = getCopyActions( indices ); 0670 m_currentMoveDestination = getMoveActions( indices ); 0671 0672 m_currentItems.clear(); 0673 foreach( const QModelIndex &index, indices ) 0674 { 0675 if( index.isValid() && index.internalPointer() ) 0676 { 0677 m_currentItems.insert( 0678 static_cast<CollectionTreeItem *>( index.internalPointer() ) ); 0679 } 0680 } 0681 0682 PopupDropperItem *subItem; 0683 0684 actions = createExtendedActions( indices ); 0685 0686 PopupDropper *morePud = nullptr; 0687 if( actions.count() > 1 ) 0688 { 0689 morePud = The::popupDropperFactory()->createPopupDropper( nullptr, true ); 0690 0691 foreach( QAction *action, actions ) 0692 morePud->addItem( The::popupDropperFactory()->createItem( action ) ); 0693 } 0694 else 0695 m_pd->addItem( The::popupDropperFactory()->createItem( actions[0] ) ); 0696 0697 //TODO: Keep bugging i18n team about problems with 3 dots 0698 if ( actions.count() > 1 ) 0699 { 0700 subItem = m_pd->addSubmenu( &morePud, i18n( "More..." ) ); 0701 The::popupDropperFactory()->adjustItem( subItem ); 0702 } 0703 0704 m_pd->show(); 0705 } 0706 0707 QTreeView::startDrag( supportedActions ); 0708 debug() << "After the drag!"; 0709 0710 if( m_pd ) 0711 { 0712 debug() << "clearing PUD"; 0713 connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::clear ); 0714 m_pd->hide(); 0715 } 0716 0717 m_ongoingDrag = false; 0718 } 0719 0720 void 0721 CollectionTreeView::selectionChanged( const QItemSelection &selected, 0722 const QItemSelection &deselected ) 0723 { 0724 QModelIndexList indexes = selected.indexes(); 0725 0726 QModelIndexList changedIndexes = indexes; 0727 changedIndexes << deselected.indexes(); 0728 foreach( const QModelIndex &index, changedIndexes ) 0729 update( index ); 0730 0731 if( indexes.count() < 1 ) 0732 return; 0733 0734 QModelIndex index; 0735 if( m_filterModel ) 0736 index = m_filterModel->mapToSource( indexes[0] ); 0737 else 0738 index = indexes[0]; 0739 0740 CollectionTreeItem *item = 0741 static_cast<CollectionTreeItem *>( index.internalPointer() ); 0742 Q_EMIT( itemSelected ( item ) ); 0743 } 0744 0745 void 0746 CollectionTreeView::slotCollapsed( const QModelIndex &index ) 0747 { 0748 if( !m_treeModel ) 0749 return; 0750 if( m_filterModel ) 0751 m_treeModel->slotCollapsed( m_filterModel->mapToSource( index ) ); 0752 else 0753 m_treeModel->slotCollapsed( index ); 0754 } 0755 0756 void 0757 CollectionTreeView::slotExpanded( const QModelIndex &index ) 0758 { 0759 if( !m_treeModel ) 0760 return; 0761 if( m_filterModel ) 0762 m_treeModel->slotExpanded( m_filterModel->mapToSource( index )); 0763 else 0764 m_treeModel->slotExpanded( index ); 0765 } 0766 0767 void 0768 CollectionTreeView::slotExpandIndex( const QModelIndex &index ) 0769 { 0770 if( !m_treeModel ) 0771 return; 0772 if( m_filterModel ) 0773 expand( m_filterModel->mapFromSource( index ) ); 0774 } 0775 0776 void 0777 CollectionTreeView::slotCheckAutoExpand( bool reallyExpand ) 0778 { 0779 if( !m_filterModel || !reallyExpand ) 0780 return; 0781 0782 // auto-deletes itself: 0783 new AutoExpander( this, m_treeModel, m_filterModel ); 0784 } 0785 0786 void 0787 CollectionTreeView::playChildTracks( CollectionTreeItem *item, Playlist::AddOptions insertMode ) 0788 { 0789 QSet<CollectionTreeItem*> items; 0790 items.insert( item ); 0791 0792 playChildTracks( items, insertMode ); 0793 } 0794 0795 void 0796 CollectionTreeView::playChildTracks( const QSet<CollectionTreeItem *> &items, 0797 Playlist::AddOptions insertMode ) 0798 { 0799 if( !m_treeModel ) 0800 return; 0801 //Ensure that if a parent and child are both selected we ignore the child 0802 QSet<CollectionTreeItem *> parents( cleanItemSet( items ) ); 0803 0804 //Store the type of playlist insert to be done and cause a slot to be invoked when the tracklist has been generated. 0805 AmarokMimeData *mime = dynamic_cast<AmarokMimeData*>( 0806 m_treeModel->mimeData( QList<CollectionTreeItem *>( parents.begin(), parents.end() ) ) ); 0807 m_playChildTracksMode.insert( mime, insertMode ); 0808 connect( mime, &AmarokMimeData::trackListSignal, 0809 this, &CollectionTreeView::playChildTracksSlot ); 0810 mime->getTrackListSignal(); 0811 } 0812 0813 void 0814 CollectionTreeView::playChildTracksSlot( Meta::TrackList list ) //slot 0815 { 0816 AmarokMimeData *mime = dynamic_cast<AmarokMimeData *>( sender() ); 0817 0818 Playlist::AddOptions insertMode = m_playChildTracksMode.take( mime ); 0819 0820 std::stable_sort( list.begin(), list.end(), Meta::Track::lessThan ); 0821 The::playlistController()->insertOptioned( list, insertMode ); 0822 0823 mime->deleteLater(); 0824 } 0825 0826 void 0827 CollectionTreeView::organizeTracks( const QSet<CollectionTreeItem *> &items ) const 0828 { 0829 DEBUG_BLOCK 0830 if( !items.count() ) 0831 return; 0832 0833 //Create query based upon items, ensuring that if a parent and child are both 0834 //selected we ignore the child 0835 Collections::QueryMaker *qm = createMetaQueryFromItems( items, true ); 0836 if( !qm ) 0837 return; 0838 0839 CollectionTreeItem *item = items.values().first(); 0840 while( item->isDataItem() ) 0841 item = item->parent(); 0842 0843 Collection *coll = item->parentCollection(); 0844 CollectionLocation *location = coll->location(); 0845 if( !location->isOrganizable() ) 0846 { 0847 debug() << "Collection not organizable"; 0848 //how did we get here?? 0849 delete location; 0850 delete qm; 0851 return; 0852 } 0853 location->prepareMove( qm, coll->location() ); 0854 } 0855 0856 void 0857 CollectionTreeView::copySelectedToLocalCollection() 0858 { 0859 DEBUG_BLOCK 0860 0861 // Get the local collection 0862 Collections::Collection *collection = nullptr; 0863 const QList<Collections::Collection*> collections = CollectionManager::instance()->collections().keys(); 0864 0865 foreach( collection, collections ) 0866 { 0867 if ( collection->collectionId() == QLatin1String("localCollection") ) 0868 break; 0869 } 0870 0871 if( !collection ) 0872 return; 0873 0874 // Get selected items 0875 QModelIndexList indexes = selectedIndexes(); 0876 if( m_filterModel ) 0877 { 0878 QModelIndexList tmp; 0879 foreach( const QModelIndex &idx, indexes ) 0880 tmp.append( m_filterModel->mapToSource( idx ) ); 0881 indexes = tmp; 0882 } 0883 0884 m_currentItems.clear(); 0885 foreach( const QModelIndex &index, indexes ) 0886 { 0887 if( index.isValid() && index.internalPointer() ) 0888 m_currentItems.insert( static_cast<CollectionTreeItem *>( index.internalPointer() ) ); 0889 } 0890 0891 copyTracks( m_currentItems, collection, false ); 0892 } 0893 0894 void 0895 CollectionTreeView::copyTracks( const QSet<CollectionTreeItem *> &items, 0896 Collection *destination, bool removeSources ) const 0897 { 0898 DEBUG_BLOCK 0899 0900 if( !destination ) 0901 { 0902 warning() << "collection is not writable (0-pointer)! Aborting"; 0903 return; 0904 } 0905 if( !destination->isWritable() ) 0906 { 0907 warning() << "collection " << destination->prettyName() << " is not writable! Aborting"; 0908 return; 0909 } 0910 //copied from organizeTracks. create a method for this somewhere 0911 if( !items.count() ) 0912 { 0913 warning() << "No items to copy! Aborting"; 0914 return; 0915 } 0916 0917 //Create query based upon items, ensuring that if a parent and child are both selected we ignore the child 0918 Collections::QueryMaker *qm = createMetaQueryFromItems( items, true ); 0919 if( !qm ) 0920 { 0921 warning() << "could not get qm!"; 0922 return; 0923 } 0924 0925 CollectionTreeItem *item = items.values().first(); 0926 while( item->isDataItem() ) 0927 { 0928 item = item->parent(); 0929 } 0930 Collection *coll = item->parentCollection(); 0931 CollectionLocation *source = coll->location(); 0932 CollectionLocation *dest = destination->location(); 0933 if( removeSources ) 0934 { 0935 if( !source->isWritable() ) //error 0936 { 0937 warning() << "We can not write to ze source!!! OMGooses!"; 0938 delete dest; 0939 delete source; 0940 delete qm; 0941 return; 0942 } 0943 0944 debug() << "starting source->prepareMove"; 0945 source->prepareMove( qm, dest ); 0946 } 0947 else 0948 { 0949 debug() << "starting source->prepareCopy"; 0950 source->prepareCopy( qm, dest ); 0951 } 0952 } 0953 0954 void 0955 CollectionTreeView::removeTracks( const QSet<CollectionTreeItem *> &items, 0956 bool useTrash ) const 0957 { 0958 DEBUG_BLOCK 0959 0960 //copied from organizeTracks. create a method for this somewhere 0961 if( !items.count() ) 0962 return; 0963 0964 //Create query based upon items, ensuring that if a parent and child are both selected we ignore the child 0965 Collections::QueryMaker *qm = createMetaQueryFromItems( items, true ); 0966 if( !qm ) 0967 return; 0968 0969 CollectionTreeItem *item = items.values().first(); 0970 while( item->isDataItem() ) 0971 item = item->parent(); 0972 Collection *coll = item->parentCollection(); 0973 0974 CollectionLocation *source = coll->location(); 0975 if( !source->isWritable() ) //error 0976 { 0977 warning() << "We can not write to ze source!!! OMGooses!"; 0978 delete source; 0979 delete qm; 0980 return; 0981 } 0982 0983 if( useTrash ) 0984 { 0985 TrashCollectionLocation *trash = new TrashCollectionLocation(); 0986 source->prepareMove( qm, trash ); 0987 } 0988 else 0989 source->prepareRemove( qm ); 0990 } 0991 0992 void 0993 CollectionTreeView::editTracks( const QSet<CollectionTreeItem *> &items ) const 0994 { 0995 //Create query based upon items, ensuring that if a parent and child are both 0996 //selected we ignore the child 0997 Collections::QueryMaker *qm = createMetaQueryFromItems( items, true ); 0998 if( !qm ) 0999 return; 1000 1001 (void)new TagDialog( qm ); //the dialog will show itself automatically as soon as it is ready 1002 } 1003 1004 void 1005 CollectionTreeView::slotSetFilter( const QString &filter ) 1006 { 1007 QString currentFilter = m_treeModel ? m_treeModel->currentFilter() : QString(); 1008 if( !m_filterModel || !m_treeModel || filter == currentFilter ) 1009 return; 1010 1011 // special case: transitioning from non-empty to empty buffer 1012 // -> trigger later restoring of the scroll position 1013 if( filter.isEmpty() ) // currentFilter must not be empty then (see earlier check) 1014 { 1015 // take first item, descending to leaf ones if expanded. There may be better 1016 // ways to determine what item should stay "fixed". 1017 QModelIndex scrollToIndex = m_filterModel->index( 0, 0 ); 1018 while( isExpanded( scrollToIndex ) && m_filterModel->rowCount( scrollToIndex ) > 0 ) 1019 scrollToIndex = m_filterModel->index( 0, 0, scrollToIndex); 1020 int topOffset = visualRect( scrollToIndex ).top(); 1021 1022 QModelIndex bottomIndex = m_filterModel->mapToSource( scrollToIndex ); 1023 // if we have somewhere to scroll to after filter is cleared... 1024 if( bottomIndex.isValid() ) 1025 // auto-destroys itself 1026 new DelayedScroller( this, m_treeModel, bottomIndex, topOffset ); 1027 } 1028 m_treeModel->setCurrentFilter( filter ); 1029 } 1030 1031 void 1032 CollectionTreeView::slotAddFilteredTracksToPlaylist() 1033 { 1034 if( !m_treeModel ) 1035 return; 1036 1037 // disconnect any possible earlier connection we've done 1038 disconnect( m_treeModel, &CollectionTreeItemModelBase::allQueriesFinished, 1039 this, &CollectionTreeView::slotAddFilteredTracksToPlaylist ); 1040 1041 if( m_treeModel->hasRunningQueries() ) 1042 // wait for the queries to finish 1043 connect( m_treeModel, &CollectionTreeItemModelBase::allQueriesFinished, 1044 this, &CollectionTreeView::slotAddFilteredTracksToPlaylist ); 1045 else 1046 { 1047 // yay, we can add the tracks now 1048 QSet<CollectionTreeItem *> items; 1049 for( int row = 0; row < m_treeModel->rowCount(); row++ ) 1050 { 1051 QModelIndex idx = m_treeModel->index( row, 0 ); 1052 CollectionTreeItem *item = idx.isValid() 1053 ? static_cast<CollectionTreeItem *>( idx.internalPointer() ) : nullptr; 1054 if( item ) 1055 items.insert( item ); 1056 } 1057 if( !items.isEmpty() ) 1058 playChildTracks( items, Playlist::OnAppendToPlaylistAction ); 1059 Q_EMIT addingFilteredTracksDone(); 1060 } 1061 } 1062 1063 QActionList 1064 CollectionTreeView::createBasicActions( const QModelIndexList &indices ) 1065 { 1066 QActionList actions; 1067 1068 if( !indices.isEmpty() ) 1069 { 1070 if( m_appendAction == nullptr ) 1071 { 1072 m_appendAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-add-amarok") ), 1073 i18n( "&Add to Playlist" ), this ); 1074 m_appendAction->setProperty( "popupdropper_svg_id", "append" ); 1075 connect( m_appendAction, &QAction::triggered, this, &CollectionTreeView::slotAppendChildTracks ); 1076 } 1077 1078 actions.append( m_appendAction ); 1079 1080 if( m_loadAction == nullptr ) 1081 { 1082 m_loadAction = new QAction( 1083 i18nc( "Replace the currently loaded tracks with these", 1084 "&Replace Playlist" ), this ); 1085 m_loadAction->setProperty( "popupdropper_svg_id", "load" ); 1086 connect( m_loadAction, &QAction::triggered, 1087 this, &CollectionTreeView::slotReplacePlaylistWithChildTracks ); 1088 } 1089 1090 actions.append( m_loadAction ); 1091 } 1092 1093 return actions; 1094 } 1095 1096 QActionList 1097 CollectionTreeView::createExtendedActions( const QModelIndexList &indices ) 1098 { 1099 QActionList actions; 1100 1101 if( !indices.isEmpty() ) 1102 { 1103 { //keep the scope of item minimal 1104 CollectionTreeItem *item = 1105 static_cast<CollectionTreeItem *>( indices.first().internalPointer() ); 1106 while( item->isDataItem() ) 1107 item = item->parent(); 1108 1109 Collection *collection = item->parentCollection(); 1110 CollectionLocation* location = collection->location(); 1111 1112 if( location->isOrganizable() ) 1113 { 1114 bool onlyOneCollection = true; 1115 foreach( const QModelIndex &index, indices ) 1116 { 1117 Q_UNUSED( index ) 1118 CollectionTreeItem *item = static_cast<CollectionTreeItem *>( 1119 indices.first().internalPointer() ); 1120 while( item->isDataItem() ) 1121 item = item->parent(); 1122 1123 onlyOneCollection = item->parentCollection() == collection; 1124 if( !onlyOneCollection ) 1125 break; 1126 } 1127 1128 if( onlyOneCollection ) 1129 { 1130 if( m_organizeAction == nullptr ) 1131 { 1132 m_organizeAction = new QAction( QIcon::fromTheme(QStringLiteral("folder-open") ), 1133 i18nc( "Organize Files", "Organize Files" ), this ); 1134 m_organizeAction->setProperty( "popupdropper_svg_id", "organize" ); 1135 connect( m_organizeAction, &QAction::triggered, 1136 this, &CollectionTreeView::slotOrganize ); 1137 } 1138 actions.append( m_organizeAction ); 1139 } 1140 } 1141 delete location; 1142 } 1143 1144 //hmmm... figure out what kind of item we are dealing with.... 1145 1146 if( indices.size() == 1 ) 1147 { 1148 debug() << "checking for global actions"; 1149 CollectionTreeItem *item = static_cast<CollectionTreeItem *>( 1150 indices.first().internalPointer() ); 1151 1152 QActionList gActions = The::globalCollectionActions()->actionsFor( item->data() ); 1153 foreach( QAction *action, gActions ) 1154 { 1155 if( action ) // Can become 0-pointer, see https://bugs.kde.org/show_bug.cgi?id=183250 1156 { 1157 actions.append( action ); 1158 debug() << "Got global action: " << action->text(); 1159 } 1160 } 1161 } 1162 1163 if( m_editAction == nullptr ) 1164 { 1165 m_editAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-edit-amarok") ), 1166 i18n( "&Edit Track Details" ), this ); 1167 setProperty( "popupdropper_svg_id", "edit" ); 1168 connect( m_editAction, &QAction::triggered, this, &CollectionTreeView::slotEditTracks ); 1169 } 1170 actions.append( m_editAction ); 1171 } 1172 else 1173 debug() << "invalid index or null internalPointer"; 1174 1175 return actions; 1176 } 1177 1178 QActionList 1179 CollectionTreeView::createCustomActions( const QModelIndexList &indices ) 1180 { 1181 QActionList actions; 1182 if( indices.count() == 1 ) 1183 { 1184 if( indices.first().isValid() && indices.first().internalPointer() ) 1185 { 1186 Meta::DataPtr data = static_cast<CollectionTreeItem *>( 1187 indices.first().internalPointer() )->data(); 1188 if( data ) 1189 { 1190 QScopedPointer<Capabilities::ActionsCapability> ac( 1191 data->create<Capabilities::ActionsCapability>() ); 1192 if( ac ) 1193 { 1194 QActionList cActions = ac->actions(); 1195 1196 foreach( QAction *action, cActions ) 1197 { 1198 Q_ASSERT( action ); 1199 actions.append( action ); 1200 debug() << "Got custom action: " << action->text(); 1201 } 1202 } 1203 1204 //check if this item can be bookmarked... 1205 QScopedPointer<Capabilities::BookmarkThisCapability> btc( 1206 data->create<Capabilities::BookmarkThisCapability>() ); 1207 if( btc && btc->isBookmarkable() && btc->bookmarkAction() ) 1208 actions.append( btc->bookmarkAction() ); 1209 } 1210 } 1211 } 1212 return actions; 1213 } 1214 1215 QActionList 1216 CollectionTreeView::createCollectionActions( const QModelIndexList &indices ) 1217 { 1218 QActionList actions; 1219 // Extract collection whose constituent was selected 1220 1221 CollectionTreeItem *item = 1222 static_cast<CollectionTreeItem *>( indices.first().internalPointer() ); 1223 1224 // Don't return any collection actions for non collection items 1225 if( item->isDataItem() ) 1226 return actions; 1227 1228 Collection *collection = item->parentCollection(); 1229 1230 // Generate CollectionCapability, test for existence 1231 1232 QScopedPointer<Capabilities::ActionsCapability> cc( 1233 collection->create<Capabilities::ActionsCapability>() ); 1234 1235 if( cc ) 1236 actions = cc->actions(); 1237 1238 return actions; 1239 } 1240 1241 1242 QHash<QAction *, Collection *> 1243 CollectionTreeView::getCopyActions( const QModelIndexList &indices ) 1244 { 1245 QHash<QAction *, Collection *> currentCopyDestination; 1246 1247 if( onlyOneCollection( indices ) ) 1248 { 1249 Collection *collection = getCollection( indices.first() ); 1250 QList<Collection *> writableCollections; 1251 QHash<Collection *, CollectionManager::CollectionStatus> hash = 1252 CollectionManager::instance()->collections(); 1253 QHash<Collection *, CollectionManager::CollectionStatus>::const_iterator it = 1254 hash.constBegin(); 1255 while( it != hash.constEnd() ) 1256 { 1257 Collection *coll = it.key(); 1258 if( coll && coll->isWritable() && coll != collection ) 1259 writableCollections.append( coll ); 1260 ++it; 1261 } 1262 if( !writableCollections.isEmpty() ) 1263 { 1264 foreach( Collection *coll, writableCollections ) 1265 { 1266 QAction *action = new QAction( coll->icon(), coll->prettyName(), nullptr ); 1267 action->setProperty( "popupdropper_svg_id", "collection" ); 1268 connect( action, &QAction::triggered, this, &CollectionTreeView::slotCopyTracks ); 1269 1270 currentCopyDestination.insert( action, coll ); 1271 } 1272 } 1273 } 1274 return currentCopyDestination; 1275 } 1276 1277 QHash<QAction *, Collection *> 1278 CollectionTreeView::getMoveActions( const QModelIndexList &indices ) 1279 { 1280 QHash<QAction *, Collection *> currentMoveDestination; 1281 1282 if( onlyOneCollection( indices ) ) 1283 { 1284 Collection *collection = getCollection( indices.first() ); 1285 QList<Collection *> writableCollections; 1286 QHash<Collection *, CollectionManager::CollectionStatus> hash = 1287 CollectionManager::instance()->collections(); 1288 QHash<Collection *, CollectionManager::CollectionStatus>::const_iterator it = 1289 hash.constBegin(); 1290 while( it != hash.constEnd() ) 1291 { 1292 Collection *coll = it.key(); 1293 if( coll && coll->isWritable() && coll != collection ) 1294 writableCollections.append( coll ); 1295 ++it; 1296 } 1297 if( !writableCollections.isEmpty() ) 1298 { 1299 if( collection->isWritable() ) 1300 { 1301 foreach( Collection *coll, writableCollections ) 1302 { 1303 QAction *action = new QAction( coll->icon(), coll->prettyName(), nullptr ); 1304 action->setProperty( "popupdropper_svg_id", "collection" ); 1305 connect( action, &QAction::triggered, this, &CollectionTreeView::slotMoveTracks ); 1306 currentMoveDestination.insert( action, coll ); 1307 } 1308 } 1309 } 1310 } 1311 return currentMoveDestination; 1312 } 1313 1314 bool CollectionTreeView::onlyOneCollection( const QModelIndexList &indices ) 1315 { 1316 if( !indices.isEmpty() ) 1317 { 1318 Collection *collection = getCollection( indices.first() ); 1319 foreach( const QModelIndex &index, indices ) 1320 { 1321 Collection *currentCollection = getCollection( index ); 1322 if( collection != currentCollection ) 1323 return false; 1324 } 1325 } 1326 1327 return true; 1328 } 1329 1330 Collection * 1331 CollectionTreeView::getCollection( const QModelIndex &index ) 1332 { 1333 Collection *collection = nullptr; 1334 if( index.isValid() ) 1335 { 1336 CollectionTreeItem *item = 1337 static_cast<CollectionTreeItem *>( index.internalPointer() ); 1338 while( item->isDataItem() ) 1339 item = item->parent(); 1340 collection = item->parentCollection(); 1341 } 1342 1343 return collection; 1344 } 1345 1346 void 1347 CollectionTreeView::slotReplacePlaylistWithChildTracks() 1348 { 1349 playChildTracks( m_currentItems, Playlist::OnReplacePlaylistAction ); 1350 } 1351 1352 void 1353 CollectionTreeView::slotAppendChildTracks() 1354 { 1355 playChildTracks( m_currentItems, Playlist::OnAppendToPlaylistAction ); 1356 } 1357 1358 void 1359 CollectionTreeView::slotQueueChildTracks() 1360 { 1361 playChildTracks( m_currentItems, Playlist::OnQueueToPlaylistAction ); 1362 } 1363 1364 void 1365 CollectionTreeView::slotEditTracks() 1366 { 1367 editTracks( m_currentItems ); 1368 } 1369 1370 void 1371 CollectionTreeView::slotCopyTracks() 1372 { 1373 if( !sender() ) 1374 return; 1375 if( QAction *action = dynamic_cast<QAction *>( sender() ) ) 1376 copyTracks( m_currentItems, m_currentCopyDestination[ action ], false ); 1377 } 1378 1379 void 1380 CollectionTreeView::slotMoveTracks() 1381 { 1382 if( !sender() ) 1383 return; 1384 if ( QAction *action = dynamic_cast<QAction *>( sender() ) ) 1385 copyTracks( m_currentItems, m_currentMoveDestination[ action ], true ); 1386 } 1387 1388 void 1389 CollectionTreeView::slotTrashTracks() 1390 { 1391 removeTracks( m_currentItems, true ); 1392 } 1393 1394 void 1395 CollectionTreeView::slotDeleteTracks() 1396 { 1397 removeTracks( m_currentItems, false /* do not use trash */ ); 1398 } 1399 1400 void 1401 CollectionTreeView::slotOrganize() 1402 { 1403 if( sender() ) 1404 { 1405 if( QAction *action = dynamic_cast<QAction *>( sender() ) ) 1406 { 1407 Q_UNUSED( action ) 1408 organizeTracks( m_currentItems ); 1409 } 1410 } 1411 } 1412 1413 QSet<CollectionTreeItem *> 1414 CollectionTreeView::cleanItemSet( const QSet<CollectionTreeItem *> &items ) 1415 { 1416 QSet<CollectionTreeItem *> parents; 1417 foreach( CollectionTreeItem *item, items ) 1418 { 1419 CollectionTreeItem *tmpItem = item; 1420 while( tmpItem ) 1421 { 1422 if( items.contains( tmpItem->parent() ) ) 1423 tmpItem = tmpItem->parent(); 1424 else 1425 { 1426 parents.insert( tmpItem ); 1427 break; 1428 } 1429 } 1430 } 1431 return parents; 1432 } 1433 1434 Collections::QueryMaker * 1435 CollectionTreeView::createMetaQueryFromItems( const QSet<CollectionTreeItem *> &items, 1436 bool cleanItems ) const 1437 { 1438 if( !m_treeModel ) 1439 return nullptr; 1440 1441 QSet<CollectionTreeItem*> parents = cleanItems ? cleanItemSet( items ) : items; 1442 1443 QList<Collections::QueryMaker *> queryMakers; 1444 foreach( CollectionTreeItem *item, parents ) 1445 { 1446 Collections::QueryMaker *qm = item->queryMaker(); 1447 for( CollectionTreeItem *tmp = item; tmp; tmp = tmp->parent() ) 1448 tmp->addMatch( qm, m_treeModel->levelCategory( tmp->level() - 1 ) ); 1449 Collections::addTextualFilter( qm, m_treeModel->currentFilter() ); 1450 queryMakers.append( qm ); 1451 } 1452 return new Collections::MetaQueryMaker( queryMakers ); 1453 } 1454 1455 #include "CollectionTreeView.moc" // Q_OBJECTs defined in CollectionTreeView.cpp 1456 #include "moc_CollectionTreeView.cpp" // Q_OBJECTs defined in CollectionTreeView.h