]> cloud.milkyroute.net Git - dolphin.git/blob - src/search/dolphinsearchbox.cpp
Port away from KDELibs4Support (we only use KDELibs4Support when baloo isn't present...
[dolphin.git] / src / search / dolphinsearchbox.cpp
1 /***************************************************************************
2 * Copyright (C) 2010 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program 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 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program 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 this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 * **************************************************************************/
19
20 #include "dolphinsearchbox.h"
21
22 #include "dolphin_searchsettings.h"
23 #include "dolphinfacetswidget.h"
24
25 #include <QIcon>
26 #include <QLineEdit>
27 #include <KLocalizedString>
28 #include <KSeparator>
29
30 #include <QButtonGroup>
31 #include <QDir>
32 #include <QEvent>
33 #include <QHBoxLayout>
34 #include <QKeyEvent>
35 #include <QLabel>
36 #include <QScrollArea>
37 #include <QTimer>
38 #include <QToolButton>
39 #include <QVBoxLayout>
40 #include <QUrlQuery>
41
42 #include <config-baloo.h>
43 #ifdef HAVE_BALOO
44 #include <Baloo/Query>
45 #include <Baloo/Term>
46 #include <Baloo/IndexerConfig>
47 #endif
48 #include <QFontDatabase>
49
50 DolphinSearchBox::DolphinSearchBox(QWidget* parent) :
51 QWidget(parent),
52 m_startedSearching(false),
53 m_active(true),
54 m_topLayout(0),
55 m_searchLabel(0),
56 m_searchInput(0),
57 m_optionsScrollArea(0),
58 m_fileNameButton(0),
59 m_contentButton(0),
60 m_separator(0),
61 m_fromHereButton(0),
62 m_everywhereButton(0),
63 m_facetsToggleButton(0),
64 m_facetsWidget(0),
65 m_searchPath(),
66 m_startSearchTimer(0)
67 {
68 }
69
70 DolphinSearchBox::~DolphinSearchBox()
71 {
72 saveSettings();
73 }
74
75 void DolphinSearchBox::setText(const QString& text)
76 {
77 m_searchInput->setText(text);
78 }
79
80 QString DolphinSearchBox::text() const
81 {
82 return m_searchInput->text();
83 }
84
85 void DolphinSearchBox::setSearchPath(const QUrl& url)
86 {
87 m_searchPath = url;
88
89 QFontMetrics metrics(m_fromHereButton->font());
90 const int maxWidth = metrics.height() * 8;
91
92 QString location = url.fileName();
93 if (location.isEmpty()) {
94 if (url.isLocalFile()) {
95 location = QLatin1String("/");
96 } else {
97 location = url.scheme() + QLatin1String(" - ") + url.host();
98 }
99 }
100
101 const QString elidedLocation = metrics.elidedText(location, Qt::ElideMiddle, maxWidth);
102 m_fromHereButton->setText(i18nc("action:button", "From Here (%1)", elidedLocation));
103
104 const bool showSearchFromButtons = url.isLocalFile();
105 m_separator->setVisible(showSearchFromButtons);
106 m_fromHereButton->setVisible(showSearchFromButtons);
107 m_everywhereButton->setVisible(showSearchFromButtons);
108
109 bool hasFacetsSupport = false;
110 #ifdef HAVE_BALOO
111 const Baloo::IndexerConfig searchInfo;
112 hasFacetsSupport = searchInfo.fileIndexingEnabled() && searchInfo.shouldBeIndexed(m_searchPath.toLocalFile());
113 #endif
114 m_facetsWidget->setEnabled(hasFacetsSupport);
115 }
116
117 QUrl DolphinSearchBox::searchPath() const
118 {
119 return m_searchPath;
120 }
121
122 QUrl DolphinSearchBox::urlForSearching() const
123 {
124 QUrl url;
125 bool useBalooSearch = false;
126 #ifdef HAVE_BALOO
127 const Baloo::IndexerConfig searchInfo;
128 useBalooSearch = searchInfo.fileIndexingEnabled() && searchInfo.shouldBeIndexed(m_searchPath.toLocalFile());
129 #endif
130 if (useBalooSearch) {
131 url = balooUrlForSearching();
132 } else {
133 url.setScheme("filenamesearch");
134
135 QUrlQuery query;
136 query.addQueryItem("search", m_searchInput->text());
137 if (m_contentButton->isChecked()) {
138 query.addQueryItem("checkContent", "yes");
139 }
140
141 QString encodedUrl;
142 if (m_everywhereButton->isChecked()) {
143 // It is very unlikely, that the majority of Dolphins target users
144 // mean "the whole harddisk" instead of "my home folder" when
145 // selecting the "Everywhere" button.
146 encodedUrl = QDir::homePath();
147 } else {
148 encodedUrl = m_searchPath.url();
149 }
150 query.addQueryItem("url", encodedUrl);
151
152 url.setQuery(query);
153 }
154
155 return url;
156 }
157
158 void DolphinSearchBox::fromSearchUrl(const QUrl& url)
159 {
160 if (url.scheme() == "baloosearch") {
161 fromBalooSearchUrl(url);
162 } else if (url.scheme() == "filenamesearch") {
163 const QUrlQuery query(url);
164 setText(query.queryItemValue("search"));
165 setSearchPath(QUrl::fromUserInput(query.queryItemValue("url"), QString(), QUrl::AssumeLocalFile));
166 m_contentButton->setChecked(query.queryItemValue("checkContent") == "yes");
167 } else {
168 setText(QString());
169 setSearchPath(url);
170 }
171 }
172
173 void DolphinSearchBox::selectAll()
174 {
175 m_searchInput->selectAll();
176 }
177
178 void DolphinSearchBox::setActive(bool active)
179 {
180 if (active != m_active) {
181 m_active = active;
182
183 if (active) {
184 emit activated();
185 }
186 }
187 }
188
189 bool DolphinSearchBox::isActive() const
190 {
191 return m_active;
192 }
193
194 bool DolphinSearchBox::event(QEvent* event)
195 {
196 if (event->type() == QEvent::Polish) {
197 init();
198 }
199 return QWidget::event(event);
200 }
201
202 void DolphinSearchBox::showEvent(QShowEvent* event)
203 {
204 if (!event->spontaneous()) {
205 m_searchInput->setFocus();
206 m_startedSearching = false;
207 }
208 }
209
210 void DolphinSearchBox::keyReleaseEvent(QKeyEvent* event)
211 {
212 QWidget::keyReleaseEvent(event);
213 if (event->key() == Qt::Key_Escape) {
214 if (m_searchInput->text().isEmpty()) {
215 emit closeRequest();
216 } else {
217 m_searchInput->clear();
218 }
219 }
220 }
221
222 bool DolphinSearchBox::eventFilter(QObject* obj, QEvent* event)
223 {
224 switch (event->type()) {
225 case QEvent::FocusIn:
226 setActive(true);
227 setFocus();
228 break;
229
230 default:
231 break;
232 }
233
234 return QObject::eventFilter(obj, event);
235 }
236
237 void DolphinSearchBox::emitSearchRequest()
238 {
239 m_startSearchTimer->stop();
240 m_startedSearching = true;
241 emit searchRequest();
242 }
243
244 void DolphinSearchBox::emitCloseRequest()
245 {
246 m_startSearchTimer->stop();
247 m_startedSearching = false;
248 emit closeRequest();
249 }
250
251 void DolphinSearchBox::slotConfigurationChanged()
252 {
253 saveSettings();
254 if (m_startedSearching) {
255 emitSearchRequest();
256 }
257 }
258
259 void DolphinSearchBox::slotSearchTextChanged(const QString& text)
260 {
261
262 if (text.isEmpty()) {
263 m_startSearchTimer->stop();
264 } else {
265 m_startSearchTimer->start();
266 }
267 emit searchTextChanged(text);
268 }
269
270 void DolphinSearchBox::slotReturnPressed()
271 {
272 emitSearchRequest();
273 emit returnPressed();
274 }
275
276 void DolphinSearchBox::slotFacetsButtonToggled()
277 {
278 const bool facetsIsVisible = !m_facetsWidget->isVisible();
279 m_facetsWidget->setVisible(facetsIsVisible);
280 updateFacetsToggleButton();
281 }
282
283 void DolphinSearchBox::slotFacetChanged()
284 {
285 m_startedSearching = true;
286 m_startSearchTimer->stop();
287 emit searchRequest();
288 }
289
290 void DolphinSearchBox::initButton(QToolButton* button)
291 {
292 button->installEventFilter(this);
293 button->setAutoExclusive(true);
294 button->setAutoRaise(true);
295 button->setCheckable(true);
296 connect(button, &QToolButton::clicked, this, &DolphinSearchBox::slotConfigurationChanged);
297 }
298
299 void DolphinSearchBox::loadSettings()
300 {
301 if (SearchSettings::location() == QLatin1String("Everywhere")) {
302 m_everywhereButton->setChecked(true);
303 } else {
304 m_fromHereButton->setChecked(true);
305 }
306
307 if (SearchSettings::what() == QLatin1String("Content")) {
308 m_contentButton->setChecked(true);
309 } else {
310 m_fileNameButton->setChecked(true);
311 }
312
313 m_facetsWidget->setVisible(SearchSettings::showFacetsWidget());
314 }
315
316 void DolphinSearchBox::saveSettings()
317 {
318 SearchSettings::setLocation(m_fromHereButton->isChecked() ? "FromHere" : "Everywhere");
319 SearchSettings::setWhat(m_fileNameButton->isChecked() ? "FileName" : "Content");
320 SearchSettings::setShowFacetsWidget(m_facetsToggleButton->isChecked());
321 SearchSettings::self()->save();
322 }
323
324 void DolphinSearchBox::init()
325 {
326 // Create close button
327 QToolButton* closeButton = new QToolButton(this);
328 closeButton->setAutoRaise(true);
329 closeButton->setIcon(QIcon::fromTheme("dialog-close"));
330 closeButton->setToolTip(i18nc("@info:tooltip", "Quit searching"));
331 connect(closeButton, &QToolButton::clicked, this, &DolphinSearchBox::emitCloseRequest);
332
333 // Create search label
334 m_searchLabel = new QLabel(this);
335
336 // Create search box
337 m_searchInput = new QLineEdit(this);
338 m_searchInput->installEventFilter(this);
339 m_searchInput->setClearButtonEnabled(true);
340 m_searchInput->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
341 connect(m_searchInput, &QLineEdit::returnPressed,
342 this, &DolphinSearchBox::slotReturnPressed);
343 connect(m_searchInput, &QLineEdit::textChanged,
344 this, &DolphinSearchBox::slotSearchTextChanged);
345 setFocusProxy(m_searchInput);
346
347 // Apply layout for the search input
348 QHBoxLayout* searchInputLayout = new QHBoxLayout();
349 searchInputLayout->setMargin(0);
350 searchInputLayout->addWidget(closeButton);
351 searchInputLayout->addWidget(m_searchLabel);
352 searchInputLayout->addWidget(m_searchInput);
353
354 // Create "Filename" and "Content" button
355 m_fileNameButton = new QToolButton(this);
356 m_fileNameButton->setText(i18nc("action:button", "Filename"));
357 initButton(m_fileNameButton);
358
359 m_contentButton = new QToolButton();
360 m_contentButton->setText(i18nc("action:button", "Content"));
361 initButton(m_contentButton);
362
363 QButtonGroup* searchWhatGroup = new QButtonGroup(this);
364 searchWhatGroup->addButton(m_fileNameButton);
365 searchWhatGroup->addButton(m_contentButton);
366
367 m_separator = new KSeparator(Qt::Vertical, this);
368
369 // Create "From Here" and "Everywhere"button
370 m_fromHereButton = new QToolButton(this);
371 m_fromHereButton->setText(i18nc("action:button", "From Here"));
372 initButton(m_fromHereButton);
373
374 m_everywhereButton = new QToolButton(this);
375 m_everywhereButton->setText(i18nc("action:button", "Everywhere"));
376 initButton(m_everywhereButton);
377
378 QButtonGroup* searchLocationGroup = new QButtonGroup(this);
379 searchLocationGroup->addButton(m_fromHereButton);
380 searchLocationGroup->addButton(m_everywhereButton);
381
382 // Create "Facets" widgets
383 m_facetsToggleButton = new QToolButton(this);
384 m_facetsToggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
385 initButton(m_facetsToggleButton);
386 connect(m_facetsToggleButton, &QToolButton::clicked, this, &DolphinSearchBox::slotFacetsButtonToggled);
387
388 m_facetsWidget = new DolphinFacetsWidget(this);
389 m_facetsWidget->installEventFilter(this);
390 m_facetsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
391 connect(m_facetsWidget, &DolphinFacetsWidget::facetChanged, this, &DolphinSearchBox::slotFacetChanged);
392
393 // Apply layout for the options
394 QHBoxLayout* optionsLayout = new QHBoxLayout();
395 optionsLayout->setMargin(0);
396 optionsLayout->addWidget(m_fileNameButton);
397 optionsLayout->addWidget(m_contentButton);
398 optionsLayout->addWidget(m_separator);
399 optionsLayout->addWidget(m_fromHereButton);
400 optionsLayout->addWidget(m_everywhereButton);
401 optionsLayout->addStretch(1);
402 optionsLayout->addWidget(m_facetsToggleButton);
403
404 // Put the options into a QScrollArea. This prevents increasing the view width
405 // in case that not enough width for the options is available.
406 QWidget* optionsContainer = new QWidget(this);
407 optionsContainer->setLayout(optionsLayout);
408
409 m_optionsScrollArea = new QScrollArea(this);
410 m_optionsScrollArea->setFrameShape(QFrame::NoFrame);
411 m_optionsScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
412 m_optionsScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
413 m_optionsScrollArea->setMaximumHeight(optionsContainer->sizeHint().height());
414 m_optionsScrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
415 m_optionsScrollArea->setWidget(optionsContainer);
416 m_optionsScrollArea->setWidgetResizable(true);
417
418 m_topLayout = new QVBoxLayout(this);
419 m_topLayout->setMargin(0);
420 m_topLayout->addLayout(searchInputLayout);
421 m_topLayout->addWidget(m_optionsScrollArea);
422 m_topLayout->addWidget(m_facetsWidget);
423
424 loadSettings();
425
426 // The searching should be started automatically after the user did not change
427 // the text within one second
428 m_startSearchTimer = new QTimer(this);
429 m_startSearchTimer->setSingleShot(true);
430 m_startSearchTimer->setInterval(1000);
431 connect(m_startSearchTimer, &QTimer::timeout, this, &DolphinSearchBox::emitSearchRequest);
432
433 updateFacetsToggleButton();
434 }
435
436 QUrl DolphinSearchBox::balooUrlForSearching() const
437 {
438 #ifdef HAVE_BALOO
439 const QString text = m_searchInput->text();
440
441 Baloo::Query query;
442 query.addType("File");
443 query.addType(m_facetsWidget->facetType());
444
445 Baloo::Term term(Baloo::Term::And);
446
447 Baloo::Term ratingTerm = m_facetsWidget->ratingTerm();
448 if (ratingTerm.isValid()) {
449 term.addSubTerm(ratingTerm);
450 }
451
452 if (m_contentButton->isChecked()) {
453 query.setSearchString(text);
454 } else if (!text.isEmpty()) {
455 term.addSubTerm(Baloo::Term(QLatin1String("filename"), text));
456 }
457
458 if (m_fromHereButton->isChecked()) {
459 query.setIncludeFolder(m_searchPath.toLocalFile());
460 }
461
462 query.setTerm(term);
463
464 return query.toSearchUrl(i18nc("@title UDS_DISPLAY_NAME for a KIO directory listing. %1 is the query the user entered.",
465 "Query Results from '%1'", text));
466 #else
467 return QUrl();
468 #endif
469 }
470
471 void DolphinSearchBox::fromBalooSearchUrl(const QUrl& url)
472 {
473 #ifdef HAVE_BALOO
474 const Baloo::Query query = Baloo::Query::fromSearchUrl(url);
475 const Baloo::Term term = query.term();
476
477 // Block all signals to avoid unnecessary "searchRequest" signals
478 // while we adjust the search text and the facet widget.
479 blockSignals(true);
480
481 const QString customDir = query.includeFolder();
482 if (!customDir.isEmpty()) {
483 setSearchPath(QUrl::fromLocalFile(customDir));
484 } else {
485 setSearchPath(QDir::homePath());
486 }
487
488 setText(query.searchString());
489
490 QStringList types = query.types();
491 types.removeOne("File"); // We are only interested in facet widget types
492 if (!types.isEmpty()) {
493 m_facetsWidget->setFacetType(types.first());
494 }
495
496 foreach (const Baloo::Term& subTerm, term.subTerms()) {
497 const QString property = subTerm.property();
498
499 if (property == QLatin1String("filename")) {
500 setText(subTerm.value().toString());
501 } else if (m_facetsWidget->isRatingTerm(subTerm)) {
502 m_facetsWidget->setRatingTerm(subTerm);
503 }
504 }
505
506 m_startSearchTimer->stop();
507 blockSignals(false);
508 #else
509 Q_UNUSED(url);
510 #endif
511 }
512
513 void DolphinSearchBox::updateFacetsToggleButton()
514 {
515 const bool facetsIsVisible = SearchSettings::showFacetsWidget();
516 m_facetsToggleButton->setChecked(facetsIsVisible ? true : false);
517 m_facetsToggleButton->setIcon(QIcon::fromTheme(facetsIsVisible ? "arrow-up-double" : "arrow-down-double"));
518 m_facetsToggleButton->setText(facetsIsVisible ? i18nc("action:button", "Fewer Options") : i18nc("action:button", "More Options"));
519 }
520