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