diff --git a/src/constants/playlistsettings.h b/src/constants/playlistsettings.h index 1af6148292..07f35f3ee7 100644 --- a/src/constants/playlistsettings.h +++ b/src/constants/playlistsettings.h @@ -45,6 +45,7 @@ constexpr char kShowToolbar[] = "show_toolbar"; constexpr char kPlaylistClear[] = "playlist_clear"; constexpr char kAutoSort[] = "auto_sort"; +constexpr char kGroupingBeforeQueue[] = "grouping_queue"; constexpr char kPathType[] = "path_type"; constexpr char kEditMetadataInline[] = "editmetadatainline"; diff --git a/src/core/player.cpp b/src/core/player.cpp index a80f3f3bb1..48f3fd667e 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -151,6 +151,7 @@ void Player::ReloadSettings() { s.beginGroup(PlaylistSettings::kSettingsGroup); continue_on_error_ = s.value("continue_on_error", false).toBool(); greyout_ = s.value("greyout_songs_play", true).toBool(); + playlist_manager_->update_grouped_before_queue(s.value(PlaylistSettings::kGroupingBeforeQueue).toInt()); s.endGroup(); s.beginGroup(BehaviourSettings::kSettingsGroup); @@ -435,7 +436,7 @@ void Player::NextItem(const EngineBase::TrackChangeFlags change, const Playlist: // Manual track changes override "Repeat track" const bool ignore_repeat_track = change & EngineBase::TrackChangeType::Manual; - int i = active_playlist->next_row(ignore_repeat_track); + int i = active_playlist->next_row(ignore_repeat_track, false); if (i == -1) { playlist_manager_->active()->set_current_row(i); playlist_manager_->active()->reset_last_played(); @@ -635,10 +636,12 @@ void Player::PreviousItem(const EngineBase::TrackChangeFlags change) { if (i == -1) { Stop(); PlayAt(i, false, 0, change, Playlist::AutoScroll::Always, true); + playlist_manager_->active()->CleanNextSongQueued(); return; } PlayAt(i, false, 0, change, Playlist::AutoScroll::Always, false); + playlist_manager_->active()->CleanNextSongQueued(); } diff --git a/src/core/song.cpp b/src/core/song.cpp index 5f0652ad26..c2324ac732 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -1122,6 +1122,16 @@ bool Song::IsOnSameAlbum(const Song &other) const { } +bool Song::IsOnSameGrouping(const Song &other) const { + + return ( !grouping().isEmpty() + && !other.grouping().isEmpty() + && AlbumKey() == other.AlbumKey() + && grouping() == other.grouping() + && filetype() == other.filetype() ); + +} + bool Song::IsSimilar(const Song &other) const { return title().compare(other.title(), Qt::CaseInsensitive) == 0 && artist().compare(other.artist(), Qt::CaseInsensitive) == 0 && diff --git a/src/core/song.h b/src/core/song.h index d028952ae8..27afc052da 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -486,6 +486,7 @@ class Song { bool IsEqual(const Song &other) const; bool IsOnSameAlbum(const Song &other) const; + bool IsOnSameGrouping(const Song &other) const; bool IsSimilar(const Song &other) const; static Source SourceFromURL(const QUrl &url); diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 9fcd183b8b..5cf0849d4b 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -132,6 +132,7 @@ Playlist::Playlist(const SharedPtr task_manager, const int id, const QString &special_type, const bool favorite, + const int grouped_before_queue, QObject *parent) : QAbstractListModel(parent), is_loading_(false), @@ -156,7 +157,10 @@ Playlist::Playlist(const SharedPtr task_manager, scrobble_point_(-1), auto_sort_(false), sort_column_(Column::Title), - sort_order_(Qt::AscendingOrder) { + sort_order_(Qt::AscendingOrder), + left_grouped_song_before_queue_(grouped_before_queue), + init_grouped_song_before_queue_(grouped_before_queue), + next_song_after_queued_(-1) { undo_stack_->setUndoLimit(kUndoStackSize); @@ -641,14 +645,72 @@ int Playlist::PreviousVirtualIndex(int i, const bool ignore_repeat_track) const } -int Playlist::next_row(const bool ignore_repeat_track) { +int Playlist::next_row(const bool ignore_repeat_track, const bool no_grouping_track_count) { - // Any queued items take priority - if (!queue_->is_empty()) { + int next_virtual_index = next_song_after_queued_; + + if (next_virtual_index < 0) { + next_virtual_index = NextVirtualIndex(current_virtual_index_, ignore_repeat_track); + switch (RepeatMode()) { + default: + + // Compare with unsigned int to avoid to check if it is less than the count but positive + if ( static_cast(current_virtual_index_) < static_cast(virtual_items_.count()) + && static_cast(next_virtual_index) < static_cast(virtual_items_.count()) ) { + // The two indexes are in the virtual items array + Song this_song = item_at(virtual_items_[current_virtual_index_])->EffectiveMetadata(); + Song next_song = item_at(virtual_items_[next_virtual_index])->EffectiveMetadata(); + + if ( this_song.IsOnSameGrouping(next_song) + && ( left_grouped_song_before_queue_ == 0 + || no_grouping_track_count + || --left_grouped_song_before_queue_ > 0 ) ) { + // We have two choices here : + // either resetting left_grouped_song_before_queue_ as soon as there is no queued song, + // that is too say, when a song will be queued, you will have to wait the setted number of grouped song before playing it + // either resetting left_grouped_song_before_queue_ when it reaches 0, + // that is too say, when a song will be queued, you will have to wait every n th grouped song before playing it + if (queue_->is_empty()) + // if (left_grouped_song_before_queue_ == 0) + { + left_grouped_song_before_queue_ = init_grouped_song_before_queue_; + } + // The next song is on the same grouping than the current one, + // it cannot be interrupt by the queued items + break; + } + } + + [[fallthrough]]; + + case PlaylistSequence::RepeatMode::Intro: + case PlaylistSequence::RepeatMode::Track: + + // Reset the grouping values + left_grouped_song_before_queue_ = init_grouped_song_before_queue_; + + // Any queued items take priority + if (!queue_->is_empty()) { + // Remember the location in the current playlist to go back right there after the queued play + next_song_after_queued_ = next_virtual_index; + + // Play the queued song + return queue_->PeekNext(); + } + + break; + } + } + else if (!queue_->is_empty()) { + // We already remember the location where we want to go back after the queued play + // we just have to return the next queued song return queue_->PeekNext(); } + else if (next_song_after_queued_ >= 0 && !no_grouping_track_count) { + // No more queued song, go back to the list to play + next_song_after_queued_ = -1; + } - int next_virtual_index = NextVirtualIndex(current_virtual_index_, ignore_repeat_track); if (next_virtual_index >= virtual_items_.count()) { // We've gone off the end of the playlist. @@ -2058,6 +2120,10 @@ bool AlbumShuffleComparator(const QHash &album_key_positions, cons void Playlist::ReshuffleIndices() { const PlaylistSequence::ShuffleMode shuffle_mode = ShuffleMode(); + + // First, cancel the replay position + next_song_after_queued_ = -1; + switch (shuffle_mode) { case PlaylistSequence::ShuffleMode::Off:{ // No shuffling - sort the virtual item list normally. diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h index 31b6892cf1..d16375df76 100644 --- a/src/playlist/playlist.h +++ b/src/playlist/playlist.h @@ -75,6 +75,9 @@ using ColumnAlignmentMap = QMap; Q_DECLARE_METATYPE(Qt::Alignment) Q_DECLARE_METATYPE(ColumnAlignmentMap) +// This default value keep the current behavior : the queued item is played at the end of the current track, even a grouped one +static constexpr int GROUPED_BEFORE_QUEUE_DEFAULT = 1; + class Playlist : public QAbstractListModel { Q_OBJECT @@ -92,6 +95,7 @@ class Playlist : public QAbstractListModel { const int id, const QString &special_type = QString(), const bool favorite = false, + const int grouped_before_queue = GROUPED_BEFORE_QUEUE_DEFAULT, QObject *parent = nullptr); ~Playlist() override; @@ -192,10 +196,12 @@ class Playlist : public QAbstractListModel { int last_played_row() const; void reset_last_played() { last_played_item_index_ = QPersistentModelIndex(); } void reset_played_indexes() { played_indexes_.clear(); } - int next_row(const bool ignore_repeat_track = false); + int next_row(const bool ignore_repeat_track = false, const bool no_grouping_track_count = true); int previous_row(const bool ignore_repeat_track = false) const; int take_previous_row(const bool ignore_repeat_track = false); + void update_grouped_before_queue(const int grouped_before_queue) { init_grouped_song_before_queue_ = grouped_before_queue; } + QModelIndex current_index() const; bool stop_after_current() const; @@ -286,6 +292,9 @@ class Playlist : public QAbstractListModel { void RateSong(const QModelIndex &idx, const float rating); void RateSongs(const QModelIndexList &index_list, const float rating); + // Clean the queued song + void CleanNextSongQueued() { next_song_after_queued_ = -1; } + void set_auto_sort(const bool auto_sort) { auto_sort_ = auto_sort; } void ItemReload(const QPersistentModelIndex &idx, const bool metadata_edit); @@ -436,6 +445,11 @@ class Playlist : public QAbstractListModel { bool auto_sort_; Column sort_column_; Qt::SortOrder sort_order_; + + // Variables to count the number of times the queue list had been ignored due to grouping values + int left_grouped_song_before_queue_; + int init_grouped_song_before_queue_; + int next_song_after_queued_; }; #endif // PLAYLIST_H diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp index 8118228f61..c0783c1d4e 100644 --- a/src/playlist/playlistmanager.cpp +++ b/src/playlist/playlistmanager.cpp @@ -82,7 +82,8 @@ PlaylistManager::PlaylistManager(const SharedPtr task_manager, playlist_container_(nullptr), current_(-1), active_(-1), - playlists_loading_(0) { + playlists_loading_(0), + grouped_before_queue_(GROUPED_BEFORE_QUEUE_DEFAULT) { setObjectName(QLatin1String(QObject::metaObject()->className())); @@ -95,6 +96,17 @@ PlaylistManager::~PlaylistManager() { } +void PlaylistManager::update_grouped_before_queue(const int grouped_before_queue) { + + grouped_before_queue_ = grouped_before_queue; + + QList datas = playlists_.values(); + for (Data &data : datas) { + data.p->update_grouped_before_queue(grouped_before_queue); + } + +} + void PlaylistManager::Init(PlaylistSequence *sequence, PlaylistContainer *playlist_container) { sequence_ = sequence; @@ -155,7 +167,7 @@ QItemSelection PlaylistManager::selection(const int id) const { Playlist *PlaylistManager::AddPlaylist(const int id, const QString &name, const QString &special_type, const QString &ui_path, const bool favorite) { - Playlist *ret = new Playlist(task_manager_, url_handlers_, playlist_backend_, collection_backend_, tagreader_client_, id, special_type, favorite); + Playlist *ret = new Playlist(task_manager_, url_handlers_, playlist_backend_, collection_backend_, tagreader_client_, id, special_type, favorite, grouped_before_queue_); ret->set_sequence(sequence_); ret->set_ui_path(ui_path); diff --git a/src/playlist/playlistmanager.h b/src/playlist/playlistmanager.h index be568aed22..04c6ea5f35 100644 --- a/src/playlist/playlistmanager.h +++ b/src/playlist/playlistmanager.h @@ -71,6 +71,10 @@ class PlaylistManager : public PlaylistManagerInterface { Playlist *playlist(const int id) const override { return playlists_[id].p; } Playlist *current() const override { return playlist(current_id()); } Playlist *active() const override { return playlist(active_id()); } + int grouped_before_queue() const { return grouped_before_queue_; } + + // Update the grouped before queue value : we have to do more than just update the attribute + void update_grouped_before_queue(const int grouped_before_queue); // Returns the collection of playlists managed by this PlaylistManager. QList GetAllPlaylists() const override; @@ -179,6 +183,7 @@ class PlaylistManager : public PlaylistManagerInterface { int current_; int active_; int playlists_loading_; + int grouped_before_queue_; }; #endif // PLAYLISTMANAGER_H diff --git a/src/settings/playlistsettingspage.cpp b/src/settings/playlistsettingspage.cpp index 30d8da1ffb..269f918bb1 100644 --- a/src/settings/playlistsettingspage.cpp +++ b/src/settings/playlistsettingspage.cpp @@ -45,12 +45,36 @@ PlaylistSettingsPage::PlaylistSettingsPage(SettingsDialog *dialog, QWidget *pare ui_->setupUi(this); setWindowIcon(IconLoader::Load(u"document-new"_s, true, 0, 32)); + ui_->spinbox_grouping_before_queue->setToolTip(GroupingBeforeQueueToolTip()); + + } PlaylistSettingsPage::~PlaylistSettingsPage() { delete ui_; } +QString PlaylistSettingsPage::GroupingBeforeQueueToolTip() { + + return "

"_L1 + + QObject::tr("This field is used to tell how many tracks of the same group will be played before the queued track will be played.") + + u' ' + + "

"_L1 + + + "0: "_L1 + + QObject::tr(" is used to say that the queued track will wait for the end of the current track group before being played.") + + "

"_L1 + + + "1: "_L1 + + QObject::tr(" is used to say that the queued track will be played after the end of the current track, whatever it belongs to a group or not.") + + "

"_L1 + + + QObject::tr("Any other value to give the number of grouped tracks played before playing the queued one(s). ") + + QObject::tr("Obviously, if there are less grouped tracks to play than the given number, the queued tracks will be played as soon as the last grouped track is played.") + + "

"_L1; + +} + void PlaylistSettingsPage::Load() { Settings s; @@ -93,6 +117,8 @@ void PlaylistSettingsPage::Load() { ui_->radiobutton_askpath->setChecked(true); } + ui_->spinbox_grouping_before_queue->setValue(s.value(kGroupingBeforeQueue, 1).toInt()); + ui_->checkbox_editmetadatainline->setChecked(s.value(kEditMetadataInline, false).toBool()); ui_->checkbox_writemetadata->setChecked(s.value(kWriteMetadata, false).toBool()); @@ -135,6 +161,7 @@ void PlaylistSettingsPage::Save() { s.setValue(kShowToolbar, ui_->checkbox_show_toolbar->isChecked()); s.setValue(kPlaylistClear, ui_->checkbox_playlist_clear->isChecked()); s.setValue(kPathType, static_cast(path_type)); + s.setValue(kGroupingBeforeQueue, ui_->spinbox_grouping_before_queue->value()); s.setValue(kEditMetadataInline, ui_->checkbox_editmetadatainline->isChecked()); s.setValue(kWriteMetadata, ui_->checkbox_writemetadata->isChecked()); s.setValue(kDeleteFiles, ui_->checkbox_delete_files->isChecked()); diff --git a/src/settings/playlistsettingspage.h b/src/settings/playlistsettingspage.h index 4fd61966ab..abc6f75123 100644 --- a/src/settings/playlistsettingspage.h +++ b/src/settings/playlistsettingspage.h @@ -42,6 +42,8 @@ class PlaylistSettingsPage : public SettingsPage { void Load() override; void Save() override; + static QString GroupingBeforeQueueToolTip(); + private: Ui_PlaylistSettingsPage *ui_; }; diff --git a/src/settings/playlistsettingspage.ui b/src/settings/playlistsettingspage.ui index c01e1359c5..678a20d390 100644 --- a/src/settings/playlistsettingspage.ui +++ b/src/settings/playlistsettingspage.ui @@ -98,6 +98,39 @@ + + + + Grouping Play + + + + + + + + Number of grouped track played before play queued track + + + 22 + + + + + + + 0 + + + 1 + + + + + + + + diff --git a/src/smartplaylists/smartplaylistsearchpreview.cpp b/src/smartplaylists/smartplaylistsearchpreview.cpp index 4d1620f09f..a2f48821bd 100644 --- a/src/smartplaylists/smartplaylistsearchpreview.cpp +++ b/src/smartplaylists/smartplaylistsearchpreview.cpp @@ -72,7 +72,7 @@ void SmartPlaylistSearchPreview::Init(const SharedPtr player, collection_backend_ = collection_backend; - model_ = new Playlist(nullptr, nullptr, nullptr, collection_backend_, nullptr, -1, QString(), false, this); + model_ = new Playlist(nullptr, nullptr, nullptr, collection_backend_, nullptr, -1, QString(), false, GROUPED_BEFORE_QUEUE_DEFAULT, this); ui_->tree->setModel(model_); ui_->tree->SetPlaylist(model_);