DynExp
Highly flexible laboratory automation for dynamically changing experiments.
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 
104 const QColor CircuitDiagram::SocketOuterColor = QColor("darkturquoise");
105 const 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 
127 {
128 }
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 
189 void 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 
416 void 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(),
425  InnerWidth + 2 * InnerMargin, Item.ItemHeight,
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,
532  CurrentY + AdditionalHeightPerParam * LinkIndex + (AdditionalHeightPerParam - SocketDiameter) / 2,
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 
677 {
678  ZoomReset();
679 }
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 * ItemPixmap
QGraphicsPixmapItem * NetworkPixmap
QGraphicsPixmapItem * StatePixmap
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 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 & GetModuleManager() noexcept
Getter for the module manager.
Definition: DynExpCore.h:293
auto & GetHardwareAdapterManager() noexcept
Getter for the hardware adapter manager.
Definition: DynExpCore.h:281
auto & GetInstrumentManager() noexcept
Getter for the instrument manager.
Definition: DynExpCore.h:287
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 darkGray(53, 53, 53)
const QColor gray(128, 128, 128)
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 EvaluateEnergy() const
double DistanceTo(const GraphType &Other) const