1 /************************************************************************
2 * file name         : blocks_graphics_view.cpp
3 * ----------------- :
4 * creation time     : 2016/06/26
5 * author            : Victor Zarubkin
6 * email             : v.s.zarubkin@gmail.com
7 * ----------------- :
8 * description       : The file contains implementation of GraphicsScene and GraphicsView and
9 *                   : it's auxiliary classes for displyaing easy_profiler blocks tree.
10 * ----------------- :
11 * change log        : * 2016/06/26 Victor Zarubkin: Moved sources from graphics_view.h
12 *                   :       and renamed classes from My* to Prof*.
13 *                   :
14 *                   : * 2016/06/27 Victor Zarubkin: Added text shifting relatively to it's parent item.
15 *                   :       Disabled border lines painting because of vertical lines painting bug.
16 *                   :       Changed height of blocks. Variable thread-block height.
17 *                   :
18 *                   : * 2016/06/29 Victor Zarubkin: Highly optimized painting performance and memory consumption.
19 *                   :
20 *                   : * 2016/06/30 Victor Zarubkin: Replaced doubles with floats (in ProfBlockItem) for less memory consumption.
21 *                   :
22 *                   : * 2016/09/15 Victor Zarubkin: Moved sources of EasyGraphicsItem and EasyChronometerItem to separate files.
23 *                   :
24 *                   : *
25 * ----------------- :
26 * license           : Lightweight profiler library for c++
27 *                   : Copyright(C) 2016-2017  Sergey Yagovtsev, Victor Zarubkin
28 *                   :
29 *                   : Licensed under either of
30 *                   :     * MIT license (LICENSE.MIT or http://opensource.org/licenses/MIT)
31 *                   :     * Apache License, Version 2.0, (LICENSE.APACHE or http://www.apache.org/licenses/LICENSE-2.0)
32 *                   : at your option.
33 *                   :
34 *                   : The MIT License
35 *                   :
36 *                   : Permission is hereby granted, free of charge, to any person obtaining a copy
37 *                   : of this software and associated documentation files (the "Software"), to deal
38 *                   : in the Software without restriction, including without limitation the rights
39 *                   : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
40 *                   : of the Software, and to permit persons to whom the Software is furnished
41 *                   : to do so, subject to the following conditions:
42 *                   :
43 *                   : The above copyright notice and this permission notice shall be included in all
44 *                   : copies or substantial portions of the Software.
45 *                   :
46 *                   : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
47 *                   : INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
48 *                   : PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
49 *                   : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
50 *                   : TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
51 *                   : USE OR OTHER DEALINGS IN THE SOFTWARE.
52 *                   :
53 *                   : The Apache License, Version 2.0 (the "License")
54 *                   :
55 *                   : You may not use this file except in compliance with the License.
56 *                   : You may obtain a copy of the License at
57 *                   :
58 *                   : http://www.apache.org/licenses/LICENSE-2.0
59 *                   :
60 *                   : Unless required by applicable law or agreed to in writing, software
61 *                   : distributed under the License is distributed on an "AS IS" BASIS,
62 *                   : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
63 *                   : See the License for the specific language governing permissions and
64 *                   : limitations under the License.
65 ************************************************************************/
66 
67 #include <math.h>
68 #include <QGraphicsScene>
69 #include <QGraphicsProxyWidget>
70 #include <QWheelEvent>
71 #include <QMouseEvent>
72 #include <QKeyEvent>
73 #include <QScrollBar>
74 #include <QGridLayout>
75 #include <QDebug>
76 #include <QSignalBlocker>
77 #include <QGraphicsDropShadowEffect>
78 #include "blocks_graphics_view.h"
79 #include "easy_graphics_item.h"
80 #include "easy_chronometer_item.h"
81 #include "easy_graphics_scrollbar.h"
82 #include "globals.h"
83 
84 //////////////////////////////////////////////////////////////////////////
85 //////////////////////////////////////////////////////////////////////////
86 
87 const qreal MIN_SCALE = pow(::profiler_gui::SCALING_COEFFICIENT_INV, 70); // Up to 1000 sec scale
88 const qreal MAX_SCALE = pow(::profiler_gui::SCALING_COEFFICIENT, 45); // ~23000 --- Up to 10 ns scale
89 const qreal BASE_SCALE = pow(::profiler_gui::SCALING_COEFFICIENT_INV, 25); // ~0.003
90 
91 EASY_CONSTEXPR uint16_t TIMELINE_ROW_SIZE = 24;
92 
93 EASY_CONSTEXPR QRgb BACKGROUND_1 = 0xffe4e4ec;
94 EASY_CONSTEXPR QRgb BACKGROUND_2 = ::profiler::colors::White;
95 EASY_CONSTEXPR QRgb TIMELINE_BACKGROUND = 0x20000000 | (::profiler::colors::Grey800 & 0x00ffffff);// 0x20303030;
96 EASY_CONSTEXPR QRgb TIMELINE_BORDER = 0xffa8a0a0;
97 
98 EASY_CONSTEXPR int IDLE_TIMER_INTERVAL = 200; // 5Hz
99 EASY_CONSTEXPR uint64_t IDLE_TIME = 400;
100 
101 EASY_CONSTEXPR int FLICKER_INTERVAL = 10; // 100Hz
102 EASY_CONSTEXPR qreal FLICKER_FACTOR = 16.0 / FLICKER_INTERVAL;
103 
104 #ifdef max
105 #undef max
106 #endif
107 
108 #ifdef min
109 #undef min
110 #endif
111 
112 using estd::clamp;
113 
114 //////////////////////////////////////////////////////////////////////////
115 
EasyBoldLabel(const QString & _text,QWidget * _parent)116 EasyBoldLabel::EasyBoldLabel(const QString& _text, QWidget* _parent) : QLabel(_text, _parent)
117 {
118     auto f = font();
119     f.setBold(true);
120     setFont(f);
121 }
122 
~EasyBoldLabel()123 EasyBoldLabel::~EasyBoldLabel()
124 {
125 
126 }
127 
128 //////////////////////////////////////////////////////////////////////////
129 
paint(QPainter * _painter,const QStyleOptionGraphicsItem *,QWidget *)130 void EasyBackgroundItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*)
131 {
132     auto const sceneView = static_cast<EasyGraphicsView*>(scene()->parent());
133     const auto visibleSceneRect = sceneView->visibleSceneRect();
134     const auto currentScale = sceneView->scale();
135     const auto offset = sceneView->offset();
136     const auto left = offset * currentScale;
137     const auto h = visibleSceneRect.height();
138     const auto visibleBottom = h - 1;
139     const auto borderColor = QColor::fromRgb(TIMELINE_BORDER);
140     const auto textShiftY = h + TIMELINE_ROW_SIZE - 5;
141 
142     QRectF rect;
143 
144     _painter->save();
145     _painter->setTransform(QTransform::fromTranslate(-x(), -y()));
146 
147     const auto& items = sceneView->getItems();
148     if (!items.empty())
149     {
150         static const uint16_t OVERLAP = ::profiler_gui::THREADS_ROW_SPACING >> 1;
151         static const QBrush brushes[2] = {QColor::fromRgb(BACKGROUND_1), QColor::fromRgb(BACKGROUND_2)};
152         int i = -1;
153 
154         // Draw background
155         _painter->setPen(::profiler_gui::SYSTEM_BORDER_COLOR);
156         for (auto item : items)
157         {
158             ++i;
159 
160             auto br = item->boundingRect();
161             auto top = item->y() + br.top() - visibleSceneRect.top();
162             auto bottom = top + br.height();
163 
164             if (top > h || bottom < 0)
165                 continue;
166 
167             if (item->threadId() == EASY_GLOBALS.selected_thread)
168                 _painter->setBrush(QBrush(QColor::fromRgb(::profiler_gui::SELECTED_THREAD_BACKGROUND)));
169             else
170                 _painter->setBrush(brushes[i & 1]);
171 
172             rect.setRect(0, top - OVERLAP, visibleSceneRect.width(), br.height() + ::profiler_gui::THREADS_ROW_SPACING);
173             const auto dh = rect.bottom() - visibleBottom;
174             if (dh > 0)
175                 rect.setHeight(rect.height() - dh);
176 
177             if (rect.top() < 0)
178                 rect.setTop(0);
179 
180             _painter->drawRect(rect);
181         }
182     }
183 
184     // Draw timeline scale marks ----------------
185     _painter->setBrush(QColor::fromRgba(TIMELINE_BACKGROUND));
186 
187     const auto sceneStep = sceneView->timelineStep();
188     const auto factor = ::profiler_gui::timeFactor(sceneStep);
189     const auto step = sceneStep * currentScale;
190     auto first = static_cast<quint64>(offset / sceneStep);
191     const int odd = first & 1;
192     const auto nsteps = (1 + odd) * 2 + static_cast<int>(visibleSceneRect.width() / step);
193     first -= odd;
194 
195     QPen pen(borderColor);
196     pen.setWidth(2);
197     _painter->setPen(pen);
198     _painter->drawLine(QPointF(0, h), QPointF(visibleSceneRect.width(), h));
199     _painter->setPen(borderColor);
200 
201     QLineF marks[20];
202     qreal first_x = first * sceneStep;
203     const auto textWidth = QFontMetricsF(_painter->font(), sceneView).width(QString::number(static_cast<quint64>(0.5 + first_x * factor))) * ::profiler_gui::FONT_METRICS_FACTOR + 10;
204     const int n = 1 + static_cast<int>(textWidth / step);
205     int next = first % n;
206     if (next)
207         next = n - next;
208 
209     first_x *= currentScale;
210     for (int i = 0; i < nsteps; ++i, --next)
211     {
212         auto current = first_x - left + step * i;
213 
214         if ((i & 1) == 0)
215         {
216             rect.setRect(current, 0, step, h);
217             _painter->drawRect(rect);
218 
219             for (int j = 0; j < 20; ++j)
220             {
221                 auto xmark = current + j * step * 0.1;
222                 marks[j].setLine(xmark, h, xmark, h + ((j % 5) ? 4 : 8));
223             }
224 
225             _painter->drawLines(marks, 20);
226         }
227 
228         if (next <= 0)
229         {
230             next = n;
231             _painter->setPen(::profiler_gui::TEXT_COLOR);
232             _painter->drawText(QPointF(current + 1, textShiftY),
233                                QString::number(static_cast<quint64>(0.5 + (current + left) * factor / currentScale)));
234             _painter->setPen(borderColor);
235         }
236 
237         // TEST
238         // this is for testing (order of lines will be painted):
239         //_painter->setPen(Qt::black);
240         //_painter->drawText(QPointF(current + step * 0.4, h - 20), QString::number(i));
241         //_painter->setPen(Qt::gray);
242         // TEST
243     }
244     // END Draw timeline scale marks ~~~~~~~~~~~~
245 
246     _painter->restore();
247 }
248 
249 //////////////////////////////////////////////////////////////////////////
250 
paint(QPainter * _painter,const QStyleOptionGraphicsItem *,QWidget *)251 void EasyTimelineIndicatorItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*)
252 {
253     const auto sceneView = static_cast<const EasyGraphicsView*>(scene()->parent());
254     const auto visibleSceneRect = sceneView->visibleSceneRect();
255     const auto step = sceneView->timelineStep() * sceneView->scale();
256     const QString text = ::profiler_gui::autoTimeStringInt(units2microseconds(sceneView->timelineStep())); // Displayed text
257 
258     // Draw scale indicator
259     _painter->save();
260     _painter->setTransform(QTransform::fromTranslate(-x(), -y()));
261     //_painter->setCompositionMode(QPainter::CompositionMode_Difference);
262     _painter->setBrush(Qt::NoBrush);
263 
264     QPen pen(Qt::black);
265     pen.setWidth(3);
266     _painter->setPen(pen);
267 
268     _painter->drawLine(QLineF(visibleSceneRect.width() - 9 - step, visibleSceneRect.height() - 10, visibleSceneRect.width() - 11, visibleSceneRect.height() - 10));
269 
270     _painter->setPen(Qt::black);
271     _painter->drawLine(QLineF(visibleSceneRect.width() - 10 - step, visibleSceneRect.height() - 6, visibleSceneRect.width() - 10 - step, visibleSceneRect.height() - 14));
272     _painter->drawLine(QLineF(visibleSceneRect.width() - 10, visibleSceneRect.height() - 6, visibleSceneRect.width() - 10, visibleSceneRect.height() - 14));
273 
274     _painter->setPen(Qt::black);
275     _painter->setFont(EASY_GLOBALS.bg_font);
276     _painter->drawText(QRectF(visibleSceneRect.width() - 10 - step, visibleSceneRect.height() - 63, step, 50), Qt::AlignRight | Qt::AlignBottom | Qt::TextDontClip, text);
277 
278     _painter->restore();
279 }
280 
281 //////////////////////////////////////////////////////////////////////////
282 
EasyGraphicsView(QWidget * _parent)283 EasyGraphicsView::EasyGraphicsView(QWidget* _parent)
284     : Parent(_parent)
285     , m_beginTime(::std::numeric_limits<decltype(m_beginTime)>::max())
286     , m_sceneWidth(0)
287     , m_scale(1)
288     , m_offset(0)
289     , m_timelineStep(0)
290     , m_idleTime(0)
291     , m_mouseButtons(Qt::NoButton)
292     , m_pScrollbar(nullptr)
293     , m_chronometerItem(nullptr)
294     , m_chronometerItemAux(nullptr)
295     , m_popupWidget(nullptr)
296     , m_flickerSpeedX(0)
297     , m_flickerSpeedY(0)
298     , m_flickerCounterX(0)
299     , m_flickerCounterY(0)
300     , m_bDoubleClick(false)
301     , m_bUpdatingRect(false)
302     , m_bEmpty(true)
303 {
304     initMode();
305     setScene(new QGraphicsScene(this));
306     updateVisibleSceneRect();
307 }
308 
~EasyGraphicsView()309 EasyGraphicsView::~EasyGraphicsView()
310 {
311 }
312 
313 //////////////////////////////////////////////////////////////////////////
314 
removePopup(bool _removeFromScene)315 void EasyGraphicsView::removePopup(bool _removeFromScene)
316 {
317     if (m_popupWidget != nullptr)
318     {
319         auto widget = m_popupWidget->widget();
320         widget->setParent(nullptr);
321         m_popupWidget->setWidget(nullptr);
322         delete widget;
323 
324         if (_removeFromScene)
325             scene()->removeItem(m_popupWidget);
326 
327         m_popupWidget = nullptr;
328     }
329 }
330 
331 //////////////////////////////////////////////////////////////////////////
332 
sceneWidth() const333 qreal EasyGraphicsView::sceneWidth() const
334 {
335     return m_sceneWidth;
336 }
337 
chronoTime() const338 qreal EasyGraphicsView::chronoTime() const
339 {
340     return m_chronometerItem->width();
341 }
342 
chronoTimeAux() const343 qreal EasyGraphicsView::chronoTimeAux() const
344 {
345     return m_chronometerItemAux->width();
346 }
347 
348 //////////////////////////////////////////////////////////////////////////
349 
createChronometer(bool _main)350 EasyChronometerItem* EasyGraphicsView::createChronometer(bool _main)
351 {
352     auto chronoItem = new EasyChronometerItem(_main);
353     chronoItem->setColor(_main ? ::profiler_gui::CHRONOMETER_COLOR : ::profiler_gui::CHRONOMETER_COLOR2);
354     chronoItem->setBoundingRect(sceneRect());
355     chronoItem->hide();
356     scene()->addItem(chronoItem);
357 
358     return chronoItem;
359 }
360 
361 //////////////////////////////////////////////////////////////////////////
362 
clear()363 void EasyGraphicsView::clear()
364 {
365     const QSignalBlocker blocker(this), sceneBlocker(scene()); // block all scene signals (otherwise clear() would be extremely slow!)
366 
367     // Stop flicking
368     m_flickerTimer.stop();
369     m_flickerSpeedX = 0;
370     m_flickerSpeedY = 0;
371     m_flickerCounterX = 0;
372     m_flickerCounterY = 0;
373 
374     // Clear all items
375     removePopup();
376     scene()->clear();
377     m_items.clear();
378     m_selectedBlocks.clear();
379 
380     m_beginTime = ::std::numeric_limits<decltype(m_beginTime)>::max(); // reset begin time
381     m_scale = 1; // scale back to initial 100% scale
382     m_timelineStep = 1;
383     m_offset = 0; // scroll back to the beginning of the scene
384 
385     m_idleTimer.stop();
386     m_idleTime = 0;
387 
388     // Reset necessary flags
389     m_bEmpty = true;
390 
391     m_sceneWidth = 10;
392     setSceneRect(0, 0, 10, 10);
393 
394     // notify ProfTreeWidget that selection was reset
395     emit intervalChanged(m_selectedBlocks, m_beginTime, 0, 0, false);
396 }
397 
setTree(const::profiler::thread_blocks_tree_t & _blocksTree)398 void EasyGraphicsView::setTree(const ::profiler::thread_blocks_tree_t& _blocksTree)
399 {
400     // clear scene
401     clear();
402 
403     if (_blocksTree.empty())
404     {
405         return;
406     }
407 
408     auto bgItem = new EasyBackgroundItem();
409     scene()->addItem(bgItem);
410 
411     // set new blocks tree
412     // calculate scene size and fill it with items
413 
414     // Calculating start and end time
415     ::profiler::timestamp_t finish = 0, busyTime = 0;
416     ::profiler::thread_id_t longestTree = 0, mainTree = 0;
417     for (const auto& threadTree : _blocksTree)
418     {
419         const auto& t = threadTree.second;
420 
421         auto timestart = m_beginTime;
422         auto timefinish = finish;
423 
424         if (!t.children.empty())
425             timestart = easyBlocksTree(t.children.front()).node->begin();
426         if (!t.sync.empty())
427             timestart = ::std::min(timestart, easyBlocksTree(t.sync.front()).node->begin());
428 
429         if (!t.children.empty())
430             timefinish = easyBlocksTree(t.children.back()).node->end();
431         if (!t.sync.empty())
432             timefinish = ::std::max(timefinish, easyBlocksTree(t.sync.back()).node->end());
433 
434         if (m_beginTime > timestart)
435             m_beginTime = timestart;
436 
437         if (finish < timefinish)
438             finish = timefinish;
439 
440         if (t.profiled_time > busyTime) {
441             busyTime = t.profiled_time;
442             longestTree = threadTree.first;
443         }
444 
445         if (mainTree == 0 && strcmp(t.name(), "Main") == 0)
446             mainTree = threadTree.first;
447     }
448 
449     const decltype(m_beginTime) additional_offset = (finish - m_beginTime) / 20; // Additional 5% before first block and after last block
450     finish += additional_offset;
451     m_beginTime -= ::std::min(m_beginTime, additional_offset);
452     EASY_GLOBALS.begin_time = m_beginTime;
453 
454     // Sort threads by name
455     ::std::vector<::std::reference_wrapper<const ::profiler::BlocksTreeRoot> > sorted_roots;
456     sorted_roots.reserve(_blocksTree.size());
457     for (const auto& threadTree : _blocksTree)
458         sorted_roots.push_back(threadTree.second);
459     ::std::sort(sorted_roots.begin(), sorted_roots.end(), [](const ::profiler::BlocksTreeRoot& _a, const ::profiler::BlocksTreeRoot& _b) {
460         return _a.thread_name < _b.thread_name;
461     });
462 
463     // Filling scene with items
464     m_items.reserve(_blocksTree.size());
465     qreal y = TIMELINE_ROW_SIZE;
466     const EasyGraphicsItem *longestItem = nullptr, *mainThreadItem = nullptr;
467     for (const ::profiler::BlocksTreeRoot& t : sorted_roots)
468     {
469         if (m_items.size() == 0xff)
470         {
471             qWarning() << "Warning: Maximum threads number (255 threads) exceeded! See EasyGraphicsView::setTree() : " << __LINE__ << " in file " << __FILE__;
472             break;
473         }
474 
475         // fill scene with new items
476         qreal h = 0, x = 0;
477 
478         if (!t.children.empty())
479             x = time2position(easyBlocksTree(t.children.front()).node->begin());
480         else if (!t.sync.empty())
481             x = time2position(easyBlocksTree(t.sync.front()).node->begin());
482 
483         auto item = new EasyGraphicsItem(static_cast<uint8_t>(m_items.size()), t);
484         if (t.depth)
485             item->setLevels(t.depth);
486         item->setPos(0, y);
487 
488         qreal children_duration = 0;
489 
490         if (!t.children.empty())
491         {
492             uint32_t dummy = 0;
493             children_duration = setTree(item, t.children, h, dummy, y, 0);
494         }
495         else
496         {
497             if (!t.sync.empty())
498                 children_duration = time2position(easyBlocksTree(t.sync.back()).node->end()) - x;
499             h = ::profiler_gui::GRAPHICS_ROW_SIZE;
500         }
501 
502         item->setBoundingRect(0, 0, children_duration + x, h);
503         m_items.push_back(item);
504         scene()->addItem(item);
505 
506         y += h + ::profiler_gui::THREADS_ROW_SPACING;
507 
508         if (longestTree == t.thread_id)
509             longestItem = item;
510 
511         if (mainTree == t.thread_id)
512             mainThreadItem = item;
513     }
514 
515     // Calculating scene rect
516     m_sceneWidth = time2position(finish);
517     setSceneRect(0, 0, m_sceneWidth, y + TIMELINE_ROW_SIZE);
518 
519     EASY_GLOBALS.scene_left  = 0;
520     EASY_GLOBALS.scene_right = m_sceneWidth;
521     emit EASY_GLOBALS.events.sceneSizeChanged();
522 
523     // Center view on the beginning of the scene
524     updateVisibleSceneRect();
525     setScrollbar(m_pScrollbar);
526 
527     // Create new chronometer item (previous item was destroyed by scene on scene()->clear()).
528     // It will be shown on mouse right button click.
529     m_chronometerItemAux = createChronometer(false);
530     m_chronometerItem = createChronometer(true);
531 
532     bgItem->setBoundingRect(0, 0, m_sceneWidth, y);
533     auto indicator = new EasyTimelineIndicatorItem();
534     indicator->setBoundingRect(0, 0, m_sceneWidth, y);
535     scene()->addItem(indicator);
536 
537     // Setting flags
538     m_bEmpty = false;
539 
540     scaleTo(BASE_SCALE);
541 
542 
543     emit treeChanged();
544 
545     if (mainThreadItem != nullptr)
546     {
547         longestItem = mainThreadItem;
548     }
549 
550     if (longestItem != nullptr)
551     {
552         EASY_GLOBALS.selected_thread = longestItem->threadId();
553         emit EASY_GLOBALS.events.selectedThreadChanged(longestItem->threadId());
554 
555         scrollTo(longestItem);
556         m_pScrollbar->setHistogramSource(longestItem->threadId(), longestItem->items(0));
557         if (!longestItem->items(0).empty())
558             m_pScrollbar->setValue(longestItem->items(0).front().left() - m_pScrollbar->sliderWidth() * 0.25);
559     }
560 
561     m_idleTimer.start(IDLE_TIMER_INTERVAL);
562 }
563 
getItems() const564 const EasyGraphicsView::Items &EasyGraphicsView::getItems() const
565 {
566     return m_items;
567 }
568 
setTree(EasyGraphicsItem * _item,const::profiler::BlocksTree::children_t & _children,qreal & _height,uint32_t & _maxDepthChild,qreal _y,short _level)569 qreal EasyGraphicsView::setTree(EasyGraphicsItem* _item, const ::profiler::BlocksTree::children_t& _children, qreal& _height, uint32_t& _maxDepthChild, qreal _y, short _level)
570 {
571     if (_children.empty())
572     {
573         return 0;
574     }
575 
576     const auto level = static_cast<uint8_t>(_level);
577     const auto n = static_cast<unsigned int>(_children.size());
578     _item->reserve(level, n);
579 
580     _maxDepthChild = 0;
581     uint8_t maxDepth = 0;
582     const short next_level = _level + 1;
583     bool warned = false;
584     qreal total_duration = 0, prev_end = 0, maxh = 0;
585     qreal start_time = -1;
586     uint32_t j = 0;
587     for (auto child_index : _children)
588     {
589         auto& gui_block = easyBlock(child_index);
590         const auto& child = gui_block.tree;
591         if (child.depth > maxDepth)
592         {
593             maxDepth = child.depth;
594             _maxDepthChild = j;
595         }
596 
597         auto xbegin = time2position(child.node->begin());
598         if (start_time < 0)
599         {
600             start_time = xbegin;
601         }
602 
603         auto duration = time2position(child.node->end()) - xbegin;
604 
605         //const auto dt = xbegin - prev_end;
606         //if (dt < 0)
607         //{
608         //    duration += dt;
609         //    xbegin -= dt;
610         //}
611 
612         //static const qreal MIN_DURATION = 0.25;
613         //if (duration < MIN_DURATION)
614         //    duration = MIN_DURATION;
615 
616         const auto i = _item->addItem(level);
617         auto& b = _item->getItem(level, i);
618 
619         gui_block.graphics_item = _item->index();
620         gui_block.graphics_item_level = level;
621         gui_block.graphics_item_index = i;
622 
623         if (next_level < 256 && next_level < _item->levels() && !child.children.empty())
624         {
625             b.children_begin = static_cast<unsigned int>(_item->items(static_cast<uint8_t>(next_level)).size());
626         }
627         else
628         {
629             ::profiler_gui::set_max(b.children_begin);
630         }
631 
632         qreal h = 0;
633         qreal children_duration = 0;
634         uint32_t maxDepthChild = 0;
635 
636         if (next_level < 256)
637         {
638             children_duration = setTree(_item, child.children, h, maxDepthChild, _y + ::profiler_gui::GRAPHICS_ROW_SIZE_FULL, next_level);
639         }
640         else if (!child.children.empty() && !warned)
641         {
642             warned = true;
643             qWarning() << "Warning: Maximum blocks depth (255) exceeded! See EasyGraphicsView::setTree() : " << __LINE__ << " in file " << __FILE__;
644         }
645 
646         if (duration < children_duration)
647         {
648             duration = children_duration;
649         }
650 
651         if (h > maxh)
652         {
653             maxh = h;
654         }
655 
656         b.block = child_index;// &child;
657 
658 #ifndef EASY_GRAPHICS_ITEM_RECURSIVE_PAINT
659         b.neighbours = n;
660         b.state = j > 0 || level == 0 ? 0 : -1;
661 #else
662         b.max_depth_child = maxDepthChild;
663 #endif
664 
665         b.setPos(xbegin, duration);
666         //b.totalHeight = ::profiler_gui::GRAPHICS_ROW_SIZE + h;
667 
668         prev_end = xbegin + duration;
669         total_duration = prev_end - start_time;
670 
671         ++j;
672     }
673 
674     _height += ::profiler_gui::GRAPHICS_ROW_SIZE_FULL + maxh;
675 
676     return total_duration;
677 }
678 
679 //////////////////////////////////////////////////////////////////////////
680 
setScrollbar(EasyGraphicsScrollbar * _scrollbar)681 void EasyGraphicsView::setScrollbar(EasyGraphicsScrollbar* _scrollbar)
682 {
683     auto const prevScrollbar = m_pScrollbar;
684     const bool makeConnect = prevScrollbar == nullptr || prevScrollbar != _scrollbar;
685 
686     if (prevScrollbar != nullptr && prevScrollbar != _scrollbar)
687     {
688         disconnect(prevScrollbar, &EasyGraphicsScrollbar::valueChanged, this, &This::onGraphicsScrollbarValueChange);
689         disconnect(prevScrollbar, &EasyGraphicsScrollbar::wheeled, this, &This::onGraphicsScrollbarWheel);
690     }
691 
692     m_pScrollbar = _scrollbar;
693     m_pScrollbar->clear();
694     m_pScrollbar->setRange(0, m_sceneWidth);
695 
696     auto vbar = verticalScrollBar();
697     const int vbar_width = (vbar != nullptr && vbar->isVisible() ? vbar->width() + 2 : 0);
698     m_pScrollbar->setSliderWidth(m_visibleSceneRect.width() + vbar_width);
699 
700     if (makeConnect)
701     {
702         connect(m_pScrollbar, &EasyGraphicsScrollbar::valueChanged, this, &This::onGraphicsScrollbarValueChange);
703         connect(m_pScrollbar, &EasyGraphicsScrollbar::wheeled, this, &This::onGraphicsScrollbarWheel);
704     }
705 
706     EASY_GLOBALS.selected_thread = 0;
707     emit EASY_GLOBALS.events.selectedThreadChanged(0);
708 }
709 
710 //////////////////////////////////////////////////////////////////////////
711 
updateVisibleSceneRect()712 int EasyGraphicsView::updateVisibleSceneRect()
713 {
714     m_visibleSceneRect = mapToScene(rect()).boundingRect();
715 
716     auto vbar = verticalScrollBar();
717     int vbar_width = 0;
718     if (vbar != nullptr && vbar->isVisible())
719         vbar_width = vbar->width() + 2;
720 
721     m_visibleSceneRect.setWidth(m_visibleSceneRect.width() - vbar_width);
722     m_visibleSceneRect.setHeight(m_visibleSceneRect.height() - TIMELINE_ROW_SIZE);
723 
724     return vbar_width;
725 }
726 
updateTimelineStep(qreal _windowWidth)727 void EasyGraphicsView::updateTimelineStep(qreal _windowWidth)
728 {
729     const auto time = units2microseconds(_windowWidth);
730     if (time < 100)
731         m_timelineStep = 1e-2;
732     else if (time < 10e3)
733         m_timelineStep = 1;
734     else if (time < 10e6)
735         m_timelineStep = 1e3;
736     else
737         m_timelineStep = 1e6;
738 
739     const auto optimal_steps = static_cast<int>(40 * m_visibleSceneRect.width() / 1500);
740     auto steps = time / m_timelineStep;
741     while (steps > optimal_steps) {
742         m_timelineStep *= 10;
743         steps *= 0.1;
744     }
745 
746     m_timelineStep = microseconds2units(m_timelineStep);
747 }
748 
repaintScene()749 void EasyGraphicsView::repaintScene()
750 {
751     scene()->update(m_visibleSceneRect);
752     emit sceneUpdated();
753 }
754 
755 //////////////////////////////////////////////////////////////////////////
756 
scaleTo(qreal _scale)757 void EasyGraphicsView::scaleTo(qreal _scale)
758 {
759     if (m_bEmpty)
760     {
761         return;
762     }
763 
764     // have to limit scale because of Qt's QPainter feature: it doesn't draw text
765     // with very big coordinates (but it draw rectangles with the same coordinates good).
766     m_scale = clamp(MIN_SCALE, _scale, MAX_SCALE);
767     const int vbar_width = updateVisibleSceneRect();
768 
769     // Update slider width for scrollbar
770     const auto windowWidth = (m_visibleSceneRect.width() + vbar_width) / m_scale;
771     m_pScrollbar->setSliderWidth(windowWidth);
772 
773     updateTimelineStep(windowWidth);
774     repaintScene();
775 }
776 
wheelEvent(QWheelEvent * _event)777 void EasyGraphicsView::wheelEvent(QWheelEvent* _event)
778 {
779     m_idleTime = 0;
780 
781     if (!m_bEmpty)
782         onWheel(mapToScene(_event->pos()).x(), _event->delta());
783     _event->accept();
784 }
785 
onGraphicsScrollbarWheel(qreal _mouseX,int _wheelDelta)786 void EasyGraphicsView::onGraphicsScrollbarWheel(qreal _mouseX, int _wheelDelta)
787 {
788     m_idleTime = 0;
789 
790     for (auto item : m_items)
791     {
792         if (item->threadId() == EASY_GLOBALS.selected_thread)
793         {
794             scrollTo(item);
795             break;
796         }
797     }
798 
799     onWheel(_mouseX, _wheelDelta);
800 }
801 
scrollTo(const EasyGraphicsItem * _item)802 void EasyGraphicsView::scrollTo(const EasyGraphicsItem* _item)
803 {
804     m_bUpdatingRect = true;
805     auto vbar = verticalScrollBar();
806     vbar->setValue(_item->y() + (_item->boundingRect().height() - vbar->pageStep()) * 0.5);
807     m_bUpdatingRect = false;
808 }
809 
onWheel(qreal _mouseX,int _wheelDelta)810 void EasyGraphicsView::onWheel(qreal _mouseX, int _wheelDelta)
811 {
812     const decltype(m_scale) scaleCoeff = _wheelDelta > 0 ? ::profiler_gui::SCALING_COEFFICIENT : ::profiler_gui::SCALING_COEFFICIENT_INV;
813 
814     // Remember current mouse position
815     _mouseX = clamp(0., _mouseX, m_sceneWidth);
816     const auto mousePosition = m_offset + _mouseX / m_scale;
817 
818     // have to limit scale because of Qt's QPainter feature: it doesn't draw text
819     // with very big coordinates (but it draw rectangles with the same coordinates good).
820     m_scale = clamp(MIN_SCALE, m_scale * scaleCoeff, MAX_SCALE);
821 
822     //updateVisibleSceneRect(); // Update scene rect
823 
824     // Update slider width for scrollbar
825     auto vbar = verticalScrollBar();
826     const int vbar_width = (vbar != nullptr && vbar->isVisible() ? vbar->width() + 2 : 0);
827     const auto windowWidth = (m_visibleSceneRect.width() + vbar_width) / m_scale;
828     m_pScrollbar->setSliderWidth(windowWidth);
829 
830     // Calculate new offset to simulate QGraphicsView::AnchorUnderMouse scaling behavior
831     m_offset = clamp(0., mousePosition - _mouseX / m_scale, m_sceneWidth - windowWidth);
832 
833     // Update slider position
834     m_bUpdatingRect = true; // To be sure that updateVisibleSceneRect will not be called by scrollbar change
835     m_pScrollbar->setValue(m_offset);
836     m_bUpdatingRect = false;
837 
838     updateVisibleSceneRect(); // Update scene rect
839     updateTimelineStep(windowWidth);
840     repaintScene(); // repaint scene
841 }
842 
843 //////////////////////////////////////////////////////////////////////////
844 
mousePressEvent(QMouseEvent * _event)845 void EasyGraphicsView::mousePressEvent(QMouseEvent* _event)
846 {
847     m_idleTime = 0;
848 
849     if (m_bEmpty)
850     {
851         _event->accept();
852         return;
853     }
854 
855     m_mouseButtons = _event->buttons();
856     m_mousePressPos = _event->pos();
857 
858     if (m_mouseButtons & Qt::LeftButton)
859     {
860         if (m_chronometerItemAux->isVisible() && (m_chronometerItemAux->hoverLeft() || m_chronometerItemAux->hoverRight()))
861         {
862             m_chronometerItemAux->setReverse(m_chronometerItemAux->hoverLeft());
863             m_bDoubleClick = true;
864         }
865         else if (m_chronometerItem->isVisible() && (m_chronometerItem->hoverLeft() || m_chronometerItem->hoverRight()))
866         {
867             m_chronometerItem->setReverse(m_chronometerItem->hoverLeft());
868             m_mouseButtons = Qt::RightButton;
869             return;
870         }
871     }
872 
873     if (m_mouseButtons & Qt::RightButton)
874     {
875         if (m_chronometerItem->isVisible() && (m_chronometerItem->hoverLeft() || m_chronometerItem->hoverRight()))
876         {
877             m_chronometerItem->setReverse(m_chronometerItem->hoverLeft());
878         }
879         else
880         {
881             const auto mouseX = m_offset + mapToScene(m_mousePressPos).x() / m_scale;
882             m_chronometerItem->setLeftRight(mouseX, mouseX);
883             m_chronometerItem->hide();
884             m_pScrollbar->hideChrono();
885         }
886     }
887 
888     _event->accept();
889 }
890 
mouseDoubleClickEvent(QMouseEvent * _event)891 void EasyGraphicsView::mouseDoubleClickEvent(QMouseEvent* _event)
892 {
893     m_idleTime = 0;
894 
895     if (m_bEmpty)
896     {
897         _event->accept();
898         return;
899     }
900 
901     m_mouseButtons = _event->buttons();
902     m_mousePressPos = _event->pos();
903     m_bDoubleClick = true;
904 
905     if (m_mouseButtons & Qt::LeftButton)
906     {
907         const auto mouseX = m_offset + mapToScene(m_mousePressPos).x() / m_scale;
908         m_chronometerItemAux->setLeftRight(mouseX, mouseX);
909         m_chronometerItemAux->hide();
910         emit sceneUpdated();
911     }
912 
913     _event->accept();
914 }
915 
916 //////////////////////////////////////////////////////////////////////////
917 
mouseReleaseEvent(QMouseEvent * _event)918 void EasyGraphicsView::mouseReleaseEvent(QMouseEvent* _event)
919 {
920     m_idleTime = 0;
921 
922     if (m_bEmpty)
923     {
924         _event->accept();
925         return;
926     }
927 
928     bool chronoHidden = false;
929     bool changedSelection = false, changedSelectedItem = false;
930     if (m_mouseButtons & Qt::RightButton)
931     {
932         if (m_chronometerItem->isVisible() && m_chronometerItem->width() < 1e-6)
933         {
934             m_chronometerItem->hide();
935             m_pScrollbar->hideChrono();
936         }
937 
938         if (!m_selectedBlocks.empty())
939         {
940             changedSelection = true;
941             m_selectedBlocks.clear();
942         }
943 
944         if (m_chronometerItem->isVisible())
945         {
946             //printf("INTERVAL: {%lf, %lf} ms\n", m_chronometerItem->left(), m_chronometerItem->right());
947 
948             for (auto item : m_items)
949             {
950                 if (!EASY_GLOBALS.only_current_thread_hierarchy || item->threadId() == EASY_GLOBALS.selected_thread)
951                     item->getBlocks(m_chronometerItem->left(), m_chronometerItem->right(), m_selectedBlocks);
952             }
953 
954             if (!m_selectedBlocks.empty())
955             {
956                 changedSelection = true;
957             }
958         }
959     }
960 
961     const ::profiler_gui::EasyBlock* selectedBlock = nullptr;
962     ::profiler::thread_id_t selectedBlockThread = 0;
963     const auto previouslySelectedBlock = EASY_GLOBALS.selected_block;
964     if (m_mouseButtons & Qt::LeftButton)
965     {
966         bool clicked = false;
967 
968         if (m_chronometerItemAux->isVisible() && m_chronometerItemAux->width() < 1e-6)
969         {
970             chronoHidden = true;
971             m_chronometerItemAux->hide();
972         }
973         else if (m_chronometerItem->isVisible() && m_chronometerItem->hoverIndicator())
974         {
975             // Jump to selected zone
976             clicked = true;
977             m_flickerSpeedX = m_flickerSpeedY = 0;
978             m_pScrollbar->setValue(m_chronometerItem->left() + m_chronometerItem->width() * 0.5 - m_pScrollbar->sliderHalfWidth());
979         }
980 
981         if (!clicked && m_mouseMovePath.manhattanLength() < 5)
982         {
983             // Handle Click
984 
985             //clicked = true;
986             auto mouseClickPos = mapToScene(m_mousePressPos);
987             if (mouseClickPos.x() >= 0)
988             {
989                 mouseClickPos.setX(m_offset + mouseClickPos.x() / m_scale);
990 
991                 // Try to select one of item blocks
992                 for (auto item : m_items)
993                 {
994                     ::profiler::block_index_t i = ~0U;
995                     auto block = item->intersect(mouseClickPos, i);
996                     if (block)
997                     {
998                         changedSelectedItem = true;
999                         selectedBlock = block;
1000                         selectedBlockThread = item->threadId();
1001                         EASY_GLOBALS.selected_block = i;
1002                         EASY_GLOBALS.selected_block_id = easyBlock(i).tree.node->id();
1003                         break;
1004                     }
1005                 }
1006 
1007                 if (!changedSelectedItem && !::profiler_gui::is_max(EASY_GLOBALS.selected_block))
1008                 {
1009                     changedSelectedItem = true;
1010                     ::profiler_gui::set_max(EASY_GLOBALS.selected_block);
1011                     ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id);
1012                 }
1013             }
1014         }
1015     }
1016 
1017     m_bDoubleClick = false;
1018     m_mouseButtons = _event->buttons();
1019     m_mouseMovePath = QPoint();
1020     _event->accept();
1021 
1022     if (changedSelection)
1023     {
1024         emit intervalChanged(m_selectedBlocks, m_beginTime, position2time(m_chronometerItem->left()), position2time(m_chronometerItem->right()), m_chronometerItem->reverse());
1025     }
1026 
1027     if (changedSelectedItem)
1028     {
1029         m_bUpdatingRect = true;
1030         if (selectedBlock != nullptr && previouslySelectedBlock == EASY_GLOBALS.selected_block && !selectedBlock->tree.children.empty())
1031         {
1032             EASY_GLOBALS.gui_blocks[previouslySelectedBlock].expanded = !EASY_GLOBALS.gui_blocks[previouslySelectedBlock].expanded;
1033             emit EASY_GLOBALS.events.itemsExpandStateChanged();
1034         }
1035         emit EASY_GLOBALS.events.selectedBlockChanged(EASY_GLOBALS.selected_block);
1036 
1037         if (EASY_GLOBALS.selecting_block_changes_thread && selectedBlock != nullptr && EASY_GLOBALS.selected_thread != selectedBlockThread)
1038         {
1039             EASY_GLOBALS.selected_thread = selectedBlockThread;
1040 
1041             m_pScrollbar->lock();
1042             emit EASY_GLOBALS.events.selectedThreadChanged(EASY_GLOBALS.selected_thread);
1043             m_pScrollbar->unlock();
1044         }
1045         m_bUpdatingRect = false;
1046 
1047         if (selectedBlock != nullptr && selectedBlockThread == EASY_GLOBALS.selected_thread)
1048             m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block_id);
1049         else
1050         {
1051             for (auto item : m_items)
1052             {
1053                 if (item->threadId() == EASY_GLOBALS.selected_thread)
1054                 {
1055                     m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, item->items(0));
1056                     break;
1057                 }
1058             }
1059         }
1060 
1061         repaintScene();
1062     }
1063     else if (chronoHidden)
1064     {
1065         emit sceneUpdated();
1066     }
1067 }
1068 
1069 //////////////////////////////////////////////////////////////////////////
1070 
moveChrono(EasyChronometerItem * _chronometerItem,qreal _mouseX)1071 bool EasyGraphicsView::moveChrono(EasyChronometerItem* _chronometerItem, qreal _mouseX)
1072 {
1073     if (_chronometerItem->reverse())
1074     {
1075         if (_mouseX > _chronometerItem->right())
1076         {
1077             _chronometerItem->setReverse(false);
1078             _chronometerItem->setLeftRight(_chronometerItem->right(), _mouseX);
1079 
1080             if (_chronometerItem->hoverLeft())
1081             {
1082                 _chronometerItem->setHoverLeft(false);
1083                 _chronometerItem->setHoverRight(true);
1084             }
1085         }
1086         else
1087         {
1088             _chronometerItem->setLeftRight(_mouseX, _chronometerItem->right());
1089         }
1090     }
1091     else
1092     {
1093         if (_mouseX < _chronometerItem->left())
1094         {
1095             _chronometerItem->setReverse(true);
1096             _chronometerItem->setLeftRight(_mouseX, _chronometerItem->left());
1097 
1098             if (_chronometerItem->hoverRight())
1099             {
1100                 _chronometerItem->setHoverLeft(true);
1101                 _chronometerItem->setHoverRight(false);
1102             }
1103         }
1104         else
1105         {
1106             _chronometerItem->setLeftRight(_chronometerItem->left(), _mouseX);
1107         }
1108     }
1109 
1110     if (!_chronometerItem->isVisible() && _chronometerItem->width() > 1e-6)
1111     {
1112         _chronometerItem->show();
1113         return true;
1114     }
1115 
1116     return false;
1117 }
1118 
mouseMoveEvent(QMouseEvent * _event)1119 void EasyGraphicsView::mouseMoveEvent(QMouseEvent* _event)
1120 {
1121     m_idleTime = 0;
1122 
1123     if (m_bEmpty || (m_mouseButtons == 0 && !m_chronometerItem->isVisible() && !m_chronometerItemAux->isVisible()))
1124     {
1125         _event->accept();
1126         return;
1127     }
1128 
1129     bool needUpdate = false;
1130     const auto pos = _event->pos();
1131     const auto delta = pos - m_mousePressPos;
1132     m_mousePressPos = pos;
1133 
1134     if (m_mouseButtons != 0)
1135     {
1136         m_mouseMovePath.setX(m_mouseMovePath.x() + qAbs(delta.x()));
1137         m_mouseMovePath.setY(m_mouseMovePath.y() + qAbs(delta.y()));
1138     }
1139 
1140     auto mouseScenePos = mapToScene(m_mousePressPos);
1141     mouseScenePos.setX(m_offset + mouseScenePos.x() / m_scale);
1142     const auto x = clamp(0., mouseScenePos.x(), m_sceneWidth);
1143 
1144     if (m_mouseButtons & Qt::RightButton)
1145     {
1146         bool showItem = moveChrono(m_chronometerItem, x);
1147         m_pScrollbar->setChronoPos(m_chronometerItem->left(), m_chronometerItem->right());
1148 
1149         if (showItem)
1150         {
1151             m_pScrollbar->showChrono();
1152         }
1153 
1154         needUpdate = true;
1155     }
1156 
1157     if (m_mouseButtons & Qt::LeftButton)
1158     {
1159         if (m_bDoubleClick)
1160         {
1161             moveChrono(m_chronometerItemAux, x);
1162         }
1163         else
1164         {
1165             auto vbar = verticalScrollBar();
1166 
1167             m_bUpdatingRect = true; // Block scrollbars from updating scene rect to make it possible to do it only once
1168             vbar->setValue(vbar->value() - delta.y());
1169             m_pScrollbar->setValue(m_pScrollbar->value() - delta.x() / m_scale);
1170             m_bUpdatingRect = false;
1171             // Seems like an ugly stub, but QSignalBlocker is also a bad decision
1172             // because if scrollbar does not emit valueChanged signal then viewport does not move
1173 
1174             updateVisibleSceneRect(); // Update scene visible rect only once
1175 
1176             // Update flicker speed
1177             m_flickerSpeedX += delta.x() >> 1;
1178             m_flickerSpeedY += delta.y();
1179             if (!m_flickerTimer.isActive())
1180             {
1181                 // If flicker timer is not started, then start it
1182                 m_flickerTimer.start(FLICKER_INTERVAL);
1183             }
1184         }
1185 
1186         needUpdate = true;
1187     }
1188 
1189     if (m_mouseButtons == 0)
1190     {
1191         if (m_chronometerItem->isVisible())
1192         {
1193             auto prevValue = m_chronometerItem->hoverIndicator();
1194             m_chronometerItem->setHoverIndicator(m_chronometerItem->indicatorContains(mouseScenePos));
1195             needUpdate = needUpdate || (prevValue != m_chronometerItem->hoverIndicator());
1196 
1197             prevValue = m_chronometerItem->hoverLeft();
1198             m_chronometerItem->setHoverLeft(m_chronometerItem->hoverLeft(mouseScenePos.x()));
1199             needUpdate = needUpdate || (prevValue != m_chronometerItem->hoverLeft());
1200 
1201             if (!m_chronometerItem->hoverLeft())
1202             {
1203                 prevValue = m_chronometerItem->hoverRight();
1204                 m_chronometerItem->setHoverRight(m_chronometerItem->hoverRight(mouseScenePos.x()));
1205                 needUpdate = needUpdate || (prevValue != m_chronometerItem->hoverRight());
1206             }
1207         }
1208 
1209         if (m_chronometerItemAux->isVisible())
1210         {
1211             auto prevValue = m_chronometerItemAux->hoverLeft();
1212             m_chronometerItemAux->setHoverLeft(m_chronometerItemAux->hoverLeft(mouseScenePos.x()));
1213             needUpdate = needUpdate || (prevValue != m_chronometerItemAux->hoverLeft());
1214 
1215             if (!m_chronometerItemAux->hoverLeft())
1216             {
1217                 prevValue = m_chronometerItemAux->hoverRight();
1218                 m_chronometerItemAux->setHoverRight(m_chronometerItemAux->hoverRight(mouseScenePos.x()));
1219                 needUpdate = needUpdate || (prevValue != m_chronometerItemAux->hoverRight());
1220             }
1221         }
1222     }
1223 
1224     if (needUpdate)
1225     {
1226         repaintScene(); // repaint scene
1227     }
1228 
1229     _event->accept();
1230 }
1231 
1232 //////////////////////////////////////////////////////////////////////////
1233 
keyPressEvent(QKeyEvent * _event)1234 void EasyGraphicsView::keyPressEvent(QKeyEvent* _event)
1235 {
1236     static const int KeyStep = 100;
1237 
1238     const int key = _event->key();
1239     m_idleTime = 0;
1240 
1241     switch (key)
1242     {
1243         case Qt::Key_Right:
1244         case Qt::Key_6:
1245         {
1246             m_pScrollbar->setValue(m_pScrollbar->value() + KeyStep / m_scale);
1247             break;
1248         }
1249 
1250         case Qt::Key_Left:
1251         case Qt::Key_4:
1252         {
1253             m_pScrollbar->setValue(m_pScrollbar->value() - KeyStep / m_scale);
1254             break;
1255         }
1256 
1257         case Qt::Key_Up:
1258         case Qt::Key_8:
1259         {
1260             auto vbar = verticalScrollBar();
1261             vbar->setValue(vbar->value() - KeyStep);
1262             break;
1263         }
1264 
1265         case Qt::Key_Down:
1266         case Qt::Key_2:
1267         {
1268             auto vbar = verticalScrollBar();
1269             vbar->setValue(vbar->value() + KeyStep);
1270             break;
1271         }
1272 
1273         case Qt::Key_Plus:
1274         case Qt::Key_Equal:
1275         {
1276             onWheel(mapToScene(mapFromGlobal(QCursor::pos())).x(), KeyStep);
1277             break;
1278         }
1279 
1280         case Qt::Key_Minus:
1281         {
1282             onWheel(mapToScene(mapFromGlobal(QCursor::pos())).x(), -KeyStep);
1283             break;
1284         }
1285     }
1286 
1287     //m_keys.insert(key);
1288     _event->accept();
1289 }
1290 
keyReleaseEvent(QKeyEvent * _event)1291 void EasyGraphicsView::keyReleaseEvent(QKeyEvent* _event)
1292 {
1293     //const int key = _event->key();
1294     m_idleTime = 0;
1295 
1296     //m_keys.erase(key);
1297     _event->accept();
1298 }
1299 
1300 //////////////////////////////////////////////////////////////////////////
1301 
resizeEvent(QResizeEvent * _event)1302 void EasyGraphicsView::resizeEvent(QResizeEvent* _event)
1303 {
1304     Parent::resizeEvent(_event);
1305 
1306     const QRectF previousRect = m_visibleSceneRect;
1307     const int vbar_width = updateVisibleSceneRect(); // Update scene visible rect only once
1308 
1309     // Update slider width for scrollbar
1310     const auto windowWidth = (m_visibleSceneRect.width() + vbar_width) / m_scale;
1311     m_pScrollbar->setSliderWidth(windowWidth);
1312 
1313     // Calculate new offset to save old screen center
1314     const auto deltaWidth = m_visibleSceneRect.width() - previousRect.width();
1315     m_offset = clamp(0., m_offset - deltaWidth * 0.5 / m_scale, m_sceneWidth - windowWidth);
1316 
1317     // Update slider position
1318     m_bUpdatingRect = true; // To be sure that updateVisibleSceneRect will not be called by scrollbar change
1319     m_pScrollbar->setValue(m_offset);
1320     m_bUpdatingRect = false;
1321 
1322     repaintScene(); // repaint scene
1323 }
1324 
1325 //////////////////////////////////////////////////////////////////////////
1326 
initMode()1327 void EasyGraphicsView::initMode()
1328 {
1329     // TODO: find mode with least number of bugs :)
1330     // There are always some display bugs...
1331 
1332     setCacheMode(QGraphicsView::CacheNone);
1333     setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
1334     setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
1335     setOptimizationFlag(QGraphicsView::DontSavePainterState, true);
1336 
1337     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1338     connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &This::onScrollbarValueChange);
1339     connect(&m_flickerTimer, &QTimer::timeout, this, &This::onFlickerTimeout);
1340     connect(&m_idleTimer, &QTimer::timeout, this, &This::onIdleTimeout);
1341 
1342     auto globalSignals = &EASY_GLOBALS.events;
1343     connect(globalSignals, &::profiler_gui::EasyGlobalSignals::hierarchyFlagChanged, this, &This::onHierarchyFlagChange);
1344     connect(globalSignals, &::profiler_gui::EasyGlobalSignals::selectedThreadChanged, this, &This::onSelectedThreadChange);
1345     connect(globalSignals, &::profiler_gui::EasyGlobalSignals::selectedBlockChanged, this, &This::onSelectedBlockChange);
1346     connect(globalSignals, &::profiler_gui::EasyGlobalSignals::itemsExpandStateChanged, this, &This::onRefreshRequired);
1347     connect(globalSignals, &::profiler_gui::EasyGlobalSignals::refreshRequired, this, &This::onRefreshRequired);
1348 
1349     connect(globalSignals, &::profiler_gui::EasyGlobalSignals::selectedBlockIdChanged, [this](::profiler::block_id_t)
1350     {
1351         if (::profiler_gui::is_max(EASY_GLOBALS.selected_block_id))
1352         {
1353             if (EASY_GLOBALS.selected_thread != 0)
1354             {
1355                 for (auto item : m_items)
1356                 {
1357                     if (item->threadId() == EASY_GLOBALS.selected_thread)
1358                     {
1359                         m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, item->items(0));
1360                         break;
1361                     }
1362                 }
1363             }
1364             else
1365             {
1366                 m_pScrollbar->setHistogramSource(0, nullptr);
1367             }
1368         }
1369         else
1370             m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block_id);
1371         onRefreshRequired();
1372     });
1373 
1374     connect(globalSignals, &::profiler_gui::EasyGlobalSignals::threadNameDecorationChanged, this, &This::onThreadViewChanged);
1375     connect(globalSignals, &::profiler_gui::EasyGlobalSignals::hexThreadIdChanged, this, &This::onThreadViewChanged);
1376 
1377     connect(globalSignals, &::profiler_gui::EasyGlobalSignals::blocksTreeModeChanged, [this]()
1378     {
1379         if (!m_selectedBlocks.empty())
1380             emit intervalChanged(m_selectedBlocks, m_beginTime, position2time(m_chronometerItem->left()), position2time(m_chronometerItem->right()), m_chronometerItem->reverse());
1381     });
1382 }
1383 
1384 //////////////////////////////////////////////////////////////////////////
1385 
onThreadViewChanged()1386 void EasyGraphicsView::onThreadViewChanged()
1387 {
1388     if (m_bEmpty)
1389         return;
1390 
1391     for (auto item : m_items)
1392         item->validateName();
1393 
1394     emit treeChanged();
1395 
1396     updateVisibleSceneRect();
1397     onHierarchyFlagChange(EASY_GLOBALS.only_current_thread_hierarchy);
1398 
1399     repaintScene();
1400 }
1401 
1402 //////////////////////////////////////////////////////////////////////////
1403 
onScrollbarValueChange(int)1404 void EasyGraphicsView::onScrollbarValueChange(int)
1405 {
1406     if (!m_bUpdatingRect && !m_bEmpty)
1407         updateVisibleSceneRect();
1408 }
1409 
onGraphicsScrollbarValueChange(qreal _value)1410 void EasyGraphicsView::onGraphicsScrollbarValueChange(qreal _value)
1411 {
1412     if (!m_bEmpty)
1413     {
1414         m_offset = _value;
1415         if (!m_bUpdatingRect)
1416         {
1417             updateVisibleSceneRect();
1418             repaintScene();
1419         }
1420     }
1421 }
1422 
1423 //////////////////////////////////////////////////////////////////////////
1424 
onFlickerTimeout()1425 void EasyGraphicsView::onFlickerTimeout()
1426 {
1427     ++m_flickerCounterX;
1428     ++m_flickerCounterY;
1429 
1430     if (m_mouseButtons & Qt::LeftButton)
1431     {
1432         // Fast slow-down and stop if mouse button is pressed, no flicking.
1433         m_flickerSpeedX >>= 1;
1434         m_flickerSpeedY >>= 1;
1435         if (m_flickerSpeedX == -1) m_flickerSpeedX = 0;
1436         if (m_flickerSpeedY == -1) m_flickerSpeedY = 0;
1437     }
1438     else
1439     {
1440         // Flick when mouse button is not pressed
1441 
1442         using estd::sign;
1443         using estd::absmin;
1444 
1445         auto vbar = verticalScrollBar();
1446 
1447         m_bUpdatingRect = true; // Block scrollbars from updating scene rect to make it possible to do it only once
1448         m_pScrollbar->setValue(m_pScrollbar->value() - m_flickerSpeedX / m_scale);
1449         vbar->setValue(vbar->value() - m_flickerSpeedY);
1450         m_bUpdatingRect = false;
1451         // Seems like an ugly stub, but QSignalBlocker is also a bad decision
1452         // because if scrollbar does not emit valueChanged signal then viewport does not move
1453 
1454         updateVisibleSceneRect(); // Update scene visible rect only once
1455         repaintScene(); // repaint scene
1456 
1457         const int dx = static_cast<int>(sign(m_flickerSpeedX) * m_flickerCounterX / FLICKER_FACTOR);
1458         const int dy = static_cast<int>(sign(m_flickerSpeedY) * m_flickerCounterY / FLICKER_FACTOR);
1459 
1460         if (abs(dx) > 0)
1461         {
1462             m_flickerSpeedX -= absmin(dx, m_flickerSpeedX);
1463             m_flickerCounterX = 0;
1464         }
1465 
1466         if (abs(dy) > 0)
1467         {
1468             m_flickerSpeedY -= absmin(dy, m_flickerSpeedY);
1469             m_flickerCounterY = 0;
1470         }
1471     }
1472 
1473     if (m_flickerSpeedX == 0 && m_flickerSpeedY == 0)
1474     {
1475         // Flicker stopped, no timer needed.
1476         m_flickerTimer.stop();
1477         m_flickerSpeedX = 0;
1478         m_flickerSpeedY = 0;
1479         m_flickerCounterX = 0;
1480         m_flickerCounterY = 0;
1481     }
1482 }
1483 
1484 //////////////////////////////////////////////////////////////////////////
1485 
onIdleTimeout()1486 void EasyGraphicsView::onIdleTimeout()
1487 {
1488     m_idleTime += IDLE_TIMER_INTERVAL;
1489 
1490     if (m_idleTime < IDLE_TIME)
1491     {
1492         removePopup(true);
1493         return;
1494     }
1495 
1496     if (m_popupWidget != nullptr)
1497         return;
1498 
1499     auto scenePos = mapToScene(mapFromGlobal(QCursor::pos()));
1500 
1501     if (scenePos.x() < m_visibleSceneRect.left() || scenePos.x() > m_visibleSceneRect.right())
1502         return;
1503 
1504     if (scenePos.y() < m_visibleSceneRect.top() || scenePos.y() > m_visibleSceneRect.bottom())
1505         return;
1506 
1507     decltype(scenePos) pos(m_offset + scenePos.x() / m_scale, scenePos.y());
1508 
1509     // Try to select one of context switches or items
1510     for (auto item : m_items)
1511     {
1512         auto cse = item->intersectEvent(pos);
1513         if (cse != nullptr)
1514         {
1515             const auto& itemBlock = cse->tree;
1516 
1517             auto widget = new QWidget(nullptr, Qt::FramelessWindowHint);
1518             if (widget == nullptr)
1519                 return;
1520 
1521             widget->setAttribute(Qt::WA_ShowWithoutActivating, true);
1522             widget->setFocusPolicy(Qt::NoFocus);
1523 
1524             auto lay = new QGridLayout(widget);
1525             if (lay == nullptr)
1526                 return;
1527 
1528             int row = 0;
1529             lay->addWidget(new EasyBoldLabel("Context switch event", widget), row, 0, 1, 3, Qt::AlignHCenter);
1530             ++row;
1531 
1532             lay->addWidget(new QLabel("Thread:", widget), row, 0, Qt::AlignRight);
1533 
1534             const char* process_name = "";
1535             ::profiler::thread_id_t tid = 0;
1536             if (EASY_GLOBALS.version < ::profiler_gui::V130)
1537             {
1538                 tid = cse->tree.node->id();
1539                 process_name = cse->tree.node->name();
1540             }
1541             else
1542             {
1543                 tid = cse->tree.cs->tid();
1544                 process_name = cse->tree.cs->name();
1545             }
1546 
1547             auto it = EASY_GLOBALS.profiler_blocks.find(tid);
1548 
1549             if (it != EASY_GLOBALS.profiler_blocks.end())
1550             {
1551                 if (EASY_GLOBALS.hex_thread_id)
1552                     lay->addWidget(new QLabel(QString("0x%1 %2").arg(tid, 0, 16).arg(it->second.name()), widget), row, 1, 1, 2, Qt::AlignLeft);
1553                 else
1554                     lay->addWidget(new QLabel(QString("%1 %2").arg(tid).arg(it->second.name()), widget), row, 1, 1, 2, Qt::AlignLeft);
1555             }
1556             else if (EASY_GLOBALS.hex_thread_id)
1557                 lay->addWidget(new QLabel(QString("0x%1").arg(tid, 0, 16), widget), row, 1, 1, 2, Qt::AlignLeft);
1558             else
1559                 lay->addWidget(new QLabel(QString::number(tid), widget), row, 1, 1, 2, Qt::AlignLeft);
1560             ++row;
1561 
1562             lay->addWidget(new QLabel("Process:", widget), row, 0, Qt::AlignRight);
1563             lay->addWidget(new QLabel(process_name, widget), row, 1, 1, 2, Qt::AlignLeft);
1564             ++row;
1565 
1566             const auto duration = itemBlock.node->duration();
1567             lay->addWidget(new QLabel("Duration:", widget), row, 0, Qt::AlignRight);
1568             lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, duration, 3), widget), row, 1, 1, 2, Qt::AlignLeft);
1569             ++row;
1570 
1571             if (itemBlock.per_thread_stats)
1572             {
1573                 lay->addWidget(new QLabel("Sum:", widget), row, 0, Qt::AlignRight);
1574                 lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, itemBlock.per_thread_stats->total_duration, 3), widget), row, 1, 1, 2, Qt::AlignLeft);
1575                 ++row;
1576 
1577                 lay->addWidget(new EasyBoldLabel("-------- Statistics --------", widget), row, 0, 1, 3, Qt::AlignHCenter);
1578                 lay->addWidget(new QLabel("per ", widget), row + 1, 0, Qt::AlignRight);
1579                 lay->addWidget(new QLabel("This %:", widget), row + 2, 0, Qt::AlignRight);
1580                 lay->addWidget(new QLabel("Sum %:", widget), row + 3, 0, Qt::AlignRight);
1581                 lay->addWidget(new QLabel("N Calls:", widget), row + 4, 0, Qt::AlignRight);
1582 
1583                 lay->addWidget(new QLabel("Thread", widget), row + 1, 1, Qt::AlignHCenter);
1584 
1585                 auto percent = ::profiler_gui::percentReal(duration, item->root()->profiled_time);
1586                 lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 2, 1, Qt::AlignHCenter);
1587 
1588                 lay->addWidget(new QLabel(QString::number(::profiler_gui::percent(itemBlock.per_thread_stats->total_duration, item->root()->profiled_time)), widget), row + 3, 1, Qt::AlignHCenter);
1589 
1590                 lay->addWidget(new QLabel(QString::number(itemBlock.per_thread_stats->calls_number), widget), row + 4, 1, Qt::AlignHCenter);
1591 
1592                 if (itemBlock.per_frame_stats && !::profiler_gui::is_max(itemBlock.per_frame_stats->parent_block))
1593                 {
1594                     int col = 2;
1595                     auto frame_duration = easyBlocksTree(itemBlock.per_frame_stats->parent_block).node->duration();
1596 
1597                     lay->addWidget(new QLabel("Frame", widget), row + 1, col, Qt::AlignHCenter);
1598 
1599                     percent = ::profiler_gui::percentReal(duration, frame_duration);
1600                     lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 2, col, Qt::AlignHCenter);
1601 
1602                     percent = ::profiler_gui::percentReal(itemBlock.per_frame_stats->total_duration, frame_duration);
1603                     lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 3, col, Qt::AlignHCenter);
1604 
1605                     lay->addWidget(new QLabel(QString::number(itemBlock.per_frame_stats->calls_number), widget), row + 4, col, Qt::AlignHCenter);
1606                 }
1607             }
1608 
1609             m_popupWidget = new QGraphicsProxyWidget();
1610             m_popupWidget->setWidget(widget);
1611 
1612             break;
1613         }
1614 
1615         ::profiler::block_index_t i = ~0U;
1616         auto block = item->intersect(pos, i);
1617         if (block != nullptr)
1618         {
1619             const auto& itemBlock = block->tree;
1620             const auto& itemDesc = easyDescriptor(itemBlock.node->id());
1621 
1622             auto widget = new QWidget(nullptr, Qt::FramelessWindowHint);
1623             if (widget == nullptr)
1624                 return;
1625 
1626             widget->setObjectName(QStringLiteral("DiagramPopup"));
1627             widget->setAttribute(Qt::WA_ShowWithoutActivating, true);
1628             widget->setFocusPolicy(Qt::NoFocus);
1629 
1630             auto lay = new QGridLayout(widget);
1631             if (lay == nullptr)
1632                 return;
1633 
1634             lay->setSpacing(2);
1635 
1636             int row = 0;
1637             switch (itemDesc.type())
1638             {
1639                 case ::profiler::BlockType::Block:
1640                 {
1641                     const auto name = *itemBlock.node->name() != 0 ? itemBlock.node->name() : itemDesc.name();
1642 
1643                     //lay->addWidget(new QLabel("Name:", widget), row, 0, Qt::AlignRight);
1644                     lay->addWidget(new EasyBoldLabel(::profiler_gui::toUnicode(name), widget), row, 0, 1, 5,
1645                                    Qt::AlignHCenter);
1646                     ++row;
1647 
1648                     const auto duration = itemBlock.node->duration();
1649                     lay->addWidget(new QLabel("Duration:", widget), row, 0, Qt::AlignRight);
1650                     lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, duration, 3),
1651                                               widget), row, 1, 1, 3, Qt::AlignLeft);
1652                     ++row;
1653 
1654                     ::profiler::timestamp_t children_duration = 0;
1655                     for (auto child : itemBlock.children)
1656                         children_duration += easyBlock(child).tree.node->duration();
1657 
1658                     const auto self_duration = duration - children_duration;
1659                     const auto self_percent =
1660                         duration == 0 ? 100. : ::profiler_gui::percentReal(self_duration, duration);
1661                     lay->addWidget(new QLabel("Self:", widget), row, 0, Qt::AlignRight);
1662                     lay->addWidget(new QLabel(QString("%1 (%2%)")
1663                                                   .arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units,
1664                                                                                     self_duration, 3))
1665                                                   .arg(QString::number(self_percent, 'g', 3)), widget),
1666                                    row, 1, 1, 3, Qt::AlignLeft);
1667                     ++row;
1668 
1669                     break;
1670                 }
1671 
1672                 case ::profiler::BlockType::Event:
1673                 {
1674                     const auto name = *itemBlock.node->name() != 0 ? itemBlock.node->name() : itemDesc.name();
1675 
1676                     lay->addWidget(new EasyBoldLabel("User defined event", widget), row, 0, 1, 2, Qt::AlignHCenter);
1677                     ++row;
1678 
1679                     lay->addWidget(new QLabel("Name:", widget), row, 0, Qt::AlignRight);
1680                     lay->addWidget(new QLabel(::profiler_gui::toUnicode(name), widget), row, 1, Qt::AlignLeft);
1681                     ++row;
1682 
1683                     break;
1684                 }
1685 
1686                 case ::profiler::BlockType::Value:
1687                 {
1688                     lay->addWidget(new EasyBoldLabel("Arbitrary Value", widget), row, 0, 1, 2, Qt::AlignHCenter);
1689                     ++row;
1690 
1691                     lay->addWidget(new QLabel("Name:", widget), row, 0, Qt::AlignRight);
1692                     lay->addWidget(new QLabel(::profiler_gui::toUnicode(itemDesc.name()), widget), row, 1, Qt::AlignLeft);
1693                     ++row;
1694 
1695                     lay->addWidget(new QLabel("Value:", widget), row, 0, Qt::AlignRight);
1696                     lay->addWidget(new QLabel(::profiler_gui::valueString(*itemBlock.value), widget), row, 1, Qt::AlignLeft);
1697                     ++row;
1698 
1699                     lay->addWidget(new QLabel("VIN:", widget), row, 0, Qt::AlignRight);
1700                     lay->addWidget(new QLabel(QString("0x%1").arg(itemBlock.value->value_id(), 0, 16), widget), row, 1, Qt::AlignLeft);
1701                     ++row;
1702 
1703                     break;
1704                 }
1705 
1706                 default:
1707                 {
1708                     delete widget;
1709                     return;
1710                 }
1711             }
1712 
1713             if (itemBlock.per_thread_stats != nullptr)
1714             {
1715                 if (itemDesc.type() == ::profiler::BlockType::Block)
1716                 {
1717                     const auto duration = itemBlock.node->duration();
1718 
1719                     lay->addWidget(new QLabel("Average:", widget), row, 0, Qt::AlignRight);
1720                     lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, itemBlock.per_thread_stats->average_duration(), 3), widget), row, 1, 1, 3, Qt::AlignLeft);
1721                     ++row;
1722 
1723                     // Calculate idle/active time
1724                     {
1725                         auto threadRoot = item->root();
1726 
1727                         ::profiler::block_index_t ind = 0;
1728                         auto it = ::std::lower_bound(threadRoot->sync.begin(), threadRoot->sync.end(), itemBlock.node->begin(), [](::profiler::block_index_t _cs_index, ::profiler::timestamp_t _val)
1729                         {
1730                             return EASY_GLOBALS.gui_blocks[_cs_index].tree.node->begin() < _val;
1731                         });
1732 
1733                         if (it != threadRoot->sync.end())
1734                         {
1735                             ind = static_cast<::profiler::block_index_t>(it - threadRoot->sync.begin());
1736                             if (ind > 0)
1737                                 --ind;
1738                         }
1739                         else
1740                         {
1741                             ind = static_cast<::profiler::block_index_t>(threadRoot->sync.size());
1742                         }
1743 
1744                         ::profiler::timestamp_t idleTime = 0;
1745                         for (auto ncs = static_cast<::profiler::block_index_t>(threadRoot->sync.size()); ind < ncs; ++ind)
1746                         {
1747                             auto cs_index = threadRoot->sync[ind];
1748                             const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node;
1749 
1750                             if (cs->begin() > itemBlock.node->end())
1751                                 break;
1752 
1753                             if (itemBlock.node->begin() <= cs->begin() && cs->end() <= itemBlock.node->end())
1754                                 idleTime += cs->duration();
1755                         }
1756 
1757                         const auto active_time = duration - idleTime;
1758                         const auto active_percent = duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, duration);
1759                         lay->addWidget(new QLabel("Active time:", widget), row, 0, Qt::AlignRight);
1760                         lay->addWidget(new QLabel(QString("%1 (%2%)").arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, active_time, 3)).arg(QString::number(active_percent, 'g', 3)), widget), row, 1, 1, 3, Qt::AlignLeft);
1761                         ++row;
1762                     }
1763 
1764                     lay->addWidget(new EasyBoldLabel("-------- Statistics --------", widget), row, 0, 1, 5, Qt::AlignHCenter);
1765                     lay->addWidget(new QLabel("per ", widget), row + 1, 0, Qt::AlignRight);
1766                     lay->addWidget(new QLabel("This %:", widget), row + 2, 0, Qt::AlignRight);
1767                     lay->addWidget(new QLabel("Sum %:", widget), row + 3, 0, Qt::AlignRight);
1768                     lay->addWidget(new QLabel("Sum self %:", widget), row + 4, 0, Qt::AlignRight);
1769                     lay->addWidget(new QLabel("N Calls:", widget), row + 5, 0, Qt::AlignRight);
1770 
1771                     lay->addWidget(new QLabel("Thread", widget), row + 1, 1, Qt::AlignHCenter);
1772 
1773                     auto percent = ::profiler_gui::percentReal(duration, item->root()->profiled_time);
1774                     lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 2, 1, Qt::AlignHCenter);
1775 
1776                     lay->addWidget(new QLabel(QString::number(::profiler_gui::percent(itemBlock.per_thread_stats->total_duration, item->root()->profiled_time)), widget), row + 3, 1, Qt::AlignHCenter);
1777 
1778                     lay->addWidget(new QLabel(QString::number(::profiler_gui::percent(itemBlock.per_thread_stats->total_duration - itemBlock.per_thread_stats->total_children_duration, item->root()->profiled_time)), widget), row + 4, 1, Qt::AlignHCenter);
1779 
1780                     lay->addWidget(new QLabel(QString::number(itemBlock.per_thread_stats->calls_number), widget), row + 5, 1, Qt::AlignHCenter);
1781 
1782                     int col = 1;
1783 
1784                     if (itemBlock.per_frame_stats->parent_block != i && !::profiler_gui::is_max(itemBlock.per_frame_stats->parent_block))
1785                     {
1786                         ++col;
1787                         auto frame_duration = easyBlocksTree(itemBlock.per_frame_stats->parent_block).node->duration();
1788 
1789                         lay->addWidget(new QLabel("Frame", widget), row + 1, col, Qt::AlignHCenter);
1790 
1791                         percent = ::profiler_gui::percentReal(duration, frame_duration);
1792                         lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 2, col, Qt::AlignHCenter);
1793 
1794                         percent = ::profiler_gui::percentReal(itemBlock.per_frame_stats->total_duration, frame_duration);
1795                         lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 3, col, Qt::AlignHCenter);
1796 
1797                         percent = ::profiler_gui::percentReal(itemBlock.per_frame_stats->total_duration - itemBlock.per_frame_stats->total_children_duration, frame_duration);
1798                         lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 4, col, Qt::AlignHCenter);
1799 
1800                         lay->addWidget(new QLabel(QString::number(itemBlock.per_frame_stats->calls_number), widget), row + 5, col, Qt::AlignHCenter);
1801                     }
1802 
1803                     if (!::profiler_gui::is_max(itemBlock.per_parent_stats->parent_block))// != item->threadId())
1804                     {
1805                         ++col;
1806                         auto parent_duration = easyBlocksTree(itemBlock.per_parent_stats->parent_block).node->duration();
1807 
1808                         lay->addWidget(new QLabel("Parent", widget), row + 1, col, Qt::AlignHCenter);
1809 
1810                         percent = ::profiler_gui::percentReal(duration, parent_duration);
1811                         lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 2, col, Qt::AlignHCenter);
1812 
1813                         percent = ::profiler_gui::percentReal(itemBlock.per_parent_stats->total_duration, parent_duration);
1814                         lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 3, col, Qt::AlignHCenter);
1815 
1816                         percent = ::profiler_gui::percentReal(itemBlock.per_parent_stats->total_duration - itemBlock.per_parent_stats->total_children_duration, parent_duration);
1817                         lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast<int>(0.5 + percent)), widget), row + 4, col, Qt::AlignHCenter);
1818 
1819                         lay->addWidget(new QLabel(QString::number(itemBlock.per_parent_stats->calls_number), widget), row + 5, col, Qt::AlignHCenter);
1820 
1821                         ++col;
1822                     }
1823                 }
1824                 else
1825                 {
1826                     lay->addWidget(new QLabel("N calls/Thread:", widget), row, 0, Qt::AlignRight);
1827                     lay->addWidget(new QLabel(QString::number(itemBlock.per_thread_stats->calls_number), widget), row, 1, Qt::AlignLeft);
1828                 }
1829             }
1830 
1831             m_popupWidget = new QGraphicsProxyWidget();
1832             m_popupWidget->setWidget(widget);
1833 
1834             break;
1835         }
1836     }
1837 
1838     if (m_popupWidget != nullptr)
1839     {
1840         auto effect = new QGraphicsDropShadowEffect();
1841         effect->setBlurRadius(5);
1842         effect->setOffset(3, 3);
1843         m_popupWidget->setGraphicsEffect(effect);
1844 
1845         scene()->addItem(m_popupWidget);
1846 
1847         auto br = m_popupWidget->boundingRect();
1848         if (scenePos.y() + br.height() > m_visibleSceneRect.bottom())
1849             scenePos.setY(::std::max(scenePos.y() - br.height(), m_visibleSceneRect.top()));
1850 
1851         if (scenePos.x() + br.width() > m_visibleSceneRect.right())
1852             scenePos.setX(::std::max(scenePos.x() - br.width(), m_visibleSceneRect.left()));
1853 
1854         m_popupWidget->setPos(scenePos);
1855         m_popupWidget->setOpacity(0.95);
1856     }
1857 }
1858 
1859 //////////////////////////////////////////////////////////////////////////
1860 
onHierarchyFlagChange(bool)1861 void EasyGraphicsView::onHierarchyFlagChange(bool)
1862 {
1863     bool changedSelection = false;
1864 
1865     if (!m_selectedBlocks.empty())
1866     {
1867         changedSelection = true;
1868         m_selectedBlocks.clear();
1869     }
1870 
1871     if (m_chronometerItem->isVisible())
1872     {
1873         for (auto item : m_items)
1874         {
1875             if (!EASY_GLOBALS.only_current_thread_hierarchy || item->threadId() == EASY_GLOBALS.selected_thread)
1876                 item->getBlocks(m_chronometerItem->left(), m_chronometerItem->right(), m_selectedBlocks);
1877         }
1878 
1879         if (!m_selectedBlocks.empty())
1880         {
1881             changedSelection = true;
1882         }
1883     }
1884 
1885     if (changedSelection)
1886     {
1887         emit intervalChanged(m_selectedBlocks, m_beginTime, position2time(m_chronometerItem->left()), position2time(m_chronometerItem->right()), m_chronometerItem->reverse());
1888     }
1889 }
1890 
onSelectedThreadChange(::profiler::thread_id_t _id)1891 void EasyGraphicsView::onSelectedThreadChange(::profiler::thread_id_t _id)
1892 {
1893     if (m_pScrollbar == nullptr || m_pScrollbar->hystThread() == _id)
1894     {
1895         return;
1896     }
1897 
1898     if (_id == 0)
1899     {
1900         m_pScrollbar->setHistogramSource(0, nullptr);
1901         return;
1902     }
1903 
1904     for (auto item : m_items)
1905     {
1906         if (item->threadId() == _id)
1907         {
1908             m_pScrollbar->setHistogramSource(_id, item->items(0));
1909 
1910             bool changedSelection = false;
1911             if (EASY_GLOBALS.only_current_thread_hierarchy)
1912             {
1913                 if (!m_selectedBlocks.empty())
1914                 {
1915                     changedSelection = true;
1916                     m_selectedBlocks.clear();
1917                 }
1918 
1919                 if (m_chronometerItem->isVisible())
1920                 {
1921                     item->getBlocks(m_chronometerItem->left(), m_chronometerItem->right(), m_selectedBlocks);
1922                     if (!m_selectedBlocks.empty())
1923                         changedSelection = true;
1924                 }
1925             }
1926 
1927             if (changedSelection)
1928             {
1929                 emit intervalChanged(m_selectedBlocks, m_beginTime, position2time(m_chronometerItem->left()), position2time(m_chronometerItem->right()), m_chronometerItem->reverse());
1930             }
1931 
1932             repaintScene();
1933             return;
1934         }
1935     }
1936 
1937     m_pScrollbar->setHistogramSource(0, nullptr);
1938     repaintScene();
1939 }
1940 
1941 //////////////////////////////////////////////////////////////////////////
1942 
onSelectedBlockChange(unsigned int _block_index)1943 void EasyGraphicsView::onSelectedBlockChange(unsigned int _block_index)
1944 {
1945     if (!m_bUpdatingRect)
1946     {
1947         if (_block_index < EASY_GLOBALS.gui_blocks.size())
1948         {
1949             // Scroll to item
1950 
1951             const auto& guiblock = EASY_GLOBALS.gui_blocks[_block_index];
1952             const auto thread_item = m_items[guiblock.graphics_item];
1953             const auto& item = thread_item->items(guiblock.graphics_item_level)[guiblock.graphics_item_index];
1954 
1955             m_flickerSpeedX = m_flickerSpeedY = 0;
1956 
1957             m_bUpdatingRect = true;
1958             verticalScrollBar()->setValue(static_cast<int>(thread_item->levelY(guiblock.graphics_item_level) - m_visibleSceneRect.height() * 0.5));
1959             m_pScrollbar->setValue(item.left() + item.width() * 0.5 - m_pScrollbar->sliderHalfWidth());
1960 
1961             if (EASY_GLOBALS.selecting_block_changes_thread && EASY_GLOBALS.selected_thread != thread_item->threadId())
1962             {
1963                 EASY_GLOBALS.selected_thread = thread_item->threadId();
1964 
1965                 m_pScrollbar->lock();
1966                 emit EASY_GLOBALS.events.selectedThreadChanged(EASY_GLOBALS.selected_thread);
1967                 m_pScrollbar->unlock();
1968             }
1969 
1970             m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, guiblock.tree.node->id());
1971 
1972             m_bUpdatingRect = false;
1973         }
1974         else if (EASY_GLOBALS.selected_thread != 0)
1975         {
1976             for (auto item : m_items)
1977             {
1978                 if (item->threadId() == EASY_GLOBALS.selected_thread)
1979                 {
1980                     m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, item->items(0));
1981                     break;
1982                 }
1983             }
1984         }
1985         else
1986         {
1987             m_pScrollbar->setHistogramSource(0, nullptr);
1988         }
1989 
1990         updateVisibleSceneRect();
1991         repaintScene();
1992     }
1993 }
1994 
1995 //////////////////////////////////////////////////////////////////////////
1996 
onRefreshRequired()1997 void EasyGraphicsView::onRefreshRequired()
1998 {
1999     if (!m_bUpdatingRect)
2000     {
2001         repaintScene();
2002     }
2003 }
2004 
2005 //////////////////////////////////////////////////////////////////////////
2006 
EasyGraphicsViewWidget(QWidget * _parent)2007 EasyGraphicsViewWidget::EasyGraphicsViewWidget(QWidget* _parent)
2008     : QWidget(_parent)
2009     , m_scrollbar(new EasyGraphicsScrollbar(this))
2010     , m_view(new EasyGraphicsView(this))
2011     , m_threadNamesWidget(new EasyThreadNamesWidget(m_view, m_scrollbar->height(), this))
2012 {
2013     initWidget();
2014 }
2015 
initWidget()2016 void EasyGraphicsViewWidget::initWidget()
2017 {
2018     auto lay = new QGridLayout(this);
2019     lay->setContentsMargins(0, 0, 0, 0);
2020     lay->setSpacing(1);
2021     lay->addWidget(m_threadNamesWidget, 0, 0, 2, 1);
2022     lay->addWidget(m_view, 0, 1);
2023     lay->addWidget(m_scrollbar, 1, 1);
2024     setLayout(lay);
2025 
2026     m_view->setScrollbar(m_scrollbar);
2027 }
2028 
~EasyGraphicsViewWidget()2029 EasyGraphicsViewWidget::~EasyGraphicsViewWidget()
2030 {
2031 
2032 }
2033 
view()2034 EasyGraphicsView* EasyGraphicsViewWidget::view()
2035 {
2036     return m_view;
2037 }
2038 
clear()2039 void EasyGraphicsViewWidget::clear()
2040 {
2041     m_scrollbar->clear();
2042     m_threadNamesWidget->clear();
2043     m_view->clear();
2044 }
2045 
2046 //////////////////////////////////////////////////////////////////////////
2047 //////////////////////////////////////////////////////////////////////////
2048 
paint(QPainter * _painter,const QStyleOptionGraphicsItem *,QWidget *)2049 void EasyThreadNameItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*)
2050 {
2051     auto const parentView = static_cast<EasyThreadNamesWidget*>(scene()->parent());
2052     const auto view = parentView->view();
2053     const auto& items = view->getItems();
2054     if (items.empty())
2055         return;
2056 
2057     const auto visibleSceneRect = view->visibleSceneRect();
2058     const auto h = visibleSceneRect.height() + TIMELINE_ROW_SIZE - 2;
2059     const auto w = parentView->width();//parentView->sceneRect().width();
2060 
2061     EASY_STATIC_CONSTEXPR uint16_t OVERLAP = ::profiler_gui::THREADS_ROW_SPACING >> 1;
2062     static const QBrush brushes[2] = {QColor::fromRgb(BACKGROUND_1), QColor::fromRgb(BACKGROUND_2)};
2063     int i = -1;
2064 
2065     QRectF rect;
2066 
2067     _painter->resetTransform();
2068 
2069     // Draw thread names
2070     auto default_font = _painter->font();
2071     _painter->setFont(EASY_GLOBALS.bg_font);
2072     for (auto item : items)
2073     {
2074         ++i;
2075 
2076         auto br = item->boundingRect();
2077         auto top = item->y() + br.top() - visibleSceneRect.top() - OVERLAP;
2078         auto hgt = br.height() + ::profiler_gui::THREADS_ROW_SPACING;
2079         auto bottom = top + hgt;
2080 
2081         if (top > h || bottom < 0)
2082             continue;
2083 
2084         if (item->threadId() == EASY_GLOBALS.selected_thread)
2085             _painter->setBrush(QBrush(QColor::fromRgb(::profiler_gui::SELECTED_THREAD_BACKGROUND)));
2086         else
2087             _painter->setBrush(brushes[i & 1]);
2088 
2089         if (top < 0)
2090         {
2091             hgt += top;
2092             top = 0;
2093         }
2094 
2095         const auto dh = top + hgt - h;
2096         if (dh > 0)
2097             hgt -= dh;
2098 
2099         rect.setRect(0, top, w, hgt);
2100 
2101         _painter->setPen(::profiler_gui::SYSTEM_BORDER_COLOR);
2102         _painter->drawRect(rect);
2103 
2104         rect.translate(-5, 0);
2105         _painter->setPen(::profiler_gui::TEXT_COLOR);
2106         _painter->drawText(rect, Qt::AlignRight | Qt::AlignVCenter, item->threadName());
2107     }
2108 
2109     const auto rect_bottom = rect.bottom();
2110     if (rect_bottom < h)
2111     {
2112         ++i;
2113         rect.translate(5, rect.height());
2114         rect.setHeight(h - rect_bottom);
2115         _painter->setBrush(brushes[i & 1]);
2116         _painter->setPen(::profiler_gui::SYSTEM_BORDER_COLOR);
2117         _painter->drawRect(rect);
2118     }
2119 
2120     // Draw separator between thread names area and information area
2121     _painter->setPen(::profiler_gui::SYSTEM_BORDER_COLOR);
2122     _painter->drawLine(QLineF(0, h, w, h));
2123     _painter->drawLine(QLineF(0, h + 2, w, h + 2));
2124 
2125     // Draw information
2126     _painter->setFont(EASY_GLOBALS.chronometer_font);
2127     QFontMetricsF fm(EASY_GLOBALS.chronometer_font, parentView);
2128     const qreal th = fm.height(); // Calculate displayed text height
2129     const qreal time1 = view->chronoTime();
2130     const qreal time2 = view->chronoTimeAux();
2131 
2132     auto y = h + 2;
2133 
2134     auto drawTimeText = [&rect, &w, &y, &fm, &_painter](qreal time, qreal th, QRgb color)
2135     {
2136         if (time > 0)
2137         {
2138             const QString text = ::profiler_gui::autoTimeStringReal(time); // Displayed text
2139             rect.setRect(0, y, w, th);
2140 
2141             _painter->setPen(color);
2142             _painter->drawText(rect, Qt::AlignCenter, text);
2143 
2144             y += th;
2145         }
2146     };
2147 
2148     drawTimeText(time1, th, ::profiler_gui::CHRONOMETER_COLOR.rgb() & 0x00ffffff);
2149     drawTimeText(time2, th, ::profiler_gui::CHRONOMETER_COLOR2.rgb() & 0x00ffffff);
2150 }
2151 
2152 //////////////////////////////////////////////////////////////////////////
2153 
EasyThreadNamesWidget(EasyGraphicsView * _view,int _additionalHeight,QWidget * _parent)2154 EasyThreadNamesWidget::EasyThreadNamesWidget(EasyGraphicsView* _view, int _additionalHeight, QWidget* _parent)
2155     : Parent(_parent)
2156     , m_idleTime(0)
2157     , m_view(_view)
2158     , m_popupWidget(nullptr)
2159     , m_maxLength(100)
2160     , m_additionalHeight(_additionalHeight + 1)
2161 {
2162     setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored);
2163 
2164     setScene(new QGraphicsScene(this));
2165 
2166     setCacheMode(QGraphicsView::CacheNone);
2167     setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
2168     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2169     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2170     setFixedWidth(m_maxLength);
2171 
2172     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedThreadChanged, [this](::profiler::thread_id_t){ repaintScene(); });
2173     connect(m_view, &EasyGraphicsView::treeChanged, this, &This::onTreeChange);
2174     connect(m_view, &EasyGraphicsView::sceneUpdated, this, &This::repaintScene);
2175     connect(m_view->verticalScrollBar(), &QScrollBar::valueChanged, verticalScrollBar(), &QScrollBar::setValue);
2176     connect(m_view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &This::setVerticalScrollbarRange);
2177     connect(&m_idleTimer, &QTimer::timeout, this, &This::onIdleTimeout);
2178 }
2179 
~EasyThreadNamesWidget()2180 EasyThreadNamesWidget::~EasyThreadNamesWidget()
2181 {
2182 
2183 }
2184 
removePopup(bool _removeFromScene)2185 void EasyThreadNamesWidget::removePopup(bool _removeFromScene)
2186 {
2187     if (m_popupWidget != nullptr)
2188     {
2189         auto widget = m_popupWidget->widget();
2190         widget->setParent(nullptr);
2191         m_popupWidget->setWidget(nullptr);
2192         delete widget;
2193 
2194         if (_removeFromScene)
2195             scene()->removeItem(m_popupWidget);
2196 
2197         m_popupWidget = nullptr;
2198     }
2199 }
2200 
clear()2201 void EasyThreadNamesWidget::clear()
2202 {
2203     const QSignalBlocker b(this);
2204     removePopup();
2205     scene()->clear();
2206 
2207     m_maxLength = 100;
2208     setFixedWidth(m_maxLength);
2209 
2210     m_idleTimer.stop();
2211     m_idleTime = 0;
2212 }
2213 
setVerticalScrollbarRange(int _minValue,int _maxValue)2214 void EasyThreadNamesWidget::setVerticalScrollbarRange(int _minValue, int _maxValue)
2215 {
2216     verticalScrollBar()->setRange(_minValue, _maxValue + m_additionalHeight);
2217 }
2218 
onTreeChange()2219 void EasyThreadNamesWidget::onTreeChange()
2220 {
2221     const QSignalBlocker b(this);
2222     removePopup();
2223     scene()->clear();
2224 
2225     m_idleTimer.stop();
2226     m_idleTime = 0;
2227 
2228     QFontMetricsF fm(EASY_GLOBALS.bg_font, this);
2229     qreal maxLength = 100;
2230     const auto& graphicsItems = m_view->getItems();
2231     for (auto graphicsItem : graphicsItems)
2232         maxLength = ::std::max(maxLength, (10 + fm.width(graphicsItem->threadName())) * ::profiler_gui::FONT_METRICS_FACTOR);
2233 
2234     auto vbar = verticalScrollBar();
2235     auto viewBar = m_view->verticalScrollBar();
2236 
2237     setVerticalScrollbarRange(viewBar->minimum(), viewBar->maximum());
2238     vbar->setSingleStep(viewBar->singleStep());
2239     vbar->setPageStep(viewBar->pageStep());
2240 
2241     auto r = m_view->sceneRect();
2242     setSceneRect(0, r.top(), maxLength, r.height() + m_additionalHeight);
2243 
2244     auto item = new EasyThreadNameItem();
2245     item->setPos(0, 0);
2246     item->setBoundingRect(sceneRect());
2247     scene()->addItem(item);
2248 
2249     m_maxLength = static_cast<int>(maxLength);
2250     setFixedWidth(m_maxLength);
2251 
2252     m_idleTimer.start(IDLE_TIMER_INTERVAL);
2253 }
2254 
onIdleTimeout()2255 void EasyThreadNamesWidget::onIdleTimeout()
2256 {
2257     static const uint16_t OVERLAP = ::profiler_gui::THREADS_ROW_SPACING >> 1;
2258 
2259     m_idleTime += IDLE_TIMER_INTERVAL;
2260 
2261     if (m_idleTime < IDLE_TIME)
2262     {
2263         removePopup(true);
2264         return;
2265     }
2266 
2267     if (m_popupWidget != nullptr)
2268         return;
2269 
2270     auto visibleSceneRect = mapToScene(rect()).boundingRect();
2271     auto scenePos = mapToScene(mapFromGlobal(QCursor::pos()));
2272 
2273     if (scenePos.x() < visibleSceneRect.left() || scenePos.x() > visibleSceneRect.right())
2274     {
2275         if (m_idleTime > 3000)
2276             setFixedWidth(m_maxLength);
2277         return;
2278     }
2279 
2280     if (scenePos.y() < visibleSceneRect.top() || scenePos.y() > visibleSceneRect.bottom())
2281     {
2282         if (m_idleTime > 3000)
2283             setFixedWidth(m_maxLength);
2284         return;
2285     }
2286 
2287     auto const parentView = static_cast<EasyThreadNamesWidget*>(scene()->parent());
2288     const auto view = parentView->view();
2289 
2290     if (scenePos.y() > view->visibleSceneRect().bottom())
2291     {
2292         if (m_idleTime > 3000)
2293             setFixedWidth(m_maxLength);
2294         return;
2295     }
2296 
2297     const qreal y = scenePos.y() - visibleSceneRect.top();
2298 
2299     const auto& items = view->getItems();
2300     if (items.empty())
2301     {
2302         if (m_idleTime > 3000)
2303             setFixedWidth(m_maxLength);
2304         return;
2305     }
2306 
2307     EasyGraphicsItem* intersectingItem = nullptr;
2308     for (auto item : items)
2309     {
2310         auto br = item->boundingRect();
2311         auto top = item->y() + br.top() - visibleSceneRect.top() - OVERLAP;
2312         auto hgt = br.height() + ::profiler_gui::THREADS_ROW_SPACING;
2313         auto bottom = top + hgt;
2314 
2315         if (bottom < y || y < top)
2316             continue;
2317 
2318         intersectingItem = item;
2319 
2320         break;
2321     }
2322 
2323     if (intersectingItem != nullptr)
2324     {
2325         auto widget = new QWidget(nullptr, Qt::FramelessWindowHint);
2326         if (widget == nullptr)
2327             return;
2328 
2329         widget->setObjectName(QStringLiteral("ThreadsPopup"));
2330         widget->setAttribute(Qt::WA_ShowWithoutActivating, true);
2331         widget->setFocusPolicy(Qt::NoFocus);
2332 
2333         auto lay = new QGridLayout(widget);
2334         if (lay == nullptr)
2335             return;
2336 
2337         int row = 0;
2338 
2339         lay->setSpacing(2);
2340         lay->addWidget(new EasyBoldLabel(intersectingItem->threadName(), widget), row, 0, 1, 2, Qt::AlignHCenter);
2341         ++row;
2342 
2343         ::profiler::timestamp_t duration = 0;
2344         const auto& root = *intersectingItem->root();
2345         if (!root.children.empty())
2346             duration = easyBlock(root.children.back()).tree.node->end() - easyBlock(root.children.front()).tree.node->begin();
2347 
2348         lay->addWidget(new QLabel("Duration:", widget), row, 0, Qt::AlignRight);
2349         lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, duration, 3), widget), row, 1, Qt::AlignLeft);
2350         ++row;
2351 
2352         lay->addWidget(new QLabel("Profiled:", widget), row, 0, Qt::AlignRight);
2353         if (duration)
2354         {
2355             lay->addWidget(new QLabel(QString("%1 (%2%)").arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, root.profiled_time, 3))
2356                 .arg(QString::number(100. * (double)root.profiled_time / (double)duration, 'f', 2)), widget), row, 1, Qt::AlignLeft);
2357         }
2358         else
2359         {
2360             lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, root.profiled_time, 3), widget), row, 1, Qt::AlignLeft);
2361         }
2362         ++row;
2363 
2364         lay->addWidget(new QLabel("Wait:", widget), row, 0, Qt::AlignRight);
2365         if (duration)
2366         {
2367             lay->addWidget(new QLabel(QString("%1 (%2%)").arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, root.wait_time, 3))
2368                 .arg(QString::number(100. * (double)root.wait_time / (double)duration, 'f', 2)), widget), row, 1, Qt::AlignLeft);
2369         }
2370         else
2371         {
2372             lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, root.wait_time, 3), widget), row, 1, Qt::AlignLeft);
2373         }
2374         ++row;
2375 
2376         const auto eventsSize = root.events.size();
2377 
2378         lay->addWidget(new QLabel("Frames:", widget), row, 0, Qt::AlignRight);
2379         lay->addWidget(new QLabel(QString::number(root.frames_number), widget), row, 1, Qt::AlignLeft);
2380         ++row;
2381 
2382         lay->addWidget(new QLabel("Blocks:", widget), row, 0, Qt::AlignRight);
2383         lay->addWidget(new QLabel(QString::number(root.blocks_number - eventsSize), widget), row, 1, Qt::AlignLeft);
2384         ++row;
2385 
2386         lay->addWidget(new QLabel("Markers:", widget), row, 0, Qt::AlignRight);
2387         lay->addWidget(new QLabel(QString::number(eventsSize), widget), row, 1, Qt::AlignLeft);
2388         ++row;
2389 
2390         m_popupWidget = new QGraphicsProxyWidget();
2391         if (m_popupWidget != nullptr)
2392         {
2393             auto effect = new QGraphicsDropShadowEffect();
2394             effect->setBlurRadius(5);
2395             effect->setOffset(3, 3);
2396             m_popupWidget->setGraphicsEffect(effect);
2397 
2398             m_popupWidget->setWidget(widget);
2399             scene()->addItem(m_popupWidget);
2400 
2401             auto br = m_popupWidget->boundingRect();
2402 
2403             if (maximumWidth() < br.width())
2404             {
2405                 setFixedWidth(static_cast<int>(br.width()));
2406                 visibleSceneRect.setWidth(br.width());
2407             }
2408 
2409             if (scenePos.y() + br.height() > visibleSceneRect.bottom())
2410                 scenePos.setY(::std::max(scenePos.y() - br.height(), visibleSceneRect.top()));
2411 
2412             if (scenePos.x() + br.width() > visibleSceneRect.right())
2413                 scenePos.setX(::std::max(scenePos.x() - br.width(), visibleSceneRect.left()));
2414 
2415             m_popupWidget->setPos(scenePos);
2416             m_popupWidget->setOpacity(0.95);
2417         }
2418     }
2419 }
2420 
repaintScene()2421 void EasyThreadNamesWidget::repaintScene()
2422 {
2423     scene()->update();
2424 }
2425 
mousePressEvent(QMouseEvent * _event)2426 void EasyThreadNamesWidget::mousePressEvent(QMouseEvent* _event)
2427 {
2428     m_idleTime = 0;
2429 
2430     QMouseEvent e(_event->type(), _event->pos() - QPointF(sceneRect().width(), 0), _event->button(), _event->buttons() & ~Qt::RightButton, _event->modifiers());
2431     m_view->mousePressEvent(&e);
2432     _event->accept();
2433 }
2434 
mouseDoubleClickEvent(QMouseEvent * _event)2435 void EasyThreadNamesWidget::mouseDoubleClickEvent(QMouseEvent* _event)
2436 {
2437     static const auto OVERLAP = ::profiler_gui::THREADS_ROW_SPACING >> 1;
2438 
2439     m_idleTime = 0;
2440 
2441     auto y = mapToScene(_event->pos()).y();
2442     const auto& items = m_view->getItems();
2443     for (auto item : items)
2444     {
2445         auto br = item->boundingRect();
2446         auto top = item->y() + br.top() - OVERLAP;
2447         auto bottom = top + br.height() + OVERLAP;
2448 
2449         if (y < top || y > bottom)
2450             continue;
2451 
2452         const auto thread_id = item->threadId();
2453         if (thread_id != EASY_GLOBALS.selected_thread)
2454         {
2455             EASY_GLOBALS.selected_thread = thread_id;
2456             emit EASY_GLOBALS.events.selectedThreadChanged(thread_id);
2457         }
2458 
2459         break;
2460     }
2461 
2462     _event->accept();
2463 }
2464 
mouseReleaseEvent(QMouseEvent * _event)2465 void EasyThreadNamesWidget::mouseReleaseEvent(QMouseEvent* _event)
2466 {
2467     m_idleTime = 0;
2468 
2469     QMouseEvent e(_event->type(), _event->pos() - QPointF(sceneRect().width(), 0), _event->button(), _event->buttons() & ~Qt::RightButton, _event->modifiers());
2470     m_view->mouseReleaseEvent(&e);
2471     _event->accept();
2472 }
2473 
mouseMoveEvent(QMouseEvent * _event)2474 void EasyThreadNamesWidget::mouseMoveEvent(QMouseEvent* _event)
2475 {
2476     m_idleTime = 0;
2477 
2478     QMouseEvent e(_event->type(), _event->pos() - QPointF(sceneRect().width(), 0), _event->button(), _event->buttons() & ~Qt::RightButton, _event->modifiers());
2479     m_view->mouseMoveEvent(&e);
2480     _event->accept();
2481 }
2482 
keyPressEvent(QKeyEvent * _event)2483 void EasyThreadNamesWidget::keyPressEvent(QKeyEvent* _event)
2484 {
2485     m_idleTime = 0;
2486     m_view->keyPressEvent(_event);
2487 }
2488 
keyReleaseEvent(QKeyEvent * _event)2489 void EasyThreadNamesWidget::keyReleaseEvent(QKeyEvent* _event)
2490 {
2491     m_idleTime = 0;
2492     m_view->keyReleaseEvent(_event);
2493 }
2494 
wheelEvent(QWheelEvent * _event)2495 void EasyThreadNamesWidget::wheelEvent(QWheelEvent* _event)
2496 {
2497     m_idleTime = 0;
2498 
2499     auto vbar = m_view->verticalScrollBar();
2500     if (vbar != nullptr)
2501     {
2502         _event->accept();
2503         vbar->setValue(vbar->value() - _event->delta());
2504     }
2505 }
2506 
2507 //////////////////////////////////////////////////////////////////////////
2508 //////////////////////////////////////////////////////////////////////////
2509 
2510