HAL  v4.5.0-124-g47ab54673
The Hardware Analyzer - a comprehensive reverse engineering and manipulation framework for gate-level netlists.
graph_widget.cpp
Go to the documentation of this file.
2 
10 //#include "gui/graph_widget/graph_layout_spinner_widget.h"
17 #include "gui/gui_def.h"
18 #include "gui/gui_globals.h"
19 #include "gui/gui_utils/netlist.h"
25 #include "gui/toolbar/toolbar.h"
29 #include "hal_core/netlist/gate.h"
31 #include "hal_core/netlist/net.h"
35 
36 #include <QApplication>
37 #include <QDebug>
38 #include <QGraphicsRectItem>
39 #include <QInputDialog>
40 #include <QKeyEvent>
41 #include <QMessageBox>
42 #include <QThread>
43 #include <QTimer>
44 #include <QToolButton>
45 #include <QVBoxLayout>
46 #include <QVariantAnimation>
47 
48 #include <atomic>
49 
50 namespace hal
51 {
52  SettingsItemSpinbox* GraphWidget::sSettingAnimationDuration =
53  new SettingsItemSpinbox("Animation Duration [1/10s]",
54  "graph_view/animation_duration",
55  10,
56  "Appearance:Graph View",
57  "Duration of 'fly to gate' animation when viewport is zooming in on new target. Unit is 1/10 second, thus 20=2sec, 10=1sec. Value of zero turns animation off.");
58 
59  GraphWidget* GraphWidget::sInstance = nullptr;
60 
62  : ContentWidget("Graph", parent), mView(new GraphGraphicsView(this)), mContext(context), mOverlay(new WidgetOverlay(this)),
63  mNavigationWidgetV3(new GraphNavigationWidget(false)), mProgressBar(nullptr),
64  mSpinnerWidget(new SpinnerWidget(this)), mCommentWidget(nullptr), mCurrentExpansion(0)
65  {
66  connect(mNavigationWidgetV3, &GraphNavigationWidget::navigationRequested, this, &GraphWidget::handleNavigationJumpRequested);
67  connect(mNavigationWidgetV3, &GraphNavigationWidget::closeRequested, mOverlay, &WidgetOverlay::hide);
68  connect(mNavigationWidgetV3, &GraphNavigationWidget::closeRequested, this, &GraphWidget::resetFocus);
69 
70  connect(mOverlay, &WidgetOverlay::clicked, this, &GraphWidget::hideOverlay);
71 
72  connect(mView, &GraphGraphicsView::moduleDoubleClicked, this, &GraphWidget::handleModuleDoubleClicked);
73 
74  mOverlay->hide();
75  mOverlay->setWidget(mNavigationWidgetV3);
76  mSpinnerWidget->hide();
77  mContentLayout->addWidget(mView);
78 
83 
84  mContext->setParentWidget(this);
85 
86  if (!mContext->sceneUpdateInProgress())
87  {
88  mView->setScene(mContext->scene());
89  mView->centerOn(0, 0);
90  }
91  sInstance = this;
93  geif->register_progress_indicator(&GraphWidget::pluginProgressIndicator);
94  }
95 
97  {
98  sInstance = nullptr;
99  }
100 
102  {
103  return mContext;
104  }
105 
107  {
108  mView->setScene(mContext->scene());
109 
110  connect(mOverlay, &WidgetOverlay::clicked, this, &GraphWidget::hideOverlay);
111 
112  hideOverlay();
113  mSpinnerWidget->hide();
114  mOverlay->setWidget(mNavigationWidgetV3);
115 
116  if (hasFocus())
117  mView->setFocus();
118  else if (mStoreViewport.mValid)
119  {
120  mView->fitInView(restoreViewport(), Qt::KeepAspectRatio);
121  }
122  }
123 
124  void GraphWidget::hideOverlay()
125  {
126  mOverlay->hide();
127  if (mProgressBar)
128  {
129  mProgressBar->deleteLater();
130  mProgressBar = nullptr;
131  mOverlay->clearWidget();
132  }
133  if (mCommentWidget)
134  {
135  mCommentWidget->deleteLater();
136  mCommentWidget = nullptr;
137  mOverlay->clearWidget();
138  }
139  }
140 
141  void GraphWidget::showBusy(int percent, const QString &text)
142  {
143  if (!mProgressBar)
144  {
145  mProgressBar = new BusyIndicator;
146  mProgressBar->setMinimumSize(256,288);
147  mOverlay->setWidget(mProgressBar);
149  mOverlay->show();
150  }
151  if (!text.isEmpty())
152  mProgressBar->setText(text);
153  mProgressBar->setValue(percent);
154  if (QThread::currentThread() == qApp->thread())
155  {
156  qApp->processEvents();
157  }
158  }
159 
161  {
162  mCommentWidget = new CommentWidget(this);
163  mCommentWidget->nodeChanged(nd);
164  mOverlay->setWidget(mCommentWidget);
165  mOverlay->show();
166  }
167 
168  void GraphWidget::showProgress(int percent, const QString& text)
169  {
170  if (!mProgressBar)
171  {
172  mProgressBar = new ProgressBar(mContext->getLayouter()->canRollback() ? mContext : nullptr);
173  mProgressBar->setMinimumSize(width() / 2, height() / 4);
174  mOverlay->setWidget(mProgressBar);
176  mOverlay->show();
177  }
178 
179  if (!text.isEmpty())
180  mProgressBar->setText(text);
181  mProgressBar->setValue(percent);
182  qApp->processEvents();
183  }
184 
186  {
187  mView->setScene(nullptr);
188 
189  disconnect(mOverlay, &WidgetOverlay::clicked, this, &GraphWidget::hideOverlay);
190 
191  mOverlay->setWidget(mSpinnerWidget);
192 
193  if (mOverlay->isHidden())
194  mOverlay->show();
195  }
196 
198  {
199  mView->setScene(nullptr);
200  mContext = nullptr;
201  }
202 
204  {
205  if (!mContext->getLayouter()->done())
206  {
207  mStoreViewport.mValid = false;
208  return;
209  }
210  QRect vg = mView->viewport()->geometry();
211  QPoint viewportCenter = (vg.topLeft() + vg.bottomRight()) / 2;
212  mStoreViewport.mValid = true;
213  mStoreViewport.mRect = mView->mapToScene(mView->viewport()->geometry()).boundingRect();
214  mStoreViewport.mGrid = mView->closestLayouterPos(mView->mapToScene(QPoint(viewportCenter)));
215  // qDebug() << "store" << viewportCenter << mStoreViewport.mGrid.first << mStoreViewport.mGrid.second << mStoreViewport.mRect;
216  }
217 
218  QRectF GraphWidget::restoreViewport(bool reset)
219  {
220  if (!mStoreViewport.mValid)
221  return QRectF();
222  if (reset)
223  mStoreViewport.mValid = false;
224 
225  QPointF centerPos(mContext->getLayouter()->gridXposition(mStoreViewport.mGrid.first.x()), mContext->getLayouter()->gridYposition(mStoreViewport.mGrid.first.y()));
226  QPointF topLeft = mStoreViewport.mRect.topLeft() - mStoreViewport.mGrid.second + centerPos;
227  return QRectF(topLeft, mStoreViewport.mRect.size());
228  }
229 
231  {
232  if (!mContext)
233  return;
234 
235  if (mContext->sceneUpdateInProgress())
236  return;
237 
238  switch (event->key())
239  {
240  case Qt::Key_Left: {
241  handleNavigationLeftRequest();
242  break;
243  }
244  case Qt::Key_Right: {
245  handleNavigationRightRequest();
246  break;
247  }
248  case Qt::Key_Up: {
249  handleNavigationUpRequest();
250  break;
251  }
252  case Qt::Key_Down: {
253  handleNavigationDownRequest();
254  break;
255  }
256  case Qt::Key_Z: {
257  if (event->modifiers() & Qt::ControlModifier) // modifiers are set as bitmasks
258  {
259  }
260  break;
261  }
262  case Qt::Key_Escape: {
264  break;
265  }
266  default:
267  break;
268  }
269  }
270 
271  void GraphWidget::substituteByVisibleModules(const QSet<u32>& gates,
272  const QSet<u32>& modules,
273  QSet<u32>& target_gates,
274  QSet<u32>& target_modules,
275  QSet<u32>& remove_gates,
276  QSet<u32>& remove_modules) const
277  {
278  // EXPAND SELECTION AND CONTEXT UP THE HIERARCHY TREE
279 
280  for (auto& mid : modules)
281  {
282  auto m = gNetlist->get_module_by_id(mid);
283  QSet<u32> common = gui_utility::parentModules(m) & mContext->modules();
284  if (common.empty())
285  {
286  // we can select the module
287  target_modules.insert(mid);
288  }
289  else
290  {
291  // we must select the respective parent module instead
292  // (this "common" set only has one element)
293  assert(common.size() == 1);
294  target_modules += common;
295  }
296  }
297 
298  for (auto& gid : gates)
299  {
300  auto g = gNetlist->get_gate_by_id(gid);
301  QSet<u32> common = gui_utility::parentModules(g) & mContext->modules();
302  if (common.empty())
303  {
304  target_gates.insert(gid);
305  }
306  else
307  {
308  // At this stage, "common" could contain multiple elements because
309  // we might have inserted a parent module where its child module is
310  // already mVisible. This is cleaned up later.
311  target_modules += common;
312  }
313  }
314 
315  // PRUNE SELECTION AND CONTEXT DOWN THE HIERARCHY TREE
316 
317  // discard (and if required schedule for removal) all modules whose
318  // parent modules we'll be showing
319  QSet<u32> new_module_set = mContext->modules() + target_modules;
320  for (auto& mid : mContext->modules())
321  {
322  auto m = gNetlist->get_module_by_id(mid);
323  if (gui_utility::parentModules(m).intersects(new_module_set))
324  {
325  remove_modules.insert(mid);
326  }
327  }
328  auto it = target_modules.constBegin();
329  while (it != target_modules.constEnd())
330  {
331  auto m = gNetlist->get_module_by_id(*it);
332  if (gui_utility::parentModules(m).intersects(new_module_set))
333  {
334  it = target_modules.erase(it);
335  }
336  else
337  {
338  ++it;
339  }
340  }
341 
342  // discard (and if required schedule for removal) all gates whose
343  // parent modules we'll be showing
344  new_module_set = (mContext->modules() - remove_modules) + target_modules;
345  for (auto& gid : mContext->gates())
346  {
347  auto g = gNetlist->get_gate_by_id(gid);
348  if (gui_utility::parentModules(g).intersects(new_module_set))
349  {
350  remove_gates.insert(gid);
351  }
352  }
353  it = target_gates.constBegin();
354  while (it != target_gates.constEnd())
355  {
356  auto g = gNetlist->get_gate_by_id(*it);
357  if (gui_utility::parentModules(g).intersects(new_module_set))
358  {
359  it = target_gates.erase(it);
360  }
361  else
362  {
363  ++it;
364  }
365  }
366  // qDebug() << "-----------";
367  // qDebug() << "requested gates" << gates;
368  // qDebug() << "requested modules" << modules;
369  // qDebug() << "target gates" << target_gates;
370  // qDebug() << "target modules" << target_modules;
371  // qDebug() << "remove gates" << remove_gates;
372  // qDebug() << "remove modules" << remove_modules;
373  }
374 
375  void GraphWidget::handleNavigationJumpRequested(const Node& origin, const u32 via_net, const QSet<u32>& to_gates, const QSet<u32>& to_modules)
376  {
377  // bool bail_animation = false;
378 
379  setFocus();
380  storeViewport();
381 
382  // ASSERT INPUTS ARE VALID
383  auto n = gNetlist->get_net_by_id(via_net);
384  if (!n || (to_gates.empty() && to_modules.empty()))
385  {
386  // prevent stuck navigation widget
387  mOverlay->hide();
388  mView->setFocus();
389  return;
390  }
391 
392  // Substitute all gates by their modules if we're showing them.
393  // This avoids ripping gates out of their already mVisible modules.
394  QSet<u32> final_modules, remove_modules;
395  QSet<u32> final_gates, remove_gates;
396  substituteByVisibleModules(to_gates, to_modules, final_gates, final_modules, remove_gates, remove_modules);
397 
398  // find out which gates and modules we still need to add to the context
399  // (this makes the cone view work)
400  QSet<u32> nonvisible_gates = final_gates - mContext->gates();
401  QSet<u32> nonvisible_modules = final_modules - mContext->modules();
402 
403  // if we don't have all gates and modules, we need to add them
404  if (!nonvisible_gates.empty() || !nonvisible_modules.empty())
405  {
406  // hint the layouter at the direction we're navigating in
407  // (so the cone view nicely extends to the right or left)
408  // either they're all inputs or all outputs, so just check the first one
409 
410  std::vector<Net*> in_nets;
411  if (to_gates.empty())
412  {
414  }
415  else
416  {
417  in_nets = utils::to_vector(gNetlist->get_gate_by_id(*to_gates.begin())->get_fan_in_nets());
418  }
419  bool netIsInput = std::find(in_nets.begin(), in_nets.end(), n) != in_nets.cend();
421 
422  // add all new gates and modules
423  if (!remove_modules.isEmpty() || !remove_gates.isEmpty())
424  {
425  ActionRemoveItemsFromObject* act = new ActionRemoveItemsFromObject(remove_modules, remove_gates);
426  act->setObject(UserActionObject(mContext->id(), UserActionObjectType::ContextView));
427  act->exec();
428  }
429  if (!nonvisible_modules.isEmpty() || !nonvisible_gates.isEmpty())
430  {
431  ActionAddItemsToObject* act = new ActionAddItemsToObject(nonvisible_modules, nonvisible_gates);
432  act->setPlacementHint(PlacementHint(placementMode, origin));
433  act->setObject(UserActionObject(mContext->id(), UserActionObjectType::ContextView));
434  act->exec();
435  }
436 
437  // FIXME find out how to do this properly
438  // If we have added any gates, the scene may have resized. In that case, the animation can be erratic,
439  // so we set a mFlag here that we can't run the animation. See end of this method for more details.
440  // bail_animation = true;
441  }
442  else
443  {
444  // if we don't need to add anything, we're done here
445 
446  mOverlay->hide();
447  //if (hasFocus())
448  mView->setFocus();
449  }
450 
451  // SELECT IN RELAY
453 
454  // TODO implement subselections on modules, then add a case for when the
455  // selection is only one module (instead of one gate)
456 
458  u32 sfinx = 0;
459 
460  if (final_gates.size() == 1 && final_modules.empty())
461  {
462  // subfocus only possible when just one gate selected
463  u32 gid = *final_gates.begin();
464  auto g = gNetlist->get_gate_by_id(gid);
465 
466  u32 cnt = 0;
467  // TODO simplify (we do actually know if we're navigating left or right)
468  for (const auto& pin : g->get_type()->get_input_pins())
469  {
470  if (g->get_fan_in_net(pin) == n) // input net
471  {
473  sfinx = cnt;
474  break;
475  }
476  cnt++;
477  }
478  if (sfoc == SelectionRelay::Subfocus::None)
479  {
480  cnt = 0;
481  for (const auto& pin : g->get_type()->get_output_pins())
482  {
483  if (g->get_fan_out_net(pin) == n) // input net
484  {
486  sfinx = cnt;
487  break;
488  }
489  cnt++;
490  }
491  }
493  }
494  else if (final_modules.size() == 1 && final_gates.empty())
495  {
496  // subfocus only possible when just one module selected
497  u32 mid = *final_modules.begin();
498  auto m = gNetlist->get_module_by_id(mid);
499  Q_ASSERT(m);
500 
501  // TODO simplify (we do actually know if we're navigating left or right)
502  Node needle(mid, Node::Module);
503  const NodeBox* nbox = mContext->getLayouter()->boxes().boxForNode(needle);
504  Q_ASSERT(nbox);
505  const GraphicsNode* gnode = static_cast<const GraphicsNode*>(nbox->item());
506  Q_ASSERT(gnode);
507 
508  int inx = gnode->inputByNet(n->get_id());
509  if (inx < 0)
510  {
511  inx = gnode->outputByNet(n->get_id());
512  if (inx >= 0)
513  {
515  sfinx = inx;
516  }
517  }
518  else
519  {
521  sfinx = inx;
522  }
524  }
525 
526  gSelectionRelay->setSelectedModules(final_modules);
527  gSelectionRelay->setSelectedGates(final_gates);
529 
530  // FIXME If the scene has been resized during this method, the animation triggered by
531  // ensure_gates_visible is broken. Thus, if that is the case, we bail out here and not
532  // trigger the animation.
533  // if (bail_animation)
534  // return;
535 
536  // JUMP TO THE GATES AND MODULES
537  ensureItemsVisible(final_gates, final_modules);
538  }
539 
540  void GraphWidget::handleModuleDoubleClicked(const u32 id)
541  {
542  // CONNECT DIRECTLY TO HANDLE ???
543  // MAYBE ADDITIONAL CODE NECESSARY HERE...
544  handleEnterModuleRequested(id);
545  }
546 
547  // ADD SOUND OR ERROR MESSAGE TO FAILED NAVIGATION ATTEMPTS
548  void GraphWidget::handleNavigationLeftRequest()
549  {
550  if (!hasFocusedItem(SelectionRelay::Subfocus::Left)) return;
552  mOverlay->setWidget(mNavigationWidgetV3);
553  switch (gSelectionRelay->focusType())
554  {
556  return;
557  }
560 
561  if (!g)
562  return;
563 
565  {
566  const GatePin* pin = g->get_type()->get_input_pins().at(gSelectionRelay->subfocusIndex());
567  Net* n = g->get_fan_in_net(pin);
568 
569  if (!n)
570  return;
571 
572  if (n->get_num_of_sources() == 0)
573  {
575  gSelectionRelay->addNet(n->get_id());
578  }
579  else if (n->get_num_of_sources() == 1)
580  {
581  handleNavigationJumpRequested(Node(g->get_id(), Node::Gate), n->get_id(), {n->get_sources().at(0)->get_gate()->get_id()}, {});
582  }
583  else
584  {
585  mNavigationWidgetV3->setup(navigateLeft);
586  mNavigationWidgetV3->setFocus();
587  mOverlay->show();
588  }
589  }
590  else if (g->get_type()->get_input_pins().size())
591  {
595  }
596 
597  return;
598  }
601 
602  if (!n)
603  return;
604 
605  if (n->get_num_of_sources() == 0)
606  return;
607 
608  if (n->get_num_of_sources() == 1)
609  {
610  handleNavigationJumpRequested(mContext->getNetDestination(n), n->get_id(), {n->get_sources()[0]->get_gate()->get_id()}, {});
611  }
612  else
613  {
614  mNavigationWidgetV3->setup(navigateLeft);
615  mNavigationWidgetV3->setFocus();
616  mOverlay->show();
617  }
618 
619  return;
620  }
623 
624  if (!m)
625  return;
626 
628  {
629  Node needle(m->get_id(), Node::Module);
630  const NodeBox* nbox = mContext->getLayouter()->boxes().boxForNode(needle);
631  Q_ASSERT(nbox);
632  const GraphicsNode* gnode = static_cast<const GraphicsNode*>(nbox->item());
633  Q_ASSERT(gnode);
634  Net* n = gNetlist->get_net_by_id(gnode->inputNets().at(gSelectionRelay->subfocusIndex()));
635  Q_ASSERT(n);
636 
637  if (n->get_num_of_sources() == 0)
638  {
640  gSelectionRelay->addNet(n->get_id());
643  }
644  else if (n->get_num_of_sources() == 1)
645  {
646  handleNavigationJumpRequested(Node(m->get_id(), Node::Module), n->get_id(), {n->get_sources()[0]->get_gate()->get_id()}, {});
647  }
648  else
649  {
650  mNavigationWidgetV3->setup(navigateLeft);
651  mNavigationWidgetV3->setFocus();
652  mOverlay->show();
653  }
654  }
655  else if (m->get_input_nets().size())
656  {
659  }
660 
661  return;
662  }
663  }
664  }
665 
666  void GraphWidget::handleNavigationRightRequest()
667  {
668  if (!hasFocusedItem(SelectionRelay::Subfocus::Right)) return;
670  mOverlay->setWidget(mNavigationWidgetV3);
671  switch (gSelectionRelay->focusType())
672  {
674  return;
675  }
678 
679  if (!g)
680  return;
681 
683  {
684  auto n = g->get_fan_out_net(g->get_type()->get_output_pins()[gSelectionRelay->subfocusIndex()]);
685  if (!n)
686  return;
687 
688  if (n->get_num_of_destinations() == 0)
689  {
691  gSelectionRelay->addNet(n->get_id());
694  }
695  else if (n->get_num_of_destinations() == 1)
696  {
697  handleNavigationJumpRequested(Node(g->get_id(), Node::Gate), n->get_id(), {n->get_destinations()[0]->get_gate()->get_id()}, {});
698  }
699  else
700  {
701  mNavigationWidgetV3->setup(navigateRight);
702  mNavigationWidgetV3->setFocus();
703  mOverlay->show();
704  }
705  }
706  else if (g->get_type()->get_output_pins().size())
707  {
710  }
711 
712  return;
713  }
716 
717  if (!n)
718  return;
719 
720  if (n->get_num_of_destinations() == 0)
721  return;
722 
723  if (n->get_num_of_destinations() == 1)
724  {
725  handleNavigationJumpRequested(mContext->getNetSource(n), n->get_id(), {n->get_destinations()[0]->get_gate()->get_id()}, {});
726  }
727  else
728  {
729  mNavigationWidgetV3->setup(navigateRight);
730  mNavigationWidgetV3->setFocus();
731  mOverlay->show();
732  }
733 
734  return;
735  }
738 
739  if (!m)
740  return;
741 
743  {
744  Node needle(m->get_id(), Node::Module);
745  const NodeBox* nbox = mContext->getLayouter()->boxes().boxForNode(needle);
746  Q_ASSERT(nbox);
747  const GraphicsNode* gnode = static_cast<const GraphicsNode*>(nbox->item());
748  Q_ASSERT(gnode);
749  Net* n = gNetlist->get_net_by_id(gnode->outputNets().at(gSelectionRelay->subfocusIndex()));
750  Q_ASSERT(n);
751 
752  if (n->get_num_of_destinations() == 0)
753  {
755  gSelectionRelay->addNet(n->get_id());
758  }
759  else if (n->get_num_of_destinations() == 1)
760  {
761  handleNavigationJumpRequested(Node(m->get_id(), Node::Module), n->get_id(), {n->get_destinations()[0]->get_gate()->get_id()}, {});
762  }
763  else
764  {
765  mNavigationWidgetV3->setup(navigateRight);
766  mNavigationWidgetV3->setFocus();
767  mOverlay->show();
768  }
769  }
770  else if (m->get_output_nets().size())
771  {
774  }
775  }
776  }
777  }
778 
779  bool GraphWidget::hasFocusedItem(SelectionRelay::Subfocus navigateDirection) const
780  {
782  {
783  // focus item has not been set but since only one item is selected it is obvious what the user wants
790  }
791 
792  u32 id = gSelectionRelay->focusId();
793  switch (gSelectionRelay->focusType())
794  {
796  if (!mContext->modules().contains(id))
797  {
798  log_warning("gui", "Cannot navigate from selected origin module ID={}, folded module not found on current view.", id);
799  return false;
800  }
801  break;
803  if (!mContext->gates().contains(id))
804  {
805  log_warning("gui", "Cannot navigate from selected origin gate ID={}, gate not found on current view.", id);
806  return false;
807  }
808  break;
810  switch (navigateDirection) {
812  for (const Endpoint* ep : gNetlist->get_net_by_id(id)->get_sources())
813  if (ep->get_gate()) return true;
814  break;
816  for (const Endpoint* ep : gNetlist->get_net_by_id(id)->get_destinations())
817  if (ep->get_gate()) return true;
818  break;
819  default:
820  return true;
821  }
822  log_warning("gui", "Cannot navigate from selected origin net ID={}, net not found on current view.", id);
823  return false;
824  default:
825  log_warning("gui", "Cannot navigate, no origin selected");
826  return false;
827  }
828  return true;
829  }
830 
831  void GraphWidget::handleNavigationUpRequest()
832  {
833  if (!hasFocusedItem(SelectionRelay::Subfocus::None)) return;
834  // FIXME this is ugly
838  }
839 
840  void GraphWidget::handleNavigationDownRequest()
841  {
842  if (!hasFocusedItem(SelectionRelay::Subfocus::None)) return;
843  // FIXME this is ugly
847  }
848 
849  void GraphWidget::handleEnterModuleRequested(const u32 id)
850  {
851  auto m = gNetlist->get_module_by_id(id);
852  if (m->get_gates().empty() && m->get_submodules().empty())
853  {
854  QMessageBox msg;
855  msg.setText("This module does not contain any gates, it cannot be unfolded.");
856  msg.setWindowTitle("Error");
857  msg.exec();
858  return;
859  // We would otherwise allow creation of a context with no gates, which
860  // is bad because that context won't react to any updates since empty
861  // contexts can't infer their corresponding module from their contents
862  }
863 
864  if (mContext->gates().isEmpty() && mContext->modules() == QSet<u32>({id}))
865  {
866  ActionUnfoldModule* act = new ActionUnfoldModule(id);
867  act->setContextId(mContext->id());
868  act->exec();
869  }
870  else
872  }
873 
874  void GraphWidget::ensureItemsVisible(const QSet<u32>& gates, const QSet<u32>& modules)
875  {
876  if (mContext->sceneUpdateInProgress())
877  return;
878 
879  int min_x = INT_MAX;
880  int min_y = INT_MAX;
881  int max_x = INT_MIN;
882  int max_y = INT_MIN;
883 
884  for (auto id : gates)
885  {
886  auto rect = mContext->scene()->getGateItem(id)->sceneBoundingRect();
887 
888  min_x = std::min(min_x, static_cast<int>(rect.left()));
889  max_x = std::max(max_x, static_cast<int>(rect.right()));
890  min_y = std::min(min_y, static_cast<int>(rect.top()));
891  max_y = std::max(max_y, static_cast<int>(rect.bottom()));
892  }
893 
894  // TODO clean up redundancy
895  for (auto id : modules)
896  {
897  auto rect = mContext->scene()->getModuleItem(id)->sceneBoundingRect();
898 
899  min_x = std::min(min_x, static_cast<int>(rect.left()));
900  max_x = std::max(max_x, static_cast<int>(rect.right()));
901  min_y = std::min(min_y, static_cast<int>(rect.top()));
902  max_y = std::max(max_y, static_cast<int>(rect.bottom()));
903  }
904 
905  auto targetRect = QRectF(min_x, min_y, max_x - min_x, max_y - min_y).marginsAdded(QMarginsF(20, 20, 20, 20));
906 
907  focusRect(targetRect, true);
908  }
909 
911  {
912  if (mContext->sceneUpdateInProgress())
913  return;
914 
915  if (!mContext->gates().contains(gSelectionRelay->selectedGates()) || !mContext->nets().contains(gSelectionRelay->selectedNets())
916  || !mContext->modules().contains(gSelectionRelay->selectedModules()))
917  return;
918 
919  int min_x = INT_MAX;
920  int min_y = INT_MAX;
921  int max_x = INT_MIN;
922  int max_y = INT_MIN;
923 
924  for (auto id : gSelectionRelay->selectedGatesList())
925  {
926  const GraphicsGate* gg = mContext->scene()->getGateItem(id);
927  if (!gg) continue;
928  auto rect = gg->sceneBoundingRect();
929 
930  min_x = std::min(min_x, static_cast<int>(rect.left()));
931  max_x = std::max(max_x, static_cast<int>(rect.right()));
932  min_y = std::min(min_y, static_cast<int>(rect.top()));
933  max_y = std::max(max_y, static_cast<int>(rect.bottom()));
934  }
935 
936  for (auto id : gSelectionRelay->selectedNetsList())
937  {
938  const GraphicsNet* gn = mContext->scene()->getNetItem(id);
939  if (!gn) continue;
940  auto rect = gn->sceneBoundingRect();
941 
942  min_x = std::min(min_x, static_cast<int>(rect.left()));
943  max_x = std::max(max_x, static_cast<int>(rect.right()));
944  min_y = std::min(min_y, static_cast<int>(rect.top()));
945  max_y = std::max(max_y, static_cast<int>(rect.bottom()));
946  }
947 
948  for (auto id : gSelectionRelay->selectedModulesList())
949  {
950  const GraphicsModule* gm = mContext->scene()->getModuleItem(id);
951  if (!gm) continue;
952  auto rect = gm->sceneBoundingRect();
953 
954  min_x = std::min(min_x, static_cast<int>(rect.left()));
955  max_x = std::max(max_x, static_cast<int>(rect.right()));
956  min_y = std::min(min_y, static_cast<int>(rect.top()));
957  max_y = std::max(max_y, static_cast<int>(rect.bottom()));
958  }
959 
960  if (min_x == INT_MAX ||
961  min_y == INT_MAX ||
962  max_x == INT_MIN ||
963  max_y == INT_MIN )
964  {
965  log_warning("gui", "Attempt to zoom in on graphics item before layout was rendered.");
966  return;
967  }
968 
969  auto targetRect = QRectF(min_x, min_y, max_x - min_x, max_y - min_y).marginsAdded(QMarginsF(20, 20, 20, 20));
970 
971  focusRect(targetRect, true);
972  }
973 
974  void GraphWidget::focusRect(QRectF targetRect, bool applyCenterFix)
975  {
976  QRectF currentRect;
977  if (mStoreViewport.mValid)
978  {
979  currentRect = restoreViewport();
980  mView->fitInView(currentRect, Qt::KeepAspectRatio);
981  /*
982  QRect vg = mView->viewport()->geometry();
983  QPoint viewportCenter = (vg.topLeft() + vg.bottomRight()) / 2;
984  QVector<QPoint> qp = mView->closestLayouterPos(mView->mapToScene(QPoint(viewportCenter)));
985  qDebug() << "focus" << viewportCenter << qp[0] << qp[1] << currentRect;
986  */
987  }
988  else
989  {
990  currentRect = mView->mapToScene(mView->viewport()->geometry()).boundingRect();
991  }
992 
993  int durationMsec = sSettingAnimationDuration->value().toInt() * 100;
994 
995  //check prevents jitter bug / resizing bug occuring due to error in 'fitToView'
996  //only happens when current and target are the same as on last usage
997  //solution -> just disable the focus alltogether if current and target are the same as last time, no need to fire animation again
998  if (!(targetRect == mLastTargetRect && currentRect == mRectAfterFocus))
999  {
1000  mLastTargetRect = targetRect;
1001 
1002  if (applyCenterFix)
1003  {
1004  auto centerFix = targetRect.center();
1005  targetRect.setWidth(std::max(targetRect.width(), currentRect.width()));
1006  targetRect.setHeight(std::max(targetRect.height(), currentRect.height()));
1007  targetRect.moveCenter(centerFix);
1008  }
1009 
1010  if (durationMsec)
1011  {
1012  auto anim = new QVariantAnimation();
1013  anim->setDuration(durationMsec);
1014  anim->setStartValue(currentRect);
1015  anim->setEndValue(targetRect);
1016 
1017  connect(anim, &QVariantAnimation::valueChanged, [=](const QVariant& value) { mView->fitInView(value.toRectF(), Qt::KeepAspectRatio); });
1018 
1019  connect(anim, &QVariantAnimation::finished, [this]() { mRectAfterFocus = mView->mapToScene(mView->viewport()->geometry()).boundingRect(); });
1020 
1022  }
1023  else
1024  {
1025  mView->fitInView(targetRect, Qt::KeepAspectRatio);
1026  }
1027  }
1028  }
1029 
1031  {
1032  const GraphicsGate* gate = mContext->scene()->getGateItem(gateId);
1033 
1034  if (gate)
1035  {
1036  QRectF targetRect = gate->sceneBoundingRect().marginsAdded(QMargins(50, 50, 50, 50));
1037  focusRect(targetRect, false);
1038  }
1039  }
1040 
1042  {
1043  const GraphicsNet* net = mContext->scene()->getNetItem(netId);
1044 
1045  if (net)
1046  {
1047  QRectF targetRect = net->sceneBoundingRect().marginsAdded(QMargins(50, 50, 50, 50));
1048  focusRect(targetRect, false);
1049  }
1050  }
1051 
1053  {
1054  const GraphicsModule* module = mContext->scene()->getModuleItem(moduleId);
1055 
1056  if (module)
1057  {
1058  QRectF targetRect = module->sceneBoundingRect().marginsAdded(QMargins(50, 50, 50, 50));
1059  focusRect(targetRect, false);
1060  }
1061  }
1062 
1063  void GraphWidget::resetFocus()
1064  {
1065  mView->setFocus();
1066  }
1067 
1069  {
1070  return mView;
1071  }
1072 
1073  void GraphWidget::pluginProgressIndicator(int percent, const std::string& msg)
1074  {
1075  // Qt widgets must only be touched on the GUI thread. If we're already on it,
1076  // call showBusy / handleSceneAvailable directly; otherwise post the update to
1077  // the main event queue.
1078  // Updates coming from a worker thread are posted to the event queue while updates from the GUI
1079  // thread are applied at once, so an update issued earlier by a worker can be delivered after the
1080  // final one and show the overlay again. Each update carries a ticket, and one that lost the race
1081  // to a newer update is dropped instead of being applied on top of it.
1082  static std::atomic<u64> issuedTicket(0);
1083  static u64 appliedTicket = 0;
1084 
1085  const u64 ticket = ++issuedTicket;
1086 
1087  QString qmsg = QString::fromStdString(msg);
1088  auto apply = [percent, qmsg, ticket]() {
1089  if (!sInstance) return;
1090  if (ticket < appliedTicket)
1091  return;
1092  appliedTicket = ticket;
1093  if (percent == 100)
1094  sInstance->handleSceneAvailable();
1095  else
1096  sInstance->showBusy(percent, qmsg);
1097  };
1098  if (QThread::currentThread() == qApp->thread())
1099  {
1100  apply();
1101  qApp->processEvents();
1102  }
1103 
1104  else
1106  }
1107 
1108 } // namespace hal
virtual void setValue(int percent)=0
virtual void setText(const QString &txt)=0
void nodeChanged(const Node &nd)
Abstract class for Widgets within HAL's ContentArea.
QVBoxLayout * mContentLayout
const std::vector< Net * > & get_fan_in_nets() const
Definition: gate.cpp:591
GateType * get_type() const
Definition: gate.cpp:125
Net * get_fan_out_net(const std::string &pin_name) const
Definition: gate.cpp:761
std::vector< GatePin * > get_input_pins() const
Definition: gate_type.cpp:269
Logical container for modules, gates, and nets.
Definition: graph_context.h:55
GraphicsScene * scene()
Node getNetSource(const Net *n) const
Node getNetDestination(const Net *n) const
const QSet< u32 > & gates() const
const QSet< u32 > & modules() const
void setParentWidget(GraphWidget *gw)
GraphLayouter * getLayouter() const
const QSet< u32 > & nets() const
bool sceneUpdateInProgress() const
void openModuleInView(u32 moduleId, bool unfold)
A view to display the rendered graph (needs a GraphicsScene).
void moduleDoubleClicked(u32 id)
qreal gridXposition(int ix) const
qreal gridYposition(int iy) const
bool canRollback() const
const NodeBoxes & boxes() const
void navigationRequested(const Node &origin, const u32 via_net, const QSet< u32 > &to_gates, const QSet< u32 > &to_modules)
void setup(SelectionRelay::Subfocus direction)
GraphWidget(GraphContext *context, QWidget *parent=nullptr)
void ensureSelectionVisible()
void keyPressEvent(QKeyEvent *event) override
void handleSceneUnavailable()
void showBusy(int percent, const QString &text)
GraphContext * getContext() const
void showProgress(int percent, const QString &text=QString())
static void pluginProgressIndicator(int percent, const std::string &msg)
void handleSceneAvailable()
void focusGate(u32 gateId)
void focusModule(u32 moduleId)
GraphGraphicsView * view()
void showComments(const Node &nd)
void focusNet(u32 netId)
void handleContextAboutToBeDeleted()
Abstract base class for gates.
Definition: graphics_gate.h:48
Abstract base class for modules.
The basic net class all other nets inherit from.
Definition: graphics_net.h:43
const GraphicsGate * getGateItem(const u32 id) const
const GraphicsModule * getModuleItem(const u32 id) const
const GraphicsNet * getNetItem(const u32 id) const
static QMap< QString, GuiExtensionInterface * > getGuiExtensions()
const std::unordered_set< Net * > & get_input_nets() const
Definition: module.cpp:542
std::vector< Endpoint * > get_destinations(const std::function< bool(Endpoint *ep)> &filter=nullptr) const
Definition: net.cpp:450
std::vector< Endpoint * > get_sources(const std::function< bool(Endpoint *ep)> &filter=nullptr) const
Definition: net.cpp:276
Gate * get_gate_by_id(const u32 gate_id) const
Definition: netlist.cpp:195
Module * get_module_by_id(u32 module_id) const
Definition: netlist.cpp:615
Net * get_net_by_id(u32 net_id) const
Definition: netlist.cpp:355
NodeBox * boxForNode(const Node &n) const
boxForNode find NodeBox by node
Definition: node_box.h:200
The Node class object represents a module or a gate.
Definition: gui_def.h:61
@ Module
Definition: gui_def.h:63
@ Gate
Definition: gui_def.h:63
void clicked()
PlacementModeType
The PlacementModeType enum either most compact arrangement (Standard) or to the left or right of give...
Definition: gui_def.h:220
int numberSelectedGates() const
ItemType focusType() const
int numberSelectedItems() const
void setFocus(ItemType ftype, u32 fid, Subfocus sfoc=Subfocus::None, u32 sfinx=0)
const QSet< u32 > & selectedNets() const
void relaySelectionChanged(void *sender)
int numberSelectedNets() const
void relaySubfocusChanged(void *sender)
QList< u32 > selectedModulesList() const
const QSet< u32 > & selectedGates() const
void setFocusDirect(ItemType ftype, u32 fid, Subfocus sfoc=Subfocus::None, u32 sfinx=0)
QList< u32 > selectedNetsList() const
const QSet< u32 > & selectedModules() const
Subfocus subfocus() const
std::vector< u32 > selectedGatesVector() const
int numberSelectedModules() const
u32 subfocusIndex() const
std::vector< u32 > selectedNetsVector() const
QList< u32 > selectedGatesList() const
void setSelectedGates(const QSet< u32 > &ids)
std::vector< u32 > selectedModulesVector() const
void setSelectedModules(const QSet< u32 > &ids)
virtual QVariant value() const override
A loading wheel.
Container for a QWidget that overlays another one.
void setWidget(QWidget *widget)
uint64_t u64
Definition: defines.h:42
uint32_t u32
Definition: defines.h:41
#define log_warning(channel,...)
Definition: log.h:76
const Module * module(const Gate *g, const NodeBoxes &boxes)
QSet< u32 > parentModules(Gate *g)
Definition: netlist.cpp:84
std::vector< T > to_vector(const Container< T, Args... > &container)
Definition: utils.h:515
Definition: defines.h:45
GraphContextManager * gGraphContextManager
Definition: plugin_gui.cpp:86
SelectionRelay * gSelectionRelay
Definition: plugin_gui.cpp:84
Netlist * gNetlist
Definition: gui_globals.h:69
Net * net
std::string origin
QWidget * viewport() const const
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void setFrameStyle(int style)
QRectF sceneBoundingRect() const const
void centerOn(const QPointF &pos)
void setDragMode(QGraphicsView::DragMode mode)
void fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRatioMode)
QPointF mapToScene(const QPoint &point) const const
void setRenderHint(QPainter::RenderHint hint, bool enabled)
void setScene(QGraphicsScene *scene)
void setTransformationAnchor(QGraphicsView::ViewportAnchor anchor)
virtual int exec() override
void setWindowTitle(const QString &title)
void setText(const QString &text)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void deleteLater()
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QThread * thread() const const
QPoint bottomRight() const const
QPoint topLeft() const const
QPointF center() const const
qreal height() const const
QRectF marginsAdded(const QMarginsF &margins) const const
void moveCenter(const QPointF &position)
void setHeight(qreal height)
void setWidth(qreal width)
qreal width() const const
QSet::iterator begin()
QSet::const_iterator constBegin() const const
QSet::const_iterator constEnd() const const
bool contains(const T &value) const const
bool empty() const const
QSet::iterator erase(QSet::iterator pos)
QSet::iterator insert(const T &value)
bool intersects(const QSet< T > &other) const const
bool isEmpty() const const
int size() const const
QString fromStdString(const std::string &str)
bool isEmpty() const const
KeepAspectRatio
QueuedConnection
Key_Left
ControlModifier
QThread * currentThread()
int toInt(bool *ok) const const
QRectF toRectF() const const
void valueChanged(const QVariant &value)
virtual bool event(QEvent *event) override
bool hasFocus() const const
void hide()
bool isHidden() const const
void setMinimumSize(const QSize &)
void setFocus()
void show()
void setSizePolicy(QSizePolicy)