2020-09-17 17:50:17 +02:00
|
|
|
/*
|
|
|
|
* Strawberry Music Player
|
|
|
|
* This file was part of Clementine.
|
|
|
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
|
|
*
|
|
|
|
* 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/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
2021-06-21 19:52:37 +02:00
|
|
|
#include <algorithm>
|
2023-07-21 05:55:24 +02:00
|
|
|
#include <memory>
|
2021-06-21 15:40:25 +02:00
|
|
|
|
2020-09-17 17:50:17 +02:00
|
|
|
#include <QWizardPage>
|
|
|
|
#include <QList>
|
|
|
|
#include <QString>
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
#include <QScrollBar>
|
|
|
|
|
2023-07-21 05:55:24 +02:00
|
|
|
#include "core/scoped_ptr.h"
|
|
|
|
#include "core/shared_ptr.h"
|
2020-09-17 17:50:17 +02:00
|
|
|
#include "playlistquerygenerator.h"
|
|
|
|
#include "smartplaylistquerywizardplugin.h"
|
|
|
|
#include "smartplaylistsearchtermwidget.h"
|
|
|
|
#include "ui_smartplaylistquerysearchpage.h"
|
|
|
|
#include "ui_smartplaylistquerysortpage.h"
|
|
|
|
|
2023-07-21 05:55:24 +02:00
|
|
|
using std::make_unique;
|
|
|
|
using std::make_shared;
|
|
|
|
|
2021-06-20 19:04:08 +02:00
|
|
|
class SmartPlaylistQueryWizardPlugin::SearchPage : public QWizardPage { // clazy:exclude=missing-qobject-macro
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
friend class SmartPlaylistQueryWizardPlugin;
|
|
|
|
|
|
|
|
public:
|
2021-06-20 19:04:08 +02:00
|
|
|
explicit SearchPage(QWidget *parent = nullptr)
|
2021-06-20 23:53:28 +02:00
|
|
|
: QWizardPage(parent),
|
|
|
|
layout_(nullptr),
|
|
|
|
new_term_(nullptr),
|
|
|
|
preview_(nullptr),
|
|
|
|
ui_(new Ui_SmartPlaylistQuerySearchPage) {
|
|
|
|
|
2020-09-17 17:50:17 +02:00
|
|
|
ui_->setupUi(this);
|
2021-06-20 23:53:28 +02:00
|
|
|
|
2020-09-17 17:50:17 +02:00
|
|
|
}
|
|
|
|
|
2021-03-21 18:40:33 +01:00
|
|
|
bool isComplete() const override {
|
2021-06-21 19:52:37 +02:00
|
|
|
if (ui_->type->currentIndex() == 2) { // All songs
|
2020-09-17 17:50:17 +02:00
|
|
|
return true;
|
2021-06-21 19:52:37 +02:00
|
|
|
}
|
2021-08-25 02:58:54 +02:00
|
|
|
return !std::any_of(terms_.begin(), terms_.end(), [](SmartPlaylistSearchTermWidget *widget) { return !widget->Term().is_valid(); });
|
2020-09-17 17:50:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QVBoxLayout *layout_;
|
|
|
|
QList<SmartPlaylistSearchTermWidget*> terms_;
|
|
|
|
SmartPlaylistSearchTermWidget *new_term_;
|
|
|
|
|
|
|
|
SmartPlaylistSearchPreview *preview_;
|
|
|
|
|
2023-07-21 05:55:24 +02:00
|
|
|
ScopedPtr<Ui_SmartPlaylistQuerySearchPage> ui_;
|
2020-09-17 17:50:17 +02:00
|
|
|
};
|
|
|
|
|
2021-06-20 19:04:08 +02:00
|
|
|
class SmartPlaylistQueryWizardPlugin::SortPage : public QWizardPage { // clazy:exclude=missing-qobject-macro
|
2020-09-17 17:50:17 +02:00
|
|
|
public:
|
|
|
|
SortPage(SmartPlaylistQueryWizardPlugin *plugin, QWidget *parent, int next_id)
|
|
|
|
: QWizardPage(parent), next_id_(next_id), plugin_(plugin) {}
|
|
|
|
|
2021-03-21 18:40:33 +01:00
|
|
|
void showEvent(QShowEvent*) override { plugin_->UpdateSortPreview(); }
|
2020-09-17 17:50:17 +02:00
|
|
|
|
2021-03-21 18:40:33 +01:00
|
|
|
int nextId() const override { return next_id_; }
|
2020-09-17 17:50:17 +02:00
|
|
|
int next_id_;
|
|
|
|
|
|
|
|
SmartPlaylistQueryWizardPlugin *plugin_;
|
|
|
|
};
|
|
|
|
|
2023-07-21 05:55:24 +02:00
|
|
|
SmartPlaylistQueryWizardPlugin::SmartPlaylistQueryWizardPlugin(Application *app, SharedPtr<CollectionBackend> collection_backend, QObject *parent)
|
2023-03-11 17:18:35 +01:00
|
|
|
: SmartPlaylistWizardPlugin(app, collection_backend, parent),
|
2020-09-17 17:50:17 +02:00
|
|
|
search_page_(nullptr),
|
|
|
|
previous_scrollarea_max_(0) {}
|
|
|
|
|
2021-06-21 15:38:58 +02:00
|
|
|
SmartPlaylistQueryWizardPlugin::~SmartPlaylistQueryWizardPlugin() = default;
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
QString SmartPlaylistQueryWizardPlugin::name() const { return tr("Collection search"); }
|
|
|
|
|
|
|
|
QString SmartPlaylistQueryWizardPlugin::description() const {
|
|
|
|
return tr("Find songs in your collection that match the criteria you specify.");
|
|
|
|
}
|
|
|
|
|
|
|
|
int SmartPlaylistQueryWizardPlugin::CreatePages(QWizard *wizard, int finish_page_id) {
|
|
|
|
|
|
|
|
// Create the UI
|
|
|
|
search_page_ = new SearchPage(wizard);
|
|
|
|
|
|
|
|
QWizardPage *sort_page = new SortPage(this, wizard, finish_page_id);
|
2023-07-21 05:55:24 +02:00
|
|
|
sort_ui_ = make_unique<Ui_SmartPlaylistQuerySortPage>();
|
2020-09-17 17:50:17 +02:00
|
|
|
sort_ui_->setupUi(sort_page);
|
|
|
|
|
|
|
|
sort_ui_->limit_value->setValue(PlaylistGenerator::kDefaultLimit);
|
|
|
|
|
2021-01-26 16:48:04 +01:00
|
|
|
QObject::connect(search_page_->ui_->type, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SmartPlaylistQueryWizardPlugin::SearchTypeChanged);
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
// Create the new search term widget
|
2023-03-11 17:18:35 +01:00
|
|
|
search_page_->new_term_ = new SmartPlaylistSearchTermWidget(collection_backend_, search_page_);
|
2020-09-17 17:50:17 +02:00
|
|
|
search_page_->new_term_->SetActive(false);
|
2021-01-26 16:48:04 +01:00
|
|
|
QObject::connect(search_page_->new_term_, &SmartPlaylistSearchTermWidget::Clicked, this, &SmartPlaylistQueryWizardPlugin::AddSearchTerm);
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
// Add an empty initial term
|
|
|
|
search_page_->layout_ = static_cast<QVBoxLayout*>(search_page_->ui_->terms_scroll_area_content->layout());
|
|
|
|
search_page_->layout_->addWidget(search_page_->new_term_);
|
|
|
|
AddSearchTerm();
|
|
|
|
|
|
|
|
// Ensure that the terms are scrolled to the bottom when a new one is added
|
2021-01-26 16:48:04 +01:00
|
|
|
QObject::connect(search_page_->ui_->terms_scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, this, &SmartPlaylistQueryWizardPlugin::MoveTermListToBottom);
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
// Add the preview widget at the bottom of the search terms page
|
|
|
|
QVBoxLayout *terms_page_layout = static_cast<QVBoxLayout*>(search_page_->layout());
|
|
|
|
terms_page_layout->addStretch();
|
|
|
|
search_page_->preview_ = new SmartPlaylistSearchPreview(search_page_);
|
|
|
|
search_page_->preview_->set_application(app_);
|
2023-03-11 17:18:35 +01:00
|
|
|
search_page_->preview_->set_collection(collection_backend_);
|
2020-09-17 17:50:17 +02:00
|
|
|
terms_page_layout->addWidget(search_page_->preview_);
|
|
|
|
|
|
|
|
// Add sort field texts
|
2023-02-18 14:09:27 +01:00
|
|
|
for (int i = 0; i < static_cast<int>(SmartPlaylistSearchTerm::Field::FieldCount); ++i) {
|
2022-06-13 00:23:42 +02:00
|
|
|
const SmartPlaylistSearchTerm::Field field = static_cast<SmartPlaylistSearchTerm::Field>(i);
|
2020-09-17 17:50:17 +02:00
|
|
|
const QString field_name = SmartPlaylistSearchTerm::FieldName(field);
|
|
|
|
sort_ui_->field_value->addItem(field_name);
|
|
|
|
}
|
2021-01-26 16:48:04 +01:00
|
|
|
QObject::connect(sort_ui_->field_value, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SmartPlaylistQueryWizardPlugin::UpdateSortOrder);
|
2020-09-17 17:50:17 +02:00
|
|
|
UpdateSortOrder();
|
|
|
|
|
|
|
|
// Set the sort and limit radio buttons back to their defaults - they would
|
|
|
|
// have been changed by setupUi
|
|
|
|
sort_ui_->random->setChecked(true);
|
|
|
|
sort_ui_->limit_none->setChecked(true);
|
|
|
|
|
|
|
|
// Set up the preview widget that's already at the bottom of the sort page
|
|
|
|
sort_ui_->preview->set_application(app_);
|
2023-03-11 17:18:35 +01:00
|
|
|
sort_ui_->preview->set_collection(collection_backend_);
|
2021-01-26 16:48:04 +01:00
|
|
|
QObject::connect(sort_ui_->field, &QRadioButton::toggled, this, &SmartPlaylistQueryWizardPlugin::UpdateSortPreview);
|
|
|
|
QObject::connect(sort_ui_->field_value, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SmartPlaylistQueryWizardPlugin::UpdateSortPreview);
|
|
|
|
QObject::connect(sort_ui_->limit_limit, &QRadioButton::toggled, this, &SmartPlaylistQueryWizardPlugin::UpdateSortPreview);
|
|
|
|
QObject::connect(sort_ui_->limit_none, &QRadioButton::toggled, this, &SmartPlaylistQueryWizardPlugin::UpdateSortPreview);
|
|
|
|
QObject::connect(sort_ui_->limit_value, QOverload<int>::of(&QSpinBox::valueChanged), this, &SmartPlaylistQueryWizardPlugin::UpdateSortPreview);
|
|
|
|
QObject::connect(sort_ui_->order, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SmartPlaylistQueryWizardPlugin::UpdateSortPreview);
|
|
|
|
QObject::connect(sort_ui_->random, &QRadioButton::toggled, this, &SmartPlaylistQueryWizardPlugin::UpdateSortPreview);
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
// Configure the page text
|
|
|
|
search_page_->setTitle(tr("Search terms"));
|
|
|
|
search_page_->setSubTitle(tr("A song will be included in the playlist if it matches these conditions."));
|
|
|
|
sort_page->setTitle(tr("Search options"));
|
|
|
|
sort_page->setSubTitle(tr("Choose how the playlist is sorted and how many songs it will contain."));
|
|
|
|
|
|
|
|
// Add the pages
|
|
|
|
const int first_page = wizard->addPage(search_page_);
|
|
|
|
wizard->addPage(sort_page);
|
|
|
|
return first_page;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SmartPlaylistQueryWizardPlugin::SetGenerator(PlaylistGeneratorPtr g) {
|
|
|
|
|
2023-07-21 05:55:24 +02:00
|
|
|
SharedPtr<PlaylistQueryGenerator> gen = std::dynamic_pointer_cast<PlaylistQueryGenerator>(g);
|
2020-09-17 17:50:17 +02:00
|
|
|
if (!gen) return;
|
|
|
|
SmartPlaylistSearch search = gen->search();
|
|
|
|
|
|
|
|
// Search type
|
2023-02-18 14:09:27 +01:00
|
|
|
search_page_->ui_->type->setCurrentIndex(static_cast<int>(search.search_type_));
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
// Search terms
|
|
|
|
qDeleteAll(search_page_->terms_);
|
|
|
|
search_page_->terms_.clear();
|
|
|
|
|
2021-06-12 20:53:23 +02:00
|
|
|
for (const SmartPlaylistSearchTerm &term : search.terms_) {
|
2020-09-17 17:50:17 +02:00
|
|
|
AddSearchTerm();
|
|
|
|
search_page_->terms_.last()->SetTerm(term);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort order
|
2023-02-18 14:09:27 +01:00
|
|
|
if (search.sort_type_ == SmartPlaylistSearch::SortType::Random) {
|
2020-09-17 17:50:17 +02:00
|
|
|
sort_ui_->random->setChecked(true);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
sort_ui_->field->setChecked(true);
|
2023-02-18 14:09:27 +01:00
|
|
|
sort_ui_->order->setCurrentIndex(search.sort_type_ == SmartPlaylistSearch::SortType::FieldAsc ? 0 : 1);
|
|
|
|
sort_ui_->field_value->setCurrentIndex(static_cast<int>(search.sort_field_));
|
2020-09-17 17:50:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Limit
|
|
|
|
if (search.limit_ == -1) {
|
|
|
|
sort_ui_->limit_none->setChecked(true);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
sort_ui_->limit_limit->setChecked(true);
|
|
|
|
sort_ui_->limit_value->setValue(search.limit_);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
PlaylistGeneratorPtr SmartPlaylistQueryWizardPlugin::CreateGenerator() const {
|
|
|
|
|
2023-07-21 05:55:24 +02:00
|
|
|
SharedPtr<PlaylistQueryGenerator> gen = make_shared<PlaylistQueryGenerator>();
|
2020-09-17 17:50:17 +02:00
|
|
|
gen->Load(MakeSearch());
|
|
|
|
|
|
|
|
return std::static_pointer_cast<PlaylistGenerator>(gen);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SmartPlaylistQueryWizardPlugin::UpdateSortOrder() {
|
|
|
|
|
2022-06-13 00:23:42 +02:00
|
|
|
const SmartPlaylistSearchTerm::Field field = static_cast<SmartPlaylistSearchTerm::Field>(sort_ui_->field_value->currentIndex());
|
2020-09-17 17:50:17 +02:00
|
|
|
const SmartPlaylistSearchTerm::Type type = SmartPlaylistSearchTerm::TypeOf(field);
|
|
|
|
const QString asc = SmartPlaylistSearchTerm::FieldSortOrderText(type, true);
|
|
|
|
const QString desc = SmartPlaylistSearchTerm::FieldSortOrderText(type, false);
|
|
|
|
|
|
|
|
const int old_current_index = sort_ui_->order->currentIndex();
|
|
|
|
sort_ui_->order->clear();
|
|
|
|
sort_ui_->order->addItem(asc);
|
|
|
|
sort_ui_->order->addItem(desc);
|
|
|
|
sort_ui_->order->setCurrentIndex(old_current_index);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SmartPlaylistQueryWizardPlugin::AddSearchTerm() {
|
|
|
|
|
2023-03-11 17:18:35 +01:00
|
|
|
SmartPlaylistSearchTermWidget *widget = new SmartPlaylistSearchTermWidget(collection_backend_, search_page_);
|
2021-01-26 16:48:04 +01:00
|
|
|
QObject::connect(widget, &SmartPlaylistSearchTermWidget::RemoveClicked, this, &SmartPlaylistQueryWizardPlugin::RemoveSearchTerm);
|
|
|
|
QObject::connect(widget, &SmartPlaylistSearchTermWidget::Changed, this, &SmartPlaylistQueryWizardPlugin::UpdateTermPreview);
|
2020-09-17 17:50:17 +02:00
|
|
|
|
2021-10-30 02:21:29 +02:00
|
|
|
search_page_->layout_->insertWidget(static_cast<int>(search_page_->terms_.count()), widget);
|
2020-09-17 17:50:17 +02:00
|
|
|
search_page_->terms_ << widget;
|
|
|
|
|
|
|
|
UpdateTermPreview();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SmartPlaylistQueryWizardPlugin::RemoveSearchTerm() {
|
|
|
|
|
|
|
|
SmartPlaylistSearchTermWidget *widget = qobject_cast<SmartPlaylistSearchTermWidget*>(sender());
|
|
|
|
if (!widget) return;
|
|
|
|
|
2021-10-30 02:21:29 +02:00
|
|
|
const qint64 index = search_page_->terms_.indexOf(widget);
|
2020-09-17 17:50:17 +02:00
|
|
|
if (index == -1) return;
|
|
|
|
|
|
|
|
search_page_->terms_.takeAt(index)->deleteLater();
|
|
|
|
UpdateTermPreview();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SmartPlaylistQueryWizardPlugin::UpdateTermPreview() {
|
|
|
|
|
|
|
|
SmartPlaylistSearch search = MakeSearch();
|
|
|
|
emit search_page_->completeChanged();
|
|
|
|
// When removing last term, update anyway the search
|
|
|
|
if (!search.is_valid() && !search_page_->terms_.isEmpty()) return;
|
|
|
|
|
|
|
|
// Don't apply limits in the term page
|
|
|
|
search.limit_ = -1;
|
|
|
|
|
|
|
|
search_page_->preview_->Update(search);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SmartPlaylistQueryWizardPlugin::UpdateSortPreview() {
|
|
|
|
|
|
|
|
SmartPlaylistSearch search = MakeSearch();
|
|
|
|
if (!search.is_valid()) return;
|
|
|
|
|
|
|
|
sort_ui_->preview->Update(search);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
SmartPlaylistSearch SmartPlaylistQueryWizardPlugin::MakeSearch() const {
|
|
|
|
|
|
|
|
SmartPlaylistSearch ret;
|
|
|
|
|
|
|
|
// Search type
|
2022-06-13 00:23:42 +02:00
|
|
|
ret.search_type_ = static_cast<SmartPlaylistSearch::SearchType>(search_page_->ui_->type->currentIndex());
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
// Search terms
|
|
|
|
for (SmartPlaylistSearchTermWidget *widget : search_page_->terms_) {
|
|
|
|
SmartPlaylistSearchTerm term = widget->Term();
|
|
|
|
if (term.is_valid()) ret.terms_ << term;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort order
|
|
|
|
if (sort_ui_->random->isChecked()) {
|
2023-02-18 14:09:27 +01:00
|
|
|
ret.sort_type_ = SmartPlaylistSearch::SortType::Random;
|
2020-09-17 17:50:17 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
const bool ascending = sort_ui_->order->currentIndex() == 0;
|
2023-02-18 14:09:27 +01:00
|
|
|
ret.sort_type_ = ascending ? SmartPlaylistSearch::SortType::FieldAsc : SmartPlaylistSearch::SortType::FieldDesc;
|
2022-06-13 00:23:42 +02:00
|
|
|
ret.sort_field_ = static_cast<SmartPlaylistSearchTerm::Field>(sort_ui_->field_value->currentIndex());
|
2020-09-17 17:50:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Limit
|
2021-06-30 16:14:08 +02:00
|
|
|
if (sort_ui_->limit_none->isChecked()) {
|
2020-09-17 17:50:17 +02:00
|
|
|
ret.limit_ = -1;
|
2021-06-30 16:14:08 +02:00
|
|
|
}
|
|
|
|
else {
|
2020-09-17 17:50:17 +02:00
|
|
|
ret.limit_ = sort_ui_->limit_value->value();
|
2021-06-30 16:14:08 +02:00
|
|
|
}
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SmartPlaylistQueryWizardPlugin::SearchTypeChanged() {
|
|
|
|
|
|
|
|
const bool all = search_page_->ui_->type->currentIndex() == 2;
|
|
|
|
search_page_->ui_->terms_scroll_area_content->setEnabled(!all);
|
|
|
|
|
|
|
|
UpdateTermPreview();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SmartPlaylistQueryWizardPlugin::MoveTermListToBottom(int min, int max) {
|
|
|
|
|
|
|
|
Q_UNUSED(min);
|
|
|
|
// Only scroll to the bottom if a new term is added
|
2021-08-23 21:21:08 +02:00
|
|
|
if (previous_scrollarea_max_ < max) {
|
2020-09-17 17:50:17 +02:00
|
|
|
search_page_->ui_->terms_scroll_area->verticalScrollBar()->setValue(max);
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
2020-09-17 17:50:17 +02:00
|
|
|
|
|
|
|
previous_scrollarea_max_ = max;
|
|
|
|
|
|
|
|
}
|