QmlDesigner: Add QML front-end for ConnectionView

This is still disabled by default until the UI gets polished.

Add roles to models. ConnectionModel, BindingModel and DynamicPropertiesModel.
In QML roles are used in the ListView to "mimic" columns. The columns are only
relevant for the "old" cpp QTableView and can be removed.

Making currentIndex part of the model. If rows are removed and added
again we have to "reset/keep" the currentIndex as part of the model, because
the index is temporarly invalid on the view.

Implementing DynamicPropertiesModelBackendDelegate as reference.
This is a pure "backend delegate" that exposes all relevant properties for
a current row to QML.

Adding StudioQuickWidget and exposing the backend.

Adding StudioQmlTextBackend and StudioQmlComboBoxBackend to
StudioQuickWidget (should be moved into their own files).
Those helper classes make is easy to expose a property to a combobox or
textfield. The backend has to know nothing about the actual frontend
and those classes act as a mini-model for a view in QML.
The API is similar to UI controls.

Change-Id: I7a2c6ad951306fbca1d586fb8f278acdd91a064b
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
Reviewed-by: Knud Dollereder <knud.dollereder@qt.io>
This commit is contained in:
Thomas Hartmann
2023-07-04 19:57:59 +02:00
parent b85eb8aa04
commit 9a11fa3ef8
24 changed files with 2052 additions and 24 deletions

View File

