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