QmlDesigner: Implement sort for the Collection Details View

Task-number: QDS-10986
Change-Id: Iba10830436d58cecb3bd2dba1afc1ac95779123d
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Ali Kianian
2023-10-16 14:19:15 +03:00
parent 8d95dc9dfd
commit 7cd9e332f3
11 changed files with 266 additions and 11 deletions

View File

@@ -13,6 +13,7 @@ Rectangle {
required property var model required property var model
required property var backend required property var backend
required property var sortedModel
implicitWidth: 600 implicitWidth: 600
implicitHeight: 400 implicitHeight: 400
@@ -80,7 +81,7 @@ Rectangle {
Rectangle { Rectangle {
clip: true clip: true
visible: root.model.isEmpty === false visible: !tableView.model.isEmpty
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline border.color: StudioTheme.Values.themeControlOutline
border.width: 2 border.width: 2
@@ -112,14 +113,14 @@ Rectangle {
clip: true clip: true
delegate: HeaderDelegate { delegate: HeaderDelegate {
selectedItem: root.model.selectedColumn selectedItem: tableView.model.selectedColumn
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
anchors.margins: 5 anchors.margins: 5
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => { onClicked: (mouse) => {
root.model.selectColumn(index) tableView.model.selectColumn(index)
if (mouse.button === Qt.RightButton) if (mouse.button === Qt.RightButton)
headerMenu.popIndex(index) headerMenu.popIndex(index)
@@ -151,6 +152,16 @@ Rectangle {
text: qsTr("Delete") text: qsTr("Delete")
onTriggered: deleteColumnDialog.popUp(headerMenu.clickedHeader) 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 Layout.alignment: Qt.AlignTop + Qt.AlignLeft
delegate: HeaderDelegate { delegate: HeaderDelegate {
selectedItem: root.model.selectedRow selectedItem: tableView.model.selectedRow
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
anchors.margins: 5 anchors.margins: 5
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onClicked: root.model.selectRow(index) onClicked: tableView.model.selectRow(index)
} }
} }
} }
TableView { TableView {
id: tableView id: tableView
model: root.model model: root.sortedModel
clip: true clip: true
Layout.preferredWidth: tableView.contentWidth Layout.preferredWidth: tableView.contentWidth

View File

@@ -15,6 +15,7 @@ Item {
property var rootView: CollectionEditorBackend.rootView property var rootView: CollectionEditorBackend.rootView
property var model: CollectionEditorBackend.model property var model: CollectionEditorBackend.model
property var collectionDetailsModel: CollectionEditorBackend.collectionDetailsModel property var collectionDetailsModel: CollectionEditorBackend.collectionDetailsModel
property var collectionDetailsSortFilterModel: CollectionEditorBackend.collectionDetailsSortFilterModel
function showWarning(title, message) { function showWarning(title, message) {
warningDialog.title = title warningDialog.title = title
@@ -145,6 +146,7 @@ Item {
CollectionDetailsView { CollectionDetailsView {
model: root.collectionDetailsModel model: root.collectionDetailsModel
backend: root.model backend: root.model
sortedModel: root.collectionDetailsSortFilterModel
anchors { anchors {
left: collectionsRect.right left: collectionsRect.right
right: parent.right right: parent.right

View File

@@ -841,7 +841,9 @@ extend_qtc_plugin(QmlDesigner
SOURCES SOURCES
collectiondetails.cpp collectiondetails.h collectiondetails.cpp collectiondetails.h
collectiondetailsmodel.cpp collectiondetailsmodel.h collectiondetailsmodel.cpp collectiondetailsmodel.h
collectiondetailssortfiltermodel.cpp collectiondetailssortfiltermodel.h
collectioneditorconstants.h collectioneditorconstants.h
collectioneditorutils.cpp collectioneditorutils.h
collectionlistmodel.cpp collectionlistmodel.h collectionlistmodel.cpp collectionlistmodel.h
collectionsourcemodel.cpp collectionsourcemodel.h collectionsourcemodel.cpp collectionsourcemodel.h
collectionview.cpp collectionview.h collectionview.cpp collectionview.h

View File

@@ -233,6 +233,11 @@ QVariant CollectionDetailsModel::headerData(int section, Qt::Orientation orienta
return {}; return {};
} }
CollectionDetails::DataType CollectionDetailsModel::propertyDataType(int column) const
{
return m_currentCollection.typeAt(column);
}
int CollectionDetailsModel::selectedColumn() const int CollectionDetailsModel::selectedColumn() const
{ {
return m_selectedColumn; return m_selectedColumn;

View File

@@ -43,6 +43,9 @@ public:
QVariant headerData(int section, QVariant headerData(int section,
Qt::Orientation orientation, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override; int role = Qt::DisplayRole) const override;
CollectionDetails::DataType propertyDataType(int column) const;
int selectedColumn() const; int selectedColumn() const;
int selectedRow() const; int selectedRow() const;
Q_INVOKABLE QString propertyName(int column) const; Q_INVOKABLE QString propertyName(int column) const;

View File

@@ -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 <utils/qtcassert.h>
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

View File

@@ -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 <QSortFilterProxyModel>
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<CollectionDetailsModel> m_source;
bool m_isEmpty = true;
};
} // namespace QmlDesigner

View File

@@ -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 <variant>
#include <QColor>
#include <QUrl>
namespace {
using CollectionDataVariant = std::variant<QString, bool, double, QUrl, QColor>;
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<QColor>();
case DataType::Url:
return value.value<QUrl>();
default:
return false;
}
}
struct LessThanVisitor
{
template<typename T1, typename T2>
bool operator()(const T1 &a, const T2 &b) const
{
return CollectionDataVariant(a).index() < CollectionDataVariant(b).index();
}
template<typename T>
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

View File

@@ -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

View File

@@ -4,6 +4,7 @@
#include "collectionwidget.h" #include "collectionwidget.h"
#include "collectiondetailsmodel.h" #include "collectiondetailsmodel.h"
#include "collectiondetailssortfiltermodel.h"
#include "collectionsourcemodel.h" #include "collectionsourcemodel.h"
#include "collectionview.h" #include "collectionview.h"
#include "qmldesignerconstants.h" #include "qmldesignerconstants.h"
@@ -40,6 +41,7 @@ CollectionWidget::CollectionWidget(CollectionView *view)
, m_view(view) , m_view(view)
, m_sourceModel(new CollectionSourceModel) , m_sourceModel(new CollectionSourceModel)
, m_collectionDetailsModel(new CollectionDetailsModel) , m_collectionDetailsModel(new CollectionDetailsModel)
, m_collectionDetailsSortFilterModel(std::make_unique<CollectionDetailsSortFilterModel>())
, m_quickWidget(new StudioQuickWidget(this)) , m_quickWidget(new StudioQuickWidget(this))
{ {
setWindowTitle(tr("Collection View", "Title of collection view widget")); setWindowTitle(tr("Collection View", "Title of collection view widget"));
@@ -50,6 +52,8 @@ CollectionWidget::CollectionWidget(CollectionView *view)
icontext->setContext(context); icontext->setContext(context);
icontext->setWidget(this); icontext->setWidget(this);
m_collectionDetailsSortFilterModel->setSourceModel(m_collectionDetailsModel);
m_quickWidget->quickWidget()->setObjectName(Constants::OBJECT_NAME_COLLECTION_EDITOR); m_quickWidget->quickWidget()->setObjectName(Constants::OBJECT_NAME_COLLECTION_EDITOR);
m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
m_quickWidget->engine()->addImportPath(collectionViewResourcesPath() + "/imports"); m_quickWidget->engine()->addImportPath(collectionViewResourcesPath() + "/imports");
@@ -65,10 +69,13 @@ CollectionWidget::CollectionWidget(CollectionView *view)
qmlRegisterAnonymousType<CollectionWidget>("CollectionEditorBackend", 1); qmlRegisterAnonymousType<CollectionWidget>("CollectionEditorBackend", 1);
auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend"); auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend");
map->setProperties( map->setProperties({
{{"rootView", QVariant::fromValue(this)}, {"rootView", QVariant::fromValue(this)},
{"model", QVariant::fromValue(m_sourceModel.data())}, {"model", QVariant::fromValue(m_sourceModel.data())},
{"collectionDetailsModel", QVariant::fromValue(m_collectionDetailsModel.data())}}); {"collectionDetailsModel", QVariant::fromValue(m_collectionDetailsModel.data())},
{"collectionDetailsSortFilterModel",
QVariant::fromValue(m_collectionDetailsSortFilterModel.get())},
});
auto hotReloadShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4), this); auto hotReloadShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4), this);
connect(hotReloadShortcut, &QShortcut::activated, this, &CollectionWidget::reloadQmlSource); connect(hotReloadShortcut, &QShortcut::activated, this, &CollectionWidget::reloadQmlSource);

View File

@@ -12,6 +12,7 @@ class StudioQuickWidget;
namespace QmlDesigner { namespace QmlDesigner {
class CollectionDetailsModel; class CollectionDetailsModel;
class CollectionDetailsSortFilterModel;
class CollectionSourceModel; class CollectionSourceModel;
class CollectionView; class CollectionView;
@@ -40,6 +41,7 @@ private:
QPointer<CollectionView> m_view; QPointer<CollectionView> m_view;
QPointer<CollectionSourceModel> m_sourceModel; QPointer<CollectionSourceModel> m_sourceModel;
QPointer<CollectionDetailsModel> m_collectionDetailsModel; QPointer<CollectionDetailsModel> m_collectionDetailsModel;
std::unique_ptr<CollectionDetailsSortFilterModel> m_collectionDetailsSortFilterModel;
QScopedPointer<StudioQuickWidget> m_quickWidget; QScopedPointer<StudioQuickWidget> m_quickWidget;
}; };