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