@@ -13,14 +13,16 @@
#include <rewritertransaction.h>
#include <rewriterview.h>
#include <utils/qtcassert.h>
#include <QMessageBox>
#include <QTimer>
namespace QmlDesigner {
BindingModel::BindingModel(ConnectionView *parent)
: QStandardItemModel(parent)
, m_connectionView(parent)
: QStandardItemModel(parent), m_connectionView(parent),
m_delegate(new BindingModelBackendDelegate(this))
{
connect(this, &QStandardItemModel::dataChanged, this, &BindingModel::handleDataChanged);
}
@@ -40,6 +42,31 @@ void BindingModel::resetModel()
endResetModel();
}
void BindingModel::add()
{
addBindingForCurrentNode();
}
void BindingModel::remove(int row)
{
deleteBindindByRow(row);
}
int BindingModel::currentIndex() const
{
return m_currentIndex;
}
void BindingModel::setCurrentIndex(int i)
{
if (m_currentIndex == i)
return;
m_currentIndex = i;
emit currentIndexChanged();
}
void BindingModel::bindingChanged(const BindingProperty &bindingProperty)
{
m_handleDataChanged = false;
@@ -232,6 +259,19 @@ void BindingModel::addBindingForCurrentNode()
}
}
static void updateDisplayRoles(QStandardItem *item, const BindingProperty &property)
{
item->setData(property.parentModelNode().id(), BindingModel::TargetNameRole);
item->setData(property.name(), BindingModel::TargetPropertyNameRole);
const AbstractProperty source = property.resolveToProperty();
if (source.isValid()) {
item->setData(source.parentModelNode().id(), BindingModel::SourceNameRole);
item->setData(source.name(), BindingModel::SourcePropertyNameRole);
}
}
void BindingModel::addBindingProperty(const BindingProperty &property)
{
QStandardItem *idItem;
@@ -248,6 +288,7 @@ void BindingModel::addBindingProperty(const BindingProperty &property)
QList<QStandardItem*> items;
items.append(idItem);
updateDisplayRoles(idItem, property);
items.append(targetPropertyNameItem);
QString sourceNodeName;
@@ -267,6 +308,10 @@ void BindingModel::updateBindingProperty(int rowNumber)
BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
if (bindingProperty.isValid()) {
QStandardItem *idItem = item(rowNumber, 0);
if (idItem)
updateDisplayRoles(idItem, bindingProperty);
QString targetPropertyName = QString::fromUtf8(bindingProperty.name());
updateDisplayRole(rowNumber, TargetPropertyNameRow, targetPropertyName);
QString sourceNodeName;
@@ -355,6 +400,7 @@ void BindingModel::updateCustomData(QStandardItem *item, const BindingProperty &
{
item->setData(bindingProperty.parentModelNode().internalId(), Qt::UserRole + 1);
item->setData(bindingProperty.name(), Qt::UserRole + 2);
updateDisplayRoles(item, bindingProperty);
}
int BindingModel::findRowForBinding(const BindingProperty &bindingProperty)
@@ -369,6 +415,8 @@ int BindingModel::findRowForBinding(const BindingProperty &bindingProperty)
bool BindingModel::getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty)
{
//TODO reimplement using existing helper functions
//### todo we assume no expressions yet
const QString expression = bindingProperty.expression();
@@ -438,4 +486,159 @@ void BindingModel::handleException()
resetModel();
}
QHash<int, QByteArray> BindingModel::roleNames() const
{
static QHash<int, QByteArray> roleNames{{TargetNameRole, "target"},
{TargetPropertyNameRole, "targetProperty"},
{SourceNameRole, "source"},
{SourcePropertyNameRole, "sourceProperty"}};
return roleNames;
}
BindingModelBackendDelegate *BindingModel::delegate() const
{
return m_delegate;
}
BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel *parent) : QObject(parent)
{
connect(&m_sourceNode, &StudioQmlComboBoxBackend::activated, this, [this]() {
handleSourceNodeChanged();
});
connect(&m_sourceNodeProperty, &StudioQmlComboBoxBackend::activated, this, [this]() {
handleSourcePropertyChanged();
});
}
int BindingModelBackendDelegate::currentRow() const
{
return m_currentRow;
}
void BindingModelBackendDelegate::setCurrentRow(int i)
{
// See BindingDelegate::createEditor
if (m_currentRow == i)
return;
m_currentRow = i;
//setup
BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return );
BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow());
QString idLabel = bindingProperty.parentModelNode().id();
if (idLabel.isEmpty())
idLabel = bindingProperty.parentModelNode().simplifiedTypeName();
m_targetNode = idLabel;
emit targetNodeChanged();
m_property.setModel(model->possibleTargetProperties(bindingProperty));
m_property.setCurrentText(QString::fromUtf8(bindingProperty.name()));
QStringList sourceNodes;
for (const ModelNode &modelNode : model->connectionView()->allModelNodes()) {
if (!modelNode.id().isEmpty())
sourceNodes.append(modelNode.id());
}
std::sort(sourceNodes.begin(), sourceNodes.end());
m_sourceNode.setModel(sourceNodes);
QString sourceNodeName;
QString sourcePropertyName;
model->getExpressionStrings(bindingProperty, &sourceNodeName, &sourcePropertyName);
m_sourceNode.setCurrentText(sourceNodeName);
m_sourceNodeProperty.setModel(model->possibleSourceProperties(bindingProperty));
m_sourceNodeProperty.setCurrentText(sourcePropertyName);
}
void BindingModelBackendDelegate::handleException()
{
QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
//reset
}
QString BindingModelBackendDelegate::targetNode() const
{
return m_targetNode;
}
StudioQmlComboBoxBackend *BindingModelBackendDelegate::property()
{
return &m_property;
}
StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceNode()
{
return &m_sourceNode;
}
StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty()
{
return &m_sourceNodeProperty;
}
void BindingModelBackendDelegate::handleSourceNodeChanged()
{
BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return );
QTC_ASSERT(model->connectionView(), return );
const QString sourceNode = m_sourceNode.currentText();
const QString sourceProperty = m_sourceNodeProperty.currentText();
QString expression;
if (sourceProperty.isEmpty()) {
expression = sourceNode;
} else {
expression = sourceNode + QLatin1String(".") + sourceProperty;
}
BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow());
model->connectionView()->executeInTransaction("BindingModel::updateExpression",
[&bindingProperty, expression]() {
bindingProperty.setExpression(
expression.trimmed());
});
}
void BindingModelBackendDelegate::handleSourcePropertyChanged()
{
BindingModel *model = qobject_cast<BindingModel *>(parent());
QTC_ASSERT(model, return );
QTC_ASSERT(model->connectionView(), return );
const QString sourceNode = m_sourceNode.currentText();
const QString sourceProperty = m_sourceNodeProperty.currentText();
QString expression;
if (sourceProperty.isEmpty()) {
expression = sourceNode;
} else {
expression = sourceNode + QLatin1String(".") + sourceProperty;
}
BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow());
model->connectionView()->executeInTransaction("BindingModel::updateExpression",
[&bindingProperty, expression]() {
bindingProperty.setExpression(
expression.trimmed());
});
}
} // namespace QmlDesigner

View File

