┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/search/dolphinfacetswidget.cpp
diff options
context:
space:
mode:
authorFelix Ernst <[email protected]>2025-04-07 21:09:00 +0000
committerFelix Ernst <[email protected]>2025-04-07 21:09:00 +0000
commit4102ccb80457eea44ea280f0ace2a419602bc34b (patch)
tree841e039cf9864276c968a397a2ae75c363199342 /src/search/dolphinfacetswidget.cpp
parentbfc177d3d1bc5a4a241e35d59086e4824e7c0bd3 (diff)
Rewrite search integration
This huge commit is a nearly complete rewrite of the Dolphin search code. It implements most of the improved Dolphin search UI/UX as designed and discussed in a collaborative effort by Kristen McWilliam, Jin Liu, Andy Betts, Tagwerk, a few others and me. See https://invent.kde.org/system/dolphin/-/issues/46. # Notable changes - A toggle to change the search tool is provided as most contributors deemed that useful in https://invent.kde.org/system/dolphin/-/merge_requests/642#note_985112. - The default search is changed to filenamesearch for maximum reliability. - Removing all search parameters will take users back to the view state prior to starting a search instead of keeping the search results open. - The UI for choosing file types or modification dates has been made more powerful with more granularity and more options. - Most search parameters can be configured from a popup menu which gives us extra space for extra clarity. - Labels and help buttons as well as hyperlinks to settings makes sure the user always knows why some search parameters are unavailable in some contexts. - Chips show important search parameters while the popup is closed. They allow quickly removing filters. - The titles of the search and the input field placeholder message change to make clear whether file names or file contents are searched. - When the user actively switches the search tool, whether content should be searched, or whether to search everywhere, this is preserved for the initial state of the search bar when the user opens it the next time after restarting Dolphin. # Architecture - The new DolphinQuery class is independent of the UI and contains all search parameters modifiable in Dolphin as easy setters and getters. - DolphinQuery objects are also used to update the states of every component in the search UI. There is now a clear separation of UI and search configuration/DolphinQuery. - DolphinQuery is responsible for exporting to and importing from search URLs. - The search UI always reflects the currently configured DolphinQuery no matter if the user changed the UI to change the DolphinQuery or loaded a DolphinQuery/older search URL which then is reflected in the UI. - I tried to simplify all classes and their interaction between each other as much as possible. - I added some tests BUG: 386754 CCBUG: 435119 CCBUG: 458761 BUG: 446387 BUG: 470136 CCBUG: 471556 CCBUG: 475439 CCBUG: 477969 BUG: 480001 BUG: 483578 BUG: 488047 BUG: 488845 BUG: 500103 FIXED-IN: 25.08
Diffstat (limited to 'src/search/dolphinfacetswidget.cpp')
-rw-r--r--src/search/dolphinfacetswidget.cpp312
1 files changed, 0 insertions, 312 deletions
diff --git a/src/search/dolphinfacetswidget.cpp b/src/search/dolphinfacetswidget.cpp
deleted file mode 100644
index da36caa36..000000000
--- a/src/search/dolphinfacetswidget.cpp
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <[email protected]>
- * SPDX-FileCopyrightText: 2019 Ismael Asensio <[email protected]>
- *
- * 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)
- , 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("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"));
- 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()));
- initComboBox(m_dateSelector);
-
- m_ratingSelector = new QComboBox(this);
- m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("non-starred-symbolic")), i18nc("@item:inlistbox", "Any Rating"), 0);
- m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "1 or more"), 1);
- m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "2 or more"), 2);
- m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "3 or more"), 3);
- m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "4 or more"), 4);
- m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "Highest Rating"), 5);
- initComboBox(m_ratingSelector);
-
- 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);
-
- resetSearchTerms();
-}
-
-DolphinFacetsWidget::~DolphinFacetsWidget()
-{
-}
-
-void DolphinFacetsWidget::changeEvent(QEvent *event)
-{
- if (event->type() == QEvent::EnabledChange) {
- if (isEnabled()) {
- updateTagsSelector();
- } else {
- resetSearchTerms();
- }
- }
-}
-
-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();
-}
-
-QStringList DolphinFacetsWidget::searchTerms() const
-{
- QStringList terms;
-
- if (m_ratingSelector->currentIndex() > 0) {
- const int rating = m_ratingSelector->currentData().toInt() * 2;
- terms << QStringLiteral("rating>=%1").arg(rating);
- }
-
- if (m_dateSelector->currentIndex() > 0) {
- const QDate date = m_dateSelector->currentData().toDate();
- terms << QStringLiteral("modified>=%1").arg(date.toString(Qt::ISODate));
- }
-
- 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
-{
- return m_typeSelector->currentData().toString();
-}
-
-bool DolphinFacetsWidget::isSearchTerm(const QString &term) const
-{
- static const QLatin1String searchTokens[]{QLatin1String("modified>="), QLatin1String("rating>="), QLatin1String("tag:"), QLatin1String("tag=")};
-
- for (const auto &searchToken : searchTokens) {
- if (term.startsWith(searchToken)) {
- return true;
- }
- }
- return false;
-}
-
-void DolphinFacetsWidget::setSearchTerm(const QString &term)
-{
- 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 QString &type)
-{
- for (int index = 0; index <= m_typeSelector->count(); index++) {
- if (type == m_typeSelector->itemData(index).toString()) {
- m_typeSelector->setCurrentIndex(index);
- break;
- }
- }
-}
-
-void DolphinFacetsWidget::setRating(const int stars)
-{
- if (stars < 0 || stars > 5) {
- return;
- }
- m_ratingSelector->setCurrentIndex(stars);
-}
-
-void DolphinFacetsWidget::setTimespan(const QDate &date)
-{
- if (!date.isValid()) {
- return;
- }
- m_dateSelector->setCurrentIndex(0);
- for (int index = 1; index <= m_dateSelector->count(); index++) {
- if (date >= m_dateSelector->itemData(index).toDate()) {
- m_dateSelector->setCurrentIndex(index);
- break;
- }
- }
-}
-
-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, &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"