diff options
Diffstat (limited to 'src/search')
| -rw-r--r-- | src/search/dolphinfacetswidget.cpp | 172 | ||||
| -rw-r--r-- | src/search/dolphinfacetswidget.h | 23 | ||||
| -rw-r--r-- | src/search/dolphinquery.cpp | 85 | ||||
| -rw-r--r-- | src/search/dolphinquery.h | 6 | ||||
| -rw-r--r-- | src/search/dolphinsearchbox.cpp | 16 |
5 files changed, 232 insertions, 70 deletions
diff --git a/src/search/dolphinfacetswidget.cpp b/src/search/dolphinfacetswidget.cpp index ae05509e7..d9943abcd 100644 --- a/src/search/dolphinfacetswidget.cpp +++ b/src/search/dolphinfacetswidget.cpp @@ -27,12 +27,15 @@ #include <QEvent> #include <QHBoxLayout> #include <QIcon> +#include <QMenu> +#include <QToolButton> DolphinFacetsWidget::DolphinFacetsWidget(QWidget* parent) : QWidget(parent), m_typeSelector(nullptr), m_dateSelector(nullptr), - m_ratingSelector(nullptr) + m_ratingSelector(nullptr), + m_tagsSelector(nullptr) { m_typeSelector = new QComboBox(this); m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("none")), i18nc("@item:inlistbox", "Any Type"), QString()); @@ -63,13 +66,27 @@ DolphinFacetsWidget::DolphinFacetsWidget(QWidget* parent) : m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "Highest Rating"), 5); initComboBox(m_ratingSelector); + m_tagsSelector = new QToolButton(this); + m_tagsSelector->setIcon(QIcon::fromTheme(QStringLiteral("tag"))); + m_tagsSelector->setMenu(new QMenu(this)); + m_tagsSelector->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + m_tagsSelector->setPopupMode(QToolButton::MenuButtonPopup); + m_tagsSelector->setAutoRaise(true); + updateTagsSelector(); + + connect(m_tagsSelector, &QToolButton::clicked, m_tagsSelector, &QToolButton::showMenu); + connect(m_tagsSelector->menu(), &QMenu::aboutToShow, this, &DolphinFacetsWidget::updateTagsMenu); + connect(&m_tagsLister, &KCoreDirLister::itemsAdded, this, &DolphinFacetsWidget::updateTagsMenuItems); + updateTagsMenu(); + QHBoxLayout* topLayout = new QHBoxLayout(this); topLayout->setContentsMargins(0, 0, 0, 0); topLayout->addWidget(m_typeSelector); topLayout->addWidget(m_dateSelector); topLayout->addWidget(m_ratingSelector); + topLayout->addWidget(m_tagsSelector); - resetOptions(); + resetSearchTerms(); } DolphinFacetsWidget::~DolphinFacetsWidget() @@ -78,19 +95,27 @@ DolphinFacetsWidget::~DolphinFacetsWidget() void DolphinFacetsWidget::changeEvent(QEvent *event) { - if (event->type() == QEvent::EnabledChange && !isEnabled()) { - resetOptions(); + if (event->type() == QEvent::EnabledChange) { + if (isEnabled()) { + updateTagsSelector(); + } else { + resetSearchTerms(); + } } } -void DolphinFacetsWidget::resetOptions() +void DolphinFacetsWidget::resetSearchTerms() { m_typeSelector->setCurrentIndex(0); m_dateSelector->setCurrentIndex(0); m_ratingSelector->setCurrentIndex(0); + + m_searchTags = QStringList(); + updateTagsSelector(); + updateTagsMenu(); } -QString DolphinFacetsWidget::ratingTerm() const +QStringList DolphinFacetsWidget::searchTerms() const { QStringList terms; @@ -104,7 +129,17 @@ QString DolphinFacetsWidget::ratingTerm() const terms << QStringLiteral("modified>=%1").arg(date.toString(Qt::ISODate)); } - return terms.join(QLatin1String(" AND ")); + if (!m_searchTags.isEmpty()) { + for (auto const &tag : m_searchTags) { + if (tag.contains(QLatin1Char(' '))) { + terms << QStringLiteral("tag:\"%1\"").arg(tag); + } else { + terms << QStringLiteral("tag:%1").arg(tag); + } + } + } + + return terms; } QString DolphinFacetsWidget::facetType() const @@ -112,42 +147,36 @@ QString DolphinFacetsWidget::facetType() const return m_typeSelector->currentData().toString(); } -bool DolphinFacetsWidget::isRatingTerm(const QString& term) const +bool DolphinFacetsWidget::isSearchTerm(const QString& term) const { - const QStringList subTerms = term.split(' ', QString::SkipEmptyParts); - - // If term has sub terms, then sone of the sub terms are always "rating" and "modified" terms. - bool containsRating = false; - bool containsModified = false; + static const QLatin1String searchTokens[] { + QLatin1String("modified>="), + QLatin1String("rating>="), + QLatin1String("tag:"), QLatin1String("tag=") + }; - foreach (const QString& subTerm, subTerms) { - if (subTerm.startsWith(QLatin1String("rating>="))) { - containsRating = true; - } else if (subTerm.startsWith(QLatin1String("modified>="))) { - containsModified = true; + for (const auto &searchToken : searchTokens) { + if (term.startsWith(searchToken)) { + return true; } } - - return containsModified || containsRating; + return false; } -void DolphinFacetsWidget::setRatingTerm(const QString& term) +void DolphinFacetsWidget::setSearchTerm(const QString& term) { - // If term has sub terms, then the sub terms are always "rating" and "modified" terms. - // If term has no sub terms, then the term itself is either a "rating" term or a "modified" - // term. To avoid code duplication we add term to subTerms list, if the list is empty. - QStringList subTerms = term.split(' ', QString::SkipEmptyParts); - - foreach (const QString& subTerm, subTerms) { - if (subTerm.startsWith(QLatin1String("modified>="))) { - const QString value = subTerm.mid(10); - const QDate date = QDate::fromString(value, Qt::ISODate); - setTimespan(date); - } else if (subTerm.startsWith(QLatin1String("rating>="))) { - const QString value = subTerm.mid(8); - const int stars = value.toInt() / 2; - setRating(stars); - } + if (term.startsWith(QLatin1String("modified>="))) { + const QString value = term.mid(10); + const QDate date = QDate::fromString(value, Qt::ISODate); + setTimespan(date); + } else if (term.startsWith(QLatin1String("rating>="))) { + const QString value = term.mid(8); + const int stars = value.toInt() / 2; + setRating(stars); + } else if (term.startsWith(QLatin1String("tag:")) || + term.startsWith(QLatin1String("tag="))) { + const QString value = term.mid(4); + addSearchTag(value); } } @@ -183,6 +212,25 @@ void DolphinFacetsWidget::setTimespan(const QDate& date) } } +void DolphinFacetsWidget::addSearchTag(const QString& tag) +{ + if (tag.isEmpty() || m_searchTags.contains(tag)) { + return; + } + m_searchTags.append(tag); + m_searchTags.sort(); + updateTagsSelector(); +} + +void DolphinFacetsWidget::removeSearchTag(const QString& tag) +{ + if (tag.isEmpty() || !m_searchTags.contains(tag)) { + return; + } + m_searchTags.removeAll(tag); + updateTagsSelector(); +} + void DolphinFacetsWidget::initComboBox(QComboBox* combo) { combo->setFrame(false); @@ -191,3 +239,55 @@ void DolphinFacetsWidget::initComboBox(QComboBox* combo) connect(combo, QOverload<int>::of(&QComboBox::activated), this, &DolphinFacetsWidget::facetChanged); } +void DolphinFacetsWidget::updateTagsSelector() +{ + const bool hasListedTags = !m_tagsSelector->menu()->isEmpty(); + const bool hasSelectedTags = !m_searchTags.isEmpty(); + + if (hasSelectedTags) { + const QString tagsText = m_searchTags.join(i18nc("String list separator", ", ")); + m_tagsSelector->setText(i18ncp("@action:button %2 is a list of tags", + "Tag: %2", "Tags: %2",m_searchTags.count(), tagsText)); + } else { + m_tagsSelector->setText(i18nc("@action:button", "Add Tags")); + } + + m_tagsSelector->setEnabled(isEnabled() && (hasListedTags || hasSelectedTags)); +} + +void DolphinFacetsWidget::updateTagsMenu() +{ + updateTagsMenuItems({}, {}); + m_tagsLister.openUrl(QUrl(QStringLiteral("tags:/")), KCoreDirLister::OpenUrlFlag::Reload); +} + +void DolphinFacetsWidget::updateTagsMenuItems(const QUrl&, const KFileItemList& items) +{ + m_tagsSelector->menu()->clear(); + + QStringList allTags = QStringList(m_searchTags); + for (const KFileItem &item: items) { + allTags.append(item.name()); + } + allTags.sort(Qt::CaseInsensitive); + allTags.removeDuplicates(); + + for (const QString& tagName : qAsConst(allTags)) { + QAction* action = m_tagsSelector->menu()->addAction(QIcon::fromTheme(QStringLiteral("tag")), tagName); + action->setCheckable(true); + action->setChecked(m_searchTags.contains(tagName)); + + connect(action, &QAction::triggered, this, [this, tagName](bool isChecked) { + if (isChecked) { + addSearchTag(tagName); + } else { + removeSearchTag(tagName); + } + emit facetChanged(); + + m_tagsSelector->menu()->show(); + }); + } + + updateTagsSelector(); +} diff --git a/src/search/dolphinfacetswidget.h b/src/search/dolphinfacetswidget.h index 0a8a5161f..2e91bcc96 100644 --- a/src/search/dolphinfacetswidget.h +++ b/src/search/dolphinfacetswidget.h @@ -21,10 +21,12 @@ #define DOLPHINFACETSWIDGET_H #include <QWidget> +#include <KCoreDirLister> class QComboBox; class QDate; class QEvent; +class QToolButton; /** * @brief Allows to filter search-queries by facets. @@ -50,13 +52,12 @@ public: explicit DolphinFacetsWidget(QWidget* parent = nullptr); ~DolphinFacetsWidget() override; - void resetOptions(); - - QString ratingTerm() const; + QStringList searchTerms() const; QString facetType() const; - bool isRatingTerm(const QString& term) const; - void setRatingTerm(const QString& term); + bool isSearchTerm(const QString& term) const; + void setSearchTerm(const QString& term); + void resetSearchTerms(); void setFacetType(const QString& type); @@ -66,15 +67,27 @@ signals: protected: void changeEvent(QEvent* event) override; +private slots: + void updateTagsMenu(); + void updateTagsMenuItems(const QUrl&, const KFileItemList& items); + private: void setRating(const int stars); void setTimespan(const QDate& date); + void addSearchTag(const QString& tag); + void removeSearchTag(const QString& tag); + void initComboBox(QComboBox* combo); + void updateTagsSelector(); private: QComboBox* m_typeSelector; QComboBox* m_dateSelector; QComboBox* m_ratingSelector; + QToolButton* m_tagsSelector; + + QStringList m_searchTags; + KCoreDirLister m_tagsLister; }; #endif diff --git a/src/search/dolphinquery.cpp b/src/search/dolphinquery.cpp index 8f8cb09ec..ab107f43f 100644 --- a/src/search/dolphinquery.cpp +++ b/src/search/dolphinquery.cpp @@ -19,28 +19,53 @@ #include "dolphinquery.h" +#include <QRegularExpression> + #include <config-baloo.h> #ifdef HAVE_BALOO #include <Baloo/Query> #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. + /** Checks if a given term in the Baloo::Query::searchString() is a special search term + * @return: the specific search token of the term, or an empty QString() if none is found */ - bool isSearchTerm(const QString& term) + QString searchTermToken(const QString& term) { static const QLatin1String searchTokens[] { + QLatin1String("filename:"), QLatin1String("modified>="), - QLatin1String("rating>=") + QLatin1String("rating>="), + QLatin1String("tag:"), QLatin1String("tag=") }; for (const auto &searchToken : searchTokens) { if (term.startsWith(searchToken)) { - return true; + return searchToken; } } - return false; + return QString(); + } + + QString stripQuotes(const QString& text) + { + if (text.length() >= 2 && text.at(0) == QLatin1Char('"') + && text.back() == QLatin1Char('"')) { + return text.mid(1, text.size() - 2); + } + return text; + } + + QStringList splitOutsideQuotes(const QString& text) + { + const QRegularExpression subTermsRegExp("(\\S*?\"[^\"]*?\"|(?<=\\s|^)\\S+(?=\\s|$))"); + auto subTermsMatchIterator = subTermsRegExp.globalMatch(text); + + QStringList textParts; + while (subTermsMatchIterator.hasNext()) { + textParts << subTermsMatchIterator.next().captured(0); + } + return textParts; } } @@ -58,29 +83,35 @@ DolphinQuery DolphinQuery::fromBalooSearchUrl(const QUrl& searchUrl) 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); - } else if (isSearchTerm(subTerm)) { - model.m_searchTerms << subTerm; + const QString token = searchTermToken(subTerm); + const QString value = stripQuotes(subTerm.mid(token.length())); + + if (token == QLatin1String("filename:")) { + if (!value.isEmpty()) { + fileName = value; + model.m_hasFileName = true; + } + continue; + } else if (!token.isEmpty()) { + model.m_searchTerms << token + value; continue; } else if (subTerm == QLatin1String("AND") && subTerm != subTerms.at(0) && subTerm != subTerms.back()) { continue; - } else { - value = subTerm; + } else if (!value.isEmpty()) { + textParts << value; + 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; } } @@ -114,3 +145,13 @@ 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 @@ -48,6 +48,10 @@ public: /** @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; @@ -55,6 +59,8 @@ private: 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..23f520de1 100644 --- a/src/search/dolphinsearchbox.cpp +++ b/src/search/dolphinsearchbox.cpp @@ -477,11 +477,7 @@ QUrl DolphinSearchBox::balooUrlForSearching() const Baloo::Query query; query.addType(m_facetsWidget->facetType()); - QStringList queryStrings; - QString ratingQuery = m_facetsWidget->ratingTerm(); - if (!ratingQuery.isEmpty()) { - queryStrings << ratingQuery; - } + QStringList queryStrings = m_facetsWidget->searchTerms(); if (m_contentButton->isChecked()) { queryStrings << text; @@ -517,11 +513,17 @@ void DolphinSearchBox::updateFromQuery(const DolphinQuery& query) setText(query.text()); - m_facetsWidget->resetOptions(); + if (query.hasContentSearch()) { + m_contentButton->setChecked(true); + } else if (query.hasFileName()) { + m_fileNameButton->setChecked(true); + } + + m_facetsWidget->resetSearchTerms(); m_facetsWidget->setFacetType(query.type()); const QStringList searchTerms = query.searchTerms(); for (const QString& searchTerm : searchTerms) { - m_facetsWidget->setRatingTerm(searchTerm); + m_facetsWidget->setSearchTerm(searchTerm); } m_startSearchTimer->stop(); |
