From 4da66867051b27354b71ff6b4690d4e2d1e53bd6 Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Mon, 19 Sep 2022 18:25:36 +0200 Subject: QmlDesigner: Add Connections Shortcuts Task-number: QDS-7641 Change-Id: I1cb8f10cb675cee7dd48481cb31e4807fc592dc3 Reviewed-by: Reviewed-by: Thomas Hartmann --- .../components/bindingeditor/actioneditor.cpp | 64 +++++ .../components/bindingeditor/actioneditor.h | 5 + .../componentcore/componentcore_constants.h | 3 + .../componentcore/designeractionmanager.cpp | 302 +++++++++++++++++++++ 4 files changed, 374 insertions(+) diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp index 21d7b37e9f..60f703bdf9 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp @@ -304,4 +304,68 @@ void ActionEditor::updateWindowName(const QString &targetName) } } +void ActionEditor::invokeEditor(SignalHandlerProperty signalHandler, + std::function onReject, + QObject * parent) +{ + if (!signalHandler.isValid()) + return; + + ModelNode connectionNode = signalHandler.parentModelNode(); + if (!connectionNode.isValid()) + return; + + if (!connectionNode.bindingProperty("target").isValid()) + return; + + ModelNode targetNode = connectionNode.bindingProperty("target").resolveToModelNode(); + if (!targetNode.isValid()) + return; + + const QString source = signalHandler.source(); + + QPointer editor = new ActionEditor(parent); + + editor->showWidget(); + editor->setModelNode(connectionNode); + editor->setConnectionValue(source); + editor->prepareConnections(); + editor->updateWindowName(targetNode.validId() + "." + signalHandler.name()); + + QObject::connect(editor, &ActionEditor::accepted, [=]() { + if (!editor) + return; + if (editor->m_modelNode.isValid()) { + editor->m_modelNode.view()->executeInTransaction("ActionEditor::" + "invokeEditorAccepted", + [=]() { + editor->m_modelNode + .signalHandlerProperty( + signalHandler.name()) + .setSource( + editor->connectionValue()); + }); + } + + //closing editor widget somewhy triggers rejected() signal. Lets disconect before it affects us: + editor->disconnect(); + editor->deleteLater(); + }); + + QObject::connect(editor, &ActionEditor::rejected, [=]() { + if (!editor) + return; + + if (onReject) { + editor->m_modelNode.view()->executeInTransaction("ActionEditor::" + "invokeEditorOnRejectFunc", + [=]() { onReject(signalHandler); }); + } + + //closing editor widget somewhy triggers rejected() signal 2nd time. Lets disconect before it affects us: + editor->disconnect(); + editor->deleteLater(); + }); +} + } // QmlDesigner namespace diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h index 09597bc8d1..1a9e2c8754 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -66,6 +67,10 @@ public: Q_INVOKABLE void updateWindowName(const QString &targetName = {}); + static void invokeEditor(SignalHandlerProperty signalHandler, + std::function onReject = nullptr, + QObject *parent = nullptr); + signals: void accepted(); void rejected(); diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index ef2e59397b..884a4f4413 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -34,6 +34,7 @@ namespace ComponentCoreConstants { const char rootCategory[] = ""; const char selectionCategory[] = "Selection"; +const char connectionsCategory[] = "Connections"; const char arrangeCategory[] = "Arrange"; const char qmlPreviewCategory[] = "QmlPreview"; const char editCategory[] = "Edit"; @@ -98,6 +99,7 @@ const char openSignalDialogCommandId[] = "OpenSignalDialog"; const char update3DAssetCommandId[] = "Update3DAsset"; const char selectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Selection"); +const char connectionsCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Connections"); const char flowConnectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Connect"); const char selectEffectDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select Effect"); const char arrangeCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Arrange"); @@ -205,6 +207,7 @@ const char editListModelDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMen const int priorityFirst = 280; const int prioritySelectionCategory = 220; +const int priorityConnectionsCategory = 210; const int priorityQmlPreviewCategory = 200; const int priorityStackCategory = 180; const int priorityEditCategory = 160; diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index f569546fbe..71451396be 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -452,6 +453,302 @@ public: } }; +QString prependSignal(QString signalHandlerName) +{ + if (signalHandlerName.isNull() || signalHandlerName.isEmpty()) + return {}; + + QChar firstChar = signalHandlerName.at(0).toUpper(); + signalHandlerName[0] = firstChar; + signalHandlerName.prepend(QLatin1String("on")); + + return signalHandlerName; +} + +QStringList getSignalsList(const ModelNode &node) +{ + if (!node.isValid()) + return {}; + + if (!node.hasMetaInfo()) + return {}; + + QStringList signalsList; + NodeMetaInfo nodeMetaInfo = node.metaInfo(); + + for (const auto &signalName : nodeMetaInfo.signalNames()) { + signalsList << QString::fromUtf8(signalName); + } + + //on...Changed are the most regular signals, we assign them the lowest priority, + //we don't need them right now +// QStringList signalsWithChanged = signalsList.filter("Changed"); + + //these are item specific, like MouseArea.clicked, they have higher priority + QStringList signalsWithoutChanged = signalsList; + signalsWithoutChanged.removeIf([](QString str) { + if (str.endsWith("Changed")) + return true; + return false; + }); + + QStringList finalResult; + finalResult.append(signalsWithoutChanged); + + + if (finalResult.isEmpty()) + finalResult = signalsList; + + finalResult.removeDuplicates(); + + return finalResult; +} + +struct SlotEntry +{ + QString category; + QString name; + std::function action; +}; + +QList getSlotsLists(const ModelNode &node) +{ + if (!node.isValid()) + return {}; + + if (!node.view()->rootModelNode().isValid()) + return {}; + + QList resultList; + + ModelNode rootNode = node.view()->rootModelNode(); + QmlObjectNode rootObjectNode(rootNode); + + const QString stateCategory = "Change State"; + + //For now we are using category as part of the state name + //We should change it, once we extend number of categories + const SlotEntry defaultState = {stateCategory, + (stateCategory + " to " + "Default State"), + [rootNode](SignalHandlerProperty signalHandler) { + signalHandler.setSource( + QString("%1.state = \"\"").arg(rootNode.id())); + }}; + resultList.push_back(defaultState); + + for (const auto &stateName : rootObjectNode.states().names()) { + SlotEntry entry = {stateCategory, + (stateCategory + " to " + stateName), + [rootNode, stateName](SignalHandlerProperty signalHandler) { + signalHandler.setSource( + QString("%1.state = \"%2\"").arg(rootNode.id(), stateName)); + }}; + + resultList.push_back(entry); + } + + return resultList; +} + +//creates connection without signalHandlerProperty +ModelNode createNewConnection(ModelNode targetNode) +{ + NodeMetaInfo connectionsMetaInfo = targetNode.view()->model()->metaInfo("QtQuick.Connections"); + ModelNode newConnectionNode = targetNode.view() + ->createModelNode("QtQuick.Connections", + connectionsMetaInfo.majorVersion(), + connectionsMetaInfo.minorVersion()); + if (QmlItemNode::isValidQmlItemNode(targetNode)) + targetNode.nodeAbstractProperty("data").reparentHere(newConnectionNode); + + newConnectionNode.bindingProperty("target").setExpression(targetNode.id()); + + return newConnectionNode; +} + +void removeSignal(SignalHandlerProperty signalHandler) +{ + auto connectionNode = signalHandler.parentModelNode(); + auto connectionSignals = connectionNode.signalProperties(); + if (connectionSignals.size() > 1) { + if (connectionSignals.contains(signalHandler)) + connectionNode.removeProperty(signalHandler.name()); + } else { + connectionNode.destroy(); + } +} + +class ConnectionsModelNodeActionGroup : public ActionGroup +{ +public: + ConnectionsModelNodeActionGroup(const QString &displayName, + const QByteArray &menuId, + int priority) + : ActionGroup(displayName, + menuId, + priority, + &SelectionContextFunctors::always, + &SelectionContextFunctors::selectionEnabled) + {} + + void updateContext() override + { + menu()->clear(); + + const auto selection = selectionContext(); + if (!selection.isValid()) + return; + if (!selection.singleNodeIsSelected()) + return; + if (!action()->isEnabled()) + return; + + ModelNode currentNode = selection.currentSingleSelectedNode(); + QmlObjectNode currentObjectNode(currentNode); + + QStringList signalsList = getSignalsList(currentNode); + QList slotsList = getSlotsLists(currentNode); + currentNode.validId(); + + for (const ModelNode &connectionNode : currentObjectNode.getAllConnections()) { + for (const AbstractProperty &property : connectionNode.properties()) { + if (property.isSignalHandlerProperty() && property.name() != "target") { + const auto signalHandler = property.toSignalHandlerProperty(); + + const QString propertyName = QString::fromUtf8(signalHandler.name()); + + QMenu *activeSignalHandlerGroup = new QMenu(propertyName, menu()); + + QMenu *editSignalGroup = new QMenu("Change Signal", menu()); + + for (const auto &signalStr : signalsList) { + if (prependSignal(signalStr).toUtf8() == signalHandler.name()) + continue; + + ActionTemplate *newSignalAction = new ActionTemplate( + (signalStr + "Id").toLatin1(), + signalStr, + [signalStr, signalHandler](const SelectionContext &) { + signalHandler.parentModelNode().view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "changeSignal", + [signalStr, signalHandler]() { + auto connectionNode = signalHandler.parentModelNode(); + auto newHandler = connectionNode.signalHandlerProperty( + prependSignal(signalStr).toLatin1()); + newHandler.setSource(signalHandler.source()); + connectionNode.removeProperty(signalHandler.name()); + }); + }); + editSignalGroup->addAction(newSignalAction); + } + + activeSignalHandlerGroup->addMenu(editSignalGroup); + + if (!slotsList.isEmpty()) { + QMenu *editSlotGroup = new QMenu("Change Slot", menu()); + + for (const auto &slot : slotsList) { + ActionTemplate *newSlotAction = new ActionTemplate( + (slot.name + "Id").toLatin1(), + slot.name, + [slot, signalHandler](const SelectionContext &) { + signalHandler.parentModelNode() + .view() + ->executeInTransaction("ConnectionsModelNodeActionGroup::" + "changeSlot", + [slot, signalHandler]() { + slot.action(signalHandler); + }); + }); + editSlotGroup->addAction(newSlotAction); + } + activeSignalHandlerGroup->addMenu(editSlotGroup); + } + + ActionTemplate *openEditorAction = new ActionTemplate( + (propertyName + "OpenEditorId").toLatin1(), + QString( + QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")), + [=](const SelectionContext &) { + signalHandler.parentModelNode().view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "openConnectionsEditor", + [signalHandler]() { ActionEditor::invokeEditor(signalHandler); }); + }); + + activeSignalHandlerGroup->addAction(openEditorAction); + + ActionTemplate *removeSignalHandlerAction = new ActionTemplate( + (propertyName + "RemoveSignalHandlerId").toLatin1(), + QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Remove this handler")), + [signalHandler](const SelectionContext &) { + signalHandler.parentModelNode().view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "removeSignalHandler", + [signalHandler]() { + removeSignal(signalHandler); + }); + }); + + activeSignalHandlerGroup->addAction(removeSignalHandlerAction); + + menu()->addMenu(activeSignalHandlerGroup); + } + } + } + + //singular add connection: + QMenu *addConnection = new QMenu(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", + "Add signal handler")), + menu()); + + for (const auto &signalStr : signalsList) { + QMenu *newSignal = new QMenu(signalStr, addConnection); + + for (const auto &slot : slotsList) { + ActionTemplate *newSlot = new ActionTemplate( + QString(signalStr + slot.name + "Id").toLatin1(), + slot.name, + [=](const SelectionContext &) { + currentNode.view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::addConnection", [=]() { + ModelNode newConnectionNode = createNewConnection(currentNode); + slot.action(newConnectionNode.signalHandlerProperty( + prependSignal(signalStr).toLatin1())); + }); + }); + newSignal->addAction(newSlot); + } + + ActionTemplate *openEditorAction = new ActionTemplate( + (signalStr + "OpenEditorId").toLatin1(), + QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Connections Editor")), + [=](const SelectionContext &) { + currentNode.view()->executeInTransaction( + "ConnectionsModelNodeActionGroup::" + "openConnectionsEditor", + [=]() { + ModelNode newConnectionNode = createNewConnection(currentNode); + + SignalHandlerProperty newHandler + = newConnectionNode.signalHandlerProperty( + prependSignal(signalStr).toLatin1()); + + newHandler.setSource( + QString("console.log(\"%1.%2\")").arg(currentNode.id(), signalStr)); + ActionEditor::invokeEditor(newHandler, removeSignal); + }); + }); + newSignal->addAction(openEditorAction); + + addConnection->addMenu(newSignal); + } + + menu()->addMenu(addConnection); + } +}; + class DocumentError : public std::exception { public: @@ -999,6 +1296,11 @@ void DesignerActionManager::createDefaultDesignerActions() selectionCategory, prioritySelectionCategory)); + addDesignerAction(new ConnectionsModelNodeActionGroup( + connectionsCategoryDisplayName, + connectionsCategory, + priorityConnectionsCategory)); + addDesignerAction(new ActionGroup( arrangeCategoryDisplayName, arrangeCategory, -- cgit v1.2.3