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:
parent
f4ecf9a318
commit
6ebd778875
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
44
src/view/edge_action.hpp
Normal 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
|
@ -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()));
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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"));
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user