]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/search/dolphinfacetswidget.cpp
Replace qAsConst with std::as_const
[dolphin.git] / src / search / dolphinfacetswidget.cpp
index ae05509e77fc6ffe8894ce4fd6a6d453b0163a17..da36caa36d5bd6759ca5cfa097571db2b1dc3e51 100644 (file)
@@ -1,57 +1,54 @@
-/***************************************************************************
-*    Copyright (C) 2012 by Peter Penz <peter.penz19@gmail.com>            *
-*    Copyright (C) 2019 by Ismael Asensio <isma.af@mgmail.com>            *
-*                                                                         *
-*    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           *
-* **************************************************************************/
+/*
+ *  SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
+ *  SPDX-FileCopyrightText: 2019 Ismael Asensio <isma.af@mgmail.com>
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
 
 #include "dolphinfacetswidget.h"
 
 #include <KLocalizedString>
+#include <KProtocolInfo>
 
 #include <QComboBox>
 #include <QDate>
 #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)
+DolphinFacetsWidget::DolphinFacetsWidget(QWidget *parent)
+    : QWidget(parent)
+    , m_typeSelector(nullptr)
+    , m_dateSelector(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());
-    m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("inode-directory")), i18nc("@item:inlistbox", "Folders") , QStringLiteral("Folder"));
-    m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("text-x-generic")), i18nc("@item:inlistbox", "Documents") , QStringLiteral("Document"));
-    m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("image-x-generic")), i18nc("@item:inlistbox", "Images") , QStringLiteral("Image"));
+    m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("inode-directory")), i18nc("@item:inlistbox", "Folders"), QStringLiteral("Folder"));
+    m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("text-x-generic")), i18nc("@item:inlistbox", "Documents"), QStringLiteral("Document"));
+    m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("image-x-generic")), i18nc("@item:inlistbox", "Images"), QStringLiteral("Image"));
     m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("audio-x-generic")), i18nc("@item:inlistbox", "Audio Files"), QStringLiteral("Audio"));
-    m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("video-x-generic")), i18nc("@item:inlistbox", "Videos") , QStringLiteral("Video"));
+    m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("video-x-generic")), i18nc("@item:inlistbox", "Videos"), QStringLiteral("Video"));
     initComboBox(m_typeSelector);
 
     const QDate currentDate = QDate::currentDate();
 
     m_dateSelector = new QComboBox(this);
     m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar")), i18nc("@item:inlistbox", "Any Date"), QDate());
-    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("go-jump-today")), i18nc("@item:inlistbox", "Today") , currentDate);
-    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("go-jump-today")), i18nc("@item:inlistbox", "Yesterday") , currentDate.addDays(-1));
-    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar-week")), i18nc("@item:inlistbox", "This Week") , currentDate.addDays(1 - currentDate.dayOfWeek()));
-    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar-month")), i18nc("@item:inlistbox", "This Month"), currentDate.addDays(1 - currentDate.day()));
-    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar-year")), i18nc("@item:inlistbox", "This Year") , currentDate.addDays(1 - currentDate.dayOfYear()));
+    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("go-jump-today")), i18nc("@item:inlistbox", "Today"), currentDate);
+    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("go-jump-today")), i18nc("@item:inlistbox", "Yesterday"), currentDate.addDays(-1));
+    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar-week")),
+                            i18nc("@item:inlistbox", "This Week"),
+                            currentDate.addDays(1 - currentDate.dayOfWeek()));
+    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar-month")),
+                            i18nc("@item:inlistbox", "This Month"),
+                            currentDate.addDays(1 - currentDate.day()));
+    m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar-year")),
+                            i18nc("@item:inlistbox", "This Year"),
+                            currentDate.addDays(1 - currentDate.dayOfYear()));
     initComboBox(m_dateSelector);
 
     m_ratingSelector = new QComboBox(this);
@@ -63,13 +60,33 @@ DolphinFacetsWidget::DolphinFacetsWidget(QWidget* parent) :
     m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "Highest Rating"), 5);
     initComboBox(m_ratingSelector);
 
-    QHBoxLayout* topLayout = new QHBoxLayout(this);
+    m_clearTagsAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-all")), i18nc("@action:inmenu", "Clear Selection"), this);
+    connect(m_clearTagsAction, &QAction::triggered, this, [this]() {
+        resetSearchTags();
+        Q_EMIT facetChanged();
+    });
+
+    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,30 @@ 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()
+QSize DolphinFacetsWidget::minimumSizeHint() const
+{
+    return QSize(0, m_typeSelector->minimumHeight());
+}
+
+void DolphinFacetsWidget::resetSearchTerms()
 {
     m_typeSelector->setCurrentIndex(0);
     m_dateSelector->setCurrentIndex(0);
     m_ratingSelector->setCurrentIndex(0);
+
+    resetSearchTags();
 }
 
-QString DolphinFacetsWidget::ratingTerm() const
+QStringList DolphinFacetsWidget::searchTerms() const
 {
     QStringList terms;
 
@@ -104,7 +132,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,46 +150,35 @@ 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);
     }
 }
 
-void DolphinFacetsWidget::setFacetType(const QStringtype)
+void DolphinFacetsWidget::setFacetType(const QString &type)
 {
     for (int index = 0; index <= m_typeSelector->count(); index++) {
         if (type == m_typeSelector->itemData(index).toString()) {
@@ -169,7 +196,7 @@ void DolphinFacetsWidget::setRating(const int stars)
     m_ratingSelector->setCurrentIndex(stars);
 }
 
-void DolphinFacetsWidget::setTimespan(const QDatedate)
+void DolphinFacetsWidget::setTimespan(const QDate &date)
 {
     if (!date.isValid()) {
         return;
@@ -183,11 +210,103 @@ void DolphinFacetsWidget::setTimespan(const QDate& date)
     }
 }
 
-void DolphinFacetsWidget::initComboBox(QComboBox* combo)
+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::resetSearchTags()
+{
+    m_searchTags = QStringList();
+    updateTagsSelector();
+    updateTagsMenu();
+}
+
+void DolphinFacetsWidget::initComboBox(QComboBox *combo)
 {
     combo->setFrame(false);
     combo->setMinimumHeight(parentWidget()->height());
     combo->setCurrentIndex(0);
-    connect(combo, QOverload<int>::of(&QComboBox::activated), this, &DolphinFacetsWidget::facetChanged);
+    connect(combo, &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));
+    m_clearTagsAction->setEnabled(hasSelectedTags);
+}
+
+void DolphinFacetsWidget::updateTagsMenu()
+{
+    updateTagsMenuItems({}, {});
+    if (KProtocolInfo::isKnownProtocol(QStringLiteral("tags"))) {
+        m_tagsLister.openUrl(QUrl(QStringLiteral("tags:/")), KCoreDirLister::OpenUrlFlag::Reload);
+    }
+}
+
+void DolphinFacetsWidget::updateTagsMenuItems(const QUrl &, const KFileItemList &items)
+{
+    QMenu *tagsMenu = m_tagsSelector->menu();
+    tagsMenu->clear();
+
+    QStringList allTags = QStringList(m_searchTags);
+    for (const KFileItem &item : items) {
+        allTags.append(item.name());
+    }
+    allTags.sort(Qt::CaseInsensitive);
+    allTags.removeDuplicates();
+
+    const bool onlyOneTag = allTags.count() == 1;
+
+    for (const QString &tagName : std::as_const(allTags)) {
+        QAction *action = tagsMenu->addAction(QIcon::fromTheme(QStringLiteral("tag")), tagName);
+        action->setCheckable(true);
+        action->setChecked(m_searchTags.contains(tagName));
+
+        connect(action, &QAction::triggered, this, [this, tagName, onlyOneTag](bool isChecked) {
+            if (isChecked) {
+                addSearchTag(tagName);
+            } else {
+                removeSearchTag(tagName);
+            }
+            Q_EMIT facetChanged();
+
+            if (!onlyOneTag) {
+                m_tagsSelector->menu()->show();
+            }
+        });
+    }
+
+    if (allTags.count() > 1) {
+        tagsMenu->addSeparator();
+        tagsMenu->addAction(m_clearTagsAction);
+    }
+
+    updateTagsSelector();
 }
 
+#include "moc_dolphinfacetswidget.cpp"