@@ -7,16 +7,22 @@
#include <bindingproperty.h>
#include <variantproperty.h>
#include <studioquickwidget.h>
#include <QStandardItemModel>
namespace QmlDesigner {
class ConnectionView;
class BindingModelBackendDelegate;
class BindingModel : public QStandardItemModel
{
Q_OBJECT
Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
Q_PROPERTY(BindingModelBackendDelegate *delegate READ delegate CONSTANT)
public:
enum ColumnRoles {
TargetModelNodeRow = 0,
@@ -24,6 +30,15 @@ public:
SourceModelNodeRow = 2,
SourcePropertyNameRow = 3
};
enum UserRoles {
InternalIdRole = Qt::UserRole + 2,
TargetNameRole,
TargetPropertyNameRole,
SourceNameRole,
SourcePropertyNameRole
};
BindingModel(ConnectionView *parent = nullptr);
void bindingChanged(const BindingProperty &bindingProperty);
void bindingRemoved(const BindingProperty &bindingProperty);
@@ -37,6 +52,18 @@ public:
void addBindingForCurrentNode();
void resetModel();
Q_INVOKABLE void add();
Q_INVOKABLE void remove(int row);
int currentIndex() const;
void setCurrentIndex(int i);
bool getExpressionStrings(const BindingProperty &bindingProperty,
QString *sourceNode,
QString *sourceProperty);
signals:
void currentIndexChanged();
protected:
void addBindingProperty(const BindingProperty &property);
void updateBindingProperty(int rowNumber);
@@ -46,11 +73,11 @@ protected:
ModelNode getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const;
void updateCustomData(QStandardItem *item, const BindingProperty &bindingProperty);
int findRowForBinding(const BindingProperty &bindingProperty);
bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty);
void updateDisplayRole(int row, int columns, const QString &string);
QHash<int, QByteArray> roleNames() const override;
BindingModelBackendDelegate *delegate() const;
private:
void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight);
void handleException();
@@ -60,7 +87,48 @@ private:
bool m_lock = false;
bool m_handleDataChanged = false;
QString m_exceptionError;
int m_currentIndex = 0;
BindingModelBackendDelegate *m_delegate = nullptr;
};
class BindingModelBackendDelegate : public QObject
{
Q_OBJECT
Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged)
Q_PROPERTY(QString targetNode READ targetNode NOTIFY targetNodeChanged)
Q_PROPERTY(StudioQmlComboBoxBackend *property READ property CONSTANT)
Q_PROPERTY(StudioQmlComboBoxBackend *sourceNode READ sourceNode CONSTANT)
Q_PROPERTY(StudioQmlComboBoxBackend *sourceProperty READ sourceProperty CONSTANT)
public:
BindingModelBackendDelegate(BindingModel *parent = nullptr);
signals:
void currentRowChanged();
//void nameChanged();
void targetNodeChanged();
private:
int currentRow() const;
void setCurrentRow(int i);
void handleException();
QString targetNode() const;
StudioQmlComboBoxBackend *property();
StudioQmlComboBoxBackend *sourceNode();
StudioQmlComboBoxBackend *sourceProperty();
void handleSourceNodeChanged();
void handleSourcePropertyChanged();
StudioQmlComboBoxBackend m_property;
StudioQmlComboBoxBackend m_sourceNode;
StudioQmlComboBoxBackend m_sourceNodeProperty;
QString m_exceptionError;
int m_currentRow = -1;
QString m_targetNode;
};
} // namespace QmlDesigner

View File

@@ -252,6 +252,19 @@ void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerP
{
item->setData(signalHandlerProperty.parentModelNode().internalId(), UserRoles::InternalIdRole);
item->setData(signalHandlerProperty.name(), UserRoles::TargetPropertyNameRole);
item->setData(signalHandlerProperty.parentModelNode()
.bindingProperty("target")
.resolveToModelNode()
.id(),
UserRoles::TargetNameRole);
// TODO signalHandlerProperty.source() contains a statement that defines the type.
// foo.bar() <- function call
// foo.state = "literal" //state change
//anything else is assignment
// e.g. foo.bal = foo2.bula ; foo.bal = "literal" ; goo.gal = true
item->setData("Assignment", UserRoles::ActionTypeRole);
}
ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connection) const
@@ -370,6 +383,16 @@ void ConnectionModel::removeRowFromTable(const SignalHandlerProperty &property)
}
}
void ConnectionModel::add()
{
addConnection();
}
void ConnectionModel::remove(int row)
{
deleteConnectionByRow(row);
}
void ConnectionModel::handleException()
{
QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
@@ -522,4 +545,12 @@ QStringList ConnectionModel::getPossibleSignalsForConnection(const ModelNode &co
return stringList;
}
QHash<int, QByteArray> ConnectionModel::roleNames() const
{
static QHash<int, QByteArray> roleNames{{TargetPropertyNameRole, "signal"},
{TargetNameRole, "target"},
{ActionTypeRole, "action"}};
return roleNames;
}
} // namespace QmlDesigner

View File

@@ -26,7 +26,9 @@ public:
};
enum UserRoles {
InternalIdRole = Qt::UserRole + 1,
TargetPropertyNameRole
TargetPropertyNameRole,
TargetNameRole,
ActionTypeRole
};
ConnectionModel(ConnectionView *parent = nullptr);
@@ -49,6 +51,9 @@ public:
void deleteConnectionByRow(int currentRow);
void removeRowFromTable(const SignalHandlerProperty &property);
Q_INVOKABLE void add();
Q_INVOKABLE void remove(int row);
protected:
void addModelNode(const ModelNode &modelNode);
void addConnection(const ModelNode &modelNode);
@@ -61,6 +66,8 @@ protected:
void updateCustomData(QStandardItem *item, const SignalHandlerProperty &signalHandlerProperty);
QStringList getPossibleSignalsForConnection(const ModelNode &connection) const;
QHash<int, QByteArray> roleNames() const override;
private:
void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight);
void handleException();

