Compare commits

...

9 Commits

Author SHA1 Message Date
Jonas Kvinge
8bea6ec5b0 Release 1.1.0 2024-07-14 14:55:14 +02:00
Jonas Kvinge
e8144487ee Update Changelog 2024-07-14 14:48:29 +02:00
Jonas Kvinge
41d9d15dda MainWindow: Only show sponsor dialog if update dialog is answered 2024-07-13 18:24:47 +02:00
Jonas Kvinge
124b97c024 Turn on git revision 2024-07-10 21:58:29 +02:00
Jonas Kvinge
98e0b45403 Release 1.1.0-rc4 2024-07-10 20:07:01 +02:00
Jonas Kvinge
1f2b8d8bf6 Rename playlist filter classes 2024-07-10 18:27:17 +02:00
Jonas Kvinge
8327751b91 CollectionFilter: Optimize use of QRegularExpression
Possible fix for #1482
2024-07-09 22:06:42 +02:00
Jonas Kvinge
6417f89596 CollectionFilter: Add std::as_const 2024-07-09 18:06:46 +02:00
Jonas Kvinge
2e53656f44 Turn on git revision 2024-07-09 17:52:21 +02:00
9 changed files with 181 additions and 167 deletions

View File

@@ -2,7 +2,7 @@ Strawberry Music Player
======================= =======================
ChangeLog ChangeLog
Version 1.1.0-rc3 (2024.07.09): Version 1.1.0 (2024.07.14):
Bugfixes: Bugfixes:
* Fixed crash when pressing CTRL + C (#1359). * Fixed crash when pressing CTRL + C (#1359).
@@ -28,14 +28,13 @@ Version 1.1.0-rc3 (2024.07.09):
* Only use playbin3 with GStreamer 1.24 and higher, not with GStreamer 1.22 or lower. * Only use playbin3 with GStreamer 1.24 and higher, not with GStreamer 1.22 or lower.
* (macOS/Windows) Fixed dash and hls streaming, plugins were missing. * (macOS/Windows) Fixed dash and hls streaming, plugins were missing.
* (Windows) Fixed incorrect colors in smart playlist wizard with Fusion in dark mode (#1399). * (Windows) Fixed incorrect colors in smart playlist wizard with Fusion in dark mode (#1399).
* (Windows) Fixed update window blocking sponsor window on startup.
Enhancements: Enhancements:
* Improve error messages when connecting and copying to devices. * Improve error messages when connecting and copying to devices.
* Allow enter to be used with multiselection to add songs to playlist (#1360) * Allow enter to be used with multiselection to add songs to playlist (#1360)
* Add song progress to taskbar using D-Bus. * Add song progress to taskbar using D-Bus.
* Use API to receive Radio Paradise channels. * Use API to receive Radio Paradise channels.
* Added letras lyrics provider.
* Added Open Tidal API (openapi.tidal.com) cover provider.
* Added button for fetching lyrics to tag editor (#1391). * Added button for fetching lyrics to tag editor (#1391).
* Added option not to skip "A", "An" and "The” when sorting artist names in collection (#1393). * Added option not to skip "A", "An" and "The” when sorting artist names in collection (#1393).
* Improved album and title disc, remastered, etc matching and stripping (#1387). * Improved album and title disc, remastered, etc matching and stripping (#1387).
@@ -54,6 +53,8 @@ Version 1.1.0-rc3 (2024.07.09):
* (Windows MSVC) Add back WASAPI2. * (Windows MSVC) Add back WASAPI2.
New features: New features:
* Letras lyrics provider.
* Open Tidal API (openapi.tidal.com) cover provider.
* Turbine analyzer. * Turbine analyzer.
* WaveRubber analyzer. * WaveRubber analyzer.
* Spotify streaming support. * Spotify streaming support.

View File

@@ -1,7 +1,7 @@
set(STRAWBERRY_VERSION_MAJOR 1) set(STRAWBERRY_VERSION_MAJOR 1)
set(STRAWBERRY_VERSION_MINOR 1) set(STRAWBERRY_VERSION_MINOR 1)
set(STRAWBERRY_VERSION_PATCH 0) set(STRAWBERRY_VERSION_PATCH 0)
set(STRAWBERRY_VERSION_PRERELEASE rc3) #set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF) set(INCLUDE_GIT_REVISION OFF)

View File

@@ -50,6 +50,7 @@
</screenshots> </screenshots>
<update_contact>eclipseo@fedoraproject.org</update_contact> <update_contact>eclipseo@fedoraproject.org</update_contact>
<releases> <releases>
<release version="1.1.0" date="2024-07-14"/>
<release version="1.0.23" date="2024-01-11"/> <release version="1.0.23" date="2024-01-11"/>
<release version="1.0.22" date="2023-12-09"/> <release version="1.0.22" date="2023-12-09"/>
<release version="1.0.21" date="2023-10-21"/> <release version="1.0.21" date="2023-10-21"/>

View File

@@ -19,6 +19,8 @@
#include "config.h" #include "config.h"
#include <utility>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
@@ -62,19 +64,17 @@ bool CollectionFilter::filterAcceptsRow(const int source_row, const QModelIndex
if (filter_text.isEmpty()) return true; if (filter_text.isEmpty()) return true;
filter_text = filter_text.replace(QRegularExpression(QStringLiteral("\\s*:\\s*")), QStringLiteral(":")) for (const QString &foperator : Operators) {
.replace(QRegularExpression(QStringLiteral("\\s*=\\s*")), QStringLiteral("=")) if (filter_text.contains(foperator)) {
.replace(QRegularExpression(QStringLiteral("\\s*==\\s*")), QStringLiteral("==")) QRegularExpression regex(QStringLiteral("\\s*") + foperator + QStringLiteral("\\s*"));
.replace(QRegularExpression(QStringLiteral("\\s*<>\\s*")), QStringLiteral("<>")) filter_text = filter_text.replace(regex, foperator);
.replace(QRegularExpression(QStringLiteral("\\s*<\\s*")), QStringLiteral("<")) }
.replace(QRegularExpression(QStringLiteral("\\s*>\\s*")), QStringLiteral(">")) }
.replace(QRegularExpression(QStringLiteral("\\s*<=\\s*")), QStringLiteral("<="))
.replace(QRegularExpression(QStringLiteral("\\s*>=\\s*")), QStringLiteral(">="));
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
const QStringList tokens = filter_text.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts); const QStringList tokens = filter_text.split(QLatin1Char(' '), Qt::SkipEmptyParts);
#else #else
const QStringList tokens = filter_text.split(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts); const QStringList tokens = filter_text.split(QLatin1Char(' '), QString::SkipEmptyParts);
#endif #endif
filter_text.clear(); filter_text.clear();
@@ -326,7 +326,7 @@ bool CollectionFilter::FieldFloatValueMatchesData(const float value, const QStri
bool CollectionFilter::ContainsOperators(const QString &token) { bool CollectionFilter::ContainsOperators(const QString &token) {
for (const QString &foperator : Operators) { for (const QString &foperator : std::as_const(Operators)) {
if (token.contains(foperator, Qt::CaseInsensitive)) return true; if (token.contains(foperator, Qt::CaseInsensitive)) return true;
} }

View File

@@ -1071,17 +1071,25 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
#endif #endif
{ {
bool asked_permission = true;
Settings s; Settings s;
s.beginGroup(kSettingsGroup); #ifdef HAVE_QTSPARKLE
constexpr char do_not_show_sponsor_message_key[] = "do_not_show_sponsor_message"; s.beginGroup("QtSparkle");
const bool do_not_show_sponsor_message = s.value(do_not_show_sponsor_message_key, false).toBool(); asked_permission = s.value("asked_permission", false).toBool();
s.endGroup(); s.endGroup();
if (!do_not_show_sponsor_message) { #endif
MessageDialog *sponsor_message = new MessageDialog(this); if (asked_permission) {
sponsor_message->set_settings_group(QLatin1String(kSettingsGroup)); s.beginGroup(kSettingsGroup);
sponsor_message->set_do_not_show_message_again(QLatin1String(do_not_show_sponsor_message_key)); constexpr char do_not_show_sponsor_message_key[] = "do_not_show_sponsor_message";
sponsor_message->setAttribute(Qt::WA_DeleteOnClose); const bool do_not_show_sponsor_message = s.value(do_not_show_sponsor_message_key, false).toBool();
sponsor_message->ShowMessage(tr("Sponsoring Strawberry"), tr("Strawberry is free and open source software. If you like Strawberry, please consider sponsoring the project. For more information about sponsorship see our website %1").arg(QStringLiteral("<a href= \"https://www.strawberrymusicplayer.org/\">www.strawberrymusicplayer.org</a>")), IconLoader::Load(QStringLiteral("dialog-information"))); s.endGroup();
if (!do_not_show_sponsor_message) {
MessageDialog *sponsor_message = new MessageDialog(this);
sponsor_message->set_settings_group(QLatin1String(kSettingsGroup));
sponsor_message->set_do_not_show_message_again(QLatin1String(do_not_show_sponsor_message_key));
sponsor_message->setAttribute(Qt::WA_DeleteOnClose);
sponsor_message->ShowMessage(tr("Sponsoring Strawberry"), tr("Strawberry is free and open source software. If you like Strawberry, please consider sponsoring the project. For more information about sponsorship see our website %1").arg(QStringLiteral("<a href= \"https://www.strawberrymusicplayer.org/\">www.strawberrymusicplayer.org</a>")), IconLoader::Load(QStringLiteral("dialog-information")));
}
} }
} }

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com> * Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -32,7 +32,7 @@
PlaylistFilter::PlaylistFilter(QObject *parent) PlaylistFilter::PlaylistFilter(QObject *parent)
: QSortFilterProxyModel(parent), : QSortFilterProxyModel(parent),
filter_tree_(new NopFilter), filter_tree_(new PlaylistNopFilter),
query_hash_(0) { query_hash_(0) {
setDynamicSortFilter(true); setDynamicSortFilter(true);
@@ -89,7 +89,7 @@ bool PlaylistFilter::filterAcceptsRow(const int row, const QModelIndex &parent)
#endif #endif
if (hash != query_hash_) { if (hash != query_hash_) {
// Parse the query // Parse the query
FilterParser p(filter_text_, column_names_, numerical_columns_); PlaylistFilterParser p(filter_text_, column_names_, numerical_columns_);
filter_tree_.reset(p.parse()); filter_tree_.reset(p.parse());
query_hash_ = hash; query_hash_ = hash;

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com> * Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -32,7 +32,7 @@
#include <QString> #include <QString>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
class FilterTree; class PlaylistFilterTree;
class PlaylistFilter : public QSortFilterProxyModel { class PlaylistFilter : public QSortFilterProxyModel {
Q_OBJECT Q_OBJECT
@@ -55,7 +55,7 @@ class PlaylistFilter : public QSortFilterProxyModel {
private: private:
// Mutable because they're modified from filterAcceptsRow() const // Mutable because they're modified from filterAcceptsRow() const
mutable QScopedPointer<FilterTree> filter_tree_; mutable QScopedPointer<PlaylistFilterTree> filter_tree_;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
mutable size_t query_hash_; mutable size_t query_hash_;
#else #else

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com> * Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -36,31 +37,31 @@
#include "playlistfilterparser.h" #include "playlistfilterparser.h"
#include "utilities/searchparserutils.h" #include "utilities/searchparserutils.h"
class SearchTermComparator { class PlaylistSearchTermComparator {
public: public:
SearchTermComparator() = default; PlaylistSearchTermComparator() = default;
virtual ~SearchTermComparator() = default; virtual ~PlaylistSearchTermComparator() = default;
virtual bool Matches(const QString &element) const = 0; virtual bool Matches(const QString &element) const = 0;
private: private:
Q_DISABLE_COPY(SearchTermComparator) Q_DISABLE_COPY(PlaylistSearchTermComparator)
}; };
// "compares" by checking if the field contains the search term // "compares" by checking if the field contains the search term
class DefaultComparator : public SearchTermComparator { class PlaylistDefaultComparator : public PlaylistSearchTermComparator {
public: public:
explicit DefaultComparator(const QString &value) : search_term_(value) {} explicit PlaylistDefaultComparator(const QString &value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element.contains(search_term_); return element.contains(search_term_);
} }
private: private:
QString search_term_; QString search_term_;
Q_DISABLE_COPY(DefaultComparator) Q_DISABLE_COPY(PlaylistDefaultComparator)
}; };
class EqComparator : public SearchTermComparator { class PlaylistEqComparator : public PlaylistSearchTermComparator {
public: public:
explicit EqComparator(const QString &value) : search_term_(value) {} explicit PlaylistEqComparator(const QString &value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return search_term_ == element; return search_term_ == element;
} }
@@ -68,9 +69,9 @@ class EqComparator : public SearchTermComparator {
QString search_term_; QString search_term_;
}; };
class NeComparator : public SearchTermComparator { class PlaylistNeComparator : public PlaylistSearchTermComparator {
public: public:
explicit NeComparator(const QString &value) : search_term_(value) {} explicit PlaylistNeComparator(const QString &value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return search_term_ != element; return search_term_ != element;
} }
@@ -78,9 +79,9 @@ class NeComparator : public SearchTermComparator {
QString search_term_; QString search_term_;
}; };
class LexicalGtComparator : public SearchTermComparator { class PlaylistLexicalGtComparator : public PlaylistSearchTermComparator {
public: public:
explicit LexicalGtComparator(const QString &value) : search_term_(value) {} explicit PlaylistLexicalGtComparator(const QString &value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element > search_term_; return element > search_term_;
} }
@@ -88,9 +89,9 @@ class LexicalGtComparator : public SearchTermComparator {
QString search_term_; QString search_term_;
}; };
class LexicalGeComparator : public SearchTermComparator { class PlaylistLexicalGeComparator : public PlaylistSearchTermComparator {
public: public:
explicit LexicalGeComparator(const QString &value) : search_term_(value) {} explicit PlaylistLexicalGeComparator(const QString &value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element >= search_term_; return element >= search_term_;
} }
@@ -98,9 +99,9 @@ class LexicalGeComparator : public SearchTermComparator {
QString search_term_; QString search_term_;
}; };
class LexicalLtComparator : public SearchTermComparator { class PlaylistLexicalLtComparator : public PlaylistSearchTermComparator {
public: public:
explicit LexicalLtComparator(const QString &value) : search_term_(value) {} explicit PlaylistLexicalLtComparator(const QString &value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element < search_term_; return element < search_term_;
} }
@@ -108,9 +109,9 @@ class LexicalLtComparator : public SearchTermComparator {
QString search_term_; QString search_term_;
}; };
class LexicalLeComparator : public SearchTermComparator { class PlaylistLexicalLeComparator : public PlaylistSearchTermComparator {
public: public:
explicit LexicalLeComparator(const QString &value) : search_term_(value) {} explicit PlaylistLexicalLeComparator(const QString &value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element <= search_term_; return element <= search_term_;
} }
@@ -119,9 +120,9 @@ class LexicalLeComparator : public SearchTermComparator {
}; };
// Float Comparators are for the rating // Float Comparators are for the rating
class FloatEqComparator : public SearchTermComparator { class PlaylistFloatEqComparator : public PlaylistSearchTermComparator {
public: public:
explicit FloatEqComparator(const float value) : search_term_(value) {} explicit PlaylistFloatEqComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return search_term_ == element.toFloat(); return search_term_ == element.toFloat();
} }
@@ -129,9 +130,9 @@ class FloatEqComparator : public SearchTermComparator {
float search_term_; float search_term_;
}; };
class FloatNeComparator : public SearchTermComparator { class PlaylistFloatNeComparator : public PlaylistSearchTermComparator {
public: public:
explicit FloatNeComparator(const float value) : search_term_(value) {} explicit PlaylistFloatNeComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return search_term_ != element.toFloat(); return search_term_ != element.toFloat();
} }
@@ -139,9 +140,9 @@ class FloatNeComparator : public SearchTermComparator {
float search_term_; float search_term_;
}; };
class FloatGtComparator : public SearchTermComparator { class PlaylistFloatGtComparator : public PlaylistSearchTermComparator {
public: public:
explicit FloatGtComparator(const float value) : search_term_(value) {} explicit PlaylistFloatGtComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element.toFloat() > search_term_; return element.toFloat() > search_term_;
} }
@@ -149,9 +150,9 @@ class FloatGtComparator : public SearchTermComparator {
float search_term_; float search_term_;
}; };
class FloatGeComparator : public SearchTermComparator { class PlaylistFloatGeComparator : public PlaylistSearchTermComparator {
public: public:
explicit FloatGeComparator(const float value) : search_term_(value) {} explicit PlaylistFloatGeComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element.toFloat() >= search_term_; return element.toFloat() >= search_term_;
} }
@@ -159,9 +160,9 @@ class FloatGeComparator : public SearchTermComparator {
float search_term_; float search_term_;
}; };
class FloatLtComparator : public SearchTermComparator { class PlaylistFloatLtComparator : public PlaylistSearchTermComparator {
public: public:
explicit FloatLtComparator(const float value) : search_term_(value) {} explicit PlaylistFloatLtComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element.toFloat() < search_term_; return element.toFloat() < search_term_;
} }
@@ -169,9 +170,9 @@ class FloatLtComparator : public SearchTermComparator {
float search_term_; float search_term_;
}; };
class FloatLeComparator : public SearchTermComparator { class PlaylistFloatLeComparator : public PlaylistSearchTermComparator {
public: public:
explicit FloatLeComparator(const float value) : search_term_(value) {} explicit PlaylistFloatLeComparator(const float value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element.toFloat() <= search_term_; return element.toFloat() <= search_term_;
} }
@@ -179,9 +180,9 @@ class FloatLeComparator : public SearchTermComparator {
float search_term_; float search_term_;
}; };
class GtComparator : public SearchTermComparator { class PlaylistGtComparator : public PlaylistSearchTermComparator {
public: public:
explicit GtComparator(const int value) : search_term_(value) {} explicit PlaylistGtComparator(const int value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element.toInt() > search_term_; return element.toInt() > search_term_;
} }
@@ -189,9 +190,9 @@ class GtComparator : public SearchTermComparator {
int search_term_; int search_term_;
}; };
class GeComparator : public SearchTermComparator { class PlaylistGeComparator : public PlaylistSearchTermComparator {
public: public:
explicit GeComparator(const int value) : search_term_(value) {} explicit PlaylistGeComparator(const int value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element.toInt() >= search_term_; return element.toInt() >= search_term_;
} }
@@ -199,9 +200,9 @@ class GeComparator : public SearchTermComparator {
int search_term_; int search_term_;
}; };
class LtComparator : public SearchTermComparator { class PlaylistLtComparator : public PlaylistSearchTermComparator {
public: public:
explicit LtComparator(const int value) : search_term_(value) {} explicit PlaylistLtComparator(const int value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element.toInt() < search_term_; return element.toInt() < search_term_;
} }
@@ -209,9 +210,9 @@ class LtComparator : public SearchTermComparator {
int search_term_; int search_term_;
}; };
class LeComparator : public SearchTermComparator { class PlaylistLeComparator : public PlaylistSearchTermComparator {
public: public:
explicit LeComparator(const int value) : search_term_(value) {} explicit PlaylistLeComparator(const int value) : search_term_(value) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return element.toInt() <= search_term_; return element.toInt() <= search_term_;
} }
@@ -222,9 +223,9 @@ class LeComparator : public SearchTermComparator {
// The length field of the playlist (entries) contains a song's running time in nanoseconds. // The length field of the playlist (entries) contains a song's running time in nanoseconds.
// However, We don't really care about nanoseconds, just seconds. // However, We don't really care about nanoseconds, just seconds.
// Thus, with this decorator we drop the last 9 digits, if that many are present. // Thus, with this decorator we drop the last 9 digits, if that many are present.
class DropTailComparatorDecorator : public SearchTermComparator { class PlaylistDropTailComparatorDecorator : public PlaylistSearchTermComparator {
public: public:
explicit DropTailComparatorDecorator(SearchTermComparator *cmp) : cmp_(cmp) {} explicit PlaylistDropTailComparatorDecorator(PlaylistSearchTermComparator *cmp) : cmp_(cmp) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
if (element.length() > 9) { if (element.length() > 9) {
@@ -235,97 +236,97 @@ class DropTailComparatorDecorator : public SearchTermComparator {
} }
} }
private: private:
QScopedPointer<SearchTermComparator> cmp_; QScopedPointer<PlaylistSearchTermComparator> cmp_;
}; };
class RatingComparatorDecorator : public SearchTermComparator { class PlaylistRatingComparatorDecorator : public PlaylistSearchTermComparator {
public: public:
explicit RatingComparatorDecorator(SearchTermComparator *cmp) : cmp_(cmp) {} explicit PlaylistRatingComparatorDecorator(PlaylistSearchTermComparator *cmp) : cmp_(cmp) {}
bool Matches(const QString &element) const override { bool Matches(const QString &element) const override {
return cmp_->Matches(QString::number(lround(element.toDouble() * 10.0))); return cmp_->Matches(QString::number(lround(element.toDouble() * 10.0)));
} }
private: private:
QScopedPointer<SearchTermComparator> cmp_; QScopedPointer<PlaylistSearchTermComparator> cmp_;
}; };
// filter that applies a SearchTermComparator to all fields of a playlist entry // Filter that applies a SearchTermComparator to all fields of a playlist entry
class FilterTerm : public FilterTree { class PlaylistFilterTerm : public PlaylistFilterTree {
public: public:
explicit FilterTerm(SearchTermComparator *comparator, const QList<int> &columns) : cmp_(comparator), columns_(columns) {} explicit PlaylistFilterTerm(PlaylistSearchTermComparator *comparator, const QList<int> &columns) : cmp_(comparator), columns_(columns) {}
bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override { bool accept(const int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override {
for (int i : columns_) { for (const int i : columns_) {
QModelIndex idx(model->index(row, i, parent)); const QModelIndex idx = model->index(row, i, parent);
if (cmp_->Matches(idx.data().toString().toLower())) return true; if (cmp_->Matches(idx.data().toString().toLower())) return true;
} }
return false; return false;
} }
FilterType type() override { return FilterType::Term; } FilterType type() override { return FilterType::Term; }
private: private:
QScopedPointer<SearchTermComparator> cmp_; QScopedPointer<PlaylistSearchTermComparator> cmp_;
QList<int> columns_; QList<int> columns_;
}; };
// filter that applies a SearchTermComparator to one specific field of a playlist entry // Filter that applies a SearchTermComparator to one specific field of a playlist entry
class FilterColumnTerm : public FilterTree { class PlaylistFilterColumnTerm : public PlaylistFilterTree {
public: public:
FilterColumnTerm(const int column, SearchTermComparator *comparator) : col(column), cmp_(comparator) {} PlaylistFilterColumnTerm(const int column, PlaylistSearchTermComparator *comparator) : col(column), cmp_(comparator) {}
bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override { bool accept(const int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override {
QModelIndex idx(model->index(row, col, parent)); const QModelIndex idx = model->index(row, col, parent);
return cmp_->Matches(idx.data().toString().toLower()); return cmp_->Matches(idx.data().toString().toLower());
} }
FilterType type() override { return FilterType::Column; } FilterType type() override { return FilterType::Column; }
private: private:
int col; int col;
QScopedPointer<SearchTermComparator> cmp_; QScopedPointer<PlaylistSearchTermComparator> cmp_;
}; };
class NotFilter : public FilterTree { class PlaylistNotFilter : public PlaylistFilterTree {
public: public:
explicit NotFilter(const FilterTree *inv) : child_(inv) {} explicit PlaylistNotFilter(const PlaylistFilterTree *inv) : child_(inv) {}
bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override { bool accept(const int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override {
return !child_->accept(row, parent, model); return !child_->accept(row, parent, model);
} }
FilterType type() override { return FilterType::Not; } FilterType type() override { return FilterType::Not; }
private: private:
QScopedPointer<const FilterTree> child_; QScopedPointer<const PlaylistFilterTree> child_;
}; };
class OrFilter : public FilterTree { class PlaylistOrFilter : public PlaylistFilterTree {
public: public:
~OrFilter() override { qDeleteAll(children_); } ~PlaylistOrFilter() override { qDeleteAll(children_); }
virtual void add(FilterTree *child) { children_.append(child); } virtual void add(PlaylistFilterTree *child) { children_.append(child); }
bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override { bool accept(const int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override {
return std::any_of(children_.begin(), children_.end(), [row, parent, model](FilterTree *child) { return child->accept(row, parent, model); }); return std::any_of(children_.begin(), children_.end(), [row, parent, model](PlaylistFilterTree *child) { return child->accept(row, parent, model); });
} }
FilterType type() override { return FilterType::Or; } FilterType type() override { return FilterType::Or; }
private: private:
QList<FilterTree*> children_; QList<PlaylistFilterTree*> children_;
}; };
class AndFilter : public FilterTree { class PlaylistAndFilter : public PlaylistFilterTree {
public: public:
~AndFilter() override { qDeleteAll(children_); } ~PlaylistAndFilter() override { qDeleteAll(children_); }
virtual void add(FilterTree *child) { children_.append(child); } virtual void add(PlaylistFilterTree *child) { children_.append(child); }
bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override { bool accept(const int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override {
return !std::any_of(children_.begin(), children_.end(), [row, parent, model](FilterTree *child) { return !child->accept(row, parent, model); }); return !std::any_of(children_.begin(), children_.end(), [row, parent, model](PlaylistFilterTree *child) { return !child->accept(row, parent, model); });
} }
FilterType type() override { return FilterType::And; } FilterType type() override { return FilterType::And; }
private: private:
QList<FilterTree*> children_; QList<PlaylistFilterTree*> children_;
}; };
FilterParser::FilterParser(const QString &filter, const QMap<QString, int> &columns, const QSet<int> &numerical_cols) : iter_{}, end_{}, filterstring_(filter), columns_(columns), numerical_columns_(numerical_cols) {} PlaylistFilterParser::PlaylistFilterParser(const QString &filter, const QMap<QString, int> &columns, const QSet<int> &numerical_cols) : iter_{}, end_{}, filterstring_(filter), columns_(columns), numerical_columns_(numerical_cols) {}
FilterTree *FilterParser::parse() { PlaylistFilterTree *PlaylistFilterParser::parse() {
iter_ = filterstring_.constBegin(); iter_ = filterstring_.constBegin();
end_ = filterstring_.constEnd(); end_ = filterstring_.constEnd();
return parseOrGroup(); return parseOrGroup();
} }
void FilterParser::advance() { void PlaylistFilterParser::advance() {
while (iter_ != end_ && iter_->isSpace()) { while (iter_ != end_ && iter_->isSpace()) {
++iter_; ++iter_;
@@ -333,28 +334,29 @@ void FilterParser::advance() {
} }
FilterTree *FilterParser::parseOrGroup() { PlaylistFilterTree *PlaylistFilterParser::parseOrGroup() {
advance(); advance();
if (iter_ == end_) return new NopFilter; if (iter_ == end_) return new PlaylistNopFilter;
OrFilter *group = new OrFilter; PlaylistOrFilter *group = new PlaylistOrFilter;
group->add(parseAndGroup()); group->add(parseAndGroup());
advance(); advance();
while (checkOr()) { while (checkOr()) {
group->add(parseAndGroup()); group->add(parseAndGroup());
advance(); advance();
} }
return group; return group;
} }
FilterTree *FilterParser::parseAndGroup() { PlaylistFilterTree *PlaylistFilterParser::parseAndGroup() {
advance(); advance();
if (iter_ == end_) return new NopFilter; if (iter_ == end_) return new PlaylistNopFilter;
AndFilter *group = new AndFilter(); PlaylistAndFilter *group = new PlaylistAndFilter();
do { do {
group->add(parseSearchExpression()); group->add(parseSearchExpression());
advance(); advance();
@@ -369,7 +371,7 @@ FilterTree *FilterParser::parseAndGroup() {
} }
bool FilterParser::checkAnd() { bool PlaylistFilterParser::checkAnd() {
if (iter_ != end_) { if (iter_ != end_) {
if (*iter_ == QLatin1Char('A')) { if (*iter_ == QLatin1Char('A')) {
@@ -390,11 +392,12 @@ bool FilterParser::checkAnd() {
} }
} }
} }
return false; return false;
} }
bool FilterParser::checkOr(const bool step_over) { bool PlaylistFilterParser::checkOr(const bool step_over) {
if (!buf_.isEmpty()) { if (!buf_.isEmpty()) {
if (buf_ == QLatin1String("OR")) { if (buf_ == QLatin1String("OR")) {
@@ -424,18 +427,19 @@ bool FilterParser::checkOr(const bool step_over) {
} }
} }
} }
return false; return false;
} }
FilterTree *FilterParser::parseSearchExpression() { PlaylistFilterTree *PlaylistFilterParser::parseSearchExpression() {
advance(); advance();
if (iter_ == end_) return new NopFilter; if (iter_ == end_) return new PlaylistNopFilter;
if (*iter_ == QLatin1Char('(')) { if (*iter_ == QLatin1Char('(')) {
++iter_; ++iter_;
advance(); advance();
FilterTree *tree = parseOrGroup(); PlaylistFilterTree *tree = parseOrGroup();
advance(); advance();
if (iter_ != end_) { if (iter_ != end_) {
if (*iter_ == QLatin1Char(')')) { if (*iter_ == QLatin1Char(')')) {
@@ -446,8 +450,8 @@ FilterTree *FilterParser::parseSearchExpression() {
} }
else if (*iter_ == QLatin1Char('-')) { else if (*iter_ == QLatin1Char('-')) {
++iter_; ++iter_;
FilterTree *tree = parseSearchExpression(); PlaylistFilterTree *tree = parseSearchExpression();
if (tree->type() != FilterTree::FilterType::Nop) return new NotFilter(tree); if (tree->type() != PlaylistFilterTree::FilterType::Nop) return new PlaylistNotFilter(tree);
return tree; return tree;
} }
else { else {
@@ -456,7 +460,7 @@ FilterTree *FilterParser::parseSearchExpression() {
} }
FilterTree *FilterParser::parseSearchTerm() { PlaylistFilterTree *PlaylistFilterParser::parseSearchTerm() {
QString col; QString col;
QString search; QString search;
@@ -508,46 +512,45 @@ FilterTree *FilterParser::parseSearchTerm() {
} }
FilterTree *FilterParser::createSearchTermTreeNode(const QString &col, const QString &prefix, const QString &search) const { PlaylistFilterTree *PlaylistFilterParser::createSearchTermTreeNode(const QString &col, const QString &prefix, const QString &search) const {
if (search.isEmpty() && prefix != QLatin1Char('=')) { if (search.isEmpty() && prefix != QLatin1Char('=')) {
return new NopFilter; return new PlaylistNopFilter;
} }
// here comes a mess :/
// well, not that much of a mess, but so many options -_- PlaylistSearchTermComparator *cmp = nullptr;
SearchTermComparator *cmp = nullptr;
// Handle the float based Rating Column // Handle the float based Rating Column
if (columns_[col] == static_cast<int>(Playlist::Column::Rating)) { if (columns_[col] == static_cast<int>(Playlist::Column::Rating)) {
float parsed_search = Utilities::ParseSearchRating(search); float parsed_search = Utilities::ParseSearchRating(search);
if (prefix == QLatin1Char('=')) { if (prefix == QLatin1Char('=')) {
cmp = new FloatEqComparator(parsed_search); cmp = new PlaylistFloatEqComparator(parsed_search);
} }
else if (prefix == QLatin1String("!=") || prefix == QLatin1String("<>")) { else if (prefix == QLatin1String("!=") || prefix == QLatin1String("<>")) {
cmp = new FloatNeComparator(parsed_search); cmp = new PlaylistFloatNeComparator(parsed_search);
} }
else if (prefix == QLatin1Char('>')) { else if (prefix == QLatin1Char('>')) {
cmp = new FloatGtComparator(parsed_search); cmp = new PlaylistFloatGtComparator(parsed_search);
} }
else if (prefix == QLatin1String(">=")) { else if (prefix == QLatin1String(">=")) {
cmp = new FloatGeComparator(parsed_search); cmp = new PlaylistFloatGeComparator(parsed_search);
} }
else if (prefix == QLatin1Char('<')) { else if (prefix == QLatin1Char('<')) {
cmp = new FloatLtComparator(parsed_search); cmp = new PlaylistFloatLtComparator(parsed_search);
} }
else if (prefix == QLatin1String("<=")) { else if (prefix == QLatin1String("<=")) {
cmp = new FloatLeComparator(parsed_search); cmp = new PlaylistFloatLeComparator(parsed_search);
} }
else { else {
cmp = new FloatEqComparator(parsed_search); cmp = new PlaylistFloatEqComparator(parsed_search);
} }
} }
else if (prefix == QLatin1String("!=") || prefix == QLatin1String("<>")) { else if (prefix == QLatin1String("!=") || prefix == QLatin1String("<>")) {
cmp = new NeComparator(search); cmp = new PlaylistNeComparator(search);
} }
else if (!col.isEmpty() && columns_.contains(col) && numerical_columns_.contains(columns_[col])) { else if (!col.isEmpty() && columns_.contains(col) && numerical_columns_.contains(columns_[col])) {
// the length column contains the time in seconds (nanoseconds, actually - the "nano" part is handled by the DropTailComparatorDecorator, though). // The length column contains the time in seconds (nanoseconds, actually - the "nano" part is handled by the DropTailComparatorDecorator, though).
int search_value = 0; int search_value = 0;
if (columns_[col] == static_cast<int>(Playlist::Column::Length)) { if (columns_[col] == static_cast<int>(Playlist::Column::Length)) {
search_value = Utilities::ParseSearchTime(search); search_value = Utilities::ParseSearchTime(search);
@@ -555,53 +558,53 @@ FilterTree *FilterParser::createSearchTermTreeNode(const QString &col, const QSt
else { else {
search_value = search.toInt(); search_value = search.toInt();
} }
// alright, back to deciding which comparator we'll use // Alright, back to deciding which comparator we'll use
if (prefix == QLatin1Char('>')) { if (prefix == QLatin1Char('>')) {
cmp = new GtComparator(search_value); cmp = new PlaylistGtComparator(search_value);
} }
else if (prefix == QLatin1String(">=")) { else if (prefix == QLatin1String(">=")) {
cmp = new GeComparator(search_value); cmp = new PlaylistGeComparator(search_value);
} }
else if (prefix == QLatin1Char('<')) { else if (prefix == QLatin1Char('<')) {
cmp = new LtComparator(search_value); cmp = new PlaylistLtComparator(search_value);
} }
else if (prefix == QLatin1String("<=")) { else if (prefix == QLatin1String("<=")) {
cmp = new LeComparator(search_value); cmp = new PlaylistLeComparator(search_value);
} }
else { else {
// convert back because for time/rating // Convert back because for time/rating
cmp = new EqComparator(QString::number(search_value)); cmp = new PlaylistEqComparator(QString::number(search_value));
} }
} }
else { else {
if (prefix == QLatin1Char('=')) { if (prefix == QLatin1Char('=')) {
cmp = new EqComparator(search); cmp = new PlaylistEqComparator(search);
} }
else if (prefix == QLatin1Char('>')) { else if (prefix == QLatin1Char('>')) {
cmp = new LexicalGtComparator(search); cmp = new PlaylistLexicalGtComparator(search);
} }
else if (prefix == QLatin1String(">=")) { else if (prefix == QLatin1String(">=")) {
cmp = new LexicalGeComparator(search); cmp = new PlaylistLexicalGeComparator(search);
} }
else if (prefix == QLatin1Char('<')) { else if (prefix == QLatin1Char('<')) {
cmp = new LexicalLtComparator(search); cmp = new PlaylistLexicalLtComparator(search);
} }
else if (prefix == QLatin1String("<=")) { else if (prefix == QLatin1String("<=")) {
cmp = new LexicalLeComparator(search); cmp = new PlaylistLexicalLeComparator(search);
} }
else { else {
cmp = new DefaultComparator(search); cmp = new PlaylistDefaultComparator(search);
} }
} }
if (columns_.contains(col)) { if (columns_.contains(col)) {
if (columns_[col] == static_cast<int>(Playlist::Column::Length)) { if (columns_[col] == static_cast<int>(Playlist::Column::Length)) {
cmp = new DropTailComparatorDecorator(cmp); cmp = new PlaylistDropTailComparatorDecorator(cmp);
} }
return new FilterColumnTerm(columns_[col], cmp); return new PlaylistFilterColumnTerm(columns_[col], cmp);
} }
else { else {
return new FilterTerm(cmp, columns_.values()); return new PlaylistFilterTerm(cmp, columns_.values());
} }
} }

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com> * Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -31,11 +32,11 @@ class QAbstractItemModel;
class QModelIndex; class QModelIndex;
// Structure for filter parse tree // Structure for filter parse tree
class FilterTree { class PlaylistFilterTree {
public: public:
FilterTree() = default; PlaylistFilterTree() = default;
virtual ~FilterTree() {} virtual ~PlaylistFilterTree() {}
virtual bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const = 0; virtual bool accept(const int row, const QModelIndex &parent, const QAbstractItemModel *const model) const = 0;
enum class FilterType { enum class FilterType {
Nop = 0, Nop = 0,
Or, Or,
@@ -46,13 +47,13 @@ class FilterTree {
}; };
virtual FilterType type() = 0; virtual FilterType type() = 0;
private: private:
Q_DISABLE_COPY(FilterTree) Q_DISABLE_COPY(PlaylistFilterTree)
}; };
// Trivial filter that accepts *anything* // Trivial filter that accepts *anything*
class NopFilter : public FilterTree { class PlaylistNopFilter : public PlaylistFilterTree {
public: public:
bool accept(int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override { Q_UNUSED(row); Q_UNUSED(parent); Q_UNUSED(model); return true; } bool accept(const int row, const QModelIndex &parent, const QAbstractItemModel *const model) const override { Q_UNUSED(row); Q_UNUSED(parent); Q_UNUSED(model); return true; }
FilterType type() override { return FilterType::Nop; } FilterType type() override { return FilterType::Nop; }
}; };
@@ -70,24 +71,24 @@ class NopFilter : public FilterTree {
// string ::= [^:-()" ]+ | '"' [^"]+ '"' // string ::= [^:-()" ]+ | '"' [^"]+ '"'
// prefix ::= '=' | '<' | '>' | '<=' | '>=' // prefix ::= '=' | '<' | '>' | '<=' | '>='
// col ::= "title" | "artist" | ... // col ::= "title" | "artist" | ...
class FilterParser { class PlaylistFilterParser {
public: public:
explicit FilterParser(const QString &filter, const QMap<QString, int> &columns, const QSet<int> &numerical_cols); explicit PlaylistFilterParser(const QString &filter, const QMap<QString, int> &columns, const QSet<int> &numerical_cols);
FilterTree *parse(); PlaylistFilterTree *parse();
private: private:
void advance(); void advance();
FilterTree *parseOrGroup(); PlaylistFilterTree *parseOrGroup();
FilterTree *parseAndGroup(); PlaylistFilterTree *parseAndGroup();
// Check if iter is at the start of 'AND' if so, step over it and return true if not, return false and leave iter where it was // Check if iter is at the start of 'AND' if so, step over it and return true if not, return false and leave iter where it was
bool checkAnd(); bool checkAnd();
// Check if iter is at the start of 'OR' // Check if iter is at the start of 'OR'
bool checkOr(bool step_over = true); bool checkOr(const bool step_over = true);
FilterTree *parseSearchExpression(); PlaylistFilterTree *parseSearchExpression();
FilterTree *parseSearchTerm(); PlaylistFilterTree *parseSearchTerm();
FilterTree *createSearchTermTreeNode(const QString &col, const QString &prefix, const QString &search) const; PlaylistFilterTree *createSearchTermTreeNode(const QString &col, const QString &prefix, const QString &search) const;
QString::const_iterator iter_; QString::const_iterator iter_;
QString::const_iterator end_; QString::const_iterator end_;