DynExp
Highly flexible laboratory automation for dynamically changing experiments.
Loading...
Searching...
No Matches
CircuitDiagram.cpp
Go to the documentation of this file.
1// This file is part of DynExp.
2
3#include "stdafx.h"
4#include "moc_CircuitDiagram.cpp"
5#include "ui_CircuitDiagram.h"
6#include "CircuitDiagram.h"
7#include "DynExpCore.h"
9
11{
12 const auto EnergyFunc = [](const NodeListType& Nodes, const NodeListType& LinkedNodes) {
13 double Energy = 0.0;
14
15 // Potentially slow algorithm, but since we are dealing with small graphs, this should be fine.
16 size_t CurrentNodeIndex = 0;
17 std::unordered_map<size_t, size_t> Links;
18 for (size_t i = 0; i < Nodes.size(); ++i)
19 for (size_t j = 0; j < Nodes[i]->LinkedParams.size(); ++j)
20 for (size_t k = 0; k < Nodes[i]->LinkedParams[j].LinkedItems.size(); ++k)
21 {
22 const auto LinkedItem = Nodes[i]->LinkedParams[j].LinkedItems[k].Item;
23 if (!LinkedItem)
24 continue;
25
26 // Ignore links skipping a layer. Such links should hardly occur in DynExp.
27 auto LinkedIt = std::find(LinkedNodes.cbegin(), LinkedNodes.cend(), LinkedItem);
28 if (LinkedIt == LinkedNodes.cend())
29 continue;
30
31 // Build a simplified map of all links. Ignore to which nodes they belong to.
32 Links[CurrentNodeIndex++] = LinkedIt - LinkedNodes.cbegin();
33
34 // Honor short links.
35 double LinkedIndex = static_cast<double>(LinkedIt - LinkedNodes.cbegin());
36 double CurrentIndex = static_cast<double>(i);
37 Energy += std::abs(LinkedIndex - CurrentIndex);
38 }
39
40 // Add huge penalty for each intersecting link.
41 for (const auto& A : Links)
42 for (const auto& B : Links)
43 if (B.first > A.first && A.second > B.second)
44 Energy += 10.0;
45
46 return Energy;
47 };
48
49 auto Energy = EnergyFunc(Instruments, HardwareAdapters) + EnergyFunc(Modules, Instruments);
50
51 // Normalize to keep energy range in the same range regardless of the graph's size.
52 Energy /= static_cast<double>(HardwareAdapters.size()) + static_cast<double>(Instruments.size()) + static_cast<double>(Modules.size());
53
54 return Energy;
55}
56
58{
59 // Currently, different samples differ by maximally one permutation.
60 return *this == Other ? 0.0 : 1.0;
61}
62
64{
65 return HardwareAdapters == Other.HardwareAdapters &&
66 Instruments == Other.Instruments &&
67 Modules == Other.Modules;
68}
69
72{
73 for (auto& LinkedParam : ObjectLinkParams)
74 {
75 const auto LinkedIDs = LinkedParam.get().GetLinkedIDs();
76 decltype(LinkedParamType::LinkedItems) Items;
77
78 if (HardwareAdapterNodes && LinkedParam.get().GetCommonManager() == &DynExpCore.GetHardwareAdapterManager())
79 {
80 // Inserts an empty (default-constructed) node if ID does not exist in HardwareAdapterNodes.
81 std::transform(LinkedIDs.cbegin(), LinkedIDs.cend(), std::inserter(Items, Items.begin()),
82 [HardwareAdapterNodes](const auto ID) { return &(*HardwareAdapterNodes)[ID]; });
83 }
84 else if (InstrumentNodes && LinkedParam.get().GetCommonManager() == &DynExpCore.GetInstrumentManager())
85 {
86 // Inserts an empty (default-constructed) node if ID does not exist in InstrumentNodes.
87 std::transform(LinkedIDs.cbegin(), LinkedIDs.cend(), std::inserter(Items, Items.begin()),
88 [&InstrumentNodes](const auto ID) { return &(*InstrumentNodes)[ID]; });
89 }
90
91 if (Items.empty())
92 Items.emplace_back(nullptr);
93
94 LinkedParams.emplace_back(LinkedParam.get().GetLinkTitle(), std::move(Items));
95 }
96}
97
99{
100 return std::accumulate(LinkedParams.cbegin(), LinkedParams.cend(), static_cast<size_t>(0), [](size_t Count, const LinkedParamType& Param) {
101 return Count + Param.LinkedItems.size();
102 });
103}
104
105const QColor CircuitDiagram::SocketOuterColor = QColor("darkturquoise");
106const QColor CircuitDiagram::SocketInnerColor = QColor("turquoise");
107
109 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint),
110 ui(std::make_unique<Ui::CircuitDiagram>()),
111 ContextMenu(new QMenu(this)), SelectionChanged(false), SelectedTreeWidgetItem(nullptr)
112{
113 ui->setupUi(this);
114
115 ContextMenu->addAction(ui->action_Zoom_in);
116 ContextMenu->addAction(ui->action_Zoom_out);
117 ContextMenu->addAction(ui->action_Zoom_reset);
118 ContextMenu->addSeparator();
119 ContextMenu->addAction(ui->action_Save_Image);
120
121 // For shortcuts
122 addAction(ui->action_Zoom_in);
123 addAction(ui->action_Zoom_out);
124 addAction(ui->action_Zoom_reset);
125 addAction(ui->action_Save_Image);
126}
127
131
133{
134 auto OldSelectionChanged = SelectionChanged;
135 SelectionChanged = false;
136
137 return OldSelectionChanged ? SelectedTreeWidgetItem : nullptr;
138}
139
141{
142 try
143 {
144 BuildTree(DynExpCore);
145 ArrangeTree();
146 Render();
147 }
148 catch (...)
149 {
150 Clear();
151
152 return false;
153 }
154
155 return true;
156}
157
159{
160 if (!Scene)
161 return true;
162
163 try
164 {
165 for (auto& Item : HardwareAdapterNodes)
166 UpdateItem(Item.second);
167 for (auto& Item : InstrumentNodes)
168 UpdateItem(Item.second);
169 for (auto& Item : ModuleNodes)
170 UpdateItem(Item.second);
171 }
172 catch (...)
173 {
174 return false;
175 }
176
177 return true;
178}
179
181{
182 QGraphicsItem* Item = ui->GVCircuit->itemAt(Event->pos());
183
184 if (Item && Item->data(Qt::ItemDataRole::UserRole).canConvert<decltype(CircuitDiagramItem::TreeWidgetItem)>())
185 {
186 SelectionChanged = true;
187 SelectedTreeWidgetItem = Item->data(Qt::ItemDataRole::UserRole).value<decltype(CircuitDiagramItem::TreeWidgetItem)>();
188 }
189}
190
191void CircuitDiagram::wheelEvent(QWheelEvent* Event)
192{
193 if (Event->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier))
194 {
195 if (Event->angleDelta().y() < 0)
196 ZoomOut();
197 if (Event->angleDelta().y() > 0)
198 ZoomIn();
199 }
200 else
201 QDialog::wheelEvent(Event);
202}
203
205{
206 QLinearGradient Gradient(0, 0, 0, 1); // gradient along y-direction
207 Gradient.setCoordinateMode(QGradient::ObjectMode);
208 Gradient.setColorAt(0, Qt::lightGray);
209 Gradient.setColorAt(.3, Qt::darkGray);
210 Gradient.setColorAt(1, Qt::darkGray);
211
212 return Gradient;
213}
214
216{
217 Scene.reset();
218
219 // Clear tree.
220 HardwareAdapterNodes.clear();
221 InstrumentNodes.clear();
222 ModuleNodes.clear();
223}
224
226{
227 Clear();
228
229 // Collect resources and links in between resources.
230 for (auto Res = DynExpCore.GetHardwareAdapterManager().cbegin(); Res != DynExpCore.GetHardwareAdapterManager().cend(); ++Res)
231 HardwareAdapterNodes.emplace(Res->first, CircuitDiagramItem(Res->second.TreeWidgetItem.get()));
232 for (auto Res = DynExpCore.GetInstrumentManager().cbegin(); Res != DynExpCore.GetInstrumentManager().cend(); ++Res)
233 {
234 auto Node = InstrumentNodes.emplace(Res->first, CircuitDiagramItem(Res->second.TreeWidgetItem.get()));
235 const auto Params = Res->second.ResourcePointer->GetParams();
236
237 Node.first->second.InsertLinks(Params->GetObjectLinkParams(), DynExpCore, &HardwareAdapterNodes, nullptr);
238
239 auto NetworkAddressPtr = Params->GetNetworkAddressParams();
240 if (NetworkAddressPtr)
241 Node.first->second.NetworkAddress = QString::fromStdString(NetworkAddressPtr->MakeAddress());
242 }
243 for (auto Res = DynExpCore.GetModuleManager().cbegin(); Res != DynExpCore.GetModuleManager().cend(); ++Res)
244 {
245 auto Node = ModuleNodes.emplace(Res->first, CircuitDiagramItem(Res->second.TreeWidgetItem.get()));
246 const auto Params = Res->second.ResourcePointer->GetParams();
247
248 Node.first->second.InsertLinks(Params->GetObjectLinkParams(), DynExpCore, &HardwareAdapterNodes, &InstrumentNodes);
249
250 auto NetworkAddressPtr = Params->GetNetworkAddressParams();
251 if (NetworkAddressPtr)
252 Node.first->second.NetworkAddress = QString::fromStdString(NetworkAddressPtr->MakeAddress());
253 }
254}
255
257{
258 // Sort by item name in lexicographical order.
259 GraphType Graph;
260
261 const auto ValuePtrGetter = [](auto& Pair) { return &Pair.second; };
262 std::transform(HardwareAdapterNodes.begin(), HardwareAdapterNodes.end(), std::back_inserter(Graph.HardwareAdapters), ValuePtrGetter);
263 std::transform(InstrumentNodes.begin(), InstrumentNodes.end(), std::back_inserter(Graph.Instruments), ValuePtrGetter);
264 std::transform(ModuleNodes.begin(), ModuleNodes.end(), std::back_inserter(Graph.Modules), ValuePtrGetter);
265
266 const auto CircuitDiagramItemSorter = [](const CircuitDiagramItem* a, const CircuitDiagramItem* b) {
267 return a && b && !a->Empty && !b->Empty?
268 QString::localeAwareCompare(a->TreeWidgetItem->text(TreeWidgetItemNameColumn), b->TreeWidgetItem->text(TreeWidgetItemNameColumn)) < 0 :
269 false;
270 };
271 std::sort(Graph.HardwareAdapters.begin(), Graph.HardwareAdapters.end(), CircuitDiagramItemSorter);
272 std::sort(Graph.Instruments.begin(), Graph.Instruments.end(), CircuitDiagramItemSorter);
273 std::sort(Graph.Modules.begin(), Graph.Modules.end(), CircuitDiagramItemSorter);
274
275 // Swap single items to simplify tree and to avoid intersecting links
277
278 // Determine items' real coordinates for painting.
279 const auto PositionCalculator = [](CircuitDiagramItem* Item, const qreal x, qreal& y) {
282 Item->TopLeftPos = { x, y };
283
284 y += Item->ItemHeight + NodeVSep;
285 };
286
287 qreal HardwareAdapterCurrentY = 0;
288 for (const auto Item : Graph.HardwareAdapters)
289 PositionCalculator(Item, 0, HardwareAdapterCurrentY);
290 qreal InstrumentCurrentY = 0;
291 for (const auto Item : Graph.Instruments)
292 PositionCalculator(Item, InnerWidth + NodeHSep, InstrumentCurrentY);
293 qreal ModuleCurrentY = 0;
294 for (const auto Item : Graph.Modules)
295 PositionCalculator(Item, 2 * (InnerWidth + NodeHSep), ModuleCurrentY);
296
297 // Center vertically.
298 if (HardwareAdapterCurrentY > InstrumentCurrentY && HardwareAdapterCurrentY > ModuleCurrentY)
299 {
300 for (const auto Item : Graph.Instruments)
301 Item->TopLeftPos += { 0, (HardwareAdapterCurrentY - InstrumentCurrentY) / 2 };
302 for (const auto Item : Graph.Modules)
303 Item->TopLeftPos += { 0, (HardwareAdapterCurrentY - ModuleCurrentY) / 2 };
304 }
305 else if (InstrumentCurrentY > HardwareAdapterCurrentY && InstrumentCurrentY > ModuleCurrentY)
306 {
307 for (const auto Item : Graph.HardwareAdapters)
308 Item->TopLeftPos += { 0, (InstrumentCurrentY - HardwareAdapterCurrentY) / 2 };
309 for (const auto Item : Graph.Modules)
310 Item->TopLeftPos += { 0, (InstrumentCurrentY - ModuleCurrentY) / 2 };
311 }
312 else
313 {
314 for (const auto Item : Graph.HardwareAdapters)
315 Item->TopLeftPos += { 0, (ModuleCurrentY - HardwareAdapterCurrentY) / 2 };
316 for (const auto Item : Graph.Instruments)
317 Item->TopLeftPos += { 0, (ModuleCurrentY - InstrumentCurrentY) / 2 };
318 }
319}
320
322{
323 auto Rnd = std::unique_ptr<gsl_rng, decltype([](gsl_rng* p) { gsl_rng_free(p); })>(gsl_rng_alloc(gsl_rng_mt19937));
324
325 // Ensure to always obtain the same stream of random numbers (see GSL documentation of gsl_rng_set()).
326 gsl_rng_set(Rnd.get(), 1);
327
328 gsl_siman_params_t SimAnParams;
329 SimAnParams.n_tries = 1;
330 SimAnParams.iters_fixed_T = 3;
331 SimAnParams.step_size = 1.0;
332 SimAnParams.k = 1.0;
333 SimAnParams.t_initial = 10;
334 SimAnParams.mu_t = 1.05;
335 SimAnParams.t_min = 1.0e-6;
336
337 const auto StepFunc = [](const gsl_rng* Rnd, void* Element, double StepSize) {
338 auto Graph = static_cast<GraphType*>(Element);
339 NodeListType* NodeListToModify = nullptr;
340
341 // Choose a random vector to modify.
342 const auto NodeListToModifyIndex = gsl_rng_uniform_int(Rnd, 3);
343 switch (NodeListToModifyIndex)
344 {
345 case 0:
346 NodeListToModify = &Graph->HardwareAdapters;
347 break;
348 case 1:
349 NodeListToModify = &Graph->Instruments;
350 break;
351 default:
352 NodeListToModify = &Graph->Modules;
353 }
354
355 if (NodeListToModify->size() < 2)
356 return;
357
358 // Choose two random elements to swap.
359 const auto MaxIndexPlusOne = static_cast<unsigned long>(std::min(static_cast<size_t>(std::numeric_limits<unsigned long>::max()), NodeListToModify->size()));
360 const auto SrcIndex = gsl_rng_uniform_int(Rnd, MaxIndexPlusOne);
361 const auto DestIndex = gsl_rng_uniform_int(Rnd, MaxIndexPlusOne);
362
363 if (SrcIndex != DestIndex)
364 {
365 auto OldNode = NodeListToModify->at(DestIndex);
366 NodeListToModify->at(DestIndex) = NodeListToModify->at(SrcIndex);
367 NodeListToModify->at(SrcIndex) = OldNode;
368 }
369 };
370
371 GraphType* StartSample = new GraphType(Graph);
372 try
373 {
374 gsl_siman_solve(Rnd.get(), static_cast<void*>(StartSample),
375 [](void* Element) { return static_cast<GraphType*>(Element)->EvaluateEnergy(); },
376 StepFunc,
377 [](void* a, void* b) { return static_cast<GraphType*>(a)->DistanceTo(*static_cast<GraphType*>(b)); },
378 nullptr,
379 [](void* Source, void* Dest) { *static_cast<GraphType*>(Dest) = *static_cast<GraphType*>(Source); },
380 [](void* Source) { return static_cast<void*>(new GraphType(*static_cast<GraphType*>(Source))); },
381 [](void* Element) { delete static_cast<GraphType*>(Element); },
382 0, SimAnParams);
383
384 Graph = *StartSample;
385 }
386 catch (...)
387 {
388 delete StartSample;
389
390 throw;
391 }
392
393 delete StartSample;
394}
395
397{
398 Scene = std::make_unique<QGraphicsScene>();
399
400 // Render items.
401 for (auto& Item : HardwareAdapterNodes)
402 RenderItem(Item.second, true);
403 for (auto& Item : InstrumentNodes)
404 RenderItem(Item.second, true);
405 for (auto& Item : ModuleNodes)
406 RenderItem(Item.second, false);
407
408 // Render links (from destiny to origin).
409 for (auto& Item : InstrumentNodes)
410 RenderLinks(Item.second);
411 for (auto& Item : ModuleNodes)
412 RenderLinks(Item.second);
413
414 Scene->setSceneRect(Scene->itemsBoundingRect());
415 ui->GVCircuit->setScene(Scene.get());
416}
417
418void CircuitDiagram::RenderItem(CircuitDiagramItem& Item, bool DrawOutputSocket)
419{
420 bool Valid = !Item.Empty && Item.TreeWidgetItem && Item.TreeWidgetItem->parent();
421
422 // Inner and outer rects
423 QPainterPath Path;
424 if (Valid)
425 {
426 Path.addRoundedRect(Item.TopLeftPos.x(), Item.TopLeftPos.y(),
429 Item.StateFrame = Scene->addPath(Path, { Item.TreeWidgetItem->foreground(TreeWidgetItemStateColumn), OuterPenLineWidth },
430 Qt::BrushStyle::NoBrush);
431 Item.StateFrame->setToolTip(Item.TreeWidgetItem->toolTip(TreeWidgetItemStateColumn));
432 Item.StateFrame->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
433 Path.clear();
434 }
435 Path.addRoundedRect(Item.TopLeftPos.x() + InnerMargin, Item.TopLeftPos.y() + InnerMargin,
438 if (Valid)
439 Item.ItemFrame = Scene->addPath(Path, { Qt::gray, InnerPenLineWidth }, GetGrayLinearGradient());
440 else
441 Item.ItemFrame = Scene->addPath(Path, { Qt::red, OuterPenLineWidth }, Qt::BrushStyle::NoBrush);
442 if (Valid)
443 {
444 Item.ItemFrame->setToolTip(Item.TreeWidgetItem->parent()->text(TreeWidgetItemTypeColumn));
445 Item.ItemFrame->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
446 }
447
448 // State
449 if (Valid)
450 {
452 Item.StatePixmap = Scene->addPixmap(Item.StateIcon.pixmap(TransformIconSize(StateIconSize)));
453 Item.StatePixmap->setFlag(QGraphicsItem::ItemIgnoresTransformations);
454 Item.StatePixmap->setPos(Item.StateFrame->boundingRect().bottomLeft() + QPointF(InnerMargin, -InnerMarginBottom));
455 Item.StatePixmap->setToolTip(Item.TreeWidgetItem->toolTip(TreeWidgetItemStateColumn));
456 Item.StatePixmap->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
457 Item.StateLabel = Scene->addSimpleText(Item.TreeWidgetItem->text(TreeWidgetItemStateColumn));
458 Item.StateLabel->setBrush(Item.TreeWidgetItem->foreground(TreeWidgetItemStateColumn));
459 Item.StateLabel->setPos(Item.StateFrame->boundingRect().bottomLeft() + QPointF(InnerMargin + StateIconSize + IconMargin, -InnerMarginBottom));
460 Item.StateLabel->setToolTip(Item.TreeWidgetItem->toolTip(TreeWidgetItemStateColumn));
461 Item.StateLabel->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
462 }
463
464 // Item name and icon
465 if (Valid)
466 Item.ItemIcon = Item.TreeWidgetItem->parent()->icon(TreeWidgetParentTypeColumn);
467 else
468 Item.ItemIcon = QIcon(DynExpUI::Icons::Delete);
469 Item.ItemPixmap = Scene->addPixmap(Item.ItemIcon.pixmap(TransformIconSize(TypeIconSize)));
470 Item.ItemPixmap->setFlag(QGraphicsItem::ItemIgnoresTransformations);
471 Item.ItemPixmap->setPos(Item.ItemFrame->boundingRect().topLeft() +
472 QPointF((Item.ItemFrame->boundingRect().width() - TypeIconSize) / 2, InnerStartHeight / 3 + IconMargin));
473 if (Valid)
474 {
475 Item.ItemPixmap->setToolTip(Item.TreeWidgetItem->parent()->text(TreeWidgetItemTypeColumn));
476 Item.ItemPixmap->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
477 Item.ItemLabel = Scene->addSimpleText(Item.TreeWidgetItem->text(TreeWidgetItemNameColumn));
478 }
479 else
480 {
481 Item.ItemLabel = Scene->addSimpleText("Deleted item");
482 Item.ItemLabel->setBrush(Qt::red);
483 }
484 auto ItemLabelFont = Item.ItemLabel->font();
485 ItemLabelFont.setBold(true);
486 Item.ItemLabel->setFont(ItemLabelFont);
487 while (Item.ItemLabel->boundingRect().width() > InnerWidth - InnerMargin)
488 Item.ItemLabel->setText(Item.ItemLabel->text().remove(Item.ItemLabel->text().length() - 6, 6) + "...");
489 Item.ItemLabel->setPos(Item.ItemFrame->boundingRect().topLeft() + QPointF(InnerMargin, InnerMargin / 2));
490 if (Valid)
491 {
492 Item.ItemLabel->setToolTip(Item.TreeWidgetItem->toolTip(TreeWidgetItemTypeColumn));
493 Item.ItemLabel->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
494 }
495
496 // Output socket
497 if (DrawOutputSocket)
498 {
499 Item.OutputSocket = Scene->addEllipse(Item.ItemFrame->boundingRect().right() - 4 * InnerPenLineWidth,
500 Item.ItemFrame->boundingRect().topRight().y() + (Item.ItemFrame->boundingRect().height() - SocketDiameter) / 6.0 *
501 (Valid && !Item.NetworkAddress.isEmpty() ? 4.0 : 3.0),
502 SocketDiameter, SocketDiameter, { Valid ? SocketOuterColor : Qt::darkRed, SocketPenLineWidth }, Valid ? SocketInnerColor : Qt::red);
503 Item.OutputSocket->setZValue(-1);
504 }
505
506 // Parameters and links between items
507 if (Valid)
508 {
509 qreal CurrentY = Item.ItemLabel->pos().y() + Item.ItemLabel->boundingRect().height() + TypeIconSize + InnerMarginTop;
510 for (auto& LinkedParam : Item.LinkedParams)
511 {
513
514 QPainterPath Path;
515 Path.addPolygon(QPolygonF({
516 { Item.ItemFrame->boundingRect().left() + InnerMargin, CurrentY},
517 { Item.ItemFrame->boundingRect().left() + InnerMargin, CurrentY + LinkedParam.LinkedItems.size() * AdditionalHeightPerParam}
518 }));
519 ParamGraphicsItem.Frame = Scene->addPath(Path, { SocketOuterColor, OuterPenLineWidth / 2 }, Qt::BrushStyle::NoBrush);
520
521 ParamGraphicsItem.Label = Scene->addSimpleText(LinkedParam.LinkTitle.data());
522 ParamGraphicsItem.Label->setBrush(SocketOuterColor);
523 while (ParamGraphicsItem.Label->boundingRect().width() > InnerWidth - InnerMargin - IconMargin)
524 ParamGraphicsItem.Label->setText(ParamGraphicsItem.Label->text().remove(ParamGraphicsItem.Label->text().length() - 6, 6) + "...");
525 ParamGraphicsItem.Label->setPos(ParamGraphicsItem.Frame->boundingRect().center() + QPointF(IconMargin, 0));
526 ParamGraphicsItem.Label->setPos(ParamGraphicsItem.Label->pos() - QPointF(0, ParamGraphicsItem.Label->boundingRect().height() / 2));
527 ParamGraphicsItem.Label->setToolTip(LinkedParam.LinkTitle.data());
528 ParamGraphicsItem.Label->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
529
530 for (size_t LinkIndex = 0; LinkIndex < LinkedParam.LinkedItems.size(); ++LinkIndex)
531 {
532 LinkedParam.LinkedItems[LinkIndex].SocketIndex = ParamGraphicsItem.Sockets.size();
533 ParamGraphicsItem.Sockets.push_back(Scene->addEllipse(Item.ItemFrame->boundingRect().left() - 2 * InnerPenLineWidth,
535 SocketDiameter, SocketDiameter, { SocketOuterColor, SocketPenLineWidth }, SocketInnerColor));
536 ParamGraphicsItem.Sockets.back()->setToolTip(ParamGraphicsItem.Label->text());
537 ParamGraphicsItem.Sockets.back()->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
538 ParamGraphicsItem.Sockets.back()->setZValue(-1);
539 }
540
541 LinkedParam.ParamGraphicsItemIndex = Item.ParamGraphicsItems.size();
542 Item.ParamGraphicsItems.push_back(std::move(ParamGraphicsItem));
543
544 CurrentY += LinkedParam.LinkedItems.size() * AdditionalHeightPerParam + ParamSep;
545 }
546 }
547
548 // Network address
549 if (Valid && !Item.NetworkAddress.isEmpty())
550 {
552 Item.NetworkPixmap = Scene->addPixmap(Item.NetworkIcon.pixmap(TransformIconSize(TypeIconSize)));
553 Item.NetworkPixmap->setFlag(QGraphicsItem::ItemIgnoresTransformations);
554 Item.NetworkPixmap->setPos(Item.ItemFrame->boundingRect().topRight() + QPointF(NetworkIconDistance, 0));
555 Item.NetworkPixmap->setToolTip(Item.NetworkAddress);
556 Item.NetworkPixmap->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
557
558 QPainterPath Path;
559 Path.moveTo(Item.ItemFrame->boundingRect().topRight() + QPointF(0, TypeIconSize / 2));
560 Path.lineTo(Item.ItemFrame->boundingRect().topRight() + QPointF(NetworkIconDistance, TypeIconSize / 2));
561 QPen Pen(DynExpUI::DarkPalette::blue, OuterPenLineWidth, Qt::CustomDashLine, Qt::FlatCap);
562 Pen.setDashPattern({ 1.5, 1, 1.5, 1, 1.5, 1, 1.5, 1, 1.5, 1 });
563 Pen.setDashOffset(1);
564 Item.NetworkLink = Scene->addPath(Path, Pen, Qt::BrushStyle::NoBrush);
565 Item.NetworkLink->setToolTip(Item.NetworkAddress);
566 Item.NetworkLink->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue(Item.TreeWidgetItem));
567 Item.NetworkLink->setZValue(-2);
568 }
569}
570
572{
573 if (Item.Empty || !Item.TreeWidgetItem || !Item.TreeWidgetItem->parent())
574 return;
575
576 for (const auto& LinkedParam : Item.LinkedParams)
577 {
578 for (auto LinkedItem : LinkedParam.LinkedItems)
579 {
580 if (!LinkedItem.Item || !LinkedItem.Item->OutputSocket)
581 continue;
582
583 auto From = Item.ParamGraphicsItems[LinkedParam.ParamGraphicsItemIndex].Sockets[LinkedItem.SocketIndex]->boundingRect().center();
584 auto To = LinkedItem.Item->OutputSocket->boundingRect().center();
585
586 QPainterPath Path;
587 Path.moveTo(From);
588 Path.cubicTo(
589 { From.x() + (To.x() - From.x()) / 4 , From.y() },
590 { From.x() + (To.x() - From.x()) / 4 * 3, To.y() }, To);
591
592 Item.ParamGraphicsItems[LinkedParam.ParamGraphicsItemIndex].Links.push_back(Scene->addPath(Path,
593 { !LinkedItem.Item->Empty ? SocketInnerColor : Qt::red, OuterPenLineWidth }, Qt::BrushStyle::NoBrush));
594 Item.ParamGraphicsItems[LinkedParam.ParamGraphicsItemIndex].Links.back()->setZValue(-2);
595 }
596 }
597}
598
600{
601 if (!Item.Empty && Item.TreeWidgetItem && Item.TreeWidgetItem->parent() &&
602 Item.StateFrame && Item.StatePixmap && Item.StateLabel)
603 {
604 // Outer rect
605 auto StateFramePen = Item.StateFrame->pen();
606 StateFramePen.setBrush(Item.TreeWidgetItem->foreground(TreeWidgetItemStateColumn));
607 Item.StateFrame->setPen(StateFramePen);
608 Item.StateFrame->setToolTip(Item.TreeWidgetItem->toolTip(TreeWidgetItemStateColumn));
609
610 // State
612 Item.StatePixmap->setPixmap(Item.StateIcon.pixmap(TransformIconSize(StateIconSize)));
613 Item.StatePixmap->setToolTip(Item.TreeWidgetItem->toolTip(TreeWidgetItemStateColumn));
614 Item.StateLabel->setText(Item.TreeWidgetItem->text(TreeWidgetItemStateColumn));
615 Item.StateLabel->setBrush(Item.TreeWidgetItem->foreground(TreeWidgetItemStateColumn));
616 Item.StateLabel->setToolTip(Item.TreeWidgetItem->toolTip(TreeWidgetItemStateColumn));
617 }
618}
619
621{
622 return ui->GVCircuit->transform().map(QPoint(Size, Size)).x();
623}
624
626{
627 const auto RescaleFunc = [this](NodeMapType::value_type& Item) {
628 if (Item.second.StatePixmap && !Item.second.StateIcon.isNull())
629 Item.second.StatePixmap->setPixmap(Item.second.StateIcon.pixmap(TransformIconSize(StateIconSize)));
630 if (Item.second.ItemPixmap && !Item.second.ItemIcon.isNull())
631 Item.second.ItemPixmap->setPixmap(Item.second.ItemIcon.pixmap(TransformIconSize(TypeIconSize)));
632 if (Item.second.NetworkPixmap && !Item.second.NetworkIcon.isNull())
633 Item.second.NetworkPixmap->setPixmap(Item.second.NetworkIcon.pixmap(TransformIconSize(TypeIconSize)));
634 };
635
636 std::for_each(HardwareAdapterNodes.begin(), HardwareAdapterNodes.end(), RescaleFunc);
637 std::for_each(InstrumentNodes.begin(), InstrumentNodes.end(), RescaleFunc);
638 std::for_each(ModuleNodes.begin(), ModuleNodes.end(), RescaleFunc);
639}
640
642{
643 ui->GVCircuit->scale(ZoomFactor, ZoomFactor);
644
645 RescaleIcons();
646}
647
649{
650 ui->GVCircuit->scale(1 / ZoomFactor, 1 / ZoomFactor);
651
652 RescaleIcons();
653}
654
656{
657 ui->GVCircuit->resetTransform();
658 ui->GVCircuit->centerOn(ui->GVCircuit->scene()->sceneRect().center());
659
660 RescaleIcons();
661}
662
664{
665 ContextMenu->exec(mapToGlobal(Position));
666}
667
669{
670 ZoomIn();
671}
672
674{
675 ZoomOut();
676}
677
682
684{
685 auto Filename = Util::PromptSaveFilePath(this, "Save circuit diagram", ".png", "Portable Network Graphics image (*.png)");
686 if (Filename.isEmpty())
687 return;
688
689 QPixmap Pixmap = ui->GVCircuit->grab();
690 if (!Pixmap.save(Filename))
691 Util::EventLog().Log("Saving the current circuit diagram failed.", Util::ErrorType::Error);
692}
Implements a window drawing the relations between all DynExp::Object instances as a graph....
Defines DynExp's core module as an interface between the UI and DynExp objects.
Implementation of a hardware adapter to communicate text-based commands over TCP sockets.
QGraphicsEllipseItem * OutputSocket
QString NetworkAddress
String of a network address and a network port indicating whether this item connects to a network....
QTreeWidgetItem * TreeWidgetItem
Pointer to QTreeWidgetItem listed in main window's tree view. Pointer may be dereferenced after this ...
QGraphicsSimpleTextItem * StateLabel
void InsertLinks(const DynExp::ParamsBase::ObjectLinkParamsType &ObjectLinkParams, const DynExp::DynExpCore &DynExpCore, NodeMapType *HardwareAdapterNodes, NodeMapType *InstrumentNodes)
QPointF TopLeftPos
Node's top left position in the diagram in painting area's coordinates.
const bool Empty
Indicates whether this item points to an existing object.
std::vector< ParamGraphicsItemsType > ParamGraphicsItems
QGraphicsPixmapItem * NetworkPixmap
std::vector< LinkedParamType > LinkedParams
List of linked params making use of this item.
QGraphicsSimpleTextItem * ItemLabel
static constexpr int AdditionalHeightPerParam
static QLinearGradient GetGrayLinearGradient()
bool Redraw(const DynExp::DynExpCore &DynExpCore)
Rebuilds the complete circuit diagram.
NodeMapType HardwareAdapterNodes
static constexpr int InnerWidth
QTreeWidgetItem * SelectedTreeWidgetItem
static constexpr int StateIconSize
void UpdateItem(CircuitDiagramItem &Item)
QTreeWidgetItem * GetSelectedEntry()
static constexpr int ParamSep
void OnContextMenuRequested(QPoint Position)
static constexpr int InnerMarginTop
static constexpr double ZoomFactor
std::unordered_map< DynExp::ItemIDType, CircuitDiagramItem > NodeMapType
static constexpr int CornerRoundingRadius
static constexpr int TreeWidgetParentTypeColumn
std::unique_ptr< Ui::CircuitDiagram > ui
std::vector< typename NodeMapType::mapped_type * > NodeListType
static constexpr int NetworkIconDistance
void RenderLinks(CircuitDiagramItem &Item)
bool UpdateStates(const DynExp::DynExpCore &DynExpCore)
Updates the items' states shown in the circuit diagram displayed currently.
CircuitDiagram(QWidget *parent)
void RenderItem(CircuitDiagramItem &Item, bool DrawOutputSocket)
static constexpr int TreeWidgetItemTypeColumn
static constexpr int TreeWidgetItemStateColumn
NodeMapType InstrumentNodes
static const QColor SocketInnerColor
virtual void mouseDoubleClickEvent(QMouseEvent *Event) override
std::unique_ptr< QGraphicsScene > Scene
static constexpr int TypeIconSize
static constexpr int InnerStartHeight
static constexpr int IconMargin
static constexpr int SocketDiameter
static constexpr int NodeHSep
static constexpr int InnerMargin
NodeMapType ModuleNodes
static constexpr int SocketPenLineWidth
static constexpr int NodeVSep
static constexpr int TreeWidgetItemNameColumn
static const QColor SocketOuterColor
virtual void wheelEvent(QWheelEvent *Event) override
static constexpr int InnerPenLineWidth
void RefineBySimulatedAnnealing(GraphType &Graph)
void BuildTree(const DynExp::DynExpCore &DynExpCore)
static constexpr int OuterPenLineWidth
int TransformIconSize(int Size) const
static constexpr int InnerMarginBottom
DynExp's core class acts as the interface between the user interface and DynExp's internal data like ...
Definition DynExpCore.h:127
auto & GetInstrumentManager() noexcept
Getter for the instrument manager.
Definition DynExpCore.h:287
auto & GetModuleManager() noexcept
Getter for the module manager.
Definition DynExpCore.h:293
auto & GetHardwareAdapterManager() noexcept
Getter for the hardware adapter manager.
Definition DynExpCore.h:281
std::vector< std::reference_wrapper< LinkBase > > ObjectLinkParamsType
Type of a list of all owned object link parameters.
Definition Object.h:352
void Log(const std::string &Message, const ErrorType Type=ErrorType::Info, const size_t Line=0, const std::string &Function="", const std::string &File="", const int ErrorCode=0, const std::stacktrace &Trace={}) noexcept
Logs an event from information specified manually.
Definition Util.cpp:317
const QColor blue(42, 130, 218)
constexpr auto Network
constexpr auto Delete
EventLogger & EventLog()
This function holds a static EventLogger instance and returns a reference to it. DynExp uses only one...
Definition Util.cpp:517
QString PromptSaveFilePath(QWidget *Parent, const QString &Title, const QString &DefaultSuffix, const QString &NameFilter, const QString &InitialDirectory)
Works as PromptOpenFilePath() but asks the user to select a single file which does not need to exist.
Definition QtUtil.cpp:128
Accumulates include statements to provide a precompiled header.
bool operator==(const GraphType &Other) const
double DistanceTo(const GraphType &Other) const