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

View File

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

View File

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

View File

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

View File

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

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 "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<CollectionDetailsSortFilterModel>())
, 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<CollectionWidget>("CollectionEditorBackend", 1);
auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend");
map->setProperties(
{{"rootView", QVariant::fromValue(this)},
map->setProperties({
{"rootView", QVariant::fromValue(this)},
{"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);
connect(hotReloadShortcut, &QShortcut::activated, this, &CollectionWidget::reloadQmlSource);

View File

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