FilterParser: Add ability to filter by rating

Playlists can now be filtered by the rating from 0-5 like:
rating:0 rating:<3 rating:!=0
or by a float value, like:
rating:f0.1 rating:>=f0.5
This commit is contained in:
Dakes 2023-06-06 21:58:33 +02:00 committed by Jonas Kvinge
parent 6af8e6c25b
commit 1deacaecf9
3 changed files with 127 additions and 1 deletions

View File

@ -56,6 +56,7 @@ PlaylistFilter::PlaylistFilter(QObject *parent)
column_names_["filename"] = Playlist::Column_Filename;
column_names_["grouping"] = Playlist::Column_Grouping;
column_names_["comment"] = Playlist::Column_Comment;
column_names_["rating"] = Playlist::Column_Rating;
numerical_columns_ << Playlist::Column_Year
<< Playlist::Column_OriginalYear

View File

@ -117,6 +117,67 @@ class LexicalLeComparator : public SearchTermComparator {
QString search_term_;
};
// Float Comparators are for the rating
class FloatEqComparator : public SearchTermComparator {
public:
explicit FloatEqComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return search_term_ == element.toFloat();
}
private:
float search_term_;
};
class FloatNeComparator : public SearchTermComparator {
public:
explicit FloatNeComparator(const float &value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return search_term_ != element.toFloat();
}
private:
float search_term_;
};
class FloatGtComparator : public SearchTermComparator {
public:
explicit FloatGtComparator(const float &value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return element.toFloat() > search_term_;
}
private:
float search_term_;
};
class FloatGeComparator : public SearchTermComparator {
public:
explicit FloatGeComparator(const float &value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return element.toFloat() >= search_term_;
}
private:
float search_term_;
};
class FloatLtComparator : public SearchTermComparator {
public:
explicit FloatLtComparator(const float &value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return element.toFloat() < search_term_;
}
private:
float search_term_;
};
class FloatLeComparator : public SearchTermComparator {
public:
explicit FloatLeComparator(const float &value) : search_term_(value) {}
bool Matches(const QString &element) const override {
return element.toFloat() <= search_term_;
}
private:
float search_term_;
};
class GtComparator : public SearchTermComparator {
public:
explicit GtComparator(const int value) : search_term_(value) {}
@ -454,7 +515,34 @@ FilterTree *FilterParser::createSearchTermTreeNode(const QString &col, const QSt
// here comes a mess :/
// well, not that much of a mess, but so many options -_-
SearchTermComparator *cmp = nullptr;
if (prefix == "!=" || prefix == "<>") {
// Handle the float based Rating Column
if (columns_[col] == Playlist::Column_Rating) {
float parsedSearch = parseRating(search);
if (prefix == "=") {
cmp = new FloatEqComparator(parsedSearch);
}
else if (prefix == "!=" || prefix == "<>") {
cmp = new FloatNeComparator(parsedSearch);
}
else if (prefix == ">") {
cmp = new FloatGtComparator(parsedSearch);
}
else if (prefix == ">=") {
cmp = new FloatGeComparator(parsedSearch);
}
else if (prefix == "<") {
cmp = new FloatLtComparator(parsedSearch);
}
else if (prefix == "<=") {
cmp = new FloatLeComparator(parsedSearch);
}
else {
cmp = new FloatEqComparator(parsedSearch);
}
}
else if (prefix == "!=" || prefix == "<>") {
cmp = new NeComparator(search);
}
else if (!col.isEmpty() && columns_.contains(col) && numerical_columns_.contains(columns_[col])) {
@ -504,6 +592,7 @@ FilterTree *FilterParser::createSearchTermTreeNode(const QString &col, const QSt
cmp = new DefaultComparator(search);
}
}
if (columns_.contains(col)) {
if (columns_[col] == Playlist::Column_Length) {
cmp = new DropTailComparatorDecorator(cmp);
@ -552,3 +641,38 @@ int FilterParser::parseTime(const QString &time_str) {
seconds = seconds * 60 + accum;
return seconds;
}
// The rating column contains the rating as a float from 0-1 or -1 if unrated.
// If the rating is a number from 0-5, map it to 0-1
// To use float values directly, the search term can be prefixed with "f" (rating:>f0.2)
// If search is 0, or by default, uses -1
float FilterParser::parseRating(const QString &rating_str) {
if (rating_str.isEmpty()) {
return -1;
}
float rating = -1;
bool ok = false;
float rating_input = rating_str.toFloat(&ok);
// is valid int from 0-5: convert to float
if (ok && rating_input >= 0 && rating_input <= 5) {
rating = rating_input / 5.;
}
// check if the search is a float
else if (rating_str.at(0) == 'f') {
QString rating_float = rating_str;
rating_float = rating_float.remove(0, 1);
ok = false;
rating_float.toFloat(&ok);
if (ok) {
rating = rating_float.toFloat(&ok);
}
}
// Songs with zero rating have -1 in the DB
if (rating == 0) {
rating = -1;
}
return rating;
}

View File

@ -89,6 +89,7 @@ class FilterParser {
FilterTree *createSearchTermTreeNode(const QString &col, const QString &prefix, const QString &search) const;
static int parseTime(const QString &time_str);
static float parseRating(const QString &rating_str);
QString::const_iterator iter_;
QString::const_iterator end_;