QmlDesigner: Add delete action to 3D Editor's context menu

This entails selecting a model upon right-clicking if it is not selected.
Also fixed a memory leak and small tweaks.

Fixes: QDS-7401
Change-Id: I592acb3fff30ecc3236f3cf2fbe126de4fb389dc
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Mahmoud Badri
2022-08-23 15:19:56 +03:00
parent f6e8b5f753
commit 8656bafbd4
6 changed files with 51 additions and 25 deletions

View File

@@ -363,7 +363,7 @@ Item {
} }
} }
function handleObjectClicked(object, multi) function handleObjectClicked(object, button, multi)
{ {
if (object instanceof View3D) { if (object instanceof View3D) {
// View3D can be the resolved pick target in case the 3D editor is showing content // View3D can be the resolved pick target in case the 3D editor is showing content
@@ -393,7 +393,14 @@ Item {
// Null object always clears entire selection // Null object always clears entire selection
var newSelection = []; var newSelection = [];
if (clickedObject) { if (clickedObject) {
if (multi && selectedNodes.length > 0) { if (button === Qt.RightButton) {
// Right-clicking does only single selection (when clickedObject is unselected)
// This is needed for selecting a target for the context menu
if (!selectedNodes.includes(clickedObject))
newSelection[0] = clickedObject;
else
newSelection = selectedNodes;
} else if (multi && selectedNodes.length > 0) {
var deselect = false; var deselect = false;
for (var i = 0; i < selectedNodes.length; ++i) { for (var i = 0; i < selectedNodes.length; ++i) {
// Multiselecting already selected object clears that object from selection // Multiselecting already selected object clears that object from selection
@@ -697,10 +704,10 @@ Item {
view3D: overlayView view3D: overlayView
dragHelper: gizmoDragHelper dragHelper: gizmoDragHelper
onPropertyValueCommit: (propName)=> { onPropertyValueCommit: (propName) => {
viewRoot.commitObjectProperty([targetNode], [propName]); viewRoot.commitObjectProperty([targetNode], [propName]);
} }
onPropertyValueChange: (propName)=> { onPropertyValueChange: (propName) => {
viewRoot.changeObjectProperty([targetNode], [propName]); viewRoot.changeObjectProperty([targetNode], [propName]);
} }
} }
@@ -772,17 +779,17 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: false hoverEnabled: false
property MouseArea3D freeDraggerArea property MouseArea3D freeDraggerArea
property point pressPoint property point pressPoint
property bool initialMoveBlock: false property bool initialMoveBlock: false
onPressed: (mouse)=> { onPressed: (mouse) => {
if (viewRoot.editView) { if (viewRoot.editView) {
var pickResult = viewRoot.editView.pick(mouse.x, mouse.y); var pickResult = viewRoot.editView.pick(mouse.x, mouse.y);
handleObjectClicked(_generalHelper.resolvePick(pickResult.objectHit), handleObjectClicked(_generalHelper.resolvePick(pickResult.objectHit), mouse.button,
mouse.modifiers & Qt.ControlModifier); mouse.modifiers & Qt.ControlModifier);
if (pickResult.objectHit && pickResult.objectHit instanceof Node) { if (pickResult.objectHit && pickResult.objectHit instanceof Node) {
@@ -800,7 +807,7 @@ Item {
} }
} }
} }
onPositionChanged: (mouse)=> { onPositionChanged: (mouse) => {
if (freeDraggerArea) { if (freeDraggerArea) {
if (initialMoveBlock && Math.abs(pressPoint.x - mouse.x) + Math.abs(pressPoint.y - mouse.y) > 10) { if (initialMoveBlock && Math.abs(pressPoint.x - mouse.x) + Math.abs(pressPoint.y - mouse.y) > 10) {
// Don't force press event at actual press, as that puts the gizmo // Don't force press event at actual press, as that puts the gizmo
@@ -825,10 +832,10 @@ Item {
} }
} }
onReleased: (mouse)=> { onReleased: (mouse) => {
handleRelease(mouse); handleRelease(mouse);
} }
onCanceled: (mouse)=> { onCanceled: (mouse) => {
handleRelease(mouse); handleRelease(mouse);
} }
} }

View File

@@ -363,7 +363,7 @@ Item {
} }
} }
function handleObjectClicked(object, multi) function handleObjectClicked(object, button, multi)
{ {
if (object instanceof View3D) { if (object instanceof View3D) {
// View3D can be the resolved pick target in case the 3D editor is showing content // View3D can be the resolved pick target in case the 3D editor is showing content
@@ -393,7 +393,14 @@ Item {
// Null object always clears entire selection // Null object always clears entire selection
var newSelection = []; var newSelection = [];
if (clickedObject) { if (clickedObject) {
if (multi && selectedNodes.length > 0) { if (button === Qt.RightButton) {
// Right-clicking does only single selection (when clickedObject is unselected)
// This is needed for selecting a target for the context menu
if (!selectedNodes.includes(clickedObject))
newSelection[0] = clickedObject;
else
newSelection = selectedNodes;
} else if (multi && selectedNodes.length > 0) {
var deselect = false; var deselect = false;
for (var i = 0; i < selectedNodes.length; ++i) { for (var i = 0; i < selectedNodes.length; ++i) {
// Multiselecting already selected object clears that object from selection // Multiselecting already selected object clears that object from selection
@@ -841,10 +848,10 @@ Item {
view3D: overlayView view3D: overlayView
dragHelper: gizmoDragHelper dragHelper: gizmoDragHelper
onPropertyValueCommit: (propName)=> { onPropertyValueCommit: (propName) => {
viewRoot.commitObjectProperty([targetNode], [propName]); viewRoot.commitObjectProperty([targetNode], [propName]);
} }
onPropertyValueChange: (propName)=> { onPropertyValueChange: (propName) => {
viewRoot.changeObjectProperty([targetNode], [propName]); viewRoot.changeObjectProperty([targetNode], [propName]);
} }
} }
@@ -917,14 +924,14 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: false hoverEnabled: false
property MouseArea3D freeDraggerArea property MouseArea3D freeDraggerArea
property point pressPoint property point pressPoint
property bool initialMoveBlock: false property bool initialMoveBlock: false
onPressed: (mouse)=> { onPressed: (mouse) => {
if (viewRoot.editView) { if (viewRoot.editView) {
// First pick overlay to check for hits there // First pick overlay to check for hits there
var pickResult = _generalHelper.pickViewAt(overlayView, mouse.x, mouse.y); var pickResult = _generalHelper.pickViewAt(overlayView, mouse.x, mouse.y);
@@ -935,7 +942,7 @@ Item {
resolvedResult = _generalHelper.resolvePick(pickResult.objectHit); resolvedResult = _generalHelper.resolvePick(pickResult.objectHit);
} }
handleObjectClicked(resolvedResult, mouse.modifiers & Qt.ControlModifier); handleObjectClicked(resolvedResult, mouse.button, mouse.modifiers & Qt.ControlModifier);
if (pickResult.objectHit && pickResult.objectHit instanceof Node) { if (pickResult.objectHit && pickResult.objectHit instanceof Node) {
if (transformMode === EditView3D.TransformMode.Move) if (transformMode === EditView3D.TransformMode.Move)
@@ -952,7 +959,7 @@ Item {
} }
} }
} }
onPositionChanged: (mouse)=> { onPositionChanged: (mouse) => {
if (freeDraggerArea) { if (freeDraggerArea) {
if (initialMoveBlock && Math.abs(pressPoint.x - mouse.x) + Math.abs(pressPoint.y - mouse.y) > 10) { if (initialMoveBlock && Math.abs(pressPoint.x - mouse.x) + Math.abs(pressPoint.y - mouse.y) > 10) {
// Don't force press event at actual press, as that puts the gizmo // Don't force press event at actual press, as that puts the gizmo
@@ -977,10 +984,10 @@ Item {
} }
} }
onReleased: (mouse)=> { onReleased: (mouse) => {
handleRelease(mouse); handleRelease(mouse);
} }
onCanceled: (mouse)=> { onCanceled: (mouse) => {
handleRelease(mouse); handleRelease(mouse);
} }
} }

View File

@@ -285,12 +285,12 @@ void Qt5InformationNodeInstanceServer::handleInputEvents()
continue; continue;
} }
} }
auto me = new QMouseEvent(command.type(), command.pos(), command.button(), QMouseEvent me(command.type(), command.pos(), command.button(), command.buttons(),
command.buttons(), command.modifiers()); command.modifiers());
// We must use sendEvent in Qt 6, as using postEvent allows the associated position // We must use sendEvent in Qt 6, as using postEvent allows the associated position
// data stored internally in QMutableEventPoint to potentially be updated by system // data stored internally in QMutableEventPoint to potentially be updated by system
// before the event is delivered. // before the event is delivered.
QGuiApplication::sendEvent(m_editView3DData.window, me); QGuiApplication::sendEvent(m_editView3DData.window, &me);
// Context menu requested // Context menu requested
if (command.button() == Qt::RightButton && command.modifiers() == Qt::NoModifier) if (command.button() == Qt::RightButton && command.modifiers() == Qt::NoModifier)
@@ -428,7 +428,8 @@ void Qt5InformationNodeInstanceServer::getModelAtPos(const QPointF &pos)
QVariant instance = resolvedPick ? instanceForObject(resolvedPick).instanceId() : -1; QVariant instance = resolvedPick ? instanceForObject(resolvedPick).instanceId() : -1;
nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::ModelAtPos, instance}); nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::ModelAtPos, instance});
return; #else
Q_UNUSED(pos)
#endif #endif
} }

View File

@@ -249,6 +249,10 @@ void Edit3DView::customNotification(const AbstractView *view, const QString &ide
void Edit3DView::modelAtPosReady(const ModelNode &modelNode) void Edit3DView::modelAtPosReady(const ModelNode &modelNode)
{ {
if (m_modelAtPosReqType == ModelAtPosReqType::ContextMenu) { if (m_modelAtPosReqType == ModelAtPosReqType::ContextMenu) {
// Make sure right-clicked item is selected. Due to a bug in puppet side right-clicking an item
// while the context-menu is shown doesn't select the item.
if (modelNode.isValid() && !modelNode.isSelected())
setSelectedModelNode(modelNode);
m_edit3DWidget->showContextMenu(m_contextMenuPos, modelNode); m_edit3DWidget->showContextMenu(m_contextMenuPos, modelNode);
} else if (m_modelAtPosReqType == ModelAtPosReqType::MaterialDrop) { } else if (m_modelAtPosReqType == ModelAtPosReqType::MaterialDrop) {
if (m_droppedMaterial.isValid() && modelNode.isValid()) { if (m_droppedMaterial.isValid() && modelNode.isValid()) {

View File

@@ -185,7 +185,12 @@ void Edit3DWidget::createContextMenu()
ModelNodeOperations::editMaterial(selCtx); ModelNodeOperations::editMaterial(selCtx);
}); });
// TODO: add more actions: delete, create, etc m_deleteAction = m_contextMenu->addAction(tr("Delete"), [&] {
view()->executeInTransaction("Edit3DWidget::createContextMenu", [&] {
for (ModelNode &node : m_view->selectedModelNodes())
node.destroy();
});
});
} }
void Edit3DWidget::contextHelp(const Core::IContext::HelpCallback &callback) const void Edit3DWidget::contextHelp(const Core::IContext::HelpCallback &callback) const
@@ -241,6 +246,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode
m_contextMenuTarget = modelNode; m_contextMenuTarget = modelNode;
m_editMaterialAction->setEnabled(modelNode.isValid()); m_editMaterialAction->setEnabled(modelNode.isValid());
m_deleteAction->setEnabled(modelNode.isValid());
m_contextMenu->popup(mapToGlobal(pos)); m_contextMenu->popup(mapToGlobal(pos));
} }

View File

@@ -76,6 +76,7 @@ private:
QPointer<QMenu> m_backgroundColorMenu; QPointer<QMenu> m_backgroundColorMenu;
QPointer<QMenu> m_contextMenu; QPointer<QMenu> m_contextMenu;
QPointer<QAction> m_editMaterialAction; QPointer<QAction> m_editMaterialAction;
QPointer<QAction> m_deleteAction;
ModelNode m_contextMenuTarget; ModelNode m_contextMenuTarget;
}; };