X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/fb501085e2c65f7b64eedf8be0e1e8ea515a3ba8..9616edbb66a8efbdd2bbc9be18e24aaf38a45b59:/src/search/dolphinsearchbox.cpp diff --git a/src/search/dolphinsearchbox.cpp b/src/search/dolphinsearchbox.cpp index 32f66adb4..61f5c2db4 100644 --- a/src/search/dolphinsearchbox.cpp +++ b/src/search/dolphinsearchbox.cpp @@ -20,49 +20,48 @@ #include "dolphinsearchbox.h" #include "dolphin_searchsettings.h" - -#include -#include -#include -#include +#include "dolphinfacetswidget.h" +#include "panels/places/placesitemmodel.h" + +#include +#include +#include +#include +#ifdef HAVE_BALOO +#include +#include +#endif #include #include -#include -#include +#include #include -#include +#include #include -#include +#include +#include #include #include -#include - -#include -#ifdef HAVE_NEPOMUK - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif +#include DolphinSearchBox::DolphinSearchBox(QWidget* parent) : QWidget(parent), m_startedSearching(false), - m_nepomukActivated(false), - m_topLayout(0), - m_searchInput(0), - m_fromHereButton(0), - m_everywhereButton(0), - m_fileNameButton(0), - m_contentButton(0), + m_active(true), + m_topLayout(nullptr), + m_searchLabel(nullptr), + m_searchInput(nullptr), + m_saveSearchAction(nullptr), + m_optionsScrollArea(nullptr), + m_fileNameButton(nullptr), + m_contentButton(nullptr), + m_separator(nullptr), + m_fromHereButton(nullptr), + m_everywhereButton(nullptr), + m_facetsToggleButton(nullptr), + m_facetsWidget(nullptr), m_searchPath(), - m_startSearchTimer(0) + m_startSearchTimer(nullptr) { } @@ -71,49 +70,125 @@ DolphinSearchBox::~DolphinSearchBox() saveSettings(); } +void DolphinSearchBox::setText(const QString& text) +{ + m_searchInput->setText(text); +} + QString DolphinSearchBox::text() const { return m_searchInput->text(); } -void DolphinSearchBox::setSearchPath(const KUrl& url) +void DolphinSearchBox::setSearchPath(const QUrl& url) { m_searchPath = url; QFontMetrics metrics(m_fromHereButton->font()); - const int maxWidth = metrics.averageCharWidth() * 15; - const QString fileName = metrics.elidedText(url.fileName(), Qt::ElideMiddle, maxWidth); - m_fromHereButton->setText(i18nc("action:button", "From Here (%1)", fileName)); + const int maxWidth = metrics.height() * 8; + + QString location = url.fileName(); + if (location.isEmpty()) { + if (url.isLocalFile()) { + location = QStringLiteral("/"); + } else { + location = url.scheme() + QLatin1String(" - ") + url.host(); + } + } + + const QString elidedLocation = metrics.elidedText(location, Qt::ElideMiddle, maxWidth); + m_fromHereButton->setText(i18nc("action:button", "From Here (%1)", elidedLocation)); + + const bool showSearchFromButtons = url.isLocalFile(); + m_separator->setVisible(showSearchFromButtons); + m_fromHereButton->setVisible(showSearchFromButtons); + m_everywhereButton->setVisible(showSearchFromButtons); + + bool hasFacetsSupport = false; +#ifdef HAVE_BALOO + const Baloo::IndexerConfig searchInfo; + hasFacetsSupport = searchInfo.fileIndexingEnabled() && searchInfo.shouldBeIndexed(m_searchPath.toLocalFile()); +#endif + m_facetsWidget->setEnabled(hasFacetsSupport); } -KUrl DolphinSearchBox::searchPath() const +QUrl DolphinSearchBox::searchPath() const { return m_searchPath; } -KUrl DolphinSearchBox::urlForSearching() const +QUrl DolphinSearchBox::urlForSearching() const { - KUrl url; - if (m_nepomukActivated && isSearchPathIndexed()) { - url = nepomukUrlForSearching(); + QUrl url; + bool useBalooSearch = false; +#ifdef HAVE_BALOO + const Baloo::IndexerConfig searchInfo; + useBalooSearch = searchInfo.fileIndexingEnabled() && searchInfo.shouldBeIndexed(m_searchPath.toLocalFile()); +#endif + if (useBalooSearch) { + url = balooUrlForSearching(); } else { - url = m_searchPath; - url.setProtocol("filenamesearch"); - url.addQueryItem("search", m_searchInput->text()); + url.setScheme(QStringLiteral("filenamesearch")); + + QUrlQuery query; + query.addQueryItem(QStringLiteral("search"), m_searchInput->text()); if (m_contentButton->isChecked()) { - url.addQueryItem("checkContent", "yes"); + query.addQueryItem(QStringLiteral("checkContent"), QStringLiteral("yes")); } + + QString encodedUrl; if (m_everywhereButton->isChecked()) { // It is very unlikely, that the majority of Dolphins target users // mean "the whole harddisk" instead of "my home folder" when // selecting the "Everywhere" button. - url.setPath(QDir::homePath()); + encodedUrl = QDir::homePath(); + } else { + encodedUrl = m_searchPath.url(); } + query.addQueryItem(QStringLiteral("url"), encodedUrl); + + url.setQuery(query); } return url; } +void DolphinSearchBox::fromSearchUrl(const QUrl& url) +{ + if (url.scheme() == QLatin1String("baloosearch")) { + fromBalooSearchUrl(url); + } else if (url.scheme() == QLatin1String("filenamesearch")) { + const QUrlQuery query(url); + setText(query.queryItemValue(QStringLiteral("search"))); + setSearchPath(QUrl::fromUserInput(query.queryItemValue(QStringLiteral("url")), QString(), QUrl::AssumeLocalFile)); + m_contentButton->setChecked(query.queryItemValue(QStringLiteral("checkContent")) == QLatin1String("yes")); + } else { + setText(QString()); + setSearchPath(url); + } +} + +void DolphinSearchBox::selectAll() +{ + m_searchInput->selectAll(); +} + +void DolphinSearchBox::setActive(bool active) +{ + if (active != m_active) { + m_active = active; + + if (active) { + emit activated(); + } + } +} + +bool DolphinSearchBox::isActive() const +{ + return m_active; +} + bool DolphinSearchBox::event(QEvent* event) { if (event->type() == QEvent::Polish) { @@ -125,20 +200,22 @@ bool DolphinSearchBox::event(QEvent* event) void DolphinSearchBox::showEvent(QShowEvent* event) { if (!event->spontaneous()) { -#ifdef HAVE_NEPOMUK - m_nepomukActivated = (Nepomuk::ResourceManager::instance()->init() == 0); -#endif - - m_searchInput->clear(); m_searchInput->setFocus(); m_startedSearching = false; } } +void DolphinSearchBox::hideEvent(QHideEvent* event) +{ + Q_UNUSED(event); + m_startedSearching = false; + m_startSearchTimer->stop(); +} + void DolphinSearchBox::keyReleaseEvent(QKeyEvent* event) { QWidget::keyReleaseEvent(event); - if ((event->key() == Qt::Key_Escape)) { + if (event->key() == Qt::Key_Escape) { if (m_searchInput->text().isEmpty()) { emit closeRequest(); } else { @@ -147,22 +224,57 @@ void DolphinSearchBox::keyReleaseEvent(QKeyEvent* event) } } -void DolphinSearchBox::emitSearchSignal() +bool DolphinSearchBox::eventFilter(QObject* obj, QEvent* event) +{ + switch (event->type()) { + case QEvent::FocusIn: + // #379135: we get the FocusIn event when we close a tab but we don't want to emit + // the activated() signal before the removeTab() call in DolphinTabWidget::closeTab() returns. + // To avoid this issue, we delay the activation of the search box. + // We also don't want to schedule the activation process if we are already active, + // otherwise we can enter in a loop of FocusIn/FocusOut events with the searchbox of another tab. + if (!isActive()) { + QTimer::singleShot(0, this, [this] { + setActive(true); + setFocus(); + }); + } + break; + + default: + break; + } + + return QObject::eventFilter(obj, event); +} + +void DolphinSearchBox::emitSearchRequest() { m_startSearchTimer->stop(); m_startedSearching = true; - emit search(m_searchInput->text()); + m_saveSearchAction->setEnabled(true); + emit searchRequest(); +} + +void DolphinSearchBox::emitCloseRequest() +{ + m_startSearchTimer->stop(); + m_startedSearching = false; + m_saveSearchAction->setEnabled(false); + emit closeRequest(); } void DolphinSearchBox::slotConfigurationChanged() { + saveSettings(); if (m_startedSearching) { - emitSearchSignal(); + emitSearchRequest(); } } void DolphinSearchBox::slotSearchTextChanged(const QString& text) { + if (text.isEmpty()) { m_startSearchTimer->stop(); } else { @@ -171,18 +283,45 @@ void DolphinSearchBox::slotSearchTextChanged(const QString& text) emit searchTextChanged(text); } -void DolphinSearchBox::slotReturnPressed(const QString& text) +void DolphinSearchBox::slotReturnPressed() { - emitSearchSignal(); - emit returnPressed(text); + emitSearchRequest(); + emit returnPressed(); } -void DolphinSearchBox::initButton(QPushButton* button) +void DolphinSearchBox::slotFacetsButtonToggled() { + const bool facetsIsVisible = !m_facetsWidget->isVisible(); + m_facetsWidget->setVisible(facetsIsVisible); + updateFacetsToggleButton(); +} + +void DolphinSearchBox::slotFacetChanged() +{ + m_startedSearching = true; + m_startSearchTimer->stop(); + emit searchRequest(); +} + +void DolphinSearchBox::slotSearchSaved() +{ + const QUrl searchURL = urlForSearching(); + if (searchURL.isValid()) { + PlacesItemModel model; + const QString label = i18n("Search for %1 in %2", text(), searchPath().fileName()); + model.createPlacesItem(label, + searchURL, + QStringLiteral("folder-saved-search-symbolic")); + } +} + +void DolphinSearchBox::initButton(QToolButton* button) +{ + button->installEventFilter(this); button->setAutoExclusive(true); - button->setFlat(true); + button->setAutoRaise(true); button->setCheckable(true); - connect(button, SIGNAL(toggled(bool)), this, SLOT(slotConfigurationChanged())); + connect(button, &QToolButton::clicked, this, &DolphinSearchBox::slotConfigurationChanged); } void DolphinSearchBox::loadSettings() @@ -198,13 +337,16 @@ void DolphinSearchBox::loadSettings() } else { m_fileNameButton->setChecked(true); } + + m_facetsWidget->setVisible(SearchSettings::showFacetsWidget()); } void DolphinSearchBox::saveSettings() { - SearchSettings::setLocation(m_fromHereButton->isChecked() ? "FromHere" : "Everywhere"); - SearchSettings::setWhat(m_fileNameButton->isChecked() ? "FileName" : "Content"); - SearchSettings::self()->writeConfig(); + SearchSettings::setLocation(m_fromHereButton->isChecked() ? QStringLiteral("FromHere") : QStringLiteral("Everywhere")); + SearchSettings::setWhat(m_fileNameButton->isChecked() ? QStringLiteral("FileName") : QStringLiteral("Content")); + SearchSettings::setShowFacetsWidget(m_facetsToggleButton->isChecked()); + SearchSettings::self()->save(); } void DolphinSearchBox::init() @@ -212,35 +354,60 @@ void DolphinSearchBox::init() // Create close button QToolButton* closeButton = new QToolButton(this); closeButton->setAutoRaise(true); - closeButton->setIcon(KIcon("dialog-close")); + closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); closeButton->setToolTip(i18nc("@info:tooltip", "Quit searching")); - connect(closeButton, SIGNAL(clicked()), SIGNAL(closeRequest())); + connect(closeButton, &QToolButton::clicked, this, &DolphinSearchBox::emitCloseRequest); // Create search label - QLabel* searchLabel = new QLabel(i18nc("@label:textbox", "Find:"), this); + m_searchLabel = new QLabel(this); // Create search box - m_searchInput = new KLineEdit(this); - m_searchInput->setClearButtonShown(true); - m_searchInput->setFont(KGlobalSettings::generalFont()); - connect(m_searchInput, SIGNAL(returnPressed(QString)), - this, SLOT(slotReturnPressed(QString))); - connect(m_searchInput, SIGNAL(textChanged(QString)), - this, SLOT(slotSearchTextChanged(QString))); + m_searchInput = new QLineEdit(this); + m_searchInput->installEventFilter(this); + m_searchInput->setClearButtonEnabled(true); + m_searchInput->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); + connect(m_searchInput, &QLineEdit::returnPressed, + this, &DolphinSearchBox::slotReturnPressed); + connect(m_searchInput, &QLineEdit::textChanged, + this, &DolphinSearchBox::slotSearchTextChanged); + setFocusProxy(m_searchInput); + + // Add "Save search" button inside search box + m_saveSearchAction = new QAction(this); + m_saveSearchAction->setIcon (QIcon::fromTheme(QStringLiteral("document-save-symbolic"))); + m_saveSearchAction->setText(i18nc("action:button", "Save this search to quickly access it again in the future")); + m_saveSearchAction->setEnabled(false); + m_searchInput->addAction(m_saveSearchAction, QLineEdit::TrailingPosition); + connect(m_saveSearchAction, &QAction::triggered, this, &DolphinSearchBox::slotSearchSaved); // Apply layout for the search input QHBoxLayout* searchInputLayout = new QHBoxLayout(); searchInputLayout->setMargin(0); searchInputLayout->addWidget(closeButton); - searchInputLayout->addWidget(searchLabel); + searchInputLayout->addWidget(m_searchLabel); searchInputLayout->addWidget(m_searchInput); + // Create "Filename" and "Content" button + m_fileNameButton = new QToolButton(this); + m_fileNameButton->setText(i18nc("action:button", "Filename")); + initButton(m_fileNameButton); + + m_contentButton = new QToolButton(); + m_contentButton->setText(i18nc("action:button", "Content")); + initButton(m_contentButton); + + QButtonGroup* searchWhatGroup = new QButtonGroup(this); + searchWhatGroup->addButton(m_fileNameButton); + searchWhatGroup->addButton(m_contentButton); + + m_separator = new KSeparator(Qt::Vertical, this); + // Create "From Here" and "Everywhere"button - m_fromHereButton = new QPushButton(this); + m_fromHereButton = new QToolButton(this); m_fromHereButton->setText(i18nc("action:button", "From Here")); initButton(m_fromHereButton); - m_everywhereButton = new QPushButton(this); + m_everywhereButton = new QToolButton(this); m_everywhereButton->setText(i18nc("action:button", "Everywhere")); initButton(m_everywhereButton); @@ -248,34 +415,64 @@ void DolphinSearchBox::init() searchLocationGroup->addButton(m_fromHereButton); searchLocationGroup->addButton(m_everywhereButton); - // Create "Filename" and "Content" button - m_fileNameButton = new QPushButton(this); - m_fileNameButton->setText(i18nc("action:button", "Filename")); - initButton(m_fileNameButton); - - m_contentButton = new QPushButton(); - m_contentButton->setText(i18nc("action:button", "Content")); - initButton(m_contentButton);; - - QButtonGroup* searchWhatGroup = new QButtonGroup(this); - searchWhatGroup->addButton(m_fileNameButton); - searchWhatGroup->addButton(m_contentButton); + auto moreSearchToolsButton = new QToolButton(this); + moreSearchToolsButton->setAutoRaise(true); + moreSearchToolsButton->setPopupMode(QToolButton::InstantPopup); + moreSearchToolsButton->setIcon(QIcon::fromTheme("arrow-down-double")); + moreSearchToolsButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + moreSearchToolsButton->setText(i18n("More Search Tools")); + moreSearchToolsButton->setMenu(new QMenu(this)); + connect(moreSearchToolsButton->menu(), &QMenu::aboutToShow, moreSearchToolsButton->menu(), [this, moreSearchToolsButton]() + { + m_menuFactory.reset(new KMoreToolsMenuFactory("dolphin/search-tools")); + moreSearchToolsButton->menu()->clear(); + m_menuFactory->fillMenuFromGroupingNames(moreSearchToolsButton->menu(), { "files-find" }, this->m_searchPath); + } ); + + // Create "Facets" widgets + m_facetsToggleButton = new QToolButton(this); + m_facetsToggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + initButton(m_facetsToggleButton); + connect(m_facetsToggleButton, &QToolButton::clicked, this, &DolphinSearchBox::slotFacetsButtonToggled); + + m_facetsWidget = new DolphinFacetsWidget(this); + m_facetsWidget->installEventFilter(this); + m_facetsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + connect(m_facetsWidget, &DolphinFacetsWidget::facetChanged, this, &DolphinSearchBox::slotFacetChanged); // Apply layout for the options QHBoxLayout* optionsLayout = new QHBoxLayout(); optionsLayout->setMargin(0); - optionsLayout->addWidget(m_fromHereButton); - optionsLayout->addWidget(m_everywhereButton); - optionsLayout->addWidget(new KSeparator(Qt::Vertical)); optionsLayout->addWidget(m_fileNameButton); optionsLayout->addWidget(m_contentButton); + optionsLayout->addWidget(m_separator); + optionsLayout->addWidget(m_fromHereButton); + optionsLayout->addWidget(m_everywhereButton); + optionsLayout->addWidget(new KSeparator(Qt::Vertical, this)); + optionsLayout->addWidget(m_facetsToggleButton); + optionsLayout->addWidget(moreSearchToolsButton); optionsLayout->addStretch(1); + // Put the options into a QScrollArea. This prevents increasing the view width + // in case that not enough width for the options is available. + QWidget* optionsContainer = new QWidget(this); + optionsContainer->setLayout(optionsLayout); + + m_optionsScrollArea = new QScrollArea(this); + m_optionsScrollArea->setFrameShape(QFrame::NoFrame); + m_optionsScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_optionsScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_optionsScrollArea->setMaximumHeight(optionsContainer->sizeHint().height()); + m_optionsScrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + m_optionsScrollArea->setWidget(optionsContainer); + m_optionsScrollArea->setWidgetResizable(true); + m_topLayout = new QVBoxLayout(this); + m_topLayout->setMargin(0); m_topLayout->addLayout(searchInputLayout); - m_topLayout->addLayout(optionsLayout); + m_topLayout->addWidget(m_optionsScrollArea); + m_topLayout->addWidget(m_facetsWidget); - searchLabel->setBuddy(m_searchInput); loadSettings(); // The searching should be started automatically after the user did not change @@ -283,79 +480,89 @@ void DolphinSearchBox::init() m_startSearchTimer = new QTimer(this); m_startSearchTimer->setSingleShot(true); m_startSearchTimer->setInterval(1000); - connect(m_startSearchTimer, SIGNAL(timeout()), this, SLOT(emitSearchSignal())); + connect(m_startSearchTimer, &QTimer::timeout, this, &DolphinSearchBox::emitSearchRequest); + + updateFacetsToggleButton(); } -bool DolphinSearchBox::isSearchPathIndexed() const +QUrl DolphinSearchBox::balooUrlForSearching() const { -#ifdef HAVE_NEPOMUK - const QString path = m_searchPath.path(); - - const KConfig strigiConfig("nepomukstrigirc"); - const QStringList indexedFolders = strigiConfig.group("General").readPathEntry("folders", QStringList()); - - // Check whether the current search path is part of an indexed folder - bool isIndexed = false; - foreach (const QString& indexedFolder, indexedFolders) { - if (path.startsWith(indexedFolder)) { - isIndexed = true; - break; - } +#ifdef HAVE_BALOO + const QString text = m_searchInput->text(); + + Baloo::Query query; + query.addType(m_facetsWidget->facetType()); + + QStringList queryStrings; + QString ratingQuery = m_facetsWidget->ratingTerm(); + if (!ratingQuery.isEmpty()) { + queryStrings << ratingQuery; } - if (isIndexed) { - // The current search path is part of an indexed folder. Check whether no - // excluded folder is part of the search path. - const QStringList excludedFolders = strigiConfig.group("General").readPathEntry("exclude folders", QStringList()); - foreach (const QString& excludedFolder, excludedFolders) { - if (path.startsWith(excludedFolder)) { - isIndexed = false; - break; - } - } + if (m_contentButton->isChecked()) { + queryStrings << text; + } else if (!text.isEmpty()) { + queryStrings << QStringLiteral("filename:\"%1\"").arg(text); } - return isIndexed; + if (m_fromHereButton->isChecked()) { + query.setIncludeFolder(m_searchPath.toLocalFile()); + } + + query.setSearchString(queryStrings.join(QStringLiteral(" "))); + + return query.toSearchUrl(i18nc("@title UDS_DISPLAY_NAME for a KIO directory listing. %1 is the query the user entered.", + "Query Results from '%1'", text)); #else - return false; + return QUrl(); #endif } -KUrl DolphinSearchBox::nepomukUrlForSearching() const +void DolphinSearchBox::fromBalooSearchUrl(const QUrl& url) { -#ifdef HAVE_NEPOMUK - Nepomuk::Query::AndTerm andTerm; +#ifdef HAVE_BALOO + const Baloo::Query query = Baloo::Query::fromSearchUrl(url); - // Add input from search filter - const QString text = m_searchInput->text(); - if (!text.isEmpty()) { - if (m_fileNameButton->isChecked()) { - QString regex = QRegExp::escape(text); - regex.replace("\\*", QLatin1String(".*")); - regex.replace("\\?", QLatin1String(".")); - regex.replace("\\", "\\\\"); - andTerm.addSubTerm(Nepomuk::Query::ComparisonTerm( - Nepomuk::Vocabulary::NFO::fileName(), - Nepomuk::Query::LiteralTerm(regex), - Nepomuk::Query::ComparisonTerm::Regexp)); - } else { - const Nepomuk::Query::Query customQuery = Nepomuk::Query::QueryParser::parseQuery(text, Nepomuk::Query::QueryParser::DetectFilenamePattern); - if (customQuery.isValid()) { - andTerm.addSubTerm(customQuery.term()); - } - } + // Block all signals to avoid unnecessary "searchRequest" signals + // while we adjust the search text and the facet widget. + blockSignals(true); + + const QString customDir = query.includeFolder(); + if (!customDir.isEmpty()) { + setSearchPath(QUrl::fromLocalFile(customDir)); + } else { + setSearchPath(QUrl::fromLocalFile(QDir::homePath())); + } + + setText(query.searchString()); + + QStringList types = query.types(); + if (!types.isEmpty()) { + m_facetsWidget->setFacetType(types.first()); } - Nepomuk::Query::FileQuery fileQuery; - fileQuery.setFileMode(Nepomuk::Query::FileQuery::QueryFiles); - fileQuery.setTerm(andTerm); + const QStringList subTerms = query.searchString().split(' ', QString::SkipEmptyParts); + foreach (const QString& subTerm, subTerms) { + if (subTerm.startsWith(QLatin1String("filename:"))) { + const QString value = subTerm.mid(9); + setText(value); + } else if (m_facetsWidget->isRatingTerm(subTerm)) { + m_facetsWidget->setRatingTerm(subTerm); + } + } - return fileQuery.toSearchUrl(i18nc("@title UDS_DISPLAY_NAME for a KIO directory listing. %1 is the query the user entered.", - "Query Results from '%1'", - text)); + m_startSearchTimer->stop(); + blockSignals(false); #else - return KUrl(); + Q_UNUSED(url); #endif } -#include "dolphinsearchbox.moc" +void DolphinSearchBox::updateFacetsToggleButton() +{ + const bool facetsIsVisible = SearchSettings::showFacetsWidget(); + m_facetsToggleButton->setChecked(facetsIsVisible ? true : false); + m_facetsToggleButton->setIcon(QIcon::fromTheme(facetsIsVisible ? QStringLiteral("arrow-up-double") : QStringLiteral("arrow-down-double"))); + m_facetsToggleButton->setText(facetsIsVisible ? i18nc("action:button", "Fewer Options") : i18nc("action:button", "More Options")); +} +