1
0
mirror of https://github.com/juzzlin/Heimer.git synced 2025-06-11 00:05:21 +08:00

Implement group deletion of edges

This commit is contained in:
Jussi Lind 2024-03-17 13:55:02 +02:00
parent f4ecf9a318
commit 6ebd778875
14 changed files with 311 additions and 152 deletions

View File

@ -7,6 +7,8 @@ New features:
* Search text also in edge labels
* Implement group deletion of edges
Bug fixes:
* Fix GitHub Issue #191: Windows: Text highlighted by the search not visible

View File

@ -70,6 +70,7 @@ set(HEIMER_LIB_SRC
view/menus/tool_bar.cpp
view/menus/widget_factory.cpp
view/mouse_action.cpp
view/node_selection_group.cpp
view/scene_items/edge.cpp
view/scene_items/edge_dot.cpp
view/scene_items/edge_text_edit.cpp
@ -78,7 +79,6 @@ set(HEIMER_LIB_SRC
view/scene_items/node_handle.cpp
view/scene_items/scene_item_base.cpp
view/scene_items/text_edit.cpp
view/node_selection_group.cpp
view/widgets/font_button.cpp
view/widgets/status_label.cpp
)
@ -135,6 +135,7 @@ set(HEIMER_LIB_HDR
view/dialogs/spinner_dialog.hpp
view/dialogs/whats_new_dialog.hpp
view/dialogs/widget_factory.hpp
view/edge_action.hpp
view/edge_selection_group.hpp
view/editor_scene.hpp
view/editor_view.hpp

View File

@ -24,6 +24,7 @@
#include "../infra/export_params.hpp"
#include "../infra/io/file_exception.hpp"
#include "../infra/settings.hpp"
#include "../view/edge_action.hpp"
#include "../view/editor_scene.hpp"
#include "../view/editor_view.hpp"
#include "../view/magic_zoom.hpp"
@ -155,6 +156,11 @@ void ApplicationService::addItem(QGraphicsItem & item, bool adjustSceneRect)
}
}
void ApplicationService::addEdgeToSelectionGroup(EdgeR edge, bool isImplicit)
{
m_editorService->addEdgeToSelectionGroup(edge, isImplicit);
}
void ApplicationService::addNodeToSelectionGroup(NodeR node, bool isImplicit)
{
m_editorService->addNodeToSelectionGroup(node, isImplicit);
@ -562,15 +568,29 @@ void ApplicationService::paste()
}
}
void ApplicationService::performEdgeAction(const EdgeAction & action)
{
juzzlin::L().debug() << "Handling EdgeAction: " << static_cast<int>(action.type());
switch (action.type()) {
case EdgeAction::Type::None:
break;
case EdgeAction::Type::Delete:
saveUndoPoint();
m_editorService->deleteSelectedEdges();
break;
}
}
void ApplicationService::performNodeAction(const NodeAction & action)
{
juzzlin::L().debug() << "Handling NodeAction: " << static_cast<int>(action.type);
juzzlin::L().debug() << "Handling NodeAction: " << static_cast<int>(action.type());
switch (action.type) {
switch (action.type()) {
case NodeAction::Type::None:
break;
case NodeAction::Type::AttachImage: {
const Image image { action.image, action.fileName.toStdString() };
const Image image { action.image(), action.fileName().toStdString() };
const auto id = m_editorService->mindMapData()->imageManager().addImage(image);
if (m_editorService->nodeSelectionGroupSize()) {
saveUndoPoint();
@ -617,14 +637,14 @@ void ApplicationService::performNodeAction(const NodeAction & action)
break;
case NodeAction::Type::SetNodeColor:
saveUndoPoint();
m_editorService->setColorForSelectedNodes(action.color);
m_editorService->setColorForSelectedNodes(action.color());
if (m_editorService->nodeSelectionGroupSize() == 1) {
m_editorService->clearNodeSelectionGroup();
}
break;
case NodeAction::Type::SetTextColor:
saveUndoPoint();
m_editorService->setTextColorForSelectedNodes(action.color);
m_editorService->setTextColorForSelectedNodes(action.color());
if (m_editorService->nodeSelectionGroupSize() == 1) {
m_editorService->clearNodeSelectionGroup();
}
@ -716,7 +736,7 @@ QSize ApplicationService::sceneRectSize() const
return m_editorScene->sceneRect().size().toSize();
}
EdgeP ApplicationService::selectedEdge() const
std::optional<EdgeP> ApplicationService::selectedEdge() const
{
return m_editorService->selectedEdge();
}
@ -726,7 +746,12 @@ std::optional<NodeP> ApplicationService::selectedNode() const
return m_editorService->selectedNode();
}
size_t ApplicationService::selectionGroupSize() const
size_t ApplicationService::edgeSelectionGroupSize() const
{
return m_editorService->edgeSelectionGroupSize();
}
size_t ApplicationService::nodeSelectionGroupSize() const
{
return m_editorService->nodeSelectionGroupSize();
}
@ -826,21 +851,6 @@ size_t ApplicationService::setNodeRectangleSelection(QRectF rect)
return nodesInRectangle;
}
void ApplicationService::setSelectedEdge(EdgeP edge)
{
L().debug() << __func__ << "(): " << reinterpret_cast<uint64_t>(edge);
if (m_editorService->selectedEdge()) {
m_editorService->selectedEdge()->setSelected(false);
}
if (edge) {
edge->setSelected(true);
}
m_editorService->setSelectedEdge(edge);
}
void ApplicationService::setSearchText(QString text)
{
// Leave zoom setting as it is if user has cleared selected nodes and search field.
@ -997,6 +1007,11 @@ void ApplicationService::clearSelectionGroups()
clearNodeSelectionGroup();
}
void ApplicationService::unselectImplicitlySelectedEdges()
{
m_editorService->clearEdgeSelectionGroup(true);
}
void ApplicationService::unselectImplicitlySelectedNodes()
{
m_editorService->clearNodeSelectionGroup(true);

View File

@ -27,12 +27,13 @@
#include "../domain/mind_map_data.hpp"
#include "../view/scene_items/node.hpp"
class MouseAction;
class EdgeAction;
class EditorScene;
class EditorView;
class ExportParams;
class Graph;
class MainWindow;
class MouseAction;
class NodeAction;
class QGraphicsItem;
struct ShadowEffectParams;
@ -59,6 +60,8 @@ public:
void addItem(QGraphicsItem & item, bool adjustSceneRect = true);
void addEdgeToSelectionGroup(EdgeR edge, bool isImplicit = false);
void addNodeToSelectionGroup(NodeR node, bool isImplicit = false);
void adjustSceneRect();
@ -135,6 +138,8 @@ public:
bool openMindMap(QString fileName);
void performEdgeAction(const EdgeAction & action);
void performNodeAction(const NodeAction & action);
void redo();
@ -147,11 +152,13 @@ public:
QSize sceneRectSize() const;
EdgeP selectedEdge() const;
std::optional<EdgeP> selectedEdge() const;
std::optional<NodeP> selectedNode() const;
size_t selectionGroupSize() const;
size_t edgeSelectionGroupSize() const;
size_t nodeSelectionGroupSize() const;
void setEditorView(EditorView & editorView);
@ -163,8 +170,6 @@ public:
//! \returns number of nodes in the current rectangle.
size_t setNodeRectangleSelection(QRectF rect);
void setSelectedEdge(EdgeP edge);
void showStatusText(QString statusText);
void toggleEdgeInSelectionGroup(EdgeR edge);
@ -173,6 +178,8 @@ public:
void undo();
void unselectImplicitlySelectedEdges();
void unselectImplicitlySelectedNodes();
void unselectSelectedNode();

View File

@ -108,8 +108,6 @@ void EditorService::loadMindMapData(QString fileName)
requestAutosave(AutosaveContext::OpenMindMap, false);
clearSelectionGroups();
m_selectedEdge = nullptr;
if (!TestMode::enabled()) {
setMindMapData(m_alzFileIO->fromFile(fileName));
} else {
@ -137,7 +135,6 @@ void EditorService::undo()
{
if (m_undoStack->isUndoable()) {
clearSelectionGroups();
m_selectedEdge = nullptr;
m_dragAndDropNode = nullptr;
saveRedoPoint();
m_mindMapData = m_undoStack->undo();
@ -167,7 +164,6 @@ void EditorService::redo()
{
if (m_undoStack->isRedoable()) {
clearSelectionGroups();
m_selectedEdge = nullptr;
m_dragAndDropNode = nullptr;
saveUndoPoint(true);
m_mindMapData = m_undoStack->redo();
@ -363,16 +359,23 @@ void EditorService::deleteNode(NodeR node)
}
}
void EditorService::deleteSelectedEdges()
{
assert(m_mindMapData);
if (auto && selectedEdges = m_edgeSelectionGroup->edges(); !selectedEdges.empty()) {
m_edgeSelectionGroup->clear();
for (auto && edge : selectedEdges) {
deleteEdge(*edge);
}
}
}
void EditorService::deleteSelectedNodes()
{
assert(m_mindMapData);
const auto selectedNodes = m_nodeSelectionGroup->nodes();
if (selectedNodes.empty()) {
if (SceneItems::Node::lastHoveredNode()) {
deleteNode(*SceneItems::Node::lastHoveredNode());
}
} else {
if (auto && selectedNodes = m_nodeSelectionGroup->nodes(); !selectedNodes.empty()) {
m_nodeSelectionGroup->clear();
for (auto && node : selectedNodes) {
deleteNode(*node);
@ -560,14 +563,9 @@ bool EditorService::nodeHasImageAttached() const
return false;
}
void EditorService::setSelectedEdge(EdgeP edge)
std::optional<EdgeP> EditorService::selectedEdge() const
{
m_selectedEdge = edge;
}
EdgeP EditorService::selectedEdge() const
{
return m_selectedEdge;
return m_edgeSelectionGroup->selectedEdge();
}
std::vector<EdgeP> EditorService::selectedEdges() const

View File

@ -75,6 +75,8 @@ public:
void deleteNode(NodeR node);
void deleteSelectedEdges();
void deleteSelectedNodes();
NodeS addNodeAt(QPointF pos);
@ -156,15 +158,11 @@ public:
void setMindMapData(MindMapDataS newMindMapData);
void setSelectedEdge(EdgeP edge);
void setSelectedNode(NodeP node);
void setImageRefForSelectedNodes(size_t id);
void setTextColorForSelectedNodes(QColor color);
EdgeP selectedEdge() const;
std::optional<EdgeP> selectedEdge() const;
std::vector<EdgeP> selectedEdges() const;
@ -225,8 +223,6 @@ private:
std::unique_ptr<UndoStack> m_undoStack;
EdgeP m_selectedEdge = nullptr;
NodeP m_dragAndDropNode = nullptr;
QPointF m_dragAndDropSourcePos;

View File

@ -114,12 +114,28 @@ void EditorServiceTest::testGroupDisconnection()
QCOMPARE(editorService.mindMapData()->graph().areDirectlyConnected(node2, node1), false);
}
void EditorServiceTest::testGroupDelete()
void EditorServiceTest::testGroupDeletionOfEdges_shouldDeleteSelectedEdges()
{
EditorService editorService;
editorService.setMindMapData(std::make_shared<MindMapData>());
auto node0 = editorService.addNodeAt(QPointF(0, 0));
auto node1 = editorService.addNodeAt(QPointF(1, 1));
const auto node0 = editorService.addNodeAt(QPointF(0, 0));
const auto node1 = editorService.addNodeAt(QPointF(1, 1));
const auto edge0 = std::make_shared<Edge>(node0, node1);
editorService.addEdge(edge0);
editorService.toggleEdgeInSelectionGroup(*edge0);
editorService.deleteSelectedEdges();
QCOMPARE(editorService.edgeSelectionGroupSize(), size_t(0));
QVERIFY(editorService.mindMapData()->graph().getEdges().empty());
}
void EditorServiceTest::testGroupDeletionOfNodes_shouldDeleteSelectedNodes()
{
EditorService editorService;
editorService.setMindMapData(std::make_shared<MindMapData>());
const auto node0 = editorService.addNodeAt(QPointF(0, 0));
const auto node1 = editorService.addNodeAt(QPointF(1, 1));
editorService.toggleNodeInSelectionGroup(*node0);
editorService.toggleNodeInSelectionGroup(*node1);
@ -127,15 +143,15 @@ void EditorServiceTest::testGroupDelete()
editorService.deleteSelectedNodes();
QCOMPARE(editorService.nodeSelectionGroupSize(), size_t(0));
QCOMPARE(editorService.mindMapData()->graph().getNodes().empty(), true);
QVERIFY(editorService.mindMapData()->graph().getNodes().empty());
}
void EditorServiceTest::testGroupMove()
{
EditorService editorService;
editorService.setMindMapData(std::make_shared<MindMapData>());
auto node0 = editorService.addNodeAt(QPointF(0, 0));
auto node1 = editorService.addNodeAt(QPointF(1, 1));
const auto node0 = editorService.addNodeAt(QPointF(0, 0));
const auto node1 = editorService.addNodeAt(QPointF(1, 1));
editorService.toggleNodeInSelectionGroup(*node0);
editorService.toggleNodeInSelectionGroup(*node1);
@ -158,7 +174,39 @@ void EditorServiceTest::testInitializationResetsFileName()
QCOMPARE(editorService.fileName(), QString { "" });
}
void EditorServiceTest::testGroupSelection()
void EditorServiceTest::testGroupSelectionOfEdges()
{
EditorService editorService;
editorService.setMindMapData(std::make_shared<MindMapData>());
const auto node0 = editorService.addNodeAt(QPointF(0, 0));
const auto node1 = editorService.addNodeAt(QPointF(1, 1));
const auto edge = std::make_shared<Edge>(node0, node1);
editorService.addEdge(edge);
QCOMPARE(editorService.edgeSelectionGroupSize(), size_t(0));
editorService.toggleEdgeInSelectionGroup(*edge);
QCOMPARE(editorService.edgeSelectionGroupSize(), size_t(1));
QVERIFY(edge->selected());
editorService.clearEdgeSelectionGroup();
QCOMPARE(editorService.edgeSelectionGroupSize(), size_t(0));
QVERIFY(!edge->selected());
editorService.addEdgeToSelectionGroup(*edge);
QCOMPARE(editorService.edgeSelectionGroupSize(), size_t(1));
QVERIFY(edge->selected());
editorService.loadMindMapData(""); // Doesn't really load anything if in unit tests
QCOMPARE(editorService.edgeSelectionGroupSize(), size_t(0));
}
void EditorServiceTest::testGroupSelectionOfNodes()
{
EditorService editorService;
editorService.setMindMapData(std::make_shared<MindMapData>());
@ -172,21 +220,21 @@ void EditorServiceTest::testGroupSelection()
editorService.toggleNodeInSelectionGroup(*node1);
QCOMPARE(editorService.nodeSelectionGroupSize(), size_t(2));
QCOMPARE(node0->selected(), true);
QCOMPARE(node1->selected(), true);
QVERIFY(node0->selected());
QVERIFY(node1->selected());
editorService.clearNodeSelectionGroup();
QCOMPARE(editorService.nodeSelectionGroupSize(), size_t(0));
QCOMPARE(node0->selected(), false);
QCOMPARE(node1->selected(), false);
QVERIFY(!node0->selected());
QVERIFY(!node1->selected());
editorService.addNodeToSelectionGroup(*node0);
editorService.addNodeToSelectionGroup(*node1);
QCOMPARE(editorService.nodeSelectionGroupSize(), size_t(2));
QCOMPARE(node0->selected(), true);
QCOMPARE(node1->selected(), true);
QVERIFY(node0->selected());
QVERIFY(node1->selected());
editorService.loadMindMapData(""); // Doesn't really load anything if in unit tests
@ -203,7 +251,7 @@ void EditorServiceTest::testLoadState()
const auto edge01 = editorService.addEdge(std::make_shared<Edge>(node0, node1));
editorService.toggleNodeInSelectionGroup(*node0);
editorService.setSelectedEdge(edge01.get());
editorService.addEdgeToSelectionGroup(*edge01.get());
editorService.saveUndoPoint();
QCOMPARE(editorService.isUndoable(), true);
@ -214,7 +262,7 @@ void EditorServiceTest::testLoadState()
QCOMPARE(editorService.fileName(), fileName);
QCOMPARE(editorService.isUndoable(), false);
QCOMPARE(editorService.nodeSelectionGroupSize(), size_t(0));
QCOMPARE(editorService.selectedEdge(), nullptr);
QVERIFY(!editorService.selectedEdge().has_value());
QVERIFY(!editorService.selectedNode().has_value());
}
@ -880,7 +928,7 @@ void EditorServiceTest::testUndoState()
const auto edge01 = editorService.addEdge(std::make_shared<Edge>(node0, node1));
editorService.toggleNodeInSelectionGroup(*node0);
editorService.setSelectedEdge(edge01.get());
editorService.addEdgeToSelectionGroup(*edge01.get());
editorService.saveUndoPoint();
QCOMPARE(editorService.isUndoable(), true);
@ -889,7 +937,7 @@ void EditorServiceTest::testUndoState()
QCOMPARE(editorService.isUndoable(), false);
QCOMPARE(editorService.nodeSelectionGroupSize(), size_t(0));
QCOMPARE(editorService.selectedEdge(), nullptr);
QVERIFY(!editorService.selectedEdge().has_value());
QVERIFY(!editorService.selectedNode().has_value());
}
@ -903,7 +951,7 @@ void EditorServiceTest::testRedoState()
const auto edge01 = editorService.addEdge(std::make_shared<Edge>(node0, node1));
editorService.toggleNodeInSelectionGroup(*node0);
editorService.setSelectedEdge(edge01.get());
editorService.addEdgeToSelectionGroup(*edge01.get());
editorService.saveUndoPoint();
QCOMPARE(editorService.isUndoable(), true);
@ -911,13 +959,13 @@ void EditorServiceTest::testRedoState()
editorService.undo();
editorService.toggleNodeInSelectionGroup(*node0);
editorService.setSelectedEdge(edge01.get());
editorService.addEdgeToSelectionGroup(*edge01.get());
editorService.redo();
QCOMPARE(editorService.isUndoable(), true);
QCOMPARE(editorService.nodeSelectionGroupSize(), size_t(0));
QCOMPARE(editorService.selectedEdge(), nullptr);
QVERIFY(!editorService.selectedEdge().has_value());
QVERIFY(!editorService.selectedNode().has_value());
editorService.undo();

View File

@ -35,11 +35,15 @@ private slots:
void testGroupDisconnection();
void testGroupDelete();
void testGroupDeletionOfEdges_shouldDeleteSelectedEdges();
void testGroupDeletionOfNodes_shouldDeleteSelectedNodes();
void testGroupMove();
void testGroupSelection();
void testGroupSelectionOfEdges();
void testGroupSelectionOfNodes();
void testInitializationResetsFileName();

View File

@ -161,18 +161,18 @@ void SelectionGroupTest::testAddNodes_Explicit()
NodeSelectionGroup selectionGroup;
selectionGroup.add(*node1);
selectionGroup.add(*node2);
QCOMPARE(selectionGroup.contains(*node1), true);
QCOMPARE(node1->selected(), true);
QCOMPARE(selectionGroup.contains(*node2), true);
QCOMPARE(node2->selected(), true);
QVERIFY(selectionGroup.contains(*node1));
QVERIFY(node1->selected());
QVERIFY(selectionGroup.contains(*node2));
QVERIFY(node2->selected());
selectionGroup.clear();
QCOMPARE(selectionGroup.contains(*node1), false);
QCOMPARE(node1->selected(), false);
QCOMPARE(selectionGroup.contains(*node2), false);
QCOMPARE(node2->selected(), false);
QVERIFY(!selectionGroup.contains(*node1));
QVERIFY(!node1->selected());
QVERIFY(!selectionGroup.contains(*node2));
QVERIFY(!node2->selected());
QCOMPARE(selectionGroup.size(), size_t(0));
}
@ -182,15 +182,15 @@ void SelectionGroupTest::testAddNodes_Implicit()
NodeSelectionGroup selectionGroup;
selectionGroup.add(*node, true);
QCOMPARE(selectionGroup.contains(*node), true);
QCOMPARE(node->selected(), true);
QVERIFY(selectionGroup.contains(*node));
QVERIFY(node->selected());
QCOMPARE(selectionGroup.size(), size_t(1));
selectionGroup.clear(true);
QCOMPARE(selectionGroup.contains(*node), false);
QCOMPARE(node->selected(), false);
QVERIFY(!selectionGroup.contains(*node));
QVERIFY(!node->selected());
QCOMPARE(selectionGroup.size(), size_t(0));
}
@ -202,27 +202,27 @@ void SelectionGroupTest::testAddNodes_ImplicitAndExplicit()
NodeSelectionGroup selectionGroup;
selectionGroup.add(*node1, true);
selectionGroup.add(*node2, false);
QCOMPARE(selectionGroup.contains(*node1), true);
QCOMPARE(node1->selected(), true);
QCOMPARE(selectionGroup.contains(*node2), true);
QCOMPARE(node2->selected(), true);
QVERIFY(selectionGroup.contains(*node1));
QVERIFY(node1->selected());
QVERIFY(selectionGroup.contains(*node2));
QVERIFY(node2->selected());
QCOMPARE(selectionGroup.size(), size_t(2));
selectionGroup.clear(true);
QCOMPARE(selectionGroup.contains(*node1), false);
QCOMPARE(node1->selected(), false);
QCOMPARE(selectionGroup.contains(*node2), true);
QCOMPARE(node2->selected(), true);
QVERIFY(!selectionGroup.contains(*node1));
QVERIFY(!node1->selected());
QVERIFY(selectionGroup.contains(*node2));
QVERIFY(node2->selected());
QCOMPARE(selectionGroup.size(), size_t(1));
selectionGroup.clear();
QCOMPARE(selectionGroup.contains(*node1), false);
QCOMPARE(node1->selected(), false);
QCOMPARE(selectionGroup.contains(*node2), false);
QCOMPARE(node2->selected(), false);
QVERIFY(!selectionGroup.contains(*node1));
QVERIFY(!node1->selected());
QVERIFY(!selectionGroup.contains(*node2));
QVERIFY(!node2->selected());
QCOMPARE(selectionGroup.size(), size_t(0));
}
@ -268,14 +268,14 @@ void SelectionGroupTest::testToggleNode()
NodeSelectionGroup selectionGroup;
selectionGroup.toggle(*node);
QCOMPARE(selectionGroup.contains(*node), true);
QCOMPARE(node->selected(), true);
QVERIFY(selectionGroup.contains(*node));
QVERIFY(node->selected());
selectionGroup.toggle(*node);
QCOMPARE(selectionGroup.contains(*node), false);
QCOMPARE(node->selected(), false);
QVERIFY(!selectionGroup.contains(*node));
QVERIFY(!node->selected());
}
QTEST_GUILESS_MAIN(SelectionGroupTest)

44
src/view/edge_action.hpp Normal file
View File

@ -0,0 +1,44 @@
// This file is part of Heimer.
// Copyright (C) 2024 Jussi Lind <jussi.lind@iki.fi>
//
// Heimer is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Heimer is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Heimer. If not, see <http://www.gnu.org/licenses/>.
#ifndef EDGE_ACTION_HPP
#define EDGE_ACTION_HPP
class EdgeAction
{
public:
enum class Type
{
None,
Delete,
};
EdgeAction(Type type)
: m_type(type)
{
}
Type type() const;
private:
Type m_type = Type::None;
};
inline EdgeAction::Type EdgeAction::type() const
{
return m_type;
}
#endif // EDGE_ACTION_HPP

View File

@ -142,7 +142,7 @@ void EditorView::handlePrimaryButtonClickOnNode(NodeR node)
SC::instance().applicationService()->toggleNodeInSelectionGroup(node);
} else {
// Clear selection group if the node is not in it
if (SC::instance().applicationService()->selectionGroupSize() && !SC::instance().applicationService()->isInSelectionGroup(node)) {
if (SC::instance().applicationService()->nodeSelectionGroupSize() && !SC::instance().applicationService()->isInSelectionGroup(node)) {
SC::instance().applicationService()->clearNodeSelectionGroup();
}
@ -177,9 +177,15 @@ void EditorView::handlePrimaryButtonClickOnNodeHandle(SceneItems::NodeHandle & n
void EditorView::handleSecondaryButtonClickOnEdge(EdgeR edge)
{
SC::instance().applicationService()->setSelectedEdge(&edge);
if (!edge.selected()) {
SC::instance().applicationService()->clearEdgeSelectionGroup();
}
SC::instance().applicationService()->addEdgeToSelectionGroup(edge, true);
openEdgeContextMenu();
SC::instance().applicationService()->unselectImplicitlySelectedEdges();
}
void EditorView::handleSecondaryButtonClickOnNode(NodeR node)
@ -199,7 +205,6 @@ void EditorView::initiateBackgroundDrag()
{
juzzlin::L().debug() << "Initiating background drag";
SC::instance().applicationService()->setSelectedEdge(nullptr);
SC::instance().applicationService()->mouseAction().setSourceNode(nullptr, MouseAction::Action::Scroll);
setDragMode(ScrollHandDrag);
@ -263,7 +268,7 @@ void EditorView::mouseMoveEvent(QMouseEvent * event)
break;
case MouseAction::Action::MoveNode:
if (const auto node = SC::instance().applicationService()->mouseAction().sourceNode()) {
if (SC::instance().applicationService()->selectionGroupSize()) {
if (SC::instance().applicationService()->nodeSelectionGroupSize()) {
SC::instance().applicationService()->moveSelectionGroup(*node, m_grid.snapToGrid(m_mappedPos - SC::instance().applicationService()->mouseAction().sourcePosOnNode()));
} else {
node->setLocation(m_grid.snapToGrid(m_mappedPos - SC::instance().applicationService()->mouseAction().sourcePosOnNode()));

View File

@ -17,6 +17,7 @@
#include "../../application/application_service.hpp"
#include "../../application/service_container.hpp"
#include "../edge_action.hpp"
#include "../scene_items/edge.hpp"
#include <cassert>
@ -28,41 +29,47 @@ EdgeContextMenu::EdgeContextMenu(QWidget * parent)
{
const auto changeEdgeDirectionAction(new QAction(tr("Change direction"), this));
QObject::connect(changeEdgeDirectionAction, &QAction::triggered, this, [=] {
SC::instance().applicationService()->saveUndoPoint();
SC::instance().applicationService()->selectedEdge()->setReversed(!SC::instance().applicationService()->selectedEdge()->reversed());
if (const auto selectedEdge = SC::instance().applicationService()->selectedEdge(); selectedEdge.has_value()) {
SC::instance().applicationService()->saveUndoPoint();
selectedEdge.value()->setReversed(!selectedEdge.value()->reversed());
}
});
using SceneItems::EdgeModel;
const auto showEdgeArrowAction(new QAction(tr("Show arrow"), this));
QObject::connect(showEdgeArrowAction, &QAction::triggered, this, [=] {
SC::instance().applicationService()->saveUndoPoint();
SC::instance().applicationService()->selectedEdge()->setArrowMode(showEdgeArrowAction->isChecked() ? EdgeModel::ArrowMode::Single : EdgeModel::ArrowMode::Hidden);
if (const auto selectedEdge = SC::instance().applicationService()->selectedEdge(); selectedEdge.has_value()) {
SC::instance().applicationService()->saveUndoPoint();
selectedEdge.value()->setArrowMode(showEdgeArrowAction->isChecked() ? EdgeModel::ArrowMode::Single : EdgeModel::ArrowMode::Hidden);
}
});
showEdgeArrowAction->setCheckable(true);
const auto doubleArrowAction(new QAction(tr("Double arrow"), this));
QObject::connect(doubleArrowAction, &QAction::triggered, this, [=] {
SC::instance().applicationService()->saveUndoPoint();
SC::instance().applicationService()->selectedEdge()->setArrowMode(doubleArrowAction->isChecked() ? EdgeModel::ArrowMode::Double : EdgeModel::ArrowMode::Single);
if (const auto selectedEdge = SC::instance().applicationService()->selectedEdge(); selectedEdge.has_value()) {
SC::instance().applicationService()->saveUndoPoint();
selectedEdge.value()->setArrowMode(doubleArrowAction->isChecked() ? EdgeModel::ArrowMode::Double : EdgeModel::ArrowMode::Single);
}
});
doubleArrowAction->setCheckable(true);
const auto dashedLineAction(new QAction(tr("Dashed line"), this));
QObject::connect(dashedLineAction, &QAction::triggered, this, [=] {
SC::instance().applicationService()->saveUndoPoint();
SC::instance().applicationService()->selectedEdge()->setDashedLine(dashedLineAction->isChecked());
if (const auto selectedEdge = SC::instance().applicationService()->selectedEdge(); selectedEdge.has_value()) {
SC::instance().applicationService()->saveUndoPoint();
selectedEdge.value()->setDashedLine(dashedLineAction->isChecked());
}
});
dashedLineAction->setCheckable(true);
const auto deleteEdgeAction(new QAction(tr("Delete edge"), this));
QObject::connect(deleteEdgeAction, &QAction::triggered, this, [=] {
SC::instance().applicationService()->setSelectedEdge(nullptr);
SC::instance().applicationService()->saveUndoPoint();
// Use a separate variable and timer here because closing the menu will always nullify the selected edge
QTimer::singleShot(0, this, [=] {
SC::instance().applicationService()->deleteEdge(*m_selectedEdge);
});
if (SC::instance().applicationService()->edgeSelectionGroupSize()) {
SC::instance().applicationService()->saveUndoPoint();
SC::instance().applicationService()->performEdgeAction({ EdgeAction::Type::Delete });
}
});
// Populate the menu
@ -78,18 +85,18 @@ EdgeContextMenu::EdgeContextMenu(QWidget * parent)
// Set correct edge config when the menu opens.
connect(this, &QMenu::aboutToShow, this, [=] {
m_selectedEdge = SC::instance().applicationService()->selectedEdge();
assert(m_selectedEdge);
changeEdgeDirectionAction->setEnabled(m_selectedEdge->arrowMode() != EdgeModel::ArrowMode::Double && m_selectedEdge->arrowMode() != EdgeModel::ArrowMode::Hidden);
dashedLineAction->setChecked(m_selectedEdge->dashedLine());
doubleArrowAction->setChecked(m_selectedEdge->arrowMode() == EdgeModel::ArrowMode::Double);
showEdgeArrowAction->setChecked(m_selectedEdge->arrowMode() != EdgeModel::ArrowMode::Hidden);
if (const auto selectedEdge = SC::instance().applicationService()->selectedEdge(); selectedEdge.has_value()) {
changeEdgeDirectionAction->setEnabled(selectedEdge.value()->arrowMode() != EdgeModel::ArrowMode::Double && selectedEdge.value()->arrowMode() != EdgeModel::ArrowMode::Hidden);
dashedLineAction->setChecked(selectedEdge.value()->dashedLine());
doubleArrowAction->setChecked(selectedEdge.value()->arrowMode() == EdgeModel::ArrowMode::Double);
showEdgeArrowAction->setChecked(selectedEdge.value()->arrowMode() != EdgeModel::ArrowMode::Hidden);
}
});
// Always clear edge selection when the menu closes.
connect(this, &QMenu::aboutToHide, [=] {
QTimer::singleShot(0, this, [=] {
SC::instance().applicationService()->setSelectedEdge(nullptr);
SC::instance().applicationService()->clearEdgeSelectionGroup();
});
});
}

View File

@ -17,6 +17,7 @@
#include "../../application/application_service.hpp"
#include "../../application/service_container.hpp"
#include "../edge_action.hpp"
#include "../grid.hpp"
#include "../mouse_action.hpp"
#include "../node_action.hpp"
@ -106,8 +107,9 @@ MainContextMenu::MainContextMenu(QWidget * parent, Grid & grid)
// is open. As a "solution" we create another shortcut and add it to the parent widget.
const auto deleteNodeSequence = QKeySequence(QKeySequence::Delete);
deleteNodeAction->setShortcut(deleteNodeSequence);
const auto deleteNodeShortCut = new QShortcut(deleteNodeSequence, parent);
connect(deleteNodeShortCut, &QShortcut::activated, this, [] {
const auto deleteNodesAndEdgesShortCut = new QShortcut(deleteNodeSequence, parent);
connect(deleteNodesAndEdgesShortCut, &QShortcut::activated, this, [] {
SC::instance().applicationService()->performEdgeAction({ EdgeAction::Type::Delete });
SC::instance().applicationService()->performNodeAction({ NodeAction::Type::Delete });
});
connect(deleteNodeAction, &QAction::triggered, this, [] {
@ -165,8 +167,8 @@ void MainContextMenu::setMode(const Mode & mode)
m_colorMenuAction->setText(mode == Mode::Node ? tr("Node &colors") : tr("General &colors"));
m_copyNodeAction->setEnabled(SC::instance().applicationService()->selectionGroupSize());
m_copyNodeAction->setText(SC::instance().applicationService()->selectionGroupSize() > 1 ? tr("Copy nodes") : tr("Copy node"));
m_copyNodeAction->setEnabled(SC::instance().applicationService()->nodeSelectionGroupSize());
m_copyNodeAction->setText(SC::instance().applicationService()->nodeSelectionGroupSize() > 1 ? tr("Copy nodes") : tr("Copy node"));
m_pasteNodeAction->setEnabled(SC::instance().applicationService()->copyStackSize());
m_pasteNodeAction->setText(SC::instance().applicationService()->copyStackSize() > 1 ? tr("Paste nodes") : tr("Paste node"));

View File

@ -19,8 +19,9 @@
#include <QColor>
#include <QImage>
struct NodeAction
class NodeAction
{
public:
enum class Type
{
None,
@ -38,30 +39,59 @@ struct NodeAction
};
NodeAction(Type type)
: type(type)
: m_type(type)
{
}
NodeAction(Type type, QColor color)
: type(type)
, color(color)
: m_type(type)
, m_color(color)
{
}
NodeAction(Type type, QImage image, QString fileName)
: type(type)
, image(image)
, fileName(fileName)
: m_type(type)
, m_image(image)
, m_fileName(fileName)
{
}
Type type = Type::None;
Type type() const;
QColor color = Qt::white;
QColor color() const;
QImage image;
QImage image() const;
QString fileName;
QString fileName() const;
private:
Type m_type = Type::None;
QColor m_color = Qt::white;
QImage m_image;
QString m_fileName;
};
inline NodeAction::Type NodeAction::type() const
{
return m_type;
}
inline QColor NodeAction::color() const
{
return m_color;
}
inline QImage NodeAction::image() const
{
return m_image;
}
inline QString NodeAction::fileName() const
{
return m_fileName;
}
#endif // NODE_ACTION_HPP