Warning, file /multimedia/amarok/src/browsers/CollectionTreeItemModelBase.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-2009 Maximilian Kossick <maximilian.kossick@googlemail.com> * 0004 * Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org> * 0005 * * 0006 * This program is free software; you can redistribute it and/or modify it under * 0007 * the terms of the GNU General Public License as published by the Free Software * 0008 * Foundation; either version 2 of the License, or (at your option) any later * 0009 * version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0013 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU General Public License along with * 0016 * this program. If not, see <http://www.gnu.org/licenses/>. * 0017 ****************************************************************************************/ 0018 0019 #define DEBUG_PREFIX "CollectionTreeItemModelBase" 0020 0021 #include "CollectionTreeItemModelBase.h" 0022 0023 #include "AmarokMimeData.h" 0024 #include "FileType.h" 0025 #include "SvgHandler.h" 0026 #include "amarokconfig.h" 0027 #include "browsers/CollectionTreeItem.h" 0028 #include "core/collections/Collection.h" 0029 #include "core/collections/QueryMaker.h" 0030 #include "core/meta/TrackEditor.h" 0031 #include "core/meta/support/MetaConstants.h" 0032 #include "core/support/Amarok.h" 0033 #include "core/support/Debug.h" 0034 #include "core-impl/collections/support/TextualQueryFilter.h" 0035 #include "widgets/PrettyTreeRoles.h" 0036 0037 #include <KLocalizedString> 0038 #include <ThreadWeaver/Lambda> 0039 #include <ThreadWeaver/Queue> 0040 0041 #include <QApplication> 0042 #include <QIcon> 0043 #include <QPixmap> 0044 #include <QPointer> 0045 #include <QStandardPaths> 0046 #include <QStyle> 0047 #include <QTimeLine> 0048 #include <QTimer> 0049 0050 #include <algorithm> 0051 #include <functional> 0052 0053 0054 using namespace Meta; 0055 0056 0057 class TrackLoaderJob : public ThreadWeaver::Job 0058 { 0059 public: 0060 TrackLoaderJob( const QModelIndex &index, const Meta::AlbumPtr &album, CollectionTreeItemModelBase *model ) 0061 : m_index( index ) 0062 , m_album( album ) 0063 , m_model( model ) 0064 , m_abortRequested( false ) 0065 { 0066 if( !m_model || !m_album || !m_index.isValid() ) 0067 requestAbort(); 0068 } 0069 0070 void requestAbort() override 0071 { 0072 m_abortRequested = true; 0073 } 0074 0075 protected: 0076 void run( ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread ) override 0077 { 0078 Q_UNUSED( self ) 0079 Q_UNUSED( thread ) 0080 0081 if( m_abortRequested || !m_model ) 0082 return; 0083 0084 const auto tracks = m_album->tracks(); 0085 0086 if( m_model && !m_abortRequested ) 0087 { 0088 auto slot = std::bind( &CollectionTreeItemModelBase::tracksLoaded, m_model, m_album, m_index, tracks ); 0089 QTimer::singleShot( 0, m_model, slot ); 0090 } 0091 } 0092 0093 private: 0094 QPersistentModelIndex m_index; 0095 Meta::AlbumPtr m_album; 0096 QPointer<CollectionTreeItemModelBase> m_model; 0097 bool m_abortRequested; 0098 }; 0099 0100 inline uint qHash( const Meta::DataPtr &data ) 0101 { 0102 return qHash( data.data() ); 0103 } 0104 0105 /** 0106 * This set determines which collection browser levels should have shown Various Artists 0107 * item under them. AlbumArtist is certain, (Track)Artist is questionable. 0108 */ 0109 static const QSet<CategoryId::CatMenuId> variousArtistCategories = 0110 QSet<CategoryId::CatMenuId>() << CategoryId::AlbumArtist; 0111 0112 CollectionTreeItemModelBase::CollectionTreeItemModelBase( ) 0113 : QAbstractItemModel() 0114 , m_rootItem( nullptr ) 0115 , m_animFrame( 0 ) 0116 , m_loading1( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, QStringLiteral("amarok/images/loading1.png") ) ) ) 0117 , m_loading2( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, QStringLiteral("amarok/images/loading2.png") ) ) ) 0118 , m_currentAnimPixmap( m_loading1 ) 0119 , m_autoExpand( false ) 0120 { 0121 m_timeLine = new QTimeLine( 10000, this ); 0122 m_timeLine->setFrameRange( 0, 20 ); 0123 m_timeLine->setLoopCount ( 0 ); 0124 connect( m_timeLine, &QTimeLine::frameChanged, this, &CollectionTreeItemModelBase::loadingAnimationTick ); 0125 } 0126 0127 CollectionTreeItemModelBase::~CollectionTreeItemModelBase() 0128 { 0129 KConfigGroup config = Amarok::config( QStringLiteral("Collection Browser") ); 0130 QList<int> levelNumbers; 0131 foreach( CategoryId::CatMenuId category, levels() ) 0132 levelNumbers.append( category ); 0133 config.writeEntry( "TreeCategory", levelNumbers ); 0134 0135 if( m_rootItem ) 0136 m_rootItem->deleteLater(); 0137 } 0138 0139 Qt::ItemFlags CollectionTreeItemModelBase::flags(const QModelIndex & index) const 0140 { 0141 Qt::ItemFlags flags = {}; 0142 if( index.isValid() ) 0143 { 0144 flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable; 0145 } 0146 return flags; 0147 } 0148 0149 bool 0150 CollectionTreeItemModelBase::setData( const QModelIndex &index, const QVariant &value, int role ) 0151 { 0152 Q_UNUSED( role ) 0153 0154 if( !index.isValid() ) 0155 return false; 0156 CollectionTreeItem *item = static_cast<CollectionTreeItem*>( index.internalPointer() ); 0157 0158 Meta::DataPtr data = item->data(); 0159 0160 if( Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( data ) ) 0161 { 0162 Meta::TrackEditorPtr ec = track->editor(); 0163 if( ec ) 0164 { 0165 ec->setTitle( value.toString() ); 0166 Q_EMIT dataChanged( index, index ); 0167 return true; 0168 } 0169 } 0170 else if( Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( data ) ) 0171 { 0172 Meta::TrackList tracks = album->tracks(); 0173 if( !tracks.isEmpty() ) 0174 { 0175 foreach( Meta::TrackPtr track, tracks ) 0176 { 0177 Meta::TrackEditorPtr ec = track->editor(); 0178 if( ec ) 0179 ec->setAlbum( value.toString() ); 0180 } 0181 Q_EMIT dataChanged( index, index ); 0182 return true; 0183 } 0184 } 0185 else if( Meta::ArtistPtr artist = Meta::ArtistPtr::dynamicCast( data ) ) 0186 { 0187 Meta::TrackList tracks = artist->tracks(); 0188 if( !tracks.isEmpty() ) 0189 { 0190 foreach( Meta::TrackPtr track, tracks ) 0191 { 0192 Meta::TrackEditorPtr ec = track->editor(); 0193 if( ec ) 0194 ec->setArtist( value.toString() ); 0195 } 0196 Q_EMIT dataChanged( index, index ); 0197 return true; 0198 } 0199 } 0200 else if( Meta::GenrePtr genre = Meta::GenrePtr::dynamicCast( data ) ) 0201 { 0202 Meta::TrackList tracks = genre->tracks(); 0203 if( !tracks.isEmpty() ) 0204 { 0205 foreach( Meta::TrackPtr track, tracks ) 0206 { 0207 Meta::TrackEditorPtr ec = track->editor(); 0208 if( ec ) 0209 ec->setGenre( value.toString() ); 0210 } 0211 Q_EMIT dataChanged( index, index ); 0212 return true; 0213 } 0214 } 0215 else if( Meta::YearPtr year = Meta::YearPtr::dynamicCast( data ) ) 0216 { 0217 Meta::TrackList tracks = year->tracks(); 0218 if( !tracks.isEmpty() ) 0219 { 0220 foreach( Meta::TrackPtr track, tracks ) 0221 { 0222 Meta::TrackEditorPtr ec = track->editor(); 0223 if( ec ) 0224 ec->setYear( value.toInt() ); 0225 } 0226 Q_EMIT dataChanged( index, index ); 0227 return true; 0228 } 0229 } 0230 else if( Meta::ComposerPtr composer = Meta::ComposerPtr::dynamicCast( data ) ) 0231 { 0232 Meta::TrackList tracks = composer->tracks(); 0233 if( !tracks.isEmpty() ) 0234 { 0235 foreach( Meta::TrackPtr track, tracks ) 0236 { 0237 Meta::TrackEditorPtr ec = track->editor(); 0238 if( ec ) 0239 ec->setComposer( value.toString() ); 0240 } 0241 Q_EMIT dataChanged( index, index ); 0242 return true; 0243 } 0244 } 0245 return false; 0246 } 0247 0248 QVariant 0249 CollectionTreeItemModelBase::dataForItem( CollectionTreeItem *item, int role, int level ) const 0250 { 0251 if( level == -1 ) 0252 level = item->level(); 0253 0254 if( item->isTrackItem() ) 0255 { 0256 Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( item->data() ); 0257 switch( role ) 0258 { 0259 case Qt::DisplayRole: 0260 case Qt::ToolTipRole: 0261 case PrettyTreeRoles::FilterRole: 0262 { 0263 QString name = track->prettyName(); 0264 Meta::AlbumPtr album = track->album(); 0265 Meta::ArtistPtr artist = track->artist(); 0266 0267 if( album && artist && album->isCompilation() ) 0268 name.prepend( QStringLiteral("%1 - ").arg(artist->prettyName()) ); 0269 0270 if( AmarokConfig::showTrackNumbers() ) 0271 { 0272 int trackNum = track->trackNumber(); 0273 if( trackNum > 0 ) 0274 name.prepend( QStringLiteral("%1 - ").arg(trackNum) ); 0275 } 0276 0277 // Check empty after track logic and before album logic 0278 if( name.isEmpty() ) 0279 name = i18nc( "The Name is not known", "Unknown" ); 0280 return name; 0281 } 0282 0283 case Qt::DecorationRole: 0284 return QIcon::fromTheme( QStringLiteral("media-album-track") ); 0285 case PrettyTreeRoles::SortRole: 0286 return track->sortableName(); 0287 } 0288 } 0289 else if( item->isAlbumItem() ) 0290 { 0291 Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( item->data() ); 0292 switch( role ) 0293 { 0294 case Qt::DisplayRole: 0295 case Qt::ToolTipRole: 0296 { 0297 QString name = album->prettyName(); 0298 // add years for named albums (if enabled) 0299 if( AmarokConfig::showYears() ) 0300 { 0301 if( m_years.contains( album.data() ) ) 0302 { 0303 int year = m_years.value( album.data() ); 0304 0305 if( year > 0 ) 0306 name.prepend( QStringLiteral("%1 - ").arg( year ) ); 0307 } 0308 else if( !album->name().isEmpty() ) 0309 { 0310 if( !m_loadingAlbums.contains( album ) ) 0311 { 0312 m_loadingAlbums.insert( album ); 0313 0314 auto nonConstThis = const_cast<CollectionTreeItemModelBase*>( this ); 0315 auto job = QSharedPointer<TrackLoaderJob>::create( itemIndex( item ), album, nonConstThis ); 0316 ThreadWeaver::Queue::instance()->enqueue( job ); 0317 } 0318 } 0319 } 0320 return name; 0321 } 0322 0323 case Qt::DecorationRole: 0324 if( AmarokConfig::showAlbumArt() ) 0325 { 0326 QStyle *style = QApplication::style(); 0327 const int largeIconSize = style->pixelMetric( QStyle::PM_LargeIconSize ); 0328 0329 return The::svgHandler()->imageWithBorder( album, largeIconSize, 2 ); 0330 } 0331 else 0332 return iconForLevel( level ); 0333 0334 case PrettyTreeRoles::SortRole: 0335 return album->sortableName(); 0336 0337 case PrettyTreeRoles::HasCoverRole: 0338 return AmarokConfig::showAlbumArt(); 0339 0340 case PrettyTreeRoles::YearRole: 0341 { 0342 if( m_years.contains( album.data() ) ) 0343 return m_years.value( album.data() ); 0344 0345 else if( !album->name().isEmpty() ) 0346 { 0347 if( !m_loadingAlbums.contains( album ) ) 0348 { 0349 m_loadingAlbums.insert( album ); 0350 0351 auto nonConstThis = const_cast<CollectionTreeItemModelBase*>( this ); 0352 auto job = QSharedPointer<TrackLoaderJob>::create( itemIndex( item ), album, nonConstThis ); 0353 ThreadWeaver::Queue::instance()->enqueue( job ); 0354 } 0355 } 0356 return -1; 0357 } 0358 } 0359 } 0360 else if( item->isDataItem() ) 0361 { 0362 switch( role ) 0363 { 0364 case Qt::DisplayRole: 0365 case Qt::ToolTipRole: 0366 case PrettyTreeRoles::FilterRole: 0367 { 0368 QString name = item->data()->prettyName(); 0369 if( name.isEmpty() ) 0370 name = i18nc( "The Name is not known", "Unknown" ); 0371 return name; 0372 } 0373 0374 case Qt::DecorationRole: 0375 { 0376 if( m_childQueries.values().contains( item ) ) 0377 { 0378 if( level < m_levelType.count() ) 0379 return m_currentAnimPixmap; 0380 } 0381 return iconForLevel( level ); 0382 } 0383 0384 case PrettyTreeRoles::SortRole: 0385 return item->data()->sortableName(); 0386 } 0387 } 0388 else if( item->isVariousArtistItem() ) 0389 { 0390 switch( role ) 0391 { 0392 case Qt::DecorationRole: 0393 return QIcon::fromTheme( QStringLiteral("similarartists-amarok") ); 0394 case Qt::DisplayRole: 0395 return i18n( "Various Artists" ); 0396 case PrettyTreeRoles::SortRole: 0397 return QString(); // so that we can compare it against other strings 0398 } 0399 } 0400 0401 // -- all other roles are handled by item 0402 return item->data( role ); 0403 } 0404 0405 QVariant 0406 CollectionTreeItemModelBase::headerData(int section, Qt::Orientation orientation, int role) const 0407 { 0408 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) 0409 { 0410 if (section == 0) 0411 return m_headerText; 0412 } 0413 return QVariant(); 0414 } 0415 0416 QModelIndex 0417 CollectionTreeItemModelBase::index(int row, int column, const QModelIndex & parent) const 0418 { 0419 //ensure sanity of parameters 0420 //we are a tree model, there are no columns 0421 if( row < 0 || column != 0 ) 0422 return QModelIndex(); 0423 0424 CollectionTreeItem *parentItem; 0425 0426 if (!parent.isValid()) 0427 parentItem = m_rootItem; 0428 else 0429 parentItem = static_cast<CollectionTreeItem*>(parent.internalPointer()); 0430 0431 CollectionTreeItem *childItem = parentItem->child(row); 0432 if( childItem ) 0433 return createIndex(row, column, childItem); 0434 else 0435 return QModelIndex(); 0436 } 0437 0438 QModelIndex 0439 CollectionTreeItemModelBase::parent(const QModelIndex & index) const 0440 { 0441 if( !index.isValid() ) 0442 return QModelIndex(); 0443 0444 CollectionTreeItem *childItem = static_cast<CollectionTreeItem*>(index.internalPointer()); 0445 CollectionTreeItem *parentItem = childItem->parent(); 0446 0447 return itemIndex( parentItem ); 0448 } 0449 0450 int 0451 CollectionTreeItemModelBase::rowCount(const QModelIndex & parent) const 0452 { 0453 CollectionTreeItem *parentItem; 0454 0455 if( !parent.isValid() ) 0456 parentItem = m_rootItem; 0457 else 0458 parentItem = static_cast<CollectionTreeItem*>(parent.internalPointer()); 0459 0460 return parentItem->childCount(); 0461 } 0462 0463 int CollectionTreeItemModelBase::columnCount(const QModelIndex & parent) const 0464 { 0465 Q_UNUSED( parent ) 0466 return 1; 0467 } 0468 0469 QStringList 0470 CollectionTreeItemModelBase::mimeTypes() const 0471 { 0472 QStringList types; 0473 types << AmarokMimeData::TRACK_MIME; 0474 return types; 0475 } 0476 0477 QMimeData* 0478 CollectionTreeItemModelBase::mimeData( const QModelIndexList &indices ) const 0479 { 0480 if ( indices.isEmpty() ) 0481 return nullptr; 0482 0483 // first, filter out duplicate entries that may arise when both parent and child are selected 0484 QSet<QModelIndex> indexSet( indices.begin(), indices.end() ); 0485 QMutableSetIterator<QModelIndex> it( indexSet ); 0486 while( it.hasNext() ) 0487 { 0488 it.next(); 0489 // we go up in parent hierarchy searching whether some parent indices are already in set 0490 QModelIndex parentIndex = it.value(); 0491 while( parentIndex.isValid() ) // leave the root (top, invalid) index intact 0492 { 0493 parentIndex = parentIndex.parent(); // yes, we start from the parent of current index 0494 if( indexSet.contains( parentIndex ) ) 0495 { 0496 it.remove(); // parent already in selected set, remove child 0497 break; // no need to continue inner loop, already deleted 0498 } 0499 } 0500 } 0501 0502 QList<CollectionTreeItem*> items; 0503 foreach( const QModelIndex &index, indexSet ) 0504 { 0505 if( index.isValid() ) 0506 items << static_cast<CollectionTreeItem*>( index.internalPointer() ); 0507 } 0508 0509 return mimeData( items ); 0510 } 0511 0512 QMimeData* 0513 CollectionTreeItemModelBase::mimeData( const QList<CollectionTreeItem*> &items ) const 0514 { 0515 if ( items.isEmpty() ) 0516 return nullptr; 0517 0518 Meta::TrackList tracks; 0519 QList<Collections::QueryMaker*> queries; 0520 0521 foreach( CollectionTreeItem *item, items ) 0522 { 0523 if( item->allDescendentTracksLoaded() ) { 0524 tracks << item->descendentTracks(); 0525 } 0526 else 0527 { 0528 Collections::QueryMaker *qm = item->queryMaker(); 0529 for( CollectionTreeItem *tmp = item; tmp; tmp = tmp->parent() ) 0530 tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); 0531 Collections::addTextualFilter( qm, m_currentFilter ); 0532 queries.append( qm ); 0533 } 0534 } 0535 0536 std::stable_sort( tracks.begin(), tracks.end(), Meta::Track::lessThan ); 0537 0538 AmarokMimeData *mimeData = new AmarokMimeData(); 0539 mimeData->setTracks( tracks ); 0540 mimeData->setQueryMakers( queries ); 0541 mimeData->startQueries(); 0542 return mimeData; 0543 } 0544 0545 bool 0546 CollectionTreeItemModelBase::hasChildren ( const QModelIndex & parent ) const 0547 { 0548 if( !parent.isValid() ) 0549 return true; // must be root item! 0550 0551 CollectionTreeItem *item = static_cast<CollectionTreeItem*>(parent.internalPointer()); 0552 //we added the collection level so we have to be careful with the item level 0553 return !item->isDataItem() || item->level() + levelModifier() <= m_levelType.count(); 0554 0555 } 0556 0557 void 0558 CollectionTreeItemModelBase::ensureChildrenLoaded( CollectionTreeItem *item ) 0559 { 0560 //only start a query if necessary and we are not querying for the item's children already 0561 if ( item->requiresUpdate() && !m_runningQueries.contains( item ) ) 0562 { 0563 listForLevel( item->level() + levelModifier(), item->queryMaker(), item ); 0564 } 0565 } 0566 0567 CollectionTreeItem * 0568 CollectionTreeItemModelBase::treeItem( const QModelIndex &index ) const 0569 { 0570 if( !index.isValid() || index.model() != this ) 0571 return nullptr; 0572 0573 return static_cast<CollectionTreeItem *>( index.internalPointer() ); 0574 } 0575 0576 QModelIndex 0577 CollectionTreeItemModelBase::itemIndex( CollectionTreeItem *item ) const 0578 { 0579 if( !item || item == m_rootItem ) 0580 return QModelIndex(); 0581 0582 return createIndex( item->row(), 0, item ); 0583 } 0584 0585 void 0586 CollectionTreeItemModelBase::listForLevel(int level, Collections::QueryMaker * qm, CollectionTreeItem * parent) 0587 { 0588 if( qm && parent ) 0589 { 0590 // this check should not hurt anyone... needs to check if single... needs it 0591 if( m_runningQueries.contains( parent ) ) 0592 return; 0593 0594 // following special cases are handled extra - right when the parent is added 0595 if( level > m_levelType.count() || 0596 parent->isVariousArtistItem() || 0597 parent->isNoLabelItem() ) 0598 { 0599 qm->deleteLater(); 0600 return; 0601 } 0602 0603 // - the last level are always the tracks 0604 if ( level == m_levelType.count() ) 0605 qm->setQueryType( Collections::QueryMaker::Track ); 0606 0607 // - all other levels are more complicate 0608 else 0609 { 0610 Collections::QueryMaker::QueryType nextLevel; 0611 if( level + 1 >= m_levelType.count() ) 0612 nextLevel = Collections::QueryMaker::Track; 0613 else 0614 nextLevel = mapCategoryToQueryType( m_levelType.value( level + 1 ) ); 0615 0616 qm->setQueryType( mapCategoryToQueryType( m_levelType.value( level ) ) ); 0617 0618 CategoryId::CatMenuId category = m_levelType.value( level ); 0619 if( category == CategoryId::Album ) 0620 { 0621 // restrict query to normal albums if the previous level 0622 // was the AlbumArtist category. In that case we handle compilations below 0623 if( levelCategory( level - 1 ) == CategoryId::AlbumArtist ) 0624 qm->setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums ); 0625 } 0626 else if( variousArtistCategories.contains( category ) ) 0627 // we used to handleCompilations() only if nextLevel is Album, but I cannot 0628 // tell any reason why we should have done this --- strohel 0629 handleCompilations( nextLevel, parent ); 0630 else if( category == CategoryId::Label ) 0631 handleTracksWithoutLabels( nextLevel, parent ); 0632 } 0633 0634 for( CollectionTreeItem *tmp = parent; tmp; tmp = tmp->parent() ) 0635 tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); 0636 Collections::addTextualFilter( qm, m_currentFilter ); 0637 addQueryMaker( parent, qm ); 0638 m_childQueries.insert( qm, parent ); 0639 qm->run(); 0640 0641 //some very quick queries may be done so fast that the loading 0642 //animation creates an unnecessary flicker, therefore delay it for a bit 0643 QTimer::singleShot( 150, this, &CollectionTreeItemModelBase::startAnimationTick ); 0644 } 0645 } 0646 0647 void 0648 CollectionTreeItemModelBase::setLevels( const QList<CategoryId::CatMenuId> &levelType ) 0649 { 0650 if( m_levelType == levelType ) 0651 return; 0652 0653 m_levelType = levelType; 0654 updateHeaderText(); 0655 m_expandedItems.clear(); 0656 m_expandedSpecialNodes.clear(); 0657 m_runningQueries.clear(); 0658 m_childQueries.clear(); 0659 m_compilationQueries.clear(); 0660 filterChildren(); 0661 } 0662 0663 Collections::QueryMaker::QueryType 0664 CollectionTreeItemModelBase::mapCategoryToQueryType( int levelType ) const 0665 { 0666 Collections::QueryMaker::QueryType type; 0667 switch( levelType ) 0668 { 0669 case CategoryId::Album: 0670 type = Collections::QueryMaker::Album; 0671 break; 0672 case CategoryId::Artist: 0673 type = Collections::QueryMaker::Artist; 0674 break; 0675 case CategoryId::AlbumArtist: 0676 type = Collections::QueryMaker::AlbumArtist; 0677 break; 0678 case CategoryId::Composer: 0679 type = Collections::QueryMaker::Composer; 0680 break; 0681 case CategoryId::Genre: 0682 type = Collections::QueryMaker::Genre; 0683 break; 0684 case CategoryId::Label: 0685 type = Collections::QueryMaker::Label; 0686 break; 0687 case CategoryId::Year: 0688 type = Collections::QueryMaker::Year; 0689 break; 0690 default: 0691 type = Collections::QueryMaker::None; 0692 break; 0693 } 0694 0695 return type; 0696 } 0697 0698 void 0699 CollectionTreeItemModelBase::tracksLoaded( const Meta::AlbumPtr &album, const QModelIndex &index, const Meta::TrackList& tracks ) 0700 { 0701 DEBUG_BLOCK 0702 0703 if( !album ) 0704 return; 0705 0706 m_loadingAlbums.remove( album ); 0707 0708 if( !index.isValid() ) 0709 return; 0710 0711 int year = 0; 0712 0713 if( !tracks.isEmpty() ) 0714 { 0715 Meta::YearPtr yearPtr = tracks.first()->year(); 0716 if( yearPtr ) 0717 year = yearPtr->year(); 0718 0719 debug() << "Valid album year found:" << year; 0720 } 0721 0722 if( !m_years.contains( album.data() ) || m_years.value( album.data() ) != year ) 0723 { 0724 m_years[ album.data() ] = year; 0725 Q_EMIT dataChanged( index, index ); 0726 } 0727 } 0728 0729 void 0730 CollectionTreeItemModelBase::addQueryMaker( CollectionTreeItem* item, 0731 Collections::QueryMaker *qm ) const 0732 { 0733 connect( qm, &Collections::QueryMaker::newTracksReady, this, &CollectionTreeItemModelBase::newTracksReady, Qt::QueuedConnection ); 0734 connect( qm, &Collections::QueryMaker::newArtistsReady, this, &CollectionTreeItemModelBase::newArtistsReady, Qt::QueuedConnection ); 0735 connect( qm, &Collections::QueryMaker::newAlbumsReady, this, &CollectionTreeItemModelBase::newAlbumsReady, Qt::QueuedConnection ); 0736 connect( qm, &Collections::QueryMaker::newGenresReady, this, &CollectionTreeItemModelBase::newGenresReady, Qt::QueuedConnection ); 0737 connect( qm, &Collections::QueryMaker::newComposersReady, this, &CollectionTreeItemModelBase::newComposersReady, Qt::QueuedConnection ); 0738 connect( qm, &Collections::QueryMaker::newYearsReady, this, &CollectionTreeItemModelBase::newYearsReady, Qt::QueuedConnection ); 0739 connect( qm, &Collections::QueryMaker::newLabelsReady, this, &CollectionTreeItemModelBase::newLabelsReady, Qt::QueuedConnection ); 0740 connect( qm, &Collections::QueryMaker::newDataReady, this, &CollectionTreeItemModelBase::newDataReady, Qt::QueuedConnection ); 0741 connect( qm, &Collections::QueryMaker::queryDone, this, &CollectionTreeItemModelBase::queryDone, Qt::QueuedConnection ); 0742 m_runningQueries.insert( item, qm ); 0743 } 0744 0745 void 0746 CollectionTreeItemModelBase::queryDone() 0747 { 0748 Collections::QueryMaker *qm = qobject_cast<Collections::QueryMaker*>( sender() ); 0749 if( !qm ) 0750 return; 0751 0752 CollectionTreeItem* item = nullptr; 0753 if( m_childQueries.contains( qm ) ) 0754 item = m_childQueries.take( qm ); 0755 else if( m_compilationQueries.contains( qm ) ) 0756 item = m_compilationQueries.take( qm ); 0757 else if( m_noLabelsQueries.contains( qm ) ) 0758 item = m_noLabelsQueries.take( qm ); 0759 0760 if( item ) 0761 m_runningQueries.remove( item, qm ); 0762 0763 //reset icon for this item 0764 if( item && item != m_rootItem ) 0765 { 0766 Q_EMIT dataChanged( itemIndex( item ), itemIndex( item ) ); 0767 } 0768 0769 //stop timer if there are no more animations active 0770 if( m_runningQueries.isEmpty() ) 0771 { 0772 Q_EMIT allQueriesFinished( m_autoExpand ); 0773 m_autoExpand = false; // reset to default value 0774 m_timeLine->stop(); 0775 } 0776 qm->deleteLater(); 0777 } 0778 0779 // TODO 0780 0781 /** Small helper function to convert a list of e.g. tracks to a list of DataPtr */ 0782 template<class PointerType> 0783 static Meta::DataList 0784 convertToDataList( const QList<PointerType>& list ) 0785 { 0786 Meta::DataList data; 0787 for( const auto &p : list ) 0788 data << Meta::DataPtr::staticCast( p ); 0789 0790 return data; 0791 } 0792 0793 void 0794 CollectionTreeItemModelBase::newTracksReady( const Meta::TrackList &res ) 0795 { 0796 newDataReady( convertToDataList( res ) ); 0797 } 0798 0799 void 0800 CollectionTreeItemModelBase::newArtistsReady( const Meta::ArtistList &res ) 0801 { 0802 newDataReady( convertToDataList( res ) ); 0803 } 0804 0805 void 0806 CollectionTreeItemModelBase::newAlbumsReady( const Meta::AlbumList &res ) 0807 { 0808 newDataReady( convertToDataList( res ) ); 0809 } 0810 0811 void 0812 CollectionTreeItemModelBase::newGenresReady( const Meta::GenreList &res ) 0813 { 0814 newDataReady( convertToDataList( res ) ); 0815 } 0816 0817 void 0818 CollectionTreeItemModelBase::newComposersReady( const Meta::ComposerList &res ) 0819 { 0820 newDataReady( convertToDataList( res ) ); 0821 } 0822 0823 void 0824 CollectionTreeItemModelBase::newYearsReady( const Meta::YearList &res ) 0825 { 0826 newDataReady( convertToDataList( res ) ); 0827 } 0828 0829 void 0830 CollectionTreeItemModelBase::newLabelsReady( const Meta::LabelList &res ) 0831 { 0832 newDataReady( convertToDataList( res ) ); 0833 } 0834 0835 void 0836 CollectionTreeItemModelBase::newDataReady( const Meta::DataList &data ) 0837 { 0838 //if we are expanding an item, we'll find the sender in childQueries 0839 //otherwise we are filtering all collections 0840 Collections::QueryMaker *qm = qobject_cast<Collections::QueryMaker*>( sender() ); 0841 if( !qm ) 0842 return; 0843 0844 if( m_childQueries.contains( qm ) ) 0845 handleNormalQueryResult( qm, data ); 0846 0847 else if( m_compilationQueries.contains( qm ) ) 0848 handleSpecialQueryResult( CollectionTreeItem::VariousArtist, qm, data ); 0849 0850 else if( m_noLabelsQueries.contains( qm ) ) 0851 handleSpecialQueryResult( CollectionTreeItem::NoLabel, qm, data ); 0852 } 0853 0854 void 0855 CollectionTreeItemModelBase::handleSpecialQueryResult( CollectionTreeItem::Type type, Collections::QueryMaker *qm, const Meta::DataList &dataList ) 0856 { 0857 CollectionTreeItem *parent = nullptr; 0858 0859 if( type == CollectionTreeItem::VariousArtist ) 0860 parent = m_compilationQueries.value( qm ); 0861 0862 else if( type == CollectionTreeItem::NoLabel ) 0863 parent = m_noLabelsQueries.value( qm ); 0864 0865 if( parent ) 0866 { 0867 QModelIndex parentIndex = itemIndex( parent ); 0868 0869 //if the special query did not return a result we have to remove the 0870 //the special node itself 0871 if( dataList.isEmpty() ) 0872 { 0873 for( int i = 0; i < parent->childCount(); i++ ) 0874 { 0875 CollectionTreeItem *cti = parent->child( i ); 0876 if( cti->type() == type ) 0877 { 0878 //found the special node 0879 beginRemoveRows( parentIndex, cti->row(), cti->row() ); 0880 cti = nullptr; //will be deleted; 0881 parent->removeChild( i ); 0882 endRemoveRows(); 0883 break; 0884 } 0885 } 0886 //we have removed the special node if it existed 0887 return; 0888 } 0889 0890 CollectionTreeItem *specialNode = nullptr; 0891 if( parent->childCount() == 0 ) 0892 { 0893 //we only insert the special node 0894 beginInsertRows( parentIndex, 0, 0 ); 0895 specialNode = new CollectionTreeItem( type, dataList, parent, this ); 0896 //set requiresUpdate, otherwise we will query for the children of specialNode again! 0897 specialNode->setRequiresUpdate( false ); 0898 endInsertRows(); 0899 } 0900 else 0901 { 0902 for( int i = 0; i < parent->childCount(); i++ ) 0903 { 0904 CollectionTreeItem *cti = parent->child( i ); 0905 if( cti->type() == type ) 0906 { 0907 //found the special node 0908 specialNode = cti; 0909 break; 0910 } 0911 } 0912 if( !specialNode ) 0913 { 0914 //we only insert the special node 0915 beginInsertRows( parentIndex, 0, 0 ); 0916 specialNode = new CollectionTreeItem( type, dataList, parent, this ); 0917 //set requiresUpdate, otherwise we will query for the children of specialNode again! 0918 specialNode->setRequiresUpdate( false ); 0919 endInsertRows(); 0920 } 0921 else 0922 { 0923 //only call populateChildren for the special node if we have not 0924 //created it in this method call. The special node ctor takes care 0925 //of that itself 0926 populateChildren( dataList, specialNode, itemIndex( specialNode ) ); 0927 } 0928 //populate children will call setRequiresUpdate on vaNode 0929 //but as the special query is based on specialNode's parent, 0930 //we have to call setRequiresUpdate on the parent too 0931 //yes, this will mean we will call setRequiresUpdate twice 0932 parent->setRequiresUpdate( false ); 0933 0934 for( int count = specialNode->childCount(), i = 0; i < count; ++i ) 0935 { 0936 CollectionTreeItem *item = specialNode->child( i ); 0937 if ( m_expandedItems.contains( item->data() ) ) //item will always be a data item 0938 { 0939 listForLevel( item->level() + levelModifier(), item->queryMaker(), item ); 0940 } 0941 } 0942 } 0943 0944 //if the special node exists, check if it has to be expanded 0945 if( specialNode ) 0946 { 0947 if( m_expandedSpecialNodes.contains( parent->parentCollection() ) ) 0948 { 0949 Q_EMIT expandIndex( createIndex( 0, 0, specialNode ) ); //we have just inserted the vaItem at row 0 0950 } 0951 } 0952 } 0953 } 0954 0955 void 0956 CollectionTreeItemModelBase::handleNormalQueryResult( Collections::QueryMaker *qm, const Meta::DataList &dataList ) 0957 { 0958 CollectionTreeItem *parent = m_childQueries.value( qm ); 0959 if( parent ) { 0960 QModelIndex parentIndex = itemIndex( parent ); 0961 populateChildren( dataList, parent, parentIndex ); 0962 0963 if ( parent->isDataItem() ) 0964 { 0965 if ( m_expandedItems.contains( parent->data() ) ) 0966 Q_EMIT expandIndex( parentIndex ); 0967 else 0968 //simply insert the item, nothing will change if it is already in the set 0969 m_expandedItems.insert( parent->data() ); 0970 } 0971 } 0972 } 0973 0974 void 0975 CollectionTreeItemModelBase::populateChildren( const DataList &dataList, CollectionTreeItem *parent, const QModelIndex &parentIndex ) 0976 { 0977 CategoryId::CatMenuId childCategory = levelCategory( parent->level() ); 0978 0979 // add new rows after existing ones here (which means all artists nodes 0980 // will be inserted after the "Various Artists" node) 0981 // figure out which children of parent have to be removed, 0982 // which new children have to be added, and preemptively Q_EMIT dataChanged for the rest 0983 // have to check how that influences performance... 0984 const QSet<Meta::DataPtr> dataSet(dataList.begin(), dataList.end()); 0985 QSet<Meta::DataPtr> childrenSet; 0986 foreach( CollectionTreeItem *child, parent->children() ) 0987 { 0988 // we don't add null children, these are special-cased below 0989 if( !child->data() ) 0990 continue; 0991 0992 childrenSet.insert( child->data() ); 0993 } 0994 const QSet<Meta::DataPtr> dataToBeAdded = dataSet - childrenSet; 0995 const QSet<Meta::DataPtr> dataToBeRemoved = childrenSet - dataSet; 0996 0997 // first remove all rows that have to be removed 0998 // walking through the children in reverse order does not screw up the order 0999 for( int i = parent->childCount() - 1; i >= 0; i-- ) 1000 { 1001 CollectionTreeItem *child = parent->child( i ); 1002 1003 // should this child be removed? 1004 bool toBeRemoved; 1005 1006 if( child->isDataItem() ) 1007 toBeRemoved = dataToBeRemoved.contains( child->data() ); 1008 else if( child->isVariousArtistItem() ) 1009 toBeRemoved = !variousArtistCategories.contains( childCategory ); 1010 else if( child->isNoLabelItem() ) 1011 toBeRemoved = childCategory != CategoryId::Label; 1012 else 1013 { 1014 warning() << "Unknown child type encountered in populateChildren(), removing"; 1015 toBeRemoved = true; 1016 } 1017 1018 if( toBeRemoved ) 1019 { 1020 beginRemoveRows( parentIndex, i, i ); 1021 parent->removeChild( i ); 1022 endRemoveRows(); 1023 } 1024 else 1025 { 1026 // the remaining child items may be dirty, so refresh them 1027 if( child->isDataItem() && child->data() && m_expandedItems.contains( child->data() ) ) 1028 ensureChildrenLoaded( child ); 1029 1030 // tell the view that the existing children may have changed 1031 QModelIndex idx = index( i, 0, parentIndex ); 1032 Q_EMIT dataChanged( idx, idx ); 1033 } 1034 } 1035 1036 // add the new rows 1037 if( !dataToBeAdded.isEmpty() ) 1038 { 1039 int lastRow = parent->childCount() - 1; 1040 //the above check ensures that Qt does not crash on beginInsertRows ( because lastRow+1 > lastRow+0) 1041 beginInsertRows( parentIndex, lastRow + 1, lastRow + dataToBeAdded.count() ); 1042 foreach( Meta::DataPtr data, dataToBeAdded ) 1043 { 1044 new CollectionTreeItem( data, parent, this ); 1045 } 1046 endInsertRows(); 1047 } 1048 1049 parent->setRequiresUpdate( false ); 1050 } 1051 1052 void 1053 CollectionTreeItemModelBase::updateHeaderText() 1054 { 1055 m_headerText.clear(); 1056 for( int i=0; i< m_levelType.count(); ++i ) 1057 m_headerText += nameForLevel( i ) + " / "; 1058 1059 m_headerText.chop( 3 ); 1060 } 1061 1062 QIcon 1063 CollectionTreeItemModelBase::iconForCategory( CategoryId::CatMenuId category ) 1064 { 1065 switch( category ) 1066 { 1067 case CategoryId::Album : 1068 return QIcon::fromTheme( QStringLiteral("media-optical-amarok") ); 1069 case CategoryId::Artist : 1070 return QIcon::fromTheme( QStringLiteral("view-media-artist-amarok") ); 1071 case CategoryId::AlbumArtist : 1072 return QIcon::fromTheme( QStringLiteral("view-media-artist-amarok") ); 1073 case CategoryId::Composer : 1074 return QIcon::fromTheme( QStringLiteral("filename-composer-amarok") ); 1075 case CategoryId::Genre : 1076 return QIcon::fromTheme( QStringLiteral("favorite-genres-amarok") ); 1077 case CategoryId::Year : 1078 return QIcon::fromTheme( QStringLiteral("clock") ); 1079 case CategoryId::Label : 1080 return QIcon::fromTheme( QStringLiteral("label-amarok") ); 1081 case CategoryId::None: 1082 default: 1083 return QIcon::fromTheme( QStringLiteral("image-missing") ); 1084 } 1085 1086 } 1087 1088 QIcon 1089 CollectionTreeItemModelBase::iconForLevel( int level ) const 1090 { 1091 return iconForCategory( m_levelType.value( level ) ); 1092 } 1093 1094 QString 1095 CollectionTreeItemModelBase::nameForCategory( CategoryId::CatMenuId category, bool showYears ) 1096 { 1097 switch( category ) 1098 { 1099 case CategoryId::Album: 1100 return showYears ? i18n( "Year - Album" ) : i18n( "Album" ); 1101 case CategoryId::Artist: 1102 return i18n( "Track Artist" ); 1103 case CategoryId::AlbumArtist: 1104 return i18n( "Album Artist" ); 1105 case CategoryId::Composer: 1106 return i18n( "Composer" ); 1107 case CategoryId::Genre: 1108 return i18n( "Genre" ); 1109 case CategoryId::Year: 1110 return i18n( "Year" ); 1111 case CategoryId::Label: 1112 return i18n( "Label" ); 1113 case CategoryId::None: 1114 return i18n( "None" ); 1115 default: 1116 return QString(); 1117 } 1118 } 1119 1120 QString 1121 CollectionTreeItemModelBase::nameForLevel( int level ) const 1122 { 1123 return nameForCategory( m_levelType.value( level ), AmarokConfig::showYears() ); 1124 } 1125 1126 void 1127 CollectionTreeItemModelBase::handleCompilations( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const 1128 { 1129 // this method will be called when we retrieve a list of artists from the database. 1130 // we have to query for all compilations, and then add a "Various Artists" node if at least 1131 // one compilation exists 1132 Collections::QueryMaker *qm = parent->queryMaker(); 1133 qm->setQueryType( queryType ); 1134 qm->setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations ); 1135 for( CollectionTreeItem *tmp = parent; tmp; tmp = tmp->parent() ) 1136 tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); 1137 1138 Collections::addTextualFilter( qm, m_currentFilter ); 1139 addQueryMaker( parent, qm ); 1140 m_compilationQueries.insert( qm, parent ); 1141 qm->run(); 1142 } 1143 1144 void 1145 CollectionTreeItemModelBase::handleTracksWithoutLabels( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const 1146 { 1147 Collections::QueryMaker *qm = parent->queryMaker(); 1148 qm->setQueryType( queryType ); 1149 qm->setLabelQueryMode( Collections::QueryMaker::OnlyWithoutLabels ); 1150 for( CollectionTreeItem *tmp = parent; tmp; tmp = tmp->parent() ) 1151 tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); 1152 1153 Collections::addTextualFilter( qm, m_currentFilter ); 1154 addQueryMaker( parent, qm ); 1155 m_noLabelsQueries.insert( qm, parent ); 1156 qm->run(); 1157 } 1158 1159 1160 void CollectionTreeItemModelBase::startAnimationTick() 1161 { 1162 //start animation 1163 if( ( m_timeLine->state() != QTimeLine::Running ) && !m_runningQueries.isEmpty() ) 1164 m_timeLine->start(); 1165 } 1166 1167 void CollectionTreeItemModelBase::loadingAnimationTick() 1168 { 1169 if ( m_animFrame == 0 ) 1170 m_currentAnimPixmap = m_loading2; 1171 else 1172 m_currentAnimPixmap = m_loading1; 1173 1174 m_animFrame = 1 - m_animFrame; 1175 1176 //trigger an update of all items being populated at the moment; 1177 1178 QList< CollectionTreeItem * > items = m_runningQueries.uniqueKeys(); 1179 foreach ( CollectionTreeItem* item, items ) 1180 { 1181 if( item == m_rootItem ) 1182 continue; 1183 Q_EMIT dataChanged( itemIndex( item ), itemIndex( item ) ); 1184 } 1185 } 1186 1187 QString 1188 CollectionTreeItemModelBase::currentFilter() const 1189 { 1190 return m_currentFilter; 1191 } 1192 1193 void 1194 CollectionTreeItemModelBase::setCurrentFilter( const QString &filter ) 1195 { 1196 m_currentFilter = filter; 1197 slotFilter( /* autoExpand */ true ); 1198 } 1199 1200 void 1201 CollectionTreeItemModelBase::slotFilter( bool autoExpand ) 1202 { 1203 m_autoExpand = autoExpand; 1204 filterChildren(); 1205 1206 // following is not auto-expansion, it is restoring the state before filtering 1207 foreach( Collections::Collection *expanded, m_expandedCollections ) 1208 { 1209 CollectionTreeItem *expandedItem = m_collections.value( expanded->collectionId() ).second; 1210 if( expandedItem ) 1211 Q_EMIT expandIndex( itemIndex( expandedItem ) ); 1212 } 1213 } 1214 1215 void 1216 CollectionTreeItemModelBase::slotCollapsed( const QModelIndex &index ) 1217 { 1218 if ( index.isValid() ) //probably unnecessary, but let's be safe 1219 { 1220 CollectionTreeItem *item = static_cast<CollectionTreeItem*>( index.internalPointer() ); 1221 1222 switch( item->type() ) 1223 { 1224 case CollectionTreeItem::Root: 1225 break; // nothing to do 1226 1227 case CollectionTreeItem::Collection: 1228 m_expandedCollections.remove( item->parentCollection() ); 1229 break; 1230 1231 case CollectionTreeItem::VariousArtist: 1232 case CollectionTreeItem::NoLabel: 1233 m_expandedSpecialNodes.remove( item->parentCollection() ); 1234 break; 1235 case CollectionTreeItem::Data: 1236 m_expandedItems.remove( item->data() ); 1237 break; 1238 } 1239 } 1240 } 1241 1242 void 1243 CollectionTreeItemModelBase::slotExpanded( const QModelIndex &index ) 1244 { 1245 if( !index.isValid() ) 1246 return; 1247 1248 CollectionTreeItem *item = static_cast<CollectionTreeItem*>( index.internalPointer() ); 1249 // we are really only interested in the special nodes here. 1250 // we have to remember whether the user expanded a various artists/no labels node or not. 1251 // otherwise we won't be able to automatically expand the special node after filtering again 1252 // there is exactly one special node per type per collection, so use the collection to store that information 1253 1254 // we also need to store collection expansion state here as they are no longer 1255 // added to th expanded set in handleNormalQueryResult() 1256 switch( item->type() ) 1257 { 1258 case CollectionTreeItem::VariousArtist: 1259 case CollectionTreeItem::NoLabel: 1260 m_expandedSpecialNodes.insert( item->parentCollection() ); 1261 break; 1262 case CollectionTreeItem::Collection: 1263 m_expandedCollections.insert( item->parentCollection() ); 1264 default: 1265 break; 1266 } 1267 } 1268 1269 void CollectionTreeItemModelBase::markSubTreeAsDirty( CollectionTreeItem *item ) 1270 { 1271 //tracks are the leaves so they are never dirty 1272 if( !item->isTrackItem() ) 1273 item->setRequiresUpdate( true ); 1274 for( int i = 0; i < item->childCount(); i++ ) 1275 { 1276 markSubTreeAsDirty( item->child( i ) ); 1277 } 1278 } 1279 1280 void CollectionTreeItemModelBase::itemAboutToBeDeleted( CollectionTreeItem *item ) 1281 { 1282 // also all the children will be deleted 1283 foreach( CollectionTreeItem *child, item->children() ) 1284 itemAboutToBeDeleted( child ); 1285 1286 if( !m_runningQueries.contains( item ) ) 1287 return; 1288 // TODO: replace this hack with QWeakPointer now than we depend on Qt >= 4.8 1289 foreach(Collections::QueryMaker *qm, m_runningQueries.values( item )) 1290 { 1291 m_childQueries.remove( qm ); 1292 m_compilationQueries.remove( qm ); 1293 m_noLabelsQueries.remove( qm ); 1294 m_runningQueries.remove(item, qm); 1295 1296 //Disconnect all signals from the QueryMaker so we do not get notified about the results 1297 qm->disconnect(); 1298 qm->abortQuery(); 1299 //Nuke it 1300 qm->deleteLater(); 1301 } 1302 } 1303 1304 void 1305 CollectionTreeItemModelBase::setDragSourceCollections( const QSet<Collections::Collection*> &collections ) 1306 { 1307 m_dragSourceCollections = collections; 1308 } 1309 1310 bool 1311 CollectionTreeItemModelBase::hasRunningQueries() const 1312 { 1313 return !m_runningQueries.isEmpty(); 1314 } 1315 1316 CategoryId::CatMenuId 1317 CollectionTreeItemModelBase::levelCategory( const int level ) const 1318 { 1319 const int actualLevel = level + levelModifier(); 1320 if( actualLevel >= 0 && actualLevel < m_levelType.count() ) 1321 return m_levelType.at( actualLevel ); 1322 1323 return CategoryId::None; 1324 } 1325