PropertyEditor: Forward currentNodes to backend

Since PropertyEditor has introduced `selection locking feature`, it
should keep its own selection for the cases that the change should
affect multiple nodes.
It means that model selected node might be different than the
propertyEditor working nodes.
Also model selection changes should be notified separately.

Change-Id: I692414811369680e16b3e25213bfa4b683576c55
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Ali Kianian
2025-03-14 12:16:33 +02:00
parent ba8303b86b
commit fa6325381b
14 changed files with 192 additions and 151 deletions

View File

@@ -23,6 +23,7 @@ Rectangle {
style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.apply_medium
tooltip: qsTr("Apply material to selected model.")
enabled: has3DModelSelected
onClicked: root.backend.toolBarAction(QmlMaterialNodeProxy.ApplyToSelected)
}

View File

@@ -164,7 +164,7 @@ Section {
id: toolTipArea
enabled: !modelNodeBackend.multiSelection && anchorBackend.hasParent
anchors.fill: parent
onClicked: toogleExportAlias()
onClicked: toggleExportAlias()
tooltip: qsTr("Exports this component as an alias property of the root component.")
}
}

View File

@@ -222,7 +222,7 @@ void MaterialEditorQmlBackend::setup(const QmlObjectNode &selectedMaterialNode,
}
// model node
m_backendModelNode.setup(selectedMaterialNode.modelNode());
m_backendModelNode.setup(selectedMaterialNode);
context()->setContextProperty("modelNodeBackend", &m_backendModelNode);
context()->setContextProperty("hasMaterial", QVariant(true));

View File

