diff --git a/src/search/dolphinfacetswidget.h b/src/search/dolphinfacetswidget.h --- a/src/search/dolphinfacetswidget.h +++ b/src/search/dolphinfacetswidget.h @@ -21,10 +21,15 @@ #define DOLPHINFACETSWIDGET_H #include +#include class QComboBox; class QDate; class QEvent; +class QStringList; +class QToolButton; + +class KFileItemList; /** * @brief Allows to filter search-queries by facets. @@ -66,15 +71,27 @@ 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/dolphinfacetswidget.cpp b/src/search/dolphinfacetswidget.cpp --- a/src/search/dolphinfacetswidget.cpp +++ b/src/search/dolphinfacetswidget.cpp @@ -27,12 +27,15 @@ #include #include #include +#include +#include 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,11 +66,24 @@ 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-symbolic"))); + 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); + 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(); } @@ -88,6 +104,9 @@ m_typeSelector->setCurrentIndex(0); m_dateSelector->setCurrentIndex(0); m_ratingSelector->setCurrentIndex(0); + + m_searchTags = QStringList(); + updateTagsSelector(); } QString DolphinFacetsWidget::ratingTerm() const @@ -104,6 +123,12 @@ terms << QStringLiteral("modified>=%1").arg(date.toString(Qt::ISODate)); } + if (!m_searchTags.isEmpty()) { + for (auto const &tag : m_searchTags) { + terms << QStringLiteral("tag:%1").arg(tag); + } + } + return terms.join(QLatin1String(" AND ")); } @@ -119,16 +144,20 @@ // If term has sub terms, then sone of the sub terms are always "rating" and "modified" terms. bool containsRating = false; bool containsModified = false; + bool containsTag = false; foreach (const QString& subTerm, subTerms) { if (subTerm.startsWith(QLatin1String("rating>="))) { containsRating = true; } else if (subTerm.startsWith(QLatin1String("modified>="))) { containsModified = true; + } else if (subTerm.startsWith(QLatin1String("tag:")) || + subTerm.startsWith(QLatin1String("tag="))) { + containsTag = true; } } - return containsModified || containsRating; + return containsModified || containsRating || containsTag; } void DolphinFacetsWidget::setRatingTerm(const QString& term) @@ -147,6 +176,10 @@ const QString value = subTerm.mid(8); const int stars = value.toInt() / 2; setRating(stars); + } else if (subTerm.startsWith(QLatin1String("tag:")) || + subTerm.startsWith(QLatin1String("tag="))) { + const QString value = subTerm.mid(4); + addSearchTag(value); } } } @@ -183,11 +216,71 @@ } } +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); combo->setMinimumHeight(parentWidget()->height()); combo->setCurrentIndex(0); connect(combo, QOverload::of(&QComboBox::activated), this, &DolphinFacetsWidget::facetChanged); } +void DolphinFacetsWidget::updateTagsSelector() +{ + if (m_searchTags.isEmpty()) { + m_tagsSelector->setText(i18nc("@action:button", "Add Tags")); + } else { + 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)); + } +} + +void DolphinFacetsWidget::updateTagsMenu() +{ + m_tagsSelector->menu()->clear(); + m_tagsLister.openUrl(QUrl(QStringLiteral("tags:/")), KCoreDirLister::OpenUrlFlag::Reload); +} + +void DolphinFacetsWidget::updateTagsMenuItems(const QUrl&, const KFileItemList& items) +{ + 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(); + }); + } +} diff --git a/src/search/dolphinsearchbox.cpp b/src/search/dolphinsearchbox.cpp --- a/src/search/dolphinsearchbox.cpp +++ b/src/search/dolphinsearchbox.cpp @@ -516,23 +516,50 @@ m_facetsWidget->resetOptions(); - setText(query.searchString()); - QStringList types = query.types(); if (!types.isEmpty()) { m_facetsWidget->setFacetType(types.first()); } + bool hasFileName = false; + QStringList searchTextItems; + const QStringList subTerms = query.searchString().split(' ', QString::SkipEmptyParts); foreach (const QString& subTerm, subTerms) { + QString value; if (subTerm.startsWith(QLatin1String("filename:"))) { - const QString value = subTerm.mid(9); - setText(value); + hasFileName = true; + value = subTerm.mid(9); } else if (m_facetsWidget->isRatingTerm(subTerm)) { m_facetsWidget->setRatingTerm(subTerm); + continue; + } else if (subTerm == QLatin1String("AND") && subTerm != subTerms.at(0) && subTerm != subTerms.back()) { + continue; + } else if (subTerm == QLatin1String("\"\"")) { + continue; + } else { + value = subTerm; + } + + 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()) { + searchTextItems << value; } } + setText(searchTextItems.join(QLatin1Char(' '))); + + if (hasFileName) { + m_fileNameButton->setChecked(true); + } else { + m_contentButton->setChecked(true); + } + m_startSearchTimer->stop(); blockSignals(false); #else