2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine .
* Copyright 2010 , David Sansome < me @ davidsansome . com >
2021-03-20 21:14:47 +01:00
* Copyright 2018 - 2021 , Jonas Kvinge < jonas @ jkvinge . net >
2018-02-27 18:06:05 +01:00
*
* Strawberry is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* Strawberry is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with Strawberry . If not , see < http : //www.gnu.org/licenses/>.
2018-08-09 18:39:44 +02:00
*
2018-02-27 18:06:05 +01:00
*/
# include "config.h"
2020-07-19 22:43:58 +02:00
# include <functional>
2021-08-22 23:26:53 +02:00
# include <utility>
2018-05-01 00:41:33 +02:00
# include <QtGlobal>
# include <QObject>
# include <QDialog>
2020-07-18 16:28:39 +02:00
# include <QtConcurrent>
2018-05-01 00:41:33 +02:00
# include <QFuture>
2021-01-30 21:53:53 +01:00
# include <QFutureWatcher>
2018-05-01 00:41:33 +02:00
# include <QDir>
2018-02-27 18:06:05 +01:00
# include <QFileDialog>
# include <QFileInfo>
2018-05-01 00:41:33 +02:00
# include <QList>
# include <QSet>
# include <QString>
2023-03-18 20:03:07 +01:00
# include <QRegularExpression>
2018-05-01 00:41:33 +02:00
# include <QUrl>
# include <QAbstractItemModel>
2020-08-09 14:00:56 +02:00
# include <QScrollBar>
2018-05-01 00:41:33 +02:00
# include <QSettings>
2022-05-13 23:14:56 +02:00
# include <QMessageBox>
2018-02-27 18:06:05 +01:00
# include "core/application.h"
# include "core/player.h"
2023-03-18 20:03:07 +01:00
# include "utilities/filenameconstants.h"
2022-12-28 03:12:00 +01:00
# include "utilities/timeutils.h"
2018-02-27 18:06:05 +01:00
# include "collection/collectionbackend.h"
2020-04-20 18:03:18 +02:00
# include "covermanager/currentalbumcoverloader.h"
2022-05-13 23:14:56 +02:00
# include "settings/playlistsettingspage.h"
2018-05-01 00:41:33 +02:00
# include "playlist.h"
# include "playlistbackend.h"
# include "playlistcontainer.h"
# include "playlistmanager.h"
# include "playlistitem.h"
# include "playlistview.h"
# include "playlistsaveoptionsdialog.h"
2018-02-27 18:06:05 +01:00
# include "playlistparsers/playlistparser.h"
2022-07-20 01:09:00 +02:00
# include "dialogs/saveplaylistsdialog.h"
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
class ParserBase ;
2018-02-27 18:06:05 +01:00
PlaylistManager : : PlaylistManager ( Application * app , QObject * parent )
: PlaylistManagerInterface ( app , parent ) ,
app_ ( app ) ,
playlist_backend_ ( nullptr ) ,
collection_backend_ ( nullptr ) ,
sequence_ ( nullptr ) ,
parser_ ( nullptr ) ,
playlist_container_ ( nullptr ) ,
current_ ( - 1 ) ,
2019-05-02 11:31:31 +02:00
active_ ( - 1 ) ,
2021-01-26 16:48:04 +01:00
playlists_loading_ ( 0 ) {
QObject : : connect ( app_ - > player ( ) , & Player : : Paused , this , & PlaylistManager : : SetActivePaused ) ;
QObject : : connect ( app_ - > player ( ) , & Player : : Playing , this , & PlaylistManager : : SetActivePlaying ) ;
QObject : : connect ( app_ - > player ( ) , & Player : : Stopped , this , & PlaylistManager : : SetActiveStopped ) ;
2018-02-27 18:06:05 +01:00
}
PlaylistManager : : ~ PlaylistManager ( ) {
2021-03-21 04:47:11 +01:00
QList < Data > datas = playlists_ . values ( ) ;
for ( const Data & data : datas ) delete data . p ;
2018-02-27 18:06:05 +01:00
}
void PlaylistManager : : Init ( CollectionBackend * collection_backend , PlaylistBackend * playlist_backend , PlaylistSequence * sequence , PlaylistContainer * playlist_container ) {
collection_backend_ = collection_backend ;
playlist_backend_ = playlist_backend ;
sequence_ = sequence ;
parser_ = new PlaylistParser ( collection_backend , this ) ;
playlist_container_ = playlist_container ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( collection_backend_ , & CollectionBackend : : SongsDiscovered , this , & PlaylistManager : : SongsDiscovered ) ;
QObject : : connect ( collection_backend_ , & CollectionBackend : : SongsStatisticsChanged , this , & PlaylistManager : : SongsDiscovered ) ;
QObject : : connect ( collection_backend_ , & CollectionBackend : : SongsRatingChanged , this , & PlaylistManager : : SongsDiscovered ) ;
2018-02-27 18:06:05 +01:00
for ( const PlaylistBackend : : Playlist & p : playlist_backend - > GetAllOpenPlaylists ( ) ) {
2020-08-23 19:17:50 +02:00
+ + playlists_loading_ ;
2019-05-02 11:31:31 +02:00
Playlist * ret = AddPlaylist ( p . id , p . name , p . special_type , p . ui_path , p . favorite ) ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( ret , & Playlist : : PlaylistLoaded , this , & PlaylistManager : : PlaylistLoaded ) ;
2018-02-27 18:06:05 +01:00
}
// If no playlist exists then make a new one
if ( playlists_ . isEmpty ( ) ) New ( tr ( " Playlist " ) ) ;
emit PlaylistManagerInitialized ( ) ;
}
2019-05-02 11:31:31 +02:00
void PlaylistManager : : PlaylistLoaded ( ) {
Playlist * playlist = qobject_cast < Playlist * > ( sender ( ) ) ;
if ( ! playlist ) return ;
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( playlist , & Playlist : : PlaylistLoaded , this , & PlaylistManager : : PlaylistLoaded ) ;
2020-08-23 19:17:50 +02:00
- - playlists_loading_ ;
2020-11-20 21:48:10 +01:00
if ( playlists_loading_ = = 0 ) {
emit AllPlaylistsLoaded ( ) ;
}
2019-05-02 11:31:31 +02:00
}
2018-02-27 18:06:05 +01:00
QList < Playlist * > PlaylistManager : : GetAllPlaylists ( ) const {
QList < Playlist * > result ;
2021-03-21 04:47:11 +01:00
QList < Data > datas = playlists_ . values ( ) ;
2021-06-20 19:04:08 +02:00
result . reserve ( datas . count ( ) ) ;
2021-03-21 04:47:11 +01:00
for ( const Data & data : datas ) {
2018-02-27 18:06:05 +01:00
result . append ( data . p ) ;
}
return result ;
}
2020-09-10 22:05:12 +02:00
QItemSelection PlaylistManager : : selection ( const int id ) const {
2018-02-27 18:06:05 +01:00
QMap < int , Data > : : const_iterator it = playlists_ . find ( id ) ;
return it - > selection ;
}
2020-08-23 19:17:50 +02:00
Playlist * PlaylistManager : : AddPlaylist ( const int id , const QString & name , const QString & special_type , const QString & ui_path , const bool favorite ) {
2018-02-27 18:06:05 +01:00
Playlist * ret = new Playlist ( playlist_backend_ , app_ - > task_manager ( ) , collection_backend_ , id , special_type , favorite ) ;
ret - > set_sequence ( sequence_ ) ;
ret - > set_ui_path ( ui_path ) ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( ret , & Playlist : : CurrentSongChanged , this , & PlaylistManager : : CurrentSongChanged ) ;
QObject : : connect ( ret , & Playlist : : SongMetadataChanged , this , & PlaylistManager : : SongMetadataChanged ) ;
QObject : : connect ( ret , & Playlist : : PlaylistChanged , this , & PlaylistManager : : OneOfPlaylistsChanged ) ;
QObject : : connect ( ret , & Playlist : : PlaylistChanged , this , & PlaylistManager : : UpdateSummaryText ) ;
QObject : : connect ( ret , & Playlist : : EditingFinished , this , & PlaylistManager : : EditingFinished ) ;
QObject : : connect ( ret , & Playlist : : Error , this , & PlaylistManager : : Error ) ;
QObject : : connect ( ret , & Playlist : : PlayRequested , this , & PlaylistManager : : PlayRequested ) ;
QObject : : connect ( playlist_container_ - > view ( ) , & PlaylistView : : ColumnAlignmentChanged , ret , & Playlist : : SetColumnAlignment ) ;
QObject : : connect ( app_ - > current_albumcover_loader ( ) , & CurrentAlbumCoverLoader : : AlbumCoverLoaded , ret , & Playlist : : AlbumCoverLoaded ) ;
2018-02-27 18:06:05 +01:00
playlists_ [ id ] = Data ( ret , name ) ;
emit PlaylistAdded ( id , name , favorite ) ;
if ( current_ = = - 1 ) {
SetCurrentPlaylist ( id ) ;
}
if ( active_ = = - 1 ) {
SetActivePlaylist ( id ) ;
}
return ret ;
}
void PlaylistManager : : New ( const QString & name , const SongList & songs , const QString & special_type ) {
2018-10-02 00:38:52 +02:00
2018-02-27 18:06:05 +01:00
if ( name . isNull ( ) ) return ;
int id = playlist_backend_ - > CreatePlaylist ( name , special_type ) ;
if ( id = = - 1 ) qFatal ( " Couldn't create playlist " ) ;
Playlist * playlist = AddPlaylist ( id , name , special_type , QString ( ) , false ) ;
playlist - > InsertSongsOrCollectionItems ( songs ) ;
SetCurrentPlaylist ( id ) ;
// If the name is just "Playlist", append the id
if ( name = = tr ( " Playlist " ) ) {
Rename ( id , QString ( " %1 %2 " ) . arg ( name ) . arg ( id ) ) ;
}
}
void PlaylistManager : : Load ( const QString & filename ) {
2018-10-02 00:38:52 +02:00
2022-05-13 23:14:56 +02:00
QFileInfo fileinfo ( filename ) ;
2018-02-27 18:06:05 +01:00
2022-05-13 23:14:56 +02:00
int id = playlist_backend_ - > CreatePlaylist ( fileinfo . completeBaseName ( ) , QString ( ) ) ;
2018-02-27 18:06:05 +01:00
if ( id = = - 1 ) {
emit Error ( tr ( " Couldn't create playlist " ) ) ;
return ;
}
2022-05-13 23:14:56 +02:00
Playlist * playlist = AddPlaylist ( id , fileinfo . completeBaseName ( ) , QString ( ) , QString ( ) , false ) ;
2018-02-27 18:06:05 +01:00
2021-02-06 00:32:01 +01:00
playlist - > InsertUrls ( QList < QUrl > ( ) < < QUrl : : fromLocalFile ( filename ) ) ;
2018-02-27 18:06:05 +01:00
}
2022-05-13 23:14:56 +02:00
void PlaylistManager : : Save ( const int id , const QString & filename , const PlaylistSettingsPage : : PathType path_type ) {
2018-02-27 18:06:05 +01:00
if ( playlists_ . contains ( id ) ) {
parser_ - > Save ( playlist ( id ) - > GetAllSongs ( ) , filename , path_type ) ;
}
else {
2022-08-28 02:44:37 +02:00
// Playlist is not in the playlist manager: probably save action was triggered from the left sidebar and the playlist isn't loaded.
2020-11-14 02:13:22 +01:00
# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
2021-01-30 21:53:53 +01:00
QFuture < SongList > future = QtConcurrent : : run ( & PlaylistBackend : : GetPlaylistSongs , playlist_backend_ , id ) ;
2020-11-14 02:13:22 +01:00
# else
2021-01-30 21:53:53 +01:00
QFuture < SongList > future = QtConcurrent : : run ( playlist_backend_ , & PlaylistBackend : : GetPlaylistSongs , id ) ;
2020-11-14 02:13:22 +01:00
# endif
2021-01-30 21:53:53 +01:00
QFutureWatcher < SongList > * watcher = new QFutureWatcher < SongList > ( ) ;
QObject : : connect ( watcher , & QFutureWatcher < SongList > : : finished , this , [ this , watcher , filename , path_type ] ( ) {
ItemsLoadedForSavePlaylist ( watcher - > result ( ) , filename , path_type ) ;
watcher - > deleteLater ( ) ;
} ) ;
2021-06-16 00:30:21 +02:00
watcher - > setFuture ( future ) ;
2018-02-27 18:06:05 +01:00
}
}
2022-05-13 23:14:56 +02:00
void PlaylistManager : : ItemsLoadedForSavePlaylist ( const SongList & songs , const QString & filename , const PlaylistSettingsPage : : PathType path_type ) {
2020-07-19 19:07:12 +02:00
2021-01-30 21:53:53 +01:00
parser_ - > Save ( songs , filename , path_type ) ;
2020-07-19 19:07:12 +02:00
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : SaveWithUI ( const int id , const QString & playlist_name ) {
2018-02-27 18:06:05 +01:00
2022-05-13 23:14:56 +02:00
QSettings s ;
s . beginGroup ( Playlist : : kSettingsGroup ) ;
QString last_save_filter = s . value ( " last_save_filter " , parser ( ) - > default_filter ( ) ) . toString ( ) ;
QString last_save_path = s . value ( " last_save_path " , QDir : : homePath ( ) ) . toString ( ) ;
QString last_save_extension = s . value ( " last_save_extension " , parser ( ) - > default_extension ( ) ) . toString ( ) ;
s . endGroup ( ) ;
2018-02-27 18:06:05 +01:00
QString suggested_filename = playlist_name ;
2023-03-18 20:03:07 +01:00
QString filename = last_save_path + " / " + suggested_filename . remove ( QRegularExpression ( QString ( kProblematicCharactersRegex ) , QRegularExpression : : CaseInsensitiveOption ) ) + " . " + last_save_extension ;
2018-02-27 18:06:05 +01:00
2022-05-13 23:14:56 +02:00
QFileInfo fileinfo ;
2018-02-27 18:06:05 +01:00
forever {
2023-02-18 14:09:27 +01:00
filename = QFileDialog : : getSaveFileName ( nullptr , tr ( " Save playlist " , " Title of the playlist save dialog. " ) , filename , parser ( ) - > filters ( PlaylistParser : : Type : : Save ) , & last_save_filter ) ;
2022-05-13 23:14:56 +02:00
if ( filename . isEmpty ( ) ) return ;
fileinfo . setFile ( filename ) ;
2023-02-18 14:09:27 +01:00
ParserBase * parser = parser_ - > ParserForExtension ( PlaylistParser : : Type : : Save , fileinfo . suffix ( ) ) ;
2022-05-13 23:14:56 +02:00
if ( parser ) break ;
QMessageBox : : warning ( nullptr , tr ( " Unknown playlist extension " ) , tr ( " Unknown file extension for playlist. " ) ) ;
2018-02-27 18:06:05 +01:00
}
2022-05-13 23:14:56 +02:00
s . beginGroup ( PlaylistSettingsPage : : kSettingsGroup ) ;
2023-02-18 14:09:27 +01:00
PlaylistSettingsPage : : PathType path_type = static_cast < PlaylistSettingsPage : : PathType > ( s . value ( " path_type " , static_cast < int > ( PlaylistSettingsPage : : PathType : : Automatic ) ) . toInt ( ) ) ;
2022-05-13 23:14:56 +02:00
s . endGroup ( ) ;
2023-02-18 14:09:27 +01:00
if ( path_type = = PlaylistSettingsPage : : PathType : : Ask_User ) {
2022-07-20 01:09:00 +02:00
PlaylistSaveOptionsDialog optionsdialog ;
2022-05-13 23:14:56 +02:00
optionsdialog . setModal ( true ) ;
if ( optionsdialog . exec ( ) ! = QDialog : : Accepted ) return ;
path_type = optionsdialog . path_type ( ) ;
2018-02-27 18:06:05 +01:00
}
2022-05-13 23:14:56 +02:00
s . beginGroup ( Playlist : : kSettingsGroup ) ;
s . setValue ( " last_save_filter " , last_save_filter ) ;
s . setValue ( " last_save_path " , fileinfo . path ( ) ) ;
s . setValue ( " last_save_extension " , fileinfo . suffix ( ) ) ;
s . endGroup ( ) ;
2018-02-27 18:06:05 +01:00
2022-05-13 23:14:56 +02:00
Save ( id = = - 1 ? current_id ( ) : id , filename , path_type ) ;
2018-02-27 18:06:05 +01:00
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : Rename ( const int id , const QString & new_name ) {
2018-02-27 18:06:05 +01:00
Q_ASSERT ( playlists_ . contains ( id ) ) ;
playlist_backend_ - > RenamePlaylist ( id , new_name ) ;
playlists_ [ id ] . name = new_name ;
emit PlaylistRenamed ( id , new_name ) ;
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : Favorite ( const int id , const bool favorite ) {
2018-02-27 18:06:05 +01:00
if ( playlists_ . contains ( id ) ) {
2018-05-01 00:41:33 +02:00
// If playlists_ contains this playlist, its means it's opened: star or unstar it.
2018-02-27 18:06:05 +01:00
playlist_backend_ - > FavoritePlaylist ( id , favorite ) ;
playlists_ [ id ] . p - > set_favorite ( favorite ) ;
}
else {
Q_ASSERT ( ! favorite ) ;
2018-05-01 00:41:33 +02:00
// Otherwise it means user wants to remove this playlist from the left panel,
// while it's not visible in the playlist tabbar either, because it has been closed: delete it.
2018-02-27 18:06:05 +01:00
playlist_backend_ - > RemovePlaylist ( id ) ;
}
emit PlaylistFavorited ( id , favorite ) ;
}
2020-08-23 19:17:50 +02:00
bool PlaylistManager : : Close ( const int id ) {
2018-02-27 18:06:05 +01:00
// Won't allow removing the last playlist
if ( playlists_ . count ( ) < = 1 | | ! playlists_ . contains ( id ) ) return false ;
int next_id = - 1 ;
2021-03-21 04:47:11 +01:00
QList < int > playlist_ids = playlists_ . keys ( ) ;
for ( const int possible_next_id : playlist_ids ) {
2018-02-27 18:06:05 +01:00
if ( possible_next_id ! = id ) {
next_id = possible_next_id ;
break ;
}
}
if ( next_id = = - 1 ) return false ;
if ( id = = active_ ) SetActivePlaylist ( next_id ) ;
if ( id = = current_ ) SetCurrentPlaylist ( next_id ) ;
Data data = playlists_ . take ( id ) ;
emit PlaylistClosed ( id ) ;
if ( ! data . p - > is_favorite ( ) ) {
playlist_backend_ - > RemovePlaylist ( id ) ;
emit PlaylistDeleted ( id ) ;
}
delete data . p ;
return true ;
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : Delete ( const int id ) {
2018-02-27 18:06:05 +01:00
if ( ! Close ( id ) ) {
return ;
}
playlist_backend_ - > RemovePlaylist ( id ) ;
emit PlaylistDeleted ( id ) ;
}
void PlaylistManager : : OneOfPlaylistsChanged ( ) {
emit PlaylistChanged ( qobject_cast < Playlist * > ( sender ( ) ) ) ;
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : SetCurrentPlaylist ( const int id ) {
2018-02-27 18:06:05 +01:00
Q_ASSERT ( playlists_ . contains ( id ) ) ;
2020-08-09 14:00:56 +02:00
// Save the scroll position for the current playlist.
if ( playlists_ . contains ( current_ ) ) {
playlists_ [ current_ ] . scroll_position = playlist_container_ - > view ( ) - > verticalScrollBar ( ) - > value ( ) ;
}
2018-02-27 18:06:05 +01:00
current_ = id ;
2020-08-09 14:00:56 +02:00
emit CurrentChanged ( current ( ) , playlists_ [ id ] . scroll_position ) ;
2018-02-27 18:06:05 +01:00
UpdateSummaryText ( ) ;
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : SetActivePlaylist ( const int id ) {
2018-02-27 18:06:05 +01:00
Q_ASSERT ( playlists_ . contains ( id ) ) ;
2019-05-02 11:31:31 +02:00
// Kinda a hack: unset the current item from the old active playlist before setting the new one
2018-02-27 18:06:05 +01:00
if ( active_ ! = - 1 & & active_ ! = id ) active ( ) - > set_current_row ( - 1 ) ;
active_ = id ;
2019-05-02 12:17:47 +02:00
2018-02-27 18:06:05 +01:00
emit ActiveChanged ( active ( ) ) ;
2020-11-20 21:48:10 +01:00
sequence_ - > set_dynamic ( active ( ) - > is_dynamic ( ) ) ;
2020-11-19 18:12:48 +01:00
2018-02-27 18:06:05 +01:00
}
void PlaylistManager : : SetActiveToCurrent ( ) {
// Check if we need to update the active playlist.
2018-05-01 00:41:33 +02:00
// By calling SetActiveToCurrent, the playlist manager emits the signal "ActiveChanged".
2019-08-22 18:45:32 +02:00
// This signal causes the network remote module to send all playlists to the clients, even if no change happen.
2018-02-27 18:06:05 +01:00
if ( current_id ( ) ! = active_id ( ) ) {
SetActivePlaylist ( current_id ( ) ) ;
}
}
void PlaylistManager : : ClearCurrent ( ) {
current ( ) - > Clear ( ) ;
}
void PlaylistManager : : ShuffleCurrent ( ) {
current ( ) - > Shuffle ( ) ;
}
void PlaylistManager : : RemoveDuplicatesCurrent ( ) {
current ( ) - > RemoveDuplicateSongs ( ) ;
}
void PlaylistManager : : RemoveUnavailableCurrent ( ) {
current ( ) - > RemoveUnavailableSongs ( ) ;
}
void PlaylistManager : : SetActivePlaying ( ) { active ( ) - > Playing ( ) ; }
void PlaylistManager : : SetActivePaused ( ) { active ( ) - > Paused ( ) ; }
void PlaylistManager : : SetActiveStopped ( ) { active ( ) - > Stopped ( ) ; }
void PlaylistManager : : ChangePlaylistOrder ( const QList < int > & ids ) {
playlist_backend_ - > SetPlaylistOrder ( ids ) ;
}
void PlaylistManager : : UpdateSummaryText ( ) {
int tracks = current ( ) - > rowCount ( ) ;
quint64 nanoseconds = 0 ;
int selected = 0 ;
// Get the length of the selected tracks
for ( const QItemSelectionRange & range : playlists_ [ current_id ( ) ] . selection ) {
if ( ! range . isValid ( ) ) continue ;
selected + = range . bottom ( ) - range . top ( ) + 1 ;
2021-08-23 21:21:08 +02:00
for ( int i = range . top ( ) ; i < = range . bottom ( ) ; + + i ) {
2018-02-27 18:06:05 +01:00
qint64 length = range . model ( ) - > index ( i , Playlist : : Column_Length ) . data ( ) . toLongLong ( ) ;
2021-08-23 21:21:08 +02:00
if ( length > 0 ) {
2018-02-27 18:06:05 +01:00
nanoseconds + = length ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
}
}
QString summary ;
if ( selected > 1 ) {
summary + = tr ( " %1 selected of " ) . arg ( selected ) + " " ;
2020-04-20 18:03:18 +02:00
}
else {
2018-02-27 18:06:05 +01:00
nanoseconds = current ( ) - > GetTotalLength ( ) ;
}
// TODO: Make the plurals translatable
summary + = tracks = = 1 ? tr ( " 1 track " ) : tr ( " %1 tracks " ) . arg ( tracks ) ;
2021-06-22 13:54:58 +02:00
if ( nanoseconds > 0 ) {
2018-02-27 18:06:05 +01:00
summary + = " - [ " + Utilities : : WordyTimeNanosec ( nanoseconds ) + " ] " ;
2021-06-22 13:54:58 +02:00
}
2018-02-27 18:06:05 +01:00
emit SummaryTextChanged ( summary ) ;
}
void PlaylistManager : : SelectionChanged ( const QItemSelection & selection ) {
playlists_ [ current_id ( ) ] . selection = selection ;
UpdateSummaryText ( ) ;
}
void PlaylistManager : : SongsDiscovered ( const SongList & songs ) {
2018-03-10 13:02:56 +01:00
// Some songs might've changed in the collection, let's update any playlist items we have that match those songs
2018-02-27 18:06:05 +01:00
for ( const Song & song : songs ) {
2021-08-22 23:26:53 +02:00
for ( const Data & data : std : : as_const ( playlists_ ) ) {
2023-02-18 14:09:27 +01:00
PlaylistItemPtrList items = data . p - > collection_items_by_id ( song . id ( ) ) ;
2020-04-20 18:03:18 +02:00
for ( PlaylistItemPtr item : items ) {
2018-02-27 18:06:05 +01:00
if ( item - > Metadata ( ) . directory_id ( ) ! = song . directory_id ( ) ) continue ;
2021-04-11 02:01:27 +02:00
item - > SetMetadata ( song ) ;
2020-08-10 00:32:57 +02:00
if ( item - > HasTemporaryMetadata ( ) ) item - > UpdateTemporaryMetadata ( song ) ;
2018-02-27 18:06:05 +01:00
data . p - > ItemChanged ( item ) ;
}
}
}
}
// When Player has processed the new song chosen by the user...
2020-08-23 19:17:50 +02:00
void PlaylistManager : : SongChangeRequestProcessed ( const QUrl & url , const bool valid ) {
2018-02-27 18:06:05 +01:00
for ( Playlist * playlist : GetAllPlaylists ( ) ) {
if ( playlist - > ApplyValidityOnCurrentSong ( url , valid ) ) {
return ;
}
}
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : InsertUrls ( const int id , const QList < QUrl > & urls , const int pos , const bool play_now , const bool enqueue ) {
2018-02-27 18:06:05 +01:00
Q_ASSERT ( playlists_ . contains ( id ) ) ;
playlists_ [ id ] . p - > InsertUrls ( urls , pos , play_now , enqueue ) ;
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : InsertSongs ( const int id , const SongList & songs , const int pos , const bool play_now , const bool enqueue ) {
2018-02-27 18:06:05 +01:00
Q_ASSERT ( playlists_ . contains ( id ) ) ;
playlists_ [ id ] . p - > InsertSongs ( songs , pos , play_now , enqueue ) ;
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : RemoveItemsWithoutUndo ( const int id , const QList < int > & indices ) {
2018-02-27 18:06:05 +01:00
Q_ASSERT ( playlists_ . contains ( id ) ) ;
playlists_ [ id ] . p - > RemoveItemsWithoutUndo ( indices ) ;
}
2021-06-22 13:45:29 +02:00
void PlaylistManager : : RemoveCurrentSong ( ) const {
2018-02-27 18:06:05 +01:00
active ( ) - > removeRows ( active ( ) - > current_index ( ) . row ( ) , 1 ) ;
}
void PlaylistManager : : InvalidateDeletedSongs ( ) {
for ( Playlist * playlist : GetAllPlaylists ( ) ) {
playlist - > InvalidateDeletedSongs ( ) ;
}
}
void PlaylistManager : : RemoveDeletedSongs ( ) {
for ( Playlist * playlist : GetAllPlaylists ( ) ) {
playlist - > RemoveDeletedSongs ( ) ;
}
}
QString PlaylistManager : : GetNameForNewPlaylist ( const SongList & songs ) {
if ( songs . isEmpty ( ) ) {
return tr ( " Playlist " ) ;
}
QSet < QString > artists ;
QSet < QString > albums ;
2021-06-20 19:04:08 +02:00
artists . reserve ( songs . count ( ) ) ;
albums . reserve ( songs . count ( ) ) ;
2018-02-27 18:06:05 +01:00
for ( const Song & song : songs ) {
artists < < ( song . artist ( ) . isEmpty ( ) ? tr ( " Unknown " ) : song . artist ( ) ) ;
albums < < ( song . album ( ) . isEmpty ( ) ? tr ( " Unknown " ) : song . album ( ) ) ;
if ( artists . size ( ) > 1 ) {
break ;
}
}
bool various_artists = artists . size ( ) > 1 ;
QString result ;
if ( various_artists ) {
result = tr ( " Various artists " ) ;
}
else {
2021-03-21 04:47:11 +01:00
QStringList artist_names = artists . values ( ) ;
result = artist_names . first ( ) ;
2018-02-27 18:06:05 +01:00
}
if ( ! various_artists & & albums . size ( ) = = 1 ) {
2021-03-21 04:47:11 +01:00
QStringList album_names = albums . values ( ) ;
result + = " - " + album_names . first ( ) ;
2018-02-27 18:06:05 +01:00
}
return result ;
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : Open ( const int id ) {
2018-02-27 18:06:05 +01:00
if ( playlists_ . contains ( id ) ) {
return ;
}
const PlaylistBackend : : Playlist & p = playlist_backend_ - > GetPlaylist ( id ) ;
if ( p . id ! = id ) {
return ;
}
AddPlaylist ( p . id , p . name , p . special_type , p . ui_path , p . favorite ) ;
}
2020-08-23 19:17:50 +02:00
void PlaylistManager : : SetCurrentOrOpen ( const int id ) {
2018-02-27 18:06:05 +01:00
Open ( id ) ;
SetCurrentPlaylist ( id ) ;
}
2020-08-23 19:17:50 +02:00
bool PlaylistManager : : IsPlaylistOpen ( const int id ) {
2018-02-27 18:06:05 +01:00
return playlists_ . contains ( id ) ;
}
2020-09-17 17:50:17 +02:00
void PlaylistManager : : PlaySmartPlaylist ( PlaylistGeneratorPtr generator , bool as_new , bool clear ) {
if ( as_new ) {
New ( generator - > name ( ) ) ;
}
if ( clear ) {
current ( ) - > Clear ( ) ;
}
current ( ) - > InsertSmartPlaylist ( generator ) ;
}
2021-10-30 18:53:14 +02:00
void PlaylistManager : : RateCurrentSong ( const float rating ) {
2020-09-17 17:50:17 +02:00
active ( ) - > RateSong ( active ( ) - > current_index ( ) , rating ) ;
}
2021-10-30 18:53:14 +02:00
void PlaylistManager : : RateCurrentSong2 ( const int rating ) {
RateCurrentSong ( static_cast < float > ( rating ) / 5.0F ) ;
2020-09-17 17:50:17 +02:00
}
2022-07-20 01:09:00 +02:00
void PlaylistManager : : SaveAllPlaylists ( ) {
2023-02-18 14:09:27 +01:00
SavePlaylistsDialog dialog ( parser ( ) - > file_extensions ( PlaylistParser : : Type : : Save ) , parser ( ) - > default_extension ( ) ) ;
2022-07-20 01:09:00 +02:00
if ( dialog . exec ( ) ! = QDialog : : Accepted ) {
return ;
}
const QString path = dialog . path ( ) ;
if ( path . isEmpty ( ) | | ! QDir ( ) . exists ( path ) ) return ;
QString extension = dialog . extension ( ) ;
if ( extension . isEmpty ( ) ) extension = parser ( ) - > default_extension ( ) ;
QSettings s ;
s . beginGroup ( PlaylistSettingsPage : : kSettingsGroup ) ;
2023-02-18 14:09:27 +01:00
PlaylistSettingsPage : : PathType path_type = static_cast < PlaylistSettingsPage : : PathType > ( s . value ( " path_type " , static_cast < int > ( PlaylistSettingsPage : : PathType : : Automatic ) ) . toInt ( ) ) ;
2022-07-20 01:09:00 +02:00
s . endGroup ( ) ;
2023-02-18 14:09:27 +01:00
if ( path_type = = PlaylistSettingsPage : : PathType : : Ask_User ) {
2022-07-20 01:09:00 +02:00
PlaylistSaveOptionsDialog optionsdialog ;
optionsdialog . setModal ( true ) ;
if ( optionsdialog . exec ( ) ! = QDialog : : Accepted ) return ;
path_type = optionsdialog . path_type ( ) ;
}
for ( QMap < int , Data > : : const_iterator it = playlists_ . constBegin ( ) ; it ! = playlists_ . constEnd ( ) ; + + it ) {
const Data & data = * it ;
const QString filepath = path + " / " + data . name + " . " + extension ;
Save ( it . key ( ) , filepath , path_type ) ;
}
}