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 }