]> cloud.milkyroute.net Git - dolphin.git/commitdiff
[dolphin/search] Search by (multiple) tags
authorIsmael Asensio <isma.af@gmail.com>
Sun, 15 Dec 2019 16:18:18 +0000 (17:18 +0100)
committerIsmael Asensio <isma.af@gmail.com>
Sun, 15 Dec 2019 16:28:38 +0000 (17:28 +0100)
Summary:
Adds a tag selector in the extended filters of the search box.
Selected tag or tags are added to the search query along with the other filters (type, date, rating).

FEATURE: 412564
CCBUG: 356062

Test Plan:
- Menu shows the user tags
- Picking any tag/s filters the search to that specific tag/s

{F7727909}

Reviewers: elvisangelaccio, ngraham, #dolphin, #vdg

Reviewed By: elvisangelaccio, ngraham, #dolphin, #vdg

Subscribers: kfm-devel

Tags: #dolphin

Maniphest Tasks: T9094

Differential Revision: https://phabricator.kde.org/D25130

src/search/dolphinfacetswidget.cpp
src/search/dolphinfacetswidget.h
src/search/dolphinquery.cpp
src/tests/dolphinquerytest.cpp

index ae05509e77fc6ffe8894ce4fd6a6d453b0163a17..c0b6c52438b8a63a17072b6c31459ff617d1d7f5 100644 (file)
 #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,11 +66,25 @@ 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();
 }
@@ -78,8 +95,12 @@ DolphinFacetsWidget::~DolphinFacetsWidget()
 
 void DolphinFacetsWidget::changeEvent(QEvent *event)
 {
-    if (event->type() == QEvent::EnabledChange && !isEnabled()) {
-        resetOptions();
+    if (event->type() == QEvent::EnabledChange) {
+        if (isEnabled()) {
+            updateTagsSelector();
+        } else {
+            resetOptions();
+        }
     }
 }
 
@@ -88,6 +109,10 @@ void DolphinFacetsWidget::resetOptions()
     m_typeSelector->setCurrentIndex(0);
     m_dateSelector->setCurrentIndex(0);
     m_ratingSelector->setCurrentIndex(0);
+
+    m_searchTags = QStringList();
+    updateTagsSelector();
+    updateTagsMenu();
 }
 
 QString DolphinFacetsWidget::ratingTerm() const
@@ -104,6 +129,12 @@ QString DolphinFacetsWidget::ratingTerm() const
         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 +150,20 @@ bool DolphinFacetsWidget::isRatingTerm(const QString& term) const
     // 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 +182,10 @@ void DolphinFacetsWidget::setRatingTerm(const QString& term)
             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,6 +222,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 +249,53 @@ 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();
+        });
+    }
+
+    updateTagsSelector();
+}
index 0a8a5161f177529cf61cd3619cf8271aa6a801ef..5325074c6b649617a92bcd2c7ec111e879b63e59 100644 (file)
 #define DOLPHINFACETSWIDGET_H
 
 #include <QWidget>
+#include <KCoreDirLister>
 
 class QComboBox;
 class QDate;
 class QEvent;
+class QToolButton;
 
 /**
  * @brief Allows to filter search-queries by facets.
@@ -66,15 +68,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
index 8f8cb09ec121c96e52cff9aa4333d7430bb5fb99..92694c09387b332b5559903df644d5e6c4618880 100644 (file)
@@ -32,7 +32,8 @@ namespace {
     {
         static const QLatin1String searchTokens[] {
             QLatin1String("modified>="),
-            QLatin1String("rating>=")
+            QLatin1String("rating>="),
+            QLatin1String("tag:"), QLatin1String("tag=")
         };
 
         for (const auto &searchToken : searchTokens) {
index 1c6b39e267a5391f58d7ae604a50f4ef84eb4cb6..e3c6fb8e35913e939ff88632f0465e602cd57a70 100644 (file)
@@ -45,6 +45,8 @@ void DolphinSearchBoxTest::testBalooSearchParsing_data()
     const QString filename = QStringLiteral("filename:\"xyz\"");
     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<QString>("searchString");
     QTest::addColumn<QString>("expectedText");
@@ -55,7 +57,8 @@ void DolphinSearchBoxTest::testBalooSearchParsing_data()
     QTest::newRow("content/empty")        << ""      << ""   << QStringList();
     QTest::newRow("content/singleQuote")  << "\""    << ""   << QStringList();
     QTest::newRow("content/doubleQuote")  << "\"\""  << ""   << QStringList();
-    // Test for empty `filename`
+
+    // Test for "Filename"
     QTest::newRow("filename")             << filename         << text << QStringList();
     QTest::newRow("filename/empty")       << "filename:"      << ""   << QStringList();
     QTest::newRow("filename/singleQuote") << "filename:\""    << ""   << QStringList();
@@ -65,14 +68,34 @@ void DolphinSearchBoxTest::testBalooSearchParsing_data()
     QTest::newRow("rating")          << rating                  << ""   << QStringList({rating});
     QTest::newRow("rating+content")  << rating + " " + text     << text << QStringList({rating});
     QTest::newRow("rating+filename") << rating + " " + filename << text << QStringList({rating});
+
     // 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});
+
+    // 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("rating+modified+content")  << rating + " AND " + modified + " " + text     << text << QStringList({modified, rating});
-    QTest::newRow("rating+modified+filename") << rating + " AND " + modified + " " + filename << text << QStringList({modified, rating});
+    QTest::newRow("rating+modified")
+        << rating + " AND " + modified
+        << "" << QStringList({modified, rating});
+
+    QTest::newRow("allTerms")
+        << rating + " AND " + modified + " AND " + tagA + " AND " + tagB
+        << "" << QStringList({modified, rating, tagA, tagB});
+
+    QTest::newRow("allTerms+content")
+        << rating + " AND " + modified + " " + text + " " + tagA + " AND " + tagB
+        << text << QStringList({modified, rating, tagA, tagB});
+    
+    QTest::newRow("allTerms+filename")
+        << rating + " AND " + modified + " " + filename + " " + tagA + " AND " + tagB
+        << text << QStringList({modified, rating, tagA, tagB});
 }
 
 /**