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:
Aleksei German
2022-09-19 18:25:36 +02:00
committed by Thomas Hartmann
parent d47e9772e0
commit 4da6686705
4 changed files with 374 additions and 0 deletions

View File

@@ -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

View File

@@ -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();

View File

@@ -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;

View File

@@ -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,