diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml index b982e3424eb..aafd334046e 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialog.qml @@ -6,6 +6,9 @@ import StudioControls 1.0 as StudioControls import StudioTheme 1.0 as StudioTheme PopupDialog { + + property alias backend: form.backend + titleBar: Row { spacing: 30 // TODO anchors.fill: parent @@ -22,9 +25,16 @@ PopupDialog { style: StudioTheme.Values.connectionPopupControlStyle width: 180 anchors.verticalCenter: parent.verticalCenter - model: ["mySpinbox", "foo", "backendObject"] + model: backend.signal.id.model ?? 0 + + onActivated: backend.signal.id.activateIndex(target.currentIndex) + property int currentTypeIndex: backend.signal.id.currentIndex ?? 0 + onCurrentTypeIndexChanged: target.currentIndex = target.currentTypeIndex } + } - ConnectionsDialogForm {} + ConnectionsDialogForm { + id: form + } } diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml index a54baa55c31..201790c6a67 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsDialogForm.qml @@ -14,12 +14,15 @@ Column { readonly property real verticalSpacing: 16 readonly property real columnWidth: (root.width - root.horizontalSpacing) / 2 + property var backend + component PopupLabel : Text { width: root.columnWidth color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.myFontSize } + /* replaced by ConnectionModelStatementDelegate defined in C++ enum ActionType { CallFunction, Assign, @@ -27,7 +30,7 @@ Column { SetProperty, PrintMessage, Custom - } + } */ y: StudioTheme.Values.popupMargin width: parent.width @@ -47,7 +50,12 @@ Column { id: signal style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - model: ["Clicked", "Pressed", "Released"] + + model: backend.signal.name.model ?? 0 + + onActivated: backend.signal.name.activateIndex(signal.currentIndex) + property int currentTypeIndex: backend.signal.name.currentIndex ?? 0 + onCurrentTypeIndexChanged: signal.currentIndex = signal.currentTypeIndex } StudioControls.TopLevelComboBox { @@ -56,21 +64,25 @@ Column { width: root.columnWidth textRole: "text" valueRole: "value" + ///model.getData(currentIndex, "role") + property int indexFromBackend: indexOfValue(backend.actionType) + onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend + onActivated: backend.changeActionType(action.currentValue) model: [ - { value: ConnectionsDialogForm.CallFunction, text: qsTr("Call Function") }, - { value: ConnectionsDialogForm.Assign, text: qsTr("Assign") }, - { value: ConnectionsDialogForm.ChangeState, text: qsTr("Change State") }, - { value: ConnectionsDialogForm.SetProperty, text: qsTr("Set Property") }, - { value: ConnectionsDialogForm.PrintMessage, text: qsTr("Print Message") }, - { value: ConnectionsDialogForm.Custom, text: qsTr("Custom") } + { value: ConnectionModelStatementDelegate.CallFunction, text: qsTr("Call Function") }, + { value: ConnectionModelStatementDelegate.Assign, text: qsTr("Assign") }, + { value: ConnectionModelStatementDelegate.ChangeState, text: qsTr("Change State") }, + { value: ConnectionModelStatementDelegate.SetProperty, text: qsTr("Set Property") }, + { value: ConnectionModelStatementDelegate.PrintMessage, text: qsTr("Print Message") }, + { value: ConnectionModelStatementDelegate.Custom, text: qsTr("Custom") } ] } } // Call Function Row { - visible: action.currentValue === ConnectionsDialogForm.CallFunction + visible: action.currentValue === ConnectionModelStatementDelegate.CallFunction spacing: root.horizontalSpacing PopupLabel { text: qsTr("Item") } @@ -78,25 +90,36 @@ Column { } Row { - visible: action.currentValue === ConnectionsDialogForm.CallFunction + visible: action.currentValue === ConnectionModelStatementDelegate.CallFunction spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { + id: functionId style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - model: ["mySpinBox", "myAnimation", "myCustomComponent"] + + model: backend.okStatement.function.id.model ?? 0 + + onActivated: backend.okStatement.function.id.activateIndex(functionId.currentIndex) + property int currentTypeIndex: backend.okStatement.function.id.currentIndex ?? 0 + onCurrentTypeIndexChanged: functionId.currentIndex = functionId.currentTypeIndex } StudioControls.TopLevelComboBox { + id: functionName style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - model: ["start", "stop", "reset"] + model: backend.okStatement.function.name.model ?? 0 + + onActivated: backend.okStatement.function.name.activateIndex(functionName.currentIndex) + property int currentTypeIndex: backend.okStatement.function.name.currentIndex ?? 0 + onCurrentTypeIndexChanged: functionName.currentIndex = functionName.currentTypeIndex } } // Assign Row { - visible: action.currentValue === ConnectionsDialogForm.Assign + visible: action.currentValue === ConnectionModelStatementDelegate.Assign spacing: root.horizontalSpacing PopupLabel { text: qsTr("From") } @@ -104,25 +127,68 @@ Column { } Row { - visible: action.currentValue === ConnectionsDialogForm.Assign + visible: action.currentValue === ConnectionModelStatementDelegate.Assign spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { + id: rhsAssignmentId style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - model: ["value", "foo", "bar"] + //from - rhs - id + + model: backend.okStatement.rhsAssignment.id.model ?? 0 + + onActivated: backend.okStatement.rhsAssignment.id.activateIndex(rhsAssignmentId.currentIndex) + property int currentTypeIndex: backend.okStatement.rhsAssignment.id.currentIndex ?? 0 + onCurrentTypeIndexChanged: rhsAssignmentId.currentIndex = rhsAssignmentId.currentTypeIndex } StudioControls.TopLevelComboBox { + id: lhsAssignmentId style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - model: ["myValue", "yourValue", "ourValue"] + //to lhs - id + model: backend.okStatement.lhs.id.model ?? 0 + + onActivated: backend.okStatement.lhs.id.activateIndex(lhsAssignmentId.currentIndex) + property int currentTypeIndex: backend.okStatement.lhs.id.currentIndex ?? 0 + onCurrentTypeIndexChanged: lhsAssignmentId.currentIndex = lhsAssignmentId.currentTypeIndex + } + } + + Row { + visible: action.currentValue === ConnectionModelStatementDelegate.Assign + spacing: root.horizontalSpacing + + StudioControls.TopLevelComboBox { + id: rhsAssignmentName + style: StudioTheme.Values.connectionPopupControlStyle + width: root.columnWidth + //from - rhs - name + + model: backend.okStatement.rhsAssignment.name.model ?? 0 + + onActivated: backend.okStatement.rhsAssignment.name.activateIndex(rhsAssignmentName.currentIndex) + property int currentTypeIndex: backend.okStatement.rhsAssignment.name.currentIndex ?? 0 + onCurrentTypeIndexChanged: rhsAssignmentName.currentIndex = rhsAssignmentName.currentTypeIndex + } + + StudioControls.TopLevelComboBox { + id: lhsAssignmentName + style: StudioTheme.Values.connectionPopupControlStyle + width: root.columnWidth + //to lhs - name + model: backend.okStatement.lhs.name.model ?? 0 + + onActivated: backend.okStatement.lhs.name.activateIndex(lhsAssignmentName.currentIndex) + property int currentTypeIndex: backend.okStatement.lhs.name.currentIndex ?? 0 + onCurrentTypeIndexChanged: lhsAssignmentName.currentIndex = lhsAssignmentName.currentTypeIndex } } // Change State Row { - visible: action.currentValue === ConnectionsDialogForm.ChangeState + visible: action.currentValue === ConnectionModelStatementDelegate.ChangeState spacing: root.horizontalSpacing PopupLabel { text: qsTr("State Group") } @@ -130,25 +196,36 @@ Column { } Row { - visible: action.currentValue === ConnectionsDialogForm.ChangeState + visible: action.currentValue === ConnectionModelStatementDelegate.ChangeState spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { + id: stateGroups style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - model: ["main", "group1", "group2"] + model: backend.okStatement.stateTargets.model ?? 0 + + onActivated: backend.okStatement.stateTargets.activateIndex(stateGroups.currentIndex) + property int currentTypeIndex: backend.okStatement.stateTargets.currentIndex ?? 0 + onCurrentTypeIndexChanged: stateGroups.currentIndex = stateGroups.currentTypeIndex } StudioControls.TopLevelComboBox { + id: states style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - model: ["state1", "state2", "state3", "state4"] + + model: backend.okStatement.states.model ?? 0 + + onActivated: backend.okStatement.states.activateIndex(states.currentIndex) + property int currentTypeIndex: backend.okStatement.states.currentIndex ?? 0 + onCurrentTypeIndexChanged: states.currentIndex = states.currentTypeIndex } } // Set Property Row { - visible: action.currentValue === ConnectionsDialogForm.SetProperty + visible: action.currentValue === ConnectionModelStatementDelegate.SetProperty spacing: root.horizontalSpacing PopupLabel { text: qsTr("Item") } @@ -156,52 +233,73 @@ Column { } Row { - visible: action.currentValue === ConnectionsDialogForm.SetProperty + visible: action.currentValue === ConnectionModelStatementDelegate.SetProperty spacing: root.horizontalSpacing StudioControls.TopLevelComboBox { + id: lhsPropertyId style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - model: ["value", "foo", "bar"] + + model: backend.okStatement.lhs.id.model ?? 0 + + onActivated: backend.okStatement.lhs.id.activateIndex(lhsPropertyId.currentIndex) + property int currentTypeIndex: backend.okStatement.lhs.id.currentIndex ?? 0 + onCurrentTypeIndexChanged: lhsPropertyId.currentIndex = lhsPropertyId.currentTypeIndex + } StudioControls.TopLevelComboBox { + id: lhsPropertyName style: StudioTheme.Values.connectionPopupControlStyle width: root.columnWidth - model: ["myValue", "yourValue", "ourValue"] + model: backend.okStatement.lhs.name.model ?? 0 + + onActivated: backend.okStatement.lhs.name.activateIndex(lhsPropertyName.currentIndex) + property int currentTypeIndex: backend.okStatement.lhs.name.currentIndex ?? 0 + onCurrentTypeIndexChanged: lhsPropertyName.currentIndex = lhsPropertyName.currentTypeIndex } } PopupLabel { - visible: action.currentValue === ConnectionsDialogForm.SetProperty + visible: action.currentValue === ConnectionModelStatementDelegate.SetProperty text: qsTr("Value") } StudioControls.TextField { - visible: action.currentValue === ConnectionsDialogForm.SetProperty + id: setPropertyArgument + visible: action.currentValue === ConnectionModelStatementDelegate.SetProperty width: root.width - text: "This is a test" actionIndicatorVisible: false translationIndicatorVisible: false + + text: backend.okStatement.stringArgument.text ?? "" + onEditingFinished: { + backend.okStatement.stringArgument.activateText(setPropertyArgument.text) + } } // Print Message PopupLabel { - visible: action.currentValue === ConnectionsDialogForm.PrintMessage + visible: action.currentValue === ConnectionModelStatementDelegate.PrintMessage text: qsTr("Message") } StudioControls.TextField { - visible: action.currentValue === ConnectionsDialogForm.PrintMessage + id: messageString + visible: action.currentValue === ConnectionModelStatementDelegate.PrintMessage width: root.width - text: "my value is" actionIndicatorVisible: false translationIndicatorVisible: false + text: backend.okStatement.stringArgument.text ?? "" + onEditingFinished: { + backend.okStatement.stringArgument.activateText(messageString.text) + } } // Custom PopupLabel { - visible: action.currentValue === ConnectionsDialogForm.Custom + visible: action.currentValue === ConnectionModelStatementDelegate.Custom text: qsTr("Custom Connections can only be edited with the binding editor") anchors.left: parent.left anchors.right: parent.right @@ -217,9 +315,60 @@ Column { iconSize: StudioTheme.Values.baseFontSize iconFont: StudioTheme.Constants.font anchors.horizontalCenter: parent.horizontalCenter - visible: action.currentValue !== ConnectionsDialogForm.Custom + visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && !backend.hasCondition - onClicked: console.log("ADD CONDITION") + onClicked: backend.addCondition() + } + + HelperWidgets.AbstractButton { + style: StudioTheme.Values.connectionPopupButtonStyle + width: 160 + buttonIcon: qsTr("Remove Condition") + iconSize: StudioTheme.Values.baseFontSize + iconFont: StudioTheme.Constants.font + anchors.horizontalCenter: parent.horizontalCenter + visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition + + onClicked: backend.removeCondition() + } + + Flow { + spacing: root.horizontalSpacing + width: root.width + Repeater { + + model: backend.conditionListModel + Text { + text: value + color: "white" + Rectangle { + z: -1 + opacity: 0.2 + color: { + if (type === ConditionListModel.Invalid) + return "red" + if (type === ConditionListModel.Operator) + return "blue" + if (type === ConditionListModel.Literal) + return "green" + if (type === ConditionListModel.Variable) + return "yellow" + } + anchors.fill: parent + } + } + } + } + + TextInput { + id: commandInput + width: root.width + onAccepted: backend.conditionListModel.command(commandInput.text) + } + + Text { + text: "invalid " + backend.conditionListModel.error + visible: !backend.conditionListModel.valid } // Editor @@ -231,7 +380,7 @@ Column { Text { anchors.centerIn: parent - text: "backend.myValue = mySpinbox.value" + text: backend.source color: StudioTheme.Values.themeTextColor font.pixelSize: StudioTheme.Values.myFontSize } diff --git a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml index e1fa90265bc..179c6d220e0 100644 --- a/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml +++ b/share/qtcreator/qmldesigner/connectionseditor/ConnectionsListView.qml @@ -38,12 +38,14 @@ ListView { onCurrentIndexChanged: { root.currentIndex = root.model.currentIndex + dialog.backend.currentRow = root.currentIndex } data: [ ConnectionsDialog { id: dialog visible: false + backend: root.model.delegate } ] @@ -79,6 +81,8 @@ ListView { onClicked: { root.model.currentIndex = index root.currentIndex = index + dialog.backend.currentRow = index + dialog.popup(mouseArea) } } diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp index 5a9f0665f74..d1c1b2d7115 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.cpp @@ -187,29 +187,7 @@ struct JSOverload return "console.log(" + std::visit(JSOverload{}, consoleLog.argument) + ")"; } - QString operator()(const ConditionToken &token) - { - switch (token) { - case ConditionToken::Not: - return "!=="; - case ConditionToken::And: - return "&&"; - case ConditionToken::Or: - return "||"; - case ConditionToken::LargerThan: - return ">"; - case ConditionToken::LargerEqualsThan: - return ">="; - case ConditionToken::SmallerThan: - return "<"; - case ConditionToken::SmallerEqualsThan: - return "<="; - case ConditionToken::Equals: - return "==="; - default: - return {}; - }; - } + QString operator()(const ConditionToken &token) { return toJavascript(token); } QString operator()(const ConnectionEditorStatements::MatchedCondition &matched) { @@ -236,7 +214,7 @@ struct JSOverload if (isEmptyStatement(statement)) return {}; - return std::visit(JSOverload{}, statement) + ";\n"; + return std::visit(JSOverload{}, statement); } QString operator()(const ConnectionEditorStatements::ConditionalStatement &conditional) @@ -334,20 +312,60 @@ QString ConnectionEditorStatements::toDisplayName(const Handler &handler) return toDisplayName(statement); } -MatchedStatement ConnectionEditorStatements::okStatement(const ConnectionEditorStatements::Handler &handler) +MatchedStatement &ConnectionEditorStatements::okStatement( + ConnectionEditorStatements::Handler &handler) { - return std::visit(Overload{[](const ConnectionEditorStatements::MatchedStatement &var) { return var; }, - [](const ConnectionEditorStatements::ConditionalStatement &statement) { - return statement.ok; - }}, + MatchedStatement statement; + std::visit([statement](auto &test) { return statement; }, handler); + + return std::visit(Overload{[](ConnectionEditorStatements::MatchedStatement &var) + -> MatchedStatement & { return var; }, + [](ConnectionEditorStatements::ConditionalStatement &statement) + -> MatchedStatement & { return statement.ok; }}, handler); } -MatchedStatement ConnectionEditorStatements::koStatement(const ConnectionEditorStatements::Handler &handler) +MatchedStatement &ConnectionEditorStatements::koStatement( + ConnectionEditorStatements::Handler &handler) { - return std::visit(Overload{[](const ConnectionEditorStatements::ConditionalStatement &statement) { - return statement.ko; - }, - [](const auto &) -> ConnectionEditorStatements::MatchedStatement { return {}; }}, - handler); + static MatchedStatement block; + + if (auto *statement = std::get_if(&handler)) + return statement->ko; + + return block; +} + +MatchedCondition &ConnectionEditorStatements::matchedCondition(Handler &handler) +{ + static MatchedCondition block; + + if (auto *statement = std::get_if(&handler)) + return statement->condition; + + return block; +} + +QString ConnectionEditorStatements::toJavascript(const ConditionToken &token) +{ + switch (token) { + case ConditionToken::Not: + return "!=="; + case ConditionToken::And: + return "&&"; + case ConditionToken::Or: + return "||"; + case ConditionToken::LargerThan: + return ">"; + case ConditionToken::LargerEqualsThan: + return ">="; + case ConditionToken::SmallerThan: + return "<"; + case ConditionToken::SmallerEqualsThan: + return "<="; + case ConditionToken::Equals: + return "==="; + default: + return {}; + }; } diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h index 1596ce2bfe6..908b7305c69 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorstatements.h @@ -115,9 +115,12 @@ QMLDESIGNER_EXPORT QString toString(const Handler &handler); QMLDESIGNER_EXPORT QString toJavascript(const Handler &handler); QMLDESIGNER_EXPORT QString toDisplayName(const MatchedStatement &statement); QMLDESIGNER_EXPORT QString toDisplayName(const Handler &handler); +QMLDESIGNER_EXPORT QString toJavascript(const ConditionToken &token); -QMLDESIGNER_EXPORT MatchedStatement okStatement(const ConnectionEditorStatements::Handler &handler); -QMLDESIGNER_EXPORT MatchedStatement koStatement(const ConnectionEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT MatchedStatement &okStatement(ConnectionEditorStatements::Handler &handler); +QMLDESIGNER_EXPORT MatchedStatement &koStatement(ConnectionEditorStatements::Handler &handler); + +QMLDESIGNER_EXPORT MatchedCondition &matchedCondition(ConnectionEditorStatements::Handler &handler); } // namespace ConnectionEditorStatements diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 6fe8af59eb7..ad9d463437e 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -3,19 +3,20 @@ #include "connectionmodel.h" #include "connectionview.h" +#include "utils/algorithm.h" #include -#include -#include -#include -#include +#include #include -#include +#include #include -#include #include #include #include +#include +#include +#include +#include #include @@ -46,8 +47,8 @@ bool isConnection(const QmlDesigner::ModelNode &modelNode) namespace QmlDesigner { ConnectionModel::ConnectionModel(ConnectionView *parent) - : QStandardItemModel(parent) - , m_connectionView(parent) + : QStandardItemModel(parent), m_connectionView(parent), + m_delegate(new ConnectionModelBackendDelegate(this)) { connect(this, &QStandardItemModel::dataChanged, this, &ConnectionModel::handleDataChanged); } @@ -264,7 +265,10 @@ void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerP //anything else is assignment // e.g. foo.bal = foo2.bula ; foo.bal = "literal" ; goo.gal = true - item->setData("Assignment", UserRoles::ActionTypeRole); + item->setData(tr(ConnectionEditorEvaluator::getDisplayStringForType( + signalHandlerProperty.source()) + .toLatin1()), + UserRoles::ActionTypeRole); } ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connection) const @@ -399,6 +403,11 @@ void ConnectionModel::handleException() resetModel(); } +ConnectionModelBackendDelegate *ConnectionModel::delegate() const +{ + return m_delegate; +} + void ConnectionModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (topLeft != bottomRight) { @@ -553,4 +562,1253 @@ QHash ConnectionModel::roleNames() const return roleNames; } +ConnectionModelBackendDelegate::ConnectionModelBackendDelegate(ConnectionModel *parent) + : QObject(parent), m_signalDelegate(parent->connectionView()), m_okStatementDelegate(parent), + m_koStatementDelegate(parent), m_conditionListModel(parent) +{ + connect(&m_signalDelegate, &PropertyTreeModelDelegate::commitData, this, [this]() { + handleTargetChanged(); + }); + + connect(&m_okStatementDelegate, + &ConnectionModelStatementDelegate::statementChanged, + this, + [this]() { handleOkStatementChanged(); }); + + connect(&m_conditionListModel, &ConditionListModel::conditionChanged, this, [this]() { + handleConditionChanged(); + }); + + m_signalDelegate.setPropertyType(PropertyTreeModel::SignalType); +} + +QString generateDefaultStatement(ConnectionModelBackendDelegate::ActionType actionType, + const QString &rootId) +{ + switch (actionType) { + case ConnectionModelStatementDelegate::CallFunction: + return "Qt.quit()"; + case ConnectionModelStatementDelegate::Assign: + return QString("%1.visible = %1.visible").arg(rootId); + case ConnectionModelStatementDelegate::ChangeState: + return QString("%1.state = \"\"").arg(rootId); + case ConnectionModelStatementDelegate::SetProperty: + return QString("%1.visible = true").arg(rootId); + case ConnectionModelStatementDelegate::PrintMessage: + return QString("console.log(\"test\")").arg(rootId); + case ConnectionModelStatementDelegate::Custom: + return {}; + }; + + //Qt.quit() + //console.log("test") + //root.state = "" + //root.visible = root.visible + //root.visible = true + + return {}; +} + +void ConnectionModelBackendDelegate::changeActionType(ActionType actionType) +{ + qDebug() << Q_FUNC_INFO << actionType; + QTC_ASSERT(actionType != ConnectionModelStatementDelegate::Custom, return ); + + ConnectionModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->connectionView()->isAttached(), return ); + + const QString validId = model->connectionView()->rootModelNode().validId(); + + SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + + // Do not take ko into account for now + + model->connectionView() + ->executeInTransaction("ConnectionModelBackendDelegate::removeCondition", [&]() { + ConnectionEditorStatements::MatchedStatement &okStatement + = ConnectionEditorStatements::okStatement(m_handler); + + //We expect a valid id on the root node + const QString validId = model->connectionView()->rootModelNode().validId(); + QString statementSource = generateDefaultStatement(actionType, validId); + + auto tempHandler = ConnectionEditorEvaluator::parseStatement(statementSource); + qDebug() << ConnectionEditorStatements::toString(tempHandler); + auto newOkStatement = ConnectionEditorStatements::okStatement(tempHandler); + qDebug() << "newOk" << statementSource; + QTC_ASSERT(!ConnectionEditorStatements::isEmptyStatement(newOkStatement), return ); + + okStatement = newOkStatement; + + QString newSource = ConnectionEditorStatements::toJavascript(m_handler); + + signalHandlerProperty.setSource(newSource); + }); + + setSource(signalHandlerProperty.source()); + + setupHandlerAndStatements(); + setupCondition(); +} + +void ConnectionModelBackendDelegate::addCondition() +{ + ConnectionEditorStatements::MatchedStatement okStatement + = ConnectionEditorStatements::okStatement(m_handler); + + ConnectionEditorStatements::MatchedCondition newCondition; + + ConnectionEditorStatements::Variable variable; + variable.nodeId = "condition"; + newCondition.statements.append(variable); + + ConnectionEditorStatements::ConditionalStatement conditionalStatement; + + conditionalStatement.ok = okStatement; + conditionalStatement.condition = newCondition; + + m_handler = conditionalStatement; + + QString newSource = ConnectionEditorStatements::toJavascript(m_handler); + + ConnectionModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->connectionView()->isAttached(), return ); + + SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + + model->connectionView()->executeInTransaction("ConnectionModelBackendDelegate::removeCondition", + [&]() { + signalHandlerProperty.setSource(newSource); + }); + + setSource(signalHandlerProperty.source()); + + setupHandlerAndStatements(); + setupCondition(); +} + +void ConnectionModelBackendDelegate::removeCondition() +{ + qDebug() << Q_FUNC_INFO; + + ConnectionEditorStatements::MatchedStatement okStatement + = ConnectionEditorStatements::okStatement(m_handler); + + m_handler = okStatement; + + QString newSource = ConnectionEditorStatements::toJavascript(m_handler); + + ConnectionModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + QTC_ASSERT(model->connectionView()->isAttached(), return ); + + SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + + model->connectionView()->executeInTransaction("ConnectionModelBackendDelegate::removeCondition", + [&]() { + signalHandlerProperty.setSource(newSource); + }); + + setSource(signalHandlerProperty.source()); + + setupHandlerAndStatements(); + setupCondition(); +} + +int ConnectionModelBackendDelegate::currentRow() const +{ + return m_currentRow; +} + +QString removeOnFromSignalName(const QString &signal) +{ + QString ret = signal; + ret.remove(0, 2); + ret[0] = ret.at(0).toLower(); + return ret; +} + +QString addOnToSignalName(const QString &signal) +{ + QString ret = signal; + ret[0] = ret.at(0).toUpper(); + ret.prepend("on"); + return ret; +} + +void ConnectionModelBackendDelegate::setCurrentRow(int i) +{ + if (m_currentRow == i) + return; + + m_currentRow = i; + + //setup + + ConnectionModel *model = qobject_cast(parent()); + + qDebug() << Q_FUNC_INFO << i << model; + + QTC_ASSERT(model, return ); + + SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + + QStringList targetNodes; + + for (const ModelNode &modelNode : model->connectionView()->allModelNodes()) { + if (!modelNode.id().isEmpty()) + targetNodes.append(modelNode.id()); + } + + std::sort(targetNodes.begin(), targetNodes.end()); + + const QString targetNodeName = signalHandlerProperty.parentModelNode() + .bindingProperty("target") + .resolveToModelNode() + .id(); + + if (!targetNodes.contains(targetNodeName)) + targetNodes.append(targetNodeName); + + setSource(signalHandlerProperty.source()); + + qDebug() << Q_FUNC_INFO + << removeOnFromSignalName(QString::fromUtf8(signalHandlerProperty.name())); + + m_signalDelegate.setup(targetNodeName, + removeOnFromSignalName(QString::fromUtf8(signalHandlerProperty.name()))); + + setupHandlerAndStatements(); + + qDebug() << Q_FUNC_INFO << ConnectionEditorStatements::toString(m_handler) + << ConnectionEditorStatements::toJavascript(m_handler); + + setupCondition(); + + QTC_ASSERT(model, return ); +} + +void ConnectionModelBackendDelegate::handleException() +{ + QMessageBox::warning(nullptr, tr("Error"), m_exceptionError); +} + +bool ConnectionModelBackendDelegate::hasCondition() const +{ + return m_hasCondition; +} + +void ConnectionModelBackendDelegate::setHasCondition(bool b) +{ + if (b == m_hasCondition) + return; + + m_hasCondition = b; + emit hasConditionChanged(); +} + +ConnectionModelBackendDelegate::ActionType ConnectionModelBackendDelegate::actionType() const +{ + return m_actionType; +} + +PropertyTreeModelDelegate *ConnectionModelBackendDelegate::signal() +{ + return &m_signalDelegate; +} + +ConnectionModelStatementDelegate *ConnectionModelBackendDelegate::okStatement() +{ + return &m_okStatementDelegate; +} + +ConnectionModelStatementDelegate *ConnectionModelBackendDelegate::koStatement() +{ + return &m_koStatementDelegate; +} + +ConditionListModel *ConnectionModelBackendDelegate::conditionListModel() +{ + return &m_conditionListModel; +} + +QString ConnectionModelBackendDelegate::source() const +{ + return m_source; +} + +void ConnectionModelBackendDelegate::setSource(const QString &source) +{ + if (source == m_source) + return; + + m_source = source; + emit sourceChanged(); +} + +void ConnectionModelBackendDelegate::setupCondition() +{ + auto &condition = ConnectionEditorStatements::matchedCondition(m_handler); + m_conditionListModel.setCondition(ConnectionEditorStatements::matchedCondition(m_handler)); + setHasCondition(!condition.statements.isEmpty()); +} + +void ConnectionModelBackendDelegate::setupHandlerAndStatements() +{ + ConnectionModel *model = qobject_cast(parent()); + QTC_ASSERT(model, return ); + SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + + m_handler = ConnectionEditorEvaluator::parseStatement(signalHandlerProperty.source()); + + const QString statementType = QmlDesigner::ConnectionEditorStatements::toDisplayName(m_handler); + + if (statementType == ConnectionEditorStatements::EMPTY_DISPLAY_NAME) { + m_actionType = ConnectionModelStatementDelegate::Custom; + } else if (statementType == ConnectionEditorStatements::ASSIGNMENT_DISPLAY_NAME) { + m_actionType = ConnectionModelStatementDelegate::Assign; + //setupAssignment(); + } else if (statementType == ConnectionEditorStatements::SETPROPERTY_DISPLAY_NAME) { + m_actionType = ConnectionModelStatementDelegate::SetProperty; + //setupSetProperty(); + } else if (statementType == ConnectionEditorStatements::FUNCTION_DISPLAY_NAME) { + m_actionType = ConnectionModelStatementDelegate::CallFunction; + //setupCallFunction(); + } else if (statementType == ConnectionEditorStatements::SETSTATE_DISPLAY_NAME) { + m_actionType = ConnectionModelStatementDelegate::ChangeState; + //setupChangeState(); + } else if (statementType == ConnectionEditorStatements::LOG_DISPLAY_NAME) { + m_actionType = ConnectionModelStatementDelegate::PrintMessage; + //setupPrintMessage(); + } else { + m_actionType = ConnectionModelStatementDelegate::Custom; + } + + ConnectionEditorStatements::MatchedStatement &okStatement + = ConnectionEditorStatements::okStatement(m_handler); + m_okStatementDelegate.setStatement(okStatement); + m_okStatementDelegate.setActionType(m_actionType); + + ConnectionEditorStatements::MatchedStatement &koStatement + = ConnectionEditorStatements::koStatement(m_handler); + + if (!ConnectionEditorStatements::isEmptyStatement(koStatement)) { + m_koStatementDelegate.setStatement(koStatement); + m_koStatementDelegate.setActionType(m_actionType); + } + + emit actionTypeChanged(); +} + +void ConnectionModelBackendDelegate::handleTargetChanged() +{ + ConnectionModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + + QTC_ASSERT(model->connectionView()->isAttached(), return ); + + SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + + const PropertyName handlerName = addOnToSignalName(m_signalDelegate.name()).toUtf8(); + + qDebug() << Q_FUNC_INFO << m_signalDelegate.id() << handlerName; + + const auto parentModelNode = signalHandlerProperty.parentModelNode(); + + QTC_ASSERT(parentModelNode.isValid(), return ); + + const auto newId = m_signalDelegate.id(); + + model->connectionView() + ->executeInTransaction("ConnectionModelBackendDelegate::handleTargetChanged", [&]() { + const auto oldTargetNodeName + = parentModelNode.bindingProperty("target").resolveToModelNode().id(); + + if (signalHandlerProperty.name() != handlerName) { + const auto expression = signalHandlerProperty.source(); + parentModelNode.removeProperty(signalHandlerProperty.name()); + parentModelNode.signalHandlerProperty(handlerName).setSource(expression); + } + + if (oldTargetNodeName != newId) + parentModelNode.bindingProperty("target").setExpression(newId); + }); +} + +void ConnectionModelBackendDelegate::handleOkStatementChanged() +{ + ConnectionEditorStatements::MatchedStatement &okStatement + = ConnectionEditorStatements::okStatement(m_handler); + + okStatement = m_okStatementDelegate.statement(); //TODO why? + + QString newSource = ConnectionEditorStatements::toJavascript(m_handler); + + ConnectionModel *model = qobject_cast(parent()); + + QTC_ASSERT(model, return ); + + QTC_ASSERT(model->connectionView()->isAttached(), return ); + + SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + + model->connectionView() + ->executeInTransaction("ConnectionModelBackendDelegate::handleOkStatementChanged", + [&]() { signalHandlerProperty.setSource(newSource); }); + + setSource(signalHandlerProperty.source()); +} + +void ConnectionModelBackendDelegate::handleConditionChanged() +{ + qDebug() << Q_FUNC_INFO; + + ConnectionModel *model = qobject_cast(parent()); + QTC_ASSERT(model, return ); + QTC_ASSERT(model->connectionView()->isAttached(), return ); + + auto view = model->connectionView(); + + ConnectionEditorStatements::MatchedCondition &condition + = ConnectionEditorStatements::matchedCondition(m_handler); + condition = m_conditionListModel.condition(); //why? + QString newSource = ConnectionEditorStatements::toJavascript(m_handler); + + qDebug() << Q_FUNC_INFO << "new source" << newSource; + + SignalHandlerProperty signalHandlerProperty = model->signalHandlerPropertyForRow(currentRow()); + + try { + RewriterTransaction transaction = view->beginRewriterTransaction( + "ConnectionModelBackendDelegate::handleConditionChanged"); + signalHandlerProperty.setSource(newSource); + transaction.commit(); + } catch (const Exception &e) { + m_conditionListModel.setInvalid(e.description()); + } + + setSource(signalHandlerProperty.source()); +} + +static ConnectionEditorStatements::MatchedStatement emptyStatement; + +ConnectionModelStatementDelegate::ConnectionModelStatementDelegate(ConnectionModel *parent) + : QObject(parent), m_functionDelegate(parent->connectionView()), + m_lhsDelegate(parent->connectionView()), m_rhsAssignmentDelegate(parent->connectionView()), + m_statement(emptyStatement), m_model(parent) +{ + m_functionDelegate.setPropertyType(PropertyTreeModel::SlotType); + + connect(&m_functionDelegate, &PropertyTreeModelDelegate::commitData, this, [this]() { + handleFunctionChanged(); + }); + + connect(&m_rhsAssignmentDelegate, &PropertyTreeModelDelegate::commitData, this, [this]() { + handleRhsAssignmentChanged(); + }); + + connect(&m_lhsDelegate, &PropertyTreeModelDelegate::commitData, this, [this]() { + handleLhsChanged(); + }); + + connect(&m_stringArgument, &StudioQmlTextBackend::activated, this, [this]() { + handleStringArgumentChanged(); + }); + + connect(&m_states, &StudioQmlComboBoxBackend::activated, this, [this]() { + handleStateChanged(); + }); + + connect(&m_stateTargets, &StudioQmlComboBoxBackend::activated, this, [this]() { + handleStateTargetsChanged(); + }); +} + +void ConnectionModelStatementDelegate::setActionType(ActionType type) +{ + if (m_actionType == type) + return; + + m_actionType = type; + emit actionTypeChanged(); + setup(); +} + +void ConnectionModelStatementDelegate::setup() +{ + switch (m_actionType) { + case CallFunction: + setupCallFunction(); + break; + case Assign: + setupAssignment(); + break; + case ChangeState: + setupChangeState(); + break; + case SetProperty: + setupSetProperty(); + break; + case PrintMessage: + setupPrintMessage(); + break; + case Custom: + break; + }; +} + +void ConnectionModelStatementDelegate::setStatement( + ConnectionEditorStatements::MatchedStatement &statement) +{ + m_statement = statement; + setup(); +} + +ConnectionEditorStatements::MatchedStatement &ConnectionModelStatementDelegate::statement() +{ + return m_statement; +} + +ConnectionModelStatementDelegate::ActionType ConnectionModelStatementDelegate::actionType() const +{ + return m_actionType; +} + +PropertyTreeModelDelegate *ConnectionModelStatementDelegate::function() +{ + return &m_functionDelegate; +} + +PropertyTreeModelDelegate *ConnectionModelStatementDelegate::lhs() +{ + return &m_lhsDelegate; +} + +PropertyTreeModelDelegate *ConnectionModelStatementDelegate::rhsAssignment() +{ + return &m_rhsAssignmentDelegate; +} + +StudioQmlTextBackend *ConnectionModelStatementDelegate::stringArgument() +{ + return &m_stringArgument; +} + +StudioQmlComboBoxBackend *ConnectionModelStatementDelegate::stateTargets() +{ + return &m_stateTargets; +} + +StudioQmlComboBoxBackend *ConnectionModelStatementDelegate::states() +{ + return &m_states; +} + +void ConnectionModelStatementDelegate::handleFunctionChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), + return ); + + ConnectionEditorStatements::MatchedFunction &functionStatement + = std::get(m_statement); + + functionStatement.functionName = m_functionDelegate.name(); + functionStatement.nodeId = m_functionDelegate.id(); + + emit statementChanged(); +} + +void ConnectionModelStatementDelegate::handleLhsChanged() +{ + if (m_actionType == Assign) { + QTC_ASSERT(std::holds_alternative(m_statement), + return ); + + ConnectionEditorStatements::Assignment &assignmentStatement + = std::get(m_statement); + + assignmentStatement.lhs.nodeId = m_lhsDelegate.id(); + assignmentStatement.lhs.propertyName = m_lhsDelegate.name(); + + } else if (m_actionType == SetProperty) { + QTC_ASSERT(std::holds_alternative(m_statement), + return ); + + ConnectionEditorStatements::PropertySet &setPropertyStatement + = std::get(m_statement); + + setPropertyStatement.lhs.nodeId = m_lhsDelegate.id(); + setPropertyStatement.lhs.propertyName = m_lhsDelegate.name(); + } else { + QTC_ASSERT(false, return ); + } + + emit statementChanged(); +} + +void ConnectionModelStatementDelegate::handleRhsAssignmentChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return ); + + ConnectionEditorStatements::Assignment &assignmentStatement + = std::get(m_statement); + + assignmentStatement.rhs.nodeId = m_rhsAssignmentDelegate.id(); + assignmentStatement.rhs.propertyName = m_rhsAssignmentDelegate.name(); + + emit statementChanged(); +} + +static ConnectionEditorStatements::Literal parseTextArgument(const QString &text) +{ + if (text.startsWith("\"") && text.endsWith("\"")) { + QString ret = text; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + + if (text == "true") + return true; + + if (text == "false") + return false; + + bool ok = true; + double d = text.toDouble(&ok); + if (ok) + return d; + + return text; +} + +static ConnectionEditorStatements::ComparativeStatement parseTextArgumentComparativeStatement( + const QString &text) +{ + if (text.startsWith("\"") && text.endsWith("\"")) { + QString ret = text; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + + if (text == "true") + return true; + + if (text == "false") + return false; + + bool ok = true; + double d = text.toDouble(&ok); + if (ok) + return d; + + return text; +} + +static ConnectionEditorStatements::RightHandSide parseLogTextArgument(const QString &text) +{ + if (text.startsWith("\"") && text.endsWith("\"")) { + QString ret = text; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + + if (text == "true") + return true; + + if (text == "false") + return true; + + bool ok = true; + double d = text.toDouble(&ok); + if (ok) + return d; + + //TODO variables and function calls + return text; +} + +void ConnectionModelStatementDelegate::handleStringArgumentChanged() +{ + if (m_actionType == SetProperty) { + QTC_ASSERT(std::holds_alternative(m_statement), + return ); + + ConnectionEditorStatements::PropertySet &propertySet + = std::get(m_statement); + + propertySet.rhs = parseTextArgument(m_stringArgument.text()); + + } else if (m_actionType == PrintMessage) { + QTC_ASSERT(std::holds_alternative(m_statement), + return ); + + ConnectionEditorStatements::ConsoleLog &consoleLog + = std::get(m_statement); + + consoleLog.argument = parseLogTextArgument(m_stringArgument.text()); + } else { + QTC_ASSERT(false, return ); + } + + emit statementChanged(); +} + +void ConnectionModelStatementDelegate::handleStateChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return ); + + ConnectionEditorStatements::StateSet &stateSet = std::get( + m_statement); + + QString stateName = m_states.currentText(); + if (stateName == baseStateName()) + stateName = ""; + stateSet.stateName = "\"" + stateName + "\""; + + emit statementChanged(); +} + +void ConnectionModelStatementDelegate::handleStateTargetsChanged() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return ); + + ConnectionEditorStatements::StateSet &stateSet = std::get( + m_statement); + + stateSet.nodeId = m_stateTargets.currentText(); + stateSet.stateName = "\"\""; + + setupStates(); + + emit statementChanged(); +} + +void ConnectionModelStatementDelegate::setupAssignment() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return ); + + const auto assignment = std::get(m_statement); + m_lhsDelegate.setup(assignment.lhs.nodeId, assignment.lhs.propertyName); + m_rhsAssignmentDelegate.setup(assignment.rhs.nodeId, assignment.rhs.propertyName); +} + +void ConnectionModelStatementDelegate::setupSetProperty() +{ + QTC_ASSERT(std::holds_alternative(m_statement), + return ); + + const auto propertySet = std::get(m_statement); + m_lhsDelegate.setup(propertySet.lhs.nodeId, propertySet.lhs.propertyName); + m_stringArgument.setText(ConnectionEditorStatements::toString(propertySet.rhs)); +} + +void ConnectionModelStatementDelegate::setupCallFunction() +{ + QTC_ASSERT(std::holds_alternative(m_statement), + return ); + + const auto functionStatement = std::get( + m_statement); + m_functionDelegate.setup(functionStatement.nodeId, functionStatement.functionName); +} + +void ConnectionModelStatementDelegate::setupChangeState() +{ + qDebug() << Q_FUNC_INFO; + QTC_ASSERT(std::holds_alternative(m_statement), return ); + + QTC_ASSERT(m_model->connectionView()->isAttached(), return ); + + auto model = m_model->connectionView()->model(); + const auto items = Utils::filtered(m_model->connectionView()->allModelNodesOfType( + model->qtQuickItemMetaInfo()), + [](const ModelNode &node) { + QmlItemNode item(node); + return node.hasId() && item.isValid() + && !item.allStateNames().isEmpty(); + }); + + QStringList itemIds = Utils::transform(items, [](const ModelNode &node) { return node.id(); }); + const auto groups = m_model->connectionView()->allModelNodesOfType( + model->qtQuickStateGroupMetaInfo()); + + const auto rootId = m_model->connectionView()->rootModelNode().id(); + itemIds.removeAll(rootId); + + QStringList groupIds = Utils::transform(groups, [](const ModelNode &node) { return node.id(); }); + + Utils::sort(itemIds); + Utils::sort(groupIds); + + if (!rootId.isEmpty()) + groupIds.prepend(rootId); + + const QStringList stateGroupModel = groupIds + itemIds; + m_stateTargets.setModel(stateGroupModel); + + const auto stateSet = std::get(m_statement); + + m_stateTargets.setCurrentText(stateSet.nodeId); + setupStates(); +} +QString stripQuotesFromState(const QString &input) +{ + if (input.startsWith("\"") && input.endsWith("\"")) { + QString ret = input; + ret.remove(0, 1); + ret.chop(1); + return ret; + } + return input; +} +void ConnectionModelStatementDelegate::setupStates() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return ); + QTC_ASSERT(m_model->connectionView()->isAttached(), return ); + + const auto stateSet = std::get(m_statement); + + qDebug() << Q_FUNC_INFO << stateSet.stateName; + + const QString nodeId = m_stateTargets.currentText(); + + const ModelNode node = m_model->connectionView()->modelNodeForId(nodeId); + + QStringList states; + if (node.metaInfo().isQtQuickItem()) { + QmlItemNode item(node); + QTC_ASSERT(item.isValid(), return ); + if (item.isRootNode()) + states = item.states().names(); //model + else + states = item.allStateNames(); //instances + } else { + QmlModelStateGroup group(node); + states = group.names(); //model + } + + const QString stateName = stripQuotesFromState(stateSet.stateName); + + states.prepend(baseStateName()); + m_states.setModel(states); + if (stateName.isEmpty()) + m_states.setCurrentText(baseStateName()); + else + m_states.setCurrentText(stateName); +} + +void ConnectionModelStatementDelegate::setupPrintMessage() +{ + QTC_ASSERT(std::holds_alternative(m_statement), return ); + + const auto consoleLog = std::get(m_statement); + m_stringArgument.setText(ConnectionEditorStatements::toString(consoleLog.argument)); +} + +QString ConnectionModelStatementDelegate::baseStateName() const +{ + return tr("Base State"); +} + +static ConnectionEditorStatements::MatchedCondition emptyCondition; + +ConditionListModel::ConditionListModel(ConnectionModel *parent) + : m_connectionModel(parent), m_condition(emptyCondition) +{} + +int ConditionListModel::rowCount(const QModelIndex &parent) const +{ + return m_tokens.size(); +} + +QHash ConditionListModel::roleNames() const +{ + static QHash roleNames{{Qt::UserRole + 1, "type"}, {Qt::UserRole + 2, "value"}}; + return roleNames; +} + +QVariant ConditionListModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid() && index.row() < rowCount()) { + if (role == Qt::UserRole + 1) { + return m_tokens.at(index.row()).type; + } else if (role == Qt::UserRole + 2) { + return m_tokens.at(index.row()).value; + } + + qWarning() << Q_FUNC_INFO << "invalid role"; + } else { + qWarning() << Q_FUNC_INFO << "invalid index"; + } + + return QVariant(); +} + +void ConditionListModel::setup() +{ + m_tokens.clear(); + + internalSetup(); + + emit validChanged(); + emit emptyChanged(); + + beginResetModel(); + endResetModel(); +} + +void ConditionListModel::setCondition(ConnectionEditorStatements::MatchedCondition &condition) +{ + m_condition = condition; + setup(); +} + +ConnectionEditorStatements::MatchedCondition &ConditionListModel::condition() +{ + return m_condition; +} + +ConditionListModel::ConditionToken ConditionListModel::tokenFromConditionToken( + const ConnectionEditorStatements::ConditionToken &token) +{ + ConditionToken ret; + ret.type = Operator; + ret.value = ConnectionEditorStatements::toJavascript(token); + + return ret; +} + +ConditionListModel::ConditionToken ConditionListModel::tokenFromComparativeStatement( + const ConnectionEditorStatements::ComparativeStatement &token) +{ + ConditionToken ret; + + if (auto *variable = std::get_if(&token)) { + ret.type = Variable; + ret.value = variable->expression(); + return ret; + } else if (auto *literal = std::get_if(&token)) { + ret.type = Literal; + ret.value = "\"" + *literal + "\""; + return ret; + } else if (auto *literal = std::get_if(&token)) { + ret.type = Literal; + if (*literal) + ret.value = "true"; + else + ret.value = "false"; + return ret; + } else if (auto *literal = std::get_if(&token)) { + ret.type = Literal; + ret.value = QString::number(*literal); + return ret; + } + + ret.type = Invalid; + ret.value = "invalid"; + return {}; +} + +void ConditionListModel::insertToken(int index, const QString &value) +{ + m_tokens.insert(index, valueToToken(value)); + validateAndRebuildTokens(); + resetModel(); +} + +void ConditionListModel::updateToken(int index, const QString &value) +{ + m_tokens[index] = valueToToken(value); + validateAndRebuildTokens(); + resetModel(); +} + +void ConditionListModel::appendToken(const QString &value) +{ + insertToken(rowCount(), value); + validateAndRebuildTokens(); + resetModel(); +} + +void ConditionListModel::removeToken(int index) +{ + m_tokens.remove(index, 1); + validateAndRebuildTokens(); + resetModel(); +} + +bool ConditionListModel::valid() const +{ + return m_valid; +} + +bool ConditionListModel::empty() const +{ + return m_tokens.isEmpty(); +} + +void ConditionListModel::command(const QString &string) +{ + //TODO remove from prodcution code + QStringList list = string.split("%", Qt::SkipEmptyParts); + + qDebug() << Q_FUNC_INFO << string << list.size(); + + if (list.size() < 2) + return; + + if (list.size() == 2) { + if (list.first() == "A") { + qDebug() << "Append" << list.last(); + appendToken(list.last()); + } else if (list.first() == "R") { + bool ok = true; + int index = list.last().toInt(&ok); + + qDebug() << "Remove" << index; + if (ok) + removeToken(index); + } + } + + if (list.size() == 3) { + if (list.first() == "U") { + bool ok = true; + int index = list.at(1).toInt(&ok); + + if (ok) + updateToken(index, list.last()); + qDebug() << "Update" << index << list.last(); + } else if (list.first() == "I") { + bool ok = true; + int index = list.at(1).toInt(&ok); + + if (ok) + insertToken(index, list.last()); + + qDebug() << "Insert" << index << list.last(); + } + } +} + +void ConditionListModel::setInvalid(const QString &errorMessage) +{ + m_valid = false; + m_errorMessage = errorMessage; + emit errorChanged(); + emit validChanged(); +} + +void ConditionListModel::setValid() +{ + m_valid = true; + m_errorMessage.clear(); + emit errorChanged(); + emit validChanged(); +} + +QString ConditionListModel::error() const +{ + return m_errorMessage; +} + +void ConditionListModel::internalSetup() +{ + setInvalid(tr("No Valid Condition")); + if (!m_condition.statements.size() && !m_condition.tokens.size()) + return; + + if (m_condition.statements.size() != m_condition.tokens.size() + 1) + return; + + auto s_it = m_condition.statements.begin(); + auto o_it = m_condition.tokens.begin(); + + while (o_it != m_condition.tokens.end()) { + m_tokens.append(tokenFromComparativeStatement(*s_it)); + m_tokens.append(tokenFromConditionToken(*o_it)); + + s_it++; + o_it++; + } + m_tokens.append(tokenFromComparativeStatement(*s_it)); + + for (const auto &token : m_tokens) + qDebug() << token.value; + + setValid(); +} + +ConditionListModel::ConditionToken ConditionListModel::valueToToken(const QString &value) +{ + const QStringList operators = {"&&", "||", "===", "!==", ">", ">=", "<", "<="}; + + if (operators.contains(value)) { + ConditionToken token; + token.type = Operator; + token.value = value; + return token; + } + + bool ok = false; + value.toDouble(&ok); + + if (value == "true" || value == "false" || ok + || (value.startsWith("\"") && value.endsWith("\""))) { + ConditionToken token; + token.type = Literal; + token.value = value; + return token; + } + + static QRegularExpression regexp("^[a-z_]\\w*|^[A-Z]\\w*\\.{1}([a-z_]\\w*\\.?)+"); + QRegularExpressionMatch match = regexp.match(value); + + if (match.hasMatch()) { //variable + ConditionToken token; + token.type = Variable; + token.value = value; + return token; + } + + ConditionToken token; + token.type = Invalid; + token.value = value; + + return token; +} + +void ConditionListModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} + +int ConditionListModel::checkOrder() const +{ + auto it = m_tokens.begin(); + + bool wasOperator = true; + + int ret = 0; + while (it != m_tokens.end()) { + if (wasOperator && it->type == Operator) + return ret; + if (!wasOperator && it->type == Literal) + return ret; + if (!wasOperator && it->type == Variable) + return ret; + wasOperator = it->type == Operator; + it++; + ret++; + } + return -1; +} + +void ConditionListModel::validateAndRebuildTokens() +{ + QString invalidValue; + const bool invalidToken = Utils::contains(m_tokens, + [&invalidValue](const ConditionToken &token) { + if (token.type == Invalid) + invalidValue = token.value; + return token.type == Invalid; + }); + + if (invalidToken) { + setInvalid(tr("Invalid token %").arg(invalidToken)); + return; + } + + if (int firstError = checkOrder() != -1) { + setInvalid(tr("Invalid order at %1").arg(firstError)); + return; + } + + setValid(); + + rebuildTokens(); +} + +void ConditionListModel::rebuildTokens() +{ + QTC_ASSERT(m_valid, return ); + + m_condition.statements.clear(); + m_condition.tokens.clear(); + + auto it = m_tokens.begin(); + + while (it != m_tokens.end()) { + QTC_ASSERT(it->type != Invalid, return ); + if (it->type == Operator) + m_condition.tokens.append(toOperatorStatement(*it)); + else if (it->type == Literal || it->type == Variable) + m_condition.statements.append(toStatement(*it)); + + it++; + } + + emit conditionChanged(); +} + +ConnectionEditorStatements::ConditionToken ConditionListModel::toOperatorStatement( + const ConditionToken &token) +{ + if (token.value == "&&") + return ConnectionEditorStatements::ConditionToken::And; + + if (token.value == "||") + return ConnectionEditorStatements::ConditionToken::Or; + + if (token.value == "===") + return ConnectionEditorStatements::ConditionToken::Equals; + + if (token.value == "!==") + return ConnectionEditorStatements::ConditionToken::Not; + + if (token.value == "!==") + return ConnectionEditorStatements::ConditionToken::Not; + + if (token.value == ">") + return ConnectionEditorStatements::ConditionToken::LargerThan; + + if (token.value == ">=") + return ConnectionEditorStatements::ConditionToken::LargerEqualsThan; + + if (token.value == "<") + return ConnectionEditorStatements::ConditionToken::SmallerThan; + + if (token.value == "<=") + return ConnectionEditorStatements::ConditionToken::SmallerEqualsThan; + + return ConnectionEditorStatements::ConditionToken::Unknown; +} + +ConnectionEditorStatements::ComparativeStatement ConditionListModel::toStatement( + const ConditionToken &token) +{ + if (token.type == Variable) { + QStringList list = token.value.split("."); + ConnectionEditorStatements::Variable variable; + + variable.nodeId = list.first(); + variable.propertyName = list.last(); + return variable; + } else if (token.type == Literal) { + return parseTextArgumentComparativeStatement(token.value); + } + + return {}; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h index fc1108d4035..499e0abea8d 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -3,6 +3,11 @@ #pragma once +#include +#include +#include + +#include #include namespace QmlDesigner { @@ -14,10 +19,14 @@ class SignalHandlerProperty; class VariantProperty; class ConnectionView; +class ConnectionModelBackendDelegate; class ConnectionModel : public QStandardItemModel { Q_OBJECT + + Q_PROPERTY(ConnectionModelBackendDelegate *delegate READ delegate CONSTANT) + public: enum ColumnRoles { TargetModelNodeRow = 0, @@ -71,11 +80,217 @@ protected: private: void handleDataChanged(const QModelIndex &topLeft, const QModelIndex& bottomRight); void handleException(); + ConnectionModelBackendDelegate *delegate() const; private: ConnectionView *m_connectionView; bool m_lock = false; QString m_exceptionError; + ConnectionModelBackendDelegate *m_delegate = nullptr; +}; + +class ConditionListModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool valid READ valid NOTIFY validChanged) + Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged) + Q_PROPERTY(QString error READ error NOTIFY errorChanged) + +public: + enum ConditionType { Invalid, Operator, Literal, Variable }; + Q_ENUM(ConditionType) + + struct ConditionToken + { + ConditionType type; + QString value; + }; + + ConditionListModel(ConnectionModel *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + void setup(); + void setCondition(ConnectionEditorStatements::MatchedCondition &condition); + ConnectionEditorStatements::MatchedCondition &condition(); + + static ConditionToken tokenFromConditionToken( + const ConnectionEditorStatements::ConditionToken &token); + + static ConditionToken tokenFromComparativeStatement( + const ConnectionEditorStatements::ComparativeStatement &token); + + Q_INVOKABLE void insertToken(int index, const QString &value); + Q_INVOKABLE void updateToken(int index, const QString &value); + Q_INVOKABLE void appendToken(const QString &value); + Q_INVOKABLE void removeToken(int index); + + bool valid() const; + bool empty() const; + + //for debugging + Q_INVOKABLE void command(const QString &string); + + void setInvalid(const QString &errorMessage); + void setValid(); + + QString error() const; + +signals: + void validChanged(); + void emptyChanged(); + void conditionChanged(); + void errorChanged(); + +private: + void internalSetup(); + ConditionToken valueToToken(const QString &value); + void resetModel(); + int checkOrder() const; + void validateAndRebuildTokens(); + void rebuildTokens(); + + ConnectionEditorStatements::ConditionToken toOperatorStatement(const ConditionToken &token); + ConnectionEditorStatements::ComparativeStatement toStatement(const ConditionToken &token); + + ConnectionModel *m_connectionModel = nullptr; + ConnectionEditorStatements::MatchedCondition &m_condition; + QList m_tokens; + bool m_valid = false; + QString m_errorMessage; +}; + +class ConnectionModelStatementDelegate : public QObject +{ + Q_OBJECT + +public: + explicit ConnectionModelStatementDelegate(ConnectionModel *parent = nullptr); + + enum ActionType { CallFunction, Assign, ChangeState, SetProperty, PrintMessage, Custom }; + + Q_ENUM(ActionType) + + Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) + + Q_PROPERTY(PropertyTreeModelDelegate *function READ function CONSTANT) + Q_PROPERTY(PropertyTreeModelDelegate *lhs READ lhs CONSTANT) + Q_PROPERTY(PropertyTreeModelDelegate *rhsAssignment READ rhsAssignment CONSTANT) + Q_PROPERTY(StudioQmlTextBackend *stringArgument READ stringArgument CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *states READ states CONSTANT) + Q_PROPERTY(StudioQmlComboBoxBackend *stateTargets READ stateTargets CONSTANT) + + void setActionType(ActionType type); + void setup(); + void setStatement(ConnectionEditorStatements::MatchedStatement &statement); + ConnectionEditorStatements::MatchedStatement &statement(); + +signals: + void actionTypeChanged(); + void statementChanged(); + +private: + ActionType actionType() const; + PropertyTreeModelDelegate *signal(); + PropertyTreeModelDelegate *function(); + PropertyTreeModelDelegate *lhs(); + PropertyTreeModelDelegate *rhsAssignment(); + StudioQmlTextBackend *stringArgument(); + StudioQmlComboBoxBackend *stateTargets(); + StudioQmlComboBoxBackend *states(); + + void handleFunctionChanged(); + void handleLhsChanged(); + void handleRhsAssignmentChanged(); + void handleStringArgumentChanged(); + void handleStateChanged(); + void handleStateTargetsChanged(); + + void setupAssignment(); + void setupSetProperty(); + void setupCallFunction(); + void setupChangeState(); + void setupStates(); + void setupPrintMessage(); + QString baseStateName() const; + + ActionType m_actionType; + PropertyTreeModelDelegate m_functionDelegate; + PropertyTreeModelDelegate m_lhsDelegate; + PropertyTreeModelDelegate m_rhsAssignmentDelegate; + ConnectionEditorStatements::MatchedStatement &m_statement; + ConnectionModel *m_model = nullptr; + StudioQmlTextBackend m_stringArgument; + StudioQmlComboBoxBackend m_stateTargets; + StudioQmlComboBoxBackend m_states; +}; + +class ConnectionModelBackendDelegate : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged) + + Q_PROPERTY(ActionType actionType READ actionType NOTIFY actionTypeChanged) + Q_PROPERTY(PropertyTreeModelDelegate *signal READ signal CONSTANT) + Q_PROPERTY(ConnectionModelStatementDelegate *okStatement READ okStatement CONSTANT) + Q_PROPERTY(ConnectionModelStatementDelegate *koStatement READ koStatement CONSTANT) + Q_PROPERTY(ConditionListModel *conditionListModel READ conditionListModel CONSTANT) + Q_PROPERTY(bool hasCondition READ hasCondition NOTIFY hasConditionChanged) + Q_PROPERTY(QString source READ source NOTIFY sourceChanged) + +public: + explicit ConnectionModelBackendDelegate(ConnectionModel *parent = nullptr); + + using ActionType = ConnectionModelStatementDelegate::ActionType; + + Q_INVOKABLE void changeActionType( + QmlDesigner::ConnectionModelStatementDelegate::ActionType actionType); + + Q_INVOKABLE void addCondition(); + Q_INVOKABLE void removeCondition(); + +signals: + void currentRowChanged(); + void actionTypeChanged(); + void hasConditionChanged(); + void sourceChanged(); + +private: + int currentRow() const; + void setCurrentRow(int i); + + void handleException(); + bool hasCondition() const; + void setHasCondition(bool b); + ActionType actionType() const; + PropertyTreeModelDelegate *signal(); + ConnectionModelStatementDelegate *okStatement(); + ConnectionModelStatementDelegate *koStatement(); + ConditionListModel *conditionListModel(); + QString source() const; + void setSource(const QString &source); + void setupCondition(); + void setupHandlerAndStatements(); + + void handleTargetChanged(); + void handleOkStatementChanged(); + void handleConditionChanged(); + + ActionType m_actionType; + QString m_exceptionError; + int m_currentRow = -1; + ConnectionEditorStatements::Handler m_handler; + PropertyTreeModelDelegate m_signalDelegate; + ConnectionModelStatementDelegate m_okStatementDelegate; + ConnectionModelStatementDelegate m_koStatementDelegate; + ConditionListModel m_conditionListModel; + bool m_hasCondition = false; + QString m_source; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index 221400dd0f0..5ee81b86a15 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -8,6 +8,7 @@ #include "bindingmodel.h" #include "connectionmodel.h" #include "dynamicpropertiesmodel.h" +#include "propertytreemodel.h" #include "theme.h" #include @@ -73,6 +74,21 @@ public: {"dynamicPropertiesModel", QVariant::fromValue(m_connectionEditorView->dynamicPropertiesModel())}}); + qmlRegisterType("ConnectionsEditorEditorBackend", + 1, + 0, + "DynamicPropertiesModelBackendDelegate"); + + qmlRegisterType("ConnectionsEditorEditorBackend", + 1, + 0, + "ConnectionModelStatementDelegate"); + + qmlRegisterType("ConnectionsEditorEditorBackend", + 1, + 0, + "ConditionListModel"); + Theme::setupTheme(engine()); setMinimumWidth(195); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index 5997f230ad8..507637b4b91 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -21,6 +21,8 @@ class ConnectionModel; class DynamicPropertiesModel; class BackendModel; class ConnectionViewQuickWidget; +class PropertyTreeModel; +class PropertyListProxyModel; class ConnectionView : public AbstractView { @@ -88,6 +90,8 @@ private: //variables BindingModel *m_bindingModel; DynamicPropertiesModel *m_dynamicPropertiesModel; BackendModel *m_backendModel; + PropertyTreeModel *m_propertyTreeModel; + PropertyListProxyModel *m_propertyListProxyModel; int m_currentIndex = 0; QPointer m_connectionViewQuickWidget; diff --git a/src/plugins/qmldesigner/designercore/include/qmlvisualnode.h b/src/plugins/qmldesigner/designercore/include/qmlvisualnode.h index 69917dbc23b..a299d379bcd 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlvisualnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlvisualnode.h @@ -106,10 +106,10 @@ private: class QMLDESIGNERCORE_EXPORT QmlModelStateGroup { friend class QmlObjectNode; - friend class StatesEditorView; public: QmlModelStateGroup() = default; + QmlModelStateGroup(const ModelNode &modelNode) : m_modelNode(modelNode) {} explicit operator bool() const { return m_modelNode.isValid(); } @@ -120,9 +120,6 @@ public: QmlModelState addState(const QString &name); void removeState(const QString &name); -protected: - QmlModelStateGroup(const ModelNode &modelNode) : m_modelNode(modelNode) {} - private: ModelNode m_modelNode; };