diff options
Diffstat (limited to 'src/search/selectors')
| -rw-r--r-- | src/search/selectors/dateselector.cpp | 61 | ||||
| -rw-r--r-- | src/search/selectors/dateselector.h | 42 | ||||
| -rw-r--r-- | src/search/selectors/filetypeselector.cpp | 81 | ||||
| -rw-r--r-- | src/search/selectors/filetypeselector.h | 36 | ||||
| -rw-r--r-- | src/search/selectors/minimumratingselector.cpp | 51 | ||||
| -rw-r--r-- | src/search/selectors/minimumratingselector.h | 42 | ||||
| -rw-r--r-- | src/search/selectors/tagsselector.cpp | 189 | ||||
| -rw-r--r-- | src/search/selectors/tagsselector.h | 45 |
8 files changed, 547 insertions, 0 deletions
diff --git a/src/search/selectors/dateselector.cpp b/src/search/selectors/dateselector.cpp new file mode 100644 index 000000000..2951b1dc4 --- /dev/null +++ b/src/search/selectors/dateselector.cpp @@ -0,0 +1,61 @@ +/* + SPDX-FileCopyrightText: 2025 Felix Ernst <[email protected]> + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "dateselector.h" + +#include "../dolphinquery.h" + +#include <KDatePicker> +#include <KDatePickerPopup> +#include <KFormat> +#include <KLocalizedString> + +using namespace Search; + +Search::DateSelector::DateSelector(std::shared_ptr<const DolphinQuery> dolphinQuery, QWidget *parent) + : QToolButton{parent} + , UpdatableStateInterface{dolphinQuery} + , m_datePickerPopup{ + new KDatePickerPopup{KDatePickerPopup::NoDate | KDatePickerPopup::DatePicker | KDatePickerPopup::Words, dolphinQuery->modifiedSinceDate(), this}} +{ + setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + setPopupMode(QToolButton::InstantPopup); + + m_datePickerPopup->setDateRange(QDate{}, QDate::currentDate()); + connect(m_datePickerPopup, &KDatePickerPopup::dateChanged, this, [this](const QDate &activatedDate) { + if (activatedDate == m_searchConfiguration->modifiedSinceDate()) { + return; // Already selected. + } + DolphinQuery searchConfigurationCopy = *m_searchConfiguration; + searchConfigurationCopy.setModifiedSinceDate(activatedDate); + Q_EMIT configurationChanged(std::move(searchConfigurationCopy)); + }); + setMenu(m_datePickerPopup); + + updateStateToMatch(std::move(dolphinQuery)); +} + +void DateSelector::removeRestriction() +{ + Q_ASSERT(m_searchConfiguration->modifiedSinceDate().isValid()); + DolphinQuery searchConfigurationCopy = *m_searchConfiguration; + searchConfigurationCopy.setModifiedSinceDate(QDate{}); + Q_EMIT configurationChanged(std::move(searchConfigurationCopy)); +} + +void DateSelector::updateState(const std::shared_ptr<const DolphinQuery> &dolphinQuery) +{ + m_datePickerPopup->setDate(dolphinQuery->modifiedSinceDate()); + if (!dolphinQuery->modifiedSinceDate().isValid()) { + setIcon(QIcon{}); // No icon for the empty state + setText(i18nc("@item:inlistbox", "Any Date")); + return; + } + setIcon(QIcon::fromTheme(QStringLiteral("view-calendar"))); + QLocale local; + KFormat formatter(local); + setText(formatter.formatRelativeDate(dolphinQuery->modifiedSinceDate(), QLocale::ShortFormat)); +} diff --git a/src/search/selectors/dateselector.h b/src/search/selectors/dateselector.h new file mode 100644 index 000000000..99cecec06 --- /dev/null +++ b/src/search/selectors/dateselector.h @@ -0,0 +1,42 @@ +/* + SPDX-FileCopyrightText: 2025 Felix Ernst <[email protected]> + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef DATESELECTOR_H +#define DATESELECTOR_H + +#include "../updatablestateinterface.h" + +#include <QToolButton> + +class KDatePickerPopup; + +namespace Search +{ + +class DateSelector : public QToolButton, public UpdatableStateInterface +{ + Q_OBJECT + +public: + explicit DateSelector(std::shared_ptr<const DolphinQuery> dolphinQuery, QWidget *parent = nullptr); + + /** Causes configurationChanged() to be emitted with a DolphinQuery object that does not contain any restriction settable by this class. */ + void removeRestriction(); + +Q_SIGNALS: + /** Is emitted whenever settings have changed and a new search might be necessary. */ + void configurationChanged(const DolphinQuery &dolphinQuery); + +private: + void updateState(const std::shared_ptr<const DolphinQuery> &dolphinQuery) override; + +private: + KDatePickerPopup *m_datePickerPopup = nullptr; +}; + +} + +#endif // DATESELECTOR_H diff --git a/src/search/selectors/filetypeselector.cpp b/src/search/selectors/filetypeselector.cpp new file mode 100644 index 000000000..7852aced2 --- /dev/null +++ b/src/search/selectors/filetypeselector.cpp @@ -0,0 +1,81 @@ +/* + SPDX-FileCopyrightText: 2025 Felix Ernst <[email protected]> + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "filetypeselector.h" + +#include "../dolphinquery.h" + +#include <KFileMetaData/TypeInfo> +#include <KLocalizedString> + +using namespace Search; + +FileTypeSelector::FileTypeSelector(std::shared_ptr<const DolphinQuery> dolphinQuery, QWidget *parent) + : QComboBox{parent} + , UpdatableStateInterface{dolphinQuery} +{ + for (KFileMetaData::Type::Type type = KFileMetaData::Type::FirstType; type <= KFileMetaData::Type::LastType; type = KFileMetaData::Type::Type(type + 1)) { + switch (type) { + case KFileMetaData::Type::Empty: + addItem(/** No icon for the empty state */ i18nc("@item:inlistbox", "Any Type"), type); + continue; + case KFileMetaData::Type::Archive: + addItem(QIcon::fromTheme(QStringLiteral("package-x-generic")), KFileMetaData::TypeInfo{type}.displayName(), type); + continue; + case KFileMetaData::Type::Audio: + addItem(QIcon::fromTheme(QStringLiteral("audio-x-generic")), KFileMetaData::TypeInfo{type}.displayName(), type); + continue; + case KFileMetaData::Type::Video: + addItem(QIcon::fromTheme(QStringLiteral("video-x-generic")), KFileMetaData::TypeInfo{type}.displayName(), type); + continue; + case KFileMetaData::Type::Image: + addItem(QIcon::fromTheme(QStringLiteral("image-x-generic")), KFileMetaData::TypeInfo{type}.displayName(), type); + continue; + case KFileMetaData::Type::Document: + addItem(QIcon::fromTheme(QStringLiteral("text-x-generic")), KFileMetaData::TypeInfo{type}.displayName(), type); + continue; + case KFileMetaData::Type::Spreadsheet: + addItem(QIcon::fromTheme(QStringLiteral("table")), KFileMetaData::TypeInfo{type}.displayName(), type); + continue; + case KFileMetaData::Type::Presentation: + addItem(QIcon::fromTheme(QStringLiteral("view-presentation")), KFileMetaData::TypeInfo{type}.displayName(), type); + continue; + case KFileMetaData::Type::Text: + addItem(QIcon::fromTheme(QStringLiteral("text-x-generic")), KFileMetaData::TypeInfo{type}.displayName(), type); + continue; + case KFileMetaData::Type::Folder: + addItem(QIcon::fromTheme(QStringLiteral("inode-directory")), KFileMetaData::TypeInfo{type}.displayName(), type); + continue; + default: + addItem(QIcon(), KFileMetaData::TypeInfo{type}.displayName(), type); + } + } + + connect(this, &QComboBox::activated, this, [this](int activatedIndex) { + auto activatedType = itemData(activatedIndex).value<KFileMetaData::Type::Type>(); + if (activatedType == m_searchConfiguration->fileType()) { + return; // Already selected. + } + DolphinQuery searchConfigurationCopy = *m_searchConfiguration; + searchConfigurationCopy.setFileType(activatedType); + Q_EMIT configurationChanged(std::move(searchConfigurationCopy)); + }); + + updateStateToMatch(std::move(dolphinQuery)); +} + +void Search::FileTypeSelector::removeRestriction() +{ + Q_ASSERT(m_searchConfiguration->fileType() != KFileMetaData::Type::Empty); + DolphinQuery searchConfigurationCopy = *m_searchConfiguration; + searchConfigurationCopy.setFileType(KFileMetaData::Type::Empty); + Q_EMIT configurationChanged(std::move(searchConfigurationCopy)); +} + +void FileTypeSelector::updateState(const std::shared_ptr<const DolphinQuery> &dolphinQuery) +{ + setCurrentIndex(findData(dolphinQuery->fileType())); +} diff --git a/src/search/selectors/filetypeselector.h b/src/search/selectors/filetypeselector.h new file mode 100644 index 000000000..bfc827344 --- /dev/null +++ b/src/search/selectors/filetypeselector.h @@ -0,0 +1,36 @@ +/* + SPDX-FileCopyrightText: 2025 Felix Ernst <[email protected]> + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef FILETYPESELECTOR_H +#define FILETYPESELECTOR_H + +#include "../updatablestateinterface.h" + +#include <QComboBox> + +namespace Search +{ + +class FileTypeSelector : public QComboBox, public UpdatableStateInterface +{ + Q_OBJECT + +public: + explicit FileTypeSelector(std::shared_ptr<const DolphinQuery> dolphinQuery, QWidget *parent = nullptr); + + /** Causes configurationChanged() to be emitted with a DolphinQuery object that does not contain any restriction settable by this class. */ + void removeRestriction(); + +Q_SIGNALS: + /** Is emitted whenever settings have changed and a new search might be necessary. */ + void configurationChanged(const DolphinQuery &dolphinQuery); + +private: + void updateState(const std::shared_ptr<const DolphinQuery> &dolphinQuery) override; +}; +} + +#endif // FILETYPESELECTOR_H diff --git a/src/search/selectors/minimumratingselector.cpp b/src/search/selectors/minimumratingselector.cpp new file mode 100644 index 000000000..e46dfd4e0 --- /dev/null +++ b/src/search/selectors/minimumratingselector.cpp @@ -0,0 +1,51 @@ +/* + SPDX-FileCopyrightText: 2012 Peter Penz <[email protected]> + SPDX-FileCopyrightText: 2025 Felix Ernst <[email protected]> + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "minimumratingselector.h" + +#include "../dolphinquery.h" + +#include <KLocalizedString> + +using namespace Search; + +MinimumRatingSelector::MinimumRatingSelector(std::shared_ptr<const DolphinQuery> dolphinQuery, QWidget *parent) + : QComboBox{parent} + , UpdatableStateInterface{dolphinQuery} +{ + addItem(/** No icon for the empty state */ i18nc("@item:inlistbox", "Any Rating"), 0); + addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "1 or more"), 2); + addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "2 or more"), 4); + addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "3 or more"), 6); + addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "4 or more"), 8); + addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox 5 star rating, has a star icon in front", "5"), 10); + + connect(this, &QComboBox::activated, this, [this](int activatedIndex) { + auto activatedMinimumRating = itemData(activatedIndex).value<int>(); + if (activatedMinimumRating == m_searchConfiguration->minimumRating()) { + return; // Already selected. + } + DolphinQuery searchConfigurationCopy = *m_searchConfiguration; + searchConfigurationCopy.setMinimumRating(activatedMinimumRating); + Q_EMIT configurationChanged(std::move(searchConfigurationCopy)); + }); + + updateStateToMatch(std::move(dolphinQuery)); +} + +void MinimumRatingSelector::removeRestriction() +{ + Q_ASSERT(m_searchConfiguration->minimumRating() > 0); + DolphinQuery searchConfigurationCopy = *m_searchConfiguration; + searchConfigurationCopy.setMinimumRating(0); + Q_EMIT configurationChanged(std::move(searchConfigurationCopy)); +} + +void MinimumRatingSelector::updateState(const std::shared_ptr<const DolphinQuery> &dolphinQuery) +{ + setCurrentIndex(findData(dolphinQuery->minimumRating())); +} diff --git a/src/search/selectors/minimumratingselector.h b/src/search/selectors/minimumratingselector.h new file mode 100644 index 000000000..02364cd1a --- /dev/null +++ b/src/search/selectors/minimumratingselector.h @@ -0,0 +1,42 @@ +/* + SPDX-FileCopyrightText: 2025 Felix Ernst <[email protected]> + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef MINIMUMRATINGSELECTOR_H +#define MINIMUMRATINGSELECTOR_H + +#include "../updatablestateinterface.h" + +#include <QComboBox> + +namespace Search +{ + +/** + * @brief Select the minimum rating search results should have. + * Values <= 0 mean no restriction. 1 is half a star, 2 one full star, etc. 10 is typically the maximum in KDE software. + * Since this box only allows selecting full star ratings, the possible values are 0, 2, 4, 6, 8, 10. + */ +class MinimumRatingSelector : public QComboBox, public UpdatableStateInterface +{ + Q_OBJECT + +public: + explicit MinimumRatingSelector(std::shared_ptr<const DolphinQuery> dolphinQuery, QWidget *parent = nullptr); + + /** Causes configurationChanged() to be emitted with a DolphinQuery object that does not contain any restriction settable by this class. */ + void removeRestriction(); + +Q_SIGNALS: + /** Is emitted whenever settings have changed and a new search might be necessary. */ + void configurationChanged(const DolphinQuery &dolphinQuery); + +private: + void updateState(const std::shared_ptr<const DolphinQuery> &dolphinQuery) override; +}; + +} + +#endif // MINIMUMRATINGSELECTOR_H diff --git a/src/search/selectors/tagsselector.cpp b/src/search/selectors/tagsselector.cpp new file mode 100644 index 000000000..95d7ff52a --- /dev/null +++ b/src/search/selectors/tagsselector.cpp @@ -0,0 +1,189 @@ +/* + SPDX-FileCopyrightText: 2019 Ismael Asensio <[email protected]> + SPDX-FileCopyrightText: 2025 Felix Ernst <[email protected]> + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "tagsselector.h" + +#include "../dolphinquery.h" + +#include <KCoreDirLister> +#include <KLocalizedString> +#include <KProtocolInfo> + +#include <QMenu> +#include <QStringList> + +using namespace Search; + +namespace +{ +/** + * @brief Provides the list of tags to all TagsSelectors. + * + * This QStringList of tags populates itself. Additional tags the user is actively searching for can be added with addTag() even though we assume that no file + * with such a tag exists if we did not find it automatically. + * @note Use the tagsList() function below instead of constructing TagsList objects yourself. + */ +class TagsList : public QStringList, public QObject +{ +public: + TagsList() + : QStringList{} + { + m_tagsLister->openUrl(QUrl(QStringLiteral("tags:/")), KCoreDirLister::OpenUrlFlag::Reload); + connect(m_tagsLister.get(), &KCoreDirLister::itemsAdded, this, [this](const QUrl &, const KFileItemList &items) { + for (const KFileItem &item : items) { + append(item.text()); + } + removeDuplicates(); + sort(Qt::CaseInsensitive); + }); + }; + + virtual ~TagsList() = default; + + void addTag(const QString &tag) + { + if (contains(tag)) { + return; + } + append(tag); + sort(Qt::CaseInsensitive); + }; + + /** Used to access to the itemsAdded signal so outside users of this class know when items were added. */ + KCoreDirLister *tagsLister() const + { + return m_tagsLister.get(); + }; + +private: + std::unique_ptr<KCoreDirLister> m_tagsLister = std::make_unique<KCoreDirLister>(); +}; + +/** + * @returns a list of all tags found since the construction of the first TagsSelector object. + * @note Use this function instead of constructing additional TagsList objects. + */ +TagsList *tagsList() +{ + static TagsList *g_tagsList = new TagsList; + return g_tagsList; +} +} + +Search::TagsSelector::TagsSelector(std::shared_ptr<const DolphinQuery> dolphinQuery, QWidget *parent) + : QToolButton{parent} + , UpdatableStateInterface{dolphinQuery} +{ + setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + setPopupMode(QToolButton::InstantPopup); + + auto menu = new QMenu{this}; + setMenu(menu); + connect(menu, &QMenu::aboutToShow, this, [this]() { + TagsList *tags = tagsList(); + // The TagsList might not have been updated for a while and new tags might be available. We update now, but this is unfortunately not instant. + // However this selector is connected to the itemsAdded() signal, so we will add any new tags eventually. + tags->tagsLister()->updateDirectory(tags->tagsLister()->url()); + updateMenu(m_searchConfiguration); + }); + + TagsList *tags = tagsList(); + if (tags->isEmpty()) { + // Either there really are no tags or the TagsList has not loaded the tags yet. It only begins loading the first time tagsList() is globally called. + setEnabled(false); + connect( + tags->tagsLister(), + &KCoreDirLister::itemsAdded, + this, + [this]() { + setEnabled(true); + }, + Qt::SingleShotConnection); + } + + connect(tags->tagsLister(), &KCoreDirLister::itemsAdded, this, [this, menu]() { + if (menu->isVisible()) { + updateMenu(m_searchConfiguration); + } + }); + + updateStateToMatch(std::move(dolphinQuery)); +} + +void TagsSelector::removeRestriction() +{ + Q_ASSERT(!m_searchConfiguration->requiredTags().isEmpty()); + DolphinQuery searchConfigurationCopy = *m_searchConfiguration; + searchConfigurationCopy.setRequiredTags({}); + Q_EMIT configurationChanged(std::move(searchConfigurationCopy)); +} + +void TagsSelector::updateMenu(const std::shared_ptr<const DolphinQuery> &dolphinQuery) +{ + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("tags"))) { + return; + } + const bool menuWasVisible = menu()->isVisible(); + if (menuWasVisible) { + menu()->hide(); // The menu needs to be hidden now, then updated, and then shown again. + } + // Delete all existing actions in the menu + for (QAction *action : menu()->actions()) { + action->deleteLater(); + } + menu()->clear(); + // Populate the menu + const TagsList *tags = tagsList(); + const bool onlyOneTagExists = tags->count() == 1; + + for (const QString &tag : *tags) { + QAction *tagAction = new QAction{QIcon::fromTheme(QStringLiteral("tag")), tag, menu()}; + tagAction->setCheckable(true); + tagAction->setChecked(dolphinQuery->requiredTags().contains(tag)); + connect(tagAction, &QAction::triggered, this, [this, tag, onlyOneTagExists](bool checked) { + QStringList requiredTags = m_searchConfiguration->requiredTags(); + if (checked == requiredTags.contains(tag)) { + return; // Already selected. + } + if (checked) { + requiredTags.append(tag); + } else { + requiredTags.removeOne(tag); + } + DolphinQuery searchConfigurationCopy = *m_searchConfiguration; + searchConfigurationCopy.setRequiredTags(requiredTags); + Q_EMIT configurationChanged(std::move(searchConfigurationCopy)); + + if (!onlyOneTagExists) { + // Keep the menu open to allow easier tag multi-selection. + menu()->show(); + } + }); + menu()->addAction(tagAction); + } + if (menuWasVisible) { + menu()->show(); + } +} + +void TagsSelector::updateState(const std::shared_ptr<const DolphinQuery> &dolphinQuery) +{ + if (dolphinQuery->requiredTags().count()) { + setIcon(QIcon::fromTheme(QStringLiteral("tag"))); + setText(dolphinQuery->requiredTags().join(i18nc("list separator for file tags e.g. all images tagged 'family & party & 2025'", " && "))); + } else { + setIcon(QIcon{}); // No icon for the empty state + setText(i18nc("@action:button Required tags for search results: None", "None")); + } + for (const auto &tag : dolphinQuery->requiredTags()) { + tagsList()->addTag(tag); // We add it just in case this tag is not (or no longer) available on the system. This way the UI always works as expected. + } + if (menu()->isVisible()) { + updateMenu(dolphinQuery); + } +} diff --git a/src/search/selectors/tagsselector.h b/src/search/selectors/tagsselector.h new file mode 100644 index 000000000..386cbb924 --- /dev/null +++ b/src/search/selectors/tagsselector.h @@ -0,0 +1,45 @@ +/* + SPDX-FileCopyrightText: 2019 Ismael Asensio <[email protected]> + SPDX-FileCopyrightText: 2025 Felix Ernst <[email protected]> + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef TAGSSELECTOR_H +#define TAGSSELECTOR_H + +#include "../updatablestateinterface.h" + +#include <QToolButton> + +namespace Search +{ + +class TagsSelector : public QToolButton, public UpdatableStateInterface +{ + Q_OBJECT + +public: + explicit TagsSelector(std::shared_ptr<const DolphinQuery> dolphinQuery, QWidget *parent = nullptr); + + /** Causes configurationChanged() to be emitted with a DolphinQuery object that does not contain any restriction settable by this class. */ + void removeRestriction(); + +Q_SIGNALS: + /** Is emitted whenever settings have changed and a new search might be necessary. */ + void configurationChanged(const DolphinQuery &dolphinQuery); + +private: + /** + * Updates the menu items for the various tags based on @p dolphinQuery and the available tags. + * This method should only be called when the menu is QMenu::aboutToShow() or the menu is currently visible already while this selector's state changes. + * If the menu is open when this method is called, the menu will automatically be reopened to reflect the updated contents. + */ + void updateMenu(const std::shared_ptr<const DolphinQuery> &dolphinQuery); + + void updateState(const std::shared_ptr<const DolphinQuery> &dolphinQuery) override; +}; + +} + +#endif // TAGSSELECTOR_H |
