Skip to content

Commit 59bfc08

Browse files
committed
Add a sub-menu to the shuffle playlist menu to decide how to shuffle.
1 parent 6503377 commit 59bfc08

8 files changed

Lines changed: 167 additions & 38 deletions

File tree

src/core/mainwindow.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ MainWindow::MainWindow(Application *app,
367367
collection_show_duplicates_(nullptr),
368368
collection_show_untagged_(nullptr),
369369
playlist_menu_(new QMenu(this)),
370+
shuffle_playlist_menu_(new QMenu(this)),
370371
playlist_play_pause_(nullptr),
371372
playlist_stop_after_(nullptr),
372373
playlist_undoredo_(nullptr),
@@ -592,7 +593,6 @@ MainWindow::MainWindow(Application *app,
592593
QObject::connect(ui_->action_toggle_show_sidebar, &QAction::toggled, this, &MainWindow::ToggleSidebar);
593594
QObject::connect(ui_->action_about_strawberry, &QAction::triggered, this, &MainWindow::ShowAboutDialog);
594595
QObject::connect(ui_->action_about_qt, &QAction::triggered, qApp, &QApplication::aboutQt);
595-
QObject::connect(ui_->action_shuffle, &QAction::triggered, &*app_->playlist_manager(), &PlaylistManager::ShuffleCurrent);
596596
QObject::connect(ui_->action_open_file, &QAction::triggered, this, &MainWindow::AddFile);
597597
QObject::connect(ui_->action_open_cd, &QAction::triggered, this, &MainWindow::AddCDTracks);
598598
QObject::connect(ui_->action_add_file, &QAction::triggered, this, &MainWindow::AddFile);
@@ -628,10 +628,20 @@ MainWindow::MainWindow(Application *app,
628628
ui_->button_scrobble->setDefaultAction(ui_->action_toggle_scrobbling);
629629
ui_->button_love->setDefaultAction(ui_->action_love);
630630

631+
// Shuffle playlist sub-menu
632+
QActionGroup *shuffle_playlist_group = new QActionGroup(this);
633+
shuffle_playlist_group->addAction(ui_->action_shuffle_playlist_all);
634+
shuffle_playlist_group->addAction(ui_->action_shuffle_playlist_albums);
635+
shuffle_playlist_group->addAction(ui_->action_shuffle_playlist_grouping);
636+
shuffle_playlist_menu_->addActions(shuffle_playlist_group->actions());
637+
638+
QObject::connect(shuffle_playlist_group, &QActionGroup::triggered, this, &MainWindow::ShufflePlaylistActionTriggered);
639+
631640
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);
632641
// Add the shuffle and repeat action groups to the menu
633642
ui_->action_shuffle_mode->setMenu(ui_->playlist_sequence->shuffle_menu());
634643
ui_->action_repeat_mode->setMenu(ui_->playlist_sequence->repeat_menu());
644+
ui_->action_shuffle->setMenu(shuffle_playlist_menu_);
635645

636646
// Stop actions
637647
QMenu *stop_menu = new QMenu(this);
@@ -3614,3 +3624,13 @@ void MainWindow::ProcessMetadataQueue() {
36143624
}
36153625

36163626
}
3627+
3628+
void MainWindow::ShufflePlaylistActionTriggered(QAction *action) {
3629+
3630+
PlaylistSequence::ShuffleMode mode = PlaylistSequence::ShuffleMode::All;
3631+
if (action == ui_->action_shuffle_playlist_albums) mode = PlaylistSequence::ShuffleMode::Albums;
3632+
if (action == ui_->action_shuffle_playlist_grouping) mode = PlaylistSequence::ShuffleMode::Grouping;
3633+
3634+
app_->playlist_manager()->ShuffleCurrent(mode);
3635+
3636+
}

src/core/mainwindow.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
277277
void FetchStreamingMetadata();
278278
void ProcessMetadataQueue();
279279

280+
void ShufflePlaylistActionTriggered(QAction *action);
281+
280282
public Q_SLOTS:
281283
void CommandlineOptionsReceived(const QByteArray &string_options);
282284
void Raise();
@@ -362,6 +364,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
362364
QAction *collection_show_untagged_;
363365

364366
QMenu *playlist_menu_;
367+
QMenu *shuffle_playlist_menu_;
365368
QAction *playlist_play_pause_;
366369
QAction *playlist_stop_after_;
367370
QAction *playlist_undoredo_;

src/core/mainwindow.ui

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -662,9 +662,6 @@
662662
<property name="text">
663663
<string>S&amp;huffle playlist</string>
664664
</property>
665-
<property name="shortcut">
666-
<string>Ctrl+H</string>
667-
</property>
668665
</action>
669666
<action name="action_add_file">
670667
<property name="text">
@@ -863,6 +860,33 @@
863860
<string>Import data from last.fm...</string>
864861
</property>
865862
</action>
863+
<action name="action_shuffle_playlist_all">
864+
<property name="checkable">
865+
<bool>false</bool>
866+
</property>
867+
<property name="text">
868+
<string>Single track as random element</string>
869+
</property>
870+
<property name="shortcut">
871+
<string>Ctrl+H</string>
872+
</property>
873+
</action>
874+
<action name="action_shuffle_playlist_albums">
875+
<property name="checkable">
876+
<bool>false</bool>
877+
</property>
878+
<property name="text">
879+
<string>Album as random element</string>
880+
</property>
881+
</action>
882+
<action name="action_shuffle_playlist_grouping">
883+
<property name="checkable">
884+
<bool>false</bool>
885+
</property>
886+
<property name="text">
887+
<string>Grouped tracks as random element</string>
888+
</property>
889+
</action>
866890
</widget>
867891
<layoutdefault spacing="6" margin="11"/>
868892
<customwidgets>

src/playlist/playlist.cpp

Lines changed: 102 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,29 +2077,47 @@ void Playlist::ReloadItems(const QList<int> &rows) {
20772077

20782078
}
20792079

2080-
void Playlist::Shuffle() {
2081-
2082-
PlaylistItemPtrList new_items(items_);
2080+
void Playlist::Shuffle(const PlaylistSequence::ShuffleMode shuffle_mode) {
20832081

2082+
QList<int> index_items;
20842083
int begin = 0;
2084+
int exclude_index = -1;
2085+
20852086
if (current_item_index_.isValid()) {
20862087
if (dynamic_playlist_) {
2087-
// Keep the history and the current track fixed; only shuffle the future region (mirrors sort()).
2088-
begin = current_item_index_.row() + 1;
2088+
begin += current_item_index_.row() + 1;
20892089
}
2090-
else {
2091-
if (new_items[0] != new_items[current_item_index_.row()]) {
2092-
std::swap(new_items[0], new_items[current_item_index_.row()]);
2093-
}
2094-
begin = 1;
2090+
else if (shuffle_mode == PlaylistSequence::ShuffleMode::All) {
2091+
// I keep the current track on the first place only when I shuffle all the tracks
2092+
exclude_index = current_item_index_.row();
20952093
}
20962094
}
20972095

20982096
const int count = static_cast<int>(items_.count());
20992097
for (int i = begin; i < count; ++i) {
2100-
const int new_pos = i + (rand() % (count - i));
2098+
if (exclude_index == i) {
2099+
continue;
2100+
}
2101+
index_items.push_back(i);
2102+
}
2103+
2104+
ReshuffleIndices(index_items, shuffle_mode, begin, true);
21012105

2102-
std::swap(new_items[i], new_items[new_pos]);
2106+
PlaylistItemPtrList new_items;
2107+
2108+
if (exclude_index >= 0) {
2109+
// Here I want to keep the current track fixed
2110+
new_items.push_back(items_[exclude_index]);
2111+
}
2112+
else if (begin > 0) {
2113+
// Here I want to keep the history and the current track fixed
2114+
for (int i = 0; i < begin; ++i) {
2115+
new_items.push_back(items_[i]);
2116+
}
2117+
}
2118+
// Then, I shuffle the remaining «future» tracks to read
2119+
for (int idx_shuffle : std::as_const(index_items)) {
2120+
new_items.push_back(items_[idx_shuffle]);
21032121
}
21042122

21052123
undo_stack_->push(new PlaylistUndoCommandShuffleItems(this, new_items));
@@ -2113,7 +2131,29 @@ bool AlbumShuffleComparator(const QHash<QString, int> &album_key_positions, cons
21132131
const int left_pos = album_key_positions[album_keys[left]];
21142132
const int right_pos = album_key_positions[album_keys[right]];
21152133

2134+
// With this return, I keep the tracks in the order they have in the playlist
21162135
if (left_pos == right_pos) return left < right;
2136+
2137+
// Sort the albums by their position in the playlist
2138+
return left_pos < right_pos;
2139+
2140+
}
2141+
2142+
// to the contrary of the function upper, I will keep the tracks in the order of their position in the album
2143+
// not with the position they have in the playlist
2144+
bool AlbumShuffleComparatorTrackOrder(const QHash<QString, int> &album_key_positions, const QHash<int, QString> &album_keys, const PlaylistItemPtrList &items, const int left, const int right) {
2145+
2146+
const int left_pos = album_key_positions[album_keys[left]];
2147+
const int right_pos = album_key_positions[album_keys[right]];
2148+
2149+
if (left_pos == right_pos) {
2150+
auto &&left_song = items[left]->EffectiveMetadata();
2151+
auto &&right_song = items[right]->EffectiveMetadata();
2152+
2153+
if (left_song.disc() == right_song.disc()) return left_song.track() < right_song.track();
2154+
2155+
return left_song.disc() < right_song.disc();
2156+
}
21172157
return left_pos < right_pos;
21182158

21192159
}
@@ -2122,18 +2162,38 @@ bool AlbumShuffleComparator(const QHash<QString, int> &album_key_positions, cons
21222162

21232163
void Playlist::ReshuffleIndices() {
21242164

2125-
const PlaylistSequence::ShuffleMode shuffle_mode = ShuffleMode();
2165+
// First, cancel the replay position
2166+
// TODO : uncomment the code below when the branch «Grouped tracks are considered as single track» will be merged
2167+
// next_song_after_queued_ = -1;
2168+
2169+
current_virtual_index_ = ReshuffleIndices(virtual_items_, ShuffleMode(), 0, false);
2170+
2171+
}
2172+
2173+
int Playlist::ReshuffleIndices(QList<int>& virtual_items, const PlaylistSequence::ShuffleMode shuffle_mode, const int base_reference, const bool album_keep_track_order) {
2174+
2175+
static std::mt19937 rng{std::random_device{}()};
2176+
21262177
switch (shuffle_mode) {
21272178
case PlaylistSequence::ShuffleMode::Off:{
21282179
// No shuffling - sort the virtual item list normally.
2129-
std::sort(virtual_items_.begin(), virtual_items_.end());
2180+
std::sort(virtual_items.begin(), virtual_items.end());
21302181
break;
21312182
}
21322183

21332184
case PlaylistSequence::ShuffleMode::All:
21342185
case PlaylistSequence::ShuffleMode::InsideAlbum:{
2135-
std::random_device rd;
2136-
std::shuffle(virtual_items_.begin(), virtual_items_.end(), std::mt19937(rd()));
2186+
std::shuffle(virtual_items.begin(), virtual_items.end(), rng);
2187+
2188+
// If the user is currently playing a song, force its track to be first
2189+
// Also check last_played_row() for cases where current_row() hasn't been set yet (e.g., on app startup)
2190+
int reference_row = current_row();
2191+
if (reference_row == -1 && last_played_row() != -1) {
2192+
reference_row = last_played_row();
2193+
}
2194+
if (reference_row > base_reference) {
2195+
std::swap(virtual_items[0], virtual_items[reference_row - base_reference]);
2196+
}
21372197
break;
21382198
}
21392199

@@ -2142,15 +2202,14 @@ void Playlist::ReshuffleIndices() {
21422202
QSet<QString> album_key_set; // unique keys
21432203

21442204
// Find all the unique albums in the playlist
2145-
for (QList<int>::const_iterator it = virtual_items_.constBegin(); it != virtual_items_.constEnd(); ++it) {
2205+
for (QList<int>::const_iterator it = virtual_items.constBegin(); it != virtual_items.constEnd(); ++it) {
21462206
const int index = *it;
21472207
const QString key = items_[index]->EffectiveMetadata().AlbumKey();
21482208
album_keys[index] = key;
21492209
album_key_set << key;
21502210
}
21512211

21522212
// Shuffle them
2153-
static std::mt19937 rng{std::random_device{}()};
21542213
QStringList shuffled_album_keys = album_key_set.values();
21552214
std::shuffle(shuffled_album_keys.begin(), shuffled_album_keys.end(), rng);
21562215

@@ -2163,8 +2222,8 @@ void Playlist::ReshuffleIndices() {
21632222
if (reference_row != -1) {
21642223
const QString key = items_[reference_row]->EffectiveMetadata().AlbumKey();
21652224
const qint64 pos = shuffled_album_keys.indexOf(key);
2166-
if (pos >= 1) {
2167-
std::swap(shuffled_album_keys[0], shuffled_album_keys[pos]);
2225+
if (pos > base_reference) {
2226+
std::swap(shuffled_album_keys[0], shuffled_album_keys[pos - base_reference]);
21682227
}
21692228
}
21702229

@@ -2174,8 +2233,18 @@ void Playlist::ReshuffleIndices() {
21742233
album_key_positions[shuffled_album_keys[i]] = i;
21752234
}
21762235

2236+
if (album_keep_track_order) {
2237+
// Sort the virtual items : use AlbumShuffleComparator with a cheap-to-copy lambda comparator
2238+
// force the track order to be the album one
2239+
std::stable_sort(virtual_items.begin(), virtual_items.end(), [&album_key_positions, &album_keys, this](const int lhs, const int rhs) {
2240+
return AlbumShuffleComparatorTrackOrder(album_key_positions, album_keys, items_, lhs, rhs);
2241+
});
2242+
2243+
break;
2244+
}
21772245
// Sort the virtual items : use AlbumShuffleComparator with a cheap-to-copy lambda comparator
2178-
std::stable_sort(virtual_items_.begin(), virtual_items_.end(), [&album_key_positions, &album_keys](const int lhs, const int rhs) {
2246+
// keep the track order they have in the playlist
2247+
std::stable_sort(virtual_items.begin(), virtual_items.end(), [&album_key_positions, &album_keys](const int lhs, const int rhs) {
21792248
return AlbumShuffleComparator(album_key_positions, album_keys, lhs, rhs);
21802249
});
21812250

@@ -2186,15 +2255,14 @@ void Playlist::ReshuffleIndices() {
21862255
QSet<QString> grouping_key_set; // unique keys
21872256

21882257
// Find all the unique grouping keys in the playlist
2189-
for (QList<int>::const_iterator it = virtual_items_.constBegin(); it != virtual_items_.constEnd(); ++it) {
2258+
for (QList<int>::const_iterator it = virtual_items.constBegin(); it != virtual_items.constEnd(); ++it) {
21902259
const int index = *it;
21912260
const QString key = items_[index]->EffectiveMetadata().GroupingKey();
21922261
grouping_keys[index] = key;
21932262
grouping_key_set << key;
21942263
}
21952264

21962265
// Shuffle them
2197-
static std::mt19937 rng{std::random_device{}()};
21982266
QStringList shuffled_grouping_keys = grouping_key_set.values();
21992267
std::shuffle(shuffled_grouping_keys.begin(), shuffled_grouping_keys.end(), rng);
22002268

@@ -2207,8 +2275,8 @@ void Playlist::ReshuffleIndices() {
22072275
if (reference_row != -1) {
22082276
const QString key = items_[reference_row]->EffectiveMetadata().GroupingKey();
22092277
const qint64 pos = shuffled_grouping_keys.indexOf(key);
2210-
if (pos >= 1) {
2211-
std::swap(shuffled_grouping_keys[0], shuffled_grouping_keys[pos]);
2278+
if (pos > base_reference) {
2279+
std::swap(shuffled_grouping_keys[0], shuffled_grouping_keys[pos - base_reference]);
22122280
}
22132281
}
22142282

@@ -2218,22 +2286,23 @@ void Playlist::ReshuffleIndices() {
22182286
grouping_key_positions[shuffled_grouping_keys[i]] = i;
22192287
}
22202288

2221-
// Sort the virtual items: use AlbumShuffleComparator as a grouping-key comparator with a cheap-to-copy lambda
2222-
std::stable_sort(virtual_items_.begin(), virtual_items_.end(), [&grouping_key_positions, &grouping_keys](const int lhs, const int rhs) {
2223-
return AlbumShuffleComparator(grouping_key_positions, grouping_keys, lhs, rhs);
2289+
// Sort the virtual items: use AlbumShuffleComparatorTrackOrder as a grouping-key comparator with a cheap-to-copy lambda
2290+
// This sort method will keep the track number order so important in the grouped read
2291+
std::stable_sort(virtual_items.begin(), virtual_items.end(), [&grouping_key_positions, &grouping_keys, this](const int lhs, const int rhs) {
2292+
return AlbumShuffleComparatorTrackOrder(grouping_key_positions, grouping_keys, items_, lhs, rhs);
22242293
});
22252294
break;
22262295
}
22272296
}
22282297

2229-
// Update current virtual index
2298+
// I keep the computation of the virtual index because it is only return, if you want to ignore it, you can
2299+
// besides, it will be usefull in the cases I want to use the updated method
22302300
if (current_item_index_.isValid()) {
2231-
current_virtual_index_ = static_cast<int>(virtual_items_.indexOf(current_item_index_.row()));
2232-
}
2233-
else {
2234-
current_virtual_index_ = -1;
2301+
return static_cast<int>(virtual_items.indexOf(current_item_index_.row()));
22352302
}
22362303

2304+
return -1;
2305+
22372306
}
22382307

22392308
void Playlist::set_sequence(PlaylistSequence *v) {

src/playlist/playlist.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ class Playlist : public QAbstractListModel {
296296

297297
void ItemReload(const QPersistentModelIndex &idx, const bool metadata_edit);
298298

299+
void Shuffle(const PlaylistSequence::ShuffleMode shuffle_mode = PlaylistSequence::ShuffleMode::All);
300+
299301
public Q_SLOTS:
300302
void set_current_row(const int i, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll::Maybe, const bool is_stopping = false, const bool force_inform = false);
301303
void Paused();
@@ -309,7 +311,6 @@ class Playlist : public QAbstractListModel {
309311
void Clear();
310312
void RemoveDuplicateSongs();
311313
void RemoveUnavailableSongs();
312-
void Shuffle();
313314

314315
void ShuffleModeChanged(const PlaylistSequence::ShuffleMode shuffle_mode);
315316

@@ -366,6 +367,8 @@ class Playlist : public QAbstractListModel {
366367
void MoveItemsWithoutUndo(int start, const QList<int> &dest_rows);
367368
void ReOrderWithoutUndo(const PlaylistItemPtrList &new_items);
368369

370+
int ReshuffleIndices(QList<int>& virtual_items, const PlaylistSequence::ShuffleMode shuffle_mode, const int base_reference, const bool album_keep_track_order);
371+
369372
void RemoveItemsNotInQueue();
370373

371374
// Removes rows with given indices from this playlist.

src/playlist/playlistmanager.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ void PlaylistManager::ShuffleCurrent() {
413413
current()->Shuffle();
414414
}
415415

416+
void PlaylistManager::ShuffleCurrent(const PlaylistSequence::ShuffleMode shuffle_mode) {
417+
current()->Shuffle(shuffle_mode);
418+
}
419+
416420
void PlaylistManager::RemoveDuplicatesCurrent() {
417421
current()->RemoveDuplicateSongs();
418422
}

0 commit comments

Comments
 (0)