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