mirror of
https://invent.kde.org/system/dolphin
synced 2024-09-17 15:31:20 +00:00
[dolphin/search] Search by (multiple) tags
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
This commit is contained in:
parent
f622956208
commit
8e80c1d6dc
|
@ -27,12 +27,15 @@
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QToolButton>
|
||||||
|
|
||||||
DolphinFacetsWidget::DolphinFacetsWidget(QWidget* parent) :
|
DolphinFacetsWidget::DolphinFacetsWidget(QWidget* parent) :
|
||||||
QWidget(parent),
|
QWidget(parent),
|
||||||
m_typeSelector(nullptr),
|
m_typeSelector(nullptr),
|
||||||
m_dateSelector(nullptr),
|
m_dateSelector(nullptr),
|
||||||
m_ratingSelector(nullptr)
|
m_ratingSelector(nullptr),
|
||||||
|
m_tagsSelector(nullptr)
|
||||||
{
|
{
|
||||||
m_typeSelector = new QComboBox(this);
|
m_typeSelector = new QComboBox(this);
|
||||||
m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("none")), i18nc("@item:inlistbox", "Any Type"), QString());
|
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);
|
m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "Highest Rating"), 5);
|
||||||
initComboBox(m_ratingSelector);
|
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);
|
QHBoxLayout* topLayout = new QHBoxLayout(this);
|
||||||
topLayout->setContentsMargins(0, 0, 0, 0);
|
topLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
topLayout->addWidget(m_typeSelector);
|
topLayout->addWidget(m_typeSelector);
|
||||||
topLayout->addWidget(m_dateSelector);
|
topLayout->addWidget(m_dateSelector);
|
||||||
topLayout->addWidget(m_ratingSelector);
|
topLayout->addWidget(m_ratingSelector);
|
||||||
|
topLayout->addWidget(m_tagsSelector);
|
||||||
|
|
||||||
resetOptions();
|
resetOptions();
|
||||||
}
|
}
|
||||||
|
@ -78,8 +95,12 @@ DolphinFacetsWidget::~DolphinFacetsWidget()
|
||||||
|
|
||||||
void DolphinFacetsWidget::changeEvent(QEvent *event)
|
void DolphinFacetsWidget::changeEvent(QEvent *event)
|
||||||
{
|
{
|
||||||
if (event->type() == QEvent::EnabledChange && !isEnabled()) {
|
if (event->type() == QEvent::EnabledChange) {
|
||||||
resetOptions();
|
if (isEnabled()) {
|
||||||
|
updateTagsSelector();
|
||||||
|
} else {
|
||||||
|
resetOptions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +109,10 @@ void DolphinFacetsWidget::resetOptions()
|
||||||
m_typeSelector->setCurrentIndex(0);
|
m_typeSelector->setCurrentIndex(0);
|
||||||
m_dateSelector->setCurrentIndex(0);
|
m_dateSelector->setCurrentIndex(0);
|
||||||
m_ratingSelector->setCurrentIndex(0);
|
m_ratingSelector->setCurrentIndex(0);
|
||||||
|
|
||||||
|
m_searchTags = QStringList();
|
||||||
|
updateTagsSelector();
|
||||||
|
updateTagsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DolphinFacetsWidget::ratingTerm() const
|
QString DolphinFacetsWidget::ratingTerm() const
|
||||||
|
@ -104,6 +129,12 @@ QString DolphinFacetsWidget::ratingTerm() const
|
||||||
terms << QStringLiteral("modified>=%1").arg(date.toString(Qt::ISODate));
|
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 "));
|
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.
|
// If term has sub terms, then sone of the sub terms are always "rating" and "modified" terms.
|
||||||
bool containsRating = false;
|
bool containsRating = false;
|
||||||
bool containsModified = false;
|
bool containsModified = false;
|
||||||
|
bool containsTag = false;
|
||||||
|
|
||||||
foreach (const QString& subTerm, subTerms) {
|
foreach (const QString& subTerm, subTerms) {
|
||||||
if (subTerm.startsWith(QLatin1String("rating>="))) {
|
if (subTerm.startsWith(QLatin1String("rating>="))) {
|
||||||
containsRating = true;
|
containsRating = true;
|
||||||
} else if (subTerm.startsWith(QLatin1String("modified>="))) {
|
} else if (subTerm.startsWith(QLatin1String("modified>="))) {
|
||||||
containsModified = true;
|
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)
|
void DolphinFacetsWidget::setRatingTerm(const QString& term)
|
||||||
|
@ -147,6 +182,10 @@ void DolphinFacetsWidget::setRatingTerm(const QString& term)
|
||||||
const QString value = subTerm.mid(8);
|
const QString value = subTerm.mid(8);
|
||||||
const int stars = value.toInt() / 2;
|
const int stars = value.toInt() / 2;
|
||||||
setRating(stars);
|
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)
|
void DolphinFacetsWidget::initComboBox(QComboBox* combo)
|
||||||
{
|
{
|
||||||
combo->setFrame(false);
|
combo->setFrame(false);
|
||||||
|
@ -191,3 +249,53 @@ void DolphinFacetsWidget::initComboBox(QComboBox* combo)
|
||||||
connect(combo, QOverload<int>::of(&QComboBox::activated), this, &DolphinFacetsWidget::facetChanged);
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -21,10 +21,12 @@
|
||||||
#define DOLPHINFACETSWIDGET_H
|
#define DOLPHINFACETSWIDGET_H
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <KCoreDirLister>
|
||||||
|
|
||||||
class QComboBox;
|
class QComboBox;
|
||||||
class QDate;
|
class QDate;
|
||||||
class QEvent;
|
class QEvent;
|
||||||
|
class QToolButton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Allows to filter search-queries by facets.
|
* @brief Allows to filter search-queries by facets.
|
||||||
|
@ -66,15 +68,27 @@ signals:
|
||||||
protected:
|
protected:
|
||||||
void changeEvent(QEvent* event) override;
|
void changeEvent(QEvent* event) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateTagsMenu();
|
||||||
|
void updateTagsMenuItems(const QUrl&, const KFileItemList& items);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setRating(const int stars);
|
void setRating(const int stars);
|
||||||
void setTimespan(const QDate& date);
|
void setTimespan(const QDate& date);
|
||||||
|
void addSearchTag(const QString& tag);
|
||||||
|
void removeSearchTag(const QString& tag);
|
||||||
|
|
||||||
void initComboBox(QComboBox* combo);
|
void initComboBox(QComboBox* combo);
|
||||||
|
void updateTagsSelector();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QComboBox* m_typeSelector;
|
QComboBox* m_typeSelector;
|
||||||
QComboBox* m_dateSelector;
|
QComboBox* m_dateSelector;
|
||||||
QComboBox* m_ratingSelector;
|
QComboBox* m_ratingSelector;
|
||||||
|
QToolButton* m_tagsSelector;
|
||||||
|
|
||||||
|
QStringList m_searchTags;
|
||||||
|
KCoreDirLister m_tagsLister;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -32,7 +32,8 @@ namespace {
|
||||||
{
|
{
|
||||||
static const QLatin1String searchTokens[] {
|
static const QLatin1String searchTokens[] {
|
||||||
QLatin1String("modified>="),
|
QLatin1String("modified>="),
|
||||||
QLatin1String("rating>=")
|
QLatin1String("rating>="),
|
||||||
|
QLatin1String("tag:"), QLatin1String("tag=")
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto &searchToken : searchTokens) {
|
for (const auto &searchToken : searchTokens) {
|
||||||
|
|
|
@ -45,6 +45,8 @@ void DolphinSearchBoxTest::testBalooSearchParsing_data()
|
||||||
const QString filename = QStringLiteral("filename:\"xyz\"");
|
const QString filename = QStringLiteral("filename:\"xyz\"");
|
||||||
const QString rating = QStringLiteral("rating>=2");
|
const QString rating = QStringLiteral("rating>=2");
|
||||||
const QString modified = QString("modified>=2019-08-07");
|
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>("searchString");
|
||||||
QTest::addColumn<QString>("expectedText");
|
QTest::addColumn<QString>("expectedText");
|
||||||
|
@ -55,7 +57,8 @@ void DolphinSearchBoxTest::testBalooSearchParsing_data()
|
||||||
QTest::newRow("content/empty") << "" << "" << QStringList();
|
QTest::newRow("content/empty") << "" << "" << QStringList();
|
||||||
QTest::newRow("content/singleQuote") << "\"" << "" << QStringList();
|
QTest::newRow("content/singleQuote") << "\"" << "" << QStringList();
|
||||||
QTest::newRow("content/doubleQuote") << "\"\"" << "" << QStringList();
|
QTest::newRow("content/doubleQuote") << "\"\"" << "" << QStringList();
|
||||||
// Test for empty `filename`
|
|
||||||
|
// Test for "Filename"
|
||||||
QTest::newRow("filename") << filename << text << QStringList();
|
QTest::newRow("filename") << filename << text << QStringList();
|
||||||
QTest::newRow("filename/empty") << "filename:" << "" << QStringList();
|
QTest::newRow("filename/empty") << "filename:" << "" << QStringList();
|
||||||
QTest::newRow("filename/singleQuote") << "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") << rating << "" << QStringList({rating});
|
||||||
QTest::newRow("rating+content") << rating + " " + text << text << QStringList({rating});
|
QTest::newRow("rating+content") << rating + " " + text << text << QStringList({rating});
|
||||||
QTest::newRow("rating+filename") << rating + " " + filename << text << QStringList({rating});
|
QTest::newRow("rating+filename") << rating + " " + filename << text << QStringList({rating});
|
||||||
|
|
||||||
// Test for modified date
|
// Test for modified date
|
||||||
QTest::newRow("modified") << modified << "" << QStringList({modified});
|
QTest::newRow("modified") << modified << "" << QStringList({modified});
|
||||||
QTest::newRow("modified+content") << modified + " " + text << text << QStringList({modified});
|
QTest::newRow("modified+content") << modified + " " + text << text << QStringList({modified});
|
||||||
QTest::newRow("modified+filename") << modified + " " + filename << 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
|
// Combined tests
|
||||||
QTest::newRow("rating+modified") << rating + " AND " + modified << "" << QStringList({modified, rating});
|
QTest::newRow("rating+modified")
|
||||||
QTest::newRow("rating+modified+content") << rating + " AND " + modified + " " + text << text << QStringList({modified, rating});
|
<< rating + " AND " + modified
|
||||||
QTest::newRow("rating+modified+filename") << rating + " AND " + modified + " " + filename << text << QStringList({modified, rating});
|
<< "" << 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});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue