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
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user