Power System Platform  2026w23a-beta
Loading...
Searching...
No Matches
ControlEditor.cpp
1/*
2 * Copyright (C) 2017 Thales Lima Oliveira <thales@ufu.br>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "ControlEditor.h"
19
20#include<wx/settings.h>
21
22#include "../elements/controlElement/ConnectionLine.h"
23#include "../elements/controlElement/Constant.h"
24#include "../elements/controlElement/ControlElement.h"
25#include "../elements/controlElement/Divider.h"
26#include "../elements/controlElement/Exponential.h"
27#include "../elements/controlElement/Gain.h"
28#include "../elements/controlElement/Limiter.h"
29#include "../elements/controlElement/MathExpression.h"
30#include "../elements/controlElement/MathOperation.h"
31#include "../elements/controlElement/Multiplier.h"
32#include "../elements/controlElement/RateLimiter.h"
33#include "../elements/controlElement/Sum.h"
34#include "../elements/controlElement/TransferFunction.h"
35#include "../elements/controlElement/ControlElementContainer.h"
36#include "../elements/controlElement/ControlElementSolver.h"
37
38#ifdef USING_WX_3_0_X
39#include "utils/DegreesAndRadians.h"
40#endif
41#include "../utils/Camera.h"
42#include "../utils/FileHanding.h"
43#include "../utils/Path.h"
44
45#include "ChartView.h"
46#include "../utils/ElementPlotData.h"
47
48#include "../elements/ControlElementDataObject.h"
49
50namespace {
51void SanitizeControlGraph(const std::vector<std::shared_ptr<ControlElement>>& elementList,
52 const std::vector<std::shared_ptr<ConnectionLine>>& connectionList)
53{
54 std::unordered_set<Element*> elementSet;
55 std::unordered_set<ConnectionLine*> lineSet;
56 elementSet.reserve(elementList.size());
57 lineSet.reserve(connectionList.size());
58
59 for (const auto& element : elementList) elementSet.insert(element.get());
60 for (const auto& line : connectionList) lineSet.insert(line.get());
61
62 for (const auto& element : elementList) {
63 std::vector<Element*> children = element->GetChildList();
64 for (Element* child : children) {
65 auto* childLine = dynamic_cast<ConnectionLine*>(child);
66 if (!childLine || !lineSet.contains(childLine)) {
67 element->RemoveChild(child);
68 }
69 }
70 }
71
72 for (const auto& line : connectionList) {
73 std::vector<Element*> parents = line->GetParentList();
74 for (Element* parent : parents) {
75 auto* parentElement = dynamic_cast<ControlElement*>(parent);
76 if (!parentElement || !elementSet.contains(parentElement)) {
77 line->RemoveParent(parent);
78 }
79 }
80
81 ConnectionLine* parentLine = line->GetParentLine();
82 if (parentLine && !lineSet.contains(parentLine)) {
83 line->SetParentLine(nullptr);
84 }
85
86 std::vector<Element*> children = line->GetChildList();
87 for (Element* child : children) {
88 auto* childLine = dynamic_cast<ConnectionLine*>(child);
89 if (!childLine || !lineSet.contains(childLine)) {
90 line->RemoveChild(child);
91 }
92 }
93 }
94}
95} // namespace
96
97ControlElementButton::ControlElementButton(wxWindow* parent, wxString label, wxImage image, wxWindowID id, GUIColour* guiColour)
98 : wxWindow(parent, id), m_guiColour(guiColour)
99{
100 SetBackgroundColour(m_guiColour->background);
101 m_buttonFont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
102 m_label = label;
103 m_image = image;
104 m_imageSize = wxSize(image.GetWidth(), image.GetHeight());
105
106 // Calculate label size.
107 wxScreenDC dc;
108 dc.SetFont(m_buttonFont);
109 wxSize textSize = dc.GetTextExtent(label);
110
111 int buttonWidth = 0;
112 if (textSize.GetWidth() > m_imageSize.GetWidth()) {
113 buttonWidth = textSize.GetWidth();
114 m_imagePosition = wxPoint((buttonWidth - m_imageSize.GetWidth()) / 2 + m_borderSize, m_borderSize);
115 m_labelPosition = wxPoint(m_borderSize, m_imageSize.GetHeight() + m_borderSize);
116 }
117 else {
118 buttonWidth = m_imageSize.GetWidth();
119 m_imagePosition = wxPoint(m_borderSize, m_borderSize);
120 m_labelPosition =
121 wxPoint((buttonWidth - textSize.GetWidth()) / 2 + m_borderSize, m_imageSize.GetHeight() + m_borderSize);
122 }
123 m_buttonSize =
124 wxSize(buttonWidth + 2 * m_borderSize, textSize.GetHeight() + m_imageSize.GetHeight() + 2 * m_borderSize);
125 SetMinSize(m_buttonSize + wxSize(m_borderSize, m_borderSize));
126
127 // Events.
128 Bind(wxEVT_PAINT, &ControlElementButton::OnPaint, this);
129 Bind(wxEVT_ENTER_WINDOW, &ControlElementButton::OnMouseEnter, this);
130 Bind(wxEVT_LEAVE_WINDOW, &ControlElementButton::OnMouseLeave, this);
131 Bind(wxEVT_LEFT_DOWN, &ControlElementButton::OnLeftClickDown, this);
132 Bind(wxEVT_LEFT_UP, &ControlElementButton::OnLeftClickUp, this);
133}
134
135ControlElementButton::ControlElementButton(wxWindow* parent, wxString label, std::unique_ptr<ControlElement> iconElement,
136 wxWindowID id, GUIColour* guiColour, double buttonScale) :
137 wxWindow(parent, id), m_guiColour(guiColour), m_iconElement(std::move(iconElement)), m_buttonScale(buttonScale)
138{
139 SetBackgroundColour(m_guiColour->background);
140 m_buttonFont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
141 //m_buttonFont.SetPointSize(static_cast<int>(m_buttonFont.GetPointSize() * (1.0 / m_buttonScale)));
142 m_label = label;
143
144 // Calculate label size.
145 wxScreenDC dc;
146 dc.SetFont(m_buttonFont);
147 //wxSize textSize = dc.GetTextExtent(label) * m_buttonScale;
148 wxSize textSize = dc.GetTextExtent(label);
149
150 m_imageSize = wxSize(static_cast<int>(m_iconElement->GetWidth()), static_cast<int>(m_iconElement->GetHeight())) * m_buttonScale;
151
152 int buttonWidth = 0;
153 if (textSize.GetWidth() > m_imageSize.GetWidth()) {
154 buttonWidth = textSize.GetWidth();
155 m_imagePosition = wxPoint((buttonWidth - m_imageSize.GetWidth()) / 2 + m_borderSize, m_borderSize);
156 m_labelPosition = wxPoint(m_borderSize, m_imageSize.GetHeight() + m_borderSize);
157 }
158 else {
159 buttonWidth = m_imageSize.GetWidth();
160 m_imagePosition = wxPoint(m_borderSize, m_borderSize);
161 m_labelPosition =
162 wxPoint((buttonWidth - textSize.GetWidth()) / 2 + m_borderSize, m_imageSize.GetHeight() + m_borderSize);
163 }
164 m_buttonSize =
165 wxSize(buttonWidth + 2 * m_borderSize, textSize.GetHeight() + m_imageSize.GetHeight() + 2 * m_borderSize);
166 SetMinSize(m_buttonSize + wxSize(m_borderSize, m_borderSize));
167
168 m_iconElement->StartMove(m_iconElement->GetPosition());
169 m_iconElement->Move(wxPoint2DDouble(m_buttonSize.GetWidth() / 2.0, (m_buttonSize.GetHeight() - textSize.GetHeight()) / 2.0));
170
171 // Events.
172 Bind(wxEVT_PAINT, &ControlElementButton::OnPaint, this);
173 Bind(wxEVT_ENTER_WINDOW, &ControlElementButton::OnMouseEnter, this);
174 Bind(wxEVT_LEAVE_WINDOW, &ControlElementButton::OnMouseLeave, this);
175 Bind(wxEVT_LEFT_DOWN, &ControlElementButton::OnLeftClickDown, this);
176 Bind(wxEVT_LEFT_UP, &ControlElementButton::OnLeftClickUp, this);
177}
178
179ControlElementButton::~ControlElementButton() {}
180void ControlElementButton::OnPaint(wxPaintEvent& event)
181{
182 wxPaintDC dc(this);
183 wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
184 if (gc) {
185
186 if (m_mouseAbove) {
187 if (m_selected) {
188 gc->SetPen(wxPen(m_guiColour->altSelection, m_borderSize - 1));
189 gc->SetBrush(m_guiColour->altSelection);
190 }
191 else {
192 gc->SetPen(*wxTRANSPARENT_PEN);
193 gc->SetBrush(m_guiColour->selection);
194 }
195 gc->DrawRectangle(m_borderSize / 2, m_borderSize / 2, m_buttonSize.GetWidth(), m_buttonSize.GetHeight());
196 }
197
198 gc->SetFont(m_buttonFont, m_guiColour->text);
199 gc->DrawText(m_label, m_labelPosition.x, m_labelPosition.y);
200
201 if (m_iconElement) {
202 gc->Translate(m_buttonSize.GetWidth() / 2.0, m_buttonSize.GetHeight() / 2.0);
203 gc->Scale(m_buttonScale, m_buttonScale);
204 gc->Translate(-m_buttonSize.GetWidth() / 2.0, -m_buttonSize.GetHeight() / 2.0);
205 m_iconElement->DrawDC(m_guiColour, wxPoint2DDouble(0, 0), 1.0, gc);
206 }
207 else {
208 gc->DrawBitmap(gc->CreateBitmapFromImage(m_image), m_imagePosition.x, m_imagePosition.y, m_imageSize.GetWidth(),
209 m_imageSize.GetHeight());
210 }
211
212 delete gc;
213 }
214}
215
216void ControlElementButton::OnMouseEnter(wxMouseEvent& event)
217{
218 m_mouseAbove = true;
219 Refresh();
220 event.Skip();
221}
222
223void ControlElementButton::OnMouseLeave(wxMouseEvent& event)
224{
225 m_mouseAbove = false;
226 Refresh();
227 event.Skip();
228}
229
230void ControlElementButton::OnLeftClickDown(wxMouseEvent& event)
231{
232 m_selected = true;
233 Refresh();
234 event.Skip();
235}
236
237void ControlElementButton::OnLeftClickUp(wxMouseEvent& event)
238{
239 m_selected = false;
240 Refresh();
241 event.Skip();
242}
243
244ControlEditor::ControlEditor(wxWindow* parent, PropertiesData* properties, int ioflags) : ControlEditorBase(parent), m_properties(properties)
245{
246 BuildControlElementPanel();
247 m_camera = new Camera();
248 m_selectionRect = wxRect2DDouble(0, 0, 0, 0);;
249 m_cePanel->SetBackgroundColour(properties->GetGUIColour()->background);
250 m_cePanel->SetBackgroundStyle(wxBG_STYLE_PAINT); // To allow wxBufferedPaintDC works properly.
251 m_ioFlags = ioflags;
252 m_font = wxFont(m_properties->GetGeneralPropertiesData().labelFontSize,
253 wxFONTFAMILY_DEFAULT,
254 wxFONTSTYLE_NORMAL,
255 wxFONTWEIGHT_NORMAL,
256 false,
257 m_properties->GetGeneralPropertiesData().labelFont);
258
259 BuildColourList();
260}
261ControlEditor::~ControlEditor()
262{
263 ClearStates();
264 delete m_camera;
265 m_camera = nullptr;
266 // m_tfButton->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(ControlEditor::LeftClickDown), m_tfButton, this);
267}
268
269wxPoint ControlEditor::GetEditorMouseClientPoint() const
270{
271 if (!m_cePanel) return wxPoint(0, 0);
272 return m_cePanel->ScreenToClient(wxGetMousePosition());
273}
274
275wxPoint2DDouble ControlEditor::GetEditorMouseWorldPoint() const
276{
277 if (!m_camera) return wxPoint2DDouble(0, 0);
278 wxPoint clientMouse = GetEditorMouseClientPoint();
279 if (m_cePanel && m_cePanel->GetClientRect().Contains(clientMouse))
280 return m_camera->ScreenToWorld(clientMouse);
281 return m_camera->GetMousePosition();
282}
283
284void ControlEditor::BuildControlElementPanel()
285{
286 m_panelControlElements->SetDoubleBuffered(true);
287
288 wxWrapSizer* wrapSizer = new wxWrapSizer();
289 m_panelControlElements->SetSizer(wrapSizer);
290
291 auto* guiColour = m_properties->GetGUIColour();
292
293 m_panelControlElements->SetBackgroundColour(guiColour->background);
294
295 wxFileName exeFileName(wxStandardPaths::Get().GetExecutablePath());
296
297 auto iconIOControl = std::make_unique<IOControl>(IOControl::IOFlags::IN_IO, -1);
298 iconIOControl->SetValue(IOControl::IOFlags::IN_IO);
299
301 m_panelControlElements, _("In/Out"),
302 std::move(iconIOControl),
303 static_cast<int>(ControlElementButtonID::ID_IO),
304 guiColour, 0.8);
305 wrapSizer->Add(ioButton, 0, wxALL, 5);
306 ioButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
307
308 auto iconTF = std::make_unique<TransferFunction>(-1);
309
311 m_panelControlElements, _("Transfer fcn"),
312 std::move(iconTF),
313 static_cast<int>(ControlElementButtonID::ID_TF),
314 guiColour, 0.8);
315 wrapSizer->Add(tfButton, 0, wxALL, 5);
316 tfButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
317
318 auto iconSum = std::make_unique<Sum>(-1);
319
321 m_panelControlElements, _("Sum"),
322 std::move(iconSum),
323 static_cast<int>(ControlElementButtonID::ID_SUM),
324 guiColour, 0.8);
325 wrapSizer->Add(sumButton, 0, wxALL, 5);
326 sumButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
327
328 auto iconConstant = std::make_unique<Constant>(-1);
329 iconConstant->SetValue(3.14);
330
332 m_panelControlElements, _("Constant"),
333 std::move(iconConstant),
334 static_cast<int>(ControlElementButtonID::ID_CONST),
335 guiColour);
336 wrapSizer->Add(constButton, 0, wxALL, 5);
337 constButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
338
339 auto iconGain = std::make_unique<Gain>(-1);
340 iconGain->SetValue(0.5);
341
343 m_panelControlElements, _("Gain"),
344 std::move(iconGain),
345 static_cast<int>(ControlElementButtonID::ID_GAIN),
346 guiColour, 0.8);
347 wrapSizer->Add(gainButton, 0, wxALL, 5);
348 gainButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
349
350 auto iconLimiter = std::make_unique<Limiter>(-1);
351
353 m_panelControlElements, _("Limiter"),
354 std::move(iconLimiter),
355 static_cast<int>(ControlElementButtonID::ID_LIMITER),
356 guiColour, 0.8);
357 wrapSizer->Add(limButton, 0, wxALL, 5);
358 limButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
359
360 auto iconRateLim = std::make_unique<RateLimiter>(-1);
361
362 ControlElementButton* rateLimButton = new ControlElementButton(
363 m_panelControlElements, _("Rate limiter"),
364 std::move(iconRateLim),
365 static_cast<int>(ControlElementButtonID::ID_RATELIM),
366 guiColour, 0.8);
367 wrapSizer->Add(rateLimButton, 0, wxALL, 5);
368 rateLimButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
369
370 auto iconMult = std::make_unique<Multiplier>(-1);
371
373 m_panelControlElements, _("Multiplier"),
374 std::move(iconMult),
375 static_cast<int>(ControlElementButtonID::ID_MULT),
376 guiColour, 0.8);
377 wrapSizer->Add(multButton, 0, wxALL, 5);
378 multButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
379
380 auto iconDiv = std::make_unique<Divider>(-1);
381
383 m_panelControlElements, _("Divider"),
384 std::move(iconDiv),
385 static_cast<int>(ControlElementButtonID::ID_MATH_DIV),
386 guiColour, 0.8);
387 wrapSizer->Add(divButton, 0, wxALL, 5);
388 divButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
389
390 auto iconMathExpr = std::make_unique<MathExpression>(-1);
391
392 ControlElementButton* mathExprButton = new ControlElementButton(
393 m_panelControlElements, _("Math Expression"),
394 std::move(iconMathExpr),
395 static_cast<int>(ControlElementButtonID::ID_MATH_EXPR),
396 guiColour, 0.8);
397 wrapSizer->Add(mathExprButton, 0, wxALL, 5);
398 mathExprButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
399
400 auto iconExp = std::make_unique<Exponential>(-1);
401
403 m_panelControlElements, _("Exponential"),
404 std::move(iconExp),
405 static_cast<int>(ControlElementButtonID::ID_EXP),
406 guiColour, 0.8);
407 wrapSizer->Add(satButton, 0, wxALL, 5);
408 satButton->Bind(wxEVT_LEFT_DOWN, &ControlEditor::LeftClickDown, this);
409}
410
411void ControlEditor::LeftClickDown(wxMouseEvent& event)
412{
413 AddElement(static_cast<ControlElementButtonID>(event.GetId()));
414 event.Skip();
415}
416
417void ControlEditor::AddElement(ControlElementButtonID id)
418{
419 switch (id) {
420 case ControlElementButtonID::ID_IO: {
421 m_mode = ControlEditorMode::MODE_INSERT;
422 auto io = std::make_shared<IOControl>(m_ioFlags, GetNextID());
423 m_elementList.push_back(io);
424 } break;
425 case ControlElementButtonID::ID_TF: {
426 m_mode = ControlEditorMode::MODE_INSERT;
427 auto tf = std::make_shared<TransferFunction>(GetNextID());
428 m_elementList.push_back(tf);
429 } break;
430 case ControlElementButtonID::ID_SUM: {
431 m_mode = ControlEditorMode::MODE_INSERT;
432 auto sum = std::make_shared<Sum>(GetNextID());
433 m_elementList.push_back(sum);
434 } break;
435 case ControlElementButtonID::ID_CONST: {
436 m_mode = ControlEditorMode::MODE_INSERT;
437 auto constant = std::make_shared<Constant>(GetNextID());
438 m_elementList.push_back(constant);
439 } break;
440 case ControlElementButtonID::ID_LIMITER: {
441 m_mode = ControlEditorMode::MODE_INSERT;
442 auto limiter = std::make_shared<Limiter>(GetNextID());
443 m_elementList.push_back(limiter);
444 } break;
445 case ControlElementButtonID::ID_GAIN: {
446 m_mode = ControlEditorMode::MODE_INSERT;
447 auto gain = std::make_shared<Gain>(GetNextID());
448 m_elementList.push_back(gain);
449 } break;
450 case ControlElementButtonID::ID_MULT: {
451 m_mode = ControlEditorMode::MODE_INSERT;
452 auto mult = std::make_shared<Multiplier>(GetNextID());
453 m_elementList.push_back(mult);
454 } break;
455 case ControlElementButtonID::ID_EXP: {
456 m_mode = ControlEditorMode::MODE_INSERT;
457 auto exp = std::make_shared<Exponential>(GetNextID());
458 m_elementList.push_back(exp);
459 } break;
460 case ControlElementButtonID::ID_RATELIM: {
461 m_mode = ControlEditorMode::MODE_INSERT;
462 auto rateLim = std::make_shared<RateLimiter>(GetNextID());
463 m_elementList.push_back(rateLim);
464 } break;
465 case ControlElementButtonID::ID_MATH_DIV: {
466 m_mode = ControlEditorMode::MODE_INSERT;
467 auto divider = std::make_shared<Divider>(GetNextID());
468 m_elementList.push_back(divider);
469 } break;
470 case ControlElementButtonID::ID_MATH_EXPR: {
471 m_mode = ControlEditorMode::MODE_INSERT;
472 auto mathExpr = std::make_shared<MathExpression>(GetNextID());
473 m_elementList.push_back(mathExpr);
474 } break;
475 }
476 for (auto& cElement : m_elementList)
477 {
478 cElement->SetFont(m_font);
479 cElement->StartMove(wxPoint2DDouble(0, 0));
480 cElement->Move(wxPoint2DDouble(0, 0));
481
482 }
483}
484
485void ControlEditor::OnPaint(wxPaintEvent& event)
486{
487 wxBufferedPaintDC dc(m_cePanel);
488 dc.Clear();
489 wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
490
491 // Draw
492 if (gc) {
493
494 gc->Scale(m_camera->GetScale(), m_camera->GetScale());
495 gc->Translate(m_camera->GetTranslation().m_x, m_camera->GetTranslation().m_y);
496
497 for (auto line : m_connectionList) {
498 //ConnectionLine* line = *it;
499 line->DrawDC(m_properties->GetGUIColour(), m_camera->GetTranslation(), m_camera->GetScale(), gc);
500 }
501
502 for (auto element : m_elementList) {
503
504 element->DrawDC(m_properties->GetGUIColour(), m_camera->GetTranslation(), m_camera->GetScale(), gc);
505 }
506
507 // Selection rectangle
508 gc->SetPen(wxPen(m_properties->GetGUIColour()->selection));
509 gc->SetBrush(wxBrush(m_properties->GetGUIColour()->selection));
510 gc->DrawRectangle(m_selectionRect.m_x, m_selectionRect.m_y, m_selectionRect.m_width, m_selectionRect.m_height);
511
512 delete gc;
513 }
514 event.Skip();
515}
516
517void ControlEditor::OnDoubleClick(wxMouseEvent& event)
518{
519 wxPoint2DDouble clickPoint = event.GetPosition();
520 bool redraw = false;
521
522 if (m_mode == ControlEditor::ControlEditorMode::MODE_EDIT) {
523 for (auto& element : m_elementList) {
524 if (element->Contains(m_camera->ScreenToWorld(clickPoint))) {
525 element->ShowForm(this, element.get());
526 CheckConnections();
527 auto childList = element->GetChildList();
528 for (auto itC = childList.begin(), itEndC = childList.end(); itC != itEndC; ++itC) {
529 ConnectionLine* line = static_cast<ConnectionLine*>(*itC);
530 line->UpdatePoints();
531 }
532 redraw = true;
533 SaveCurrentState();
534 break;
535 }
536 }
537 }
538
539 if (redraw) Redraw();
540}
541
542void ControlEditor::OnLeftClickDown(wxMouseEvent& event)
543{
544 wxPoint2DDouble clickPoint = event.GetPosition();
545 bool foundElement = false;
546
547 if (m_mode == ControlEditorMode::MODE_PASTE || m_mode == ControlEditorMode::MODE_DRAG_PASTE) {
548 m_mode = ControlEditorMode::MODE_EDIT;
549 SaveCurrentState();
550 }
551 else if (m_mode == ControlEditorMode::MODE_INSERT) {
552 // Update the font of the last element in the list.
553 //auto cElement = m_elementList.back();
554 //cElement->SetFont(m_font);
555 m_mode = ControlEditor::ControlEditorMode::MODE_EDIT;
556 }
557 else {
558 for (auto& element : m_elementList) {
559 bool foundNode = false;
560 auto nodeList = element->GetNodeList();
561 for (auto itN = nodeList.begin(), itNEnd = nodeList.end(); itN != itNEnd; ++itN) {
562 Node* node = *itN;
563 if (node->Contains(m_camera->ScreenToWorld(clickPoint))) {
564 m_mode = ControlEditorMode::MODE_INSERT_LINE;
565 auto line = std::make_shared<ConnectionLine>(node, GetNextID());
566 m_connectionList.push_back(line);
567 element->AddChild(line.get());
568 line->AddParent(element.get());
569 foundElement = true;
570 foundNode = true;
571 }
572 }
573
574 if (!foundNode) {
575 // Set movement initial position (not necessarily will be moved).
576 element->StartMove(m_camera->ScreenToWorld(clickPoint));
577
578 // Click in an element.
579 if (element->Contains(m_camera->ScreenToWorld(clickPoint))) {
580 if (!foundElement) {
581 element->SetSelected();
582 foundElement = true;
583 }
584 m_mode = ControlEditor::ControlEditorMode::MODE_MOVE_ELEMENT;
585 }
586 }
587 }
588 if (m_mode != ControlEditorMode::MODE_INSERT_LINE) {
589 for (auto& line : m_connectionList) {
590 line->StartMove(m_camera->ScreenToWorld(clickPoint));
591 if (line->Contains(m_camera->ScreenToWorld(clickPoint))) {
592 line->SetSelected();
593 foundElement = true;
594 m_mode = ControlEditorMode::MODE_MOVE_LINE;
595 }
596 }
597 }
598 }
599
600 if (!foundElement) {
601 m_mode = ControlEditorMode::MODE_SELECTION_RECT;
602 m_startSelRect = m_camera->ScreenToWorld(clickPoint);
603 }
604
605 Redraw();
606 event.Skip();
607}
608
609void ControlEditor::OnLeftClickUp(wxMouseEvent& event)
610{
611 bool foundNode = false;
612 bool saveCurrentState = false;
613
614 for (auto& element : m_elementList) {
615 if (m_mode == ControlEditorMode::MODE_INSERT_LINE) {
616 auto nodeList = element->GetNodeList();
617 for (auto node : nodeList) {
618 if (node->Contains(m_camera->ScreenToWorld(event.GetPosition()))) {
619 //ConnectionLine* line = *(m_connectionList.end() - 1);
620 auto line = m_connectionList.back();
621 if (line->AppendNode(node, element.get())) {
622 line->AddParent(element.get());
623 element->AddChild(line.get());
624 line->UpdatePoints();
625 m_mode = ControlEditor::ControlEditorMode::MODE_EDIT;
626 foundNode = true;
627 saveCurrentState = true;
628 }
629 }
630 }
631 }
632 else if (m_mode == ControlEditorMode::MODE_SELECTION_RECT) {
633 if (element->Intersects(m_selectionRect)) {
634 element->SetSelected();
635 }
636 else if (!event.ControlDown()) {
637 element->SetSelected(false);
638 }
639 }
640 else if (!event.ControlDown()) {
641 if (!element->Contains(m_camera->ScreenToWorld(event.GetPosition()))) { element->SetSelected(false); }
642 }
643 }
644
645 if (!m_connectionList.empty()) {
646 auto& lastLine = m_connectionList.back();
647 for (auto& cLine : m_connectionList) {
648 if (m_mode == ControlEditorMode::MODE_INSERT_LINE && !foundNode && cLine.get() != lastLine.get()) {
649 if (cLine->Contains(m_camera->ScreenToWorld(event.GetPosition()))) {
650 //ConnectionLine* iLine = *(m_connectionList.end() - 1);
651 auto iLine = m_connectionList.back();
652 if (iLine->SetParentLine(cLine.get())) {
653 cLine->AddChild(iLine.get());
654 iLine->UpdatePoints();
655 m_mode = ControlEditor::ControlEditorMode::MODE_EDIT;
656 foundNode = true;
657 }
658 }
659 }
660 else if (m_mode == ControlEditorMode::MODE_SELECTION_RECT) {
661 if (cLine->Intersects(m_selectionRect)) {
662 cLine->SetSelected();
663 }
664 else if (!event.ControlDown()) {
665 cLine->SetSelected(false);
666 }
667 }
668 else if (!event.ControlDown()) {
669 if (!cLine->Contains(m_camera->ScreenToWorld(event.GetPosition()))) { cLine->SetSelected(false); }
670 }
671 }
672 }
673
674 m_selectionRect = wxRect2DDouble(0, 0, 0, 0);
675
676 if (m_mode == ControlEditorMode::MODE_INSERT_LINE && !foundNode) {
677 //ConnectionLine* cLine = *(m_connectionList.end() - 1);
678 auto cLine = m_connectionList.back();
679 // Free nodes
680 auto nodeList = cLine->GetNodeList();
681 for (auto itN = nodeList.begin(), itEndN = nodeList.end(); itN != itEndN; ++itN) {
682 Node* node = *itN;
683 node->SetConnected(false);
684 }
685 // Remove the associated child from parents.
686 auto parentList = cLine->GetParentList();
687 for (auto it = parentList.begin(), itEnd = parentList.end(); it != itEnd; ++it) {
688 Element* element = *it;
689 element->RemoveChild(cLine.get());
690 }
691 m_connectionList.pop_back();
692 //if (cLine) delete cLine;
693 m_mode = ControlEditor::ControlEditorMode::MODE_EDIT;
694 }
695 else if (m_mode != ControlEditorMode::MODE_INSERT) {
696 if (m_mode == ControlEditorMode::MODE_MOVE_ELEMENT || m_mode == ControlEditorMode::MODE_MOVE_LINE) {
697 saveCurrentState = true;
698 }
699 m_mode = ControlEditor::ControlEditorMode::MODE_EDIT;
700 }
701
702 if (saveCurrentState) SaveCurrentState();
703
704 Redraw();
705 event.Skip();
706}
707
708void ControlEditor::OnMiddleDown(wxMouseEvent& event)
709{
710 // Set to drag mode.
711 switch (m_mode) {
712 case ControlEditorMode::MODE_INSERT: {
713 m_mode = ControlEditorMode::MODE_DRAG_INSERT;
714 } break;
715 case ControlEditorMode::MODE_PASTE: {
716 m_mode = ControlEditorMode::MODE_DRAG_PASTE;
717 } break;
718 default: {
719 m_mode = ControlEditorMode::MODE_DRAG;
720 } break;
721 }
722 m_camera->StartTranslation(GetEditorMouseWorldPoint());
723}
724
725void ControlEditor::OnMiddleUp(wxMouseEvent& event)
726{
727 switch (m_mode) {
728 case ControlEditorMode::MODE_DRAG_INSERT: {
729 m_mode = ControlEditorMode::MODE_INSERT;
730 } break;
731 case ControlEditorMode::MODE_DRAG_PASTE: {
732 m_mode = ControlEditorMode::MODE_PASTE;
733 } break;
734 case ControlEditorMode::MODE_INSERT:
735 case ControlEditorMode::MODE_PASTE: {
736 // Does nothing.
737 } break;
738 default: {
739 m_mode = ControlEditor::ControlEditorMode::MODE_EDIT;
740 } break;
741 }
742}
743
744void ControlEditor::OnMouseMotion(wxMouseEvent& event)
745{
746 wxPoint2DDouble clickPoint = event.GetPosition();
747 bool redraw = false;
748
749 switch (m_mode) {
750 case ControlEditorMode::MODE_INSERT: {
751 //Element* newElement = *(m_elementList.end() - 1); // Get the last element in the list.
752 auto newElement = m_elementList.back();
753 newElement->Move(m_camera->ScreenToWorld(clickPoint));
754 redraw = true;
755 } break;
756 case ControlEditorMode::MODE_INSERT_LINE: {
757 //ConnectionLine* line = *(m_connectionList.end() - 1);
758 auto line = m_connectionList.back();
759 line->SetTemporarySecondPoint(m_camera->ScreenToWorld(clickPoint));
760 line->UpdatePoints();
761 redraw = true;
762 } break;
763 case ControlEditorMode::MODE_DRAG:
764 case ControlEditorMode::MODE_DRAG_INSERT:
765 case ControlEditorMode::MODE_DRAG_PASTE: {
766 m_camera->SetTranslation(clickPoint);
767 redraw = true;
768 } break;
769 case ControlEditorMode::MODE_PASTE: {
770 bool movedSelectedElement = false;
771 for (auto& element : m_elementList) {
772 if (element->IsSelected()) {
773 movedSelectedElement = true;
774 element->Move(m_camera->ScreenToWorld(clickPoint));
775 auto childList = element->GetChildList();
776 for (Element* child : childList) {
777 if (auto line = dynamic_cast<ConnectionLine*>(child)) line->UpdatePoints();
778 }
779 redraw = true;
780 }
781 }
782 // Only move lines directly when no element is selected; otherwise line movement would
783 // be applied twice and corrupt offsets.
784 if (!movedSelectedElement) {
785 for (auto& line : m_connectionList) {
786 if (line->IsSelected()) {
787 line->Move(m_camera->ScreenToWorld(clickPoint));
788 redraw = true;
789 }
790 }
791 }
792 } break;
793 case ControlEditor::ControlEditorMode::MODE_MOVE_ELEMENT: {
794 for (auto& element : m_elementList) {
795 if (element->IsSelected()) {
796 element->Move(m_camera->ScreenToWorld(clickPoint));
797 auto childList = element->GetChildList();
798 for (auto itC = childList.begin(), itEndC = childList.end(); itC != itEndC; itC++) {
799 ConnectionLine* line = static_cast<ConnectionLine*>(*itC);
800 line->UpdatePoints();
801 }
802 redraw = true;
803 }
804 }
805 } break;
806 case ControlEditorMode::MODE_MOVE_LINE: {
807 for (auto& line : m_connectionList) {
808 if (line->IsSelected()) {
809 line->Move(m_camera->ScreenToWorld(clickPoint));
810 redraw = true;
811 }
812 }
813 } break;
814 case ControlEditorMode::MODE_SELECTION_RECT: {
815 wxPoint2DDouble currentPos = m_camera->ScreenToWorld(clickPoint);
816 double x, y, w, h;
817 if (currentPos.m_x < m_startSelRect.m_x) {
818 x = currentPos.m_x;
819 w = m_startSelRect.m_x - currentPos.m_x;
820 }
821 else {
822 x = m_startSelRect.m_x;
823 w = currentPos.m_x - m_startSelRect.m_x;
824 }
825 if (currentPos.m_y < m_startSelRect.m_y) {
826 y = currentPos.m_y;
827 h = m_startSelRect.m_y - currentPos.m_y;
828 }
829 else {
830 y = m_startSelRect.m_y;
831 h = currentPos.m_y - m_startSelRect.m_y;
832 }
833
834 m_selectionRect = wxRect2DDouble(x, y, w, h);
835 redraw = true;
836 } break;
837 default:
838 break;
839 }
840
841 m_camera->UpdateMousePosition(clickPoint);
842 if (redraw) Redraw();
843 event.Skip();
844}
845
846void ControlEditor::OnScroll(wxMouseEvent& event)
847{
848 if (event.GetWheelRotation() > 0)
849 m_camera->SetScale(event.GetPosition(), +0.05);
850 else
851 m_camera->SetScale(event.GetPosition(), -0.05);
852
853 Redraw();
854}
855
856void ControlEditor::OnIdle(wxIdleEvent& event)
857{
858 //if(m_justOpened) {
859 // this->Raise();
860 //
861 // // Update all text elements
862 // m_justOpened = false;
863 // for(auto it = m_elementList.begin(), itEnd = m_elementList.end(); it != itEnd; ++it) {
864 // ControlElement* element = *it;
865 // if(!element->UpdateText()) m_justOpened = true;
866 // }
867 // Redraw();
868 //}
869}
870void ControlEditor::OnKeyDown(wxKeyEvent& event)
871{
872 bool hasInsertInProgress = (m_mode == ControlEditorMode::MODE_INSERT ||
873 m_mode == ControlEditorMode::MODE_INSERT_LINE);
874 char key = event.GetUnicodeKey();
875 if (key != WXK_NONE) {
876 switch (key) {
877 case WXK_DELETE: // Delete selected elements.
878 {
879 if (!hasInsertInProgress) {
880 wxCommandEvent dummyEvent;
881 OnDeleteClick(dummyEvent);
882 }
883 } break;
884 case 'R': // Rotate the selected elements.
885 {
886 RotateSelectedElements(event.GetModifiers() != wxMOD_SHIFT);
887 SaveCurrentState();
888 } break;
889 case 'C': {
890 if (!hasInsertInProgress && event.GetModifiers() == wxMOD_CONTROL) {
891 wxCommandEvent dummyEvent;
892 OnCopyClick(dummyEvent);
893 }
894 } break;
895 case 'V': {
896 if (!hasInsertInProgress && event.GetModifiers() == wxMOD_CONTROL) {
897 wxCommandEvent dummyEvent;
898 OnPasteClick(dummyEvent);
899 }
900 } break;
901 case 'Z': {
902 if (!hasInsertInProgress && event.ControlDown() && !event.ShiftDown()) {
903 wxCommandEvent dummyEvent;
904 OnUndoClick(dummyEvent);
905 }
906 if (!hasInsertInProgress && event.ControlDown() && event.ShiftDown()) {
907 wxCommandEvent dummyEvent;
908 OnRedoClick(dummyEvent);
909 }
910 } break;
911 case 'Y': {
912 if (!hasInsertInProgress && event.GetModifiers() == wxMOD_CONTROL) {
913 wxCommandEvent dummyEvent;
914 OnRedoClick(dummyEvent);
915 }
916 } break;
917 case 'N': {
918 if (!hasInsertInProgress && event.GetModifiers() == wxMOD_CONTROL) {
919 wxCommandEvent dummyEvent;
920 OnNewClick(dummyEvent);
921 }
922 } break;
923 case 'L': {
924 // tests
925 } break;
926 }
927 }
928}
929
930void ControlEditor::RotateSelectedElements(bool clockwise)
931{
932 for (auto& element : m_elementList) {
933 if (element->IsSelected()) {
934 element->Rotate(clockwise);
935 auto childList = element->GetChildList();
936 for (auto itC = childList.begin(), itEndC = childList.end(); itC != itEndC; itC++) {
937 ConnectionLine* line = static_cast<ConnectionLine*>(*itC);
938 line->UpdatePoints();
939 }
940 }
941 }
942 Redraw();
943}
944
945void ControlEditor::DeleteSelectedElements()
946{
947 for (auto it = m_elementList.begin(); it != m_elementList.end();) {
948 Element* element = it->get();
949 if (element->IsSelected()) {
950 // Remove child/parent.
951 auto childList = element->GetChildList();
952 for (auto child : childList) {
953 // The child is always a connection line, but check it.
954 //ConnectionLine* child = static_cast<ConnectionLine*>(*itC);
955 if (auto childLine = dynamic_cast<ConnectionLine*>(child)) {
956 // Delete the connection line.
957 for (auto itCo = m_connectionList.begin(); itCo != m_connectionList.end(); ) {
958 if (itCo->get() == childLine)
959 itCo = DeleteLineFromList(itCo);
960 else
961 ++itCo;
962 }
963 }
964
965 }
966 m_elementList.erase(it);
967 //if (element) delete element;
968 }
969 else {
970 ++it;
971 }
972 }
973
974 for (auto it = m_connectionList.begin(); it != m_connectionList.end(); ) {
975 ConnectionLine* line = it->get();
976 if (line->IsSelected())
977 it = DeleteLineFromList(it);
978 else
979 ++it;
980 }
981 Redraw();
982}
983
984std::vector< std::shared_ptr<ConnectionLine> >::iterator ControlEditor::DeleteLineFromList(std::vector< std::shared_ptr<ConnectionLine> >::iterator& it)
985{
986 ConnectionLine* cLine = it->get();
987
988 // Delete children recursively
989 auto childList = cLine->GetLineChildList();
990 for (auto child : childList) {
991 for (auto itL = m_connectionList.begin(); itL != m_connectionList.end(); ) {
992 if (itL->get() == child)
993 itL = DeleteLineFromList(itL);
994 else
995 ++itL;
996 }
997 }
998 // Remove parents
999 auto parentList = cLine->GetParentList();
1000 for (auto parent : parentList) {
1001 if (parent) parent->RemoveChild(cLine);
1002 }
1003 if (cLine->GetParentLine()) cLine->GetParentLine()->RemoveChild(cLine);
1004
1005 // Free nodes
1006 auto nodeList = cLine->GetNodeList();
1007 for (auto node : nodeList)
1008 node->SetConnected(false);
1009
1010 return m_connectionList.erase(it);
1011}
1012
1013void ControlEditor::CheckConnections()
1014{
1015 for (auto it = m_connectionList.begin(); it != m_connectionList.end(); ++it) {
1016 ConnectionLine* cLine = it->get();
1017 if (cLine->GetType() == ConnectionLine::ConnectionLineType::ELEMENT_ELEMENT) {
1018 if (cLine->GetParentList().size() < 2) { it = DeleteLineFromList(it); }
1019 }
1020 else if (cLine->GetParentList().size() < 1) {
1021 it = DeleteLineFromList(it);
1022 }
1023 }
1024}
1025
1026void ControlEditor::SetElementsList(const std::vector<std::shared_ptr<ControlElement>>& elementList)
1027{
1028 m_elementList = elementList;
1029 for (auto& cElement : m_elementList)
1030 {
1031 cElement->SetFont(m_font);
1032 }
1033}
1034
1035void ControlEditor::OnExportClick(wxCommandEvent& event)
1036{
1037 FileHanding fileHandling(this);
1038
1039 wxFileDialog saveFileDialog(this, _("Save CTL file"), "", "", "CTL files (*.ctl)|*.ctl",
1040 wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
1041 if (saveFileDialog.ShowModal() == wxID_CANCEL) return;
1042
1043 fileHandling.SaveControl(saveFileDialog.GetPath());
1044 wxFileName fileName(saveFileDialog.GetPath());
1045 event.Skip();
1046}
1047
1048void ControlEditor::OnImportClick(wxCommandEvent& event)
1049{
1050 wxFileDialog openFileDialog(this, _("Open CTL file"), "", "", "CTL files (*.ctl)|*.ctl",
1051 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
1052 if (openFileDialog.ShowModal() == wxID_CANCEL) return;
1053
1054 wxFileName fileName(openFileDialog.GetPath());
1055
1056 FileHanding fileHandling(this);
1057 if (!fileHandling.OpenControl(fileName, m_elementList, m_connectionList)) {
1058 wxMessageDialog msgDialog(this, _("It was not possible to open the selected file."), _("Error"),
1059 wxOK | wxCENTRE | wxICON_ERROR);
1060 msgDialog.ShowModal();
1061 }
1062 for (auto cElement : m_elementList)
1063 {
1064 cElement->SetFont(m_font);
1065 }
1066 Redraw();
1067 event.Skip();
1068}
1069
1070void ControlEditor::OnTestClick(wxCommandEvent& event)
1071{
1072 // Reset colour list position for the test plot.
1073 m_itColourList = --m_colourList.end();
1074
1075 std::vector<IOControl*> ioList;
1076
1077 for (auto& element : m_elementList) {
1078 if (auto io = dynamic_cast<IOControl*>(element.get())) {
1079 ioList.push_back(io);
1080 }
1081 }
1082
1083 ControlSystemTest csTest(this, ioList, &m_inputType, &m_startTime, &m_slope, &m_timeStep, &m_simTime);
1084 if (csTest.ShowModal() == wxID_OK) {
1085 double printStep = 1e-3;
1086 double pdbStep = 1e-1;
1087
1088 struct InputData {
1089 wxString name;
1090 std::vector<double> values;
1091 };
1092
1093 std::vector<InputData> inputList;
1094
1095 // Store real flags for restoring after the test and set initial value.
1096 std::vector<IOControl::IOFlags> realFlagValue;
1097 for (auto* io : ioList) {
1098 realFlagValue.push_back(io->GetValue());
1099 if (io->GetType() == Node::NodeType::NODE_OUT) {
1100 SimTestData testData = io->GetSimTestData();
1101 io->SetValue(IOControl::IN_TEST);
1102 io->SetTestValue(testData.initialValue);
1103 inputList.push_back({ io->GetName(), {} });
1104 }
1105 }
1106
1107 wxProgressDialog pbd(_("Test"), _("Initializing..."), 100, this,
1108 wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_SMOOTH);
1109 ControlElementSolver solver(this, m_timeStep, 1e-5);
1110 solver.InitializeValues(false);
1111 if (solver.IsOK()) {
1112 bool simStopped = false;
1113 double currentTime = 0.0;
1114 double printTime = 0.0;
1115 double pdbTime = 0.0;
1116 std::vector<double> time;
1117 std::vector<double> solution;
1118
1119 while (currentTime <= m_simTime) {
1120 for (auto* io : ioList) {
1121 SimTestData testData = io->GetSimTestData();
1122 if (currentTime >= testData.startTime) {
1123 switch (testData.type) {
1124 case 0: {
1125 io->SetTestValue(testData.slope);
1126 } break;
1127 case 1: {
1128 io->SetTestValue(testData.slope * (currentTime - testData.startTime));
1129 } break;
1130 case 2: {
1131 io->SetTestValue(testData.slope * std::pow(currentTime - testData.startTime, 2));
1132 } break;
1133 default: {
1134 io->SetTestValue(0.0);
1135 break;
1136 }
1137 }
1138 }
1139 }
1140
1141 solver.SetCurrentTime(currentTime);
1142 solver.SolveNextStep();
1143 if (!solver.IsOK()) {
1144 wxString msg = wxString::Format(_("Failed to solve the control system.\n%s"), solver.GetErrorMessage());
1145 wxMessageDialog msgDialog(this, msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
1146 msgDialog.ShowModal();
1147 simStopped = true;
1148 currentTime = m_simTime;
1149 }
1150
1151 if (printTime >= printStep) {
1152 time.push_back(currentTime);
1153 solution.push_back(solver.GetLastSolution());
1154 //inputV.push_back(input);
1155 int i = 0;
1156 for (auto* io : ioList) {
1157 if (io->GetType() == Node::NodeType::NODE_OUT) {
1158 inputList[i].values.push_back(io->GetTestValue());
1159 i++;
1160 }
1161 }
1162 printTime = 0.0;
1163 }
1164
1165 if (pdbTime > pdbStep) {
1166 if (!pbd.Update((currentTime / m_simTime) * 100, wxString::Format("Time = %.2fs", currentTime))) {
1167 pbd.Update(100);
1168 simStopped = true;
1169 currentTime = m_simTime;
1170 }
1171 pdbTime = 0.0;
1172 }
1173
1174 printTime += m_timeStep;
1175 currentTime += m_timeStep;
1176 pdbTime += m_timeStep;
1177 }
1178
1179 if (!simStopped) {
1180 std::vector<ElementPlotData> epdList;
1181 ElementPlotData curveData(_("I/O"), ElementPlotData::CurveType::CT_TEST);
1182 int i = 0;
1183 for (const auto& input : inputList) {
1184 curveData.AddData(input.values, input.name);
1185 curveData.SetPlot(i);
1186 curveData.SetColour(i, GetNextColour());
1187 i++;
1188 }
1189 //curveData.AddData(inputV, _("Input"));
1190 curveData.AddData(solution, _("Output"));
1191 curveData.SetPlot(i);
1192 curveData.SetColour(i, GetNextColour());
1193
1194 //curveData.SetPlot(0);
1195 //curveData.SetColour(0, *wxRED);
1196 //curveData.SetPlot(1);
1197 //curveData.SetColour(1, *wxBLUE);
1198
1199 epdList.push_back(curveData);
1200
1201 ChartView* cView = new ChartView(this, epdList, time, static_cast<PlotLib>(m_plotLib));
1202 cView->Show();
1203 cView->UpdatePlot();
1204 }
1205 }
1206 else {
1207 wxMessageDialog msgDialog(
1208 this,
1209 wxString::Format(_("Failed to solve the control system.\n%s"), solver.GetErrorMessage()),
1210 _("Error"),
1211 wxOK | wxCENTRE | wxICON_ERROR
1212 );
1213
1214 msgDialog.ShowModal();
1215 }
1216
1217 // Restore real flags.
1218 for (size_t i = 0; i < ioList.size(); i++) {
1219 if (ioList[i]->GetType() == Node::NodeType::NODE_OUT)
1220 ioList[i]->SetValue(realFlagValue[i]);
1221 }
1222 }
1223}
1224
1225std::vector<Element*> ControlEditor::GetElementList() const
1226{
1227 std::vector<Element*> elementList;
1228 for (auto& element : m_elementList) {
1229 elementList.push_back(element.get());
1230 }
1231 return elementList;
1232}
1233
1234void ControlEditor::Fit()
1235{
1236 wxPoint2DDouble leftUpCorner(0, 0);
1237 wxPoint2DDouble rightDownCorner(0, 0);
1238 std::vector<Element*> elementList = GetElementList();
1239
1240 if (!GetElementsCorners(leftUpCorner, rightDownCorner, elementList)) return;
1241 wxPoint2DDouble middleCoords = (leftUpCorner + rightDownCorner) / 2.0;
1242
1243 int width = 0.0;
1244 int height = 0.0;
1245 m_cePanel->GetSize(&width, &height);
1246
1247 const double scaleX = static_cast<double>(width) / (rightDownCorner.m_x - leftUpCorner.m_x);
1248 const double scaleY = static_cast<double>(height) / (rightDownCorner.m_y - leftUpCorner.m_y);
1249
1250 double scale = scaleX < scaleY ? scaleX : scaleY;
1251 scale = std::min(scale, m_camera->GetZoomMax());
1252 scale = std::max(scale, m_camera->GetZoomMin());
1253
1254 m_camera->SetScale(scale);
1255
1256 m_camera->StartTranslation(middleCoords);
1257 m_camera->SetTranslation(wxPoint2DDouble(width / 2.0, height / 2.0));
1258
1259 Redraw();
1260}
1261
1262void ControlEditor::OnClose(wxCloseEvent& event)
1263{
1264 if (m_ctrlContainer) { m_ctrlContainer->FillContainer(this); }
1265 ClearStates();
1266 if (m_ctrlManager)
1267 m_ctrlManager->Remove(this);
1268 Destroy();
1269 //event.Skip();
1270}
1271
1272void ControlEditor::UnselectAll()
1273{
1274 for (auto& element : m_elementList) element->SetSelected(false);
1275 for (auto& line : m_connectionList) line->SetSelected(false);
1276}
1277
1278void ControlEditor::ApplyFontToElements()
1279{
1280 for (auto& element : m_elementList) {
1281 element->SetFont(m_font);
1282 }
1283}
1284
1285bool ControlEditor::GetElementsCorners(wxPoint2DDouble& leftUpCorner,
1286 wxPoint2DDouble& rightDownCorner,
1287 const std::vector<Element*>& elementList) const
1288{
1289 if (elementList.empty()) return false;
1290 elementList[0]->CalculateBoundaries(leftUpCorner, rightDownCorner);
1291 for (size_t i = 1; i < elementList.size(); i++) {
1292 wxPoint2DDouble lu, rd;
1293 elementList[i]->CalculateBoundaries(lu, rd);
1294 if (lu.m_x < leftUpCorner.m_x) leftUpCorner.m_x = lu.m_x;
1295 if (lu.m_y < leftUpCorner.m_y) leftUpCorner.m_y = lu.m_y;
1296 if (rd.m_x > rightDownCorner.m_x) rightDownCorner.m_x = rd.m_x;
1297 if (rd.m_y > rightDownCorner.m_y) rightDownCorner.m_y = rd.m_y;
1298 }
1299 return true;
1300}
1301
1302void ControlEditor::ClearStates()
1303{
1304 m_elementListState.clear();
1305 m_connectionListState.clear();
1306 m_currentState = -1;
1307}
1308
1309void ControlEditor::SaveCurrentState()
1310{
1311 std::vector<std::shared_ptr<ControlElement>> stateElements;
1312 std::vector<std::shared_ptr<ConnectionLine>> stateLines;
1313
1314 // Deep copy via ControlElementContainer (same remapping logic used by generators).
1315 ControlElementContainer container;
1316 container.FillContainer(m_elementList, m_connectionList);
1317 container.GetContainerCopy(stateElements, stateLines);
1318
1319 // Drop redo states
1320 m_elementListState.resize(m_currentState + 1);
1321 m_connectionListState.resize(m_currentState + 1);
1322
1323 m_currentState++;
1324 if (m_currentState >= m_maxStates) {
1325 m_currentState = m_maxStates - 1;
1326 m_elementListState.erase(m_elementListState.begin());
1327 m_connectionListState.erase(m_connectionListState.begin());
1328 }
1329
1330 m_elementListState.emplace_back(std::move(stateElements));
1331 m_connectionListState.emplace_back(std::move(stateLines));
1332}
1333
1334void ControlEditor::SetPreviousState()
1335{
1336 m_currentState--;
1337 if (m_currentState < 0) {
1338 m_currentState = 0;
1339 return;
1340 }
1341 if (static_cast<size_t>(m_currentState) >= m_elementListState.size() ||
1342 static_cast<size_t>(m_currentState) >= m_connectionListState.size()) {
1343 return;
1344 }
1345
1346 std::vector<std::shared_ptr<ControlElement>> newElements;
1347 std::vector<std::shared_ptr<ConnectionLine>> newLines;
1348 ControlElementContainer container;
1349 container.FillContainer(m_elementListState[m_currentState], m_connectionListState[m_currentState]);
1350 container.GetContainerCopy(newElements, newLines);
1351
1352 m_elementList = std::move(newElements);
1353 m_connectionList = std::move(newLines);
1354 ApplyFontToElements();
1355 Redraw();
1356}
1357
1358void ControlEditor::SetNextState()
1359{
1360 m_currentState++;
1361 if (m_currentState < 0) m_currentState = 0;
1362 if (static_cast<size_t>(m_currentState) >= m_elementListState.size() ||
1363 static_cast<size_t>(m_currentState) >= m_connectionListState.size()) {
1364 m_currentState--;
1365 return;
1366 }
1367
1368 std::vector<std::shared_ptr<ControlElement>> newElements;
1369 std::vector<std::shared_ptr<ConnectionLine>> newLines;
1370 ControlElementContainer container;
1371 container.FillContainer(m_elementListState[m_currentState], m_connectionListState[m_currentState]);
1372 container.GetContainerCopy(newElements, newLines);
1373
1374 m_elementList = std::move(newElements);
1375 m_connectionList = std::move(newLines);
1376 ApplyFontToElements();
1377 Redraw();
1378}
1379
1380void ControlEditor::CopySelectionToClipboard()
1381{
1382 std::vector<std::shared_ptr<ControlElement>> selectedElements;
1383 std::vector<std::shared_ptr<ConnectionLine>> selectedLines;
1384
1385 for (auto& el : m_elementList) {
1386 if (el->IsSelected()) selectedElements.push_back(el);
1387 }
1388 for (auto& ln : m_connectionList) {
1389 if (ln->IsSelected()) selectedLines.push_back(ln);
1390 }
1391
1392 // If user selected only elements, include the connection lines between them (and children) to keep the
1393 // copied system consistent.
1394 if (!selectedElements.empty() && selectedLines.empty()) {
1395 std::unordered_set<Element*> selectedSet;
1396 selectedSet.reserve(selectedElements.size());
1397 for (auto& el : selectedElements) selectedSet.insert(el.get());
1398
1399 for (auto& ln : m_connectionList) {
1400 auto parents = ln->GetParentList();
1401 if (parents.size() >= 2 &&
1402 selectedSet.contains(parents[0]) &&
1403 selectedSet.contains(parents[1])) {
1404 selectedLines.push_back(ln);
1405 }
1406 }
1407 }
1408
1409 // Deep copy into clipboard payload
1410 ControlElementContainer container;
1411 container.FillContainer(selectedElements, selectedLines);
1412 std::vector<std::shared_ptr<ControlElement>> copyElements;
1413 std::vector<std::shared_ptr<ConnectionLine>> copyLines;
1414 container.GetContainerCopy(copyElements, copyLines);
1415 SanitizeControlGraph(copyElements, copyLines);
1416
1417 auto* dataObject = new ControlElementDataObject(copyElements, copyLines);
1418 if (wxTheClipboard->Open()) {
1419 wxTheClipboard->SetData(dataObject);
1420 wxTheClipboard->Close();
1421 }
1422}
1423
1424bool ControlEditor::PasteFromClipboard()
1425{
1426 if (!wxTheClipboard->Open()) {
1427 wxMessageDialog dialog(this, _("It was not possible to paste from clipboard."), _("Error"),
1428 wxOK | wxCENTER | wxICON_ERROR, wxDefaultPosition);
1429 dialog.ShowModal();
1430 return false;
1431 }
1432
1433 ControlElementDataObject dataObject;
1434 if (!wxTheClipboard->IsSupported(dataObject.GetFormat())) {
1435 wxTheClipboard->Close();
1436 return false;
1437 }
1438 if (!wxTheClipboard->GetData(dataObject)) {
1439 wxMessageDialog dialog(this, _("It was not possible to paste from clipboard."), _("Error"),
1440 wxOK | wxCENTER | wxICON_ERROR, wxDefaultPosition);
1441 dialog.ShowModal();
1442 wxTheClipboard->Close();
1443 return false;
1444 }
1445 wxTheClipboard->Close();
1446
1447 auto* lists = dataObject.GetElementsLists();
1448 if (!lists) return false;
1449
1450 // Deep copy again (clipboard payload must remain immutable)
1451 std::vector<std::shared_ptr<ControlElement>> pastedElements;
1452 std::vector<std::shared_ptr<ConnectionLine>> pastedLines;
1453 ControlElementContainer container;
1454 container.FillContainer(lists->elementList, lists->connectionList);
1455 container.GetContainerCopy(pastedElements, pastedLines);
1456 SanitizeControlGraph(pastedElements, pastedLines);
1457
1458 if (pastedElements.empty() && pastedLines.empty()) return false;
1459
1460 UnselectAll();
1461
1462 // Assign new unique IDs
1463 for (auto& el : pastedElements) {
1464 el->SetID(GetNextID());
1465 el->SetSelected(true);
1466 }
1467 for (auto& ln : pastedLines) {
1468 ln->SetID(GetNextID());
1469 ln->SetSelected(true);
1470 }
1471
1472 // Insert
1473 for (auto& el : pastedElements) {
1474 el->SetFont(m_font);
1475 m_elementList.push_back(el);
1476 }
1477 for (auto& ln : pastedLines) {
1478 m_connectionList.push_back(ln);
1479 ln->UpdatePoints();
1480 }
1481
1482 // Move the pasted objects to the mouse position (center them at mouse, like Workspace).
1483 wxPoint2DDouble mouseWorld = GetEditorMouseWorldPoint();
1484
1485 std::vector<Element*> pastedAsElements;
1486 pastedAsElements.reserve(pastedElements.size() + pastedLines.size());
1487 for (auto& el : pastedElements) pastedAsElements.push_back(el.get());
1488 for (auto& ln : pastedLines) pastedAsElements.push_back(ln.get());
1489
1490 wxPoint2DDouble leftUpCorner, rightDownCorner;
1491 if (GetElementsCorners(leftUpCorner, rightDownCorner, pastedAsElements)) {
1492 wxPoint2DDouble startPosition = (leftUpCorner + rightDownCorner) / 2.0;
1493 for (auto& e : pastedElements) {
1494 e->StartMove(startPosition);
1495 e->Move(mouseWorld);
1496 }
1497 // Ensure lines follow moved elements; do not move lines directly to avoid offset corruption.
1498 for (auto& ln : pastedLines) ln->UpdatePoints();
1499 }
1500
1501 m_mode = ControlEditorMode::MODE_PASTE;
1502 Redraw();
1503 return true;
1504}
1505
1506int ControlEditor::GetNextID()
1507{
1508 int id = 0;
1509 for (auto& element : m_elementList) {
1510 if (element->GetID() > id) id = element->GetID();
1511 }
1512 for (auto& line : m_connectionList) {
1513 if (line->GetID() > id) id = line->GetID();
1514 }
1515 id++;
1516 return id;
1517}
1518
1519void ControlEditor::BuildColourList()
1520{
1521 m_colourList.push_back(wxColour(255, 30, 0));
1522 m_colourList.push_back(wxColour(0, 30, 255));
1523 m_colourList.push_back(wxColour(0, 128, 0));
1524 m_colourList.push_back(wxColour(100, 100, 100));
1525 m_colourList.push_back(wxColour(255, 128, 0));
1526 m_colourList.push_back(wxColour(128, 0, 255));
1527 m_colourList.push_back(wxColour(0, 255, 128));
1528 m_colourList.push_back(wxColour(255, 255, 0));
1529 m_colourList.push_back(wxColour(255, 0, 255));
1530 m_colourList.push_back(wxColour(0, 255, 255));
1531 m_colourList.push_back(wxColour(128, 255, 0));
1532 m_colourList.push_back(wxColour(255, 0, 128));
1533 m_colourList.push_back(wxColour(0, 128, 255));
1534 m_colourList.push_back(wxColour(128, 128, 128));
1535 m_colourList.push_back(*wxBLACK);
1536 m_itColourList = --m_colourList.end();
1537}
1538
1539wxColour ControlEditor::GetNextColour()
1540{
1541 if (*m_itColourList == *wxBLACK)
1542 m_itColourList = m_colourList.begin();
1543 else
1544 ++m_itColourList;
1545
1546 return *m_itColourList;
1547}
1548void ControlEditor::OnCopyClick(wxCommandEvent& event)
1549{
1550 CopySelectionToClipboard();
1551}
1552void ControlEditor::OnDeleteClick(wxCommandEvent& event)
1553{
1554 DeleteSelectedElements();
1555 SaveCurrentState();
1556}
1557void ControlEditor::OnDragClick(wxCommandEvent& event)
1558{
1559 // Same behavior as middle mouse drag: anchor at current mouse, pan only on motion (no jump).
1560 m_mode = ControlEditorMode::MODE_DRAG;
1561 m_camera->StartTranslation(GetEditorMouseWorldPoint());
1562 if (m_cePanel) m_cePanel->SetFocus();
1563}
1564void ControlEditor::OnMoveClick(wxCommandEvent& event)
1565{
1566 bool hasSelectedElement = false;
1567 bool hasSelectedLine = false;
1568 std::vector<Element*> selectedObjects;
1569
1570 for (auto& element : m_elementList) {
1571 if (element->IsSelected()) {
1572 hasSelectedElement = true;
1573 selectedObjects.push_back(element.get());
1574 }
1575 }
1576 for (auto& line : m_connectionList) {
1577 if (line->IsSelected()) {
1578 hasSelectedLine = true;
1579 selectedObjects.push_back(line.get());
1580 }
1581 }
1582 if (!hasSelectedElement && !hasSelectedLine) return;
1583
1584 m_mode = hasSelectedElement ? ControlEditorMode::MODE_MOVE_ELEMENT : ControlEditorMode::MODE_MOVE_LINE;
1585
1586 wxPoint2DDouble mouseWorld = GetEditorMouseWorldPoint();
1587
1588 wxPoint2DDouble leftUp, rightDown;
1589 if (!GetElementsCorners(leftUp, rightDown, selectedObjects)) return;
1590 wxPoint2DDouble startWorld = (leftUp + rightDown) / 2.0;
1591
1592 if (hasSelectedElement) {
1593 for (auto& element : m_elementList) {
1594 if (element->IsSelected()) {
1595 element->StartMove(startWorld);
1596 element->Move(mouseWorld);
1597 auto childList = element->GetChildList();
1598 for (Element* child : childList) {
1599 if (auto line = dynamic_cast<ConnectionLine*>(child)) line->UpdatePoints();
1600 }
1601 }
1602 }
1603 }
1604 else {
1605 for (auto& line : m_connectionList) {
1606 if (line->IsSelected()) {
1607 line->StartMove(startWorld);
1608 line->Move(mouseWorld);
1609 }
1610 }
1611 }
1612 Redraw();
1613}
1614void ControlEditor::OnNewClick(wxCommandEvent& event)
1615{
1616 wxMessageDialog msgDialog(this,
1617 _("Do you want to create a new control system?\n\nAll elements will be removed."),
1618 _("Warning"),
1619 wxYES_NO | wxCENTRE | wxICON_WARNING);
1620 if (msgDialog.ShowModal() != wxID_YES) return;
1621
1622 m_elementList.clear();
1623 m_connectionList.clear();
1624 m_selectionRect = wxRect2DDouble(0, 0, 0, 0);
1625 m_mode = ControlEditorMode::MODE_EDIT;
1626 ClearStates();
1627 SaveCurrentState();
1628 Redraw();
1629}
1630void ControlEditor::OnPasteClick(wxCommandEvent& event)
1631{
1632 PasteFromClipboard();
1633}
1634void ControlEditor::OnRedoClick(wxCommandEvent& event)
1635{
1636 SetNextState();
1637}
1638void ControlEditor::OnUndoClick(wxCommandEvent& event)
1639{
1640 SetPreviousState();
1641}
1642void ControlEditor::OnFitClick(wxCommandEvent& event)
1643{
1644 Fit();
1645}
1646void ControlEditor::OnMiddleDoubleClick(wxMouseEvent& event)
1647{
1648 Fit();
1649 event.Skip();
1650}
Class responsible for the correct visualization of the elements on screen.
Definition Camera.h:31
This class is responsible to manage the charts generated in the transient electromechanical studies.
Definition ChartView.h:49
Connection between two control elements or other connection line and an element.
virtual void StartMove(wxPoint2DDouble position)
Update the element attributes related to the movement.
virtual void Move(wxPoint2DDouble position)
Move the element other position.
This class is responsible to handle the user interaction with control elements.
Class that can contain all control elements. Can identify (using RTTI) the elements from a generic li...
Clipboard payload for ControlEditor copy/paste (control elements + connection lines).
Solves in the time the control system. Can solve the control system directly from a ControlEditor or ...
Form to edit properties to test the control system created.
Base class of all elements of the program. This class is responsible for manage graphical and his dat...
Definition Element.h:114
virtual std::vector< Element * > GetParentList() const
Get the parent list.
Definition Element.h:562
virtual int GetID() const
Get the element ID.
Definition Element.h:273
virtual std::vector< Element * > GetChildList() const
Get the Child list.
Definition Element.h:567
virtual void RemoveChild(Element *child)
Remove a child from the list.
Definition Element.cpp:500
virtual void StartMove(wxPoint2DDouble position)
Update the element attributes related to the movement.
Definition Element.cpp:265
virtual void RemoveParent(Element *parent)
Remove a parent.
Definition Element.h:374
virtual void Move(wxPoint2DDouble position)
Move the element other position.
Definition Element.cpp:271
bool IsSelected() const
Checks if the element is selected.
Definition Element.h:203
virtual void Rotate(bool clockwise=true)
Rotate the element.
Definition Element.h:319
Save and opens the projects created on disk.
Definition FileHanding.h:43
Provides the communication with the power element.
Definition IOControl.h:43
Node of a control element. This class manages the user interaction with the connection and control el...
General and simulation data manager.