aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksei German <aleksei.german@qt.io>2022-09-19 18:25:36 +0200
committerThomas Hartmann <thomas.hartmann@qt.io>2022-09-27 07:02:16 +0000
commit4da66867051b27354b71ff6b4690d4e2d1e53bd6 (patch)
treea4d59644b82dd326bbbad19057ae5af922362099
parentd47e9772e0e9c807e2b76e91df241bd2fdcd7ea8 (diff)
QmlDesigner: Add Connections Shortcuts
Task-number: QDS-7641 Change-Id: I1cb8f10cb675cee7dd48481cb31e4807fc592dc3 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
-rw-r--r--src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp64
-rw-r--r--src/plugins/qmldesigner/components/bindingeditor/actioneditor.h5
-rw-r--r--src/plugins/qmldesigner/components/componentcore/componentcore_constants.h3
-rw-r--r--src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp302
4 files changed, 374 insertions, 0 deletions
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<void(SignalHandlerProperty)> 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<ActionEditor> 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 <bindingeditor/actioneditordialog.h>
#include <qmldesignercorelib_global.h>
#include <modelnode.h>
+#include <signalhandlerproperty.h>
#include <QtQml>
#include <QObject>
@@ -66,6 +67,10 @@ public:
Q_INVOKABLE void updateWindowName(const QString &targetName = {});
+ static void invokeEditor(SignalHandlerProperty signalHandler,
+ std::function<void(SignalHandlerProperty)> 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 <documentmanager.h>
#include <qmldesignerplugin.h>
#include <viewmanager.h>
+#include <actioneditor.h>
#include <listmodeleditor/listmodeleditordialog.h>
#include <listmodeleditor/listmodeleditormodel.h>
@@ -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<void(SignalHandlerProperty)> action;
+};
+
+QList<SlotEntry> getSlotsLists(const ModelNode &node)
+{
+ if (!node.isValid())
+ return {};
+
+ if (!node.view()->rootModelNode().isValid())
+ return {};
+
+ QList<SlotEntry> 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<SlotEntry> 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,