forked from qt-creator/qt-creator
QmlDesigner: Add a view for single collection
Change-Id: Iee103cf9344872e0f2eaa564fa1feeaea4d26d6a Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io> Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
@@ -14,6 +14,7 @@ Item {
|
|||||||
|
|
||||||
property var rootView: CollectionEditorBackend.rootView
|
property var rootView: CollectionEditorBackend.rootView
|
||||||
property var model: CollectionEditorBackend.model
|
property var model: CollectionEditorBackend.model
|
||||||
|
property var singleCollectionModel: CollectionEditorBackend.singleCollectionModel
|
||||||
|
|
||||||
function showWarning(title, message) {
|
function showWarning(title, message) {
|
||||||
warningDialog.title = title
|
warningDialog.title = title
|
||||||
@@ -60,9 +61,9 @@ Item {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
height: StudioTheme.Values.height + 5
|
height: StudioTheme.Values.height + 5
|
||||||
color: StudioTheme.Values.themeToolbarBackground
|
color: StudioTheme.Values.themeToolbarBackground
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: collectionText
|
id: collectionText
|
||||||
@@ -141,11 +142,8 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
SingleCollectionView {
|
||||||
id: collectionRect
|
model: root.singleCollectionModel
|
||||||
|
|
||||||
color: StudioTheme.Values.themeBackgroundColorAlternate
|
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: collectionsRect.right
|
left: collectionsRect.right
|
||||||
right: parent.right
|
right: parent.right
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -808,6 +808,7 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
collectionmodel.cpp collectionmodel.h
|
collectionmodel.cpp collectionmodel.h
|
||||||
collectionview.cpp collectionview.h
|
collectionview.cpp collectionview.h
|
||||||
collectionwidget.cpp collectionwidget.h
|
collectionwidget.cpp collectionwidget.h
|
||||||
|
singlecollectionmodel.cpp singlecollectionmodel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
extend_qtc_plugin(QmlDesigner
|
extend_qtc_plugin(QmlDesigner
|
||||||
|
@@ -171,6 +171,15 @@ void CollectionModel::selectCollection(const ModelNode &node)
|
|||||||
selectCollectionIndex(nodePlace, true);
|
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
|
bool CollectionModel::isEmpty() const
|
||||||
{
|
{
|
||||||
return m_collections.isEmpty();
|
return m_collections.isEmpty();
|
||||||
|
@@ -41,6 +41,8 @@ public:
|
|||||||
int collectionIndex(const ModelNode &node) const;
|
int collectionIndex(const ModelNode &node) const;
|
||||||
void selectCollection(const ModelNode &node);
|
void selectCollection(const ModelNode &node);
|
||||||
|
|
||||||
|
ModelNode collectionNodeAt(int idx);
|
||||||
|
|
||||||
Q_INVOKABLE bool isEmpty() const;
|
Q_INVOKABLE bool isEmpty() const;
|
||||||
Q_INVOKABLE void selectCollectionIndex(int idx, bool selectAtLeastOne = false);
|
Q_INVOKABLE void selectCollectionIndex(int idx, bool selectAtLeastOne = false);
|
||||||
Q_INVOKABLE void deselect();
|
Q_INVOKABLE void deselect();
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#include "nodemetainfo.h"
|
#include "nodemetainfo.h"
|
||||||
#include "qmldesignerconstants.h"
|
#include "qmldesignerconstants.h"
|
||||||
#include "qmldesignerplugin.h"
|
#include "qmldesignerplugin.h"
|
||||||
|
#include "singlecollectionmodel.h"
|
||||||
#include "variantproperty.h"
|
#include "variantproperty.h"
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
@@ -352,6 +353,12 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo()
|
|||||||
|
|
||||||
auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.data());
|
auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.data());
|
||||||
Core::ICore::addContextObject(collectionEditorContext);
|
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(),
|
return createWidgetInfo(m_widget.data(),
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
#include "collectionview.h"
|
#include "collectionview.h"
|
||||||
#include "qmldesignerconstants.h"
|
#include "qmldesignerconstants.h"
|
||||||
#include "qmldesignerplugin.h"
|
#include "qmldesignerplugin.h"
|
||||||
|
#include "singlecollectionmodel.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
|
|
||||||
#include <studioquickwidget.h>
|
#include <studioquickwidget.h>
|
||||||
@@ -36,6 +37,7 @@ CollectionWidget::CollectionWidget(CollectionView *view)
|
|||||||
: QFrame()
|
: QFrame()
|
||||||
, m_view(view)
|
, m_view(view)
|
||||||
, m_model(new CollectionModel)
|
, m_model(new CollectionModel)
|
||||||
|
, m_singleCollectionModel(new SingleCollectionModel)
|
||||||
, 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"));
|
||||||
@@ -62,7 +64,9 @@ 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)}, {"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);
|
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);
|
||||||
@@ -83,6 +87,11 @@ QPointer<CollectionModel> CollectionWidget::collectionModel() const
|
|||||||
return m_model;
|
return m_model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPointer<SingleCollectionModel> CollectionWidget::singleCollectionModel() const
|
||||||
|
{
|
||||||
|
return m_singleCollectionModel;
|
||||||
|
}
|
||||||
|
|
||||||
void CollectionWidget::reloadQmlSource()
|
void CollectionWidget::reloadQmlSource()
|
||||||
{
|
{
|
||||||
const QString collectionViewQmlPath = collectionViewResourcesPath() + "/CollectionView.qml";
|
const QString collectionViewQmlPath = collectionViewResourcesPath() + "/CollectionView.qml";
|
||||||
|
@@ -12,6 +12,7 @@ namespace QmlDesigner {
|
|||||||
|
|
||||||
class CollectionModel;
|
class CollectionModel;
|
||||||
class CollectionView;
|
class CollectionView;
|
||||||
|
class SingleCollectionModel;
|
||||||
|
|
||||||
class CollectionWidget : public QFrame
|
class CollectionWidget : public QFrame
|
||||||
{
|
{
|
||||||
@@ -22,6 +23,7 @@ public:
|
|||||||
void contextHelp(const Core::IContext::HelpCallback &callback) const;
|
void contextHelp(const Core::IContext::HelpCallback &callback) const;
|
||||||
|
|
||||||
QPointer<CollectionModel> collectionModel() const;
|
QPointer<CollectionModel> collectionModel() const;
|
||||||
|
QPointer<SingleCollectionModel> singleCollectionModel() const;
|
||||||
|
|
||||||
void reloadQmlSource();
|
void reloadQmlSource();
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
QPointer<CollectionView> m_view;
|
QPointer<CollectionView> m_view;
|
||||||
QPointer<CollectionModel> m_model;
|
QPointer<CollectionModel> m_model;
|
||||||
|
QPointer<SingleCollectionModel> m_singleCollectionModel;
|
||||||
QScopedPointer<StudioQuickWidget> m_quickWidget;
|
QScopedPointer<StudioQuickWidget> m_quickWidget;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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 <utils/qtcassert.h>
|
||||||
|
|
||||||
|
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
|
@@ -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 <QAbstractTableModel>
|
||||||
|
|
||||||
|
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
|
Reference in New Issue
Block a user