@@ -138,7 +138,7 @@ QStringList PropertyEditorContextObject::autoComplete(const QString &text, int p
return {};
}
void PropertyEditorContextObject::toogleExportAlias()
void PropertyEditorContextObject::toggleExportAlias()
{
QTC_ASSERT(m_model && m_model->rewriterView(), return);
@@ -156,11 +156,11 @@ void PropertyEditorContextObject::toogleExportAlias()
PropertyName modelNodeId = selectedNode.id().toUtf8();
ModelNode rootModelNode = rewriterView->rootModelNode();
rewriterView->executeInTransaction("PropertyEditorContextObject:toogleExportAlias", [&objectNode, &rootModelNode, modelNodeId](){
rewriterView->executeInTransaction("PropertyEditorContextObject:toggleExportAlias",
[&objectNode, &rootModelNode, modelNodeId]() {
if (!objectNode.isAliasExported())
objectNode.ensureAliasExport();
else
if (rootModelNode.hasProperty(modelNodeId))
else if (rootModelNode.hasProperty(modelNodeId))
rootModelNode.removeProperty(modelNodeId);
});
}
@@ -294,7 +294,7 @@ void PropertyEditorContextObject::changeTypeName(const QString &typeName)
try {
auto transaction = RewriterTransaction(rewriterView, "PropertyEditorContextObject:changeTypeName");
ModelNodes selectedNodes = rewriterView->selectedModelNodes(); // TODO: replace it by PropertyEditorView::currentNodes()
ModelNodes selectedNodes = m_editorNodes;
for (ModelNode &selectedNode : selectedNodes)
changeNodeTypeName(selectedNode);
@@ -489,9 +489,9 @@ bool PropertyEditorContextObject::hasQuick3DImport() const
return m_hasQuick3DImport;
}
void PropertyEditorContextObject::setSelectedNode(const ModelNode &node)
void PropertyEditorContextObject::setEditorNodes(const ModelNodes &nodes)
{
m_selectedNode = node;
m_editorNodes = nodes;
}
void PropertyEditorContextObject::setHasQuick3DImport(bool value)
@@ -531,18 +531,18 @@ void PropertyEditorContextObject::setIsQt6Project(bool value)
emit isQt6ProjectChanged();
}
bool PropertyEditorContextObject::has3DModelSelection() const
bool PropertyEditorContextObject::has3DModelSelected() const
{
return m_has3DModelSelection;
return m_has3DModelSelected;
}
void PropertyEditorContextObject::set3DHasModelSelection(bool value)
void PropertyEditorContextObject::setHas3DModelSelected(bool value)
{
if (value == m_has3DModelSelection)
if (value == m_has3DModelSelected)
return;
m_has3DModelSelection = value;
emit has3DModelSelectionChanged();
m_has3DModelSelected = value;
emit has3DModelSelectedChanged();
}
void PropertyEditorContextObject::setSpecificsUrl(const QUrl &newSpecificsUrl)

View File

@@ -57,7 +57,7 @@ class PropertyEditorContextObject : public QObject
Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport NOTIFY hasQuick3DImportChanged)
Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary NOTIFY hasMaterialLibraryChanged)
Q_PROPERTY(bool isQt6Project READ isQt6Project NOTIFY isQt6ProjectChanged)
Q_PROPERTY(bool has3DModelSelection READ has3DModelSelection NOTIFY has3DModelSelectionChanged)
Q_PROPERTY(bool has3DModelSelected READ has3DModelSelected NOTIFY has3DModelSelectedChanged)
public:
PropertyEditorContextObject(QQuickWidget *widget, QObject *parent = nullptr);
@@ -78,7 +78,7 @@ public:
Q_INVOKABLE QStringList autoComplete(const QString &text, int pos, bool explicitComplete, bool filter);
Q_INVOKABLE void toogleExportAlias();
Q_INVOKABLE void toggleExportAlias();
Q_INVOKABLE void goIntoComponent();
@@ -142,10 +142,10 @@ public:
bool isQt6Project() const;
void setIsQt6Project(bool value);
bool has3DModelSelection() const;
void set3DHasModelSelection(bool value);
bool has3DModelSelected() const;
void setHas3DModelSelected(bool value);
void setSelectedNode(const ModelNode &node);
void setEditorNodes(const ModelNodes &nodes);
void setIsSelectionLocked(bool lock);
bool isSelectionLocked() const;
@@ -169,7 +169,7 @@ signals:
void hasMultiSelectionChanged();
void hasQuick3DImportChanged();
void hasMaterialLibraryChanged();
void has3DModelSelectionChanged();
void has3DModelSelectedChanged();
void isQt6ProjectChanged();
void isSelectionLockedChanged();
@@ -217,7 +217,7 @@ private:
bool m_hasQuick3DImport = false;
bool m_hasMaterialLibrary = false;
bool m_has3DModelSelection = false;
bool m_has3DModelSelected = false;
bool m_isQt6Project = false;
QQmlComponent *m_qmlComponent;
@@ -240,7 +240,7 @@ private:
bool m_insightEnabled = false;
QStringList m_insightCategories;
ModelNode m_selectedNode;
ModelNodes m_editorNodes; // Nodes that are being edited by PropertyEditor
};
class EasingCurveEditor : public QObject

View File

