From 7cd9e332f362fb64220fe3f53edc74ae41454418 Mon Sep 17 00:00:00 2001 From: Ali Kianian Date: Mon, 16 Oct 2023 14:19:15 +0300 Subject: [PATCH] QmlDesigner: Implement sort for the Collection Details View Task-number: QDS-10986 Change-Id: Iba10830436d58cecb3bd2dba1afc1ac95779123d Reviewed-by: Mahmoud Badri --- .../CollectionDetailsView.qml | 24 +++-- .../CollectionView.qml | 2 + src/plugins/qmldesigner/CMakeLists.txt | 2 + .../collectiondetailsmodel.cpp | 5 + .../collectioneditor/collectiondetailsmodel.h | 3 + .../collectiondetailssortfiltermodel.cpp | 96 +++++++++++++++++++ .../collectiondetailssortfiltermodel.h | 51 ++++++++++ .../collectioneditorutils.cpp | 65 +++++++++++++ .../collectioneditor/collectioneditorutils.h | 12 +++ .../collectioneditor/collectionwidget.cpp | 15 ++- .../collectioneditor/collectionwidget.h | 2 + 11 files changed, 266 insertions(+), 11 deletions(-) create mode 100644 src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp create mode 100644 src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h create mode 100644 src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp create mode 100644 src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml index ac57169905c..192d027e635 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionDetailsView.qml @@ -13,6 +13,7 @@ Rectangle { required property var model required property var backend + required property var sortedModel implicitWidth: 600 implicitHeight: 400 @@ -80,7 +81,7 @@ Rectangle { Rectangle { clip: true - visible: root.model.isEmpty === false + visible: !tableView.model.isEmpty color: StudioTheme.Values.themeControlBackground border.color: StudioTheme.Values.themeControlOutline border.width: 2 @@ -112,14 +113,14 @@ Rectangle { clip: true delegate: HeaderDelegate { - selectedItem: root.model.selectedColumn + selectedItem: tableView.model.selectedColumn MouseArea { anchors.fill: parent anchors.margins: 5 acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: (mouse) => { - root.model.selectColumn(index) + tableView.model.selectColumn(index) if (mouse.button === Qt.RightButton) headerMenu.popIndex(index) @@ -151,6 +152,16 @@ Rectangle { text: qsTr("Delete") onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeader) } + + StudioControls.MenuItem { + text: qsTr("Sort Ascending") + onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.AscendingOrder) + } + + StudioControls.MenuItem { + text: qsTr("Sort Descending") + onTriggered: sortedModel.sort(headerMenu.clickedHeader, Qt.DescendingOrder) + } } } @@ -165,22 +176,21 @@ Rectangle { Layout.alignment: Qt.AlignTop + Qt.AlignLeft delegate: HeaderDelegate { - selectedItem: root.model.selectedRow + selectedItem: tableView.model.selectedRow MouseArea { anchors.fill: parent anchors.margins: 5 acceptedButtons: Qt.LeftButton - onClicked: root.model.selectRow(index) + onClicked: tableView.model.selectRow(index) } - } } TableView { id: tableView - model: root.model + model: root.sortedModel clip: true Layout.preferredWidth: tableView.contentWidth diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml index 594930723a1..beaaad71641 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml @@ -15,6 +15,7 @@ Item { property var rootView: CollectionEditorBackend.rootView property var model: CollectionEditorBackend.model property var collectionDetailsModel: CollectionEditorBackend.collectionDetailsModel + property var collectionDetailsSortFilterModel: CollectionEditorBackend.collectionDetailsSortFilterModel function showWarning(title, message) { warningDialog.title = title @@ -145,6 +146,7 @@ Item { CollectionDetailsView { model: root.collectionDetailsModel backend: root.model + sortedModel: root.collectionDetailsSortFilterModel anchors { left: collectionsRect.right right: parent.right diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 275e75a8cff..eae9460cdce 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -841,7 +841,9 @@ extend_qtc_plugin(QmlDesigner SOURCES collectiondetails.cpp collectiondetails.h collectiondetailsmodel.cpp collectiondetailsmodel.h + collectiondetailssortfiltermodel.cpp collectiondetailssortfiltermodel.h collectioneditorconstants.h + collectioneditorutils.cpp collectioneditorutils.h collectionlistmodel.cpp collectionlistmodel.h collectionsourcemodel.cpp collectionsourcemodel.h collectionview.cpp collectionview.h diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp index df69af21445..c1997db7532 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp @@ -233,6 +233,11 @@ QVariant CollectionDetailsModel::headerData(int section, Qt::Orientation orienta return {}; } +CollectionDetails::DataType CollectionDetailsModel::propertyDataType(int column) const +{ + return m_currentCollection.typeAt(column); +} + int CollectionDetailsModel::selectedColumn() const { return m_selectedColumn; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h index 92ec3850f37..904ad57742f 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h @@ -43,6 +43,9 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + CollectionDetails::DataType propertyDataType(int column) const; + int selectedColumn() const; int selectedRow() const; Q_INVOKABLE QString propertyName(int column) const; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp new file mode 100644 index 00000000000..5fa82ace937 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "collectiondetailssortfiltermodel.h" + +#include "collectiondetailsmodel.h" +#include "collectioneditorutils.h" + +#include + +namespace QmlDesigner { + +CollectionDetailsSortFilterModel::CollectionDetailsSortFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + connect(this, &CollectionDetailsSortFilterModel::rowsInserted, + this, &CollectionDetailsSortFilterModel::updateEmpty); + connect(this, &CollectionDetailsSortFilterModel::rowsRemoved, + this, &CollectionDetailsSortFilterModel::updateEmpty); + connect(this, &CollectionDetailsSortFilterModel::modelReset, + this, &CollectionDetailsSortFilterModel::updateEmpty); +} + +void CollectionDetailsSortFilterModel::setSourceModel(CollectionDetailsModel *model) +{ + m_source = model; + Super::setSourceModel(model); + connect(m_source, &CollectionDetailsModel::selectedColumnChanged, this, [this](int sourceColumn) { + emit selectedColumnChanged(mapFromSource(m_source->index(0, sourceColumn)).column()); + }); + + connect(m_source, &CollectionDetailsModel::selectedRowChanged, this, [this](int sourceRow) { + emit selectedRowChanged(mapFromSource(m_source->index(sourceRow, 0)).row()); + }); +} + +int CollectionDetailsSortFilterModel::selectedRow() const +{ + QTC_ASSERT(m_source, return -1); + + return mapFromSource(m_source->index(m_source->selectedRow(), 0)).row(); +} + +int CollectionDetailsSortFilterModel::selectedColumn() const +{ + QTC_ASSERT(m_source, return -1); + + return mapFromSource(m_source->index(0, m_source->selectedColumn())).column(); +} + +bool CollectionDetailsSortFilterModel::selectRow(int row) +{ + QTC_ASSERT(m_source, return false); + + return m_source->selectRow(mapToSource(index(row, 0)).row()); +} + +bool CollectionDetailsSortFilterModel::selectColumn(int column) +{ + QTC_ASSERT(m_source, return false); + + return m_source->selectColumn(mapToSource(index(0, column)).column()); +} + +CollectionDetailsSortFilterModel::~CollectionDetailsSortFilterModel() = default; + +bool CollectionDetailsSortFilterModel::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const +{ + return true; +} + +bool CollectionDetailsSortFilterModel::lessThan(const QModelIndex &sourceleft, + const QModelIndex &sourceRight) const +{ + QTC_ASSERT(m_source, return false); + + if (sourceleft.column() == sourceRight.column()) { + int column = sourceleft.column(); + CollectionDetails::DataType columnType = m_source->propertyDataType(column); + return CollectionEditor::variantIslessThan(sourceleft.data(), sourceRight.data(), columnType); + } + + return false; +} + +void CollectionDetailsSortFilterModel::updateEmpty() +{ + bool newValue = rowCount() == 0; + if (m_isEmpty != newValue) { + m_isEmpty = newValue; + emit isEmptyChanged(m_isEmpty); + } +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h new file mode 100644 index 00000000000..bb2c68126d7 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h @@ -0,0 +1,51 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace QmlDesigner { + +class CollectionDetailsModel; + +class CollectionDetailsSortFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(int selectedColumn READ selectedColumn WRITE selectColumn NOTIFY selectedColumnChanged) + Q_PROPERTY(int selectedRow READ selectedRow WRITE selectRow NOTIFY selectedRowChanged) + Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + + using Super = QSortFilterProxyModel; + +public: + explicit CollectionDetailsSortFilterModel(QObject *parent = nullptr); + virtual ~CollectionDetailsSortFilterModel(); + + void setSourceModel(CollectionDetailsModel *model); + + int selectedRow() const; + int selectedColumn() const; + + Q_INVOKABLE bool selectRow(int row); + Q_INVOKABLE bool selectColumn(int column); + +signals: + void selectedColumnChanged(int); + void selectedRowChanged(int); + void isEmptyChanged(bool); + +protected: + using Super::setSourceModel; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &sourceleft, const QModelIndex &sourceRight) const override; + +private: + void updateEmpty(); + + QPointer m_source; + bool m_isEmpty = true; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp new file mode 100644 index 00000000000..b18ea366bb6 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "collectioneditorutils.h" + +#include + +#include +#include + +namespace { + +using CollectionDataVariant = std::variant; + +inline CollectionDataVariant valueToVariant(const QVariant &value, + QmlDesigner::CollectionDetails::DataType type) +{ + using DataType = QmlDesigner::CollectionDetails::DataType; + switch (type) { + case DataType::String: + return value.toString(); + case DataType::Number: + return value.toDouble(); + case DataType::Boolean: + return value.toBool(); + case DataType::Color: + return value.value(); + case DataType::Url: + return value.value(); + default: + return false; + } +} + +struct LessThanVisitor +{ + template + bool operator()(const T1 &a, const T2 &b) const + { + return CollectionDataVariant(a).index() < CollectionDataVariant(b).index(); + } + + template + bool operator()(const T &a, const T &b) const + { + return a < b; + } + + template<> + bool operator()(const QColor &a, const QColor &b) const + { + return a.name(QColor::HexArgb) < b.name(QColor::HexArgb); + } +}; + +} // namespace + +namespace QmlDesigner::CollectionEditor { + +bool variantIslessThan(const QVariant &a, const QVariant &b, CollectionDetails::DataType type) +{ + return std::visit(LessThanVisitor{}, valueToVariant(a, type), valueToVariant(b, type)); +} + +} // namespace QmlDesigner::CollectionEditor diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h new file mode 100644 index 00000000000..1281197633c --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h @@ -0,0 +1,12 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "collectiondetails.h" + +namespace QmlDesigner::CollectionEditor { + +bool variantIslessThan(const QVariant &a, const QVariant &b, CollectionDetails::DataType type); + +} // namespace QmlDesigner::CollectionEditor diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp index 9ad61f71e5f..12e9e9ee856 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp @@ -4,6 +4,7 @@ #include "collectionwidget.h" #include "collectiondetailsmodel.h" +#include "collectiondetailssortfiltermodel.h" #include "collectionsourcemodel.h" #include "collectionview.h" #include "qmldesignerconstants.h" @@ -40,6 +41,7 @@ CollectionWidget::CollectionWidget(CollectionView *view) , m_view(view) , m_sourceModel(new CollectionSourceModel) , m_collectionDetailsModel(new CollectionDetailsModel) + , m_collectionDetailsSortFilterModel(std::make_unique()) , m_quickWidget(new StudioQuickWidget(this)) { setWindowTitle(tr("Collection View", "Title of collection view widget")); @@ -50,6 +52,8 @@ CollectionWidget::CollectionWidget(CollectionView *view) icontext->setContext(context); icontext->setWidget(this); + m_collectionDetailsSortFilterModel->setSourceModel(m_collectionDetailsModel); + m_quickWidget->quickWidget()->setObjectName(Constants::OBJECT_NAME_COLLECTION_EDITOR); m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); m_quickWidget->engine()->addImportPath(collectionViewResourcesPath() + "/imports"); @@ -65,10 +69,13 @@ CollectionWidget::CollectionWidget(CollectionView *view) qmlRegisterAnonymousType("CollectionEditorBackend", 1); auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend"); - map->setProperties( - {{"rootView", QVariant::fromValue(this)}, - {"model", QVariant::fromValue(m_sourceModel.data())}, - {"collectionDetailsModel", QVariant::fromValue(m_collectionDetailsModel.data())}}); + map->setProperties({ + {"rootView", QVariant::fromValue(this)}, + {"model", QVariant::fromValue(m_sourceModel.data())}, + {"collectionDetailsModel", QVariant::fromValue(m_collectionDetailsModel.data())}, + {"collectionDetailsSortFilterModel", + QVariant::fromValue(m_collectionDetailsSortFilterModel.get())}, + }); auto hotReloadShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4), this); connect(hotReloadShortcut, &QShortcut::activated, this, &CollectionWidget::reloadQmlSource); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h index fd422fa5d99..21fddc0092b 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h @@ -12,6 +12,7 @@ class StudioQuickWidget; namespace QmlDesigner { class CollectionDetailsModel; +class CollectionDetailsSortFilterModel; class CollectionSourceModel; class CollectionView; @@ -40,6 +41,7 @@ private: QPointer m_view; QPointer m_sourceModel; QPointer m_collectionDetailsModel; + std::unique_ptr m_collectionDetailsSortFilterModel; QScopedPointer m_quickWidget; };