diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml index 1896535f5c1..0d8a9e8c3ee 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml @@ -42,13 +42,22 @@ DirectionalDraggable { return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos; } + onPressed: { + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + } + onDragged: { targetNode.position = localPos(sceneRelativeDistance); + if (targetNode == multiSelectionNode) + _generalHelper.moveMultiSelection(false); positionMove(); } onReleased: { targetNode.position = localPos(sceneRelativeDistance); + if (targetNode == multiSelectionNode) + _generalHelper.moveMultiSelection(true); positionCommit(); } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml index be38251e07b..2de922997c9 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml @@ -49,7 +49,7 @@ Item { property int selectionMode: EditView3D.SelectionMode.Item property int transformMode: EditView3D.TransformMode.Move - property Node selectedNode: null // This is non-null only in single selection case + property Node selectedNode: null // This is multiSelectionNode in multi-selection case property var selectedNodes: [] // All selected nodes property var lightIconGizmos: [] @@ -62,8 +62,8 @@ Item { property real fps: 0 signal selectionChanged(var selectedNodes) - signal commitObjectProperty(var object, var propName) - signal changeObjectProperty(var object, var propName) + signal commitObjectProperty(var objects, var propNames) + signal changeObjectProperty(var objects, var propNames) signal notifyActiveSceneChange() onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective) @@ -268,10 +268,14 @@ Item { selectionBoxes[i].targetNode = null; selectedNodes = objects; - if (objects.length === 0 || objects.length > 1) + if (objects.length === 0) { selectedNode = null; - else + } else if (objects.length > 1) { + selectedNode = multiSelectionNode; + _generalHelper.setMultiSelectionTargets(multiSelectionNode, objects); + } else { selectedNode = objects[0]; + } } function handleObjectClicked(object, multi) @@ -513,6 +517,11 @@ Item { view3D: overlayView } + Node { + id: multiSelectionNode + objectName: "multiSelectionNode" + } + MoveGizmo { id: moveGizmo scale: autoScale.getScale(Qt.vector3d(5, 5, 5)) @@ -522,9 +531,20 @@ Item { visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Move view3D: overlayView dragHelper: gizmoDragHelper + property var propertyNames: ["position"] - onPositionCommit: viewRoot.commitObjectProperty(viewRoot.selectedNode, "position") - onPositionMove: viewRoot.changeObjectProperty(viewRoot.selectedNode, "position") + onPositionCommit: { + if (targetNode == multiSelectionNode) + viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames); + else + viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames); + } + onPositionMove: { + if (targetNode == multiSelectionNode) + viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames); + else + viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); + } } ScaleGizmo { @@ -535,9 +555,21 @@ Item { visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Scale view3D: overlayView dragHelper: gizmoDragHelper + property var propertyNames: ["scale"] + property var propertyNamesMulti: ["position", "scale"] - onScaleCommit: viewRoot.commitObjectProperty(viewRoot.selectedNode, "scale") - onScaleChange: viewRoot.changeObjectProperty(viewRoot.selectedNode, "scale") + onScaleCommit: { + if (targetNode == multiSelectionNode) + viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames); + } + onScaleChange: { + if (targetNode == multiSelectionNode) + viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); + } } RotateGizmo { @@ -549,19 +581,31 @@ Item { visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Rotate view3D: overlayView dragHelper: gizmoDragHelper + property var propertyNames: ["eulerRotation"] + property var propertyNamesMulti: ["position", "eulerRotation"] - onRotateCommit: viewRoot.commitObjectProperty(viewRoot.selectedNode, "eulerRotation") - onRotateChange: viewRoot.changeObjectProperty(viewRoot.selectedNode, "eulerRotation") + onRotateCommit: { + if (targetNode == multiSelectionNode) + viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames); + } + onRotateChange: { + if (targetNode == multiSelectionNode) + viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); + } } LightGizmo { id: lightGizmo - targetNode: viewRoot.selectedNode + targetNode: viewRoot.selectedNode != multiSelectionNode ? viewRoot.selectedNode : null view3D: overlayView dragHelper: gizmoDragHelper - onPropertyValueCommit: viewRoot.commitObjectProperty(targetNode, propName) - onPropertyValueChange: viewRoot.changeObjectProperty(targetNode, propName) + onPropertyValueCommit: viewRoot.commitObjectProperty([targetNode], [propName]) + onPropertyValueChange: viewRoot.changeObjectProperty([targetNode], [propName]) } AutoScaleHelper { @@ -578,7 +622,7 @@ Item { Line3D { id: pivotLine - visible: viewRoot.selectedNode + visible: viewRoot.selectedNode && viewRoot.selectedNode != multiSelectionNode name: "3D Edit View Pivot Line" color: "#ddd600" diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml index 5f8b2799221..069150cbaae 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml @@ -43,13 +43,22 @@ PlanarDraggable { return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos; } + onPressed: { + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + } + onDragged: { targetNode.position = localPos(sceneRelativeDistance); + if (targetNode == multiSelectionNode) + _generalHelper.moveMultiSelection(false); positionMove(); } onReleased: { targetNode.position = localPos(sceneRelativeDistance); + if (targetNode == multiSelectionNode) + _generalHelper.moveMultiSelection(true); positionCommit(); } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarScaleHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarScaleHandle.qml index 5515fa96cef..3c6cd9d14b5 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarScaleHandle.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarScaleHandle.qml @@ -41,18 +41,24 @@ PlanarDraggable { property vector3d _startScale onPressed: { + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); _startScale = targetNode.scale; } onDragged: { targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x), axisX, axisY); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(false); scaleChange(); } onReleased: { targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x), axisX, axisY); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(true); scaleCommit(); } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml index 880ced6e9cd..1d1e280c1a3 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml @@ -208,6 +208,9 @@ Node { if (!rotateGizmo.targetNode) return; + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + // Need to recreate vector as we need to adjust it and we can't do that on reference of // scenePosition, which is read-only property var scenePos = rotateGizmo.dragHelper.pivotScenePosition(rotateGizmo.targetNode); @@ -237,6 +240,9 @@ Node { rotateGizmo.targetNode, _startRotation, _pointerPosPressed, Qt.vector3d(screenPos.x, screenPos.y, 0)); + if (targetNode == multiSelectionNode) + _generalHelper.rotateMultiSelection(false); + rotateGizmo.rotateChange(); } @@ -249,6 +255,9 @@ Node { rotateGizmo.targetNode, _startRotation, _pointerPosPressed, Qt.vector3d(screenPos.x, screenPos.y, 0)); + if (targetNode == multiSelectionNode) + _generalHelper.rotateMultiSelection(true); + rotateGizmo.rotateCommit(); dragging = false; } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml index 19760b8ff9d..f61739526ac 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml @@ -77,6 +77,9 @@ Model { if (!targetNode) return; + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + // Need to recreate vector as we need to adjust it and we can't do that on reference of // scenePosition, which is read-only property var scenePos = mouseAreaMain.pivotScenePosition(targetNode); @@ -107,6 +110,10 @@ Model { return; applyLocalRotation(screenPos); + + if (targetNode == multiSelectionNode) + _generalHelper.rotateMultiSelection(false); + currentMousePos = screenPos; rotateChange(); } @@ -117,6 +124,10 @@ Model { return; applyLocalRotation(screenPos); + + if (targetNode == multiSelectionNode) + _generalHelper.rotateMultiSelection(true); + rotateCommit(); currentAngle = 0; currentMousePos = screenPos; diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml index 076f8d53a62..f7071a8b79e 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml @@ -212,6 +212,9 @@ Node { if (!scaleGizmo.targetNode) return; + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + // Recreate vector so we don't follow the changes in targetNode.scale _startScale = Qt.vector3d(scaleGizmo.targetNode.scale.x, scaleGizmo.targetNode.scale.y, @@ -222,6 +225,8 @@ Node { if (!scaleGizmo.targetNode) return; scaleGizmo.targetNode.scale = localScale(screenPos); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(false); scaleGizmo.scaleChange(); } onReleased: { @@ -229,6 +234,8 @@ Node { return; scaleGizmo.targetNode.scale = localScale(screenPos); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(true); scaleGizmo.scaleCommit(); } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleRod.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleRod.qml index fcdf4a74158..af21ce4339f 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleRod.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleRod.qml @@ -50,18 +50,24 @@ DirectionalDraggable { } onPressed: { + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); _startScale = targetNode.scale; } onDragged: { targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0), axis, Qt.vector3d(0, 0, 0)); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(false); scaleChange(); } onReleased: { targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0), axis, Qt.vector3d(0, 0, 0)); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(true); scaleCommit(); } } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp index f65e3ccc4f3..f212cb553d5 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp @@ -347,6 +347,171 @@ double GeneralHelper::brightnessScaler() const #endif } +void GeneralHelper::setMultiSelectionTargets(QQuick3DNode *multiSelectRootNode, + const QVariantList &selectedList) +{ + // Filter selection to contain only topmost parent nodes in the selection + m_multiSelDataMap.clear(); + m_multiSelNodes.clear(); + for (auto &connection : qAsConst(m_multiSelectConnections)) + disconnect(connection); + m_multiSelectConnections.clear(); + m_multiSelectRootNode = multiSelectRootNode; + QSet selNodes; + + for (const auto &var : selectedList) { + QQuick3DNode *node = nullptr; + node = var.value(); + if (node) + selNodes.insert(node); + } + for (const auto selNode : qAsConst(selNodes)) { + bool found = false; + QQuick3DNode *parent = selNode->parentNode(); + while (parent) { + if (selNodes.contains(parent)) { + found = true; + break; + } + parent = parent->parentNode(); + } + if (!found) { + m_multiSelDataMap.insert(selNode, {}); + m_multiSelNodes.append(QVariant::fromValue(selNode)); + m_multiSelectConnections.append(connect(selNode, &QObject::destroyed, [this]() { + // If any multiselected node is destroyed, assume the entire selection is invalid. + // The new selection should be notified by creator immediately after anyway. + m_multiSelDataMap.clear(); + m_multiSelNodes.clear(); + for (auto &connection : qAsConst(m_multiSelectConnections)) + disconnect(connection); + m_multiSelectConnections.clear(); + })); + m_multiSelectConnections.append(connect(selNode, &QQuick3DNode::sceneTransformChanged, + [this]() { + // Reposition the multiselection root node if scene transform of any multiselected + // node changes outside of drag (i.e. changes originating from creator side) + if (!m_blockMultiSelectionNodePositioning) { + QVector3D newPos; + for (auto it = m_multiSelDataMap.begin(); it != m_multiSelDataMap.end(); ++it) + newPos += it.key()->scenePosition(); + newPos /= m_multiSelDataMap.size(); + m_multiSelectRootNode->setPosition(newPos); + } + })); + } + } + + restartMultiSelection(); + m_blockMultiSelectionNodePositioning = false; +} + +void GeneralHelper::restartMultiSelection() +{ + for (auto it = m_multiSelDataMap.begin(); it != m_multiSelDataMap.end(); ++it) { + it.value() = {it.key()->scenePosition(), + it.key()->scale(), + it.key()->rotation()}; + } + + m_multiSelNodeData = {}; + if (!m_multiSelDataMap.isEmpty()) { + for (const auto &data : qAsConst(m_multiSelDataMap)) + m_multiSelNodeData.startScenePos += data.startScenePos; + m_multiSelNodeData.startScenePos /= m_multiSelDataMap.size(); + } + m_multiSelectRootNode->setPosition(m_multiSelNodeData.startScenePos); + m_multiSelectRootNode->setRotation({}); + m_multiSelectRootNode->setScale({1.f, 1.f, 1.f}); + m_blockMultiSelectionNodePositioning = true; +} + +QVariantList GeneralHelper::multiSelectionTargets() const +{ + return m_multiSelNodes; +} + +void GeneralHelper::moveMultiSelection(bool commit) +{ + // Move the multiselected nodes in global space by offset from multiselection start to scenePos + QVector3D globalOffset = m_multiSelectRootNode->scenePosition() - m_multiSelNodeData.startScenePos; + for (auto it = m_multiSelDataMap.constBegin(); it != m_multiSelDataMap.constEnd(); ++it) { + QVector3D newGlobalPos = it.value().startScenePos + globalOffset; + QMatrix4x4 m; + if (it.key()->parentNode()) + m = it.key()->parentNode()->sceneTransform(); + it.key()->setPosition(m.inverted() * newGlobalPos); + } + m_blockMultiSelectionNodePositioning = !commit; +} + +void GeneralHelper::scaleMultiSelection(bool commit) +{ + // Offset the multiselected nodes in global space according to scale factor and scale them by + // the same factor. + + // TODO: The math below breaks down with negative scaling, so don't allow it for now + const QVector3D sceneScale = m_multiSelectRootNode->sceneScale(); + QVector3D fixedSceneScale = sceneScale; + for (int i = 0; i < 3; ++i) { + if (sceneScale[i] < 0.f) + fixedSceneScale[i] = -sceneScale[i]; + } + + const QVector3D unitVector {1.f, 1.f, 1.f}; + const QVector3D diffScale = (fixedSceneScale - unitVector); + + for (auto it = m_multiSelDataMap.constBegin(); it != m_multiSelDataMap.constEnd(); ++it) { + const QVector3D newGlobalPos = m_multiSelNodeData.startScenePos + + (it.value().startScenePos - m_multiSelNodeData.startScenePos) * sceneScale; + QMatrix4x4 parentMat; + if (it.key()->parentNode()) + parentMat = it.key()->parentNode()->sceneTransform().inverted(); + it.key()->setPosition(parentMat * newGlobalPos); + + QMatrix4x4 mat; + mat.rotate(it.key()->sceneRotation()); + + auto scaleDim = [&](int dim) -> QVector3D { + QVector3D dimScale; + float diffScaleDim = diffScale[dim]; + dimScale[dim] = diffScaleDim; + dimScale = (mat.inverted() * dimScale).normalized() * diffScaleDim; + for (int i = 0; i < 3; ++i) + dimScale[i] = qAbs(dimScale[i]); + if (fixedSceneScale[dim] < 1.0f) + dimScale = -dimScale; + return dimScale; + }; + + QVector3D finalScale = scaleDim(0) + scaleDim(1) + scaleDim(2) + unitVector; + + it.key()->setScale(finalScale * it.value().startScale); + } + m_blockMultiSelectionNodePositioning = !commit; +} + +void GeneralHelper::rotateMultiSelection(bool commit) +{ + // Rotate entire selection around the multiselection node + const QQuaternion sceneRotation = m_multiSelectRootNode->sceneRotation(); + QVector3D rotAxis; + float rotAngle = 0; + sceneRotation.getAxisAndAngle(&rotAxis, &rotAngle); + + for (auto it = m_multiSelDataMap.constBegin(); it != m_multiSelDataMap.constEnd(); ++it) { + QVector3D globalOffset = it.value().startScenePos - m_multiSelNodeData.startScenePos; + QVector3D newGlobalPos = m_multiSelNodeData.startScenePos + sceneRotation * globalOffset; + QMatrix4x4 parentMat; + if (it.key()->parentNode()) + parentMat = it.key()->parentNode()->sceneTransform().inverted(); + it.key()->setPosition(parentMat * newGlobalPos); + it.key()->setRotation(it.value().startRot); + it.key()->rotate(rotAngle, rotAxis, QQuick3DNode::SceneSpace); + } + m_blockMultiSelectionNodePositioning = !commit; +} + bool GeneralHelper::isMacOS() const { #ifdef Q_OS_MACOS diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h index 212411eee4a..a73ad5a0a79 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h @@ -27,13 +27,14 @@ #ifdef QUICK3D_MODULE -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include QT_BEGIN_NAMESPACE class QQuick3DCamera; @@ -91,6 +92,14 @@ public: Q_INVOKABLE double brightnessScaler() const; + Q_INVOKABLE void setMultiSelectionTargets(QQuick3DNode *multiSelectRootNode, + const QVariantList &selectedList); + Q_INVOKABLE void restartMultiSelection(); + Q_INVOKABLE QVariantList multiSelectionTargets() const; + Q_INVOKABLE void moveMultiSelection(bool commit); + Q_INVOKABLE void scaleMultiSelection(bool commit); + Q_INVOKABLE void rotateMultiSelection(bool commit); + bool isMacOS() const; void addRotationBlocks(const QSet &nodes); @@ -116,6 +125,19 @@ private: QHash m_toolStatesPending; QSet m_gizmoTargets; QSet m_rotationBlockedNodes; + + struct MultiSelData { + QVector3D startScenePos; + QVector3D startScale; + QQuaternion startRot; + }; + + QHash m_multiSelDataMap; + QVariantList m_multiSelNodes; + MultiSelData m_multiSelNodeData; + QQuick3DNode *m_multiSelectRootNode = nullptr; + QList m_multiSelectConnections; + bool m_blockMultiSelectionNodePositioning = false; }; } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 2b2ec60da88..4156c8e040c 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -152,6 +152,34 @@ static bool isQuick3DMode() return mode3D; } +static QObjectList toObjectList(const QVariant &variantList) +{ + QObjectList objList; + if (!variantList.isNull()) { + const auto varList = variantList.value(); + for (const auto &var : varList) { + QObject *obj = var.value(); + if (obj) + objList.append(obj); + } + } + return objList; +} + +static QList toPropertyNameList(const QVariant &variantList) +{ + QList propList; + if (!variantList.isNull()) { + const auto varList = variantList.value(); + for (const auto &var : varList) { + PropertyName prop = var.toByteArray(); + if (!prop.isEmpty()) + propList.append(prop); + } + } + return propList; +} + void Qt5InformationNodeInstanceServer::createAuxiliaryQuickView(const QUrl &url, RenderViewData &viewData) { @@ -422,64 +450,108 @@ Qt5InformationNodeInstanceServer::propertyToPropertyValueTriples( return result; } -void Qt5InformationNodeInstanceServer::modifyVariantValue( - const QVariant &node, - const PropertyName &propertyName, - ValuesModifiedCommand::TransactionOption option) +void Qt5InformationNodeInstanceServer::modifyVariantValue(const QObjectList &objects, + const QList &propNames, ValuesModifiedCommand::TransactionOption option) { - PropertyName targetPropertyName; + struct PropNamePair { + PropertyName origPropName; + PropertyName targetPropName; + }; + + QList propNamePairs; // Position is a special case, because the position can be 'position.x 'or simply 'x'. // We prefer 'x'. - if (propertyName != "position") - targetPropertyName = propertyName; + for (const auto &propName : propNames) { + if (propName != "position") + propNamePairs.append({propName, propName}); + else + propNamePairs.append({propName, PropertyName{}}); + } - auto *obj = node.value(); + if (!objects.isEmpty()) { + QVector valueVector; + for (const auto listObj : objects) { + ServerNodeInstance instance = instanceForObject(listObj); + if (instance.isValid()) { + const qint32 instId = instance.instanceId(); + if (option == ValuesModifiedCommand::TransactionOption::Start) + instance.setModifiedFlag(true); + else if (option == ValuesModifiedCommand::TransactionOption::End) + instance.setModifiedFlag(false); + for (const auto &propNamePair : propNamePairs) { + // We do have to split vector3d props into foobar.x, foobar.y, foobar.z + const QVector triple + = propertyToPropertyValueTriples(instance, propNamePair.targetPropName, + listObj->property(propNamePair.origPropName)); + for (const auto &property : triple) { + const PropertyName propName = property.propertyName; + const QVariant propValue = property.propertyValue; + valueVector.append(PropertyValueContainer(instId, propName, + propValue, PropertyName())); + } + } + } + } - if (obj) { - ServerNodeInstance instance = instanceForObject(obj); - - if (option == ValuesModifiedCommand::TransactionOption::Start) - instance.setModifiedFlag(true); - else if (option == ValuesModifiedCommand::TransactionOption::End) - instance.setModifiedFlag(false); - - // We do have to split position into position.x, position.y, position.z - ValuesModifiedCommand command = createValuesModifiedCommand(propertyToPropertyValueTriples( - instance, - targetPropertyName, - obj->property(propertyName))); - - command.transactionOption = option; - - nodeInstanceClient()->valuesModified(command); + if (!valueVector.isEmpty()) { + ValuesModifiedCommand command(valueVector); + command.transactionOption = option; + nodeInstanceClient()->valuesModified(command); + } } } -void Qt5InformationNodeInstanceServer::handleObjectPropertyCommit(const QVariant &object, - const QVariant &propName) +void Qt5InformationNodeInstanceServer::handleObjectPropertyCommit(const QVariant &objects, + const QVariant &propNames) { - modifyVariantValue(object, propName.toByteArray(), + modifyVariantValue(toObjectList(objects), toPropertyNameList(propNames), ValuesModifiedCommand::TransactionOption::End); - m_changedNode = {}; - m_changedProperty = {}; + m_changedNodes.clear(); + m_changedProperties.clear(); m_propertyChangeTimer.stop(); } -void Qt5InformationNodeInstanceServer::handleObjectPropertyChange(const QVariant &object, - const QVariant &propName) +void Qt5InformationNodeInstanceServer::handleObjectPropertyChange(const QVariant &objects, + const QVariant &propNames) { - PropertyName propertyName(propName.toByteArray()); - if (m_changedProperty != propertyName || m_changedNode != object) { - if (!m_changedNode.isNull()) - handleObjectPropertyCommit(m_changedNode, m_changedProperty); - modifyVariantValue(object, propertyName, - ValuesModifiedCommand::TransactionOption::Start); + QObjectList objList = toObjectList(objects); + QList propList = toPropertyNameList(propNames); + + bool nodeChanged = true; + if (objList.size() == m_changedNodes.size()) { + nodeChanged = false; + for (int i = 0; i < objList.size(); ++i) { + if (objList[i] != m_changedNodes[i]) { + nodeChanged = true; + break; + } + } + } + if (!nodeChanged && propList.size() == m_changedProperties.size()) { + for (int i = 0; i < propList.size(); ++i) { + if (propList[i] != m_changedProperties[i]) { + nodeChanged = true; + break; + } + } + } + + if (nodeChanged) { + if (!m_changedNodes.isEmpty()) { + // Nodes/properties changed, commit currently pending changes + modifyVariantValue(m_changedNodes, m_changedProperties, + ValuesModifiedCommand::TransactionOption::End); + m_changedNodes.clear(); + m_changedProperties.clear(); + m_propertyChangeTimer.stop(); + } + modifyVariantValue(objList, propList, ValuesModifiedCommand::TransactionOption::Start); } else if (!m_propertyChangeTimer.isActive()) { m_propertyChangeTimer.start(); } - m_changedNode = object; - m_changedProperty = propertyName; + m_changedNodes = objList; + m_changedProperties = propList; } void Qt5InformationNodeInstanceServer::handleActiveSceneChange() @@ -1125,7 +1197,7 @@ void Qt5InformationNodeInstanceServer::initializeAuxiliaryViews() void Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout() { - modifyVariantValue(m_changedNode, m_changedProperty, + modifyVariantValue(m_changedNodes, m_changedProperties, ValuesModifiedCommand::TransactionOption::None); } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index 6e882e87280..ed4f97d81be 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -77,8 +77,8 @@ public: private slots: void handleSelectionChanged(const QVariant &objs); - void handleObjectPropertyCommit(const QVariant &object, const QVariant &propName); - void handleObjectPropertyChange(const QVariant &object, const QVariant &propName); + void handleObjectPropertyCommit(const QVariant &objects, const QVariant &propNames); + void handleObjectPropertyChange(const QVariant &objects, const QVariant &propNames); void handleActiveSceneChange(); void handleToolStateChanged(const QString &sceneId, const QString &tool, const QVariant &toolState); @@ -115,8 +115,8 @@ private: const ServerNodeInstance &instance, const PropertyName &propertyName, const QVariant &variant); - void modifyVariantValue(const QVariant &node, - const PropertyName &propertyName, + void modifyVariantValue(const QObjectList &objects, + const QList &propNames, ValuesModifiedCommand::TransactionOption option); void updateView3DRect(QObject *view3D); void updateActiveSceneToEditView3D(); @@ -159,8 +159,8 @@ private: QTimer m_render3DEditViewTimer; QTimer m_renderModelNodeImageViewTimer; QTimer m_inputEventTimer; - QVariant m_changedNode; - PropertyName m_changedProperty; + QObjectList m_changedNodes; + QList m_changedProperties; ChangeSelectionCommand m_lastSelectionChangeCommand; QList m_pendingInputEventCommands; QObject *m_3dHelper = nullptr;