@@ -375,6 +375,12 @@ void PropertyEditorQmlBackend::handleModelNodePreviewPixmapChanged(const ModelNo
refreshPreview();
}
void PropertyEditorQmlBackend::handleModelSelectedNodesChanged(PropertyEditorView *propertyEditor)
{
contextObject()->setHas3DModelSelected(!Utils3D::getSelectedModels(propertyEditor).isEmpty());
m_backendTextureNode.updateSelectionDetails();
}
void PropertyEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode,
PropertyNameView name,
const QVariant &value,
@@ -566,9 +572,17 @@ void PropertyEditorQmlBackend::updateInstanceImage()
refreshPreview();
}
void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor)
void PropertyEditorQmlBackend::setup(const ModelNodes &editorNodes,
const QString &stateName,
const QUrl &qmlSpecificsFile,
PropertyEditorView *propertyEditor)
{
if (qmlObjectNode.isValid()) {
QmlObjectNode qmlObjectNode(editorNodes.isEmpty() ? ModelNode{} : editorNodes.first());
if (!qmlObjectNode.isValid()) {
qWarning() << "PropertyEditor: invalid node for setup";
return;
}
m_contextObject->setModel(propertyEditor->model());
qCInfo(propertyEditorBenchmark) << Q_FUNC_INFO;
@@ -583,10 +597,10 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
setupAuxiliaryProperties(qmlObjectNode, propertyEditor);
// model node
m_backendModelNode.setup(qmlObjectNode.modelNode());
m_backendModelNode.setup(editorNodes);
context()->setContextProperty("modelNodeBackend", &m_backendModelNode);
m_backendMaterialNode.setup(qmlObjectNode);
m_backendMaterialNode.setup(editorNodes);
m_backendTextureNode.setup(qmlObjectNode);
insertValue(Constants::PROPERTY_EDITOR_CLASSNAME_PROPERTY,
@@ -602,8 +616,7 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
m_backendAnchorBinding.setup(qmlObjectNode.modelNode());
setupContextProperties();
contextObject()->setHasMultiSelection(
!qmlObjectNode.view()->singleSelectedModelNode().isValid());
contextObject()->setHasMultiSelection(m_backendModelNode.multiSelection());
qCInfo(propertyEditorBenchmark) << "anchors:" << time.elapsed();
@@ -614,8 +627,6 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
qCInfo(propertyEditorBenchmark) << "specifics:" << time.elapsed();
contextObject()->setStateName(stateName);
if (!qmlObjectNode.isValid())
return;
context()->setContextProperty(QLatin1String("propertyCount"),
QVariant(qmlObjectNode.modelNode().properties().size()));
@@ -632,8 +643,6 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
contextObject()->setSelectionChanged(false);
contextObject()->setSelectionChanged(false);
NodeMetaInfo metaInfo = qmlObjectNode.modelNode().metaInfo();
#ifdef QDS_USE_PROJECTSTORAGE
@@ -657,17 +666,13 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
contextObject()->setHasMaterialLibrary(Utils3D::materialLibraryNode(propertyEditor).isValid());
contextObject()->setIsQt6Project(propertyEditor->externalDependencies().isQt6Project());
contextObject()->set3DHasModelSelection(!Utils3D::getSelectedModels(propertyEditor).isEmpty());
contextObject()->setSelectedNode(qmlObjectNode);
contextObject()->setEditorNodes(editorNodes);
contextObject()->setHasQuick3DImport(propertyEditor->model()->hasImport("QtQuick3D"));
m_view->instanceImageProvider()->setModelNode(propertyEditor->firstSelectedModelNode());
m_view->instanceImageProvider()->setModelNode(m_backendModelNode.singleSelectedNode());
updateInstanceImage();
qCInfo(propertyEditorBenchmark) << "final:" << time.elapsed();
} else {
qWarning() << "PropertyEditor: invalid node for setup";
}
}
QString PropertyEditorQmlBackend::propertyEditorResourcesPath()
@@ -960,35 +965,6 @@ QString PropertyEditorQmlBackend::resourcesPath(const QString &dir)
return Core::ICore::resourcePath("qmldesigner/" + dir).toUrlishString();
}
static NodeMetaInfo findCommonSuperClass(const NodeMetaInfo &first, const NodeMetaInfo &second)
{
auto commonBase = first.commonBase(second);
return commonBase.isValid() ? commonBase : first;
}
NodeMetaInfo PropertyEditorQmlBackend::findCommonAncestor(const ModelNode &node)
{
if (!node.isValid())
return node.metaInfo();
AbstractView *view = node.view();
const QList<ModelNode> &selectedNodes = view->selectedModelNodes();
if (selectedNodes.size() > 1) {
NodeMetaInfo commonClass = node.metaInfo();
for (const ModelNode &selectedNode : selectedNodes) {
const NodeMetaInfo &nodeMetaInfo = selectedNode.metaInfo();
if (nodeMetaInfo.isValid() && !nodeMetaInfo.isBasedOn(commonClass))
commonClass = findCommonSuperClass(nodeMetaInfo, commonClass);
}
return commonClass;
}
return node.metaInfo();
}
void PropertyEditorQmlBackend::refreshBackendModel()
{
m_backendModelNode.refresh();
@@ -1064,7 +1040,6 @@ bool PropertyEditorQmlBackend::checkIfUrlExists(const QUrl &url)
void PropertyEditorQmlBackend::emitSelectionToBeChanged()
{
m_backendModelNode.emitSelectionToBeChanged();
m_backendTextureNode.updateSelectionDetails();
}
void PropertyEditorQmlBackend::emitSelectionChanged()

View File

@@ -40,7 +40,10 @@ public:
class AsynchronousImageCache &imageCache);
~PropertyEditorQmlBackend();
void setup(const QmlObjectNode &fxObjectNode, const QString &stateName, const QUrl &qmlSpecificsFile, PropertyEditorView *propertyEditor);
void setup(const ModelNodes &editorNodes,
const QString &stateName,
const QUrl &qmlSpecificsFile,
PropertyEditorView *propertyEditor);
void setValue(const QmlObjectNode &fxObjectNode, PropertyNameView name, const QVariant &value);
void setExpression(PropertyNameView propName, const QString &exp);
@@ -94,8 +97,7 @@ public:
void handleModelNodePreviewPixmapChanged(const ModelNode &node,
const QPixmap &pixmap,
const QByteArray &requestId);
static NodeMetaInfo findCommonAncestor(const ModelNode &node);
void handleModelSelectedNodesChanged(PropertyEditorView *propertyEditor);
void refreshBackendModel();
void refreshPreview();

