┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/search
diff options
context:
space:
mode:
Diffstat (limited to 'src/search')
-rw-r--r--src/search/dolphinfacetswidget.cpp116
-rw-r--r--src/search/dolphinfacetswidget.h14
-rw-r--r--src/search/dolphinquery.cpp69
-rw-r--r--src/search/dolphinquery.h6
-rw-r--r--src/search/dolphinsearchbox.cpp6
5 files changed, 194 insertions, 17 deletions
diff --git a/src/search/dolphinfacetswidget.cpp b/src/search/dolphinfacetswidget.cpp
index ae05509e7..c0b6c5243 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,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();
+}
diff --git a/src/search/dolphinfacetswidget.h b/src/search/dolphinfacetswidget.h
index 0a8a5161f..5325074c6 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.
@@ -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
diff --git a/src/search/dolphinquery.cpp b/src/search/dolphinquery.cpp
index 8f8cb09ec..4d5f8e132 100644
--- a/src/search/dolphinquery.cpp
+++ b/src/search/dolphinquery.cpp
@@ -19,6 +19,8 @@
#include "dolphinquery.h"
+#include <QRegularExpression>
+
#include <config-baloo.h>
#ifdef HAVE_BALOO
#include <Baloo/Query>
@@ -32,7 +34,8 @@ namespace {
{
static const QLatin1String searchTokens[] {
QLatin1String("modified>="),
- QLatin1String("rating>=")
+ QLatin1String("rating>="),
+ QLatin1String("tag:"), QLatin1String("tag=")
};
for (const auto &searchToken : searchTokens) {
@@ -42,6 +45,30 @@ namespace {
}
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)
@@ -58,29 +85,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);
+ 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;
}
}
@@ -114,3 +147,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..302081d7d 100644
--- a/src/search/dolphinsearchbox.cpp
+++ b/src/search/dolphinsearchbox.cpp
@@ -517,6 +517,12 @@ void DolphinSearchBox::updateFromQuery(const DolphinQuery& query)
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();