File indexing completed on 2025-01-05 04:25:40
0001 /* 0002 * Copyright (C) 2017 Malte Veerman <malte.veerman@gmail.com> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License along 0015 * with this program; if not, write to the Free Software Foundation, Inc., 0016 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0017 */ 0018 0019 #include "AlbumsEngine.h" 0020 #include "AlbumsDefs.h" 0021 #include "AlbumItem.h" 0022 #include "TrackItem.h" 0023 0024 #include <capabilities/ActionsCapability.h> 0025 #include <collections/QueryMaker.h> 0026 #include <core/support/Amarok.h> 0027 #include <core-impl/collections/support/CollectionManager.h> 0028 #include <Debug.h> 0029 #include <dialogs/TagDialog.h> 0030 #include <EngineController.h> 0031 #include <playlist/PlaylistController.h> 0032 0033 #include <QMenu> 0034 0035 #include <KLocalizedString> 0036 0037 #include <algorithm> 0038 0039 AlbumsEngine::AlbumsEngine( QObject *parent ) 0040 : QObject( parent ) 0041 , m_lastQueryMaker( nullptr ) 0042 , m_model( new AlbumsModel( this ) ) 0043 , m_proxyModel( new AlbumsProxyModel( this ) ) 0044 { 0045 EngineController* engine = The::engineController(); 0046 0047 connect( engine, &EngineController::trackPlaying, 0048 this, &AlbumsEngine::slotTrackChanged ); 0049 connect( engine, &EngineController::stopped, 0050 this, &AlbumsEngine::stopped ); 0051 connect( engine, &EngineController::trackMetadataChanged, 0052 this, &AlbumsEngine::slotTrackMetadataChanged ); 0053 0054 m_model->setColumnCount( 1 ); 0055 m_proxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive ); 0056 m_proxyModel->setSortLocaleAware( true ); 0057 m_proxyModel->setDynamicSortFilter( true ); 0058 m_proxyModel->setSourceModel( m_model ); 0059 m_proxyModel->setFilterRole( NameRole ); 0060 } 0061 0062 void AlbumsEngine::slotTrackMetadataChanged( Meta::TrackPtr track ) 0063 { 0064 if( !track || !track->album() || !track->album()->albumArtist() ) 0065 return; 0066 0067 if( track->album()->albumArtist() == m_artist ) 0068 return; 0069 0070 m_artist = track->album()->albumArtist(); 0071 update(); 0072 } 0073 0074 void AlbumsEngine::slotTrackChanged( const Meta::TrackPtr &track ) 0075 { 0076 if( !track || track == m_currentTrack ) 0077 return; 0078 0079 m_currentTrack = track; 0080 slotTrackMetadataChanged( track ); 0081 } 0082 0083 0084 void AlbumsEngine::stopped() 0085 { 0086 m_currentTrack.clear(); 0087 m_artist.clear(); 0088 0089 // Collect data for the recently added albums 0090 Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker(); 0091 qm->setAutoDelete( true ); 0092 qm->setQueryType( Collections::QueryMaker::Album ); 0093 qm->excludeFilter( Meta::valAlbum, QString(), true, true ); 0094 qm->orderBy( Meta::valCreateDate, true ); 0095 qm->limitMaxResultSize( Amarok::config("Albums Applet").readEntry("RecentlyAdded", 5) ); 0096 0097 connect( qm, &Collections::QueryMaker::newAlbumsReady, 0098 this, &AlbumsEngine::resultReady, Qt::QueuedConnection ); 0099 0100 m_lastQueryMaker = qm; 0101 qm->run(); 0102 } 0103 0104 void AlbumsEngine::update() 0105 { 0106 DEBUG_BLOCK 0107 0108 // -- search the collection for albums with the same artist 0109 Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker(); 0110 qm->setAutoDelete( true ); 0111 qm->addFilter( Meta::valArtist, m_artist->name(), true, true ); 0112 qm->setAlbumQueryMode( Collections::QueryMaker::AllAlbums ); 0113 qm->setQueryType( Collections::QueryMaker::Album ); 0114 0115 connect( qm, &Collections::QueryMaker::newAlbumsReady, 0116 this, &AlbumsEngine::resultReady, Qt::QueuedConnection ); 0117 0118 m_lastQueryMaker = qm; 0119 qm->run(); 0120 } 0121 0122 void AlbumsEngine::resultReady( const Meta::AlbumList &albums ) 0123 { 0124 if( sender() != m_lastQueryMaker ) 0125 return; 0126 0127 m_model->clear(); 0128 m_proxyModel->setMode( m_currentTrack ? AlbumsProxyModel::SortByYear : AlbumsProxyModel::SortByCreateDate ); 0129 0130 for( auto album : albums ) 0131 { 0132 // do not show all tracks without an album from the collection, this takes ages 0133 // TODO: show all tracks from this artist that are not part of an album 0134 if( album->name().isEmpty() ) 0135 continue; 0136 0137 Meta::TrackList tracks = album->tracks(); 0138 if( tracks.isEmpty() ) 0139 continue; 0140 0141 AlbumItem *albumItem = new AlbumItem(); 0142 albumItem->setIconSize( 50 ); 0143 albumItem->setAlbum( album ); 0144 albumItem->setShowArtist( !m_currentTrack ); 0145 0146 int numberOfDiscs = 0; 0147 int childRow = 0; 0148 0149 std::stable_sort( tracks.begin(), tracks.end(), Meta::Track::lessThan ); 0150 0151 QMultiHash< int, TrackItem* > trackItems; // hash of tracks items for each disc 0152 for( const auto &track : tracks ) 0153 { 0154 if( numberOfDiscs < track->discNumber() ) 0155 numberOfDiscs = track->discNumber(); 0156 0157 TrackItem *trackItem = new TrackItem(); 0158 trackItem->setTrack( track ); 0159 0160 // bold the current track to make it more visible 0161 if( m_currentTrack && m_currentTrack == track ) 0162 { 0163 trackItem->bold(); 0164 } 0165 0166 // If compilation and same artist, then highlight, but only if there's a current track 0167 if( m_currentTrack 0168 && m_currentTrack->artist() && track->artist() 0169 && album->isCompilation() ) 0170 { 0171 trackItem->italicise(); 0172 } 0173 trackItems.insert( track->discNumber(), trackItem ); 0174 } 0175 0176 for( int i = 0; i <= numberOfDiscs; ++i ) 0177 { 0178 QList<TrackItem*> items = trackItems.values( i ); 0179 if( !items.isEmpty() ) 0180 { 0181 const TrackItem *item = items.first(); 0182 QStandardItem *discItem( nullptr ); 0183 if( numberOfDiscs > 1 ) 0184 { 0185 discItem = new QStandardItem( i18n("Disc %1", item->track()->discNumber()) ); 0186 albumItem->setChild( childRow++, discItem ); 0187 int discChildRow = 0; 0188 foreach( TrackItem *trackItem, items ) 0189 discItem->setChild( discChildRow++, trackItem ); 0190 } 0191 else 0192 { 0193 foreach( TrackItem *trackItem, items ) 0194 albumItem->setChild( childRow++, trackItem ); 0195 } 0196 } 0197 } 0198 m_model->appendRow( albumItem ); 0199 } 0200 0201 m_proxyModel->sort( 0 ); 0202 } 0203 0204 QString AlbumsEngine::filterPattern() const 0205 { 0206 return m_proxyModel->filterRegExp().pattern(); 0207 } 0208 0209 void AlbumsEngine::setFilterPattern( const QString &pattern ) 0210 { 0211 if( m_proxyModel->filterRegExp().pattern() == pattern ) 0212 return; 0213 0214 m_proxyModel->setFilterRegExp( QRegExp(pattern, Qt::CaseInsensitive) ); 0215 Q_EMIT filterPatternChanged(); 0216 } 0217 0218 void AlbumsEngine::clear() 0219 { 0220 qDeleteAll( m_model->findItems( QLatin1String( "*" ), Qt::MatchWildcard ) ); 0221 m_model->clear(); 0222 } 0223 0224 void AlbumsEngine::appendSelected( const QModelIndexList& indexes ) const 0225 { 0226 Meta::TrackList selected = getSelectedTracks( indexes ); 0227 The::playlistController()->insertOptioned( selected, Playlist::OnAppendToPlaylistAction ); 0228 } 0229 0230 void AlbumsEngine::replaceWithSelected( const QModelIndexList& indexes ) const 0231 { 0232 Meta::TrackList selected = getSelectedTracks( indexes ); 0233 The::playlistController()->insertOptioned( selected, Playlist::OnReplacePlaylistAction ); 0234 } 0235 0236 void AlbumsEngine::queueSelected( const QModelIndexList& indexes ) const 0237 { 0238 Meta::TrackList selected = getSelectedTracks( indexes ); 0239 The::playlistController()->insertOptioned( selected, Playlist::OnQueueToPlaylistAction ); 0240 } 0241 0242 void AlbumsEngine::editSelected( const QModelIndexList& indexes ) const 0243 { 0244 Meta::TrackList selected = getSelectedTracks( indexes ); 0245 if( !selected.isEmpty() ) 0246 { 0247 TagDialog *dialog = new TagDialog( selected ); 0248 dialog->show(); 0249 } 0250 } 0251 0252 void AlbumsEngine::showContextMenu( const QModelIndexList &indexes, const QModelIndex &mouseOverIndex ) const 0253 { 0254 if( indexes.isEmpty() || !mouseOverIndex.isValid() ) 0255 return; 0256 0257 QMenu menu; 0258 QAction *appendAction = new QAction( QIcon::fromTheme( "media-track-add-amarok" ), i18n( "&Add to Playlist" ), &menu ); 0259 QAction *loadAction = new QAction( QIcon::fromTheme( "folder-open" ), i18nc( "Replace the currently loaded tracks with these", "&Replace Playlist" ), &menu ); 0260 QAction *queueAction = new QAction( QIcon::fromTheme( "media-track-queue-amarok" ), i18n( "&Queue" ), &menu ); 0261 QAction *editAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ), i18n( "Edit Track Details" ), &menu ); 0262 0263 menu.addAction( appendAction ); 0264 menu.addAction( loadAction ); 0265 menu.addAction( queueAction ); 0266 menu.addAction( editAction ); 0267 0268 connect( appendAction, &QAction::triggered, this, [this, indexes] () { appendSelected( indexes ); } ); 0269 connect( loadAction, &QAction::triggered, this, [this, indexes] () { replaceWithSelected( indexes ); } ); 0270 connect( queueAction, &QAction::triggered, this, [this, indexes] () { queueSelected( indexes ); } ); 0271 connect( editAction, &QAction::triggered, this, [this, indexes] () { editSelected( indexes ); } ); 0272 0273 QMenu menuCover( i18n( "Album" ), &menu ); 0274 const QStandardItem *item = m_model->itemFromIndex( m_proxyModel->mapToSource( mouseOverIndex ) ); 0275 if( item->type() == AlbumType ) 0276 { 0277 Meta::AlbumPtr album = static_cast<const AlbumItem*>( item )->album(); 0278 QScopedPointer<Capabilities::ActionsCapability> ac( album->create<Capabilities::ActionsCapability>() ); 0279 if( ac ) 0280 { 0281 QList<QAction *> actions = ac->actions(); 0282 if( !actions.isEmpty() ) 0283 { 0284 // ensure that the actions are cleaned up afterwards 0285 foreach( QAction *action, actions ) 0286 { 0287 if( !action->parent() ) 0288 action->setParent( &menuCover ); 0289 } 0290 0291 menuCover.addActions( actions ); 0292 menuCover.setIcon( QIcon::fromTheme( "filename-album-amarok" ) ); 0293 menu.addMenu( &menuCover ); 0294 } 0295 } 0296 } 0297 menu.exec( QCursor::pos() ); 0298 } 0299 0300 QString AlbumsEngine::getSelectedUrlList(const QModelIndexList &indexes) const 0301 { 0302 const Meta::TrackList list=getSelectedTracks(indexes); 0303 QString urlList; 0304 for(const Meta::TrackPtr &t : list) 0305 { 0306 urlList+=t->playableUrl().toString()+"\n"; 0307 } 0308 return urlList; 0309 } 0310 0311 Meta::TrackList AlbumsEngine::getSelectedTracks( const QModelIndexList& indexes ) const 0312 { 0313 Meta::TrackList selected; 0314 0315 for( const QModelIndex &index : indexes ) 0316 { 0317 if( index.isValid() ) 0318 { 0319 const QModelIndex &srcIndex = m_proxyModel->mapToSource( index ); 0320 const QStandardItem *item = m_model->itemFromIndex( srcIndex ); 0321 if( item->type() == AlbumType ) 0322 { 0323 selected << static_cast<const AlbumItem*>( item )->album()->tracks(); 0324 } 0325 else if( item->type() == TrackType ) 0326 { 0327 selected << static_cast<const TrackItem*>( item )->track(); 0328 } 0329 else if( m_model->hasChildren( srcIndex ) ) // disc type 0330 { 0331 for( int i = m_model->rowCount( srcIndex ) - 1; i >= 0; --i ) 0332 { 0333 const QStandardItem *trackItem = m_model->itemFromIndex( m_model->index(i, 0, srcIndex) ); 0334 selected << static_cast<const TrackItem*>( trackItem )->track(); 0335 } 0336 } 0337 } 0338 } 0339 0340 return selected; 0341 }