View File

@@ -61,6 +61,13 @@ static bool propertyIsAttachedInsightProperty(PropertyNameView propertyName)
return propertyName.contains("InsightCategory.");
}
static NodeMetaInfo findCommonSuperClass(const NodeMetaInfo &first, const NodeMetaInfo &second)
{
auto commonBase = first.commonBase(second);
return commonBase.isValid() ? commonBase : first;
}
PropertyEditorView::PropertyEditorView(AsynchronousImageCache &imageCache,
ExternalDependenciesInterface &externalDependencies)
: AbstractView(externalDependencies)
@@ -428,6 +435,26 @@ PropertyEditorView *PropertyEditorView::instance()
return s_instance;
}
NodeMetaInfo PropertyEditorView::findCommonAncestor(const ModelNode &node)
{
if (!node.isValid())
return node.metaInfo();
const QList<ModelNode> allNodes = currentNodes();
if (allNodes.size() > 1) {
NodeMetaInfo commonClass = node.metaInfo();
for (const ModelNode &selectedNode : allNodes) {
const NodeMetaInfo &nodeMetaInfo = selectedNode.metaInfo();
if (nodeMetaInfo.isValid() && !nodeMetaInfo.isBasedOn(commonClass))
commonClass = findCommonSuperClass(nodeMetaInfo, commonClass);
}
return commonClass;
}
return node.metaInfo();
}
void PropertyEditorView::updateSize()
{
if (!m_qmlBackEndForCurrentType)
@@ -542,7 +569,7 @@ PropertyEditorQmlBackend *getQmlBackend(QHash<QString, PropertyEditorQmlBackend
}
void setupCurrentQmlBackend(PropertyEditorQmlBackend *currentQmlBackend,
const ModelNode &selectedNode,
const ModelNodes &editorNodes,
const QUrl &qmlSpecificsFile,
const QmlModelState &currentState,
PropertyEditorView *propertyEditorView,
@@ -551,10 +578,9 @@ void setupCurrentQmlBackend(PropertyEditorQmlBackend *currentQmlBackend,
QString currentStateName = currentState.isBaseState() ? QStringLiteral("invalid state")
: currentState.name();
QmlObjectNode qmlObjectNode{selectedNode};
if (specificQmlData.isEmpty())
currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData);
currentQmlBackend->setup(qmlObjectNode, currentStateName, qmlSpecificsFile, propertyEditorView);
currentQmlBackend->setup(editorNodes, currentStateName, qmlSpecificsFile, propertyEditorView);
currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData);
}
@@ -634,7 +660,7 @@ void PropertyEditorView::handleToolBarAction(int action)
void PropertyEditorView::setupQmlBackend()
{
#ifdef QDS_USE_PROJECTSTORAGE
const NodeMetaInfo commonAncestor = PropertyEditorQmlBackend::findCommonAncestor(activeNode());
const NodeMetaInfo commonAncestor = findCommonAncestor(activeNode());
auto selfAndPrototypes = commonAncestor.selfAndPrototypes();
bool isEditableComponent = activeNode().isComponent()
&& !QmlItemNode(activeNode()).isEffectItem();
@@ -648,7 +674,7 @@ void PropertyEditorView::setupQmlBackend()
m_stackedWidget,
this);
setupCurrentQmlBackend(currentQmlBackend,
activeNode(),
currentNodes(),
QUrl::fromLocalFile(QString{specificsPath}),
currentStateNode(),
this,
@@ -660,7 +686,7 @@ void PropertyEditorView::setupQmlBackend()
setupInsight(rootModelNode(), currentQmlBackend);
#else
const NodeMetaInfo commonAncestor = PropertyEditorQmlBackend::findCommonAncestor(activeNode());
const NodeMetaInfo commonAncestor = findCommonAncestor(activeNode());
// qmlFileUrl is panel url. and specifics is its metainfo
const auto [qmlFileUrl, specificsClassMetaInfo] = PropertyEditorQmlBackend::getQmlUrlForMetaInfo(
@@ -681,7 +707,7 @@ void PropertyEditorView::setupQmlBackend()
this);
setupCurrentQmlBackend(currentQmlBackend,
activeNode(),
currentNodes(),
qmlSpecificsFile,
currentStateNode(),
this,
@@ -788,10 +814,12 @@ QList<ModelNode> PropertyEditorView::currentNodes() const
void PropertyEditorView::selectedNodesChanged(const QList<ModelNode> &,
const QList<ModelNode> &)
{
if (m_isSelectionLocked)
return;
if (!m_isSelectionLocked)
select();
// Notify model selection changes to backend regardless of being locked
if (m_qmlBackEndForCurrentType)
m_qmlBackEndForCurrentType->handleModelSelectedNodesChanged(this);
}
bool PropertyEditorView::isNodeOrChildSelected(const ModelNode &node) const

View File

@@ -143,6 +143,8 @@ private: //functions
static PropertyEditorView *instance();
NodeMetaInfo findCommonAncestor(const ModelNode &node);
private: //variables
AsynchronousImageCache &m_imageCache;
ModelNode m_activeNode;

View File

@@ -41,11 +41,13 @@ QmlMaterialNodeProxy::QmlMaterialNodeProxy()
QmlMaterialNodeProxy::~QmlMaterialNodeProxy() = default;
void QmlMaterialNodeProxy::setup(const QmlObjectNode &objectNode)
void QmlMaterialNodeProxy::setup(const ModelNodes &editorNodes)
{
QmlObjectNode objectNode = editorNodes.isEmpty() ? ModelNode{} : editorNodes.first();
const QmlObjectNode material = objectNode.metaInfo().isQtQuick3DMaterial() ? objectNode
: QmlObjectNode{};
setMaterialNode(material);
setEditorNodes(editorNodes);
updatePossibleTypes();
updatePreviewModel();
}
@@ -55,6 +57,11 @@ ModelNode QmlMaterialNodeProxy::materialNode() const
return m_materialNode;
}
ModelNodes QmlMaterialNodeProxy::editorNodes() const
{
return m_editorNodes;
}
void QmlMaterialNodeProxy::setPossibleTypes(const QStringList &types)
{
if (types == m_possibleTypes)
@@ -81,7 +88,7 @@ void QmlMaterialNodeProxy::updatePossibleTypes()
}
const QString &matType = materialNode().simplifiedTypeName();
const ModelNodes selectedNodes = materialView()->selectedModelNodes(); // TODO: replace it by PropertyEditorView::currentNodes()
const ModelNodes selectedNodes = editorNodes();
bool allAreBasic = Utils::allOf(selectedNodes, [&](const ModelNode &node) {
return basicTypes.contains(node.simplifiedTypeName());
});
@@ -275,6 +282,11 @@ void QmlMaterialNodeProxy::setMaterialNode(const QmlObjectNode &material)
emit materialNodeChanged();
}
void QmlMaterialNodeProxy::setEditorNodes(const ModelNodes &editorNodes)
{
m_editorNodes = editorNodes;
}
bool QmlMaterialNodeProxy::hasQuick3DImport() const
{
return materialNode().isValid() && materialNode().model()->hasImport("QtQuick3D"_L1);

View File

@@ -35,11 +35,12 @@ public:
explicit QmlMaterialNodeProxy();
~QmlMaterialNodeProxy() override;
void setup(const QmlObjectNode &objectNode);
void setup(const ModelNodes &editorNodes);
QStringList possibleTypes() const { return m_possibleTypes; }
ModelNode materialNode() const;
ModelNodes editorNodes() const;
int possibleTypeIndex() const { return m_possibleTypeIndex; }
@@ -67,15 +68,15 @@ private: // Methods
void updatePossibleTypeIndex();
void updatePreviewModel();
void setMaterialNode(const QmlObjectNode &material);
void setEditorNodes(const ModelNodes &editorNodes);
bool hasQuick3DImport() const;
AbstractView *materialView() const;
private:
bool m_has3DModelSelection = false;
QmlObjectNode m_materialNode;
ModelNodes m_editorNodes;
QStringList m_possibleTypes;
int m_possibleTypeIndex = -1;

View File

@@ -22,9 +22,15 @@ QmlModelNodeProxy::QmlModelNodeProxy(QObject *parent) :
{
}
void QmlModelNodeProxy::setup(const QmlObjectNode &objectNode)
void QmlModelNodeProxy::setup(const ModelNode &node)
{
m_qmlObjectNode = objectNode;
setup(ModelNodes{node});
}
void QmlModelNodeProxy::setup(const ModelNodes &editorNodes)
{
m_qmlObjectNode = editorNodes.isEmpty() ? ModelNode{} : editorNodes.first();
m_editorNodes = editorNodes;
m_subselection.clear();
@@ -61,12 +67,22 @@ ModelNode QmlModelNodeProxy::modelNode() const
return m_qmlObjectNode.modelNode();
}
ModelNodes QmlModelNodeProxy::editorNodes() const
{
return m_editorNodes;
}
ModelNode QmlModelNodeProxy::singleSelectedNode() const
{
return multiSelection() ? ModelNode{} : modelNode();
}
bool QmlModelNodeProxy::multiSelection() const
{
if (!m_qmlObjectNode.isValid())
return false;
return m_qmlObjectNode.view()->selectedModelNodes().size() > 1;
return editorNodes().size() > 1;
}
QString QmlModelNodeProxy::nodeId() const

View File

@@ -22,7 +22,8 @@ class QMLDESIGNER_EXPORT QmlModelNodeProxy : public QObject
public:
explicit QmlModelNodeProxy(QObject *parent = nullptr);
void setup(const QmlObjectNode &objectNode);
void setup(const ModelNode &node);
void setup(const ModelNodes &editorNodes);
static void registerDeclarativeType();
@@ -33,6 +34,8 @@ public:
QmlObjectNode qmlObjectNode() const;
ModelNode modelNode() const;
ModelNodes editorNodes() const;
ModelNode singleSelectedNode() const;
bool multiSelection() const;
@@ -81,6 +84,7 @@ private:
PropertyEditorSubSelectionWrapper *findWrapper(int internalId) const;
QmlObjectNode m_qmlObjectNode;
ModelNodes m_editorNodes;
QList<QSharedPointer<PropertyEditorSubSelectionWrapper>> m_subselection;
};

View File

@@ -211,7 +211,7 @@ void TextureEditorQmlBackend::setup(const QmlObjectNode &selectedTextureNode, co
}
// model node
m_backendModelNode.setup(selectedTextureNode.modelNode());
m_backendModelNode.setup(selectedTextureNode);
context()->setContextProperty("modelNodeBackend", &m_backendModelNode);
context()->setContextProperty("hasTexture", QVariant(true));