/************************************************************************ * file name : blocks_graphics_view.cpp * ----------------- : * creation time : 2016/06/26 * author : Victor Zarubkin * email : v.s.zarubkin@gmail.com * ----------------- : * description : The file contains implementation of GraphicsScene and GraphicsView and * : it's auxiliary classes for displyaing easy_profiler blocks tree. * ----------------- : * change log : * 2016/06/26 Victor Zarubkin: Moved sources from graphics_view.h * : and renamed classes from My* to Prof*. * : * : * 2016/06/27 Victor Zarubkin: Added text shifting relatively to it's parent item. * : Disabled border lines painting because of vertical lines painting bug. * : Changed height of blocks. Variable thread-block height. * : * : * 2016/06/29 Victor Zarubkin: Highly optimized painting performance and memory consumption. * : * : * 2016/06/30 Victor Zarubkin: Replaced doubles with floats (in ProfBlockItem) for less memory consumption. * : * : * 2016/09/15 Victor Zarubkin: Moved sources of BlocksGraphicsItem and GraphicsRulerItem to separate files. * : * : * * ----------------- : * license : Lightweight profiler library for c++ * : Copyright(C) 2016-2018 Sergey Yagovtsev, Victor Zarubkin * : * : Licensed under either of * : * MIT license (LICENSE.MIT or http://opensource.org/licenses/MIT) * : * Apache License, Version 2.0, (LICENSE.APACHE or http://www.apache.org/licenses/LICENSE-2.0) * : at your option. * : * : The MIT License * : * : Permission is hereby granted, free of charge, to any person obtaining a copy * : of this software and associated documentation files (the "Software"), to deal * : in the Software without restriction, including without limitation the rights * : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * : of the Software, and to permit persons to whom the Software is furnished * : to do so, subject to the following conditions: * : * : The above copyright notice and this permission notice shall be included in all * : copies or substantial portions of the Software. * : * : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * : INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * : PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * : TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * : USE OR OTHER DEALINGS IN THE SOFTWARE. * : * : The Apache License, Version 2.0 (the "License") * : * : You may not use this file except in compliance with the License. * : You may obtain a copy of the License at * : * : http://www.apache.org/licenses/LICENSE-2.0 * : * : Unless required by applicable law or agreed to in writing, software * : distributed under the License is distributed on an "AS IS" BASIS, * : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * : See the License for the specific language governing permissions and * : limitations under the License. ************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "blocks_graphics_view.h" #include "easy_graphics_item.h" #include "easy_chronometer_item.h" #include "easy_graphics_scrollbar.h" #include "arbitrary_value_tooltip.h" #include "globals.h" ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// const qreal MIN_SCALE = pow(::profiler_gui::SCALING_COEFFICIENT_INV, 70); // Up to 1000 sec scale const qreal MAX_SCALE = pow(::profiler_gui::SCALING_COEFFICIENT, 45); // ~23000 --- Up to 10 ns scale const qreal BASE_SCALE = pow(::profiler_gui::SCALING_COEFFICIENT_INV, 25); // ~0.003 EASY_CONSTEXPR QRgb BACKGROUND_1 = 0xffe4e4ec; EASY_CONSTEXPR QRgb BACKGROUND_2 = ::profiler::colors::White; EASY_CONSTEXPR QRgb TIMELINE_BACKGROUND = 0x20000000 | (::profiler::colors::Grey800 & 0x00ffffff);// 0x20303030; EASY_CONSTEXPR QRgb TIMELINE_BORDER = 0xffa8a0a0; EASY_CONSTEXPR int IDLE_TIMER_INTERVAL = 200; // 5Hz EASY_CONSTEXPR uint64_t IDLE_TIME = 400; EASY_CONSTEXPR int FLICKER_INTERVAL = 10; // 100Hz EASY_CONSTEXPR qreal FLICKER_FACTOR = 16.0 / FLICKER_INTERVAL; #ifdef max #undef max #endif #ifdef min #undef min #endif using estd::clamp; ////////////////////////////////////////////////////////////////////////// BoldLabel::BoldLabel(const QString& _text, QWidget* _parent) : QLabel(_text, _parent) { auto f = font(); f.setBold(true); setFont(f); } BoldLabel::~BoldLabel() { } ////////////////////////////////////////////////////////////////////////// void BackgroundItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*) { auto const sceneView = static_cast(scene()->parent()); const auto visibleSceneRect = sceneView->visibleSceneRect(); const auto currentScale = sceneView->scale(); const auto offset = sceneView->offset(); const auto left = offset * currentScale; const auto h = visibleSceneRect.height(); const auto visibleBottom = h - 1; const auto borderColor = QColor::fromRgb(TIMELINE_BORDER); const auto textShiftY = h + EASY_GLOBALS.size.timeline_height - 5; QRectF rect; _painter->save(); _painter->setTransform(QTransform::fromTranslate(-x(), -y())); const auto& items = sceneView->getItems(); if (!items.empty()) { const auto overlap = EASY_GLOBALS.size.threads_row_spacing >> 1; static const QBrush brushes[2] = {QColor::fromRgb(BACKGROUND_1), QColor::fromRgb(BACKGROUND_2)}; int i = -1; // Draw background _painter->setPen(::profiler_gui::SYSTEM_BORDER_COLOR); for (auto item : items) { ++i; auto br = item->boundingRect(); auto top = item->y() + br.top() - visibleSceneRect.top(); auto bottom = top + br.height(); if (top > h || bottom < 0) continue; if (item->threadId() == EASY_GLOBALS.selected_thread) _painter->setBrush(QBrush(QColor::fromRgb(::profiler_gui::SELECTED_THREAD_BACKGROUND))); else _painter->setBrush(brushes[i & 1]); rect.setRect(0, top - overlap, visibleSceneRect.width(), br.height() + EASY_GLOBALS.size.threads_row_spacing); const auto dh = rect.bottom() - visibleBottom; if (dh > 0) rect.setHeight(rect.height() - dh); if (rect.top() < 0) rect.setTop(0); _painter->drawRect(rect); } } // Draw timeline scale marks ---------------- _painter->setBrush(QColor::fromRgba(TIMELINE_BACKGROUND)); const auto sceneStep = sceneView->timelineStep(); const auto factor = ::profiler_gui::timeFactor(sceneStep); const auto step = sceneStep * currentScale; auto first = static_cast(offset / sceneStep); const int odd = first & 1; const auto nsteps = (1 + odd) * 2 + static_cast(visibleSceneRect.width() / step); first -= odd; QPen pen(borderColor); pen.setWidth(2); _painter->setPen(pen); _painter->drawLine(QPointF(0, h), QPointF(visibleSceneRect.width(), h)); _painter->setPen(borderColor); QLineF marks[20]; qreal first_x = first * sceneStep; const auto textWidth = QFontMetricsF(_painter->font(), sceneView).width(QString::number(static_cast(0.5 + first_x * factor))) * ::profiler_gui::FONT_METRICS_FACTOR + 10; const int n = 1 + static_cast(textWidth / step); int next = first % n; if (next != 0) next = n - next; first_x *= currentScale; for (int i = 0; i < nsteps; ++i, --next) { auto current = first_x - left + step * i; if ((i & 1) == 0) { rect.setRect(current, 0, step, h); _painter->drawRect(rect); for (int j = 0; j < 20; ++j) { auto xmark = current + j * step * 0.1; marks[j].setLine(xmark, h, xmark, h + ((j % 5) ? 4 : 8)); } _painter->drawLines(marks, 20); } if (next <= 0) { next = n; _painter->setPen(::profiler_gui::TEXT_COLOR); _painter->drawText(QPointF(current + 1, textShiftY), QString::number(static_cast(0.5 + (current + left) * factor / currentScale))); _painter->setPen(borderColor); } // TEST // this is for testing (order of lines will be painted): //_painter->setPen(Qt::black); //_painter->drawText(QPointF(current + step * 0.4, h - 20), QString::number(i)); //_painter->setPen(Qt::gray); // TEST } // END Draw timeline scale marks ~~~~~~~~~~~~ _painter->restore(); } ////////////////////////////////////////////////////////////////////////// void TimelineIndicatorItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*) { const auto sceneView = static_cast(scene()->parent()); const auto visibleSceneRect = sceneView->visibleSceneRect(); const auto step = sceneView->timelineStep() * sceneView->scale(); const QString text = ::profiler_gui::autoTimeStringInt(units2microseconds(sceneView->timelineStep())); // Displayed text // Draw scale indicator _painter->save(); _painter->setTransform(QTransform::fromTranslate(-x(), -y())); //_painter->setCompositionMode(QPainter::CompositionMode_Difference); _painter->setBrush(Qt::NoBrush); QPen pen(Qt::black); pen.setWidth(3); _painter->setPen(pen); _painter->drawLine(QLineF(visibleSceneRect.width() - 9 - step, visibleSceneRect.height() - 10, visibleSceneRect.width() - 11, visibleSceneRect.height() - 10)); _painter->setPen(Qt::black); _painter->drawLine(QLineF(visibleSceneRect.width() - 10 - step, visibleSceneRect.height() - 6, visibleSceneRect.width() - 10 - step, visibleSceneRect.height() - 14)); _painter->drawLine(QLineF(visibleSceneRect.width() - 10, visibleSceneRect.height() - 6, visibleSceneRect.width() - 10, visibleSceneRect.height() - 14)); _painter->setPen(Qt::black); _painter->setFont(EASY_GLOBALS.font.background); _painter->drawText(QRectF(visibleSceneRect.width() - 10 - step, visibleSceneRect.height() - 63, step, 50), Qt::AlignRight | Qt::AlignBottom | Qt::TextDontClip, text); _painter->restore(); } ////////////////////////////////////////////////////////////////////////// BlocksGraphicsView::BlocksGraphicsView(QWidget* _parent) : Parent(_parent) , m_beginTime(::std::numeric_limits::max()) , m_sceneWidth(0) , m_scale(1) , m_offset(0) , m_visibleRegionWidth(0) , m_timelineStep(0) , m_idleTime(0) , m_mouseButtons(Qt::NoButton) , m_pScrollbar(nullptr) , m_selectionItem(nullptr) , m_rulerItem(nullptr) , m_popupWidget(nullptr) , m_flickerSpeedX(0) , m_flickerSpeedY(0) , m_flickerCounterX(0) , m_flickerCounterY(0) , m_bDoubleClick(false) , m_bUpdatingRect(false) , m_bEmpty(true) , m_isArbitraryValueTooltip(false) , m_bHovered(false) { initMode(); setScene(new QGraphicsScene(this)); updateVisibleSceneRect(); } BlocksGraphicsView::~BlocksGraphicsView() { removePopup(); } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::removePopup() { delete m_popupWidget; m_popupWidget = nullptr; m_isArbitraryValueTooltip = false; } bool BlocksGraphicsView::needToIgnoreMouseEvent() const { return m_isArbitraryValueTooltip && m_popupWidget->rect().contains(m_popupWidget->mapFromGlobal(QCursor::pos())); } ////////////////////////////////////////////////////////////////////////// qreal BlocksGraphicsView::sceneWidth() const { return m_sceneWidth; } qreal BlocksGraphicsView::chronoTime() const { return m_selectionItem->width(); } qreal BlocksGraphicsView::chronoTimeAux() const { return m_rulerItem->width(); } ////////////////////////////////////////////////////////////////////////// GraphicsRulerItem* BlocksGraphicsView::createChronometer(bool _main) { auto chronoItem = new GraphicsRulerItem(_main); chronoItem->setColor(_main ? ::profiler_gui::CHRONOMETER_COLOR : ::profiler_gui::CHRONOMETER_COLOR2); chronoItem->setBoundingRect(sceneRect()); chronoItem->hide(); scene()->addItem(chronoItem); return chronoItem; } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::clear() { const QSignalBlocker blocker(this), sceneBlocker(scene()); // block all scene signals (otherwise clear() would be extremely slow!) // Stop flicking m_flickerTimer.stop(); m_flickerSpeedX = 0; m_flickerSpeedY = 0; m_flickerCounterX = 0; m_flickerCounterY = 0; // Clear all items removePopup(); scene()->clear(); m_items.clear(); m_selectedBlocks.clear(); m_beginTime = ::std::numeric_limits::max(); // reset begin time m_scale = 1; // scale back to initial 100% scale m_timelineStep = 1; m_offset = 0; // scroll back to the beginning of the scene if (m_idleTimer.isActive()) m_idleTimer.stop(); m_idleTime = 0; // Reset necessary flags m_bEmpty = true; m_sceneWidth = 10; m_visibleRegionWidth = 10; setSceneRect(0, 0, 10, 10); auto& sceneData = EASY_GLOBALS.scene; sceneData.left = 0; sceneData.right = m_sceneWidth; sceneData.window = m_visibleRegionWidth; sceneData.offset = m_offset; sceneData.empty = true; // notify ProfTreeWidget that selection was reset emit intervalChanged(m_selectedBlocks, m_beginTime, 0, 0, false); EASY_GLOBALS.selected_thread = 0; emit EASY_GLOBALS.events.selectedThreadChanged(0); emit EASY_GLOBALS.events.rulerVisible(false); } void BlocksGraphicsView::notifySceneSizeChange() { EASY_GLOBALS.scene.left = 0; EASY_GLOBALS.scene.right = m_sceneWidth; emit EASY_GLOBALS.events.sceneSizeChanged(0, m_sceneWidth); } void BlocksGraphicsView::notifyVisibleRegionSizeChange() { auto vbar = verticalScrollBar(); const int vbar_width = (vbar != nullptr && vbar->isVisible() ? vbar->width() + 2 : 0); notifyVisibleRegionSizeChange((m_visibleSceneRect.width() + vbar_width) / m_scale); } void BlocksGraphicsView::notifyVisibleRegionSizeChange(qreal _size) { m_visibleRegionWidth = _size; EASY_GLOBALS.scene.window = _size; emit EASY_GLOBALS.events.sceneVisibleRegionSizeChanged(_size); } void BlocksGraphicsView::notifyVisibleRegionPosChange() { EASY_GLOBALS.scene.offset = m_offset; emit EASY_GLOBALS.events.sceneVisibleRegionPosChanged(m_offset); } void BlocksGraphicsView::notifyVisibleRegionPosChange(qreal _pos) { if (m_sceneWidth < m_visibleRegionWidth) m_offset = 0; else m_offset = estd::clamp(0., _pos, m_sceneWidth - m_visibleRegionWidth); notifyVisibleRegionPosChange(); } void BlocksGraphicsView::setTree(const ::profiler::thread_blocks_tree_t& _blocksTree) { // clear scene clear(); emit EASY_GLOBALS.events.sceneCleared(); if (_blocksTree.empty()) return; auto bgItem = new BackgroundItem(); scene()->addItem(bgItem); // set new blocks tree // calculate scene size and fill it with items // Calculating start and end time ::profiler::timestamp_t finish = 0, busyTime = 0; ::profiler::thread_id_t longestTree = 0, mainTree = 0; for (const auto& threadTree : _blocksTree) { const auto& t = threadTree.second; auto timestart = m_beginTime; auto timefinish = finish; if (!t.children.empty()) timestart = easyBlocksTree(t.children.front()).node->begin(); if (!t.sync.empty()) timestart = ::std::min(timestart, easyBlocksTree(t.sync.front()).node->begin()); if (!t.children.empty()) timefinish = easyBlocksTree(t.children.back()).node->end(); if (!t.sync.empty()) timefinish = ::std::max(timefinish, easyBlocksTree(t.sync.back()).node->end()); if (m_beginTime > timestart) m_beginTime = timestart; if (finish < timefinish) finish = timefinish; if (t.profiled_time > busyTime) { busyTime = t.profiled_time; longestTree = threadTree.first; } if (mainTree == 0 && strcmp(t.name(), "Main") == 0) mainTree = threadTree.first; } const decltype(m_beginTime) additional_offset = (finish - m_beginTime) / 20; // Additional 5% before first block and after last block finish += additional_offset; m_beginTime -= ::std::min(m_beginTime, additional_offset); EASY_GLOBALS.begin_time = m_beginTime; // Sort threads by name ::std::vector<::std::reference_wrapper > sorted_roots; sorted_roots.reserve(_blocksTree.size()); for (const auto& threadTree : _blocksTree) sorted_roots.push_back(threadTree.second); ::std::sort(sorted_roots.begin(), sorted_roots.end(), [](const ::profiler::BlocksTreeRoot& _a, const ::profiler::BlocksTreeRoot& _b) { return _a.thread_name < _b.thread_name; }); const auto row_height = EASY_GLOBALS.size.graphics_row_height; const auto threads_spacing = EASY_GLOBALS.size.threads_row_spacing; // Filling scene with items m_items.reserve(_blocksTree.size()); qreal y = EASY_GLOBALS.size.timeline_height; const BlocksGraphicsItem *longestItem = nullptr, *mainThreadItem = nullptr; for (const ::profiler::BlocksTreeRoot& t : sorted_roots) { if (m_items.size() == 0xff) { qWarning() << "Warning: Maximum threads number (255 threads) exceeded! See BlocksGraphicsView::setTree() : " << __LINE__ << " in file " << __FILE__; break; } // fill scene with new items qreal h = 0, x = 0; if (!t.children.empty()) x = time2position(easyBlocksTree(t.children.front()).node->begin()); else if (!t.sync.empty()) x = time2position(easyBlocksTree(t.sync.front()).node->begin()); auto item = new BlocksGraphicsItem(static_cast(m_items.size()), t); if (t.depth) item->setLevels(t.depth); item->setPos(0, y); qreal children_duration = 0; if (!t.children.empty()) { uint32_t dummy = 0; children_duration = setTree(item, t.children, h, dummy, y, 0); } else { if (!t.sync.empty()) children_duration = time2position(easyBlocksTree(t.sync.back()).node->end()) - x; h = EASY_GLOBALS.size.graphics_row_height; } item->setBoundingRect(0, 0, children_duration + x, h); m_items.push_back(item); scene()->addItem(item); y += h + EASY_GLOBALS.size.threads_row_spacing; if (longestTree == t.thread_id) longestItem = item; if (mainTree == t.thread_id) mainThreadItem = item; } // Calculating scene rect m_sceneWidth = time2position(finish); setSceneRect(0, 0, m_sceneWidth, y + EASY_GLOBALS.size.timeline_height); EASY_GLOBALS.scene.empty = false; // Center view on the beginning of the scene updateVisibleSceneRect(); //setScrollbar(m_pScrollbar); notifySceneSizeChange(); notifyVisibleRegionSizeChange(); // Create new chronometer item (previous item was destroyed by scene on scene()->clear()). // It will be shown on mouse right button click. m_rulerItem = createChronometer(false); m_selectionItem = createChronometer(true); bgItem->setBoundingRect(0, 0, m_sceneWidth, y); auto indicator = new TimelineIndicatorItem(); indicator->setBoundingRect(0, 0, m_sceneWidth, y); scene()->addItem(indicator); // Setting flags m_bEmpty = false; scaleTo(BASE_SCALE); emit treeChanged(); if (mainThreadItem != nullptr) longestItem = mainThreadItem; if (longestItem != nullptr) { EASY_GLOBALS.selected_thread = longestItem->threadId(); emit EASY_GLOBALS.events.selectedThreadChanged(longestItem->threadId()); scrollTo(longestItem); m_pScrollbar->setHistogramSource(longestItem->threadId(), longestItem->items(0)); if (!longestItem->items(0).empty()) notifyVisibleRegionPosChange(longestItem->items(0).front().left() - m_visibleRegionWidth * 0.25); } if (m_bHovered && !m_idleTimer.isActive()) m_idleTimer.start(); } const BlocksGraphicsView::Items &BlocksGraphicsView::getItems() const { return m_items; } bool BlocksGraphicsView::getSelectionRegionForSaving(profiler::timestamp_t& _beginTime, profiler::timestamp_t& _endTime) const { if (m_bEmpty) return false; if (!m_selectionItem->isVisible() && !m_rulerItem->isVisible()) return false; decltype(m_selectionItem) ruler = m_selectionItem->isVisible() ? m_selectionItem : m_rulerItem; _beginTime = m_beginTime + position2time(ruler->left()); _endTime = m_beginTime + position2time(ruler->right()); return true; } qreal BlocksGraphicsView::setTree(BlocksGraphicsItem* _item, const ::profiler::BlocksTree::children_t& _children, qreal& _height, uint32_t& _maxDepthChild, qreal _y, short _level) { if (_children.empty()) { return 0; } const auto level = static_cast(_level); const auto n = static_cast(_children.size()); _item->reserve(level, n); _maxDepthChild = 0; uint8_t maxDepth = 0; const short next_level = _level + 1; bool warned = false; qreal total_duration = 0, prev_end = 0, maxh = 0; qreal start_time = -1; uint32_t j = 0; for (auto child_index : _children) { auto& gui_block = easyBlock(child_index); const auto& child = gui_block.tree; if (child.depth > maxDepth) { maxDepth = child.depth; _maxDepthChild = j; } auto xbegin = time2position(child.node->begin()); if (start_time < 0) { start_time = xbegin; } auto duration = time2position(child.node->end()) - xbegin; //const auto dt = xbegin - prev_end; //if (dt < 0) //{ // duration += dt; // xbegin -= dt; //} //static const qreal MIN_DURATION = 0.25; //if (duration < MIN_DURATION) // duration = MIN_DURATION; const auto i = _item->addItem(level); auto& b = _item->getItem(level, i); gui_block.graphics_item = _item->index(); gui_block.graphics_item_level = level; gui_block.graphics_item_index = i; if (next_level < 256 && next_level < _item->levels() && !child.children.empty()) { b.children_begin = static_cast(_item->items(static_cast(next_level)).size()); } else { ::profiler_gui::set_max(b.children_begin); } qreal h = 0; qreal children_duration = 0; uint32_t maxDepthChild = 0; if (next_level < 256) { children_duration = setTree(_item, child.children, h, maxDepthChild, _y + EASY_GLOBALS.size.graphics_row_full, next_level); } else if (!child.children.empty() && !warned) { warned = true; qWarning() << "Warning: Maximum blocks depth (255) exceeded! See BlocksGraphicsView::setTree() : " << __LINE__ << " in file " << __FILE__; } if (duration < children_duration) { duration = children_duration; } if (h > maxh) { maxh = h; } b.block = child_index;// &child; #ifndef EASY_GRAPHICS_ITEM_RECURSIVE_PAINT b.neighbours = n; b.state = j > 0 || level == 0 ? 0 : -1; #else b.max_depth_child = maxDepthChild; #endif b.setPos(xbegin, duration); //b.totalHeight = ::profiler_gui::GRAPHICS_ROW_SIZE + h; prev_end = xbegin + duration; total_duration = prev_end - start_time; ++j; } _height += EASY_GLOBALS.size.graphics_row_full + maxh; return total_duration; } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::setScrollbar(BlocksGraphicsScrollbar* _scrollbar) { disconnect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::chartSliderChanged, this, &This::onGraphicsScrollbarValueChange); disconnect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::chartWheeled, this, &This::onGraphicsScrollbarWheel); m_pScrollbar = _scrollbar; m_pScrollbar->clear(); m_pScrollbar->setRange(0, m_sceneWidth); auto vbar = verticalScrollBar(); const int vbar_width = (vbar != nullptr && vbar->isVisible() ? vbar->width() + 2 : 0); m_pScrollbar->setSliderWidth(m_visibleSceneRect.width() + vbar_width); connect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::chartSliderChanged, this, &This::onGraphicsScrollbarValueChange); connect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::chartWheeled, this, &This::onGraphicsScrollbarWheel); EASY_GLOBALS.selected_thread = 0; emit EASY_GLOBALS.events.selectedThreadChanged(0); } ////////////////////////////////////////////////////////////////////////// int BlocksGraphicsView::updateVisibleSceneRect() { m_visibleSceneRect = mapToScene(rect()).boundingRect(); auto vbar = verticalScrollBar(); int vbar_width = 0; if (vbar != nullptr && vbar->isVisible()) vbar_width = vbar->width() + 2; m_visibleSceneRect.setWidth(m_visibleSceneRect.width() - vbar_width); m_visibleSceneRect.setHeight(m_visibleSceneRect.height() - EASY_GLOBALS.size.timeline_height); return vbar_width; } void BlocksGraphicsView::updateTimelineStep(qreal _windowWidth) { const auto time = units2microseconds(_windowWidth); if (time < 100) m_timelineStep = 1e-2; else if (time < 10e3) m_timelineStep = 1; else if (time < 10e6) m_timelineStep = 1e3; else m_timelineStep = 1e6; const auto optimal_steps = static_cast(40 * m_visibleSceneRect.width() / 1500); auto steps = time / m_timelineStep; while (steps > optimal_steps) { m_timelineStep *= 10; steps *= 0.1; } m_timelineStep = microseconds2units(m_timelineStep); } void BlocksGraphicsView::repaintScene() { scene()->update(m_visibleSceneRect); emit sceneUpdated(); } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::scaleTo(qreal _scale) { if (m_bEmpty) { return; } // have to limit scale because of Qt's QPainter feature: it doesn't draw text // with very big coordinates (but it draw rectangles with the same coordinates good). m_scale = clamp(MIN_SCALE, _scale, MAX_SCALE); const int vbar_width = updateVisibleSceneRect(); // Update slider width for scrollbar const auto windowWidth = (m_visibleSceneRect.width() + vbar_width) / m_scale; notifyVisibleRegionSizeChange(windowWidth); updateTimelineStep(windowWidth); repaintScene(); } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::enterEvent(QEvent* _event) { Parent::enterEvent(_event); m_bHovered = true; if (!m_bEmpty && !m_idleTimer.isActive()) m_idleTimer.start(); m_idleTime = 0; } void BlocksGraphicsView::leaveEvent(QEvent* _event) { Parent::leaveEvent(_event); m_bHovered = false; if (m_idleTimer.isActive()) m_idleTimer.stop(); m_idleTime = 0; } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::wheelEvent(QWheelEvent* _event) { if (needToIgnoreMouseEvent()) return; m_idleTime = 0; if (!m_bEmpty) onWheel(mapToDiagram(mapToScene(_event->pos()).x()), _event->delta()); _event->accept(); } void BlocksGraphicsView::onGraphicsScrollbarWheel(qreal _scenePos, int _wheelDelta) { m_idleTime = 0; for (auto item : m_items) { if (item->threadId() == EASY_GLOBALS.selected_thread) { scrollTo(item); break; } } onWheel(_scenePos, _wheelDelta); } void BlocksGraphicsView::scrollTo(const BlocksGraphicsItem* _item) { m_bUpdatingRect = true; auto vbar = verticalScrollBar(); vbar->setValue(static_cast(_item->y() + (_item->boundingRect().height() - vbar->pageStep()) * 0.5)); m_bUpdatingRect = false; } qreal BlocksGraphicsView::mapToDiagram(qreal x) const { return m_offset + x / m_scale; } void BlocksGraphicsView::onWheel(qreal _scenePos, int _wheelDelta) { const decltype(m_scale) scaleCoeff = _wheelDelta > 0 ? profiler_gui::SCALING_COEFFICIENT : profiler_gui::SCALING_COEFFICIENT_INV; // Remember current mouse position _scenePos = clamp(0., _scenePos, m_sceneWidth); const auto initialPosition = _scenePos; // have to limit scale because of Qt's QPainter feature: it doesn't draw text // with very big coordinates (but it draw rectangles with the same coordinates good). _scenePos -= m_offset; _scenePos *= m_scale; m_scale = clamp(MIN_SCALE, m_scale * scaleCoeff, MAX_SCALE); //updateVisibleSceneRect(); // Update scene rect // Update slider width for scrollbar notifyVisibleRegionSizeChange(); // Calculate new offset to simulate QGraphicsView::AnchorUnderMouse scaling behavior if (m_sceneWidth < m_visibleRegionWidth) m_offset = 0; else m_offset = clamp(0., initialPosition - _scenePos / m_scale, m_sceneWidth - m_visibleRegionWidth); // Update slider position profiler_gui::BoolFlagGuard guard(m_bUpdatingRect, true); // To be sure that updateVisibleSceneRect will not be called by scrollbar change notifyVisibleRegionPosChange(); guard.restore(); updateVisibleSceneRect(); // Update scene rect updateTimelineStep(m_visibleRegionWidth); repaintScene(); // repaint scene } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::mousePressEvent(QMouseEvent* _event) { if (needToIgnoreMouseEvent()) return; m_idleTime = 0; if (m_bEmpty) { _event->accept(); return; } m_mouseButtons = _event->buttons(); m_mousePressPos = _event->pos(); if (m_mouseButtons & Qt::LeftButton) { if (m_rulerItem->isVisible() && (m_rulerItem->hoverLeft() || m_rulerItem->hoverRight())) { m_rulerItem->setReverse(m_rulerItem->hoverLeft()); m_bDoubleClick = true; } else if (m_selectionItem->isVisible() && (m_selectionItem->hoverLeft() || m_selectionItem->hoverRight())) { m_selectionItem->setReverse(m_selectionItem->hoverLeft()); m_mouseButtons = Qt::RightButton; return; } } if (m_mouseButtons & Qt::RightButton) { if (m_selectionItem->isVisible() && (m_selectionItem->hoverLeft() || m_selectionItem->hoverRight())) { m_selectionItem->setReverse(m_selectionItem->hoverLeft()); } else { const auto mouseX = m_offset + mapToScene(m_mousePressPos).x() / m_scale; m_selectionItem->setLeftRight(mouseX, mouseX); m_selectionItem->hide(); m_pScrollbar->hideSelectionIndicator(); emit EASY_GLOBALS.events.rulerVisible(m_rulerItem->isVisible()); } } _event->accept(); } void BlocksGraphicsView::mouseDoubleClickEvent(QMouseEvent* _event) { if (needToIgnoreMouseEvent()) return; m_idleTime = 0; if (m_bEmpty) { _event->accept(); return; } m_mouseButtons = _event->buttons(); m_mousePressPos = _event->pos(); m_bDoubleClick = true; if (m_mouseButtons & Qt::LeftButton) { const auto mouseX = m_offset + mapToScene(m_mousePressPos).x() / m_scale; m_rulerItem->setLeftRight(mouseX, mouseX); m_rulerItem->hide(); emit sceneUpdated(); emit EASY_GLOBALS.events.rulerVisible(m_selectionItem->isVisible()); } _event->accept(); } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::mouseReleaseEvent(QMouseEvent* _event) { if (needToIgnoreMouseEvent()) return; if (m_bEmpty) { _event->accept(); return; } bool chronoHidden = false; bool changedSelection = false, changedSelectedItem = false; if (m_mouseButtons & Qt::RightButton) { if (m_selectionItem->isVisible() && m_selectionItem->width() < 1e-6) { m_selectionItem->hide(); m_pScrollbar->hideSelectionIndicator(); emit EASY_GLOBALS.events.rulerVisible(m_rulerItem->isVisible()); } if (!m_selectedBlocks.empty()) { changedSelection = true; m_selectedBlocks.clear(); } if (m_selectionItem->isVisible()) { //printf("INTERVAL: {%lf, %lf} ms\n", m_selectionItem->left(), m_selectionItem->right()); for (auto item : m_items) { if (!EASY_GLOBALS.only_current_thread_hierarchy || item->threadId() == EASY_GLOBALS.selected_thread) item->getBlocks(m_selectionItem->left(), m_selectionItem->right(), m_selectedBlocks); } if (!m_selectedBlocks.empty()) { changedSelection = true; } } } const ::profiler_gui::EasyBlock* selectedBlock = nullptr; ::profiler::thread_id_t selectedBlockThread = 0; const auto previouslySelectedBlock = EASY_GLOBALS.selected_block; if (m_mouseButtons & Qt::LeftButton) { bool clicked = false; if (m_rulerItem->isVisible() && m_rulerItem->width() < 1e-6) { chronoHidden = true; m_rulerItem->hide(); emit EASY_GLOBALS.events.rulerVisible(m_selectionItem->isVisible()); } else if (m_selectionItem->isVisible() && m_selectionItem->hoverIndicator()) { // Jump to selected zone clicked = true; m_flickerSpeedX = m_flickerSpeedY = 0; notifyVisibleRegionPosChange(m_selectionItem->left() + (m_selectionItem->width() - m_visibleRegionWidth) * 0.5); } if (!clicked && m_mouseMovePath.manhattanLength() < 5) { // Handle Click //clicked = true; auto mouseClickPos = mapToScene(m_mousePressPos); if (mouseClickPos.x() >= 0) { mouseClickPos.setX(m_offset + mouseClickPos.x() / m_scale); // Try to select one of item blocks for (auto item : m_items) { ::profiler::block_index_t i = ~0U; auto block = item->intersect(mouseClickPos, i); if (block != nullptr) { changedSelectedItem = true; selectedBlock = block; selectedBlockThread = item->threadId(); EASY_GLOBALS.selected_block = i; EASY_GLOBALS.selected_block_id = easyBlock(i).tree.node->id(); break; } } if (!changedSelectedItem && !::profiler_gui::is_max(EASY_GLOBALS.selected_block)) { changedSelectedItem = true; ::profiler_gui::set_max(EASY_GLOBALS.selected_block); ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id); } } } } const bool isDoubleClick = m_bDoubleClick; m_bDoubleClick = false; m_mouseButtons = _event->buttons(); m_mouseMovePath = QPoint(); _event->accept(); if (changedSelection) { emit intervalChanged(m_selectedBlocks, m_beginTime, position2time(m_selectionItem->left()), position2time(m_selectionItem->right()), m_selectionItem->reverse()); } if (changedSelectedItem) { profiler_gui::BoolFlagGuard guard(m_bUpdatingRect, true); emit EASY_GLOBALS.events.selectedBlockChanged(EASY_GLOBALS.selected_block); if (EASY_GLOBALS.selecting_block_changes_thread && selectedBlock != nullptr && EASY_GLOBALS.selected_thread != selectedBlockThread) { EASY_GLOBALS.selected_thread = selectedBlockThread; emit EASY_GLOBALS.events.lockCharts(); emit EASY_GLOBALS.events.selectedThreadChanged(EASY_GLOBALS.selected_thread); emit EASY_GLOBALS.events.unlockCharts(); } if (selectedBlock != nullptr && isDoubleClick) { if (!selectedBlock->tree.children.empty()) { auto& selected = EASY_GLOBALS.gui_blocks[EASY_GLOBALS.selected_block]; selected.expanded = !selected.expanded; emit EASY_GLOBALS.events.itemsExpandStateChanged(); } else if (easyDescriptor(selectedBlock->tree.node->id()).type() == profiler::BlockType::Value) { emit EASY_GLOBALS.events.selectValue(selectedBlockThread, EASY_GLOBALS.selected_block, *selectedBlock->tree.value); } } guard.restore(); if (selectedBlock != nullptr && selectedBlockThread == EASY_GLOBALS.selected_thread) m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block_id); else { for (auto item : m_items) { if (item->threadId() == EASY_GLOBALS.selected_thread) { m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, item->items(0)); break; } } } repaintScene(); } else if (chronoHidden) { emit sceneUpdated(); } } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::addSelectionToHierarchy() { if (!m_selectionItem->isVisible()) return; //printf("INTERVAL: {%lf, %lf} ms\n", m_selectionItem->left(), m_selectionItem->right()); bool changedSelection = false; if (!m_selectedBlocks.empty()) { changedSelection = true; m_selectedBlocks.clear(); } for (auto item : m_items) { if (!EASY_GLOBALS.only_current_thread_hierarchy || item->threadId() == EASY_GLOBALS.selected_thread) item->getBlocks(m_selectionItem->left(), m_selectionItem->right(), m_selectedBlocks); } if (!m_selectedBlocks.empty()) { changedSelection = true; } if (changedSelection) { emit intervalChanged(m_selectedBlocks, m_beginTime, position2time(m_selectionItem->left()), position2time(m_selectionItem->right()), m_selectionItem->reverse()); } } void BlocksGraphicsView::onZoomSelection() { if (m_selectionItem->width() < 1e-6) return; auto deltaScale = m_visibleRegionWidth / m_selectionItem->width(); if (fabs(deltaScale - 1) < 1e-6) { // Restore scale value multiple to SCALING_COEFFICIENT const int steps = static_cast(log(m_scale / MIN_SCALE) / log(profiler_gui::SCALING_COEFFICIENT)); const auto desiredScale = MIN_SCALE * pow(profiler_gui::SCALING_COEFFICIENT, steps); deltaScale = desiredScale / m_scale; } m_offset = m_selectionItem->left() + (m_selectionItem->width() - m_visibleRegionWidth / deltaScale) * 0.5; m_scale = clamp(MIN_SCALE, m_scale * deltaScale, MAX_SCALE); // Update slider width for scrollbar notifyVisibleRegionSizeChange(); // Update slider position profiler_gui::BoolFlagGuard guard(m_bUpdatingRect, true); // To be sure that updateVisibleSceneRect will not be called by scrollbar change notifyVisibleRegionPosChange(); guard.restore(); updateVisibleSceneRect(); // Update scene rect updateTimelineStep(m_visibleRegionWidth); repaintScene(); // repaint scene } void BlocksGraphicsView::onInspectCurrentView(bool _strict) { if (m_bEmpty) return; if (!m_selectionItem->isVisible()) { m_selectionItem->setReverse(_strict); m_selectionItem->setLeftRight(m_offset, m_offset + m_visibleRegionWidth); m_selectionItem->show(); m_pScrollbar->setSelectionPos(m_selectionItem->left(), m_selectionItem->right()); m_pScrollbar->showSelectionIndicator(); emit EASY_GLOBALS.events.rulerVisible(true); addSelectionToHierarchy(); } else { onZoomSelection(); } } ////////////////////////////////////////////////////////////////////////// bool BlocksGraphicsView::moveChrono(GraphicsRulerItem* _chronometerItem, qreal _mouseX) { if (_chronometerItem->reverse()) { if (_mouseX > _chronometerItem->right()) { _chronometerItem->setReverse(false); _chronometerItem->setLeftRight(_chronometerItem->right(), _mouseX); if (_chronometerItem->hoverLeft()) { _chronometerItem->setHoverLeft(false); _chronometerItem->setHoverRight(true); } } else { _chronometerItem->setLeftRight(_mouseX, _chronometerItem->right()); } } else { if (_mouseX < _chronometerItem->left()) { _chronometerItem->setReverse(true); _chronometerItem->setLeftRight(_mouseX, _chronometerItem->left()); if (_chronometerItem->hoverRight()) { _chronometerItem->setHoverLeft(true); _chronometerItem->setHoverRight(false); } } else { _chronometerItem->setLeftRight(_chronometerItem->left(), _mouseX); } } if (!_chronometerItem->isVisible() && _chronometerItem->width() > 1e-6) { _chronometerItem->show(); emit EASY_GLOBALS.events.rulerVisible(true); return true; } return false; } void BlocksGraphicsView::mouseMoveEvent(QMouseEvent* _event) { if (m_isArbitraryValueTooltip) { const int size = std::min(m_popupWidget->width(), m_popupWidget->height()) >> 1; const auto rect = m_popupWidget->rect().adjusted(-size, -size, size, size); if (rect.contains(m_popupWidget->mapFromGlobal(QCursor::pos()))) return; } m_idleTime = 0; if (m_bEmpty || (m_mouseButtons == 0 && !m_selectionItem->isVisible() && !m_rulerItem->isVisible())) { _event->accept(); return; } bool needUpdate = false; const auto pos = _event->pos(); const auto delta = pos - m_mousePressPos; m_mousePressPos = pos; if (m_mouseButtons != 0) { m_mouseMovePath.setX(m_mouseMovePath.x() + qAbs(delta.x())); m_mouseMovePath.setY(m_mouseMovePath.y() + qAbs(delta.y())); } auto mouseScenePos = mapToScene(m_mousePressPos); mouseScenePos.setX(m_offset + mouseScenePos.x() / m_scale); const auto x = clamp(0., mouseScenePos.x(), m_sceneWidth); if (m_mouseButtons & Qt::RightButton) { bool showItem = moveChrono(m_selectionItem, x); m_pScrollbar->setSelectionPos(m_selectionItem->left(), m_selectionItem->right()); if (showItem) { m_pScrollbar->showSelectionIndicator(); } needUpdate = true; } if (m_mouseButtons & Qt::LeftButton) { if (m_bDoubleClick) { moveChrono(m_rulerItem, x); } else { auto vbar = verticalScrollBar(); profiler_gui::BoolFlagGuard guard(m_bUpdatingRect, true); // Block scrollbars from updating scene rect to make it possible to do it only once vbar->setValue(vbar->value() - delta.y()); notifyVisibleRegionPosChange(m_offset - delta.x() / m_scale); guard.restore(); // Seems like an ugly stub, but QSignalBlocker is also a bad decision // because if scrollbar does not emit valueChanged signal then viewport does not move updateVisibleSceneRect(); // Update scene visible rect only once // Update flicker speed m_flickerSpeedX += delta.x() >> 1; m_flickerSpeedY += delta.y(); if (!m_flickerTimer.isActive()) { // If flicker timer is not started, then start it m_flickerTimer.start(FLICKER_INTERVAL); } } needUpdate = true; } if (m_mouseButtons == 0) { if (m_selectionItem->isVisible()) { auto prevValue = m_selectionItem->hoverIndicator(); m_selectionItem->setHoverIndicator(m_selectionItem->indicatorContains(mouseScenePos)); needUpdate = needUpdate || (prevValue != m_selectionItem->hoverIndicator()); prevValue = m_selectionItem->hoverLeft(); m_selectionItem->setHoverLeft(m_selectionItem->hoverLeft(mouseScenePos.x())); needUpdate = needUpdate || (prevValue != m_selectionItem->hoverLeft()); if (!m_selectionItem->hoverLeft()) { prevValue = m_selectionItem->hoverRight(); m_selectionItem->setHoverRight(m_selectionItem->hoverRight(mouseScenePos.x())); needUpdate = needUpdate || (prevValue != m_selectionItem->hoverRight()); } } if (m_rulerItem->isVisible()) { auto prevValue = m_rulerItem->hoverLeft(); m_rulerItem->setHoverLeft(m_rulerItem->hoverLeft(mouseScenePos.x())); needUpdate = needUpdate || (prevValue != m_rulerItem->hoverLeft()); if (!m_rulerItem->hoverLeft()) { prevValue = m_rulerItem->hoverRight(); m_rulerItem->setHoverRight(m_rulerItem->hoverRight(mouseScenePos.x())); needUpdate = needUpdate || (prevValue != m_rulerItem->hoverRight()); } } } if (needUpdate) { repaintScene(); // repaint scene } _event->accept(); } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::keyPressEvent(QKeyEvent* _event) { static const int KeyStep = 100; const int key = _event->key(); switch (key) { case Qt::Key_Right: case Qt::Key_6: { notifyVisibleRegionPosChange(m_offset + KeyStep / m_scale); break; } case Qt::Key_Left: case Qt::Key_4: { notifyVisibleRegionPosChange(m_offset - KeyStep / m_scale); break; } case Qt::Key_Up: case Qt::Key_8: { auto vbar = verticalScrollBar(); vbar->setValue(vbar->value() - KeyStep); break; } case Qt::Key_Down: case Qt::Key_2: { auto vbar = verticalScrollBar(); vbar->setValue(vbar->value() + KeyStep); break; } case Qt::Key_Plus: case Qt::Key_Equal: { onWheel(mapToDiagram(mapToScene(mapFromGlobal(QCursor::pos())).x()), KeyStep); break; } case Qt::Key_Minus: { onWheel(mapToDiagram(mapToScene(mapFromGlobal(QCursor::pos())).x()), -KeyStep); break; } } m_idleTime = 0; _event->accept(); } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::resizeEvent(QResizeEvent* _event) { Parent::resizeEvent(_event); const QRectF previousRect = m_visibleSceneRect; const int vbar_width = updateVisibleSceneRect(); // Update scene visible rect only once // Update slider width for scrollbar const auto windowWidth = (m_visibleSceneRect.width() + vbar_width) / m_scale; notifyVisibleRegionSizeChange(windowWidth); // Calculate new offset to save old screen center const auto deltaWidth = m_visibleSceneRect.width() - previousRect.width(); m_offset = clamp(0., m_offset - deltaWidth * 0.5 / m_scale, m_sceneWidth - windowWidth); // Update slider position profiler_gui::BoolFlagGuard guard(m_bUpdatingRect, true); // To be sure that updateVisibleSceneRect will not be called by scrollbar change notifyVisibleRegionPosChange(); guard.restore(); repaintScene(); // repaint scene } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::initMode() { // TODO: find mode with least number of bugs :) // There are always some display bugs... setCacheMode(QGraphicsView::CacheNone); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setViewportUpdateMode(QGraphicsView::FullViewportUpdate); setOptimizationFlag(QGraphicsView::DontSavePainterState, true); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &This::onScrollbarValueChange); connect(&m_flickerTimer, &QTimer::timeout, this, &This::onFlickerTimeout); connect(&m_idleTimer, &QTimer::timeout, this, &This::onIdleTimeout); m_idleTimer.setInterval(IDLE_TIMER_INTERVAL); using profiler_gui::GlobalSignals; auto globalSignals = &EASY_GLOBALS.events; connect(globalSignals, &GlobalSignals::hierarchyFlagChanged, this, &This::onHierarchyFlagChange); connect(globalSignals, &GlobalSignals::selectedThreadChanged, this, &This::onSelectedThreadChange); connect(globalSignals, &GlobalSignals::selectedBlockChanged, this, &This::onSelectedBlockChange); connect(globalSignals, &GlobalSignals::itemsExpandStateChanged, this, &This::onRefreshRequired); connect(globalSignals, &GlobalSignals::refreshRequired, this, &This::onRefreshRequired); connect(globalSignals, &GlobalSignals::allDataGoingToBeDeleted, this, &This::clear); connect(globalSignals, &GlobalSignals::fileOpened, [this] { setTree(EASY_GLOBALS.profiler_blocks); }); connect(globalSignals, &GlobalSignals::selectedBlockIdChanged, [this](::profiler::block_id_t) { if (profiler_gui::is_max(EASY_GLOBALS.selected_block_id)) { if (EASY_GLOBALS.selected_thread != 0) { for (auto item : m_items) { if (item->threadId() == EASY_GLOBALS.selected_thread) { m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, item->items(0)); break; } } } else { m_pScrollbar->setHistogramSource(0, nullptr); } } else m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block_id); onRefreshRequired(); }); connect(globalSignals, &::profiler_gui::GlobalSignals::threadNameDecorationChanged, this, &This::onThreadViewChanged); connect(globalSignals, &::profiler_gui::GlobalSignals::hexThreadIdChanged, this, &This::onThreadViewChanged); connect(globalSignals, &::profiler_gui::GlobalSignals::blocksTreeModeChanged, [this]() { if (!m_selectedBlocks.empty()) emit intervalChanged(m_selectedBlocks, m_beginTime, position2time(m_selectionItem->left()), position2time(m_selectionItem->right()), m_selectionItem->reverse()); }); connect(globalSignals, &profiler_gui::GlobalSignals::chartSliderChanged, this, &This::onGraphicsScrollbarValueChange); connect(globalSignals, &profiler_gui::GlobalSignals::chartWheeled, this, &This::onGraphicsScrollbarWheel); } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::onThreadViewChanged() { if (m_bEmpty) return; for (auto item : m_items) item->validateName(); updateVisibleSceneRect(); emit treeChanged(); onHierarchyFlagChange(EASY_GLOBALS.only_current_thread_hierarchy); repaintScene(); } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::onScrollbarValueChange(int) { if (!m_bUpdatingRect && !m_bEmpty) updateVisibleSceneRect(); } void BlocksGraphicsView::onGraphicsScrollbarValueChange(qreal _value) { if (!m_bEmpty) { m_offset = _value; if (!m_bUpdatingRect) { updateVisibleSceneRect(); repaintScene(); } } } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::onFlickerTimeout() { ++m_flickerCounterX; ++m_flickerCounterY; if (m_mouseButtons & Qt::LeftButton) { // Fast slow-down and stop if mouse button is pressed, no flicking. m_flickerSpeedX >>= 1; m_flickerSpeedY >>= 1; if (m_flickerSpeedX == -1) m_flickerSpeedX = 0; if (m_flickerSpeedY == -1) m_flickerSpeedY = 0; } else { // Flick when mouse button is not pressed using estd::sign; using estd::absmin; auto vbar = verticalScrollBar(); profiler_gui::BoolFlagGuard guard(m_bUpdatingRect, true); // Block scrollbars from updating scene rect to make it possible to do it only once notifyVisibleRegionPosChange(m_offset - m_flickerSpeedX / m_scale); vbar->setValue(vbar->value() - m_flickerSpeedY); guard.restore(); // Seems like an ugly stub, but QSignalBlocker is also a bad decision // because if scrollbar does not emit valueChanged signal then viewport does not move updateVisibleSceneRect(); // Update scene visible rect only once repaintScene(); // repaint scene const int dx = static_cast(sign(m_flickerSpeedX) * m_flickerCounterX / FLICKER_FACTOR); const int dy = static_cast(sign(m_flickerSpeedY) * m_flickerCounterY / FLICKER_FACTOR); if (abs(dx) > 0) { m_flickerSpeedX -= absmin(dx, m_flickerSpeedX); m_flickerCounterX = 0; } if (abs(dy) > 0) { m_flickerSpeedY -= absmin(dy, m_flickerSpeedY); m_flickerCounterY = 0; } } if (m_flickerSpeedX == 0 && m_flickerSpeedY == 0) { // Flicker stopped, no timer needed. m_flickerTimer.stop(); m_flickerSpeedX = 0; m_flickerSpeedY = 0; m_flickerCounterX = 0; m_flickerCounterY = 0; } } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::onIdleTimeout() { m_idleTime += IDLE_TIMER_INTERVAL; if (m_idleTime < IDLE_TIME) { removePopup(); return; } if (m_popupWidget != nullptr) return; m_isArbitraryValueTooltip = false; auto scenePos = mapToScene(mapFromGlobal(QCursor::pos())); if (scenePos.x() < m_visibleSceneRect.left() || scenePos.x() > m_visibleSceneRect.right()) return; if (scenePos.y() < m_visibleSceneRect.top() || scenePos.y() > m_visibleSceneRect.bottom()) return; decltype(scenePos) pos(m_offset + scenePos.x() / m_scale, scenePos.y()); // Try to select one of context switches or items for (auto item : m_items) { auto cse = item->intersectEvent(pos); if (cse != nullptr) { const auto& itemBlock = cse->tree; auto widget = new QWidget(this, Qt::ToolTip | Qt::WindowStaysOnTopHint | Qt::WindowTransparentForInput); if (widget == nullptr) return; widget->setObjectName(QStringLiteral("DiagramPopup")); auto lay = new QGridLayout(widget); if (lay == nullptr) return; int row = 0; lay->addWidget(new BoldLabel("Context switch event", widget), row, 0, 1, 3, Qt::AlignHCenter); ++row; lay->addWidget(new QLabel("Thread:", widget), row, 0, Qt::AlignRight); const char* process_name = ""; ::profiler::thread_id_t tid = 0; if (EASY_GLOBALS.version < ::profiler_gui::V130) { tid = cse->tree.node->id(); process_name = cse->tree.node->name(); } else { tid = cse->tree.cs->tid(); process_name = cse->tree.cs->name(); } auto it = EASY_GLOBALS.profiler_blocks.find(tid); if (it != EASY_GLOBALS.profiler_blocks.end()) { if (EASY_GLOBALS.hex_thread_id) lay->addWidget(new QLabel(QString("0x%1 %2").arg(tid, 0, 16).arg(it->second.name()), widget), row, 1, 1, 2, Qt::AlignLeft); else lay->addWidget(new QLabel(QString("%1 %2").arg(tid).arg(it->second.name()), widget), row, 1, 1, 2, Qt::AlignLeft); } else if (EASY_GLOBALS.hex_thread_id) lay->addWidget(new QLabel(QString("0x%1").arg(tid, 0, 16), widget), row, 1, 1, 2, Qt::AlignLeft); else lay->addWidget(new QLabel(QString::number(tid), widget), row, 1, 1, 2, Qt::AlignLeft); ++row; lay->addWidget(new QLabel("Process:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(process_name, widget), row, 1, 1, 2, Qt::AlignLeft); ++row; const auto duration = itemBlock.node->duration(); lay->addWidget(new QLabel("Duration:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, duration, 3), widget), row, 1, 1, 2, Qt::AlignLeft); ++row; if (itemBlock.per_thread_stats) { lay->addWidget(new QLabel("Sum:", widget), row, 0, Qt::AlignRight); 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); ++row; lay->addWidget(new BoldLabel("-------- Statistics --------", widget), row, 0, 1, 3, Qt::AlignHCenter); lay->addWidget(new QLabel("per ", widget), row + 1, 0, Qt::AlignRight); lay->addWidget(new QLabel("This %:", widget), row + 2, 0, Qt::AlignRight); lay->addWidget(new QLabel("Sum %:", widget), row + 3, 0, Qt::AlignRight); lay->addWidget(new QLabel("N Calls:", widget), row + 4, 0, Qt::AlignRight); lay->addWidget(new QLabel("Thread", widget), row + 1, 1, Qt::AlignHCenter); auto percent = ::profiler_gui::percentReal(duration, item->root()->profiled_time); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 2, 1, Qt::AlignHCenter); 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); lay->addWidget(new QLabel(QString::number(itemBlock.per_thread_stats->calls_number), widget), row + 4, 1, Qt::AlignHCenter); if (itemBlock.per_frame_stats && !::profiler_gui::is_max(itemBlock.per_frame_stats->parent_block)) { int col = 2; auto frame_duration = easyBlocksTree(itemBlock.per_frame_stats->parent_block).node->duration(); lay->addWidget(new QLabel("Frame", widget), row + 1, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(duration, frame_duration); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 2, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(itemBlock.per_frame_stats->total_duration, frame_duration); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 3, col, Qt::AlignHCenter); lay->addWidget(new QLabel(QString::number(itemBlock.per_frame_stats->calls_number), widget), row + 4, col, Qt::AlignHCenter); } } m_popupWidget = widget; break; } ::profiler::block_index_t i = ~0U; auto block = item->intersect(pos, i); if (block != nullptr) { const auto& itemBlock = block->tree; const auto& itemDesc = easyDescriptor(itemBlock.node->id()); if (itemDesc.type() == profiler::BlockType::Value) { m_isArbitraryValueTooltip = true; m_popupWidget = new ArbitraryValueToolTip(itemDesc.name(), itemBlock, this); break; } auto widget = new QWidget(this, Qt::ToolTip | Qt::WindowStaysOnTopHint | Qt::WindowTransparentForInput); if (widget == nullptr) return; widget->setObjectName(QStringLiteral("DiagramPopup")); auto lay = new QGridLayout(widget); if (lay == nullptr) return; lay->setSpacing(2); int row = 0; switch (itemDesc.type()) { case ::profiler::BlockType::Block: { const auto name = *itemBlock.node->name() != 0 ? itemBlock.node->name() : itemDesc.name(); //lay->addWidget(new QLabel("Name:", widget), row, 0, Qt::AlignRight); lay->addWidget(new BoldLabel(::profiler_gui::toUnicode(name), widget), row, 0, 1, 5, Qt::AlignHCenter); ++row; const auto duration = itemBlock.node->duration(); lay->addWidget(new QLabel("Duration:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, duration, 3), widget), row, 1, 1, 3, Qt::AlignLeft); ++row; ::profiler::timestamp_t children_duration = 0; for (auto child : itemBlock.children) children_duration += easyBlock(child).tree.node->duration(); const auto self_duration = duration - children_duration; const auto self_percent = duration == 0 ? 100. : ::profiler_gui::percentReal(self_duration, duration); lay->addWidget(new QLabel("Self:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(QString("%1 (%2%)") .arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, self_duration, 3)) .arg(QString::number(self_percent, 'g', 3)), widget), row, 1, 1, 3, Qt::AlignLeft); ++row; break; } case ::profiler::BlockType::Event: { const auto name = *itemBlock.node->name() != 0 ? itemBlock.node->name() : itemDesc.name(); lay->addWidget(new BoldLabel("User defined event", widget), row, 0, 1, 2, Qt::AlignHCenter); ++row; lay->addWidget(new QLabel("Name:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(::profiler_gui::toUnicode(name), widget), row, 1, Qt::AlignLeft); ++row; break; } case ::profiler::BlockType::Value: { lay->addWidget(new BoldLabel("Arbitrary Value", widget), row, 0, 1, 2, Qt::AlignHCenter); ++row; lay->addWidget(new QLabel("Name:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(::profiler_gui::toUnicode(itemDesc.name()), widget), row, 1, Qt::AlignLeft); ++row; lay->addWidget(new QLabel("Value:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(::profiler_gui::shortValueString(*itemBlock.value), widget), row, 1, Qt::AlignLeft); ++row; lay->addWidget(new QLabel("VIN:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(QString("0x%1").arg(itemBlock.value->value_id(), 0, 16), widget), row, 1, Qt::AlignLeft); ++row; break; } default: { delete widget; return; } } if (itemBlock.per_thread_stats != nullptr) { if (itemDesc.type() == ::profiler::BlockType::Block) { const auto duration = itemBlock.node->duration(); lay->addWidget(new QLabel("Average:", widget), row, 0, Qt::AlignRight); 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); ++row; // Calculate idle/active time { auto threadRoot = item->root(); ::profiler::block_index_t ind = 0; auto it = ::std::lower_bound(threadRoot->sync.begin(), threadRoot->sync.end(), itemBlock.node->begin(), [](::profiler::block_index_t _cs_index, ::profiler::timestamp_t _val) { return EASY_GLOBALS.gui_blocks[_cs_index].tree.node->begin() < _val; }); if (it != threadRoot->sync.end()) { ind = static_cast<::profiler::block_index_t>(it - threadRoot->sync.begin()); if (ind > 0) --ind; } else { ind = static_cast<::profiler::block_index_t>(threadRoot->sync.size()); } ::profiler::timestamp_t idleTime = 0; for (auto ncs = static_cast<::profiler::block_index_t>(threadRoot->sync.size()); ind < ncs; ++ind) { auto cs_index = threadRoot->sync[ind]; const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; if (cs->begin() > itemBlock.node->end()) break; if (itemBlock.node->begin() <= cs->begin() && cs->end() <= itemBlock.node->end()) idleTime += cs->duration(); } const auto active_time = duration - idleTime; const auto active_percent = duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, duration); lay->addWidget(new QLabel("Active time:", widget), row, 0, Qt::AlignRight); 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); ++row; } lay->addWidget(new BoldLabel("-------- Statistics --------", widget), row, 0, 1, 5, Qt::AlignHCenter); lay->addWidget(new QLabel("per ", widget), row + 1, 0, Qt::AlignRight); lay->addWidget(new QLabel("This %:", widget), row + 2, 0, Qt::AlignRight); lay->addWidget(new QLabel("Sum %:", widget), row + 3, 0, Qt::AlignRight); lay->addWidget(new QLabel("Sum self %:", widget), row + 4, 0, Qt::AlignRight); lay->addWidget(new QLabel("N Calls:", widget), row + 5, 0, Qt::AlignRight); lay->addWidget(new QLabel("Thread", widget), row + 1, 1, Qt::AlignHCenter); auto percent = ::profiler_gui::percentReal(duration, item->root()->profiled_time); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 2, 1, Qt::AlignHCenter); 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); 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); lay->addWidget(new QLabel(QString::number(itemBlock.per_thread_stats->calls_number), widget), row + 5, 1, Qt::AlignHCenter); int col = 1; if (itemBlock.per_frame_stats->parent_block != i && !::profiler_gui::is_max(itemBlock.per_frame_stats->parent_block)) { ++col; auto frame_duration = easyBlocksTree(itemBlock.per_frame_stats->parent_block).node->duration(); lay->addWidget(new QLabel("Frame", widget), row + 1, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(duration, frame_duration); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 2, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(itemBlock.per_frame_stats->total_duration, frame_duration); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 3, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(itemBlock.per_frame_stats->total_duration - itemBlock.per_frame_stats->total_children_duration, frame_duration); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 4, col, Qt::AlignHCenter); lay->addWidget(new QLabel(QString::number(itemBlock.per_frame_stats->calls_number), widget), row + 5, col, Qt::AlignHCenter); } if (!::profiler_gui::is_max(itemBlock.per_parent_stats->parent_block))// != item->threadId()) { ++col; auto parent_duration = easyBlocksTree(itemBlock.per_parent_stats->parent_block).node->duration(); lay->addWidget(new QLabel("Parent", widget), row + 1, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(duration, parent_duration); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 2, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(itemBlock.per_parent_stats->total_duration, parent_duration); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 3, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(itemBlock.per_parent_stats->total_duration - itemBlock.per_parent_stats->total_children_duration, parent_duration); lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), row + 4, col, Qt::AlignHCenter); lay->addWidget(new QLabel(QString::number(itemBlock.per_parent_stats->calls_number), widget), row + 5, col, Qt::AlignHCenter); ++col; } } else { lay->addWidget(new QLabel("N calls/Thread:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(QString::number(itemBlock.per_thread_stats->calls_number), widget), row, 1, Qt::AlignLeft); } } m_popupWidget = widget; break; } } if (m_popupWidget != nullptr) { m_popupWidget->move(QCursor::pos()); m_popupWidget->show(); m_popupWidget->raise(); const auto w = std::min(m_popupWidget->width(), (int)m_visibleSceneRect.width()); const auto h = std::min(m_popupWidget->height(), (int)m_visibleSceneRect.height()); m_popupWidget->setFixedSize(w, h); const auto prevPos = scenePos; auto br = m_popupWidget->rect(); if (scenePos.y() + br.height() > m_visibleSceneRect.bottom()) scenePos.setY(::std::max(scenePos.y() - br.height(), m_visibleSceneRect.top())); if (scenePos.x() + br.width() > m_visibleSceneRect.right()) scenePos.setX(::std::max(scenePos.x() - br.width(), m_visibleSceneRect.left())); if ((scenePos - prevPos).manhattanLength() != 0) m_popupWidget->move(mapToGlobal(mapFromScene(scenePos))); } } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::onHierarchyFlagChange(bool) { bool changedSelection = false; if (!m_selectedBlocks.empty()) { changedSelection = true; m_selectedBlocks.clear(); } if (m_selectionItem->isVisible()) { for (auto item : m_items) { if (!EASY_GLOBALS.only_current_thread_hierarchy || item->threadId() == EASY_GLOBALS.selected_thread) item->getBlocks(m_selectionItem->left(), m_selectionItem->right(), m_selectedBlocks); } if (!m_selectedBlocks.empty()) { changedSelection = true; } } if (changedSelection) { emit intervalChanged(m_selectedBlocks, m_beginTime, position2time(m_selectionItem->left()), position2time(m_selectionItem->right()), m_selectionItem->reverse()); } } void BlocksGraphicsView::onSelectedThreadChange(::profiler::thread_id_t _id) { if (m_pScrollbar == nullptr || m_pScrollbar->hystThread() == _id) { return; } if (_id == 0) { m_pScrollbar->setHistogramSource(0, nullptr); return; } for (auto item : m_items) { if (item->threadId() == _id) { m_pScrollbar->setHistogramSource(_id, item->items(0)); bool changedSelection = false; if (EASY_GLOBALS.only_current_thread_hierarchy) { if (!m_selectedBlocks.empty()) { changedSelection = true; m_selectedBlocks.clear(); } if (m_selectionItem->isVisible()) { item->getBlocks(m_selectionItem->left(), m_selectionItem->right(), m_selectedBlocks); if (!m_selectedBlocks.empty()) changedSelection = true; } } if (changedSelection) { emit intervalChanged(m_selectedBlocks, m_beginTime, position2time(m_selectionItem->left()), position2time(m_selectionItem->right()), m_selectionItem->reverse()); } repaintScene(); return; } } m_pScrollbar->setHistogramSource(0, nullptr); repaintScene(); } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::onSelectedBlockChange(unsigned int _block_index) { if (!m_bUpdatingRect) { if (_block_index < EASY_GLOBALS.gui_blocks.size()) { // Scroll to item const auto& guiblock = EASY_GLOBALS.gui_blocks[_block_index]; const auto thread_item = m_items[guiblock.graphics_item]; const auto& item = thread_item->items(guiblock.graphics_item_level)[guiblock.graphics_item_index]; m_flickerSpeedX = m_flickerSpeedY = 0; const profiler_gui::BoolFlagGuard guard(m_bUpdatingRect, true); verticalScrollBar()->setValue(static_cast(thread_item->levelY(guiblock.graphics_item_level) - m_visibleSceneRect.height() * 0.5)); notifyVisibleRegionPosChange(item.left() + (item.width() - m_visibleRegionWidth) * 0.5); if (EASY_GLOBALS.selecting_block_changes_thread && EASY_GLOBALS.selected_thread != thread_item->threadId()) { EASY_GLOBALS.selected_thread = thread_item->threadId(); emit EASY_GLOBALS.events.lockCharts(); emit EASY_GLOBALS.events.selectedThreadChanged(EASY_GLOBALS.selected_thread); emit EASY_GLOBALS.events.unlockCharts(); } m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, guiblock.tree.node->id()); } else if (EASY_GLOBALS.selected_thread != 0) { for (auto item : m_items) { if (item->threadId() == EASY_GLOBALS.selected_thread) { m_pScrollbar->setHistogramSource(EASY_GLOBALS.selected_thread, item->items(0)); break; } } } else { m_pScrollbar->setHistogramSource(0, nullptr); } updateVisibleSceneRect(); repaintScene(); } } ////////////////////////////////////////////////////////////////////////// void BlocksGraphicsView::onRefreshRequired() { if (!m_bUpdatingRect) { repaintScene(); } } ////////////////////////////////////////////////////////////////////////// DiagramWidget::DiagramWidget(QWidget* _parent) : QWidget(_parent) , m_splitter(new QSplitter(Qt::Vertical, this)) , m_scrollbar(new BlocksGraphicsScrollbar(px(85) + 2 + (EASY_GLOBALS.size.font_height << 1), this)) , m_view(new BlocksGraphicsView(this)) , m_threadNamesWidget(new ThreadNamesWidget(m_view, m_scrollbar->height(), this)) { initWidget(); } void DiagramWidget::initWidget() { m_splitter->setHandleWidth(1); m_splitter->setContentsMargins(0, 0, 0, 0); m_splitter->addWidget(m_view); m_splitter->addWidget(m_scrollbar); m_splitter->setStretchFactor(0, 500); m_splitter->setStretchFactor(1, 1); auto lay = new QHBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); lay->setSpacing(1); lay->addWidget(m_threadNamesWidget); lay->addWidget(m_splitter); m_view->setScrollbar(m_scrollbar); } DiagramWidget::~DiagramWidget() { } BlocksGraphicsView* DiagramWidget::view() { return m_view; } void DiagramWidget::clear() { m_scrollbar->clear(); m_threadNamesWidget->clear(); m_view->clear(); } void DiagramWidget::save(QSettings& settings) { settings.setValue("diagram/vsplitter/geometry", m_splitter->saveGeometry()); settings.setValue("diagram/vsplitter/state", m_splitter->saveState()); } void DiagramWidget::restore(QSettings& settings) { auto geometry = settings.value("diagram/vsplitter/geometry").toByteArray(); if (!geometry.isEmpty()) m_splitter->restoreGeometry(geometry); auto state = settings.value("diagram/vsplitter/state").toByteArray(); if (!state.isEmpty()) m_splitter->restoreState(state); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void ThreadNameItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*) { auto const parentView = static_cast(scene()->parent()); const auto view = parentView->view(); const auto& items = view->getItems(); if (items.empty()) return; const auto visibleSceneRect = view->visibleSceneRect(); const auto h = visibleSceneRect.height() + EASY_GLOBALS.size.timeline_height - 2; const auto w = parentView->width();//parentView->sceneRect().width(); const auto overlap = EASY_GLOBALS.size.threads_row_spacing >> 1; static const QBrush brushes[2] = {QColor::fromRgb(BACKGROUND_1), QColor::fromRgb(BACKGROUND_2)}; int i = -1; QRectF rect; _painter->resetTransform(); // Draw thread names auto default_font = _painter->font(); _painter->setFont(EASY_GLOBALS.font.background); for (auto item : items) { ++i; auto br = item->boundingRect(); auto top = item->y() + br.top() - visibleSceneRect.top() - overlap; auto hgt = br.height() + EASY_GLOBALS.size.threads_row_spacing; auto bottom = top + hgt; if (top > h || bottom < 0) continue; if (item->threadId() == EASY_GLOBALS.selected_thread) _painter->setBrush(QBrush(QColor::fromRgb(::profiler_gui::SELECTED_THREAD_BACKGROUND))); else _painter->setBrush(brushes[i & 1]); if (top < 0) { hgt += top; top = 0; } const auto dh = top + hgt - h; if (dh > 0) hgt -= dh; rect.setRect(0, top, w, hgt); _painter->setPen(::profiler_gui::SYSTEM_BORDER_COLOR); _painter->drawRect(rect); rect.translate(-5, 0); _painter->setPen(::profiler_gui::TEXT_COLOR); _painter->drawText(rect, Qt::AlignRight | Qt::AlignVCenter, item->threadName()); } const auto rect_bottom = rect.bottom(); if (rect_bottom < h) { ++i; rect.translate(5, rect.height()); rect.setHeight(h - rect_bottom); _painter->setBrush(brushes[i & 1]); _painter->setPen(::profiler_gui::SYSTEM_BORDER_COLOR); _painter->drawRect(rect); } if (h + 2 >= parentView->height()) return; // Draw separator between thread names area and information area _painter->setPen(::profiler_gui::SYSTEM_BORDER_COLOR); _painter->drawLine(QLineF(0, h, w, h)); _painter->drawLine(QLineF(0, h + 2, w, h + 2)); // Draw information _painter->setFont(EASY_GLOBALS.font.ruler); QFontMetricsF fm(EASY_GLOBALS.font.ruler, parentView); const qreal th = fm.height(); // Calculate displayed text height const qreal time1 = view->chronoTime(); const qreal time2 = view->chronoTimeAux(); auto y = h + 2; auto drawTimeText = [&rect, &w, &y, &fm, &_painter](qreal time, qreal th, QRgb color) { if (time > 0) { const QString text = ::profiler_gui::autoTimeStringReal(time); // Displayed text rect.setRect(0, y, w, th); _painter->setPen(color); _painter->drawText(rect, Qt::AlignCenter, text); y += th; } }; drawTimeText(time1, th, ::profiler_gui::CHRONOMETER_COLOR.rgb() & 0x00ffffff); drawTimeText(time2, th, ::profiler_gui::CHRONOMETER_COLOR2.rgb() & 0x00ffffff); } ////////////////////////////////////////////////////////////////////////// ThreadNamesWidget::ThreadNamesWidget(BlocksGraphicsView* _view, int _additionalHeight, QWidget* _parent) : Parent(_parent) , m_idleTime(0) , m_view(_view) , m_popupWidget(nullptr) , m_maxLength(100) , m_additionalHeight(_additionalHeight + 1) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); setScene(new QGraphicsScene(this)); setCacheMode(QGraphicsView::CacheNone); setViewportUpdateMode(QGraphicsView::FullViewportUpdate); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFixedWidth(m_maxLength); connect(&EASY_GLOBALS.events, &::profiler_gui::GlobalSignals::selectedThreadChanged, [this](::profiler::thread_id_t){ repaintScene(); }); connect(&EASY_GLOBALS.events, &::profiler_gui::GlobalSignals::allDataGoingToBeDeleted, this, &This::clear); connect(m_view, &BlocksGraphicsView::treeChanged, this, &This::onTreeChange); connect(m_view, &BlocksGraphicsView::sceneUpdated, this, &This::repaintScene); connect(m_view->verticalScrollBar(), &QScrollBar::valueChanged, verticalScrollBar(), &QScrollBar::setValue, Qt::QueuedConnection); connect(m_view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &This::setVerticalScrollbarRange, Qt::QueuedConnection); connect(&m_idleTimer, &QTimer::timeout, this, &This::onIdleTimeout); } ThreadNamesWidget::~ThreadNamesWidget() { } void ThreadNamesWidget::removePopup(bool _removeFromScene) { if (m_popupWidget != nullptr) { auto widget = m_popupWidget->widget(); widget->setParent(nullptr); m_popupWidget->setWidget(nullptr); delete widget; if (_removeFromScene) scene()->removeItem(m_popupWidget); m_popupWidget = nullptr; } } void ThreadNamesWidget::clear() { const QSignalBlocker b(this); removePopup(); scene()->clear(); m_maxLength = 100; setFixedWidth(m_maxLength); m_idleTimer.stop(); m_idleTime = 0; } void ThreadNamesWidget::setVerticalScrollbarRange(int _minValue, int _maxValue) { verticalScrollBar()->setRange(_minValue, _maxValue + m_additionalHeight); } void ThreadNamesWidget::onTreeChange() { const QSignalBlocker b(this); removePopup(); scene()->clear(); m_idleTimer.stop(); m_idleTime = 0; QFontMetricsF fm(EASY_GLOBALS.font.background, this); qreal maxLength = 100; const auto& graphicsItems = m_view->getItems(); for (auto graphicsItem : graphicsItems) maxLength = ::std::max(maxLength, (10 + fm.width(graphicsItem->threadName())) * ::profiler_gui::FONT_METRICS_FACTOR); auto vbar = verticalScrollBar(); auto viewBar = m_view->verticalScrollBar(); auto r = m_view->sceneRect(); setSceneRect(0, r.top(), maxLength, r.height() + m_additionalHeight); setVerticalScrollbarRange(viewBar->minimum(), viewBar->maximum()); vbar->setSingleStep(viewBar->singleStep()); vbar->setPageStep(viewBar->pageStep()); vbar->setValue(viewBar->value()); auto item = new ThreadNameItem(); item->setPos(0, 0); item->setBoundingRect(sceneRect()); scene()->addItem(item); m_maxLength = static_cast(maxLength); setFixedWidth(m_maxLength); m_idleTimer.start(IDLE_TIMER_INTERVAL); } void ThreadNamesWidget::onIdleTimeout() { m_idleTime += IDLE_TIMER_INTERVAL; if (m_idleTime < IDLE_TIME) { removePopup(true); return; } if (m_popupWidget != nullptr) return; const auto localPos = mapFromGlobal(QCursor::pos()); auto visibleSceneRect = mapToScene(rect()).boundingRect(); visibleSceneRect.setTop(m_view->visibleSceneRect().top()); auto scenePos = QPointF(mapToScene(localPos).x(), m_view->mapToScene(localPos).y()); if (scenePos.x() < visibleSceneRect.left() || scenePos.x() > visibleSceneRect.right()) { if (m_idleTime > 3000) setFixedWidth(m_maxLength); return; } if (scenePos.y() < visibleSceneRect.top() || scenePos.y() > visibleSceneRect.bottom()) { if (m_idleTime > 3000) setFixedWidth(m_maxLength); return; } const qreal y = scenePos.y() - visibleSceneRect.top(); const auto& items = m_view->getItems(); if (items.empty()) { if (m_idleTime > 3000) setFixedWidth(m_maxLength); return; } const auto overlap = EASY_GLOBALS.size.threads_row_spacing >> 1; BlocksGraphicsItem* intersectingItem = nullptr; for (auto item : items) { auto br = item->boundingRect(); auto top = item->y() + br.top() - visibleSceneRect.top() - overlap; auto hgt = br.height() + EASY_GLOBALS.size.threads_row_spacing; auto bottom = top + hgt; if (bottom < y || y < top) continue; intersectingItem = item; break; } if (intersectingItem != nullptr) { auto widget = new QWidget(this, Qt::ToolTip | Qt::WindowStaysOnTopHint | Qt::WindowTransparentForInput); if (widget == nullptr) return; widget->setObjectName(QStringLiteral("ThreadsPopup")); widget->setAttribute(Qt::WA_ShowWithoutActivating, true); widget->setFocusPolicy(Qt::NoFocus); auto lay = new QGridLayout(widget); if (lay == nullptr) return; int row = 0; lay->setSpacing(2); lay->addWidget(new BoldLabel(intersectingItem->threadName(), widget), row, 0, 1, 2, Qt::AlignHCenter); ++row; ::profiler::timestamp_t duration = 0; const auto& root = *intersectingItem->root(); if (!root.children.empty()) duration = easyBlock(root.children.back()).tree.node->end() - easyBlock(root.children.front()).tree.node->begin(); lay->addWidget(new QLabel("Duration:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, duration, 3), widget), row, 1, Qt::AlignLeft); ++row; lay->addWidget(new QLabel("Profiled:", widget), row, 0, Qt::AlignRight); if (duration != 0) { lay->addWidget(new QLabel(QString("%1 (%2%)").arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, root.profiled_time, 3)) .arg(QString::number(100. * (double)root.profiled_time / (double)duration, 'f', 2)), widget), row, 1, Qt::AlignLeft); } else { lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, root.profiled_time, 3), widget), row, 1, Qt::AlignLeft); } ++row; lay->addWidget(new QLabel("Wait:", widget), row, 0, Qt::AlignRight); if (duration != 0) { lay->addWidget(new QLabel(QString("%1 (%2%)").arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, root.wait_time, 3)) .arg(QString::number(100. * (double)root.wait_time / (double)duration, 'f', 2)), widget), row, 1, Qt::AlignLeft); } else { lay->addWidget(new QLabel(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, root.wait_time, 3), widget), row, 1, Qt::AlignLeft); } ++row; const auto eventsSize = root.events.size(); lay->addWidget(new QLabel("Frames:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(QString::number(root.frames_number), widget), row, 1, Qt::AlignLeft); ++row; lay->addWidget(new QLabel("Blocks:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(QString::number(root.blocks_number - eventsSize), widget), row, 1, Qt::AlignLeft); ++row; lay->addWidget(new QLabel("Markers:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(QString::number(eventsSize), widget), row, 1, Qt::AlignLeft); ++row; m_popupWidget = new QGraphicsProxyWidget(); if (m_popupWidget != nullptr) { auto effect = new QGraphicsDropShadowEffect(); effect->setBlurRadius(5); effect->setOffset(3, 3); m_popupWidget->setGraphicsEffect(effect); m_popupWidget->setWidget(widget); scene()->addItem(m_popupWidget); auto br = m_popupWidget->boundingRect(); if (maximumWidth() < br.width()) { setFixedWidth(static_cast(br.width())); visibleSceneRect.setWidth(br.width()); } if (scenePos.y() + br.height() > visibleSceneRect.bottom()) scenePos.setY(::std::max(scenePos.y() - br.height(), visibleSceneRect.top())); if (scenePos.x() + br.width() > visibleSceneRect.right()) scenePos.setX(::std::max(scenePos.x() - br.width(), visibleSceneRect.left())); m_popupWidget->setPos(scenePos.x(), scenePos.y() - visibleSceneRect.top()); } } } void ThreadNamesWidget::repaintScene() { scene()->update(); } void ThreadNamesWidget::mousePressEvent(QMouseEvent* _event) { m_idleTime = 0; QMouseEvent e(_event->type(), _event->pos() - QPointF(sceneRect().width(), 0), _event->button(), _event->buttons() & ~Qt::RightButton, _event->modifiers()); m_view->mousePressEvent(&e); _event->accept(); } void ThreadNamesWidget::mouseDoubleClickEvent(QMouseEvent* _event) { const auto overlap = EASY_GLOBALS.size.threads_row_spacing >> 1; m_idleTime = 0; auto y = m_view->mapToScene(mapFromGlobal(QCursor::pos())).y(); const auto& items = m_view->getItems(); for (auto item : items) { auto br = item->boundingRect(); auto top = item->y() + br.top() - overlap; auto bottom = top + br.height() + overlap; if (y < top || y > bottom) continue; const auto thread_id = item->threadId(); if (thread_id != EASY_GLOBALS.selected_thread) { EASY_GLOBALS.selected_thread = thread_id; emit EASY_GLOBALS.events.selectedThreadChanged(thread_id); } break; } _event->accept(); } void ThreadNamesWidget::mouseReleaseEvent(QMouseEvent* _event) { m_idleTime = 0; QMouseEvent e(_event->type(), _event->pos() - QPointF(sceneRect().width(), 0), _event->button(), _event->buttons() & ~Qt::RightButton, _event->modifiers()); m_view->mouseReleaseEvent(&e); _event->accept(); } void ThreadNamesWidget::mouseMoveEvent(QMouseEvent* _event) { m_idleTime = 0; QMouseEvent e(_event->type(), _event->pos() - QPointF(sceneRect().width(), 0), _event->button(), _event->buttons() & ~Qt::RightButton, _event->modifiers()); m_view->mouseMoveEvent(&e); _event->accept(); } void ThreadNamesWidget::keyPressEvent(QKeyEvent* _event) { m_idleTime = 0; m_view->keyPressEvent(_event); } void ThreadNamesWidget::keyReleaseEvent(QKeyEvent* _event) { m_idleTime = 0; _event->accept(); } void ThreadNamesWidget::wheelEvent(QWheelEvent* _event) { m_idleTime = 0; auto vbar = m_view->verticalScrollBar(); if (vbar != nullptr) { _event->accept(); const auto prev = vbar->value(); vbar->setValue(vbar->value() - _event->delta()); if (prev != vbar->value()) { verticalScrollBar()->setValue(vbar->value()); repaintScene(); } } } ////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////