View File

@@ -8,6 +8,7 @@
#include "bindingmodel.h"
#include "connectionmodel.h"
#include "dynamicpropertiesmodel.h"
#include "theme.h"
#include <bindingproperty.h>
#include <nodeabstractproperty.h>
@@ -16,19 +17,116 @@
#include <qmldesignerplugin.h>
#include <viewmanager.h>
#include <studioquickwidget.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
#include <utils/qtcassert.h>
#include <QQmlEngine>
#include <QShortcut>
#include <QTableView>
namespace QmlDesigner {
static QString propertyEditorResourcesPath()
{
#ifdef SHARE_QML_PATH
if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources";
#endif
return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
}
class ConnectionViewQuickWidget : public StudioQuickWidget
{
// Q_OBJECT carefull
public:
ConnectionViewQuickWidget(ConnectionView *connectionEditorView)
: m_connectionEditorView(connectionEditorView)
{
engine()->addImportPath(qmlSourcesPath());
engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
engine()->addImportPath(qmlSourcesPath() + "/imports");
m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F12), this);
connect(m_qmlSourceUpdateShortcut,
&QShortcut::activated,
this,
&ConnectionViewQuickWidget::reloadQmlSource);
//setObjectName(Constants::OBJECT_NAME_STATES_EDITOR);
setResizeMode(QQuickWidget::SizeRootObjectToView);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
auto map = registerPropertyMap("ConnectionsEditorEditorBackend");
qmlRegisterAnonymousType<DynamicPropertiesModel>("ConnectionsEditorEditorBackend", 1);
qmlRegisterAnonymousType<DynamicPropertiesModelBackendDelegate>(
"ConnectionsEditorEditorBackend", 1);
map->setProperties(
{{"connectionModel", QVariant::fromValue(m_connectionEditorView->connectionModel())}});
map->setProperties(
{{"bindingModel", QVariant::fromValue(m_connectionEditorView->bindingModel())}});
map->setProperties(
{{"dynamicPropertiesModel",
QVariant::fromValue(m_connectionEditorView->dynamicPropertiesModel())}});
Theme::setupTheme(engine());
setMinimumWidth(195);
setMinimumHeight(195);
// init the first load of the QML UI elements
reloadQmlSource();
}
~ConnectionViewQuickWidget() = default;
static QString qmlSourcesPath()
{
#ifdef SHARE_QML_PATH
if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
return QLatin1String(SHARE_QML_PATH) + "/connectionseditor";
#endif
return Core::ICore::resourcePath("qmldesigner/connectionseditor").toString();
}
private:
void reloadQmlSource()
{
QString connectionEditorQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml");
QTC_ASSERT(QFileInfo::exists(connectionEditorQmlFilePath), return );
setSource(QUrl::fromLocalFile(connectionEditorQmlFilePath));
if (!rootObject()) {
QString errorString;
for (const QQmlError &error : errors())
errorString += "\n" + error.toString();
Core::AsynchronousMessageBox::warning(
tr("Cannot Create QtQuick View"),
tr("ConnectionsEditorWidget: %1 cannot be created.%2")
.arg(qmlSourcesPath(), errorString));
return;
}
}
private:
QPointer<ConnectionView> m_connectionEditorView;
QShortcut *m_qmlSourceUpdateShortcut;
};
ConnectionView::ConnectionView(ExternalDependenciesInterface &externalDependencies)
: AbstractView{externalDependencies}
, m_connectionViewWidget(new ConnectionViewWidget())
, m_connectionModel(new ConnectionModel(this))
, m_bindingModel(new BindingModel(this))
, m_dynamicPropertiesModel(new DynamicPropertiesModel(false, this))
, m_backendModel(new BackendModel(this))
: AbstractView{externalDependencies}, m_connectionViewWidget(new ConnectionViewWidget()),
m_connectionModel(new ConnectionModel(this)), m_bindingModel(new BindingModel(this)),
m_dynamicPropertiesModel(new DynamicPropertiesModel(false, this)),
m_backendModel(new BackendModel(this)),
m_connectionViewQuickWidget(new ConnectionViewQuickWidget(this))
{
connectionViewWidget()->setBindingModel(m_bindingModel);
connectionViewWidget()->setConnectionModel(m_connectionModel);
@@ -36,8 +134,11 @@ ConnectionView::ConnectionView(ExternalDependenciesInterface &externalDependenci
connectionViewWidget()->setBackendModel(m_backendModel);
}
ConnectionView::~ConnectionView() = default;
ConnectionView::~ConnectionView()
{
// Ensure that QML is deleted first to avoid calling back to C++.
delete m_connectionViewQuickWidget.data();
}
void ConnectionView::modelAttached(Model *model)
{
AbstractView::modelAttached(model);
@@ -195,7 +296,14 @@ void ConnectionView::currentStateChanged(const ModelNode &)
WidgetInfo ConnectionView::widgetInfo()
{
return createWidgetInfo(m_connectionViewWidget.data(),
/* Enable new connection editor here */
const bool newEditor = false;
QWidget *widget = m_connectionViewWidget.data();
if (newEditor)
widget = m_connectionViewQuickWidget.data();
return createWidgetInfo(widget,
QLatin1String("ConnectionView"),
WidgetInfo::LeftPane,
0,
@@ -257,6 +365,20 @@ BackendModel *ConnectionView::backendModel() const
return m_backendModel;
}
int ConnectionView::currentIndex() const
{
return m_currentIndex;
}
void ConnectionView::setCurrentIndex(int i)
{
if (m_currentIndex == i)
return;
m_currentIndex = i;
emit currentIndexChanged();
}
ConnectionView *ConnectionView::instance()
{

View File

@@ -20,11 +20,14 @@ class BindingModel;
class ConnectionModel;
class DynamicPropertiesModel;
class BackendModel;
class ConnectionViewQuickWidget;
class ConnectionView : public AbstractView
class ConnectionView : public AbstractView
{
Q_OBJECT
Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
public:
ConnectionView(ExternalDependenciesInterface &externalDependencies);
~ConnectionView() override;
@@ -70,14 +73,24 @@ public:
BindingModel *bindingModel() const;
BackendModel *backendModel() const;
int currentIndex() const;
void setCurrentIndex(int i);
static ConnectionView *instance();
signals:
void currentIndexChanged();
private: //variables
QPointer<ConnectionViewWidget> m_connectionViewWidget;
ConnectionModel *m_connectionModel;
BindingModel *m_bindingModel;
DynamicPropertiesModel *m_dynamicPropertiesModel;
BackendModel *m_backendModel;
int m_currentIndex = 0;
QPointer<ConnectionViewQuickWidget> m_connectionViewQuickWidget;
};
} // namespace QmlDesigner

View File

@@ -90,6 +90,8 @@ ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) :
this, &ConnectionViewWidget::handleTabChanged);
ui->stackedWidget->setCurrentIndex(0);
ui->stackedWidget->parentWidget()->hide();
}
ConnectionViewWidget::~ConnectionViewWidget()

View File

@@ -152,10 +152,34 @@ QString DynamicPropertiesModel::defaultExpressionForType(const TypeName &type)
return expression;
}
void DynamicPropertiesModel::add()
{
addDynamicPropertyForCurrentNode();
}
void DynamicPropertiesModel::remove(int row)
{
deleteDynamicPropertyByRow(row);
}
int DynamicPropertiesModel::currentIndex() const
{
return m_currentIndex;
}
void DynamicPropertiesModel::setCurrentIndex(int i)
{
if (m_currentIndex == i)
return;
m_currentIndex = i;
emit currentIndexChanged();
}
DynamicPropertiesModel::DynamicPropertiesModel(bool explicitSelection, AbstractView *parent)
: QStandardItemModel(parent)
, m_view(parent)
, m_explicitSelection(explicitSelection)
: QStandardItemModel(parent), m_view(parent), m_explicitSelection(explicitSelection),
m_delegate(new DynamicPropertiesModelBackendDelegate(this))
{
connect(this, &QStandardItemModel::dataChanged, this, &DynamicPropertiesModel::handleDataChanged);
}
@@ -163,6 +187,7 @@ DynamicPropertiesModel::DynamicPropertiesModel(bool explicitSelection, AbstractV
void DynamicPropertiesModel::resetModel()
{
beginResetModel();
const int backIndex = m_currentIndex;
clear();
setHorizontalHeaderLabels({tr("Item"), tr("Property"), tr("Property Type"), tr("Property Value")});
@@ -172,7 +197,9 @@ void DynamicPropertiesModel::resetModel()
addModelNode(modelNode);
}
emit currentIndexChanged();
endResetModel();
m_currentIndex = backIndex;
}
@@ -344,6 +371,8 @@ void DynamicPropertiesModel::bindingRemoved(const BindingProperty &bindingProper
removeRow(rowNumber);
}
emit currentIndexChanged();
m_handleDataChanged = true;
}
@@ -360,6 +389,8 @@ void DynamicPropertiesModel::variantRemoved(const VariantProperty &variantProper
removeRow(rowNumber);
}
emit currentIndexChanged();
m_handleDataChanged = true;
}
@@ -368,6 +399,7 @@ void DynamicPropertiesModel::reset()
m_handleDataChanged = false;
resetModel();
m_handleDataChanged = true;
emit currentIndexChanged();
}
void DynamicPropertiesModel::setSelectedNode(const ModelNode &node)
@@ -597,6 +629,8 @@ void DynamicPropertiesModel::updateBindingProperty(int rowNumber)
BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
if (bindingProperty.isValid()) {
updateCustomData(rowNumber, bindingProperty);
QString propertyName = QString::fromUtf8(bindingProperty.name());
updateDisplayRole(rowNumber, PropertyNameRow, propertyName);
QString value = bindingProperty.expression();
@@ -617,6 +651,7 @@ void DynamicPropertiesModel::updateVariantProperty(int rowNumber)
VariantProperty variantProperty = variantPropertyForRow(rowNumber);
if (variantProperty.isValid()) {
updateCustomData(rowNumber, variantProperty);
QString propertyName = QString::fromUtf8(variantProperty.name());
updateDisplayRole(rowNumber, PropertyNameRow, propertyName);
QVariant value = variantProperty.value();
@@ -787,6 +822,16 @@ void DynamicPropertiesModel::updateCustomData(QStandardItem *item, const Abstrac
{
item->setData(property.parentModelNode().internalId(), Qt::UserRole + 1);
item->setData(property.name(), Qt::UserRole + 2);
item->setData(property.parentModelNode().id(), TargetNameRole);
item->setData(property.name(), PropertyNameRole);
item->setData(property.parentModelNode().id(), TargetNameRole);
item->setData(property.dynamicTypeName(), PropertyTypeRole);
if (property.isVariantProperty())
item->setData(property.toVariantProperty().value(), PropertyValueRole);
if (property.isBindingProperty())
item->setData(property.toBindingProperty().expression(), PropertyValueRole);
}
void DynamicPropertiesModel::updateCustomData(int row, const AbstractProperty &property)
@@ -924,4 +969,217 @@ const ModelNode DynamicPropertiesModel::singleSelectedNode() const
return m_view->singleSelectedModelNode();
}
QHash<int, QByteArray> DynamicPropertiesModel::roleNames() const
{
static QHash<int, QByteArray> roleNames{{TargetNameRole, "target"},
{PropertyNameRole, "name"},
{PropertyTypeRole, "type"},
{PropertyValueRole, "value"}};
return roleNames;
}
DynamicPropertiesModelBackendDelegate *DynamicPropertiesModel::delegate() const
{
return m_delegate;
}
DynamicPropertiesModelBackendDelegate::DynamicPropertiesModelBackendDelegate(
DynamicPropertiesModel *parent)
: QObject(parent)
{
m_type.setModel({"int", "bool", "var", "real", "string", "url", "color"});
connect(&m_type, &StudioQmlComboBoxBackend::activated, this, [this]() { handleTypeChanged(); });
connect(&m_name, &StudioQmlTextBackend::activated, this, [this]() { handleNameChanged(); });
connect(&m_value, &StudioQmlTextBackend::activated, this, [this]() { handleValueChanged(); });
}
int DynamicPropertiesModelBackendDelegate::currentRow() const
{
return m_currentRow;
}
void DynamicPropertiesModelBackendDelegate::setCurrentRow(int i)
{
if (m_currentRow == i)
return;
m_currentRow = i;
//setup
DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent());
QTC_ASSERT(model, return );
AbstractProperty property = model->abstractPropertyForRow(i);
m_type.setCurrentText(QString::fromUtf8(property.dynamicTypeName()));
m_name.setText(QString::fromUtf8(property.name()));
if (property.isVariantProperty())
m_value.setText(property.toVariantProperty().value().toString());
else if (property.isBindingProperty())
m_value.setText(property.toBindingProperty().expression());
}
void DynamicPropertiesModelBackendDelegate::handleTypeChanged()
{
//void DynamicPropertiesModel::updatePropertyType(int rowNumber)
const TypeName type = m_type.currentText().toUtf8();
DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent());
QTC_ASSERT(model, return );
QTC_ASSERT(model->view(), return );
BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow());
VariantProperty variantProperty = model->variantPropertyForRow(currentRow());
RewriterTransaction transaction = model->view()->beginRewriterTransaction(__FUNCTION__);
try {
if (bindingProperty.isBindingProperty() || type == "var") { //var is always a binding
const QString expression = bindingProperty.expression();
variantProperty.parentModelNode().removeProperty(variantProperty.name());
bindingProperty.setDynamicTypeNameAndExpression(type, expression);
} else if (variantProperty.isVariantProperty()) {
variantProperty.parentModelNode().removeProperty(variantProperty.name());
variantProperty.setDynamicTypeNameAndValue(type, variantValue());
}
transaction.commit(); // committing in the try block
} catch (Exception &e) {
m_exceptionError = e.description();
QTimer::singleShot(200, this, &DynamicPropertiesModelBackendDelegate::handleException);
}
}
void DynamicPropertiesModelBackendDelegate::handleNameChanged()
{
//see DynamicPropertiesModel::updatePropertyName
const PropertyName newName = m_name.text().toUtf8();
QTC_ASSERT(!newName.isEmpty(), return );
DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent());
QTC_ASSERT(model, return );
QTC_ASSERT(model->view(), return );
BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow());
ModelNode targetNode = bindingProperty.parentModelNode();
if (bindingProperty.isBindingProperty()) {
model->view()->executeInTransaction(__FUNCTION__, [bindingProperty, newName, &targetNode]() {
const QString expression = bindingProperty.expression();
const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName();
targetNode.bindingProperty(newName).setDynamicTypeNameAndExpression(dynamicPropertyType,
expression);
targetNode.removeProperty(bindingProperty.name());
});
return;
}
VariantProperty variantProperty = model->variantPropertyForRow(currentRow());
if (variantProperty.isVariantProperty()) {
const QVariant value = variantProperty.value();
const PropertyName dynamicPropertyType = variantProperty.dynamicTypeName();
ModelNode targetNode = variantProperty.parentModelNode();
model->view()->executeInTransaction(__FUNCTION__, [=]() {
targetNode.variantProperty(newName).setDynamicTypeNameAndValue(dynamicPropertyType,
value);
targetNode.removeProperty(variantProperty.name());
});
}
AbstractProperty property = targetNode.property(newName);
//order might have changed because of name change we have to select the correct row
int newRow = model->findRowForProperty(property);
model->setCurrentIndex(newRow);
setCurrentRow(newRow);
}
void DynamicPropertiesModelBackendDelegate::handleValueChanged()
{
//see void DynamicPropertiesModel::updateValue(int row)
DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent());
QTC_ASSERT(model, return );
QTC_ASSERT(model->view(), return );
BindingProperty bindingProperty = model->bindingPropertyForRow(currentRow());
if (bindingProperty.isBindingProperty()) {
const QString expression = m_value.text();
RewriterTransaction transaction = model->view()->beginRewriterTransaction(__FUNCTION__);
try {
bindingProperty.setDynamicTypeNameAndExpression(bindingProperty.dynamicTypeName(),
expression);
transaction.commit(); // committing in the try block
} catch (Exception &e) {
m_exceptionError = e.description();
QTimer::singleShot(200, this, &DynamicPropertiesModelBackendDelegate::handleException);
}
return;
}
VariantProperty variantProperty = model->variantPropertyForRow(currentRow());
if (variantProperty.isVariantProperty()) {
RewriterTransaction transaction = model->view()->beginRewriterTransaction(__FUNCTION__);
try {
variantProperty.setDynamicTypeNameAndValue(variantProperty.dynamicTypeName(),
variantValue());
transaction.commit(); // committing in the try block
} catch (Exception &e) {
m_exceptionError = e.description();
QTimer::singleShot(200, this, &DynamicPropertiesModelBackendDelegate::handleException);
}
}
}
void DynamicPropertiesModelBackendDelegate::handleException()
{
QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
//reset
}
QVariant DynamicPropertiesModelBackendDelegate::variantValue() const
{
//improve
const QString type = m_type.currentText();
if (type == "real" || type == "int")
return m_value.text().toFloat();
if (type == "bool")
return m_value.text() == "true";
return m_value.text();
}
StudioQmlComboBoxBackend *DynamicPropertiesModelBackendDelegate::type()
{
return &m_type;
}
StudioQmlTextBackend *DynamicPropertiesModelBackendDelegate::name()
{
return &m_name;
}
StudioQmlTextBackend *DynamicPropertiesModelBackendDelegate::value()
{
return &m_value;
}
} // namespace QmlDesigner

