diff --git a/kcms/colors/CMakeLists.txt b/kcms/colors/CMakeLists.txt --- a/kcms/colors/CMakeLists.txt +++ b/kcms/colors/CMakeLists.txt @@ -4,6 +4,7 @@ set(kcm_colors_SRCS ../krdb/krdb.cpp colors.cpp + filterproxymodel.cpp ) # needed for krdb diff --git a/kcms/colors/colors.h b/kcms/colors/colors.h --- a/kcms/colors/colors.h +++ b/kcms/colors/colors.h @@ -70,9 +70,9 @@ Q_INVOKABLE void getNewStuff(QQuickItem *ctx); Q_INVOKABLE void installSchemeFromFile(const QUrl &url); - Q_INVOKABLE void setPendingDeletion(int index, bool pending); + Q_INVOKABLE void setPendingDeletion(const QString &schemeName, bool pending); - Q_INVOKABLE void editScheme(int index, QQuickItem *ctx); + Q_INVOKABLE void editScheme(const QString &schemeName, QQuickItem *ctx); public Q_SLOTS: void load() override; diff --git a/kcms/colors/colors.cpp b/kcms/colors/colors.cpp --- a/kcms/colors/colors.cpp +++ b/kcms/colors/colors.cpp @@ -49,6 +49,8 @@ #include "../krdb/krdb.h" +#include "filterproxymodel.h" + static const QString s_defaultColorSchemeName = QStringLiteral("Breeze"); K_PLUGIN_FACTORY_WITH_JSON(KCMColorsFactory, "kcm_colors.json", registerPlugin();) @@ -58,6 +60,7 @@ , m_config(KSharedConfig::openConfig(QStringLiteral("kdeglobals"))) { qmlRegisterType(); + qmlRegisterType("org.kde.private.kcms.colors", 1, 0, "FilterProxyModel"); KAboutData *about = new KAboutData(QStringLiteral("kcm_colors"), i18n("Choose the color scheme"), QStringLiteral("2.0"), QString(), KAboutLicense::GPL); @@ -126,13 +129,13 @@ return m_tempCopyJob; } -void KCMColors::setPendingDeletion(int index, bool pending) +void KCMColors::setPendingDeletion(const QString &schemeName, bool pending) { - QModelIndex idx = m_model->index(index, 0); + QModelIndex idx = m_model->index(indexOfScheme(schemeName), 0); m_model->setData(idx, pending, PendingDeletionRole); - if (pending && selectedSchemeIndex() == index) { + if (pending && selectedSchemeIndex() == idx.row()) { // move to the next non-pending theme const auto nonPending = m_model->match(idx, PendingDeletionRole, false); setSelectedScheme(nonPending.first().data(SchemeNameRole).toString()); @@ -324,13 +327,13 @@ emit showSuccessMessage(i18n("Color scheme installed successfully.")); } -void KCMColors::editScheme(int index, QQuickItem *ctx) +void KCMColors::editScheme(const QString &schemeName, QQuickItem *ctx) { if (m_editDialogProcess) { return; } - QModelIndex idx = m_model->index(index, 0); + QModelIndex idx = m_model->index(indexOfScheme(schemeName), 0); m_editDialogProcess = new QProcess(this); connect(m_editDialogProcess, QOverload::of(&QProcess::finished), this, diff --git a/kcms/colors/filterproxymodel.h b/kcms/colors/filterproxymodel.h new file mode 100644 --- /dev/null +++ b/kcms/colors/filterproxymodel.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Kai Uwe Broulik + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#pragma once + +#include + +class FilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setSourceModelProxy NOTIFY sourceModelChanged) + + Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged) + Q_PROPERTY(SchemeFilter filter READ filter WRITE setFilter NOTIFY filterChanged) + +public: + FilterProxyModel(QObject *parent = nullptr); + ~FilterProxyModel() override; + + enum SchemeFilter { + AllSchemes, + LightSchemes, + DarkSchemes + }; + Q_ENUM(SchemeFilter) + + void setSourceModelProxy(QAbstractItemModel *sourceModel); + + QString query() const; + void setQuery(const QString &query); + + SchemeFilter filter() const; + void setFilter(SchemeFilter filter); + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +Q_SIGNALS: + void sourceModelChanged(); + + void queryChanged(); + void filterChanged(); + +private: + QString m_query; + SchemeFilter m_filter = AllSchemes; + +}; diff --git a/kcms/colors/filterproxymodel.cpp b/kcms/colors/filterproxymodel.cpp new file mode 100644 --- /dev/null +++ b/kcms/colors/filterproxymodel.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2019 Kai Uwe Broulik + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +#include "filterproxymodel.h" +#include "colors.h" + +FilterProxyModel::FilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) +{ + +} + +FilterProxyModel::~FilterProxyModel() = default; + +void FilterProxyModel::setSourceModelProxy(QAbstractItemModel *sourceModel) +{ + setSourceModel(sourceModel); + emit sourceModelChanged(); +} + +QString FilterProxyModel::query() const +{ + return m_query; +} + +void FilterProxyModel::setQuery(const QString &query) +{ + if (m_query != query) { + m_query = query; + + invalidateFilter(); + + emit queryChanged(); + } +} + +FilterProxyModel::SchemeFilter FilterProxyModel::filter() const +{ + return m_filter; +} + +void FilterProxyModel::setFilter(SchemeFilter filter) +{ + if (m_filter != filter) { + m_filter = filter; + + invalidateFilter(); + + emit filterChanged(); + } +} + +bool FilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + const QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); + + if (!m_query.isEmpty()) { + if (!idx.data(Qt::DisplayRole).toString().contains(m_query, Qt::CaseInsensitive) + && !idx.data(KCMColors::SchemeNameRole).toString().contains(m_query, Qt::CaseInsensitive)) { + return false; + } + } + + if (m_filter != AllSchemes) { + const QPalette palette = idx.data(KCMColors::PaletteRole).value(); + + const int windowBackgroundGray = qGray(palette.window().color().rgb()); + + if (m_filter == DarkSchemes) { + return windowBackgroundGray < 192; + } else if (m_filter == LightSchemes) { + return windowBackgroundGray >= 192; + } + } + + return true; +} diff --git a/kcms/colors/package/contents/ui/TextFieldButton.qml b/kcms/colors/package/contents/ui/TextFieldButton.qml new file mode 100644 --- /dev/null +++ b/kcms/colors/package/contents/ui/TextFieldButton.qml @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Kai Uwe Broulik + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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, see . + */ + +import QtQuick 2.6 +import QtQuick.Controls 2.3 as QtControls +import org.kde.kirigami 2.4 as Kirigami + +MouseArea { + id: button + + property alias iconSource: icon.source + property bool highlighted + + Kirigami.Icon { + id: icon + anchors.fill: parent + active: button.pressed + color: button.highlighted ? Kirigami.Theme.focusColor : Kirigami.Theme.textColor + } + + Behavior on opacity { + NumberAnimation { + duration: Kirigami.Units.longDuration + } + } +} + diff --git a/kcms/colors/package/contents/ui/main.qml b/kcms/colors/package/contents/ui/main.qml --- a/kcms/colors/package/contents/ui/main.qml +++ b/kcms/colors/package/contents/ui/main.qml @@ -26,11 +26,26 @@ import org.kde.kirigami 2.4 as Kirigami import org.kde.kconfig 1.0 // for KAuthorized import org.kde.kcm 1.1 as KCM +import org.kde.private.kcms.colors 1.0 as Private KCM.GridViewKCM { KCM.ConfigModule.quickHelp: i18n("This module lets you choose the color scheme.") - view.model: kcm.colorsModel + view.model: Private.FilterProxyModel { + query: searchField.text + filter: { + var filterOptions = filterMenu.contentData; + for (var i = 0, length = filterOptions.length; i < length; ++i) { + var option = filterOptions[i]; + if (option.hasOwnProperty("filterKey") && option.checked) { + return option.filterKey; + } + } + return Private.FilterProxyModel.AllSchemes; + } + + sourceModel: kcm.colorsModel + } view.currentIndex: kcm.selectedSchemeIndex enabled: !kcm.downloadingFile @@ -71,6 +86,73 @@ } } } + + QtControls.TextField { + id: searchField + Layout.fillWidth: true + placeholderText: i18n("Search color schemes...") + leftPadding: LayoutMirroring.enabled ? filterButtonsRow.width : undefined + rightPadding: LayoutMirroring.enabled ? undefined : filterButtonsRow.width + + RowLayout { + id: filterButtonsRow + anchors { + top: parent.top + bottom: parent.bottom + right: parent.right + topMargin: parent.topPadding + bottomMargin: parent.bottomPadding + rightMargin: 5 + } + + TextFieldButton { + Layout.fillHeight: true + Layout.preferredWidth: height + iconSource: "edit-clear" // TODO ltr/rtl + onClicked: searchField.clear() + opacity: searchField.length > 0 + } + + TextFieldButton { + Layout.fillHeight: true + Layout.preferredWidth: height + iconSource: "view-filter" + highlighted: view.model.filter !== Private.FilterProxyModel.AllSchemes + onClicked: filterMenu.open() + + QtControls.Menu { + id: filterMenu + modal: true + transformOrigin: QtControls.Popup.TopRight + x: parent.width - width + y: parent.height + + QtControls.MenuItem { + id: allSchemesEntry + readonly property int filterKey: Private.FilterProxyModel.AllSchemes + text: i18n("All Schemes") + checkable: true + checked: true + autoExclusive: true + } + + QtControls.MenuItem { + readonly property int filterKey: Private.FilterProxyModel.LightSchemes + text: i18n("Light Schemes") + checkable: true + autoExclusive: true + } + + QtControls.MenuItem { + readonly property int filterKey: Private.FilterProxyModel.DarkSchemes + text: i18n("Dark Schemes") + checkable: true + autoExclusive: true + } + } + } + } + } } view.delegate: KCM.GridDelegate { @@ -176,20 +258,20 @@ iconName: "document-edit" tooltip: i18n("Edit Color Scheme") enabled: !model.pendingDeletion - onTriggered: kcm.editScheme(model.index, parent) + onTriggered: kcm.editScheme(model.schemeName, parent) }, Kirigami.Action { iconName: "edit-delete" tooltip: i18n("Remove Color Scheme") enabled: model.removable visible: !model.pendingDeletion - onTriggered: kcm.setPendingDeletion(model.index, true) + onTriggered: kcm.setPendingDeletion(model.schemeName, true) }, Kirigami.Action { iconName: "edit-undo" tooltip: i18n("Restore Color Scheme") visible: model.pendingDeletion - onTriggered: kcm.setPendingDeletion(model.index, false) + onTriggered: kcm.setPendingDeletion(model.schemeName, false) } ] onClicked: {