diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml index 17ac70167d2..30128ecadf4 100644 --- a/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/CollectionView.qml @@ -14,6 +14,7 @@ Item { property var rootView: CollectionEditorBackend.rootView property var model: CollectionEditorBackend.model + property var singleCollectionModel: CollectionEditorBackend.singleCollectionModel function showWarning(title, message) { warningDialog.title = title @@ -60,9 +61,9 @@ Item { width: parent.width Rectangle { + width: parent.width height: StudioTheme.Values.height + 5 color: StudioTheme.Values.themeToolbarBackground - width: parent.width Text { id: collectionText @@ -141,11 +142,8 @@ Item { } } - Rectangle { - id: collectionRect - - color: StudioTheme.Values.themeBackgroundColorAlternate - + SingleCollectionView { + model: root.singleCollectionModel anchors { left: collectionsRect.right right: parent.right diff --git a/share/qtcreator/qmldesigner/collectionEditorQmlSource/SingleCollectionView.qml b/share/qtcreator/qmldesigner/collectionEditorQmlSource/SingleCollectionView.qml new file mode 100644 index 00000000000..8ad8a63e916 --- /dev/null +++ b/share/qtcreator/qmldesigner/collectionEditorQmlSource/SingleCollectionView.qml @@ -0,0 +1,134 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import StudioTheme 1.0 as StudioTheme + +Rectangle { + id: root + + required property var model + + property alias leftPadding: topRow.leftPadding + property real rightPadding: topRow.rightPadding + + color: StudioTheme.Values.themeBackgroundColorAlternate + + Column { + id: topRow + + spacing: 0 + width: parent.width + leftPadding: 20 + rightPadding: 0 + topPadding: 5 + + Text { + id: collectionNameText + + leftPadding: 8 + rightPadding: 8 + topPadding: 3 + bottomPadding: 3 + + color: StudioTheme.Values.themeTextColor + text: root.model.collectionName + font.pixelSize: StudioTheme.Values.mediumIconFont + elide: Text.ElideRight + + Rectangle { + anchors.fill: parent + z: parent.z - 1 + color: StudioTheme.Values.themeBackgroundColorNormal + } + } + + Item { // spacer + width: 1 + height: 10 + } + + HorizontalHeaderView { + id: headerView + + property real topPadding: 5 + property real bottomPadding: 5 + + height: headerMetrics.height + topPadding + bottomPadding + + syncView: tableView + clip: true + + delegate: Rectangle { + implicitWidth: 100 + implicitHeight: headerText.height + color: StudioTheme.Values.themeControlBackground + border.width: 2 + border.color: StudioTheme.Values.themeControlOutline + clip: true + + Text { + id: headerText + + topPadding: headerView.topPadding + bottomPadding: headerView.bottomPadding + leftPadding: 5 + rightPadding: 5 + text: display + font.pixelSize: headerMetrics.font + color: StudioTheme.Values.themeIdleGreen + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.centerIn: parent + elide: Text.ElideRight + } + } + + TextMetrics { + id: headerMetrics + + font.pixelSize: StudioTheme.Values.baseFontSize + text: "Xq" + } + } + } + + TableView { + id: tableView + + anchors { + top: topRow.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + leftMargin: root.leftPadding + rightMargin: root.rightPadding + } + + model: root.model + + delegate: Rectangle { + implicitWidth: 100 + implicitHeight: itemText.height + color: StudioTheme.Values.themeControlBackground + border.width: 1 + border.color: StudioTheme.Values.themeControlOutline + + Text { + id: itemText + + text: display + width: parent.width + leftPadding: 5 + topPadding: 3 + bottomPadding: 3 + font.pixelSize: StudioTheme.Values.baseFontSize + color: StudioTheme.Values.themeTextColor + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + } +} diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 93a384217a5..5d9263771c2 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -808,6 +808,7 @@ extend_qtc_plugin(QmlDesigner collectionmodel.cpp collectionmodel.h collectionview.cpp collectionview.h collectionwidget.cpp collectionwidget.h + singlecollectionmodel.cpp singlecollectionmodel.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.cpp index a0cb6bc6bf2..18e651fc2e2 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.cpp @@ -171,6 +171,15 @@ void CollectionModel::selectCollection(const ModelNode &node) selectCollectionIndex(nodePlace, true); } +QmlDesigner::ModelNode CollectionModel::collectionNodeAt(int idx) +{ + QModelIndex data = index(idx); + if (!data.isValid()) + return {}; + + return m_collections.at(idx); +} + bool CollectionModel::isEmpty() const { return m_collections.isEmpty(); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.h index f3a1e7de786..17831732543 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionmodel.h @@ -41,6 +41,8 @@ public: int collectionIndex(const ModelNode &node) const; void selectCollection(const ModelNode &node); + ModelNode collectionNodeAt(int idx); + Q_INVOKABLE bool isEmpty() const; Q_INVOKABLE void selectCollectionIndex(int idx, bool selectAtLeastOne = false); Q_INVOKABLE void deselect(); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp index e967e850d19..aaf2605b64f 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp @@ -9,6 +9,7 @@ #include "nodemetainfo.h" #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" +#include "singlecollectionmodel.h" #include "variantproperty.h" #include @@ -352,6 +353,12 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.data()); Core::ICore::addContextObject(collectionEditorContext); + CollectionModel *collectionModel = m_widget->collectionModel().data(); + + connect(collectionModel, &CollectionModel::selectedIndexChanged, this, [&](int selectedIndex) { + m_widget->singleCollectionModel()->setCollection( + m_widget->collectionModel()->collectionNodeAt(selectedIndex)); + }); } return createWidgetInfo(m_widget.data(), diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp index df119654d08..7e65516143c 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp @@ -6,6 +6,7 @@ #include "collectionview.h" #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" +#include "singlecollectionmodel.h" #include "theme.h" #include @@ -36,6 +37,7 @@ CollectionWidget::CollectionWidget(CollectionView *view) : QFrame() , m_view(view) , m_model(new CollectionModel) + , m_singleCollectionModel(new SingleCollectionModel) , m_quickWidget(new StudioQuickWidget(this)) { setWindowTitle(tr("Collection View", "Title of collection view widget")); @@ -62,7 +64,9 @@ CollectionWidget::CollectionWidget(CollectionView *view) qmlRegisterAnonymousType("CollectionEditorBackend", 1); auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend"); map->setProperties( - {{"rootView", QVariant::fromValue(this)}, {"model", QVariant::fromValue(m_model.data())}}); + {{"rootView", QVariant::fromValue(this)}, + {"model", QVariant::fromValue(m_model.data())}, + {"singleCollectionModel", QVariant::fromValue(m_singleCollectionModel.data())}}); auto hotReloadShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4), this); connect(hotReloadShortcut, &QShortcut::activated, this, &CollectionWidget::reloadQmlSource); @@ -83,6 +87,11 @@ QPointer CollectionWidget::collectionModel() const return m_model; } +QPointer CollectionWidget::singleCollectionModel() const +{ + return m_singleCollectionModel; +} + void CollectionWidget::reloadQmlSource() { const QString collectionViewQmlPath = collectionViewResourcesPath() + "/CollectionView.qml"; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h index 1f6f57470c2..e76237d0c14 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h @@ -12,6 +12,7 @@ namespace QmlDesigner { class CollectionModel; class CollectionView; +class SingleCollectionModel; class CollectionWidget : public QFrame { @@ -22,6 +23,7 @@ public: void contextHelp(const Core::IContext::HelpCallback &callback) const; QPointer collectionModel() const; + QPointer singleCollectionModel() const; void reloadQmlSource(); @@ -36,6 +38,7 @@ public: private: QPointer m_view; QPointer m_model; + QPointer m_singleCollectionModel; QScopedPointer m_quickWidget; }; diff --git a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp new file mode 100644 index 00000000000..040f9307b67 --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.cpp @@ -0,0 +1,110 @@ +// 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 "singlecollectionmodel.h" + +#include "nodemetainfo.h" +#include "variantproperty.h" + +#include + +namespace { +inline bool isListElement(const QmlDesigner::ModelNode &node) +{ + return node.metaInfo().isQtQuickListElement(); +} + +inline QByteArrayList getHeaders(const QByteArray &headersValue) +{ + QByteArrayList result; + const QByteArrayList initialHeaders = headersValue.split(','); + for (QByteArray header : initialHeaders) { + header = header.trimmed(); + if (header.size()) + result.append(header); + } + return result; +} +} // namespace + +namespace QmlDesigner { +SingleCollectionModel::SingleCollectionModel(QObject *parent) + : QAbstractTableModel(parent) +{} + +int SingleCollectionModel::rowCount([[maybe_unused]] const QModelIndex &parent) const +{ + return m_elements.count(); +} + +int SingleCollectionModel::columnCount([[maybe_unused]] const QModelIndex &parent) const +{ + return m_headers.count(); +} + +QVariant SingleCollectionModel::data(const QModelIndex &index, int) const +{ + if (!index.isValid()) + return {}; + + const QByteArray &propertyName = m_headers.at(index.column()); + const ModelNode &elementNode = m_elements.at(index.row()); + + if (elementNode.hasVariantProperty(propertyName)) + return elementNode.variantProperty(propertyName).value(); + + return {}; +} + +bool SingleCollectionModel::setData(const QModelIndex &, const QVariant &, int) +{ + return false; +} + +Qt::ItemFlags SingleCollectionModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + return {Qt::ItemIsSelectable | Qt::ItemIsEnabled}; +} + +QVariant SingleCollectionModel::headerData(int section, + Qt::Orientation orientation, + [[maybe_unused]] int role) const +{ + if (orientation == Qt::Horizontal) + return m_headers.at(section); + + return {}; +} + +void SingleCollectionModel::setCollection(const ModelNode &collection) +{ + beginResetModel(); + m_collectionNode = collection; + updateCollectionName(); + + QTC_ASSERT(collection.isValid() && collection.hasVariantProperty("headers"), { + m_headers.clear(); + m_elements.clear(); + endResetModel(); + return; + }); + + m_headers = getHeaders(collection.variantProperty("headers").value().toByteArray()); + m_elements = Utils::filtered(collection.allSubModelNodes(), &isListElement); + endResetModel(); +} + +void SingleCollectionModel::updateCollectionName() +{ + QString newCollectionName = m_collectionNode.isValid() + ? m_collectionNode.variantProperty("objectName").value().toString() + : ""; + if (m_collectionName != newCollectionName) { + m_collectionName = newCollectionName; + emit this->collectionNameChanged(m_collectionName); + } +} +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h new file mode 100644 index 00000000000..8d2c6c41b4f --- /dev/null +++ b/src/plugins/qmldesigner/components/collectioneditor/singlecollectionmodel.h @@ -0,0 +1,46 @@ +// 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 "modelnode.h" + +#include + +QT_BEGIN_NAMESPACE +class QJsonArray; +QT_END_NAMESPACE + +namespace QmlDesigner { +class SingleCollectionModel : public QAbstractTableModel +{ + Q_OBJECT + + Q_PROPERTY(QString collectionName MEMBER m_collectionName NOTIFY collectionNameChanged) + +public: + explicit SingleCollectionModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + void setCollection(const ModelNode &collection); + +signals: + void collectionNameChanged(const QString &collectionName); + +private: + void updateCollectionName(); + + QByteArrayList m_headers; + ModelNodes m_elements; + ModelNode m_collectionNode; + QString m_collectionName; +}; + +} // namespace QmlDesigner