1 #include "traditional_view.hh"
2 #include "tree_scroll_area.hh"
3 #include "layout.hh"
4 #include "structure.hh"
5 #include "node_tree.hh"
6 
7 #include <queue>
8 #include <thread>
9 #include <cmath>
10 
11 #include <QPainter>
12 #include <QDebug>
13 #include <QTimer>
14 #include <QScrollBar>
15 #include <QMouseEvent>
16 #include <QInputDialog>
17 #include <QLineEdit>
18 #include <QTextEdit>
19 #include <QThread>
20 #include <QVBoxLayout>
21 
22 #include "cursors/nodevisitor.hh"
23 #include "cursors/hide_failed_cursor.hh"
24 #include "cursors/hide_not_highlighted_cursor.hh"
25 #include "../utils/std_ext.hh"
26 #include "node_id.hh"
27 #include "shape.hh"
28 #include "../user_data.hh"
29 #include "../solver_data.hh"
30 #include "layout_computer.hh"
31 #include "../config.hh"
32 
33 #include "../nogood_dialog.hh"
34 
35 #include "../utils/perf_helper.hh"
36 #include "../utils/tree_utils.hh"
37 
38 namespace cpprofiler
39 {
40 namespace tree
41 {
42 
TraditionalView(const NodeTree & tree,UserData & ud,SolverData & sd)43 TraditionalView::TraditionalView(const NodeTree &tree, UserData &ud, SolverData &sd)
44     : tree_(tree),
45       user_data_(ud),
46       solver_data_(sd),
47       vis_flags_(utils::make_unique<VisualFlags>()),
48       layout_(utils::make_unique<Layout>()),
49       layout_computer_(utils::make_unique<LayoutComputer>(tree, *layout_, *vis_flags_))
50 {
51     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
52 
53     scroll_area_.reset(new TreeScrollArea(tree_.getRoot(), tree_, user_data_, *layout_, *vis_flags_));
54 
55     // std::cerr << "traditional view thread:" << std::this_thread::get_id() << std::endl;
56 
57     // connect(scroll_area_.get(), &TreeScrollArea::nodeClicked, this, &TraditionalView::setCurrentNode);
58     connect(scroll_area_.get(), &TreeScrollArea::nodeClicked, this, &TraditionalView::nodeSelected);
59     connect(scroll_area_.get(), &TreeScrollArea::nodeDoubleClicked, this, &TraditionalView::handleDoubleClick);
60 
61     connect(this, &TraditionalView::needsRedrawing, this, &TraditionalView::redraw);
62     connect(this, &TraditionalView::needsLayoutUpdate, this, &TraditionalView::updateLayout);
63 
64     connect(&tree, &NodeTree::childrenStructureChanged, [this](NodeID nid) {
65         if (nid == NodeID::NoNode)
66         {
67             return;
68         }
69         layout_computer_->dirtyUpLater(nid);
70         // layout_->setLayoutDone(nid, false);
71     });
72 
73     auto autoLayoutTimer = new QTimer(this);
74 
75     connect(autoLayoutTimer, &QTimer::timeout, this, &TraditionalView::autoUpdate);
76 
77     /// stop this timer up when the tree is finished?
78     autoLayoutTimer->start(100);
79 }
80 
81 TraditionalView::~TraditionalView() = default;
82 
redraw()83 void TraditionalView::redraw()
84 {
85     scroll_area_->viewport()->update();
86 }
87 
node() const88 NodeID TraditionalView::node() const
89 {
90     return user_data_.getSelectedNode();
91 }
92 
setNode(NodeID nid)93 void TraditionalView::setNode(NodeID nid)
94 {
95     user_data_.setSelectedNode(nid);
96 }
97 
navRoot()98 void TraditionalView::navRoot()
99 {
100     auto root = tree_.getRoot();
101     emit nodeSelected(root);
102     centerCurrentNode(); /// TODO: this should be needed
103     emit needsRedrawing();
104 }
105 
navDown()106 void TraditionalView::navDown()
107 {
108 
109     const auto nid = node();
110 
111     if (nid == NodeID::NoNode)
112         return;
113 
114     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
115 
116     const auto kids = tree_.childrenCount(nid);
117 
118     if (kids == 0 || vis_flags_->isHidden(nid))
119         return;
120 
121     auto first_kid = tree_.getChild(nid, 0);
122 
123     emit nodeSelected(first_kid);
124     centerCurrentNode();
125     emit needsRedrawing();
126 }
127 
navDownAlt()128 void TraditionalView::navDownAlt()
129 {
130     const auto nid = node();
131     if (nid == NodeID::NoNode)
132         return;
133 
134     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
135 
136     const auto kids = tree_.childrenCount(nid);
137 
138     if (kids == 0 || vis_flags_->isHidden(nid))
139         return;
140 
141     auto last_kid = tree_.getChild(nid, kids - 1);
142     emit nodeSelected(last_kid);
143     centerCurrentNode();
144     emit needsRedrawing();
145 }
146 
navUp()147 void TraditionalView::navUp()
148 {
149     auto nid = node();
150     if (nid == NodeID::NoNode)
151         return;
152 
153     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
154 
155     auto pid = tree_.getParent(nid);
156 
157     if (pid != NodeID::NoNode)
158     {
159         emit nodeSelected(pid);
160         centerCurrentNode();
161         emit needsRedrawing();
162     }
163 }
164 
navLeft()165 void TraditionalView::navLeft()
166 {
167     auto nid = node();
168     if (nid == NodeID::NoNode)
169         return;
170 
171     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
172 
173     auto pid = tree_.getParent(nid);
174     if (pid == NodeID::NoNode)
175         return;
176 
177     auto cur_alt = tree_.getAlternative(nid);
178 
179     if (cur_alt > 0)
180     {
181         auto kid = tree_.getChild(pid, cur_alt - 1);
182         emit nodeSelected(kid);
183         centerCurrentNode();
184         emit needsRedrawing();
185     }
186 }
187 
navRight()188 void TraditionalView::navRight()
189 {
190     /// lock mutex
191     auto nid = node();
192     if (nid == NodeID::NoNode)
193         return;
194 
195     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
196 
197     auto pid = tree_.getParent(nid);
198 
199     if (pid == NodeID::NoNode)
200         return;
201 
202     auto cur_alt = tree_.getAlternative(nid);
203 
204     auto kids = tree_.childrenCount(pid);
205 
206     if (cur_alt + 1 < kids)
207     {
208         const auto kid = tree_.getChild(pid, cur_alt + 1);
209         emit nodeSelected(kid);
210         centerCurrentNode();
211         emit needsRedrawing();
212     }
213 }
214 
setLabelShown(NodeID nid,bool val)215 void TraditionalView::setLabelShown(NodeID nid, bool val)
216 {
217     vis_flags_->setLabelShown(nid, val);
218     layout_computer_->dirtyUpLater(nid);
219 }
220 
toggleShowLabel()221 void TraditionalView::toggleShowLabel()
222 {
223     auto nid = node();
224     if (nid == NodeID::NoNode)
225         return;
226 
227     auto val = !vis_flags_->isLabelShown(nid);
228     setLabelShown(nid, val);
229     emit needsRedrawing();
230     layout_computer_->dirtyUpLater(nid);
231 }
232 
showLabelsDown()233 void TraditionalView::showLabelsDown()
234 {
235     auto nid = node();
236     if (nid == NodeID::NoNode)
237         return;
238 
239     auto val = !vis_flags_->isLabelShown(nid);
240 
241     utils::pre_order_apply(tree_, nid, [val, this](NodeID nid) {
242         setLabelShown(nid, val);
243     });
244 
245     emit needsLayoutUpdate();
246     emit needsRedrawing();
247 }
248 
showLabelsUp()249 void TraditionalView::showLabelsUp()
250 {
251     auto nid = node();
252     if (nid == NodeID::NoNode)
253         return;
254 
255     auto pid = tree_.getParent(nid);
256 
257     /// if it is root, toggle for the root only
258     if (pid == NodeID::NoNode)
259     {
260         toggleShowLabel();
261         return;
262     }
263 
264     auto val = !vis_flags_->isLabelShown(pid);
265 
266     while (nid != NodeID::NoNode)
267     {
268         setLabelShown(nid, val);
269         nid = tree_.getParent(nid);
270     }
271 
272     emit needsLayoutUpdate();
273     emit needsRedrawing();
274 }
275 
is_leaf(const NodeTree & nt,NodeID nid)276 static bool is_leaf(const NodeTree &nt, NodeID nid)
277 {
278     return nt.childrenCount(nid) == 0;
279 }
280 
hideNode(NodeID n,bool delayed)281 void TraditionalView::hideNode(NodeID n, bool delayed)
282 {
283     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
284     utils::DebugMutexLocker layout_lock(&layout_->getMutex());
285 
286     if (is_leaf(tree_, n))
287         return;
288 
289     vis_flags_->setHidden(n, true);
290 
291     dirtyUp(n);
292 
293     if (delayed)
294     {
295         setLayoutOutdated();
296     }
297     else
298     {
299         emit needsLayoutUpdate();
300     }
301     emit needsRedrawing();
302 }
303 
toggleHidden()304 void TraditionalView::toggleHidden()
305 {
306     auto nid = node();
307     if (nid == NodeID::NoNode)
308         return;
309 
310     // do not hide leaf nodes
311     if (is_leaf(tree_, nid))
312         return;
313 
314     auto val = !vis_flags_->isHidden(nid);
315     vis_flags_->setHidden(nid, val);
316 
317     dirtyUp(nid);
318 
319     emit needsLayoutUpdate();
320     emit needsRedrawing();
321 }
322 
hideFailedAt(NodeID n,bool onlyDirty)323 void TraditionalView::hideFailedAt(NodeID n, bool onlyDirty)
324 {
325     /// Do nothing if there is no tree
326     if (tree_.nodeCount() == 0)
327         return;
328 
329     bool modified = false;
330 
331     HideFailedCursor hfc(n, tree_, *vis_flags_, *layout_computer_, onlyDirty, modified);
332     PostorderNodeVisitor<HideFailedCursor>(hfc).run();
333 
334     if (modified)
335     {
336         emit needsLayoutUpdate();
337         emit needsRedrawing();
338     }
339 }
340 
hideFailed(bool onlyDirty)341 void TraditionalView::hideFailed(bool onlyDirty)
342 {
343     auto nid = node();
344     if (nid == NodeID::NoNode)
345         return;
346 
347     hideFailedAt(nid, onlyDirty);
348 }
349 
autoUpdate()350 void TraditionalView::autoUpdate()
351 {
352     if (!layout_stale_)
353         return;
354 
355     const auto changed = updateLayout();
356     if (changed)
357     {
358         emit needsRedrawing();
359     }
360 }
361 
handleDoubleClick()362 void TraditionalView::handleDoubleClick()
363 {
364 
365     auto nid = node();
366     if (nid == NodeID::NoNode)
367         return;
368 
369     auto status = tree_.getStatus(nid);
370 
371     if (status == NodeStatus::BRANCH)
372     {
373         unhideNode(nid);
374     }
375     else if (status == NodeStatus::MERGED)
376     {
377         toggleCollapsePentagon(nid);
378     }
379 
380     /// should this happen automatically whenever the layout is changed?
381     centerCurrentNode();
382 }
383 
toggleCollapsePentagon(NodeID nid)384 void TraditionalView::toggleCollapsePentagon(NodeID nid)
385 {
386     /// Use the same 'hidden' flag for now
387     auto val = !vis_flags_->isHidden(nid);
388     vis_flags_->setHidden(nid, val);
389     dirtyUp(nid);
390     emit needsLayoutUpdate();
391     emit needsRedrawing();
392 }
393 
setNodeHidden(NodeID n,bool val)394 void TraditionalView::setNodeHidden(NodeID n, bool val)
395 {
396     vis_flags_->setHidden(n, false);
397     layout_->setLayoutDone(n, false);
398 }
399 
unhideNode(NodeID nid)400 void TraditionalView::unhideNode(NodeID nid)
401 {
402     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
403     utils::DebugMutexLocker layout_lock(&layout_->getMutex());
404 
405     auto hidden = vis_flags_->isHidden(nid);
406     if (hidden)
407     {
408         setNodeHidden(nid, false);
409 
410         dirtyUp(nid);
411         emit needsLayoutUpdate();
412         emit needsRedrawing();
413     }
414 }
415 
bookmarkCurrentNode()416 void TraditionalView::bookmarkCurrentNode()
417 {
418     auto nid = node();
419 
420     if (!user_data_.isBookmarked(nid))
421     {
422         /// Add bookmark
423         bool accepted;
424         auto text = QInputDialog::getText(nullptr, "Add bookmark", "Name:", QLineEdit::Normal, "", &accepted);
425         if (!accepted)
426             return;
427 
428         user_data_.setBookmark(nid, text.toStdString());
429     }
430     else
431     {
432         /// Remove bookmark
433         user_data_.clearBookmark(nid);
434     }
435 
436     emit needsRedrawing();
437 }
438 
unhideAllAt(NodeID n)439 void TraditionalView::unhideAllAt(NodeID n)
440 {
441     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
442     utils::DebugMutexLocker layout_lock(&layout_->getMutex());
443 
444     if (n == tree_.getRoot())
445     {
446         unhideAll();
447         return;
448     }
449 
450     /// indicates if any change was made
451     bool modified = false;
452 
453     const auto action = [&](NodeID n) {
454         if (vis_flags_->isHidden(n))
455         {
456             vis_flags_->setHidden(n, false);
457             layout_->setLayoutDone(n, false);
458             dirtyUp(n);
459             modified = true;
460         }
461     };
462 
463     utils::apply_below(tree_, n, action);
464 
465     if (modified)
466     {
467         emit needsLayoutUpdate();
468         emit needsRedrawing();
469     }
470 
471     centerCurrentNode();
472 }
473 
unhideAll()474 void TraditionalView::unhideAll()
475 {
476 
477     /// faster version for the entire tree
478     if (vis_flags_->hiddenCount() == 0)
479     {
480         return;
481     }
482 
483     for (auto n : vis_flags_->hidden_nodes())
484     {
485         dirtyUp(n);
486         layout_->setLayoutDone(n, false);
487     }
488 
489     vis_flags_->unhideAll();
490 
491     emit needsLayoutUpdate();
492     emit needsRedrawing();
493     centerNode(tree_.getRoot());
494 }
495 
unhideAllAtCurrent()496 void TraditionalView::unhideAllAtCurrent()
497 {
498     auto nid = node();
499     if (nid == NodeID::NoNode)
500         return;
501 
502     unhideAllAt(nid);
503 }
504 
toggleHighlighted()505 void TraditionalView::toggleHighlighted()
506 {
507     auto nid = node();
508     if (nid == NodeID::NoNode)
509         return;
510 
511     auto val = !vis_flags_->isHighlighted(nid);
512     vis_flags_->setHighlighted(nid, val);
513 
514     emit needsRedrawing();
515 }
516 
widget()517 QWidget *TraditionalView::widget()
518 {
519     return scroll_area_.get();
520 }
521 
layout() const522 const Layout &TraditionalView::layout() const
523 {
524     return *layout_;
525 }
526 
setScale(int val)527 void TraditionalView::setScale(int val)
528 {
529     scroll_area_->setScale(val);
530 }
531 
532 /// relative to the root
global_node_x_offset(const NodeTree & tree,const Layout & layout,NodeID nid)533 static int global_node_x_offset(const NodeTree &tree, const Layout &layout, NodeID nid)
534 {
535     auto x_off = 0;
536 
537     while (nid != NodeID::NoNode)
538     {
539         x_off += layout.getOffset(nid);
540         nid = tree.getParent(nid);
541     }
542 
543     return x_off;
544 }
545 
546 /// Does this need any locking?
centerNode(NodeID nid)547 void TraditionalView::centerNode(NodeID nid)
548 {
549 
550     const auto x_offset = global_node_x_offset(tree_, *layout_, nid);
551 
552     const auto root_nid = tree_.getRoot();
553     const auto bb = layout_->getBoundingBox(root_nid);
554 
555     const auto value_x = x_offset - bb.left;
556 
557     const auto depth = utils::calculate_depth(tree_, nid);
558     const auto value_y = depth * layout::dist_y;
559 
560     scroll_area_->centerPoint(value_x, value_y);
561 }
562 
centerCurrentNode()563 void TraditionalView::centerCurrentNode()
564 {
565     centerNode(node());
566 }
567 
setCurrentNode(NodeID nid)568 void TraditionalView::setCurrentNode(NodeID nid)
569 {
570     user_data_.setSelectedNode(nid);
571     emit needsRedrawing();
572 }
573 
setAndCenterNode(NodeID nid)574 void TraditionalView::setAndCenterNode(NodeID nid)
575 {
576     setCurrentNode(nid);
577     centerNode(nid);
578 }
579 
updateLayout()580 bool TraditionalView::updateLayout()
581 {
582     const auto changed = layout_computer_->compute();
583     layout_stale_ = false;
584 
585     return changed;
586 }
587 
setLayoutOutdated()588 void TraditionalView::setLayoutOutdated()
589 {
590     layout_stale_ = true;
591 }
592 
dirtyUp(NodeID nid)593 void TraditionalView::dirtyUp(NodeID nid)
594 {
595     layout_computer_->dirtyUpLater(nid);
596 }
597 
dirtyCurrentNodeUp()598 void TraditionalView::dirtyCurrentNodeUp()
599 {
600     const auto nid = node();
601     if (nid == NodeID::NoNode)
602         return;
603 
604     dirtyUp(nid);
605 }
606 
printNodeInfo()607 void TraditionalView::printNodeInfo()
608 {
609     const auto nid = node();
610     if (nid == NodeID::NoNode)
611         return;
612 
613     print("--- Node Info: {} ----", nid);
614     print("offset: {}", layout_->getOffset(nid));
615     auto bb = layout_->getBoundingBox(nid);
616     print("bb:[{},{}]", bb.left, bb.right);
617     print("dirty: {}", layout_->isDirty(nid));
618     print("layout done for node: {}", layout_->getLayoutDone(nid));
619     print("hidden: {}", vis_flags_->isHidden(nid));
620     print("total kids: {}, ", tree_.childrenCount(nid));
621     print("has solved kids: {}, ", tree_.hasSolvedChildren(nid));
622     print("has open kids: {}", tree_.hasOpenChildren(nid));
623 
624     const auto &ng = tree_.getNogood(nid);
625 
626     if (ng.has_renamed())
627     {
628         print("nogood: {} ({})", ng.renamed(), ng.original());
629     }
630     else
631     {
632         print("nogood: {}", ng.original());
633     }
634     print("alt: {}", tree_.getAlternative(nid));
635 }
636 
637 /// Show specified subtrees and hide everyting else
showSubtrees(const NodeTree & tree,VisualFlags & vf,LayoutComputer & lc)638 static void showSubtrees(const NodeTree &tree, VisualFlags &vf, LayoutComputer &lc)
639 {
640     auto root = tree.getRoot();
641 
642     HideNotHighlightedCursor hnhc(root, tree, vf, lc);
643     PostorderNodeVisitor<HideNotHighlightedCursor>(hnhc).run();
644 }
645 
646 class TreeHighlighter : public QThread
647 {
648 
649     const NodeTree &tree_;
650     VisualFlags &vf_;
651     Layout &layout_;
652     LayoutComputer &lc_;
653 
654   public:
TreeHighlighter(const NodeTree & tree,VisualFlags & vf,Layout & lo,LayoutComputer & lc)655     TreeHighlighter(const NodeTree &tree, VisualFlags &vf, Layout &lo, LayoutComputer &lc)
656         : tree_(tree), vf_(vf), layout_(lo), lc_(lc) {}
657 
run()658     void run() override
659     {
660         utils::DebugMutexLocker t_locker(&tree_.treeMutex());
661         utils::DebugMutexLocker l_locker(&layout_.getMutex());
662 
663         auto root = tree_.getRoot();
664 
665         HideNotHighlightedCursor hnhc(root, tree_, vf_, lc_);
666         PostorderNodeVisitor<HideNotHighlightedCursor>(hnhc).run();
667     }
668 };
669 
revealNode(NodeID n)670 void TraditionalView::revealNode(NodeID n)
671 {
672     utils::DebugMutexLocker t_locker(&tree_.treeMutex());
673     utils::DebugMutexLocker l_locker(&layout_->getMutex());
674 
675     layout_computer_->dirtyUpUnconditional(n);
676 
677     while (n != NodeID::NoNode)
678     {
679         setNodeHidden(n, false);
680         n = tree_.getParent(n);
681     }
682 
683     emit needsLayoutUpdate();
684     emit needsRedrawing();
685 }
686 
highlightSubtrees(const std::vector<NodeID> & nodes,bool hide_rest,bool show_outline)687 void TraditionalView::highlightSubtrees(const std::vector<NodeID> &nodes, bool hide_rest, bool show_outline)
688 {
689     vis_flags_->unhighlightAll();
690 
691     detail::PerformanceHelper phelper;
692 
693     for (auto nid : nodes)
694     {
695         vis_flags_->setHighlighted(nid, true);
696     }
697 
698     if (hide_rest)
699     {
700         unhideAll();
701 
702         auto root = tree_.getRoot();
703         HideNotHighlightedCursor hnhc(root, tree_, *vis_flags_, *layout_computer_);
704         PostorderNodeVisitor<HideNotHighlightedCursor>(hnhc).run();
705 
706         emit needsLayoutUpdate();
707     }
708 
709     /// Note: this duplication could probably be avoided
710     if (!show_outline)
711     {
712         for (auto nid : nodes)
713         {
714             vis_flags_->setHighlighted(nid, false);
715         }
716     }
717 
718     emit needsRedrawing();
719 }
720 
721 /// Lantern Tree Visualisation
hideBySize(int size_limit)722 void TraditionalView::hideBySize(int size_limit)
723 {
724     utils::DebugMutexLocker tree_lock(&tree_.treeMutex());
725 
726     unhideAll();
727 
728     vis_flags_->resetLanternSizes();
729 
730     const int max_lantern = 127;
731 
732     const auto sizes = utils::calc_subtree_sizes(tree_);
733 
734     const auto root = tree_.getRoot();
735 
736     std::stack<NodeID> stack;
737 
738     stack.push(root);
739 
740     while (!stack.empty())
741     {
742         const auto n = stack.top();
743         stack.pop();
744 
745         const auto size = sizes.at(n);
746         const auto nkids = tree_.childrenCount(n);
747 
748         if (size > size_limit)
749         {
750             /// visit children
751             for (auto alt = 0u; alt < nkids; ++alt)
752             {
753                 stack.push(tree_.getChild(n, alt));
754             }
755         }
756         else
757         {
758             /// turn the node into a "lantern"
759             if (nkids > 0)
760             {
761                 vis_flags_->setHidden(n, true);
762                 /// lantern size
763                 auto lsize = (size * max_lantern) / size_limit;
764                 vis_flags_->setLanternSize(n, lsize);
765                 layout_->setLayoutDone(n, false);
766                 dirtyUp(n);
767             }
768         }
769     }
770 
771     emit needsLayoutUpdate();
772     emit needsRedrawing();
773 
774     centerCurrentNode();
775 }
776 
undoLanterns()777 void TraditionalView::undoLanterns()
778 {
779     vis_flags_->resetLanternSizes();
780 }
781 
showNodeInfo() const782 void TraditionalView::showNodeInfo() const
783 {
784 
785     if (!solver_data_.hasInfo())
786         return;
787 
788     const auto cur_nid = node();
789     if (cur_nid == NodeID::NoNode)
790         return;
791 
792     auto info_dialog = new QDialog;
793     auto layout = new QVBoxLayout(info_dialog);
794 
795     QTextEdit* te = new QTextEdit;
796     te->append(solver_data_.getInfo(cur_nid).c_str());
797 
798     layout->addWidget(te);
799 
800     info_dialog->setAttribute(Qt::WA_DeleteOnClose);
801     info_dialog->show();
802 }
803 
showNogoods() const804 void TraditionalView::showNogoods() const
805 {
806 
807     if (!solver_data_.hasNogoods())
808         return;
809 
810     const auto cur_nid = node();
811     if (cur_nid == NodeID::NoNode)
812         return;
813 
814     const auto nodes = utils::nodes_below(tree_, cur_nid);
815 
816     auto ng_dialog = new NogoodDialog(tree_, nodes);
817     ng_dialog->setAttribute(Qt::WA_DeleteOnClose);
818 
819     connect(ng_dialog, &NogoodDialog::nogoodClicked, [this](NodeID nid) {
820         const_cast<TraditionalView *>(this)->revealNode(nid);
821         const_cast<TraditionalView *>(this)->setAndCenterNode(nid);
822         emit nogoodsClicked({nid});
823     });
824 
825     ng_dialog->show();
826 }
827 
debugCheckLayout() const828 void TraditionalView::debugCheckLayout() const
829 {
830     ///
831     const auto order = utils::any_order(tree_);
832 
833     for (const auto n : order)
834     {
835         auto bb = layout_->getBoundingBox(n);
836         // print("bb for {} is fine", n);
837     }
838 }
839 
setDebugMode(bool v)840 void TraditionalView::setDebugMode(bool v)
841 {
842     scroll_area_->setDebugMode(true);
843     layout_computer_->setDebugMode(true);
844     emit needsLayoutUpdate();
845     emit needsRedrawing();
846 }
847 
848 } // namespace tree
849 } // namespace cpprofiler
850