From 0bfc2ee198a861bf45de13c432eb311e084c5440 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 26 Jul 2025 12:44:20 +0200 Subject: [PATCH] Add sort columns to playlists Increment playlist state version from 1 to 2 to get sort columns next to their "original" column. Discard old stored playlist state in config file. --- src/playlist/playlist.cpp | 60 ++++++++++++++++++++++++++++++ src/playlist/playlist.h | 6 +++ src/playlist/playlistdelegates.cpp | 20 ++++++---- src/playlist/playlistview.cpp | 43 +++++++++++++++------ src/playlist/playlistview.h | 1 - 5 files changed, 111 insertions(+), 19 deletions(-) diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 30cd6bd81..039f3c01c 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -219,11 +219,17 @@ bool Playlist::column_is_editable(const Playlist::Column column) { switch (column) { case Column::Title: + case Column::TitleSort: case Column::Artist: + case Column::ArtistSort: case Column::Album: + case Column::AlbumSort: case Column::AlbumArtist: + case Column::AlbumArtistSort: case Column::Composer: + case Column::ComposerSort: case Column::Performer: + case Column::PerformerSort: case Column::Grouping: case Column::Track: case Column::Disc: @@ -247,21 +253,39 @@ bool Playlist::set_column_value(Song &song, const Playlist::Column column, const case Column::Title: song.set_title(value.toString()); break; + case Column::TitleSort: + song.set_titlesort(value.toString()); + break; case Column::Artist: song.set_artist(value.toString()); break; + case Column::ArtistSort: + song.set_artistsort(value.toString()); + break; case Column::Album: song.set_album(value.toString()); break; + case Column::AlbumSort: + song.set_albumsort(value.toString()); + break; case Column::AlbumArtist: song.set_albumartist(value.toString()); break; + case Column::AlbumArtistSort: + song.set_albumartistsort(value.toString()); + break; case Column::Composer: song.set_composer(value.toString()); break; + case Column::ComposerSort: + song.set_composersort(value.toString()); + break; case Column::Performer: song.set_performer(value.toString()); break; + case Column::PerformerSort: + song.set_performersort(value.toString()); + break; case Column::Grouping: song.set_grouping(value.toString()); break; @@ -319,8 +343,11 @@ QVariant Playlist::data(const QModelIndex &idx, const int role) const { // Don't forget to change Playlist::CompareItems when adding new columns switch (static_cast(idx.column())) { case Column::Title: return song.PrettyTitle(); + case Column::TitleSort: return song.titlesort(); case Column::Artist: return song.artist(); + case Column::ArtistSort: return song.artistsort(); case Column::Album: return song.album(); + case Column::AlbumSort: return song.albumsort(); case Column::Length: return song.length_nanosec(); case Column::Track: return song.track(); case Column::Disc: return song.disc(); @@ -328,8 +355,11 @@ QVariant Playlist::data(const QModelIndex &idx, const int role) const { case Column::OriginalYear: return song.effective_originalyear(); case Column::Genre: return song.genre(); case Column::AlbumArtist: return song.playlist_albumartist(); + case Column::AlbumArtistSort: return song.albumartistsort(); case Column::Composer: return song.composer(); + case Column::ComposerSort: return song.composersort(); case Column::Performer: return song.performer(); + case Column::PerformerSort: return song.performersort(); case Column::Grouping: return song.grouping(); case Column::PlayCount: return song.playcount(); @@ -1326,8 +1356,11 @@ bool Playlist::CompareItems(const Column column, const Qt::SortOrder order, Play switch (column) { case Column::Title: strcmp(title_sortable); + case Column::TitleSort: strcmp(titlesort); case Column::Artist: strcmp(artist_sortable); + case Column::ArtistSort: strcmp(artistsort); case Column::Album: strcmp(album_sortable); + case Column::AlbumSort: strcmp(albumsort); case Column::Length: cmp(length_nanosec); case Column::Track: cmp(track); case Column::Disc: cmp(disc); @@ -1335,8 +1368,11 @@ bool Playlist::CompareItems(const Column column, const Qt::SortOrder order, Play case Column::OriginalYear: cmp(effective_originalyear); case Column::Genre: strcmp(genre); case Column::AlbumArtist: strcmp(playlist_albumartist_sortable); + case Column::AlbumArtistSort: strcmp(albumartistsort); case Column::Composer: strcmp(composer); + case Column::ComposerSort: strcmp(composersort); case Column::Performer: strcmp(performer); + case Column::PerformerSort: strcmp(performersort); case Column::Grouping: strcmp(grouping); case Column::PlayCount: cmp(playcount); @@ -1380,8 +1416,11 @@ QString Playlist::column_name(const Column column) { switch (column) { case Column::Title: return tr("Title"); + case Column::TitleSort: return tr("Title Sort"); case Column::Artist: return tr("Artist"); + case Column::ArtistSort: return tr("Artist Sort"); case Column::Album: return tr("Album"); + case Column::AlbumSort: return tr("Album Sort"); case Column::Track: return tr("Track"); case Column::Disc: return tr("Disc"); case Column::Length: return tr("Length"); @@ -1389,8 +1428,11 @@ QString Playlist::column_name(const Column column) { case Column::OriginalYear: return tr("Original Year"); case Column::Genre: return tr("Genre"); case Column::AlbumArtist: return tr("Album Artist"); + case Column::AlbumArtistSort: return tr("Album Artist Sort"); case Column::Composer: return tr("Composer"); + case Column::ComposerSort: return tr("Composer Sort"); case Column::Performer: return tr("Performer"); + case Column::PerformerSort: return tr("Performer Sort"); case Column::Grouping: return tr("Grouping"); case Column::PlayCount: return tr("Play Count"); @@ -2109,21 +2151,39 @@ Playlist::Columns Playlist::ChangedColumns(const Song &metadata1, const Song &me if (metadata1.title() != metadata2.title()) { columns << Column::Title; } + if (metadata1.titlesort() != metadata2.titlesort()) { + columns << Column::TitleSort; + } if (metadata1.artist() != metadata2.artist()) { columns << Column::Artist; } + if (metadata1.artistsort() != metadata2.artistsort()) { + columns << Column::ArtistSort; + } if (metadata1.album() != metadata2.album()) { columns << Column::Album; } + if (metadata1.albumsort() != metadata2.albumsort()) { + columns << Column::AlbumSort; + } if (metadata1.effective_albumartist() != metadata2.effective_albumartist()) { columns << Column::AlbumArtist; } + if (metadata1.albumartistsort() != metadata2.albumartistsort()) { + columns << Column::AlbumArtistSort; + } if (metadata1.performer() != metadata2.performer()) { columns << Column::Performer; } + if (metadata1.performersort() != metadata2.performersort()) { + columns << Column::PerformerSort; + } if (metadata1.composer() != metadata2.composer()) { columns << Column::Composer; } + if (metadata1.composersort() != metadata2.composersort()) { + columns << Column::ComposerSort; + } if (metadata1.year() != metadata2.year()) { columns << Column::Year; } diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h index 7b1e1cc48..91619a77d 100644 --- a/src/playlist/playlist.h +++ b/src/playlist/playlist.h @@ -101,11 +101,17 @@ class Playlist : public QAbstractListModel { // Always add new columns to the end of this enum - the values are persisted enum class Column { Title = 0, + TitleSort, Artist, + ArtistSort, Album, + AlbumSort, AlbumArtist, + AlbumArtistSort, Performer, + PerformerSort, Composer, + ComposerSort, Year, OriginalYear, Track, diff --git a/src/playlist/playlistdelegates.cpp b/src/playlist/playlistdelegates.cpp index 61d24564d..98c7c1af6 100644 --- a/src/playlist/playlistdelegates.cpp +++ b/src/playlist/playlistdelegates.cpp @@ -387,13 +387,19 @@ TagCompletionModel::TagCompletionModel(SharedPtr backend, con QString TagCompletionModel::database_column(const Playlist::Column column) { switch (column) { - case Playlist::Column::Artist: return u"artist"_s; - case Playlist::Column::Album: return u"album"_s; - case Playlist::Column::AlbumArtist: return u"albumartist"_s; - case Playlist::Column::Composer: return u"composer"_s; - case Playlist::Column::Performer: return u"performer"_s; - case Playlist::Column::Grouping: return u"grouping"_s; - case Playlist::Column::Genre: return u"genre"_s; + case Playlist::Column::Artist: return u"artist"_s; + case Playlist::Column::ArtistSort: return u"artistsort"_s; + case Playlist::Column::Album: return u"album"_s; + case Playlist::Column::AlbumSort: return u"albumsort"_s; + case Playlist::Column::AlbumArtist: return u"albumartist"_s; + case Playlist::Column::AlbumArtistSort: return u"albumartistsort"_s; + case Playlist::Column::Composer: return u"composer"_s; + case Playlist::Column::ComposerSort: return u"composersort"_s; + case Playlist::Column::Performer: return u"performer"_s; + case Playlist::Column::PerformerSort: return u"performersort"_s; + case Playlist::Column::Grouping: return u"grouping"_s; + case Playlist::Column::Genre: return u"genre"_s; + case Playlist::Column::TitleSort: return u"titlesort"_s; default: qLog(Warning) << "Unknown column" << static_cast(column); return QString(); diff --git a/src/playlist/playlistview.cpp b/src/playlist/playlistview.cpp index 9035d23cb..9557d57f3 100644 --- a/src/playlist/playlistview.cpp +++ b/src/playlist/playlistview.cpp @@ -87,6 +87,7 @@ constexpr int kGlowIntensitySteps = 24; constexpr int kAutoscrollGraceTimeout = 30; // seconds constexpr int kDropIndicatorWidth = 2; constexpr int kDropIndicatorGradientWidth = 5; +constexpr int kHeaderStateVersion = 2; } // namespace PlaylistView::PlaylistView(QWidget *parent) @@ -131,7 +132,6 @@ PlaylistView::PlaylistView(QWidget *parent) cached_current_row_row_(-1), drop_indicator_row_(-1), drag_over_(false), - header_state_version_(1), column_alignment_(DefaultColumnAlignment()), rating_locked_(false), dynamic_controls_(new DynamicPlaylistControls(this)), @@ -217,12 +217,18 @@ void PlaylistView::SetItemDelegates() { setItemDelegate(new PlaylistDelegateBase(this)); setItemDelegateForColumn(static_cast(Playlist::Column::Title), new TextItemDelegate(this)); + setItemDelegateForColumn(static_cast(Playlist::Column::TitleSort), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::TitleSort)); setItemDelegateForColumn(static_cast(Playlist::Column::Album), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::Album)); + setItemDelegateForColumn(static_cast(Playlist::Column::AlbumSort), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::AlbumSort)); setItemDelegateForColumn(static_cast(Playlist::Column::Artist), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::Artist)); + setItemDelegateForColumn(static_cast(Playlist::Column::ArtistSort), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::ArtistSort)); setItemDelegateForColumn(static_cast(Playlist::Column::AlbumArtist), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::AlbumArtist)); + setItemDelegateForColumn(static_cast(Playlist::Column::AlbumArtistSort), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::AlbumArtistSort)); setItemDelegateForColumn(static_cast(Playlist::Column::Genre), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::Genre)); setItemDelegateForColumn(static_cast(Playlist::Column::Composer), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::Composer)); + setItemDelegateForColumn(static_cast(Playlist::Column::ComposerSort), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::ComposerSort)); setItemDelegateForColumn(static_cast(Playlist::Column::Performer), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::Performer)); + setItemDelegateForColumn(static_cast(Playlist::Column::PerformerSort), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::PerformerSort)); setItemDelegateForColumn(static_cast(Playlist::Column::Grouping), new TagCompletionItemDelegate(this, collection_backend_, Playlist::Column::Grouping)); setItemDelegateForColumn(static_cast(Playlist::Column::Length), new LengthItemDelegate(this)); setItemDelegateForColumn(static_cast(Playlist::Column::Filesize), new SizeItemDelegate(this)); @@ -304,11 +310,26 @@ void PlaylistView::LoadHeaderState() { Settings s; s.beginGroup(PlaylistSettings::kSettingsGroup); + // Since we use serialized internal data structures, we cannot read anything but the current version + const int header_state_version = s.value(PlaylistSettings::kStateVersion, 0).toInt(); if (s.contains(PlaylistSettings::kState)) { - header_state_version_ = s.value(PlaylistSettings::kStateVersion, 0).toInt(); - header_state_ = s.value(PlaylistSettings::kState).toByteArray(); + if (header_state_version == kHeaderStateVersion) { + header_state_ = s.value(PlaylistSettings::kState).toByteArray(); + } + else { + // Force header state reset since column indices may have changed between versions + header_state_.clear(); + } + } + if (s.contains(PlaylistSettings::kColumnAlignments)) { + if (header_state_version == kHeaderStateVersion) { + column_alignment_ = s.value(PlaylistSettings::kColumnAlignments).value(); + } + else { + // Force column alignment reset since column indices may have changed between versions + column_alignment_.clear(); + } } - if (s.contains(PlaylistSettings::kColumnAlignments)) column_alignment_ = s.value(PlaylistSettings::kColumnAlignments).value(); s.endGroup(); if (column_alignment_.isEmpty()) { @@ -329,7 +350,6 @@ void PlaylistView::SetHeaderState() { void PlaylistView::ResetHeaderState() { set_initial_header_layout_ = true; - header_state_version_ = 1; header_state_ = header_->ResetState(); RestoreHeaderState(); @@ -347,9 +367,15 @@ void PlaylistView::RestoreHeaderState() { header_->SetStretchEnabled(true); + header_->HideSection(static_cast(Playlist::Column::TitleSort)); + header_->HideSection(static_cast(Playlist::Column::ArtistSort)); + header_->HideSection(static_cast(Playlist::Column::AlbumSort)); header_->HideSection(static_cast(Playlist::Column::AlbumArtist)); + header_->HideSection(static_cast(Playlist::Column::AlbumArtistSort)); header_->HideSection(static_cast(Playlist::Column::Performer)); + header_->HideSection(static_cast(Playlist::Column::PerformerSort)); header_->HideSection(static_cast(Playlist::Column::Composer)); + header_->HideSection(static_cast(Playlist::Column::ComposerSort)); header_->HideSection(static_cast(Playlist::Column::Year)); header_->HideSection(static_cast(Playlist::Column::OriginalYear)); header_->HideSection(static_cast(Playlist::Column::Disc)); @@ -400,11 +426,6 @@ void PlaylistView::RestoreHeaderState() { } - if (header_state_version_ < 1) { - header_->HideSection(static_cast(Playlist::Column::Rating)); - header_state_version_ = 1; - } - // Make sure at least one column is visible bool all_hidden = true; for (int i = 0; i < header_->count(); ++i) { @@ -1305,7 +1326,7 @@ void PlaylistView::SaveSettings() { Settings s; s.beginGroup(PlaylistSettings::kSettingsGroup); - s.setValue(PlaylistSettings::kStateVersion, header_state_version_); + s.setValue(PlaylistSettings::kStateVersion, kHeaderStateVersion); s.setValue(PlaylistSettings::kState, header_->SaveState()); s.setValue(PlaylistSettings::kColumnAlignments, QVariant::fromValue(column_alignment_)); s.setValue(PlaylistSettings::kRatingLocked, rating_locked_); diff --git a/src/playlist/playlistview.h b/src/playlist/playlistview.h index 850178a3a..601959c15 100644 --- a/src/playlist/playlistview.h +++ b/src/playlist/playlistview.h @@ -284,7 +284,6 @@ class PlaylistView : public QTreeView { int drop_indicator_row_; bool drag_over_; - int header_state_version_; QByteArray header_state_; ColumnAlignmentMap column_alignment_; bool rating_locked_;