forked from qt-creator/qt-creator
QmlDesigner: Add "Select Parent" to 3d Context Menu
"Select Parent" action is added to the context menu of the Edit3DWidget. Task-number: QDS-8199 Change-Id: Iff6ce50eba966bea60f1b09f497632c3ff290c27 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include "edit3dvisibilitytogglesmenu.h"
|
||||
#include "metainfo.h"
|
||||
#include "modelnodeoperations.h"
|
||||
#include "nodeabstractproperty.h"
|
||||
#include "qmldesignerconstants.h"
|
||||
#include "qmldesignerplugin.h"
|
||||
#include "qmlvisualnode.h"
|
||||
@@ -217,6 +218,19 @@ void Edit3DWidget::createContextMenu()
|
||||
});
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
|
||||
m_selectParentAction = m_contextMenu->addAction(tr("Select Parent"), [&] {
|
||||
ModelNode parentNode = ModelNode::lowestCommonAncestor(view()->selectedModelNodes());
|
||||
if (!parentNode.isValid())
|
||||
return;
|
||||
|
||||
if (!parentNode.isRootNode() && view()->isSelectedModelNode(parentNode))
|
||||
parentNode = parentNode.parentProperty().parentModelNode();
|
||||
|
||||
view()->setSelectedModelNode(parentNode);
|
||||
});
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
}
|
||||
|
||||
bool Edit3DWidget::isPasteAvailable() const
|
||||
@@ -372,19 +386,21 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode
|
||||
|
||||
const bool isValid = modelNode.isValid();
|
||||
const bool isModel = modelNode.metaInfo().isQtQuick3DModel();
|
||||
const bool isNotRoot = isValid && !modelNode.isRootNode();
|
||||
const bool isCamera = isValid && modelNode.metaInfo().isQtQuick3DCamera();
|
||||
const bool isSingleComponent = view()->hasSingleSelectedModelNode() && modelNode.isComponent();
|
||||
const bool anyNodeSelected = view()->hasSelectedModelNodes();
|
||||
const bool selectionExcludingRoot = anyNodeSelected && !view()->rootModelNode().isSelected();
|
||||
|
||||
m_editComponentAction->setEnabled(isSingleComponent);
|
||||
m_editMaterialAction->setEnabled(isModel);
|
||||
m_duplicateAction->setEnabled(isNotRoot);
|
||||
m_copyAction->setEnabled(isNotRoot);
|
||||
m_duplicateAction->setEnabled(selectionExcludingRoot);
|
||||
m_copyAction->setEnabled(selectionExcludingRoot);
|
||||
m_pasteAction->setEnabled(isPasteAvailable());
|
||||
m_deleteAction->setEnabled(isNotRoot);
|
||||
m_fitSelectedAction->setEnabled(isNotRoot);
|
||||
m_deleteAction->setEnabled(selectionExcludingRoot);
|
||||
m_fitSelectedAction->setEnabled(anyNodeSelected);
|
||||
m_alignCameraAction->setEnabled(isCamera);
|
||||
m_alignViewAction->setEnabled(isCamera);
|
||||
m_selectParentAction->setEnabled(selectionExcludingRoot);
|
||||
|
||||
m_contextMenu->popup(mapToGlobal(pos));
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ private:
|
||||
QPointer<QAction> m_fitSelectedAction;
|
||||
QPointer<QAction> m_alignCameraAction;
|
||||
QPointer<QAction> m_alignViewAction;
|
||||
QPointer<QAction> m_selectParentAction;
|
||||
QPointer<QMenu> m_createSubMenu;
|
||||
ModelNode m_contextMenuTarget;
|
||||
QVector3D m_contextMenuPos3d;
|
||||
|
||||
@@ -222,6 +222,7 @@ public:
|
||||
void setLocked(bool value);
|
||||
|
||||
static bool isThisOrAncestorLocked(const ModelNode &node);
|
||||
static ModelNode lowestCommonAncestor(const QList<ModelNode> &nodes);
|
||||
|
||||
qint32 internalId() const;
|
||||
|
||||
|
||||
@@ -1313,6 +1313,96 @@ bool ModelNode::isThisOrAncestorLocked(const ModelNode &node)
|
||||
return isThisOrAncestorLocked(node.parentProperty().parentModelNode());
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief The lowest common ancestor node for node1 and node2. If one of the nodes (Node A) is
|
||||
* the ancestor of the other node, the return value is Node A and not the parent of Node A.
|
||||
* \param node1 First node
|
||||
* \param node2 Second node
|
||||
* \param depthOfLCA Depth of the return value
|
||||
* \param depthOfNode1 Depth of node1. Use this parameter for optimization
|
||||
* \param depthOfNode2 Depth of node2. Use this parameter for optimization
|
||||
*/
|
||||
static ModelNode lowestCommonAncestor(const ModelNode &node1,
|
||||
const ModelNode &node2,
|
||||
int &depthOfLCA,
|
||||
const int &depthOfNode1 = -1,
|
||||
const int &depthOfNode2 = -1)
|
||||
{
|
||||
Q_ASSERT(node1.isValid() && node2.isValid());
|
||||
|
||||
auto depthOfNode = [] (const ModelNode &node) -> int {
|
||||
int depth = 0;
|
||||
ModelNode parentNode = node;
|
||||
while (!parentNode.isRootNode()) {
|
||||
depth++;
|
||||
parentNode = parentNode.parentProperty().parentModelNode();
|
||||
}
|
||||
return depth;
|
||||
};
|
||||
|
||||
if (node1 == node2) {
|
||||
depthOfLCA = (depthOfNode1 < 0)
|
||||
? ((depthOfNode2 < 0) ? depthOfNode(node1) : depthOfNode2)
|
||||
: depthOfNode1;
|
||||
return node1;
|
||||
}
|
||||
|
||||
if (node1.isRootNode()) {
|
||||
depthOfLCA = 0;
|
||||
return node1;
|
||||
}
|
||||
|
||||
if (node2.isRootNode()) {
|
||||
depthOfLCA = 0;
|
||||
return node2;
|
||||
}
|
||||
|
||||
ModelNode nodeLower = node1;
|
||||
ModelNode nodeHigher = node2;
|
||||
int depthLower = (depthOfNode1 < 0) ? depthOfNode(nodeLower) : depthOfNode1;
|
||||
int depthHigher = (depthOfNode2 < 0) ? depthOfNode(nodeHigher) :depthOfNode2;
|
||||
|
||||
if (depthLower > depthHigher) {
|
||||
std::swap(depthLower, depthHigher);
|
||||
std::swap(nodeLower, nodeHigher);
|
||||
}
|
||||
|
||||
int depthDiff = depthHigher - depthLower;
|
||||
while (depthDiff--)
|
||||
nodeHigher = nodeHigher.parentProperty().parentModelNode();
|
||||
|
||||
while (nodeLower != nodeHigher) {
|
||||
nodeLower = nodeLower.parentProperty().parentModelNode();
|
||||
nodeHigher = nodeHigher.parentProperty().parentModelNode();
|
||||
--depthLower;
|
||||
}
|
||||
|
||||
depthOfLCA = depthLower;
|
||||
return nodeLower;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief The lowest common node containing all nodes. If one of the nodes (Node A) is
|
||||
* the ancestor of the other nodes, the return value is Node A and not the parent of Node A.
|
||||
*/
|
||||
ModelNode ModelNode::lowestCommonAncestor(const QList<ModelNode> &nodes)
|
||||
{
|
||||
if (nodes.isEmpty())
|
||||
return {};
|
||||
|
||||
ModelNode accumulatedNode = nodes.first();
|
||||
int accumulatedNodeDepth = -1;
|
||||
Utils::span<const ModelNode> nodesExceptFirst(nodes.constBegin() + 1, nodes.constEnd());
|
||||
for (const ModelNode &node : nodesExceptFirst) {
|
||||
accumulatedNode = QmlDesigner::lowestCommonAncestor(accumulatedNode,
|
||||
node,
|
||||
accumulatedNodeDepth,
|
||||
accumulatedNodeDepth);
|
||||
}
|
||||
|
||||
return accumulatedNode;
|
||||
}
|
||||
|
||||
void ModelNode::setScriptFunctions(const QStringList &scriptFunctionList)
|
||||
{
|
||||
model()->d->setScriptFunctions(m_internalNode, scriptFunctionList);
|
||||
|
||||
Reference in New Issue
Block a user