1 /* This file is part of Clementine.
2 Copyright 2010, David Sansome <me@davidsansome.com>
3
4 Clementine is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 Clementine is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with Clementine. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "querygenerator.h"
19 #include "querywizardplugin.h"
20 #include "searchtermwidget.h"
21 #include "ui_querysearchpage.h"
22 #include "ui_querysortpage.h"
23 #include "core/logging.h"
24
25 #include <QScrollBar>
26 #include <QWizardPage>
27
28 namespace smart_playlists {
29
30 class QueryWizardPlugin::SearchPage : public QWizardPage {
31 friend class QueryWizardPlugin;
32
33 public:
SearchPage(QWidget * parent=0)34 SearchPage(QWidget* parent = 0)
35 : QWizardPage(parent), ui_(new Ui_SmartPlaylistQuerySearchPage) {
36 ui_->setupUi(this);
37 }
38
isComplete() const39 bool isComplete() const {
40 if (ui_->type->currentIndex() == 2) // All songs
41 return true;
42
43 for (SearchTermWidget* widget : terms_) {
44 if (!widget->Term().is_valid()) return false;
45 }
46 return true;
47 }
48
49 QVBoxLayout* layout_;
50 QList<SearchTermWidget*> terms_;
51 SearchTermWidget* new_term_;
52
53 SearchPreview* preview_;
54
55 std::unique_ptr<Ui_SmartPlaylistQuerySearchPage> ui_;
56 };
57
58 class QueryWizardPlugin::SortPage : public QWizardPage {
59 public:
SortPage(QueryWizardPlugin * plugin,QWidget * parent,int next_id)60 SortPage(QueryWizardPlugin* plugin, QWidget* parent, int next_id)
61 : QWizardPage(parent), next_id_(next_id), plugin_(plugin) {}
62
showEvent(QShowEvent *)63 void showEvent(QShowEvent*) { plugin_->UpdateSortPreview(); }
64
nextId() const65 int nextId() const { return next_id_; }
66 int next_id_;
67
68 QueryWizardPlugin* plugin_;
69 };
70
QueryWizardPlugin(Application * app,LibraryBackend * library,QObject * parent)71 QueryWizardPlugin::QueryWizardPlugin(Application* app, LibraryBackend* library,
72 QObject* parent)
73 : WizardPlugin(app, library, parent),
74 search_page_(nullptr),
75 previous_scrollarea_max_(0) {}
76
~QueryWizardPlugin()77 QueryWizardPlugin::~QueryWizardPlugin() {}
78
name() const79 QString QueryWizardPlugin::name() const { return tr("Library search"); }
80
description() const81 QString QueryWizardPlugin::description() const {
82 return tr("Find songs in your library that match the criteria you specify.");
83 }
84
CreatePages(QWizard * wizard,int finish_page_id)85 int QueryWizardPlugin::CreatePages(QWizard* wizard, int finish_page_id) {
86 // Create the UI
87 search_page_ = new SearchPage(wizard);
88
89 QWizardPage* sort_page = new SortPage(this, wizard, finish_page_id);
90 sort_ui_.reset(new Ui_SmartPlaylistQuerySortPage);
91 sort_ui_->setupUi(sort_page);
92
93 sort_ui_->limit_value->setValue(Generator::kDefaultLimit);
94
95 connect(search_page_->ui_->type, SIGNAL(currentIndexChanged(int)),
96 SLOT(SearchTypeChanged()));
97
98 // Create the new search term widget
99 search_page_->new_term_ = new SearchTermWidget(library_, search_page_);
100 search_page_->new_term_->SetActive(false);
101 connect(search_page_->new_term_, SIGNAL(Clicked()), SLOT(AddSearchTerm()));
102
103 // Add an empty initial term
104 search_page_->layout_ = static_cast<QVBoxLayout*>(
105 search_page_->ui_->terms_scroll_area_content->layout());
106 search_page_->layout_->addWidget(search_page_->new_term_);
107 AddSearchTerm();
108
109 // Ensure that the terms are scrolled to the bottom when a new one is added
110 connect(search_page_->ui_->terms_scroll_area->verticalScrollBar(),
111 SIGNAL(rangeChanged(int, int)), this,
112 SLOT(MoveTermListToBottom(int, int)));
113
114 // Add the preview widget at the bottom of the search terms page
115 QVBoxLayout* terms_page_layout =
116 static_cast<QVBoxLayout*>(search_page_->layout());
117 terms_page_layout->addStretch();
118 search_page_->preview_ = new SearchPreview(search_page_);
119 search_page_->preview_->set_application(app_);
120 search_page_->preview_->set_library(library_);
121 terms_page_layout->addWidget(search_page_->preview_);
122
123 // Add sort field texts
124 for (int i = 0; i < SearchTerm::FieldCount; ++i) {
125 const SearchTerm::Field field = SearchTerm::Field(i);
126 const QString field_name = SearchTerm::FieldName(field);
127 sort_ui_->field_value->addItem(field_name);
128 }
129 connect(sort_ui_->field_value, SIGNAL(currentIndexChanged(int)),
130 SLOT(UpdateSortOrder()));
131 UpdateSortOrder();
132
133 // Set the sort and limit radio buttons back to their defaults - they would
134 // have been changed by setupUi
135 sort_ui_->random->setChecked(true);
136 sort_ui_->limit_none->setChecked(true);
137
138 // Set up the preview widget that's already at the bottom of the sort page
139 sort_ui_->preview->set_application(app_);
140 sort_ui_->preview->set_library(library_);
141 connect(sort_ui_->field, SIGNAL(toggled(bool)), SLOT(UpdateSortPreview()));
142 connect(sort_ui_->field_value, SIGNAL(currentIndexChanged(int)),
143 SLOT(UpdateSortPreview()));
144 connect(sort_ui_->limit_limit, SIGNAL(toggled(bool)),
145 SLOT(UpdateSortPreview()));
146 connect(sort_ui_->limit_none, SIGNAL(toggled(bool)),
147 SLOT(UpdateSortPreview()));
148 connect(sort_ui_->limit_value, SIGNAL(valueChanged(QString)),
149 SLOT(UpdateSortPreview()));
150 connect(sort_ui_->order, SIGNAL(currentIndexChanged(int)),
151 SLOT(UpdateSortPreview()));
152 connect(sort_ui_->random, SIGNAL(toggled(bool)), SLOT(UpdateSortPreview()));
153
154 // Configure the page text
155 search_page_->setTitle(tr("Search terms"));
156 search_page_->setSubTitle(
157 tr("A song will be included in the playlist if it matches these "
158 "conditions."));
159 sort_page->setTitle(tr("Search options"));
160 sort_page->setSubTitle(tr(
161 "Choose how the playlist is sorted and how many songs it will contain."));
162
163 // Add the pages
164 const int first_page = wizard->addPage(search_page_);
165 wizard->addPage(sort_page);
166 return first_page;
167 }
168
SetGenerator(GeneratorPtr g)169 void QueryWizardPlugin::SetGenerator(GeneratorPtr g) {
170 std::shared_ptr<QueryGenerator> gen =
171 std::dynamic_pointer_cast<QueryGenerator>(g);
172 if (!gen) return;
173 Search search = gen->search();
174
175 // Search type
176 search_page_->ui_->type->setCurrentIndex(search.search_type_);
177
178 // Search terms
179 qDeleteAll(search_page_->terms_);
180 search_page_->terms_.clear();
181
182 for (const SearchTerm& term : search.terms_) {
183 AddSearchTerm();
184 search_page_->terms_.last()->SetTerm(term);
185 }
186
187 // Sort order
188 if (search.sort_type_ == Search::Sort_Random) {
189 sort_ui_->random->setChecked(true);
190 } else {
191 sort_ui_->field->setChecked(true);
192 sort_ui_->order->setCurrentIndex(
193 search.sort_type_ == Search::Sort_FieldAsc ? 0 : 1);
194 sort_ui_->field_value->setCurrentIndex(search.sort_field_);
195 }
196
197 // Limit
198 if (search.limit_ == -1) {
199 sort_ui_->limit_none->setChecked(true);
200 } else {
201 sort_ui_->limit_limit->setChecked(true);
202 sort_ui_->limit_value->setValue(search.limit_);
203 }
204 }
205
CreateGenerator() const206 GeneratorPtr QueryWizardPlugin::CreateGenerator() const {
207 std::shared_ptr<QueryGenerator> gen(new QueryGenerator);
208 gen->Load(MakeSearch());
209
210 return std::static_pointer_cast<Generator>(gen);
211 }
212
UpdateSortOrder()213 void QueryWizardPlugin::UpdateSortOrder() {
214 const SearchTerm::Field field =
215 SearchTerm::Field(sort_ui_->field_value->currentIndex());
216 const SearchTerm::Type type = SearchTerm::TypeOf(field);
217 const QString asc = SearchTerm::FieldSortOrderText(type, true);
218 const QString desc = SearchTerm::FieldSortOrderText(type, false);
219
220 const int old_current_index = sort_ui_->order->currentIndex();
221 sort_ui_->order->clear();
222 sort_ui_->order->addItem(asc);
223 sort_ui_->order->addItem(desc);
224 sort_ui_->order->setCurrentIndex(old_current_index);
225 }
226
AddSearchTerm()227 void QueryWizardPlugin::AddSearchTerm() {
228 SearchTermWidget* widget = new SearchTermWidget(library_, search_page_);
229 connect(widget, SIGNAL(RemoveClicked()), SLOT(RemoveSearchTerm()));
230 connect(widget, SIGNAL(Changed()), SLOT(UpdateTermPreview()));
231
232 search_page_->layout_->insertWidget(search_page_->terms_.count(), widget);
233 search_page_->terms_ << widget;
234
235 UpdateTermPreview();
236 }
237
RemoveSearchTerm()238 void QueryWizardPlugin::RemoveSearchTerm() {
239 SearchTermWidget* widget = qobject_cast<SearchTermWidget*>(sender());
240 if (!widget) return;
241
242 const int index = search_page_->terms_.indexOf(widget);
243 if (index == -1) return;
244
245 search_page_->terms_.takeAt(index)->deleteLater();
246 UpdateTermPreview();
247 }
248
UpdateTermPreview()249 void QueryWizardPlugin::UpdateTermPreview() {
250 Search search = MakeSearch();
251 emit search_page_->completeChanged();
252 // When removing last term, update anyway the search
253 if (!search.is_valid() && !search_page_->terms_.isEmpty()) return;
254
255 // Don't apply limits in the term page
256 search.limit_ = -1;
257
258 search_page_->preview_->Update(search);
259 }
260
UpdateSortPreview()261 void QueryWizardPlugin::UpdateSortPreview() {
262 Search search = MakeSearch();
263 if (!search.is_valid()) return;
264
265 sort_ui_->preview->Update(search);
266 }
267
MakeSearch() const268 Search QueryWizardPlugin::MakeSearch() const {
269 Search ret;
270
271 // Search type
272 ret.search_type_ =
273 Search::SearchType(search_page_->ui_->type->currentIndex());
274
275 // Search terms
276 for (SearchTermWidget* widget : search_page_->terms_) {
277 SearchTerm term = widget->Term();
278 if (term.is_valid()) ret.terms_ << term;
279 }
280
281 // Sort order
282 if (sort_ui_->random->isChecked()) {
283 ret.sort_type_ = Search::Sort_Random;
284 } else {
285 const bool ascending = sort_ui_->order->currentIndex() == 0;
286 ret.sort_type_ = ascending ? Search::Sort_FieldAsc : Search::Sort_FieldDesc;
287 ret.sort_field_ = SearchTerm::Field(sort_ui_->field_value->currentIndex());
288 }
289
290 // Limit
291 if (sort_ui_->limit_none->isChecked())
292 ret.limit_ = -1;
293 else
294 ret.limit_ = sort_ui_->limit_value->value();
295
296 return ret;
297 }
298
SearchTypeChanged()299 void QueryWizardPlugin::SearchTypeChanged() {
300 const bool all = search_page_->ui_->type->currentIndex() == 2;
301 search_page_->ui_->terms_scroll_area_content->setEnabled(!all);
302
303 UpdateTermPreview();
304 }
305
MoveTermListToBottom(int min,int max)306 void QueryWizardPlugin::MoveTermListToBottom(int min, int max) {
307 Q_UNUSED(min);
308 // Only scroll to the bottom if a new term is added
309 if (previous_scrollarea_max_ < max)
310 search_page_->ui_->terms_scroll_area->verticalScrollBar()->setValue(max);
311
312 previous_scrollarea_max_ = max;
313 }
314
315 } // namespace smart_playlists
316