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