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