Add sort columns to filter parser

Also pass the filter column enum through to filter tree instead of string.
This commit is contained in:
Jonas Kvinge
2026-01-17 23:18:01 +01:00
parent 3416ede211
commit 6f7b8ab162
7 changed files with 317 additions and 153 deletions

View File

@@ -0,0 +1,53 @@
/*
* Strawberry Music Player
* Copyright 2026, Jonas Kvinge <jonas@jkvinge.net>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2026, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2023, Daniel Ostertag <daniel.ostertag@dakes.de>
*
* 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<QString, FilterOperator> &GetFilterOperatorsMap() {
static const QMap<QString, FilterOperator> filter_operators_map_ = []() {
QMap<QString, FilterOperator> 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<QString, ColumnType> &GetColumnTypeMap() {
static const QMap<QString, ColumnType> column_types = []() {
QMap<QString, ColumnType> 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<QString, FilterColumn> &GetFilterColumnsMap() {
static const QMap<QString, FilterColumn> filter_columns_map_ = []() {
QMap<QString, FilterColumn> 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<FilterColumn, ColumnType> &GetColumnTypesMap() {
static const QMap<FilterColumn, ColumnType> column_types_map_ = []() {
QMap<FilterColumn, ColumnType> 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) {
switch (filter_operator) {
case FilterOperator::Eq:
cmp = new FilterParserTextEqComparator(value);
}
else if (prefix == "!="_L1 || prefix == "<>"_L1) {
break;
case FilterOperator::Ne:
cmp = new FilterParserTextNeComparator(value);
}
else {
break;
default:
cmp = new FilterParserTextContainsComparator(value);
break;
}
break;
}
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:{
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:{
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) {
switch (filter_operator) {
case FilterOperator::None:
case FilterOperator::Eq:
cmp = new FilterParserInt64EqComparator(number);
}
else if (prefix == "!="_L1 || prefix == "<>"_L1) {
break;
case FilterOperator::Ne:
cmp = new FilterParserInt64NeComparator(number);
}
else if (prefix.size() == 1 && prefix[0] == u'>') {
break;
case FilterOperator::Gt:
cmp = new FilterParserInt64GtComparator(number);
}
else if (prefix == ">="_L1) {
break;
case FilterOperator::Ge:
cmp = new FilterParserInt64GeComparator(number);
}
else if (prefix.size() == 1 && prefix[0] == u'<') {
break;
case FilterOperator::Lt:
cmp = new FilterParserInt64LtComparator(number);
}
else if (prefix == "<="_L1) {
break;
case FilterOperator::Le:
cmp = new FilterParserInt64LeComparator(number);
}
else {
cmp = new FilterParserInt64EqComparator(number);
break;
}
break;
}
case ColumnType::Float:{
const float rating = ParseRating(value);
if ((prefix.size() == 1 && prefix[0] == u'=') || prefix == "=="_L1) {
switch (filter_operator) {
case FilterOperator::None:
case FilterOperator::Eq:
cmp = new FilterParserFloatEqComparator(rating);
}
else if (prefix == "!="_L1 || prefix == "<>"_L1) {
break;
case FilterOperator::Ne:
cmp = new FilterParserFloatNeComparator(rating);
}
else if (prefix.size() == 1 && prefix[0] == u'>') {
break;
case FilterOperator::Gt:
cmp = new FilterParserFloatGtComparator(rating);
}
else if (prefix == ">="_L1) {
break;
case FilterOperator::Ge:
cmp = new FilterParserFloatGeComparator(rating);
}
else if (prefix.size() == 1 && prefix[0] == u'<') {
break;
case FilterOperator::Lt:
cmp = new FilterParserFloatLtComparator(rating);
}
else if (prefix == "<="_L1) {
break;
case FilterOperator::Le:
cmp = new FilterParserFloatLeComparator(rating);
}
else {
cmp = new FilterParserFloatEqComparator(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));

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2026, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2023, Daniel Ostertag <daniel.ostertag@dakes.de>
*
* Strawberry is free software: you can redistribute it and/or modify

View File

@@ -1,8 +1,6 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2026, Jonas Kvinge <jonas@jkvinge.net>
*
* 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 <QString>
#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();

View File

@@ -1,8 +1,6 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2026, Jonas Kvinge <jonas@jkvinge.net>
*
* 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 <QString>
#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)

View File

@@ -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));
}

View File

@@ -26,20 +26,20 @@
#include <QScopedPointer>
#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<FilterParserSearchTermComparator> cmp_;
Q_DISABLE_COPY(FilterTreeColumnTerm)