INSTINCT Code Coverage Report


Directory: src/
File: internal/gui/GlobalActions.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 0 126 0.0%
Functions: 0 5 0.0%
Branches: 0 378 0.0%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 #include "GlobalActions.hpp"
10
11 #include "internal/FlowManager.hpp"
12
13 #include "internal/gui/NodeEditorApplication.hpp"
14
15 #include <imgui_node_editor.h>
16 #include <imgui_node_editor_internal.h>
17 namespace ed = ax::NodeEditor;
18
19 #include <nlohmann/json.hpp>
20 using json = nlohmann::json; ///< json namespace
21 #include "util/Json.hpp"
22
23 #include "internal/ConfigManager.hpp"
24
25 #include <vector>
26 #include <deque>
27 #include <limits>
28 #include <iterator>
29
30 namespace NAV::gui
31 {
32 namespace
33 {
34 /// @brief Specifies if the elements in the clipboard are cutted or copied
35 bool elementsCutted = false;
36 /// @brief Clipboard storage
37 json clipboard;
38
39 } // namespace
40 } // namespace NAV::gui
41
42 ✗ bool NAV::gui::canCutOrCopyFlowElements()
43 {
44 ✗ return static_cast<bool>(ed::GetSelectedNodes(nullptr, ed::GetSelectedObjectCount()));
45 }
46
47 ✗ bool NAV::gui::canPasteFlowElements()
48 {
49 ✗ return !clipboard.empty();
50 }
51
52 ✗ void NAV::gui::cutFlowElements()
53 {
54 ✗ std::vector<ax::NodeEditor::NodeId> selectedNodeIds;
55 ✗ selectedNodeIds.resize(static_cast<size_t>(ed::GetSelectedObjectCount()));
56
57 ✗ auto selectedNodesCount = ed::GetSelectedNodes(selectedNodeIds.data(), ed::GetSelectedObjectCount());
58 ✗ selectedNodeIds.resize(static_cast<size_t>(selectedNodesCount));
59
60 ✗ clipboard.clear();
61
62 ✗ for (const auto& nodeId : selectedNodeIds)
63 {
64 ✗ const NAV::Node* node = flow::FindNode(nodeId);
65
66 ✗ clipboard["nodes"]["node-" + std::to_string(size_t(node->id))] = *node;
67 ✗ clipboard["nodes"]["node-" + std::to_string(size_t(node->id))]["data"] = node->save();
68
69 ✗ for (const auto& outputPin : node->outputPins)
70 {
71 ✗ for (const auto& link : outputPin.links)
72 {
73 ✗ auto& j = clipboard["links"]["link-" + std::to_string(size_t(link.linkId))];
74 ✗ j["id"] = size_t(link.linkId);
75 ✗ j["startPinId"] = size_t(outputPin.id);
76 ✗ j["endPinId"] = size_t(link.connectedPinId);
77 }
78 }
79
80 ✗ flow::DeleteNode(nodeId);
81 }
82
83 ✗ elementsCutted = true;
84 ✗ }
85
86 ✗ void NAV::gui::copyFlowElements()
87 {
88 ✗ std::vector<ax::NodeEditor::NodeId> selectedNodeIds;
89 ✗ selectedNodeIds.resize(static_cast<size_t>(ed::GetSelectedObjectCount()));
90
91 ✗ auto selectedNodesCount = ed::GetSelectedNodes(selectedNodeIds.data(), ed::GetSelectedObjectCount());
92 ✗ selectedNodeIds.resize(static_cast<size_t>(selectedNodesCount));
93
94 ✗ clipboard.clear();
95
96 ✗ for (const auto& nodeId : selectedNodeIds)
97 {
98 ✗ const NAV::Node* node = flow::FindNode(nodeId);
99
100 ✗ clipboard["nodes"]["node-" + std::to_string(size_t(node->id))] = *node;
101 ✗ clipboard["nodes"]["node-" + std::to_string(size_t(node->id))]["data"] = node->save();
102
103 ✗ for (const auto& outputPin : node->outputPins)
104 {
105 ✗ for (const auto& link : outputPin.links)
106 {
107 ✗ auto& j = clipboard["links"]["link-" + std::to_string(size_t(link.linkId))];
108 ✗ j["id"] = size_t(link.linkId);
109 ✗ j["startPinId"] = size_t(outputPin.id);
110 ✗ j["endPinId"] = size_t(link.connectedPinId);
111 }
112 }
113 }
114
115 ✗ elementsCutted = false;
116 ✗ }
117
118 ✗ void NAV::gui::pasteFlowElements()
119 {
120 // Store the node count to later iterate over the new nodes
121 ✗ auto nodeCountBeforeLoad = flow::m_Nodes().size();
122
123 ✗ LOG_DEBUG("Pasting clipboard {}", clipboard.dump(4));
124
125 ✗ flow::LoadJson(clipboard, !elementsCutted);
126
127 // Find Top Left Position of all new nodes to move them to the mouse cursor
128 ✗ ImVec2 leftTopMostPos{ std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity() };
129 ✗ if (clipboard.contains("nodes"))
130 {
131 ✗ for (const auto& nodeJson : clipboard.at("nodes"))
132 {
133 ✗ ImVec2 pos;
134 ✗ if (nodeJson.contains("pos"))
135 {
136 ✗ nodeJson.at("pos").get_to(pos);
137
138 ✗ leftTopMostPos.x = std::min(pos.x, leftTopMostPos.x);
139 ✗ leftTopMostPos.y = std::min(pos.y, leftTopMostPos.y);
140 }
141 }
142 }
143
144 // Get Mouse Position in editor coordinates
145 ✗ auto viewRect = reinterpret_cast<ax::NodeEditor::Detail::EditorContext*>(ed::GetCurrentEditor())->GetViewRect();
146 ✗ ImVec2 mousePos = ImGui::GetMousePos();
147 ✗ mousePos.x -= NodeEditorApplication::leftPaneWidth + NodeEditorApplication::SPLITTER_THICKNESS + 10.0F;
148 ✗ mousePos.y -= NodeEditorApplication::menuBarHeight;
149 ✗ mousePos *= ed::GetCurrentZoom();
150 ✗ mousePos += viewRect.GetTL();
151
152 // Move the Nodes relative to the current mouse position
153 ✗ for (size_t i = nodeCountBeforeLoad; i < flow::m_Nodes().size(); i++)
154 {
155 ✗ auto* node = flow::m_Nodes().at(i);
156 ✗ ed::SetNodePosition(node->id, mousePos + (ed::GetNodePosition(node->id) - leftTopMostPos));
157 }
158
159 // Collect the node ids which get new links to call the restoreAfterLinks function on them
160 ✗ std::map<size_t, ed::NodeId> newlyLinkedNodes;
161
162 // Recreate links
163 ✗ if (clipboard.contains("links"))
164 {
165 ✗ for (const auto& linkJson : clipboard.at("links"))
166 {
167 ✗ auto startPinId = linkJson.at("startPinId").get<size_t>();
168 ✗ auto endPinId = linkJson.at("endPinId").get<size_t>();
169
170 ✗ size_t startPinOldParentNodeId = 0;
171 ✗ size_t startPinParentNodeIndex = 0;
172 ✗ size_t startPinIndex = 0;
173 ✗ Pin::Kind startPinKind = Pin::Kind::None;
174
175 ✗ size_t endPinOldParentNodeId = 0;
176 ✗ size_t endPinParentNodeIndex = 0;
177 ✗ size_t endPinIndex = 0;
178 ✗ Pin::Kind endPinKind = Pin::Kind::None;
179
180 // Search for the nodes and pins which where connected by the old link
181 ✗ if (clipboard.contains("nodes"))
182 {
183 ✗ size_t nodeIndex = 0;
184 ✗ for (const auto& nodeJson : clipboard.at("nodes"))
185 {
186 ✗ if (nodeJson.contains("inputPins"))
187 {
188 ✗ size_t pinIndex = 0;
189 ✗ for (const auto& pinJson : nodeJson.at("inputPins"))
190 {
191 ✗ if (pinJson.at("id").get<size_t>() == startPinId)
192 {
193 ✗ startPinOldParentNodeId = nodeJson.at("id");
194 ✗ startPinParentNodeIndex = nodeCountBeforeLoad + nodeIndex;
195 ✗ startPinIndex = pinIndex;
196 ✗ startPinKind = Pin::Kind::Input;
197 }
198 ✗ if (pinJson.at("id").get<size_t>() == endPinId)
199 {
200 ✗ endPinOldParentNodeId = nodeJson.at("id");
201 ✗ endPinParentNodeIndex = nodeCountBeforeLoad + nodeIndex;
202 ✗ endPinIndex = pinIndex;
203 ✗ endPinKind = Pin::Kind::Input;
204 }
205 ✗ pinIndex++;
206 }
207 }
208 ✗ if (nodeJson.contains("outputPins"))
209 {
210 ✗ size_t pinIndex = 0;
211 ✗ for (const auto& pinJson : nodeJson.at("outputPins"))
212 {
213 ✗ if (pinJson.at("id").get<size_t>() == startPinId)
214 {
215 ✗ startPinOldParentNodeId = nodeJson.at("id");
216 ✗ startPinParentNodeIndex = nodeCountBeforeLoad + nodeIndex;
217 ✗ startPinIndex = pinIndex;
218 ✗ startPinKind = Pin::Kind::Output;
219 }
220 ✗ if (pinJson.at("id").get<size_t>() == endPinId)
221 {
222 ✗ endPinOldParentNodeId = nodeJson.at("id");
223 ✗ endPinParentNodeIndex = nodeCountBeforeLoad + nodeIndex;
224 ✗ endPinIndex = pinIndex;
225 ✗ endPinKind = Pin::Kind::Output;
226 }
227 ✗ pinIndex++;
228 }
229 }
230 ✗ nodeIndex++;
231 }
232 }
233
234 ✗ if (startPinKind != Pin::Kind::None && endPinKind != Pin::Kind::None)
235 {
236 ✗ if (startPinKind == Pin::Kind::Output && endPinKind == Pin::Kind::Input)
237 {
238 ✗ auto& startPin = flow::m_Nodes().at(startPinParentNodeIndex)->outputPins.at(startPinIndex);
239 ✗ auto& endPin = flow::m_Nodes().at(endPinParentNodeIndex)->inputPins.at(endPinIndex);
240
241 ✗ if (!endPin.isPinLinked())
242 {
243 ✗ startPin.createLink(endPin);
244 }
245 }
246
247 ✗ newlyLinkedNodes[startPinOldParentNodeId] = flow::m_Nodes().at(startPinParentNodeIndex)->id;
248 ✗ newlyLinkedNodes[endPinOldParentNodeId] = flow::m_Nodes().at(endPinParentNodeIndex)->id;
249 }
250 }
251 }
252 ✗ if (clipboard.contains("nodes"))
253 {
254 ✗ for (auto [oldId, newId] : newlyLinkedNodes)
255 {
256 ✗ auto* node = flow::FindNode(newId);
257
258 ✗ if (clipboard.at("nodes").contains("node-" + std::to_string(oldId)))
259 {
260 ✗ [[maybe_unused]] auto* oldNode = flow::FindNode(oldId);
261
262 ✗ LOG_DEBUG("Calling restoreAtferLink() for new node '{}', which was copied from node '{}'", node->nameId(), oldNode->nameId());
263
264 ✗ const auto& nodeJson = clipboard.at("nodes").at("node-" + std::to_string(oldId));
265 ✗ if (nodeJson.contains("data"))
266 {
267 ✗ node->restoreAtferLink(nodeJson.at("data"));
268 }
269 }
270 }
271 }
272
273 ✗ elementsCutted = false;
274 ✗ }
275