From acbaeddceb29d1114aba43f59b6cefb168e1d7a8 Mon Sep 17 00:00:00 2001 From: Ernie Pasveer Date: Sun, 14 Jun 2026 12:47:44 -0500 Subject: [PATCH] Clean up code to my style. --- src/SeerParallelStacksCommon.cpp | 38 +- src/SeerParallelStacksCommon.h | 36 +- src/SeerParallelStacksGraphicsView.cpp | 519 +++++++++++---------- src/SeerParallelStacksGraphicsView.h | 194 ++++---- src/SeerParallelStacksVisualizerWidget.cpp | 2 + 5 files changed, 382 insertions(+), 407 deletions(-) diff --git a/src/SeerParallelStacksCommon.cpp b/src/SeerParallelStacksCommon.cpp index 4fa5236..4214264 100644 --- a/src/SeerParallelStacksCommon.cpp +++ b/src/SeerParallelStacksCommon.cpp @@ -3,22 +3,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "SeerParallelStacksVisualizerWidget.h" -#include "SeerHelpPageDialog.h" #include "SeerUtl.h" -#include -#include -#include -#include -#include -#include -#include -#include #include -#include #include -namespace Seer { -namespace PSV { +namespace Seer::PSV { Frame::Frame() { } @@ -135,13 +124,13 @@ namespace PSV { QString result = QString("Thread %1").arg(_id); - for (const auto &f : _frames) + for (const auto& f : _frames) result += "\n " + f.toString(); return result; } - static std::shared_ptr buildImpl( QVector &threadPtrs, const QString ¤tFunction, int depth) { + static std::shared_ptr buildImpl( QVector& threadPtrs, const QString& currentFunction, int depth) { auto node = std::make_shared(); node->depth = depth; @@ -149,14 +138,14 @@ namespace PSV { node->threads = threadPtrs; // Group threads by the function at position [-depth-1] (bottom-up). - QMap> functionThreads; + QMap> functionThreads; int level = -depth - 1; - for (Thread *t : threadPtrs) { + for (Thread* t : threadPtrs) { int idx = t->frames().size() + level; // convert negative index if (idx < 0 || idx >= t->frames().size()) continue; - const QString &fn = t->frames()[idx].function(); + const QString& fn = t->frames()[idx].function(); functionThreads[fn].append(t); } @@ -168,12 +157,12 @@ namespace PSV { return node; } - std::shared_ptr buildParallelStacks(QVector &threads) { + std::shared_ptr buildParallelStacks(QVector& threads) { - QVector ptrs; + QVector ptrs; ptrs.reserve(threads.size()); - for (auto &t : threads) + for (auto& t : threads) ptrs.append(&t); return buildImpl(ptrs, QString(), 0); @@ -182,13 +171,13 @@ namespace PSV { // --------------------------------------------------------------- // fillStack — flatten StackNode tree into Stack tree for graphing // --------------------------------------------------------------- - std::shared_ptr fillStack(const std::shared_ptr &node) { + std::shared_ptr fillStack(const std::shared_ptr& node) { auto stack = std::make_shared(); stack->threadCount = static_cast(node->threads.size()); // Collect thread IDs for this node - for (const Thread *t : node->threads) + for (const Thread* t : node->threads) stack->threadIds.append(QString::number(t->id())); if (!node->function.isEmpty()) @@ -203,13 +192,12 @@ namespace PSV { stack->threadCount = child->threadCount; stack->threadIds = child->threadIds; } else { - for (const auto &childNode : node->children) { + for (const auto& childNode : node->children) { stack->stacks.append(fillStack(childNode)); } } return stack; } +} -} // namespace PSV -} // namespace Seer diff --git a/src/SeerParallelStacksCommon.h b/src/SeerParallelStacksCommon.h index 4e6a96a..ba43d1d 100644 --- a/src/SeerParallelStacksCommon.h +++ b/src/SeerParallelStacksCommon.h @@ -6,13 +6,9 @@ #include #include -#include -#include -#include -#include +#include -namespace Seer { -namespace PSV { +namespace Seer::PSV { class Frame { public: @@ -72,28 +68,22 @@ namespace PSV { typedef QVector Threads; - typedef QVector ThreadIds; - struct StackNode { - QString function; // empty == root - int depth = 0; - QVector threads; // non-owning pointers - QVector> children; + QString function; // empty == root + int depth = 0; + QVector threads; // non-owning pointers + QVector> children; }; - // Build the parallel-stacks tree from a flat list of threads. - std::shared_ptr buildParallelStacks(QVector &threads); - // Flat "Stack" representation used when building the graph. struct Stack { - QVector functions; - QVector> stacks; - int threadCount = 0; - QVector threadIds; // IDs of every thread in this node + QVector functions; + QVector> stacks; + int threadCount = 0; + QVector threadIds; // IDs of every thread in this node }; - std::shared_ptr fillStack(const std::shared_ptr &node); - -} // namespace PSV -} // namespace Seer + std::shared_ptr buildParallelStacks (QVector& threads); // Build the parallel-stacks tree from a flat list of threads. + std::shared_ptr fillStack (const std::shared_ptr& node); +} diff --git a/src/SeerParallelStacksGraphicsView.cpp b/src/SeerParallelStacksGraphicsView.cpp index 0fa7619..18a58a6 100644 --- a/src/SeerParallelStacksGraphicsView.cpp +++ b/src/SeerParallelStacksGraphicsView.cpp @@ -8,239 +8,231 @@ #include #include -namespace Seer { -namespace PSV { +namespace Seer::PSV { -// ================================================================ -// StackBoxItem -// ================================================================ + // ================================================================ + // StackBoxItem + // ================================================================ -StackBoxItem::StackBoxItem(const Stack &stack, QGraphicsItem *parent) - : QGraphicsItem(parent) -{ - // Enable geometry-change notifications so itemChange() fires on setPos() - setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); + StackBoxItem::StackBoxItem(const Stack& stack, QGraphicsItem* parent) : QGraphicsItem(parent) { - m_headerLeft = QString("%1 Thread%2") - .arg(stack.threadCount) - .arg(stack.threadCount == 1 ? "" : "s"); + // Enable geometry-change notifications so itemChange() fires on setPos() + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); - if (!stack.threadIds.isEmpty()) { - QStringList ids = stack.threadIds; - if (ids.size() > 8) - m_headerRight = QString("[%1 … +%2]") - .arg(ids.mid(0, 8).join(", ")) - .arg(ids.size() - 8); - else - m_headerRight = "[" + ids.join(", ") + "]"; + _headerLeft = QString("%1 Thread%2") .arg(stack.threadCount) .arg(stack.threadCount == 1 ? "" : "s"); + + if (!stack.threadIds.isEmpty()) { + QStringList ids = stack.threadIds; + if (ids.size() > 8) + _headerRight = QString("[%1 … +%2]") .arg(ids.mid(0, 8).join(", ")) .arg(ids.size() - 8); + else + _headerRight = "[" + ids.join(", ") + "]"; + } + + for (int i = stack.functions.size() - 1; i >= 0; --i) { + _rows.append({ stack.functions[i], Function }); + } + + QFont boldFont; boldFont.setBold(true); + QFontMetrics boldFm(boldFont); + QFont normFont; + QFontMetrics normFm(normFont); + + qreal headerW = boldFm.horizontalAdvance(_headerLeft) + + boldFm.horizontalAdvance(_headerRight) + + kHeaderGap; + qreal maxTextW = headerW; + + for (const auto& row : _rows) { + maxTextW = std::max(maxTextW, (qreal)normFm.horizontalAdvance(row.text)); + } + + _width = maxTextW + 2 * kPadX; + _height = kPadY + kRowH * (1 + (int)_rows.size()) + kPadY; } - for (int i = stack.functions.size() - 1; i >= 0; --i) - m_rows.append({ stack.functions[i], Function }); - - QFont boldFont; boldFont.setBold(true); - QFontMetrics boldFm(boldFont); - QFont normFont; - QFontMetrics normFm(normFont); - - qreal headerW = boldFm.horizontalAdvance(m_headerLeft) - + boldFm.horizontalAdvance(m_headerRight) - + kHeaderGap; - qreal maxTextW = headerW; - for (const auto &row : m_rows) - maxTextW = std::max(maxTextW, (qreal)normFm.horizontalAdvance(row.text)); - - m_width = maxTextW + 2 * kPadX; - m_height = kPadY + kRowH * (1 + (int)m_rows.size()) + kPadY; -} - -QRectF StackBoxItem::boundingRect() const -{ - return QRectF(0, 0, m_width, m_height); -} - -void StackBoxItem::paint(QPainter *painter, - const QStyleOptionGraphicsItem *, - QWidget *) -{ - painter->setRenderHint(QPainter::Antialiasing); - - painter->setBrush(QColor(0xFA, 0xFA, 0xFA)); - painter->setPen(QPen(QColor(0x88, 0x88, 0x88), 1.5)); - painter->drawRoundedRect(boundingRect(), 6, 6); - - QFont boldFont; boldFont.setBold(true); - QFont normFont; - const qreal innerW = m_width - 2 * kPadX; - qreal y = kPadY; - - painter->setFont(boldFont); - painter->setPen(QColor(0x22, 0x22, 0x22)); - painter->drawText(QRectF(kPadX, y, innerW, kRowH), - Qt::AlignLeft | Qt::AlignVCenter, m_headerLeft); - if (!m_headerRight.isEmpty()) { - painter->setPen(QColor(0x1A, 0x52, 0xA8)); - painter->drawText(QRectF(kPadX, y, innerW, kRowH), - Qt::AlignRight | Qt::AlignVCenter, m_headerRight); + QRectF StackBoxItem::boundingRect() const { + return QRectF(0, 0, _width, _height); } - y += kRowH; - painter->setPen(QPen(QColor(0xCC, 0xCC, 0xCC), 1)); - painter->drawLine(QPointF(0, y), QPointF(m_width, y)); + void StackBoxItem::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { + + painter->setRenderHint(QPainter::Antialiasing); + + painter->setBrush(QColor(0xFA, 0xFA, 0xFA)); + painter->setPen(QPen(QColor(0x88, 0x88, 0x88), 1.5)); + painter->drawRoundedRect(boundingRect(), 6, 6); + + QFont boldFont; boldFont.setBold(true); + QFont normFont; + const qreal innerW = _width - 2 * kPadX; + qreal y = kPadY; + + painter->setFont(boldFont); + painter->setPen(QColor(0x22, 0x22, 0x22)); + painter->drawText(QRectF(kPadX, y, innerW, kRowH), Qt::AlignLeft | Qt::AlignVCenter, _headerLeft); + + if (!_headerRight.isEmpty()) { + painter->setPen(QColor(0x1A, 0x52, 0xA8)); + painter->drawText(QRectF(kPadX, y, innerW, kRowH), Qt::AlignRight | Qt::AlignVCenter, _headerRight); + } - painter->setFont(normFont); - painter->setPen(QColor(0x00, 0x7A, 0x33)); - for (const auto &row : m_rows) { - painter->drawText(QRectF(kPadX, y, innerW, kRowH), - Qt::AlignLeft | Qt::AlignVCenter, row.text); y += kRowH; + + painter->setPen(QPen(QColor(0xCC, 0xCC, 0xCC), 1)); + painter->drawLine(QPointF(0, y), QPointF(_width, y)); + + painter->setFont(normFont); + painter->setPen(QColor(0x00, 0x7A, 0x33)); + + for (const auto& row : _rows) { + painter->drawText(QRectF(kPadX, y, innerW, kRowH), Qt::AlignLeft | Qt::AlignVCenter, row.text); + y += kRowH; + } + + if (_dragging) { + painter->setBrush(Qt::NoBrush); + painter->setPen(QPen(QColor(0x1A, 0x52, 0xA8), 2.0, Qt::DashLine)); + painter->drawRoundedRect(boundingRect().adjusted(1, 1, -1, -1), 6, 6); + } } - if (m_dragging) { + QPointF StackBoxItem::sceneBottom() const { + return mapToScene(QPointF(_width / 2.0, _height)); + } + + QPointF StackBoxItem::sceneTop() const { + return mapToScene(QPointF(_width / 2.0, 0)); + } + + QVariant StackBoxItem::itemChange(GraphicsItemChange change, const QVariant& value) { + + if (change == ItemPositionHasChanged) { + for (LiveEdge* e : _edges) { + e->update(); // ask each connected edge to repaint + } + } + + return QGraphicsItem::itemChange(change, value); + } + + void StackBoxItem::mousePressEvent(QGraphicsSceneMouseEvent* event) { + + if (event->button() == Qt::LeftButton && event->modifiers() & Qt::ControlModifier) { + + _dragging = true; + _dragOffset = event->pos(); + + setCursor(Qt::ClosedHandCursor); + setZValue(10); + update(); + + event->accept(); + + }else{ + QGraphicsItem::mousePressEvent(event); + } + } + + void StackBoxItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { + if (_dragging) { + setPos(mapToScene(event->pos() - _dragOffset)); + event->accept(); + }else{ + QGraphicsItem::mouseMoveEvent(event); + } + } + + void StackBoxItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { + if (_dragging && event->button() == Qt::LeftButton) { + + _dragging = false; + + setCursor(Qt::ArrowCursor); + setZValue(0); + update(); + + event->accept(); + + }else{ + QGraphicsItem::mouseReleaseEvent(event); + } + } + + // ================================================================ + // LiveEdge — redraws itself each paint() from current endpoint positions + // ================================================================ + + LiveEdge::LiveEdge(StackBoxItem* from, StackBoxItem* to, QGraphicsItem* parent) : QGraphicsItem(parent) , _from(from) , _to(to) { + + setZValue(-1); + // Position the edge item at the scene origin; all coordinates are scene-space. + setPos(0, 0); + + _from->registerEdge(this); + _to->registerEdge(this); + } + + LiveEdge::~LiveEdge() { + // Guard against half-destroyed scenes + if (_from) _from->unregisterEdge(this); + if (_to) _to->unregisterEdge(this); + } + + QRectF LiveEdge::boundingRect() const { + + // Return the bounding rect of the two endpoints plus generous padding + // so the bezier and arrowhead are never clipped. + if (!_from || !_to) return QRectF(); + + QPointF f = _from->sceneBottom(); + QPointF t = _to->sceneTop(); + + qreal pad = kArrow + kVCtrl + 4; + + return QRectF(f, t).normalized().adjusted(-pad, -pad, pad, pad); + } + + void LiveEdge::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { + + if (!_from || !_to) return; + + painter->setRenderHint(QPainter::Antialiasing); + + QPointF from = _from->sceneBottom(); + QPointF to = _to->sceneTop(); + + // Bezier: control points pull vertically toward each other + QPainterPath path; + path.moveTo(from); + path.cubicTo(from + QPointF(0, kVCtrl), to + QPointF(0, -kVCtrl), to); + + painter->setPen(QPen(QColor(0x55, 0x55, 0x55), 1.5)); painter->setBrush(Qt::NoBrush); - painter->setPen(QPen(QColor(0x1A, 0x52, 0xA8), 2.0, Qt::DashLine)); - painter->drawRoundedRect(boundingRect().adjusted(1, 1, -1, -1), 6, 6); + painter->drawPath(path); + + // Arrowhead at `to` pointing downward (into the parent box) + // The tangent direction at the end of the cubic is (to - cp2) + QPointF cp2 = to + QPointF(0, -kVCtrl); + QPointF dir = to - cp2; + double len = std::hypot(dir.x(), dir.y()); + if (len < 1e-6) return; + dir /= len; // normalise + + // Perpendicular + QPointF perp(-dir.y(), dir.x()); + + QPointF a1 = to - dir * kArrow + perp * (kArrow * 0.5); + QPointF a2 = to - dir * kArrow - perp * (kArrow * 0.5); + + QPolygonF arrowHead; + arrowHead << to << a1 << a2; + painter->setPen(Qt::NoPen); + painter->setBrush(QColor(0x55, 0x55, 0x55)); + painter->drawPolygon(arrowHead); } } -QPointF StackBoxItem::sceneBottom() const -{ - return mapToScene(QPointF(m_width / 2.0, m_height)); -} - -QPointF StackBoxItem::sceneTop() const -{ - return mapToScene(QPointF(m_width / 2.0, 0)); -} - -QVariant StackBoxItem::itemChange(GraphicsItemChange change, const QVariant &value) -{ - if (change == ItemPositionHasChanged) { - for (LiveEdge *e : m_edges) - e->update(); // ask each connected edge to repaint - } - return QGraphicsItem::itemChange(change, value); -} - -void StackBoxItem::mousePressEvent(QGraphicsSceneMouseEvent *event) -{ - if (event->button() == Qt::LeftButton && - event->modifiers() & Qt::ControlModifier) { - m_dragging = true; - m_dragOffset = event->pos(); - setCursor(Qt::ClosedHandCursor); - setZValue(10); - update(); - event->accept(); - } else { - QGraphicsItem::mousePressEvent(event); - } -} - -void StackBoxItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) -{ - if (m_dragging) { - setPos(mapToScene(event->pos() - m_dragOffset)); - event->accept(); - } else { - QGraphicsItem::mouseMoveEvent(event); - } -} - -void StackBoxItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) -{ - if (m_dragging && event->button() == Qt::LeftButton) { - m_dragging = false; - setCursor(Qt::ArrowCursor); - setZValue(0); - update(); - event->accept(); - } else { - QGraphicsItem::mouseReleaseEvent(event); - } -} - -// ================================================================ -// LiveEdge — redraws itself each paint() from current endpoint positions -// ================================================================ - -LiveEdge::LiveEdge(StackBoxItem *from, StackBoxItem *to, QGraphicsItem *parent) - : QGraphicsItem(parent) - , m_from(from) - , m_to(to) -{ - setZValue(-1); - // Position the edge item at the scene origin; all coordinates are scene-space. - setPos(0, 0); - - m_from->registerEdge(this); - m_to->registerEdge(this); -} - -LiveEdge::~LiveEdge() -{ - // Guard against half-destroyed scenes - if (m_from) m_from->unregisterEdge(this); - if (m_to) m_to->unregisterEdge(this); -} - -QRectF LiveEdge::boundingRect() const -{ - // Return the bounding rect of the two endpoints plus generous padding - // so the bezier and arrowhead are never clipped. - if (!m_from || !m_to) return QRectF(); - QPointF f = m_from->sceneBottom(); - QPointF t = m_to->sceneTop(); - qreal pad = kArrow + kVCtrl + 4; - return QRectF(f, t).normalized().adjusted(-pad, -pad, pad, pad); -} - -void LiveEdge::paint(QPainter *painter, - const QStyleOptionGraphicsItem *, - QWidget *) -{ - if (!m_from || !m_to) return; - - painter->setRenderHint(QPainter::Antialiasing); - - QPointF from = m_from->sceneBottom(); - QPointF to = m_to->sceneTop(); - - // Bezier: control points pull vertically toward each other - QPainterPath path; - path.moveTo(from); - path.cubicTo(from + QPointF(0, kVCtrl), - to + QPointF(0, -kVCtrl), - to); - - painter->setPen(QPen(QColor(0x55, 0x55, 0x55), 1.5)); - painter->setBrush(Qt::NoBrush); - painter->drawPath(path); - - // Arrowhead at `to` pointing downward (into the parent box) - // The tangent direction at the end of the cubic is (to - cp2) - QPointF cp2 = to + QPointF(0, -kVCtrl); - QPointF dir = to - cp2; - double len = std::hypot(dir.x(), dir.y()); - if (len < 1e-6) return; - dir /= len; // normalise - - // Perpendicular - QPointF perp(-dir.y(), dir.x()); - - QPointF a1 = to - dir * kArrow + perp * (kArrow * 0.5); - QPointF a2 = to - dir * kArrow - perp * (kArrow * 0.5); - - QPolygonF arrowHead; - arrowHead << to << a1 << a2; - painter->setPen(Qt::NoPen); - painter->setBrush(QColor(0x55, 0x55, 0x55)); - painter->drawPolygon(arrowHead); -} - -} // namespace PSV -} // namespace Seer - // ================================================================ // SeerParallelStacksGraphicsView // ================================================================ @@ -251,29 +243,30 @@ using Seer::PSV::LiveEdge; constexpr qreal kHGap = 30.0; constexpr qreal kVGap = 60.0; -SeerParallelStacksGraphicsView::SeerParallelStacksGraphicsView(QWidget *parent) - : QGraphicsView(parent) - , m_scene(new QGraphicsScene(this)) -{ - setScene(m_scene); +SeerParallelStacksGraphicsView::SeerParallelStacksGraphicsView(QWidget* parent) : QGraphicsView(parent) , _scene(new QGraphicsScene(this)) { + + setScene(_scene); setRenderHint(QPainter::Antialiasing); setDragMode(QGraphicsView::ScrollHandDrag); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setBackgroundBrush(QColor(0xF0, 0xF0, 0xF0)); } -void SeerParallelStacksGraphicsView::wheelEvent(QWheelEvent *event) -{ +void SeerParallelStacksGraphicsView::wheelEvent(QWheelEvent* event) { + const double factor = event->angleDelta().y() > 0 ? 1.15 : 1.0 / 1.15; + scale(factor, factor); } -void SeerParallelStacksGraphicsView::setStack(const std::shared_ptr &root) -{ - m_scene->clear(); +void SeerParallelStacksGraphicsView::setStack(const std::shared_ptr& root) { + + _scene->clear(); + if (!root) return; - auto *rootPN = new PlacedNode; + auto* rootPN = new PlacedNode; + buildPlacedTree(rootPN, root, nullptr); qreal xCursor = 0; @@ -295,56 +288,58 @@ void SeerParallelStacksGraphicsView::setStack(const std::shared_ptrsetSceneRect(m_scene->itemsBoundingRect().adjusted(-40, -40, 40, 40)); - fitInView(m_scene->sceneRect(), Qt::KeepAspectRatio); + _scene->setSceneRect(_scene->itemsBoundingRect().adjusted(-40, -40, 40, 40)); + fitInView(_scene->sceneRect(), Qt::KeepAspectRatio); } // Recursively find the maximum bottom edge (item->y() + item->height()) in the tree. -void SeerParallelStacksGraphicsView::collectMaxBottom(PlacedNode *pn, qreal &maxBottom) -{ - if (pn->item) +void SeerParallelStacksGraphicsView::collectMaxBottom(PlacedNode* pn, qreal& maxBottom) { + + if (pn->item) { maxBottom = std::max(maxBottom, pn->item->y() + pn->item->height()); - for (auto *child : pn->children) + } + + for (auto* child : pn->children) { collectMaxBottom(child, maxBottom); + } } // Shift every parentless item (direct visual root — parent has no box) // downward so its bottom edge sits at maxBottom. -void SeerParallelStacksGraphicsView::alignParentlessToBottom(PlacedNode *pn, qreal maxBottom) -{ - for (auto *child : pn->children) { +void SeerParallelStacksGraphicsView::alignParentlessToBottom(PlacedNode* pn, qreal maxBottom) { + + for (auto* child : pn->children) { // child->parent == rootPN (which has no item), so child is parentless. if (child->item) { qreal currentBottom = child->item->y() + child->item->height(); qreal dy = maxBottom - currentBottom; - if (std::abs(dy) > 0.5) + if (std::abs(dy) > 0.5) { child->item->setPos(child->item->x(), child->item->y() + dy); + } } // Do NOT recurse — only top-level parentless nodes are shifted. } } -void SeerParallelStacksGraphicsView::buildPlacedTree(PlacedNode *pn, - const std::shared_ptr &stack, - PlacedNode *parentPN) -{ +void SeerParallelStacksGraphicsView::buildPlacedTree(PlacedNode* pn, const std::shared_ptr& stack, PlacedNode* parentPN) { + pn->stack = stack; pn->parent = parentPN; if (!stack->functions.isEmpty()) { pn->item = new StackBoxItem(*stack); - m_scene->addItem(pn->item); + _scene->addItem(pn->item); } - for (const auto &child : stack->stacks) { - auto *childPN = new PlacedNode; + for (const auto& child : stack->stacks) { + auto* childPN = new PlacedNode; buildPlacedTree(childPN, child, pn); pn->children.append(childPN); } } -void SeerParallelStacksGraphicsView::layoutTree(PlacedNode *pn, qreal &xCursor, qreal yTop) -{ +void SeerParallelStacksGraphicsView::layoutTree(PlacedNode* pn, qreal& xCursor, qreal yTop) { + qreal itemW = pn->item ? pn->item->width() : 0; qreal itemH = pn->item ? pn->item->height() : 0; @@ -355,11 +350,11 @@ void SeerParallelStacksGraphicsView::layoutTree(PlacedNode *pn, qreal &xCursor, pn->cy = yTop + itemH; } xCursor += itemW + kHGap; - } else { + }else{ qreal childY = yTop; qreal firstCx = -1, lastCx = -1; - for (auto *child : pn->children) { + for (auto* child : pn->children) { layoutTree(child, xCursor, childY); if (firstCx < 0) firstCx = child->cx; lastCx = child->cx; @@ -368,35 +363,41 @@ void SeerParallelStacksGraphicsView::layoutTree(PlacedNode *pn, qreal &xCursor, pn->cx = (firstCx + lastCx) / 2.0; qreal maxChildBottom = childY; - for (auto *child : pn->children) + + for (auto* child : pn->children) { maxChildBottom = std::max(maxChildBottom, child->cy); + } + qreal parentY = maxChildBottom + kVGap; if (pn->item) { pn->item->setPos(pn->cx - itemW / 2.0, parentY); pn->cy = parentY + itemH; - } else { + }else{ pn->cy = parentY; } } } -void SeerParallelStacksGraphicsView::addEdges(PlacedNode *pn) -{ - for (auto *child : pn->children) { +void SeerParallelStacksGraphicsView::addEdges(PlacedNode* pn) { + + for (auto* child : pn->children) { if (pn->item && child->item) { // LiveEdge registers itself with both endpoints on construction. // The scene takes ownership via addItem. - auto *edge = new LiveEdge(child->item, pn->item); - m_scene->addItem(edge); + auto* edge = new LiveEdge(child->item, pn->item); + _scene->addItem(edge); } addEdges(child); } } -void SeerParallelStacksGraphicsView::deleteTree(PlacedNode *pn) -{ - for (auto *child : pn->children) +void SeerParallelStacksGraphicsView::deleteTree(PlacedNode* pn) { + + for (auto* child : pn->children) { deleteTree(child); + } + delete pn; } + diff --git a/src/SeerParallelStacksGraphicsView.h b/src/SeerParallelStacksGraphicsView.h index a4dd394..cabc53e 100644 --- a/src/SeerParallelStacksGraphicsView.h +++ b/src/SeerParallelStacksGraphicsView.h @@ -1,4 +1,5 @@ #pragma once + #include "SeerParallelStacksCommon.h" #include #include @@ -7,121 +8,114 @@ #include #include -namespace Seer { -namespace PSV { +namespace Seer::PSV { -class LiveEdge; // forward — StackBoxItem needs to know it + class LiveEdge; // forward — StackBoxItem needs to know it -// --------------------------------------------------------------- -// A single box in the graph: shows thread count + IDs + call frames. -// Ctrl+LMB grabs and moves the item freely within the scene. -// Moving the item automatically redraws all connected LiveEdges. -// --------------------------------------------------------------- -class StackBoxItem : public QGraphicsItem -{ -public: - explicit StackBoxItem(const Stack &stack, QGraphicsItem *parent = nullptr); + // --------------------------------------------------------------- + // A single box in the graph: shows thread count + IDs + call frames. + // Ctrl+LMB grabs and moves the item freely within the scene. + // Moving the item automatically redraws all connected LiveEdges. + // --------------------------------------------------------------- + class StackBoxItem : public QGraphicsItem { - QRectF boundingRect() const override; - void paint(QPainter *painter, - const QStyleOptionGraphicsItem *option, - QWidget *widget) override; + public: + explicit StackBoxItem(const Stack& stack, QGraphicsItem* parent = nullptr); - qreal width() const { return m_width; } - qreal height() const { return m_height; } + QRectF boundingRect () const override; + void paint (QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; - // Edge registry — called by LiveEdge on construction/destruction - void registerEdge (LiveEdge *e) { m_edges.append(e); } - void unregisterEdge(LiveEdge *e) { m_edges.removeAll(e); } + qreal width () const { return _width; } + qreal height () const { return _height; } - // Bottom-centre and top-centre in scene coordinates (edge attach points) - QPointF sceneBottom() const; - QPointF sceneTop() const; + // Edge registry — called by LiveEdge on construction/destruction + void registerEdge (LiveEdge* e) { _edges.append(e); } + void unregisterEdge (LiveEdge* e) { _edges.removeAll(e); } -protected: - QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + // Bottom-centre and top-centre in scene coordinates (edge attach points) + QPointF sceneBottom () const; + QPointF sceneTop () const; - void mousePressEvent (QGraphicsSceneMouseEvent *event) override; - void mouseMoveEvent (QGraphicsSceneMouseEvent *event) override; - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + protected: + QVariant itemChange (GraphicsItemChange change, const QVariant& value) override; -private: - enum RowKind { Function }; - struct Row { QString text; RowKind kind; }; + void mousePressEvent (QGraphicsSceneMouseEvent* event) override; + void mouseMoveEvent (QGraphicsSceneMouseEvent* event) override; + void mouseReleaseEvent (QGraphicsSceneMouseEvent* event) override; - QString m_headerLeft; - QString m_headerRight; - QVector m_rows; - qreal m_width = 0; - qreal m_height = 0; + private: + enum RowKind { Function }; + struct Row { QString text; RowKind kind; }; - bool m_dragging = false; - QPointF m_dragOffset; + QString _headerLeft; + QString _headerRight; + QVector _rows; + qreal _width = 0; + qreal _height = 0; - QVector m_edges; // non-owning + bool _dragging = false; + QPointF _dragOffset; - static constexpr qreal kPadX = 12; - static constexpr qreal kPadY = 8; - static constexpr qreal kRowH = 20; - static constexpr qreal kHeaderGap = 16; -}; + QVector _edges; // non-owning -// --------------------------------------------------------------- -// A live bezier edge between two StackBoxItems. -// It redraws itself whenever either endpoint moves. -// --------------------------------------------------------------- -class LiveEdge : public QGraphicsItem -{ -public: - LiveEdge(StackBoxItem *from, StackBoxItem *to, QGraphicsItem *parent = nullptr); - ~LiveEdge() override; - - QRectF boundingRect() const override; - void paint(QPainter *painter, - const QStyleOptionGraphicsItem *option, - QWidget *widget) override; - -private: - StackBoxItem *m_from; // child (bottom anchor) - StackBoxItem *m_to; // parent (top anchor) - - static constexpr qreal kArrow = 8.0; - static constexpr qreal kVCtrl = 60.0 * 0.4; // bezier control-point stretch -}; - -} // namespace PSV -} // namespace Seer - -// --------------------------------------------------------------- -// The full graph view. Scroll-drag (no modifier) pans the canvas. -// --------------------------------------------------------------- -class SeerParallelStacksGraphicsView : public QGraphicsView -{ - Q_OBJECT -public: - explicit SeerParallelStacksGraphicsView(QWidget *parent = nullptr); - void setStack(const std::shared_ptr &root); - -protected: - void wheelEvent(QWheelEvent *event) override; - -private: - struct PlacedNode { - std::shared_ptr stack; - Seer::PSV::StackBoxItem *item = nullptr; - PlacedNode *parent = nullptr; - QVector children; - qreal cx = 0; - qreal cy = 0; + static constexpr qreal kPadX = 12; + static constexpr qreal kPadY = 8; + static constexpr qreal kRowH = 20; + static constexpr qreal kHeaderGap = 16; }; - QGraphicsScene *m_scene; + // --------------------------------------------------------------- + // A live bezier edge between two StackBoxItems. + // It redraws itself whenever either endpoint moves. + // --------------------------------------------------------------- + class LiveEdge : public QGraphicsItem { - void buildPlacedTree(PlacedNode *pn, const std::shared_ptr &stack, - PlacedNode *parentPN); - void layoutTree(PlacedNode *pn, qreal &xCursor, qreal yTop); - void collectMaxBottom(PlacedNode *pn, qreal &maxBottom); - void alignParentlessToBottom(PlacedNode *pn, qreal maxBottom); - void addEdges(PlacedNode *pn); - void deleteTree(PlacedNode *pn); + public: + LiveEdge(StackBoxItem* from, StackBoxItem* to, QGraphicsItem* parent = nullptr); + ~LiveEdge() override; + + QRectF boundingRect () const override; + void paint (QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + private: + StackBoxItem* _from; // child (bottom anchor) + StackBoxItem* _to; // parent (top anchor) + + static constexpr qreal kArrow = 8.0; + static constexpr qreal kVCtrl = 60.0 * 0.4; // bezier control-point stretch + }; + +} + +class SeerParallelStacksGraphicsView : public QGraphicsView { + + Q_OBJECT + + public: + explicit SeerParallelStacksGraphicsView(QWidget* parent = nullptr); + + void setStack (const std::shared_ptr& root); + + protected: + void wheelEvent (QWheelEvent* event) override; + + private: + struct PlacedNode { + std::shared_ptr stack; + Seer::PSV::StackBoxItem* item = nullptr; + PlacedNode* parent = nullptr; + QVector children; + qreal cx = 0; + qreal cy = 0; + }; + + void buildPlacedTree (PlacedNode* pn, const std::shared_ptr& stack, PlacedNode* parentPN); + void layoutTree (PlacedNode* pn, qreal& xCursor, qreal yTop); + void collectMaxBottom (PlacedNode* pn, qreal& maxBottom); + void alignParentlessToBottom (PlacedNode* pn, qreal maxBottom); + void addEdges (PlacedNode* pn); + void deleteTree (PlacedNode* pn); + + QGraphicsScene* _scene; }; + diff --git a/src/SeerParallelStacksVisualizerWidget.cpp b/src/SeerParallelStacksVisualizerWidget.cpp index 7ae6599..c7db4da 100644 --- a/src/SeerParallelStacksVisualizerWidget.cpp +++ b/src/SeerParallelStacksVisualizerWidget.cpp @@ -174,8 +174,10 @@ void SeerParallelStacksVisualizerWidget::createDirectedGraph() { // Build parallel-stacks tree QVector local = _threads; // mutable copy for ptr stability + auto root = Seer::PSV::buildParallelStacks(local); auto stack = Seer::PSV::fillStack(root); + graphicsView->setStack(stack); }