diff --git a/src/search/dolphinquery.cpp b/src/search/dolphinquery.cpp index 92694c093..4d5f8e132 100644 --- a/src/search/dolphinquery.cpp +++ b/src/search/dolphinquery.cpp @@ -1,117 +1,159 @@ /*************************************************************************** * Copyright (C) 2019 by Ismael Asensio * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "dolphinquery.h" +#include + #include #ifdef HAVE_BALOO #include #endif namespace { /** Checks if a given term in the Baloo::Query::searchString() is a special search term. * This is a copy of `DolphinFacetsWidget::isRatingTerm()` method. */ bool isSearchTerm(const QString& term) { static const QLatin1String searchTokens[] { QLatin1String("modified>="), QLatin1String("rating>="), QLatin1String("tag:"), QLatin1String("tag=") }; for (const auto &searchToken : searchTokens) { if (term.startsWith(searchToken)) { return true; } } return false; } + + QString stripQuotes(const QString& text) + { + QString cleanedText = text; + if (!cleanedText.isEmpty() && cleanedText.at(0) == QLatin1Char('"')) { + cleanedText = cleanedText.mid(1); + } + if (!cleanedText.isEmpty() && cleanedText.back() == QLatin1Char('"')) { + cleanedText = cleanedText.mid(0, cleanedText.size() - 1); + } + return cleanedText; + } + + QStringList splitOutsideQuotes(const QString& text) + { + const QRegularExpression subTermsRegExp("([^ ]*\"[^\"]*\"|(?<= |^)[^ ]+(?= |$))"); + auto subTermsMatchIterator = subTermsRegExp.globalMatch(text); + + QStringList textParts; + while (subTermsMatchIterator.hasNext()) { + textParts << subTermsMatchIterator.next().captured(0); + } + return textParts; + } } DolphinQuery DolphinQuery::fromBalooSearchUrl(const QUrl& searchUrl) { DolphinQuery model; model.m_searchUrl = searchUrl; #ifdef HAVE_BALOO const Baloo::Query query = Baloo::Query::fromSearchUrl(searchUrl); model.m_includeFolder = query.includeFolder(); const QStringList types = query.types(); model.m_fileType = types.isEmpty() ? QString() : types.first(); QStringList textParts; + QString fileName; - const QStringList subTerms = query.searchString().split(' ', QString::SkipEmptyParts); + const QStringList subTerms = splitOutsideQuotes(query.searchString()); foreach (const QString& subTerm, subTerms) { - QString value; if (subTerm.startsWith(QLatin1String("filename:"))) { - value = subTerm.mid(9); + fileName = stripQuotes(subTerm.mid(9)); + if (!fileName.isEmpty()) { + model.m_hasFileName = true; + } + continue; } else if (isSearchTerm(subTerm)) { model.m_searchTerms << subTerm; continue; } else if (subTerm == QLatin1String("AND") && subTerm != subTerms.at(0) && subTerm != subTerms.back()) { continue; } else { - value = subTerm; + const QString cleanedTerm = stripQuotes(subTerm); + if (!cleanedTerm.isEmpty()) { + textParts << cleanedTerm; + model.m_hasContentSearch = true; + } } + } - if (!value.isEmpty() && value.at(0) == QLatin1Char('"')) { - value = value.mid(1); - } - if (!value.isEmpty() && value.back() == QLatin1Char('"')) { - value = value.mid(0, value.size() - 1); - } - if (!value.isEmpty()) { - textParts << value; + if (model.m_hasFileName) { + if (model.m_hasContentSearch) { + textParts << QStringLiteral("filename:\"%1\"").arg(fileName); + } else { + textParts << fileName; } } model.m_searchText = textParts.join(QLatin1Char(' ')); #endif return model; } QUrl DolphinQuery::searchUrl() const { return m_searchUrl; } QString DolphinQuery::text() const { return m_searchText; } QString DolphinQuery::type() const { return m_fileType; } QStringList DolphinQuery::searchTerms() const { return m_searchTerms; } QString DolphinQuery::includeFolder() const { return m_includeFolder; } + +bool DolphinQuery::hasContentSearch() const +{ + return m_hasContentSearch; +} + +bool DolphinQuery::hasFileName() const +{ + return m_hasFileName; +} diff --git a/src/search/dolphinquery.h b/src/search/dolphinquery.h index e60008e3b..544f246bc 100644 --- a/src/search/dolphinquery.h +++ b/src/search/dolphinquery.h @@ -1,60 +1,66 @@ /*************************************************************************** * Copyright (C) 2019 by Ismael Asensio * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #ifndef DOLPHINQUERY_H #define DOLPHINQUERY_H #include "dolphin_export.h" #include #include /** * @brief Simple query model that parses a Baloo search Url and extracts its * separate components to be displayed on dolphin search box. */ class DolphinQuery { public: /** Calls Baloo::Query::fromSearchUrl() with the given @p searchUrl * and parses the result to extract its separate components */ static DolphinQuery fromBalooSearchUrl(const QUrl& searchUrl); /** @return the \a searchUrl passed to Baloo::Query::fromSearchUrl() */ QUrl searchUrl() const; /** @return the user text part of the query, to be shown in the searchbar */ QString text() const; /** @return the first of Baloo::Query::types(), or an empty string */ QString type() const; /** @return a list of the search terms of the Baloo::Query that act as a filter, * such as \"rating>= value\" or \"modified>= date\"*/ QStringList searchTerms() const; /** @return Baloo::Query::includeFolder(), that is, the initial directory * for the query or an empty string if its a global search" */ QString includeFolder() const; + /** @return whether the query includes search in file content */ + bool hasContentSearch() const; + /** @return whether the query includes a filter by fileName */ + bool hasFileName() const; private: QUrl m_searchUrl; QString m_searchText; QString m_fileType; QStringList m_searchTerms; QString m_includeFolder; + bool m_hasContentSearch = false; + bool m_hasFileName = false; }; #endif //DOLPHINQUERY_H diff --git a/src/search/dolphinsearchbox.cpp b/src/search/dolphinsearchbox.cpp index 16f17bbcd..302081d7d 100644 --- a/src/search/dolphinsearchbox.cpp +++ b/src/search/dolphinsearchbox.cpp @@ -1,546 +1,552 @@ /*************************************************************************** * Copyright (C) 2010 by Peter Penz * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * **************************************************************************/ #include "global.h" #include "dolphinsearchbox.h" #include "dolphin_searchsettings.h" #include "dolphinfacetswidget.h" #include "dolphinquery.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 DolphinSearchBox::DolphinSearchBox(QWidget* parent) : QWidget(parent), m_startedSearching(false), m_active(true), m_topLayout(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_facetsWidget(nullptr), m_searchPath(), m_startSearchTimer(nullptr) { } 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 QUrl& url) { if (url == m_searchPath) { return; } const QUrl cleanedUrl = url.adjusted(QUrl::RemoveUserInfo | QUrl::StripTrailingSlash); if (cleanedUrl.path() == QDir::homePath()) { m_fromHereButton->setChecked(false); m_everywhereButton->setChecked(true); if (!m_searchPath.isEmpty()) { return; } } else { m_everywhereButton->setChecked(false); m_fromHereButton->setChecked(true); } m_searchPath = url; QFontMetrics metrics(m_fromHereButton->font()); const int maxWidth = metrics.height() * 8; QString location = cleanedUrl.fileName(); if (location.isEmpty()) { location = cleanedUrl.toString(QUrl::PreferLocalFile); } const QString elidedLocation = metrics.elidedText(location, Qt::ElideMiddle, maxWidth); m_fromHereButton->setText(i18nc("action:button", "From Here (%1)", elidedLocation)); m_fromHereButton->setToolTip(i18nc("action:button", "Limit search to '%1' and its subfolders", cleanedUrl.toString(QUrl::PreferLocalFile))); } QUrl DolphinSearchBox::searchPath() const { return m_everywhereButton->isChecked() ? QUrl::fromLocalFile(QDir::homePath()) : m_searchPath; } QUrl DolphinSearchBox::urlForSearching() const { QUrl url; if (isIndexingEnabled()) { url = balooUrlForSearching(); } else { url.setScheme(QStringLiteral("filenamesearch")); QUrlQuery query; query.addQueryItem(QStringLiteral("search"), m_searchInput->text()); if (m_contentButton->isChecked()) { query.addQueryItem(QStringLiteral("checkContent"), QStringLiteral("yes")); } query.addQueryItem(QStringLiteral("url"), searchPath().url()); url.setQuery(query); } return url; } void DolphinSearchBox::fromSearchUrl(const QUrl& url) { if (url.scheme() == QLatin1String("baloosearch")) { const DolphinQuery query = DolphinQuery::fromBalooSearchUrl(url); updateFromQuery(query); } else if (url.scheme() == QLatin1String("filenamesearch")) { const QUrlQuery query(url); setText(query.queryItemValue(QStringLiteral("search"))); if (m_searchPath.scheme() != url.scheme()) { m_searchPath = QUrl(); } setSearchPath(QUrl::fromUserInput(query.queryItemValue(QStringLiteral("url")), QString(), QUrl::AssumeLocalFile)); m_contentButton->setChecked(query.queryItemValue(QStringLiteral("checkContent")) == QLatin1String("yes")); } else { setText(QString()); m_searchPath = QUrl(); setSearchPath(url); } updateFacetsVisible(); } 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) { init(); } return QWidget::event(event); } void DolphinSearchBox::showEvent(QShowEvent* event) { if (!event->spontaneous()) { 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 (m_searchInput->text().isEmpty()) { emit closeRequest(); } else { m_searchInput->clear(); } } } 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; 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) { emitSearchRequest(); } } void DolphinSearchBox::slotSearchTextChanged(const QString& text) { if (text.isEmpty()) { m_startSearchTimer->stop(); } else { m_startSearchTimer->start(); } emit searchTextChanged(text); } void DolphinSearchBox::slotReturnPressed() { emitSearchRequest(); emit returnPressed(); } 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->setAutoRaise(true); button->setCheckable(true); connect(button, &QToolButton::clicked, this, &DolphinSearchBox::slotConfigurationChanged); } void DolphinSearchBox::loadSettings() { if (SearchSettings::location() == QLatin1String("Everywhere")) { m_everywhereButton->setChecked(true); } else { m_fromHereButton->setChecked(true); } if (SearchSettings::what() == QLatin1String("Content")) { m_contentButton->setChecked(true); } else { m_fileNameButton->setChecked(true); } updateFacetsVisible(); } void DolphinSearchBox::saveSettings() { SearchSettings::setLocation(m_fromHereButton->isChecked() ? QStringLiteral("FromHere") : QStringLiteral("Everywhere")); SearchSettings::setWhat(m_fileNameButton->isChecked() ? QStringLiteral("FileName") : QStringLiteral("Content")); SearchSettings::self()->save(); } void DolphinSearchBox::init() { // Create close button QToolButton* closeButton = new QToolButton(this); closeButton->setAutoRaise(true); closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); closeButton->setToolTip(i18nc("@info:tooltip", "Quit searching")); connect(closeButton, &QToolButton::clicked, this, &DolphinSearchBox::emitCloseRequest); // Create search box m_searchInput = new QLineEdit(this); m_searchInput->setPlaceholderText(i18n("Search...")); 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->setContentsMargins(0, 0, 0, 0); searchInputLayout->addWidget(closeButton); 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 "Your files" buttons m_fromHereButton = new QToolButton(this); m_fromHereButton->setText(i18nc("action:button", "From Here")); initButton(m_fromHereButton); m_everywhereButton = new QToolButton(this); m_everywhereButton->setText(i18nc("action:button", "Your files")); m_everywhereButton->setToolTip(i18nc("action:button", "Search in your home directory")); m_everywhereButton->setIcon(QIcon::fromTheme(QStringLiteral("user-home"))); m_everywhereButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); initButton(m_everywhereButton); QButtonGroup* searchLocationGroup = new QButtonGroup(this); searchLocationGroup->addButton(m_fromHereButton); searchLocationGroup->addButton(m_everywhereButton); 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" widget m_facetsWidget = new DolphinFacetsWidget(this); m_facetsWidget->installEventFilter(this); m_facetsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); m_facetsWidget->layout()->setSpacing(Dolphin::LAYOUT_SPACING_SMALL); connect(m_facetsWidget, &DolphinFacetsWidget::facetChanged, this, &DolphinSearchBox::slotFacetChanged); // Apply layout for the options QHBoxLayout* optionsLayout = new QHBoxLayout(); optionsLayout->setContentsMargins(0, 0, 0, 0); optionsLayout->setSpacing(Dolphin::LAYOUT_SPACING_SMALL); 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(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->setContentsMargins(0, 0, 0, 0); m_topLayout->setSpacing(Dolphin::LAYOUT_SPACING_SMALL); m_topLayout->addLayout(searchInputLayout); m_topLayout->addWidget(m_optionsScrollArea); m_topLayout->addWidget(m_facetsWidget); loadSettings(); // The searching should be started automatically after the user did not change // the text within one second m_startSearchTimer = new QTimer(this); m_startSearchTimer->setSingleShot(true); m_startSearchTimer->setInterval(1000); connect(m_startSearchTimer, &QTimer::timeout, this, &DolphinSearchBox::emitSearchRequest); } QUrl DolphinSearchBox::balooUrlForSearching() const { #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 (m_contentButton->isChecked()) { queryStrings << text; } else if (!text.isEmpty()) { queryStrings << QStringLiteral("filename:\"%1\"").arg(text); } if (m_fromHereButton->isChecked()) { query.setIncludeFolder(m_searchPath.toLocalFile()); } query.setSearchString(queryStrings.join(QLatin1Char(' '))); 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 QUrl(); #endif } void DolphinSearchBox::updateFromQuery(const DolphinQuery& query) { // 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.text()); + if (query.hasContentSearch()) { + m_contentButton->setChecked(true); + } else if (query.hasFileName()) { + m_fileNameButton->setChecked(true); + } + m_facetsWidget->resetOptions(); m_facetsWidget->setFacetType(query.type()); const QStringList searchTerms = query.searchTerms(); for (const QString& searchTerm : searchTerms) { m_facetsWidget->setRatingTerm(searchTerm); } m_startSearchTimer->stop(); blockSignals(false); } void DolphinSearchBox::updateFacetsVisible() { const bool indexingEnabled = isIndexingEnabled(); m_facetsWidget->setEnabled(indexingEnabled); m_facetsWidget->setVisible(indexingEnabled); } bool DolphinSearchBox::isIndexingEnabled() const { #ifdef HAVE_BALOO const Baloo::IndexerConfig searchInfo; return searchInfo.fileIndexingEnabled() && searchInfo.shouldBeIndexed(searchPath().toLocalFile()); #else return false; #endif } diff --git a/src/tests/dolphinquerytest.cpp b/src/tests/dolphinquerytest.cpp index e3c6fb8e3..65dc7350e 100644 --- a/src/tests/dolphinquerytest.cpp +++ b/src/tests/dolphinquerytest.cpp @@ -1,152 +1,163 @@ /*************************************************************************** * Copyright (C) 2019 by Ismael Asensio * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "search/dolphinquery.h" #include #include #include #include #include #include class DolphinSearchBoxTest : public QObject { Q_OBJECT private slots: void testBalooSearchParsing_data(); void testBalooSearchParsing(); }; /** * Defines the parameters for the test cases in testBalooSearchParsing() */ void DolphinSearchBoxTest::testBalooSearchParsing_data() { - const QString text = QStringLiteral("xyz"); - const QString filename = QStringLiteral("filename:\"xyz\""); + const QString text = QStringLiteral("abc xyz"); + const QString filename = QStringLiteral("filename:\"%1\"").arg(text); const QString rating = QStringLiteral("rating>=2"); const QString modified = QString("modified>=2019-08-07"); const QString tagA = QString("tag:tagA"); const QString tagB = QString("tag:tagB"); QTest::addColumn("searchString"); QTest::addColumn("expectedText"); QTest::addColumn("expectedTerms"); + QTest::addColumn("hasContent"); + QTest::addColumn("hasFileName"); // Test for "Content" - QTest::newRow("content") << text << text << QStringList(); - QTest::newRow("content/empty") << "" << "" << QStringList(); - QTest::newRow("content/singleQuote") << "\"" << "" << QStringList(); - QTest::newRow("content/doubleQuote") << "\"\"" << "" << QStringList(); + QTest::newRow("content") << text << text << QStringList() << true << false; + QTest::newRow("content/empty") << "" << "" << QStringList() << false << false; + QTest::newRow("content/singleQuote") << "\"" << "" << QStringList() << false << false; + QTest::newRow("content/doubleQuote") << "\"\"" << "" << QStringList() << false << false; - // Test for "Filename" - QTest::newRow("filename") << filename << text << QStringList(); - QTest::newRow("filename/empty") << "filename:" << "" << QStringList(); - QTest::newRow("filename/singleQuote") << "filename:\"" << "" << QStringList(); - QTest::newRow("filename/doubleQuote") << "filename:\"\"" << "" << QStringList(); + // Test for "FileName" + QTest::newRow("filename") << filename << text << QStringList() << false << true; + QTest::newRow("filename/empty") << "filename:" << "" << QStringList() << false << false; + QTest::newRow("filename/singleQuote") << "filename:\"" << "" << QStringList() << false << false; + QTest::newRow("filename/doubleQuote") << "filename:\"\"" << "" << QStringList() << false << false; + + // Combined content and filename search + QTest::newRow("content+filename") << text + " " + filename << text + " " + filename << QStringList() << true << true; // Test for rating - QTest::newRow("rating") << rating << "" << QStringList({rating}); - QTest::newRow("rating+content") << rating + " " + text << text << QStringList({rating}); - QTest::newRow("rating+filename") << rating + " " + filename << text << QStringList({rating}); + QTest::newRow("rating") << rating << "" << QStringList({rating}) << false << false; + QTest::newRow("rating+content") << rating + " " + text << text << QStringList({rating}) << true << false; + QTest::newRow("rating+filename") << rating + " " + filename << text << QStringList({rating}) << false << true; // Test for modified date - QTest::newRow("modified") << modified << "" << QStringList({modified}); - QTest::newRow("modified+content") << modified + " " + text << text << QStringList({modified}); - QTest::newRow("modified+filename") << modified + " " + filename << text << QStringList({modified}); + QTest::newRow("modified") << modified << "" << QStringList({modified}) << false << false; + QTest::newRow("modified+content") << modified + " " + text << text << QStringList({modified}) << true << false; + QTest::newRow("modified+filename") << modified + " " + filename << text << QStringList({modified}) << false << true; // Test for tags - QTest::newRow("tag") << tagA << "" << QStringList({tagA}); - QTest::newRow("tag/double") << tagA + " " + tagB << "" << QStringList({tagA, tagB}); - QTest::newRow("tag+content") << tagA + " " + text << text << QStringList({tagA}); - QTest::newRow("tag+filename") << tagA + " " + filename << text << QStringList({tagA}); - - // Combined tests - QTest::newRow("rating+modified") - << rating + " AND " + modified - << "" << QStringList({modified, rating}); + QTest::newRow("tag") << tagA << "" << QStringList({tagA}) << false << false; + QTest::newRow("tag/double") << tagA + " " + tagB << "" << QStringList({tagA, tagB}) << false << false; + QTest::newRow("tag+content") << tagA + " " + text << text << QStringList({tagA}) << true << false; + QTest::newRow("tag+filename") << tagA + " " + filename << text << QStringList({tagA}) << false << true; + // Combined search terms QTest::newRow("allTerms") << rating + " AND " + modified + " AND " + tagA + " AND " + tagB - << "" << QStringList({modified, rating, tagA, tagB}); + << "" << QStringList({modified, rating, tagA, tagB}) << false << false; QTest::newRow("allTerms+content") << rating + " AND " + modified + " " + text + " " + tagA + " AND " + tagB - << text << QStringList({modified, rating, tagA, tagB}); - + << text << QStringList({modified, rating, tagA, tagB}) << true << false; + QTest::newRow("allTerms+filename") << rating + " AND " + modified + " " + filename + " " + tagA + " AND " + tagB - << text << QStringList({modified, rating, tagA, tagB}); + << text << QStringList({modified, rating, tagA, tagB}) << false << true; + + QTest::newRow("allTerms+content+filename") + << text + " " + filename + " " + rating + " AND " + modified + " AND " + tagA + " AND " + tagB + << text + " " + filename << QStringList({modified, rating, tagA, tagB}) << true << true; } /** * Helper function to compose the baloo query URL used for searching */ QUrl composeQueryUrl(const QString& searchString) { const QJsonObject jsonObject { {"searchString", searchString} }; const QJsonDocument doc(jsonObject); const QString queryString = QString::fromUtf8(doc.toJson(QJsonDocument::Compact)); QUrlQuery urlQuery; urlQuery.addQueryItem(QStringLiteral("json"), queryString); QUrl searchUrl; searchUrl.setScheme(QLatin1String("baloosearch")); searchUrl.setQuery(urlQuery); return searchUrl; } /** * The test verifies whether the different terms of a Baloo search URL ("baloosearch:") are * properly handled by the searchbox, and only "user" or filename terms are added to the * text bar of the searchbox. */ void DolphinSearchBoxTest::testBalooSearchParsing() { QFETCH(QString, searchString); QFETCH(QString, expectedText); QFETCH(QStringList, expectedTerms); + QFETCH(bool, hasContent); + QFETCH(bool, hasFileName); const QUrl testUrl = composeQueryUrl(searchString); const DolphinQuery query = DolphinQuery::fromBalooSearchUrl(testUrl); QStringList searchTerms = query.searchTerms(); searchTerms.sort(); // Check for parsed text (would be displayed on the input search bar) QCOMPARE(query.text(), expectedText); // Check for parsed search terms (would be displayed by the facetsWidget) QCOMPARE(searchTerms.count(), expectedTerms.count()); for (int i = 0; i < expectedTerms.count(); i++) { QCOMPARE(searchTerms.at(i), expectedTerms.at(i)); } + + // Check for filename and content detection + QCOMPARE(query.hasContentSearch(), hasContent); + QCOMPARE(query.hasFileName(), hasFileName); } QTEST_MAIN(DolphinSearchBoxTest) #include "dolphinquerytest.moc"