View File

@@ -5,6 +5,8 @@
#include <nodeinstanceglobal.h>
#include <studioquickwidget.h>
#include <QStandardItemModel>
namespace QmlDesigner {
@@ -15,6 +17,8 @@ class BindingProperty;
class ModelNode;
class VariantProperty;
class DynamicPropertiesModelBackendDelegate;
class DynamicPropertiesModel : public QStandardItemModel
{
Q_OBJECT
@@ -22,11 +26,22 @@ class DynamicPropertiesModel : public QStandardItemModel
public:
enum ColumnRoles {
TargetModelNodeRow = 0,
PropertyNameRow = 1,
PropertyTypeRow = 2,
PropertyValueRow = 3
PropertyNameRow = 1,
PropertyTypeRow = 2,
PropertyValueRow = 3
};
enum UserRoles {
InternalIdRole = Qt::UserRole + 2,
TargetNameRole,
PropertyNameRole,
PropertyTypeRole,
PropertyValueRole
};
Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
Q_PROPERTY(DynamicPropertiesModelBackendDelegate *delegate READ delegate CONSTANT)
DynamicPropertiesModel(bool explicitSelection, AbstractView *parent);
void bindingPropertyChanged(const BindingProperty &bindingProperty);
@@ -62,6 +77,17 @@ public:
static QVariant defaultValueForType(const TypeName &type);
static QString defaultExpressionForType(const TypeName &type);
Q_INVOKABLE void add();
Q_INVOKABLE void remove(int row);
int currentIndex() const;
void setCurrentIndex(int i);
int findRowForProperty(const AbstractProperty &abstractProperty) const;
signals:
void currentIndexChanged();
protected:
void addProperty(const QVariant &propertyValue,
const QString &propertyType,
@@ -79,12 +105,17 @@ protected:
void updateCustomData(int row, const AbstractProperty &property);
int findRowForBindingProperty(const BindingProperty &bindingProperty) const;
int findRowForVariantProperty(const VariantProperty &variantProperty) const;
int findRowForProperty(const AbstractProperty &abstractProperty) const;
bool getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty);
bool getExpressionStrings(const BindingProperty &bindingProperty,
QString *sourceNode,
QString *sourceProperty);
void updateDisplayRole(int row, int columns, const QString &string);
QHash<int, QByteArray> roleNames() const override;
DynamicPropertiesModelBackendDelegate *delegate() const;
private:
void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void handleException();
@@ -95,6 +126,48 @@ private:
QString m_exceptionError;
QList<ModelNode> m_selectedNodes;
bool m_explicitSelection = false;
int m_currentIndex = 0;
DynamicPropertiesModelBackendDelegate *m_delegate = nullptr;
};
class DynamicPropertiesModelBackendDelegate : public QObject
{
Q_OBJECT
Q_PROPERTY(StudioQmlComboBoxBackend *type READ type CONSTANT)
Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged)
Q_PROPERTY(StudioQmlTextBackend *name READ name CONSTANT)
Q_PROPERTY(StudioQmlTextBackend *value READ value CONSTANT)
//Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged)
public:
DynamicPropertiesModelBackendDelegate(DynamicPropertiesModel *parent = nullptr);
signals:
void currentRowChanged();
void nameChanged();
void valueChanged();
private:
int currentRow() const;
void setCurrentRow(int i);
void handleTypeChanged();
void handleNameChanged();
void handleValueChanged();
void handleException();
QVariant variantValue() const;
StudioQmlComboBoxBackend *type();
StudioQmlTextBackend *name();
StudioQmlTextBackend *value();
StudioQmlComboBoxBackend m_type;
StudioQmlTextBackend m_name;
StudioQmlTextBackend m_value;
int m_currentRow = -1;
QString m_exceptionError;
};
} // namespace QmlDesigner

