forked from qt-creator/qt-creator
QmlDesigner: Move nodes to a specific position
A list of nodes could be moved to a new position. This is useful for positioning the visual items within the transactions of the DesignDocument. Paste to position is also available. Paste method is modified by this commit. Task-number: QDS-8063 Change-Id: Id5a295ee6a096952379e2b46d8e740d28562d5e9 Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Samuel Ghinet <samuel.ghinet@qt.io> Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
@@ -191,6 +191,93 @@ bool DesignDocument::pasteSVG()
|
||||
return true;
|
||||
}
|
||||
|
||||
void DesignDocument::moveNodesToPosition(const QList<ModelNode> &nodes, const std::optional<QVector3D> &position)
|
||||
{
|
||||
if (!nodes.size())
|
||||
return;
|
||||
|
||||
QList<ModelNode> movingNodes = nodes;
|
||||
DesignDocumentView view{m_externalDependencies};
|
||||
currentModel()->attachView(&view);
|
||||
|
||||
ModelNode targetNode; // the node that new nodes should be placed in
|
||||
if (!view.selectedModelNodes().isEmpty())
|
||||
targetNode = view.firstSelectedModelNode();
|
||||
|
||||
// in case we copy and paste a selection we paste in the parent item
|
||||
if ((view.selectedModelNodes().count() == movingNodes.count())
|
||||
&& targetNode.hasParentProperty()) {
|
||||
targetNode = targetNode.parentProperty().parentModelNode();
|
||||
} else if (view.selectedModelNodes().isEmpty()) {
|
||||
// if selection is empty and copied nodes are all 3D nodes, paste them under the active scene
|
||||
bool all3DNodes = Utils::allOf(movingNodes, [](const ModelNode &node) {
|
||||
return node.metaInfo().isQtQuick3DNode();
|
||||
});
|
||||
|
||||
if (all3DNodes) {
|
||||
auto data = rootModelNode().auxiliaryData(active3dSceneProperty);
|
||||
if (data) {
|
||||
if (int activeSceneId = data->toInt(); activeSceneId != -1) {
|
||||
NodeListProperty sceneNodeProperty = QmlVisualNode::findSceneNodeProperty(
|
||||
rootModelNode().view(), activeSceneId);
|
||||
targetNode = sceneNodeProperty.parentModelNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetNode.isValid())
|
||||
targetNode = view.rootModelNode();
|
||||
|
||||
for (const ModelNode &node : std::as_const(movingNodes)) {
|
||||
for (const ModelNode &node2 : std::as_const(movingNodes)) {
|
||||
if (node.isAncestorOf(node2))
|
||||
movingNodes.removeAll(node2);
|
||||
}
|
||||
}
|
||||
|
||||
bool isSingleNode = movingNodes.size() == 1;
|
||||
if (isSingleNode) {
|
||||
ModelNode singleNode = movingNodes.first();
|
||||
if (targetNode.hasParentProperty()
|
||||
&& singleNode.simplifiedTypeName() == targetNode.simplifiedTypeName()
|
||||
&& singleNode.variantProperty("width").value() == targetNode.variantProperty("width").value()
|
||||
&& singleNode.variantProperty("height").value() == targetNode.variantProperty("height").value()) {
|
||||
targetNode = targetNode.parentProperty().parentModelNode();
|
||||
}
|
||||
}
|
||||
|
||||
const PropertyName defaultPropertyName = targetNode.metaInfo().defaultPropertyName();
|
||||
if (!targetNode.metaInfo().property(defaultPropertyName).isListProperty())
|
||||
qWarning() << Q_FUNC_INFO << "Cannot reparent to" << targetNode;
|
||||
NodeListProperty parentProperty = targetNode.nodeListProperty(defaultPropertyName);
|
||||
QList<ModelNode> pastedNodeList;
|
||||
|
||||
const double scatterRange = 20.;
|
||||
int offset = QRandomGenerator::global()->generateDouble() * scatterRange - scatterRange / 2;
|
||||
|
||||
std::optional<QmlVisualNode> firstVisualNode;
|
||||
QVector3D translationVect;
|
||||
for (const ModelNode &node : std::as_const(movingNodes)) {
|
||||
ModelNode pastedNode(view.insertModel(node));
|
||||
pastedNodeList.append(pastedNode);
|
||||
parentProperty.reparentHere(pastedNode);
|
||||
|
||||
QmlVisualNode visualNode(pastedNode);
|
||||
if (!firstVisualNode.has_value() && visualNode.isValid()){
|
||||
firstVisualNode = visualNode;
|
||||
translationVect = (position.has_value() && firstVisualNode.has_value())
|
||||
? position.value() - firstVisualNode->position()
|
||||
: QVector3D();
|
||||
}
|
||||
visualNode.translate(translationVect);
|
||||
visualNode.scatter(targetNode, offset);
|
||||
}
|
||||
|
||||
view.setSelectedModelNodes(pastedNodeList);
|
||||
view.model()->clearMetaInfoCache();
|
||||
}
|
||||
|
||||
bool DesignDocument::inFileComponentModelActive() const
|
||||
{
|
||||
return bool(m_inFileComponentModel);
|
||||
@@ -492,46 +579,12 @@ void DesignDocument::cutSelected()
|
||||
deleteSelected();
|
||||
}
|
||||
|
||||
static void scatterItem(const ModelNode &pastedNode, const ModelNode &targetNode, int offset = -2000)
|
||||
void DesignDocument::paste()
|
||||
{
|
||||
if (targetNode.metaInfo().isValid() && targetNode.metaInfo().isLayoutable())
|
||||
return;
|
||||
|
||||
if (!(pastedNode.hasVariantProperty("x") && pastedNode.hasVariantProperty("y")))
|
||||
return;
|
||||
|
||||
bool scatter = false;
|
||||
for (const ModelNode &childNode : targetNode.directSubModelNodes()) {
|
||||
if (childNode.variantProperty("x").value() == pastedNode.variantProperty("x").value() &&
|
||||
childNode.variantProperty("y").value() == pastedNode.variantProperty("y").value()) {
|
||||
scatter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!scatter)
|
||||
return;
|
||||
|
||||
if (offset == -2000) { // scatter in range
|
||||
double x = pastedNode.variantProperty("x").value().toDouble();
|
||||
double y = pastedNode.variantProperty("y").value().toDouble();
|
||||
|
||||
const double scatterRange = 20.;
|
||||
x += QRandomGenerator::global()->generateDouble() * scatterRange - scatterRange / 2;
|
||||
y += QRandomGenerator::global()->generateDouble() * scatterRange - scatterRange / 2;
|
||||
|
||||
pastedNode.variantProperty("x").setValue(int(x));
|
||||
pastedNode.variantProperty("y").setValue(int(y));
|
||||
} else { // offset
|
||||
int x = pastedNode.variantProperty("x").value().toInt();
|
||||
int y = pastedNode.variantProperty("y").value().toInt();
|
||||
x += offset;
|
||||
y += offset;
|
||||
pastedNode.variantProperty("x").setValue(x);
|
||||
pastedNode.variantProperty("y").setValue(y);
|
||||
}
|
||||
pasteToPosition({});
|
||||
}
|
||||
|
||||
void DesignDocument::paste()
|
||||
void DesignDocument::pasteToPosition(const std::optional<QVector3D> &position)
|
||||
{
|
||||
if (pasteSVG())
|
||||
return;
|
||||
@@ -547,116 +600,21 @@ void DesignDocument::paste()
|
||||
DesignDocumentView view{m_externalDependencies};
|
||||
pasteModel->attachView(&view);
|
||||
ModelNode rootNode(view.rootModelNode());
|
||||
QList<ModelNode> selectedNodes = rootNode.directSubModelNodes();
|
||||
pasteModel->detachView(&view);
|
||||
|
||||
if (rootNode.type() == "empty")
|
||||
return;
|
||||
|
||||
if (rootNode.id() == "__multi__selection__") { // pasting multiple objects
|
||||
currentModel()->attachView(&view);
|
||||
QList<ModelNode> selectedNodes;
|
||||
if (rootNode.id() == "__multi__selection__")
|
||||
selectedNodes << rootNode.directSubModelNodes();
|
||||
else
|
||||
selectedNodes << rootNode;
|
||||
|
||||
ModelNode targetNode;
|
||||
pasteModel->detachView(&view);
|
||||
|
||||
if (!view.selectedModelNodes().isEmpty())
|
||||
targetNode = view.firstSelectedModelNode();
|
||||
|
||||
// in case we copy and paste a selection we paste in the parent item
|
||||
if ((view.selectedModelNodes().count() == selectedNodes.count())
|
||||
&& targetNode.hasParentProperty()) {
|
||||
targetNode = targetNode.parentProperty().parentModelNode();
|
||||
} else if (view.selectedModelNodes().isEmpty()) {
|
||||
// if selection is empty and copied nodes are all 3D nodes, paste them under the active scene
|
||||
bool all3DNodes = std::find_if(selectedNodes.cbegin(),
|
||||
selectedNodes.cend(),
|
||||
[](const ModelNode &node) {
|
||||
return !node.metaInfo().isQtQuick3DNode();
|
||||
})
|
||||
== selectedNodes.cend();
|
||||
if (all3DNodes) {
|
||||
auto data = rootModelNode().auxiliaryData(active3dSceneProperty);
|
||||
if (data) {
|
||||
if (int activeSceneId = data->toInt(); activeSceneId != -1) {
|
||||
NodeListProperty sceneNodeProperty = QmlVisualNode::findSceneNodeProperty(
|
||||
rootModelNode().view(), activeSceneId);
|
||||
targetNode = sceneNodeProperty.parentModelNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetNode.isValid())
|
||||
targetNode = view.rootModelNode();
|
||||
|
||||
for (const ModelNode &node : std::as_const(selectedNodes)) {
|
||||
for (const ModelNode &node2 : std::as_const(selectedNodes)) {
|
||||
if (node.isAncestorOf(node2))
|
||||
selectedNodes.removeAll(node2);
|
||||
}
|
||||
}
|
||||
|
||||
rewriterView()->executeInTransaction("DesignDocument::paste1", [&view, selectedNodes, targetNode]() {
|
||||
QList<ModelNode> pastedNodeList;
|
||||
|
||||
const double scatterRange = 20.;
|
||||
int offset = QRandomGenerator::global()->generateDouble() * scatterRange - scatterRange / 2;
|
||||
|
||||
const auto defaultPropertyName = targetNode.metaInfo().defaultPropertyName();
|
||||
auto parentProperty = targetNode.nodeListProperty(defaultPropertyName);
|
||||
for (const ModelNode &node : std::as_const(selectedNodes)) {
|
||||
ModelNode pastedNode(view.insertModel(node));
|
||||
pastedNodeList.append(pastedNode);
|
||||
scatterItem(pastedNode, targetNode, offset);
|
||||
parentProperty.reparentHere(pastedNode);
|
||||
}
|
||||
|
||||
view.setSelectedModelNodes(pastedNodeList);
|
||||
});
|
||||
|
||||
} else { // pasting single object
|
||||
rewriterView()->executeInTransaction("DesignDocument::paste1", [this, &view, rootNode]() {
|
||||
currentModel()->attachView(&view);
|
||||
ModelNode pastedNode(view.insertModel(rootNode));
|
||||
ModelNode targetNode;
|
||||
|
||||
if (!view.selectedModelNodes().isEmpty()) {
|
||||
targetNode = view.firstSelectedModelNode();
|
||||
} else {
|
||||
// if selection is empty and this is a 3D Node, paste it under the active scene
|
||||
if (pastedNode.metaInfo().isQtQuick3DNode()) {
|
||||
auto data = rootModelNode().auxiliaryData(active3dSceneProperty);
|
||||
if (data) {
|
||||
if (int activeSceneId = data->toInt(); activeSceneId != -1) {
|
||||
NodeListProperty sceneNodeProperty = QmlVisualNode::findSceneNodeProperty(
|
||||
rootModelNode().view(), activeSceneId);
|
||||
targetNode = sceneNodeProperty.parentModelNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetNode.isValid())
|
||||
targetNode = view.rootModelNode();
|
||||
|
||||
if (targetNode.hasParentProperty() &&
|
||||
pastedNode.simplifiedTypeName() == targetNode.simplifiedTypeName() &&
|
||||
pastedNode.variantProperty("width").value() == targetNode.variantProperty("width").value() &&
|
||||
pastedNode.variantProperty("height").value() == targetNode.variantProperty("height").value()) {
|
||||
targetNode = targetNode.parentProperty().parentModelNode();
|
||||
}
|
||||
|
||||
PropertyName defaultProperty(targetNode.metaInfo().defaultPropertyName());
|
||||
|
||||
scatterItem(pastedNode, targetNode);
|
||||
if (targetNode.metaInfo().property(defaultProperty).isListProperty())
|
||||
targetNode.nodeListProperty(defaultProperty).reparentHere(pastedNode);
|
||||
else
|
||||
qWarning() << "Cannot reparent to" << targetNode;
|
||||
|
||||
view.setSelectedModelNodes({pastedNode});
|
||||
});
|
||||
view.model()->clearMetaInfoCache();
|
||||
}
|
||||
rewriterView()->executeInTransaction("DesignDocument::pasteToPosition", [this, selectedNodes, position]() {
|
||||
moveNodesToPosition(selectedNodes, position);
|
||||
});
|
||||
}
|
||||
|
||||
void DesignDocument::selectAll()
|
||||
|
||||
@@ -99,6 +99,7 @@ public:
|
||||
void copySelected();
|
||||
void cutSelected();
|
||||
void paste();
|
||||
void pasteToPosition(const std::optional<QVector3D> &position);
|
||||
void selectAll();
|
||||
void undo();
|
||||
void redo();
|
||||
@@ -128,6 +129,7 @@ private: // functions
|
||||
ModelPointer createInFileComponentModel();
|
||||
|
||||
bool pasteSVG();
|
||||
void moveNodesToPosition(const QList<ModelNode> &nodes, const std::optional<QVector3D> &position);
|
||||
|
||||
private: // variables
|
||||
ModelPointer m_documentModel;
|
||||
|
||||
Reference in New Issue
Block a user