forked from qt-creator/qt-creator
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>
This commit is contained in:
committed by
Thomas Hartmann
parent
d47e9772e0
commit
4da6686705
@@ -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
|
} // QmlDesigner namespace
|
||||||
|
@@ -29,6 +29,7 @@
|
|||||||
#include <bindingeditor/actioneditordialog.h>
|
#include <bindingeditor/actioneditordialog.h>
|
||||||
#include <qmldesignercorelib_global.h>
|
#include <qmldesignercorelib_global.h>
|
||||||
#include <modelnode.h>
|
#include <modelnode.h>
|
||||||
|
#include <signalhandlerproperty.h>
|
||||||
|
|
||||||
#include <QtQml>
|
#include <QtQml>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -66,6 +67,10 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void updateWindowName(const QString &targetName = {});
|
Q_INVOKABLE void updateWindowName(const QString &targetName = {});
|
||||||
|
|
||||||
|
static void invokeEditor(SignalHandlerProperty signalHandler,
|
||||||
|
std::function<void(SignalHandlerProperty)> onReject = nullptr,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void accepted();
|
void accepted();
|
||||||
void rejected();
|
void rejected();
|
||||||
|
@@ -34,6 +34,7 @@ namespace ComponentCoreConstants {
|
|||||||
const char rootCategory[] = "";
|
const char rootCategory[] = "";
|
||||||
|
|
||||||
const char selectionCategory[] = "Selection";
|
const char selectionCategory[] = "Selection";
|
||||||
|
const char connectionsCategory[] = "Connections";
|
||||||
const char arrangeCategory[] = "Arrange";
|
const char arrangeCategory[] = "Arrange";
|
||||||
const char qmlPreviewCategory[] = "QmlPreview";
|
const char qmlPreviewCategory[] = "QmlPreview";
|
||||||
const char editCategory[] = "Edit";
|
const char editCategory[] = "Edit";
|
||||||
@@ -98,6 +99,7 @@ const char openSignalDialogCommandId[] = "OpenSignalDialog";
|
|||||||
const char update3DAssetCommandId[] = "Update3DAsset";
|
const char update3DAssetCommandId[] = "Update3DAsset";
|
||||||
|
|
||||||
const char selectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Selection");
|
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 flowConnectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Connect");
|
||||||
const char selectEffectDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select Effect");
|
const char selectEffectDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select Effect");
|
||||||
const char arrangeCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Arrange");
|
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 priorityFirst = 280;
|
||||||
const int prioritySelectionCategory = 220;
|
const int prioritySelectionCategory = 220;
|
||||||
|
const int priorityConnectionsCategory = 210;
|
||||||
const int priorityQmlPreviewCategory = 200;
|
const int priorityQmlPreviewCategory = 200;
|
||||||
const int priorityStackCategory = 180;
|
const int priorityStackCategory = 180;
|
||||||
const int priorityEditCategory = 160;
|
const int priorityEditCategory = 160;
|
||||||
|
@@ -44,6 +44,7 @@
|
|||||||
#include <documentmanager.h>
|
#include <documentmanager.h>
|
||||||
#include <qmldesignerplugin.h>
|
#include <qmldesignerplugin.h>
|
||||||
#include <viewmanager.h>
|
#include <viewmanager.h>
|
||||||
|
#include <actioneditor.h>
|
||||||
|
|
||||||
#include <listmodeleditor/listmodeleditordialog.h>
|
#include <listmodeleditor/listmodeleditordialog.h>
|
||||||
#include <listmodeleditor/listmodeleditormodel.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
|
class DocumentError : public std::exception
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -999,6 +1296,11 @@ void DesignerActionManager::createDefaultDesignerActions()
|
|||||||
selectionCategory,
|
selectionCategory,
|
||||||
prioritySelectionCategory));
|
prioritySelectionCategory));
|
||||||
|
|
||||||
|
addDesignerAction(new ConnectionsModelNodeActionGroup(
|
||||||
|
connectionsCategoryDisplayName,
|
||||||
|
connectionsCategory,
|
||||||
|
priorityConnectionsCategory));
|
||||||
|
|
||||||
addDesignerAction(new ActionGroup(
|
addDesignerAction(new ActionGroup(
|
||||||
arrangeCategoryDisplayName,
|
arrangeCategoryDisplayName,
|
||||||
arrangeCategory,
|
arrangeCategory,
|
||||||
|
Reference in New Issue
Block a user