diff --git a/src/filterparser/filtercolumn.h b/src/filterparser/filtercolumn.h new file mode 100644 index 000000000..fc7e82432 --- /dev/null +++ b/src/filterparser/filtercolumn.h @@ -0,0 +1,53 @@ +/* + * Strawberry Music Player + * Copyright 2026, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef FILTERCOLUMN_H +#define FILTERCOLUMN_H + +enum class FilterColumn { + Unknown, + Title, + TitleSort, + Album, + AlbumSort, + Artist, + ArtistSort, + AlbumArtist, + AlbumArtistSort, + Composer, + ComposerSort, + Performer, + PerformerSort, + Grouping, + Genre, + Comment, + Filename, + URL, + Track, + Year, + Samplerate, + Bitdepth, + Bitrate, + Playcount, + Skipcount, + Length, + Rating, +}; + +#endif // FILTERCOLUMN_H diff --git a/src/filterparser/filterparser.cpp b/src/filterparser/filterparser.cpp index 8b634f597..9592a5653 100644 --- a/src/filterparser/filterparser.cpp +++ b/src/filterparser/filterparser.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2012, David Sansome - * Copyright 2018-2024, Jonas Kvinge + * Copyright 2018-2026, Jonas Kvinge * Copyright 2023, Daniel Ostertag * * Strawberry is free software: you can redistribute it and/or modify @@ -33,11 +33,41 @@ #include "filtertreeterm.h" #include "filtertreecolumnterm.h" #include "filterparsersearchcomparators.h" +#include "filtercolumn.h" using namespace Qt::Literals::StringLiterals; namespace { +enum class FilterOperator { + None, + Eq, + Ne, + Gt, + Ge, + Lt, + Le +}; + +const QMap &GetFilterOperatorsMap() { + + static const QMap filter_operators_map_ = []() { + QMap filter_operators_map; + filter_operators_map.insert(u"="_s, FilterOperator::Eq); + filter_operators_map.insert(u"=="_s, FilterOperator::Eq); + filter_operators_map.insert(u"!="_s, FilterOperator::Ne); + filter_operators_map.insert(u"<>"_s, FilterOperator::Ne); + filter_operators_map.insert(u">"_s, FilterOperator::Gt); + filter_operators_map.insert(u">="_s, FilterOperator::Ge); + filter_operators_map.insert(u"<"_s, FilterOperator::Lt); + filter_operators_map.insert(u"<="_s, FilterOperator::Le); + return filter_operators_map; + }(); + + return filter_operators_map_; + +} + enum class ColumnType { Unknown, Text, @@ -47,32 +77,78 @@ enum class ColumnType { Float }; -const QMap &GetColumnTypeMap() { - static const QMap column_types = []() { - QMap map; - map.insert(u"title"_s, ColumnType::Text); - map.insert(u"album"_s, ColumnType::Text); - map.insert(u"artist"_s, ColumnType::Text); - map.insert(u"albumartist"_s, ColumnType::Text); - map.insert(u"composer"_s, ColumnType::Text); - map.insert(u"performer"_s, ColumnType::Text); - map.insert(u"grouping"_s, ColumnType::Text); - map.insert(u"genre"_s, ColumnType::Text); - map.insert(u"comment"_s, ColumnType::Text); - map.insert(u"filename"_s, ColumnType::Text); - map.insert(u"url"_s, ColumnType::Text); - map.insert(u"track"_s, ColumnType::Int); - map.insert(u"year"_s, ColumnType::Int); - map.insert(u"samplerate"_s, ColumnType::Int); - map.insert(u"bitdepth"_s, ColumnType::Int); - map.insert(u"bitrate"_s, ColumnType::Int); - map.insert(u"playcount"_s, ColumnType::UInt); - map.insert(u"skipcount"_s, ColumnType::UInt); - map.insert(u"length"_s, ColumnType::Int64); - map.insert(u"rating"_s, ColumnType::Float); - return map; +const QMap &GetFilterColumnsMap() { + + static const QMap filter_columns_map_ = []() { + QMap filter_columns_map; + filter_columns_map.insert(u"albumartist"_s, FilterColumn::AlbumArtist); + filter_columns_map.insert(u"albumartistsort"_s, FilterColumn::AlbumArtistSort); + filter_columns_map.insert(u"artist"_s, FilterColumn::Artist); + filter_columns_map.insert(u"artistsort"_s, FilterColumn::ArtistSort); + filter_columns_map.insert(u"album"_s, FilterColumn::Album); + filter_columns_map.insert(u"albumsort"_s, FilterColumn::AlbumSort); + filter_columns_map.insert(u"title"_s, FilterColumn::Title); + filter_columns_map.insert(u"titlesort"_s, FilterColumn::TitleSort); + filter_columns_map.insert(u"composer"_s, FilterColumn::Composer); + filter_columns_map.insert(u"composersort"_s, FilterColumn::ComposerSort); + filter_columns_map.insert(u"performer"_s, FilterColumn::Performer); + filter_columns_map.insert(u"performersort"_s, FilterColumn::PerformerSort); + filter_columns_map.insert(u"grouping"_s, FilterColumn::Grouping); + filter_columns_map.insert(u"genre"_s, FilterColumn::Genre); + filter_columns_map.insert(u"comment"_s, FilterColumn::Comment); + filter_columns_map.insert(u"filename"_s, FilterColumn::Filename); + filter_columns_map.insert(u"url"_s, FilterColumn::URL); + filter_columns_map.insert(u"track"_s, FilterColumn::Track); + filter_columns_map.insert(u"year"_s, FilterColumn::Year); + filter_columns_map.insert(u"samplerate"_s, FilterColumn::Samplerate); + filter_columns_map.insert(u"bitdepth"_s, FilterColumn::Bitdepth); + filter_columns_map.insert(u"bitrate"_s, FilterColumn::Bitrate); + filter_columns_map.insert(u"playcount"_s, FilterColumn::Playcount); + filter_columns_map.insert(u"skipcount"_s, FilterColumn::Skipcount); + filter_columns_map.insert(u"length"_s, FilterColumn::Length); + filter_columns_map.insert(u"rating"_s, FilterColumn::Rating); + return filter_columns_map; }(); - return column_types; + + return filter_columns_map_; + +} + +const QMap &GetColumnTypesMap() { + + static const QMap column_types_map_ = []() { + QMap column_types_map; + column_types_map.insert(FilterColumn::AlbumArtist, ColumnType::Text); + column_types_map.insert(FilterColumn::AlbumArtistSort, ColumnType::Text); + column_types_map.insert(FilterColumn::Artist, ColumnType::Text); + column_types_map.insert(FilterColumn::ArtistSort, ColumnType::Text); + column_types_map.insert(FilterColumn::Album, ColumnType::Text); + column_types_map.insert(FilterColumn::AlbumSort, ColumnType::Text); + column_types_map.insert(FilterColumn::Title, ColumnType::Text); + column_types_map.insert(FilterColumn::TitleSort, ColumnType::Text); + column_types_map.insert(FilterColumn::Composer, ColumnType::Text); + column_types_map.insert(FilterColumn::ComposerSort, ColumnType::Text); + column_types_map.insert(FilterColumn::Performer, ColumnType::Text); + column_types_map.insert(FilterColumn::PerformerSort, ColumnType::Text); + column_types_map.insert(FilterColumn::Grouping, ColumnType::Text); + column_types_map.insert(FilterColumn::Genre, ColumnType::Text); + column_types_map.insert(FilterColumn::Comment, ColumnType::Text); + column_types_map.insert(FilterColumn::Filename, ColumnType::Text); + column_types_map.insert(FilterColumn::URL, ColumnType::Text); + column_types_map.insert(FilterColumn::Track, ColumnType::Int); + column_types_map.insert(FilterColumn::Year, ColumnType::Int); + column_types_map.insert(FilterColumn::Samplerate, ColumnType::Int); + column_types_map.insert(FilterColumn::Bitdepth, ColumnType::Int); + column_types_map.insert(FilterColumn::Bitrate, ColumnType::Int); + column_types_map.insert(FilterColumn::Playcount, ColumnType::UInt); + column_types_map.insert(FilterColumn::Skipcount, ColumnType::UInt); + column_types_map.insert(FilterColumn::Length, ColumnType::Int64); + column_types_map.insert(FilterColumn::Rating, ColumnType::Float); + return column_types_map; + }(); + + return column_types_map_; + } } // namespace @@ -298,133 +374,135 @@ FilterTree *FilterParser::createSearchTermTreeNode(const QString &column, const return new FilterTreeNop; } + FilterColumn filter_column = FilterColumn::Unknown; FilterParserSearchTermComparator *cmp = nullptr; if (!column.isEmpty()) { - const ColumnType column_type = GetColumnTypeMap().value(column, ColumnType::Unknown); - + filter_column = GetFilterColumnsMap().value(column, FilterColumn::Unknown); + const ColumnType column_type = GetColumnTypesMap().value(filter_column, ColumnType::Unknown); + const FilterOperator filter_operator = GetFilterOperatorsMap().value(prefix, FilterOperator::None); switch (column_type) { - case ColumnType::Text: { - if ((prefix.size() == 1 && prefix[0] == u'=') || prefix == "=="_L1) { - cmp = new FilterParserTextEqComparator(value); - } - else if (prefix == "!="_L1 || prefix == "<>"_L1) { - cmp = new FilterParserTextNeComparator(value); - } - else { - cmp = new FilterParserTextContainsComparator(value); + case ColumnType::Text:{ + switch (filter_operator) { + case FilterOperator::Eq: + cmp = new FilterParserTextEqComparator(value); + break; + case FilterOperator::Ne: + cmp = new FilterParserTextNeComparator(value); + break; + default: + cmp = new FilterParserTextContainsComparator(value); + break; } break; } - case ColumnType::Int: { + case ColumnType::Int:{ bool ok = false; - int number = value.toInt(&ok); - if (ok) { - if ((prefix.size() == 1 && prefix[0] == u'=') || prefix == "=="_L1) { + const int number = value.toInt(&ok); + if (!ok) break; + switch (filter_operator) { + case FilterOperator::None: + case FilterOperator::Eq: cmp = new FilterParserIntEqComparator(number); - } - else if (prefix == "!="_L1 || prefix == "<>"_L1) { + break; + case FilterOperator::Ne: cmp = new FilterParserIntNeComparator(number); - } - else if (prefix.size() == 1 && prefix[0] == u'>') { + break; + case FilterOperator::Gt: cmp = new FilterParserIntGtComparator(number); - } - else if (prefix == ">="_L1) { + break; + case FilterOperator::Ge: cmp = new FilterParserIntGeComparator(number); - } - else if (prefix.size() == 1 && prefix[0] == u'<') { + break; + case FilterOperator::Lt: cmp = new FilterParserIntLtComparator(number); - } - else if (prefix == "<="_L1) { + break; + case FilterOperator::Le: cmp = new FilterParserIntLeComparator(number); - } - else { - cmp = new FilterParserIntEqComparator(number); - } + break; } break; } - case ColumnType::UInt: { + case ColumnType::UInt:{ bool ok = false; - uint number = value.toUInt(&ok); - if (ok) { - if ((prefix.size() == 1 && prefix[0] == u'=') || prefix == "=="_L1) { + const uint number = value.toUInt(&ok); + if (!ok) break; + switch (filter_operator) { + case FilterOperator::None: + case FilterOperator::Eq: cmp = new FilterParserUIntEqComparator(number); - } - else if (prefix == "!="_L1 || prefix == "<>"_L1) { + break; + case FilterOperator::Ne: cmp = new FilterParserUIntNeComparator(number); - } - else if (prefix.size() == 1 && prefix[0] == u'>') { + break; + case FilterOperator::Gt: cmp = new FilterParserUIntGtComparator(number); - } - else if (prefix == ">="_L1) { + break; + case FilterOperator::Ge: cmp = new FilterParserUIntGeComparator(number); - } - else if (prefix.size() == 1 && prefix[0] == u'<') { + break; + case FilterOperator::Lt: cmp = new FilterParserUIntLtComparator(number); - } - else if (prefix == "<="_L1) { + break; + case FilterOperator::Le: cmp = new FilterParserUIntLeComparator(number); - } - else { - cmp = new FilterParserUIntEqComparator(number); - } + break; } break; } - case ColumnType::Int64: { + case ColumnType::Int64:{ qint64 number = 0; - if (column == "length"_L1) { + if (filter_column == FilterColumn::Length) { number = ParseTime(value) * kNsecPerSec; } else { number = value.toLongLong(); } - if ((prefix.size() == 1 && prefix[0] == u'=') || prefix == "=="_L1) { - cmp = new FilterParserInt64EqComparator(number); - } - else if (prefix == "!="_L1 || prefix == "<>"_L1) { - cmp = new FilterParserInt64NeComparator(number); - } - else if (prefix.size() == 1 && prefix[0] == u'>') { - cmp = new FilterParserInt64GtComparator(number); - } - else if (prefix == ">="_L1) { - cmp = new FilterParserInt64GeComparator(number); - } - else if (prefix.size() == 1 && prefix[0] == u'<') { - cmp = new FilterParserInt64LtComparator(number); - } - else if (prefix == "<="_L1) { - cmp = new FilterParserInt64LeComparator(number); - } - else { - cmp = new FilterParserInt64EqComparator(number); + switch (filter_operator) { + case FilterOperator::None: + case FilterOperator::Eq: + cmp = new FilterParserInt64EqComparator(number); + break; + case FilterOperator::Ne: + cmp = new FilterParserInt64NeComparator(number); + break; + case FilterOperator::Gt: + cmp = new FilterParserInt64GtComparator(number); + break; + case FilterOperator::Ge: + cmp = new FilterParserInt64GeComparator(number); + break; + case FilterOperator::Lt: + cmp = new FilterParserInt64LtComparator(number); + break; + case FilterOperator::Le: + cmp = new FilterParserInt64LeComparator(number); + break; } break; } - case ColumnType::Float: { + case ColumnType::Float:{ const float rating = ParseRating(value); - if ((prefix.size() == 1 && prefix[0] == u'=') || prefix == "=="_L1) { - cmp = new FilterParserFloatEqComparator(rating); - } - else if (prefix == "!="_L1 || prefix == "<>"_L1) { - cmp = new FilterParserFloatNeComparator(rating); - } - else if (prefix.size() == 1 && prefix[0] == u'>') { - cmp = new FilterParserFloatGtComparator(rating); - } - else if (prefix == ">="_L1) { - cmp = new FilterParserFloatGeComparator(rating); - } - else if (prefix.size() == 1 && prefix[0] == u'<') { - cmp = new FilterParserFloatLtComparator(rating); - } - else if (prefix == "<="_L1) { - cmp = new FilterParserFloatLeComparator(rating); - } - else { - cmp = new FilterParserFloatEqComparator(rating); + switch (filter_operator) { + case FilterOperator::None: + case FilterOperator::Eq: + cmp = new FilterParserFloatEqComparator(rating); + break; + case FilterOperator::Ne: + cmp = new FilterParserFloatNeComparator(rating); + break; + case FilterOperator::Gt: + cmp = new FilterParserFloatGtComparator(rating); + break; + case FilterOperator::Ge: + cmp = new FilterParserFloatGeComparator(rating); + break; + case FilterOperator::Lt: + cmp = new FilterParserFloatLtComparator(rating); + break; + case FilterOperator::Le: + cmp = new FilterParserFloatLeComparator(rating); + break; } break; } @@ -433,8 +511,8 @@ FilterTree *FilterParser::createSearchTermTreeNode(const QString &column, const } } - if (cmp) { - return new FilterTreeColumnTerm(column, cmp); + if (filter_column != FilterColumn::Unknown && cmp != nullptr) { + return new FilterTreeColumnTerm(filter_column, cmp); } return new FilterTreeTerm(new FilterParserTextContainsComparator(value)); diff --git a/src/filterparser/filterparser.h b/src/filterparser/filterparser.h index 8b54820b5..48af634e0 100644 --- a/src/filterparser/filterparser.h +++ b/src/filterparser/filterparser.h @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2012, David Sansome - * Copyright 2018-2024, Jonas Kvinge + * Copyright 2018-2026, Jonas Kvinge * Copyright 2023, Daniel Ostertag * * Strawberry is free software: you can redistribute it and/or modify diff --git a/src/filterparser/filtertree.cpp b/src/filterparser/filtertree.cpp index 1b6d4cfb4..d5159acdd 100644 --- a/src/filterparser/filtertree.cpp +++ b/src/filterparser/filtertree.cpp @@ -1,8 +1,6 @@ /* * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2012, David Sansome - * Copyright 2018-2024, Jonas Kvinge + * Copyright 2018-2026, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +20,7 @@ #include #include "filtertree.h" - +#include "filtercolumn.h" #include "core/song.h" using namespace Qt::Literals::StringLiterals; @@ -30,28 +28,64 @@ using namespace Qt::Literals::StringLiterals; FilterTree::FilterTree() = default; FilterTree::~FilterTree() = default; -QVariant FilterTree::DataFromColumn(const QString &column, const Song &metadata) { +QVariant FilterTree::DataFromColumn(const FilterColumn filter_column, const Song &song) { - if (column == "albumartist"_L1) return metadata.effective_albumartist(); - if (column == "artist"_L1) return metadata.artist(); - if (column == "album"_L1) return metadata.album(); - if (column == "title"_L1) return metadata.PrettyTitle(); - if (column == "composer"_L1) return metadata.composer(); - if (column == "performer"_L1) return metadata.performer(); - if (column == "grouping"_L1) return metadata.grouping(); - if (column == "genre"_L1) return metadata.genre(); - if (column == "comment"_L1) return metadata.comment(); - if (column == "track"_L1) return metadata.track(); - if (column == "year"_L1) return metadata.year(); - if (column == "length"_L1) return metadata.length_nanosec(); - if (column == "samplerate"_L1) return metadata.samplerate(); - if (column == "bitdepth"_L1) return metadata.bitdepth(); - if (column == "bitrate"_L1) return metadata.bitrate(); - if (column == "rating"_L1) return metadata.rating(); - if (column == "playcount"_L1) return metadata.playcount(); - if (column == "skipcount"_L1) return metadata.skipcount(); - if (column == "filename"_L1) return metadata.basefilename(); - if (column == "url"_L1) return metadata.effective_url().toString(); + switch (filter_column) { + case FilterColumn::AlbumArtist: + return song.effective_albumartist(); + case FilterColumn::AlbumArtistSort: + return song.effective_albumartistsort(); + case FilterColumn::Artist: + return song.artist(); + case FilterColumn::ArtistSort: + return song.effective_artistsort(); + case FilterColumn::Album: + return song.album(); + case FilterColumn::AlbumSort: + return song.effective_albumsort(); + case FilterColumn::Title: + return song.PrettyTitle(); + case FilterColumn::TitleSort: + return song.effective_titlesort(); + case FilterColumn::Composer: + return song.composer(); + case FilterColumn::ComposerSort: + return song.effective_composersort(); + case FilterColumn::Performer: + return song.performer(); + case FilterColumn::PerformerSort: + return song.effective_performersort(); + case FilterColumn::Grouping: + return song.grouping(); + case FilterColumn::Genre: + return song.genre(); + case FilterColumn::Comment: + return song.comment(); + case FilterColumn::Track: + return song.track(); + case FilterColumn::Year: + return song.year(); + case FilterColumn::Length: + return song.length_nanosec(); + case FilterColumn::Samplerate: + return song.samplerate(); + case FilterColumn::Bitdepth: + return song.bitdepth(); + case FilterColumn::Bitrate: + return song.bitrate(); + case FilterColumn::Rating: + return song.rating(); + case FilterColumn::Playcount: + return song.playcount(); + case FilterColumn::Skipcount: + return song.skipcount(); + case FilterColumn::Filename: + return song.basefilename(); + case FilterColumn::URL: + return song.effective_url().toString(); + case FilterColumn::Unknown: + break; + } return QVariant(); diff --git a/src/filterparser/filtertree.h b/src/filterparser/filtertree.h index 5e49c78b8..59189a14e 100644 --- a/src/filterparser/filtertree.h +++ b/src/filterparser/filtertree.h @@ -1,8 +1,6 @@ /* * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2012, David Sansome - * Copyright 2018-2024, Jonas Kvinge + * Copyright 2018-2026, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +23,7 @@ #include #include "core/song.h" +#include "filtercolumn.h" class FilterTree { public: @@ -45,7 +44,7 @@ class FilterTree { virtual bool accept(const Song &song) const = 0; protected: - static QVariant DataFromColumn(const QString &column, const Song &metadata); + static QVariant DataFromColumn(const FilterColumn filter_column, const Song &metadata); private: Q_DISABLE_COPY(FilterTree) diff --git a/src/filterparser/filtertreecolumnterm.cpp b/src/filterparser/filtertreecolumnterm.cpp index 9c1d8f572..14926c2dc 100644 --- a/src/filterparser/filtertreecolumnterm.cpp +++ b/src/filterparser/filtertreecolumnterm.cpp @@ -24,8 +24,8 @@ #include "filtertreecolumnterm.h" #include "filterparsersearchtermcomparator.h" -FilterTreeColumnTerm::FilterTreeColumnTerm(const QString &column, FilterParserSearchTermComparator *comparator) : column_(column), cmp_(comparator) {} +FilterTreeColumnTerm::FilterTreeColumnTerm(const FilterColumn filter_column, FilterParserSearchTermComparator *comparator) : filter_column_(filter_column), cmp_(comparator) {} bool FilterTreeColumnTerm::accept(const Song &song) const { - return cmp_->Matches(DataFromColumn(column_, song)); + return cmp_->Matches(DataFromColumn(filter_column_, song)); } diff --git a/src/filterparser/filtertreecolumnterm.h b/src/filterparser/filtertreecolumnterm.h index ffaa40376..c7f108b03 100644 --- a/src/filterparser/filtertreecolumnterm.h +++ b/src/filterparser/filtertreecolumnterm.h @@ -26,20 +26,20 @@ #include #include "filtertree.h" - +#include "filtercolumn.h" #include "core/song.h" class FilterParserSearchTermComparator; class FilterTreeColumnTerm : public FilterTree { public: - explicit FilterTreeColumnTerm(const QString &column, FilterParserSearchTermComparator *comparator); + explicit FilterTreeColumnTerm(const FilterColumn filter_column, FilterParserSearchTermComparator *comparator); FilterType type() const override { return FilterType::Column; } bool accept(const Song &song) const override; private: - const QString column_; + const FilterColumn filter_column_; QScopedPointer cmp_; Q_DISABLE_COPY(FilterTreeColumnTerm)