diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp
index f38a63c8f0..ced4356a4f 100644
--- a/src/core/mainwindow.cpp
+++ b/src/core/mainwindow.cpp
@@ -367,6 +367,7 @@ MainWindow::MainWindow(Application *app,
collection_show_duplicates_(nullptr),
collection_show_untagged_(nullptr),
playlist_menu_(new QMenu(this)),
+ shuffle_playlist_menu_(new QMenu(this)),
playlist_play_pause_(nullptr),
playlist_stop_after_(nullptr),
playlist_undoredo_(nullptr),
@@ -592,7 +593,6 @@ MainWindow::MainWindow(Application *app,
QObject::connect(ui_->action_toggle_show_sidebar, &QAction::toggled, this, &MainWindow::ToggleSidebar);
QObject::connect(ui_->action_about_strawberry, &QAction::triggered, this, &MainWindow::ShowAboutDialog);
QObject::connect(ui_->action_about_qt, &QAction::triggered, qApp, &QApplication::aboutQt);
- QObject::connect(ui_->action_shuffle, &QAction::triggered, &*app_->playlist_manager(), &PlaylistManager::ShuffleCurrent);
QObject::connect(ui_->action_open_file, &QAction::triggered, this, &MainWindow::AddFile);
QObject::connect(ui_->action_open_cd, &QAction::triggered, this, &MainWindow::AddCDTracks);
QObject::connect(ui_->action_add_file, &QAction::triggered, this, &MainWindow::AddFile);
@@ -628,10 +628,20 @@ MainWindow::MainWindow(Application *app,
ui_->button_scrobble->setDefaultAction(ui_->action_toggle_scrobbling);
ui_->button_love->setDefaultAction(ui_->action_love);
+ // Shuffle playlist sub-menu
+ QActionGroup *shuffle_playlist_group = new QActionGroup(this);
+ shuffle_playlist_group->addAction(ui_->action_shuffle_playlist_all);
+ shuffle_playlist_group->addAction(ui_->action_shuffle_playlist_albums);
+ shuffle_playlist_group->addAction(ui_->action_shuffle_playlist_grouping);
+ shuffle_playlist_menu_->addActions(shuffle_playlist_group->actions());
+
+ QObject::connect(shuffle_playlist_group, &QActionGroup::triggered, this, &MainWindow::ShufflePlaylistActionTriggered);
+
ui_->playlist->SetActions(ui_->action_new_playlist, ui_->action_load_playlist, ui_->action_save_playlist, ui_->action_clear_playlist, ui_->action_next_playlist, /* These two actions aren't associated */ ui_->action_previous_playlist /* to a button but to the main window */, ui_->action_save_all_playlists);
// Add the shuffle and repeat action groups to the menu
ui_->action_shuffle_mode->setMenu(ui_->playlist_sequence->shuffle_menu());
ui_->action_repeat_mode->setMenu(ui_->playlist_sequence->repeat_menu());
+ ui_->action_shuffle->setMenu(shuffle_playlist_menu_);
// Stop actions
QMenu *stop_menu = new QMenu(this);
@@ -3620,3 +3630,13 @@ void MainWindow::ProcessMetadataQueue() {
}
}
+
+void MainWindow::ShufflePlaylistActionTriggered(QAction *action) {
+
+ PlaylistSequence::ShuffleMode mode = PlaylistSequence::ShuffleMode::All;
+ if (action == ui_->action_shuffle_playlist_albums) mode = PlaylistSequence::ShuffleMode::Albums;
+ if (action == ui_->action_shuffle_playlist_grouping) mode = PlaylistSequence::ShuffleMode::Grouping;
+
+ app_->playlist_manager()->ShuffleCurrent(mode);
+
+}
diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h
index ffe6b73d68..aa1b21e8ed 100644
--- a/src/core/mainwindow.h
+++ b/src/core/mainwindow.h
@@ -277,6 +277,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void FetchStreamingMetadata();
void ProcessMetadataQueue();
+ void ShufflePlaylistActionTriggered(QAction *action);
+
public Q_SLOTS:
void CommandlineOptionsReceived(const QByteArray &string_options);
void Raise();
@@ -362,6 +364,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
QAction *collection_show_untagged_;
QMenu *playlist_menu_;
+ QMenu *shuffle_playlist_menu_;
QAction *playlist_play_pause_;
QAction *playlist_stop_after_;
QAction *playlist_undoredo_;
diff --git a/src/core/mainwindow.ui b/src/core/mainwindow.ui
index 00c31f683e..8c2e0631db 100644
--- a/src/core/mainwindow.ui
+++ b/src/core/mainwindow.ui
@@ -662,9 +662,6 @@
S&huffle playlist
-
- Ctrl+H
-
@@ -863,6 +860,33 @@
Import data from last.fm...
+
+
+ false
+
+
+ Single track as random element
+
+
+ Ctrl+H
+
+
+
+
+ false
+
+
+ Album as random element
+
+
+
+
+ false
+
+
+ Grouped tracks as random element
+
+
diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp
index 4750aa6cfc..b66ecb9f30 100644
--- a/src/playlist/playlist.cpp
+++ b/src/playlist/playlist.cpp
@@ -2014,27 +2014,26 @@ void Playlist::ReloadItems(const QList &rows) {
}
-void Playlist::Shuffle() {
-
- PlaylistItemPtrList new_items(items_);
+void Playlist::Shuffle(const PlaylistSequence::ShuffleMode shuffle_mode) {
int begin = 0;
- if (current_item_index_.isValid()) {
- if (new_items[0] != new_items[current_item_index_.row()]) {
- std::swap(new_items[0], new_items[current_item_index_.row()]);
- }
- begin = 1;
- }
if (dynamic_playlist_ && current_item_index_.isValid()) {
- begin += current_item_index_.row() + 1;
+ begin += current_item_index_.row();
}
+ QList index_items;
const int count = static_cast(items_.count());
for (int i = begin; i < count; ++i) {
- const int new_pos = i + (rand() % (count - i));
+ index_items.push_back(i);
+ }
+
+ ReshuffleIndices(index_items, shuffle_mode, begin, true);
+
+ PlaylistItemPtrList new_items;
- std::swap(new_items[i], new_items[new_pos]);
+ for (int idx_shuffle : std::as_const(index_items)) {
+ new_items.push_back(items_[idx_shuffle]);
}
undo_stack_->push(new PlaylistUndoCommandShuffleItems(this, new_items));
@@ -2048,7 +2047,29 @@ bool AlbumShuffleComparator(const QHash &album_key_positions, cons
const int left_pos = album_key_positions[album_keys[left]];
const int right_pos = album_key_positions[album_keys[right]];
+ // With this return, I keep the tracks in the order they have in the playlist
if (left_pos == right_pos) return left < right;
+
+ // Sort the albums by their position in the playlist
+ return left_pos < right_pos;
+
+}
+
+// to the contrary of the function upper, I will keep the tracks in the order of their position in the album
+// not with the position they have in the playlist
+bool AlbumShuffleComparatorTrackOrder(const QHash &album_key_positions, const QHash &album_keys, const PlaylistItemPtrList &items, const int left, const int right) {
+
+ const int left_pos = album_key_positions[album_keys[left]];
+ const int right_pos = album_key_positions[album_keys[right]];
+
+ if (left_pos == right_pos) {
+ auto &&left_song = items[left]->EffectiveMetadata();
+ auto &&right_song = items[right]->EffectiveMetadata();
+
+ if (left_song.disc() == right_song.disc()) return left_song.track() < right_song.track();
+
+ return left_song.disc() < right_song.disc();
+ }
return left_pos < right_pos;
}
@@ -2057,18 +2078,38 @@ bool AlbumShuffleComparator(const QHash &album_key_positions, cons
void Playlist::ReshuffleIndices() {
- const PlaylistSequence::ShuffleMode shuffle_mode = ShuffleMode();
+ // First, cancel the replay position
+ // TODO : uncomment the code below when the branch «Grouped tracks are considered as single track» will be merged
+ // next_song_after_queued_ = -1;
+
+ current_virtual_index_ = ReshuffleIndices(virtual_items_, ShuffleMode(), 0, false);
+
+}
+
+int Playlist::ReshuffleIndices(QList& virtual_items, const PlaylistSequence::ShuffleMode shuffle_mode, const int base_reference, const bool album_keep_track_order) {
+
+ static std::mt19937 rng{std::random_device{}()};
+
switch (shuffle_mode) {
case PlaylistSequence::ShuffleMode::Off:{
// No shuffling - sort the virtual item list normally.
- std::sort(virtual_items_.begin(), virtual_items_.end());
+ std::sort(virtual_items.begin(), virtual_items.end());
break;
}
case PlaylistSequence::ShuffleMode::All:
case PlaylistSequence::ShuffleMode::InsideAlbum:{
- std::random_device rd;
- std::shuffle(virtual_items_.begin(), virtual_items_.end(), std::mt19937(rd()));
+ std::shuffle(virtual_items.begin(), virtual_items.end(), rng);
+
+ // If the user is currently playing a song, force its track to be first
+ // Also check last_played_row() for cases where current_row() hasn't been set yet (e.g., on app startup)
+ int reference_row = current_row();
+ if (reference_row == -1 && last_played_row() != -1) {
+ reference_row = last_played_row();
+ }
+ if (reference_row > base_reference) {
+ std::swap(virtual_items[0], virtual_items[reference_row - base_reference]);
+ }
break;
}
@@ -2077,7 +2118,7 @@ void Playlist::ReshuffleIndices() {
QSet album_key_set; // unique keys
// Find all the unique albums in the playlist
- for (QList::const_iterator it = virtual_items_.constBegin(); it != virtual_items_.constEnd(); ++it) {
+ for (QList::const_iterator it = virtual_items.constBegin(); it != virtual_items.constEnd(); ++it) {
const int index = *it;
const QString key = items_[index]->EffectiveMetadata().AlbumKey();
album_keys[index] = key;
@@ -2085,7 +2126,6 @@ void Playlist::ReshuffleIndices() {
}
// Shuffle them
- static std::mt19937 rng{std::random_device{}()};
QStringList shuffled_album_keys = album_key_set.values();
std::shuffle(shuffled_album_keys.begin(), shuffled_album_keys.end(), rng);
@@ -2098,8 +2138,8 @@ void Playlist::ReshuffleIndices() {
if (reference_row != -1) {
const QString key = items_[reference_row]->EffectiveMetadata().AlbumKey();
const qint64 pos = shuffled_album_keys.indexOf(key);
- if (pos >= 1) {
- std::swap(shuffled_album_keys[0], shuffled_album_keys[pos]);
+ if (pos > base_reference) {
+ std::swap(shuffled_album_keys[0], shuffled_album_keys[pos - base_reference]);
}
}
@@ -2109,8 +2149,18 @@ void Playlist::ReshuffleIndices() {
album_key_positions[shuffled_album_keys[i]] = i;
}
+ if (album_keep_track_order) {
+ // Sort the virtual items : use AlbumShuffleComparator with a cheap-to-copy lambda comparator
+ // force the track order to be the album one
+ std::stable_sort(virtual_items.begin(), virtual_items.end(), [&album_key_positions, &album_keys, this](const int lhs, const int rhs) {
+ return AlbumShuffleComparatorTrackOrder(album_key_positions, album_keys, items_, lhs, rhs);
+ });
+
+ break;
+ }
// Sort the virtual items : use AlbumShuffleComparator with a cheap-to-copy lambda comparator
- std::stable_sort(virtual_items_.begin(), virtual_items_.end(), [&album_key_positions, &album_keys](const int lhs, const int rhs) {
+ // keep the track order they have in the playlist
+ std::stable_sort(virtual_items.begin(), virtual_items.end(), [&album_key_positions, &album_keys](const int lhs, const int rhs) {
return AlbumShuffleComparator(album_key_positions, album_keys, lhs, rhs);
});
@@ -2121,7 +2171,7 @@ void Playlist::ReshuffleIndices() {
QSet grouping_key_set; // unique keys
// Find all the unique grouping keys in the playlist
- for (QList::const_iterator it = virtual_items_.constBegin(); it != virtual_items_.constEnd(); ++it) {
+ for (QList::const_iterator it = virtual_items.constBegin(); it != virtual_items.constEnd(); ++it) {
const int index = *it;
const QString key = items_[index]->EffectiveMetadata().GroupingKey();
grouping_keys[index] = key;
@@ -2129,7 +2179,6 @@ void Playlist::ReshuffleIndices() {
}
// Shuffle them
- static std::mt19937 rng{std::random_device{}()};
QStringList shuffled_grouping_keys = grouping_key_set.values();
std::shuffle(shuffled_grouping_keys.begin(), shuffled_grouping_keys.end(), rng);
@@ -2142,8 +2191,8 @@ void Playlist::ReshuffleIndices() {
if (reference_row != -1) {
const QString key = items_[reference_row]->EffectiveMetadata().GroupingKey();
const qint64 pos = shuffled_grouping_keys.indexOf(key);
- if (pos >= 1) {
- std::swap(shuffled_grouping_keys[0], shuffled_grouping_keys[pos]);
+ if (pos > base_reference) {
+ std::swap(shuffled_grouping_keys[0], shuffled_grouping_keys[pos - base_reference]);
}
}
@@ -2153,22 +2202,23 @@ void Playlist::ReshuffleIndices() {
grouping_key_positions[shuffled_grouping_keys[i]] = i;
}
- // Sort the virtual items: use AlbumShuffleComparator as a grouping-key comparator with a cheap-to-copy lambda
- std::stable_sort(virtual_items_.begin(), virtual_items_.end(), [&grouping_key_positions, &grouping_keys](const int lhs, const int rhs) {
- return AlbumShuffleComparator(grouping_key_positions, grouping_keys, lhs, rhs);
+ // Sort the virtual items: use AlbumShuffleComparatorTrackOrder as a grouping-key comparator with a cheap-to-copy lambda
+ // This sort method will keep the track number order so important in the grouped read
+ std::stable_sort(virtual_items.begin(), virtual_items.end(), [&grouping_key_positions, &grouping_keys, this](const int lhs, const int rhs) {
+ return AlbumShuffleComparatorTrackOrder(grouping_key_positions, grouping_keys, items_, lhs, rhs);
});
break;
}
}
- // Update current virtual index
+ // I keep the computation of the virtual index because it is only return, if you want to ignore it, you can
+ // besides, it will be usefull in the cases I want to use the updated method
if (current_item_index_.isValid()) {
- current_virtual_index_ = static_cast(virtual_items_.indexOf(current_item_index_.row()));
- }
- else {
- current_virtual_index_ = -1;
+ return static_cast(virtual_items.indexOf(current_item_index_.row()));
}
+ return -1;
+
}
void Playlist::set_sequence(PlaylistSequence *v) {
diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h
index 31b6892cf1..b8ba82565b 100644
--- a/src/playlist/playlist.h
+++ b/src/playlist/playlist.h
@@ -290,6 +290,8 @@ class Playlist : public QAbstractListModel {
void ItemReload(const QPersistentModelIndex &idx, const bool metadata_edit);
+ void Shuffle(const PlaylistSequence::ShuffleMode shuffle_mode = PlaylistSequence::ShuffleMode::All);
+
public Q_SLOTS:
void set_current_row(const int i, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll::Maybe, const bool is_stopping = false, const bool force_inform = false);
void Paused();
@@ -303,7 +305,6 @@ class Playlist : public QAbstractListModel {
void Clear();
void RemoveDuplicateSongs();
void RemoveUnavailableSongs();
- void Shuffle();
void ShuffleModeChanged(const PlaylistSequence::ShuffleMode shuffle_mode);
@@ -356,6 +357,8 @@ class Playlist : public QAbstractListModel {
void MoveItemsWithoutUndo(int start, const QList &dest_rows);
void ReOrderWithoutUndo(const PlaylistItemPtrList &new_items);
+ int ReshuffleIndices(QList& virtual_items, const PlaylistSequence::ShuffleMode shuffle_mode, const int base_reference, const bool album_keep_track_order);
+
void RemoveItemsNotInQueue();
// Removes rows with given indices from this playlist.
diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp
index 01d60e29b3..a6cb00f3ac 100644
--- a/src/playlist/playlistmanager.cpp
+++ b/src/playlist/playlistmanager.cpp
@@ -409,6 +409,10 @@ void PlaylistManager::ShuffleCurrent() {
current()->Shuffle();
}
+void PlaylistManager::ShuffleCurrent(const PlaylistSequence::ShuffleMode shuffle_mode) {
+ current()->Shuffle(shuffle_mode);
+}
+
void PlaylistManager::RemoveDuplicatesCurrent() {
current()->RemoveDuplicateSongs();
}
diff --git a/src/playlist/playlistmanager.h b/src/playlist/playlistmanager.h
index be568aed22..43495b8ec7 100644
--- a/src/playlist/playlistmanager.h
+++ b/src/playlist/playlistmanager.h
@@ -94,6 +94,8 @@ class PlaylistManager : public PlaylistManagerInterface {
PlaylistParser *parser() const override { return parser_; }
PlaylistContainer *playlist_container() const override { return playlist_container_; }
+ void ShuffleCurrent(const PlaylistSequence::ShuffleMode shuffle_mode) override;
+
public Q_SLOTS:
void New(const QString &name, const SongList &songs = SongList(), const QString &special_type = QString()) override;
void Load(const QString &filename) override;
@@ -118,6 +120,7 @@ class PlaylistManager : public PlaylistManagerInterface {
// Convenience slots that defer to either current() or active()
void ClearCurrent() override;
+ // TODO : this method is only used for external unit tests : the version with parameter is the one that is used.
void ShuffleCurrent() override;
void RemoveDuplicatesCurrent() override;
void RemoveUnavailableCurrent() override;
diff --git a/src/playlist/playlistmanagerinterface.h b/src/playlist/playlistmanagerinterface.h
index f22b8c299c..184fbac4c0 100644
--- a/src/playlist/playlistmanagerinterface.h
+++ b/src/playlist/playlistmanagerinterface.h
@@ -76,6 +76,8 @@ class PlaylistManagerInterface : public QObject {
virtual void PlaySmartPlaylist(PlaylistGeneratorPtr generator, const bool as_new, const bool clear) = 0;
+ virtual void ShuffleCurrent(const PlaylistSequence::ShuffleMode shuffle_mode) = 0;
+
public Q_SLOTS:
virtual void New(const QString &name, const SongList &songs = SongList(), const QString &special_type = QString()) = 0;
virtual void Load(const QString &filename) = 0;
@@ -96,6 +98,7 @@ class PlaylistManagerInterface : public QObject {
// Convenience slots that defer to either current() or active()
virtual void ClearCurrent() = 0;
+ // TODO : this method is only used for external unit tests : the version with parameter is the one that is used.
virtual void ShuffleCurrent() = 0;
virtual void RemoveDuplicatesCurrent() = 0;
virtual void RemoveUnavailableCurrent() = 0;