diff --git a/.reuse/dep5 b/.reuse/dep5 index a29da500..47588b3a 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -37,3 +37,10 @@ License: GPL-3.0-or-later Files: *.deepin.dde.*.xml */org.desktopspec.*.xml Copyright: UnionTech Software Technology Co., Ltd. License: GPL-3.0-or-later + +# vendored from KItemModels +Files: src/quick/ksortfilterproxymodel.cpp src/quick/ksortfilterproxymodel.h +Copyright: 2010, Marco Martin + 2019, David Edmundson +License: LGPL-2.0-or-later + diff --git a/main.cpp b/main.cpp index ecacd79b..e119905e 100644 --- a/main.cpp +++ b/main.cpp @@ -22,6 +22,7 @@ #include #include #include +#include DCORE_USE_NAMESPACE DGUI_USE_NAMESPACE @@ -76,6 +77,7 @@ int main(int argc, char* argv[]) LauncherController::instance().setVisible(true); } + qmlRegisterType("org.deepin.vendored", 1, 0, "KSortFilterProxyModel"); qmlRegisterUncreatableType("org.deepin.launchpad", 1, 0, "AppItem", "AppItem should only be created from C++ side"); qmlRegisterSingletonInstance("org.deepin.launchpad", 1, 0, "AppsModel", &AppsModel::instance()); qmlRegisterSingletonInstance("org.deepin.launchpad", 1, 0, "FavoritedProxyModel", &FavoritedProxyModel::instance()); diff --git a/qml/DebugDialog.qml b/qml/DebugDialog.qml index 6afb09a8..ffdbb979 100644 --- a/qml/DebugDialog.qml +++ b/qml/DebugDialog.qml @@ -9,7 +9,6 @@ import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 import org.deepin.dtk 1.0 -import org.kde.kitemmodels 1.0 import org.deepin.launchpad 1.0 diff --git a/qml/FullscreenFrame.qml b/qml/FullscreenFrame.qml index f3861c74..41dba330 100644 --- a/qml/FullscreenFrame.qml +++ b/qml/FullscreenFrame.qml @@ -9,7 +9,7 @@ import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 import org.deepin.dtk 1.0 -import org.kde.kitemmodels 1.0 +import org.deepin.vendored 1.0 import org.deepin.launchpad 1.0 diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt index 8439b83e..4856d3b1 100644 --- a/src/quick/CMakeLists.txt +++ b/src/quick/CMakeLists.txt @@ -8,10 +8,17 @@ add_library(launcher-qml-utils OBJECT) target_sources(launcher-qml-utils PRIVATE - launcherappiconprovider.h launcherappiconprovider.cpp - blurhashimageprovider.h blurhashimageprovider.cpp + ksortfilterproxymodel.cpp +) + +target_sources(launcher-qml-utils PUBLIC +FILE_SET HEADERS +FILES + launcherappiconprovider.h + blurhashimageprovider.h + ksortfilterproxymodel.h ) target_include_directories(launcher-qml-utils PUBLIC ${CMAKE_CURRENT_LIST_DIR}) diff --git a/src/quick/ksortfilterproxymodel.cpp b/src/quick/ksortfilterproxymodel.cpp new file mode 100644 index 00000000..d5d538b7 --- /dev/null +++ b/src/quick/ksortfilterproxymodel.cpp @@ -0,0 +1,263 @@ +/* + * Copyright 2010 by Marco Martin + * Copyright 2019 by David Edmundson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "ksortfilterproxymodel.h" + +#include +#include + +// This is just an ECM-based logging category thing +//#include "kitemmodels_debug.h" +#include +Q_DECLARE_LOGGING_CATEGORY(KITEMMODELS_LOG) +Q_LOGGING_CATEGORY(KITEMMODELS_LOG, "kf.itemmodels.core", QtInfoMsg) + +KSortFilterProxyModel::KSortFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setDynamicSortFilter(true); + connect(this, &KSortFilterProxyModel::modelReset, this, &KSortFilterProxyModel::rowCountChanged); + connect(this, &KSortFilterProxyModel::rowsInserted, this, &KSortFilterProxyModel::rowCountChanged); + connect(this, &KSortFilterProxyModel::rowsRemoved, this, &KSortFilterProxyModel::rowCountChanged); + // NOTE: some models actually fill their roleNames() only when they get some actual data, this works around the bad behavior + connect(this, &KSortFilterProxyModel::rowCountChanged, this, &KSortFilterProxyModel::syncRoleNames); + + connect(this, &KSortFilterProxyModel::filterRoleChanged, this, [this](int role) { + const QString roleName = QString::fromUtf8(roleNames().value(role)); + if (m_filterRoleName != roleName) { + m_filterRoleName = roleName; + Q_EMIT filterRoleNameChanged(); + } + }); + + connect(this, &KSortFilterProxyModel::sortRoleChanged, this, [this](int role) { + const QString roleName = QString::fromUtf8(roleNames().value(role)); + if (m_sortRoleName != roleName) { + m_sortRoleName = roleName; + Q_EMIT sortRoleNameChanged(); + } + }); +} + +KSortFilterProxyModel::~KSortFilterProxyModel() +{ +} + +void KSortFilterProxyModel::syncRoleNames() +{ + if (!sourceModel()) { + return; + } + + m_roleIds.clear(); + const QHash rNames = roleNames(); + m_roleIds.reserve(rNames.count()); + for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) { + m_roleIds[QString::fromUtf8(i.value())] = i.key(); + } + + setFilterRoleName(m_filterRoleName); + setSortRoleName(m_sortRoleName); +} + +int KSortFilterProxyModel::roleNameToId(const QString &name) const +{ + return m_roleIds.value(name, Qt::DisplayRole); +} + +void KSortFilterProxyModel::setModel(QAbstractItemModel *model) +{ + if (model == sourceModel()) { + return; + } + + QSortFilterProxyModel::setSourceModel(model); + + if (m_componentCompleted) { + syncRoleNames(); + setFilterRoleName(m_filterRoleName); + setSortRoleName(m_sortRoleName); + } +} + +bool KSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if (m_filterRowCallback.isCallable()) { + QJSEngine *engine = qjsEngine(this); + QJSValueList args = {QJSValue(source_row), engine->toScriptValue(source_parent)}; + + QJSValue result = const_cast(this)->m_filterRowCallback.call(args); + if (result.isError()) { + qCWarning(KITEMMODELS_LOG) << "Row filter callback produced an error:"; + qCWarning(KITEMMODELS_LOG) << result.toString(); + return true; + } else { + return result.toBool(); + } + } + + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); +} + +bool KSortFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const +{ + if (m_filterColumnCallback.isCallable()) { + QJSEngine *engine = qjsEngine(this); + QJSValueList args = {QJSValue(source_column), engine->toScriptValue(source_parent)}; + + QJSValue result = const_cast(this)->m_filterColumnCallback.call(args); + if (result.isError()) { + qCWarning(KITEMMODELS_LOG) << "Row filter callback produced an error:"; + qCWarning(KITEMMODELS_LOG) << result.toString(); + return true; + } else { + return result.toBool(); + } + } + + return QSortFilterProxyModel::filterAcceptsColumn(source_column, source_parent); +} + +void KSortFilterProxyModel::setFilterString(const QString &filterString) +{ + if (filterString == m_filterString) { + return; + } + m_filterString = filterString; + QSortFilterProxyModel::setFilterFixedString(filterString); + Q_EMIT filterStringChanged(); +} + +QString KSortFilterProxyModel::filterString() const +{ + return m_filterString; +} + +QJSValue KSortFilterProxyModel::filterRowCallback() const +{ + return m_filterRowCallback; +} + +void KSortFilterProxyModel::setFilterRowCallback(const QJSValue &callback) +{ + if (m_filterRowCallback.strictlyEquals(callback)) { + return; + } + + if (!callback.isNull() && !callback.isCallable()) { + return; + } + + m_filterRowCallback = callback; + invalidateFilter(); + + Q_EMIT filterRowCallbackChanged(callback); +} + +void KSortFilterProxyModel::setFilterColumnCallback(const QJSValue &callback) +{ + if (m_filterColumnCallback.strictlyEquals(callback)) { + return; + } + + if (!callback.isNull() && !callback.isCallable()) { + return; + } + + m_filterColumnCallback = callback; + invalidateFilter(); + + Q_EMIT filterColumnCallbackChanged(callback); +} + +QJSValue KSortFilterProxyModel::filterColumnCallback() const +{ + return m_filterColumnCallback; +} + +void KSortFilterProxyModel::setFilterRoleName(const QString &roleName) +{ + QSortFilterProxyModel::setFilterRole(roleNameToId(roleName)); + if (roleName != m_filterRoleName) { + m_filterRoleName = roleName; + Q_EMIT filterRoleNameChanged(); + } +} + +QString KSortFilterProxyModel::filterRoleName() const +{ + return m_filterRoleName; +} + +void KSortFilterProxyModel::setSortRoleName(const QString &roleName) +{ + if (roleName.isEmpty()) { + sort(-1, Qt::AscendingOrder); + } else if (sourceModel()) { + QSortFilterProxyModel::setSortRole(roleNameToId(roleName)); + sort(std::max(sortColumn(), 0), sortOrder()); + } + + if (roleName != m_sortRoleName) { + m_sortRoleName = roleName; + Q_EMIT sortRoleNameChanged(); + } +} + +QString KSortFilterProxyModel::sortRoleName() const +{ + return m_sortRoleName; +} + +void KSortFilterProxyModel::setSortOrder(const Qt::SortOrder order) +{ + sort(std::max(sortColumn(), 0), order); + Q_EMIT sortOrderChanged(); +} + +void KSortFilterProxyModel::setSortColumn(int column) +{ + if (column == sortColumn()) { + return; + } + sort(column, sortOrder()); + Q_EMIT sortColumnChanged(); +} + +void KSortFilterProxyModel::classBegin() +{ +} + +void KSortFilterProxyModel::componentComplete() +{ + m_componentCompleted = true; + if (sourceModel()) { + syncRoleNames(); + setFilterRoleName(m_filterRoleName); + setSortRoleName(m_sortRoleName); + } +} + +void KSortFilterProxyModel::invalidateFilter() +{ + QSortFilterProxyModel::invalidateFilter(); +} + +#include "moc_ksortfilterproxymodel.cpp" diff --git a/src/quick/ksortfilterproxymodel.h b/src/quick/ksortfilterproxymodel.h new file mode 100644 index 00000000..909a2e3e --- /dev/null +++ b/src/quick/ksortfilterproxymodel.h @@ -0,0 +1,170 @@ +/* + * Copyright 2010 by Marco Martin + * Copyright 2019 by David Edmundson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KSORTFILTERPROXYMODEL_H +#define KSORTFILTERPROXYMODEL_H + +#include +#include +#include +#include +#include + +/** + * @class SortFilterModel + * @short Filter and sort an existing QAbstractItemModel + * + * @since 5.67 + */ +class KSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + /** + * The source model of this sorting proxy model. + */ + Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setModel NOTIFY sourceModelChanged) + + /** + * The string for the filter, only rows with their filterRole matching filterString will be displayed + */ + Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged) + /** + * A JavaScript callable that can be used to perform advanced filters on a given row. + * The callback is passed the source row, and source parent for a given row as arguments + * + * The callable's return value is evaluated as boolean to determine + * whether the row is accepted (true) or filtered out (false). It overrides the default implementation + * that uses filterRegExp or filterString; while filterCallback is set those two properties are + * ignored. Attempts to write a non-callable to this property are silently ignored, but you can set + * it to null. + * + * @code + * filterRowCallback: function(source_row, source_parent) { + * return sourceModel.data(sourceModel.index(source_row, 0, source_parent), Qt.DisplayRole) == "..."; + * }; + * @endcode + */ + Q_PROPERTY(QJSValue filterRowCallback READ filterRowCallback WRITE setFilterRowCallback NOTIFY filterRowCallbackChanged) + + /** + * A JavaScript callable that can be used to perform advanced filters on a given column. + * The callback is passed the source column, and source parent for a given column as arguments. + * + * @see filterRowCallback + */ + Q_PROPERTY(QJSValue filterColumnCallback READ filterColumnCallback WRITE setFilterColumnCallback NOTIFY filterColumnCallbackChanged) + + /** + * The role of the sourceModel on which the filter will be applied. + * This can either be the numerical role value or the role name as a string. + */ + Q_PROPERTY(QString filterRoleName READ filterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged) + + /** + * The role of the sourceModel that will be used for sorting. if empty the order will be left unaltered + * This can either be the numerical role value or the role name as a string. + */ + Q_PROPERTY(QString sortRoleName READ sortRoleName WRITE setSortRoleName NOTIFY sortRoleNameChanged) + + /** + * One of Qt.AscendingOrder or Qt.DescendingOrder + */ + Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged) + + /** + * Specify which column should be used for sorting + * The default value is -1. + * If \a sortRole is set, the default value is 0. + */ + Q_PROPERTY(int sortColumn READ sortColumn WRITE setSortColumn NOTIFY sortColumnChanged) + + /** + * The number of top level rows. + */ + Q_PROPERTY(int count READ rowCount NOTIFY rowCountChanged) + +public: + explicit KSortFilterProxyModel(QObject *parent = nullptr); + ~KSortFilterProxyModel() override; + + void setModel(QAbstractItemModel *source); + + void setFilterRowCallback(const QJSValue &callback); + QJSValue filterRowCallback() const; + + void setFilterString(const QString &filterString); + QString filterString() const; + + void setFilterColumnCallback(const QJSValue &callback); + QJSValue filterColumnCallback() const; + + void setFilterRoleName(const QString &roleName); + QString filterRoleName() const; + + void setSortRoleName(const QString &roleName); + QString sortRoleName() const; + + void setSortOrder(const Qt::SortOrder order); + void setSortColumn(int column); + + void classBegin() override; + void componentComplete() override; + +public Q_SLOTS: + /** + * Invalidates the current filtering. + * + * This function should be called if you are implementing custom filtering through + * filterRowCallback or filterColumnCallback, and your filter parameters have changed. + * + * @since 5.70 + */ + void invalidateFilter(); + +Q_SIGNALS: + void filterStringChanged(); + void filterRoleNameChanged(); + void sortRoleNameChanged(); + void sortOrderChanged(); + void sortColumnChanged(); + void sourceModelChanged(QObject *); + void filterRowCallbackChanged(const QJSValue &); + void filterColumnCallbackChanged(const QJSValue &); + void rowCountChanged(); + +protected: + int roleNameToId(const QString &name) const; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; + +protected Q_SLOTS: + void syncRoleNames(); + +private: + bool m_componentCompleted = false; + QString m_filterRoleName; + QString m_filterString; + QString m_sortRoleName; + QJSValue m_filterRowCallback; + QJSValue m_filterColumnCallback; + QHash m_roleIds; +}; +#endif