View File

@@ -9,6 +9,127 @@
#include <QQmlPropertyMap>
#include <QtQuickWidgets/QQuickWidget>
class QMLDESIGNERBASE_EXPORT StudioQmlTextBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
public:
explicit StudioQmlTextBackend(QObject *parent = nullptr) : QObject(parent) {}
void setText(const QString &text)
{
if (m_text == text)
return;
m_text = text;
emit textChanged();
}
QString text() const { return m_text; }
Q_INVOKABLE void activateText(const QString &text)
{
if (m_text == text)
return;
setText(text);
emit activated(text);
}
signals:
void textChanged();
void activated(const QString &text);
private:
QString m_text;
};
class QMLDESIGNERBASE_EXPORT StudioQmlComboBoxBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
Q_PROPERTY(QString currentText READ currentText WRITE setCurrentText NOTIFY currentTextChanged)
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(QStringList model READ model NOTIFY modelChanged) //TODO turn into model
public:
explicit StudioQmlComboBoxBackend(QObject *parent = nullptr) : QObject(parent) {}
void setModel(const QStringList &model)
{
if (m_model == model)
return;
m_model = model;
emit countChanged();
emit modelChanged();
emit currentTextChanged();
emit currentIndexChanged();
}
QStringList model() const { return m_model; }
int count() const { return m_model.count(); }
QString currentText() const
{
if (m_currentIndex < 0)
return {};
if (m_model.isEmpty())
return {};
if (m_currentIndex >= m_model.count())
return {};
return m_model.at(m_currentIndex);
}
int currentIndex() const { return m_currentIndex; }
void setCurrentIndex(int i)
{
if (m_currentIndex == i)
return;
m_currentIndex = i;
emit currentTextChanged();
emit currentIndexChanged();
}
void setCurrentText(const QString &text)
{
if (currentText() == text)
return;
if (!m_model.contains(text))
return;
setCurrentIndex(m_model.indexOf(text));
}
Q_INVOKABLE void activateIndex(int i)
{
if (m_currentIndex == i)
return;
setCurrentIndex(i);
emit activated(i);
}
signals:
void currentIndexChanged();
void currentTextChanged();
void countChanged();
void modelChanged();
void activated(int i);
private:
int m_currentIndex = -1;
QStringList m_model;
};
class QMLDESIGNERBASE_EXPORT StudioPropertyMap : public QQmlPropertyMap
{
public: