Clean up code to my style.

This commit is contained in:
Ernie Pasveer
2026-06-14 12:47:44 -05:00
parent 93a118b7be
commit acbaeddceb
5 changed files with 382 additions and 407 deletions
+13 -25
View File
@@ -3,22 +3,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "SeerParallelStacksVisualizerWidget.h"
#include "SeerHelpPageDialog.h"
#include "SeerUtl.h"
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QFileDialog>
#include <QtGui/QIntValidator>
#include <QtGui/QIcon>
#include <QtPrintSupport/QPrinter>
#include <QtPrintSupport/QPrintDialog>
#include <QtCore/QSettings>
#include <QtCore/QProcess>
#include <QtCore/QStringList>
#include <QtCore/QFile>
#include <QtCore/QDebug>
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<StackNode> buildImpl( QVector<Thread *> &threadPtrs, const QString &currentFunction, int depth) {
static std::shared_ptr<StackNode> buildImpl( QVector<Thread*>& threadPtrs, const QString& currentFunction, int depth) {
auto node = std::make_shared<StackNode>();
node->depth = depth;
@@ -149,14 +138,14 @@ namespace PSV {
node->threads = threadPtrs;
// Group threads by the function at position [-depth-1] (bottom-up).
QMap<QString, QVector<Thread *>> functionThreads;
QMap<QString, QVector<Thread*>> 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<StackNode> buildParallelStacks(QVector<Thread> &threads) {
std::shared_ptr<StackNode> buildParallelStacks(QVector<Thread>& threads) {
QVector<Thread *> ptrs;
QVector<Thread*> 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<Stack> fillStack(const std::shared_ptr<StackNode> &node) {
std::shared_ptr<Stack> fillStack(const std::shared_ptr<StackNode>& node) {
auto stack = std::make_shared<Stack>();
stack->threadCount = static_cast<int>(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
+13 -23
View File
@@ -6,13 +6,9 @@
#include <QtWidgets/QWidget>
#include <QtCore/QVector>
#include <QWidget>
#include <QGraphicsItem>
#include <QPointF>
#include <QString>
#include <QtCore/QString>
namespace Seer {
namespace PSV {
namespace Seer::PSV {
class Frame {
public:
@@ -72,28 +68,22 @@ namespace PSV {
typedef QVector<Thread> Threads;
typedef QVector<int> ThreadIds;
struct StackNode {
QString function; // empty == root
int depth = 0;
QVector<Thread *> threads; // non-owning pointers
QVector<std::shared_ptr<StackNode>> children;
QString function; // empty == root
int depth = 0;
QVector<Thread*> threads; // non-owning pointers
QVector<std::shared_ptr<StackNode>> children;
};
// Build the parallel-stacks tree from a flat list of threads.
std::shared_ptr<StackNode> buildParallelStacks(QVector<Thread> &threads);
// Flat "Stack" representation used when building the graph.
struct Stack {
QVector<QString> functions;
QVector<std::shared_ptr<Stack>> stacks;
int threadCount = 0;
QVector<QString> threadIds; // IDs of every thread in this node
QVector<QString> functions;
QVector<std::shared_ptr<Stack>> stacks;
int threadCount = 0;
QVector<QString> threadIds; // IDs of every thread in this node
};
std::shared_ptr<Stack> fillStack(const std::shared_ptr<StackNode> &node);
} // namespace PSV
} // namespace Seer
std::shared_ptr<StackNode> buildParallelStacks (QVector<Thread>& threads); // Build the parallel-stacks tree from a flat list of threads.
std::shared_ptr<Stack> fillStack (const std::shared_ptr<StackNode>& node);
}
+260 -259
View File
@@ -8,239 +8,231 @@
#include <algorithm>
#include <cmath>
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<Seer::PSV::Stack> &root)
{
m_scene->clear();
void SeerParallelStacksGraphicsView::setStack(const std::shared_ptr<Seer::PSV::Stack>& 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_ptr<Seer::PSV::S
addEdges(rootPN);
deleteTree(rootPN);
m_scene->setSceneRect(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<Seer::PSV::Stack> &stack,
PlacedNode *parentPN)
{
void SeerParallelStacksGraphicsView::buildPlacedTree(PlacedNode* pn, const std::shared_ptr<Seer::PSV::Stack>& 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;
}
+94 -100
View File
@@ -1,4 +1,5 @@
#pragma once
#include "SeerParallelStacksCommon.h"
#include <QWidget>
#include <QGraphicsView>
@@ -7,121 +8,114 @@
#include <QPointF>
#include <QVector>
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<Row> 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<Row> _rows;
qreal _width = 0;
qreal _height = 0;
QVector<LiveEdge *> 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<LiveEdge*> _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<Seer::PSV::Stack> &root);
protected:
void wheelEvent(QWheelEvent *event) override;
private:
struct PlacedNode {
std::shared_ptr<Seer::PSV::Stack> stack;
Seer::PSV::StackBoxItem *item = nullptr;
PlacedNode *parent = nullptr;
QVector<PlacedNode *> 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<Seer::PSV::Stack> &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<Seer::PSV::Stack>& root);
protected:
void wheelEvent (QWheelEvent* event) override;
private:
struct PlacedNode {
std::shared_ptr<Seer::PSV::Stack> stack;
Seer::PSV::StackBoxItem* item = nullptr;
PlacedNode* parent = nullptr;
QVector<PlacedNode*> children;
qreal cx = 0;
qreal cy = 0;
};
void buildPlacedTree (PlacedNode* pn, const std::shared_ptr<Seer::PSV::Stack>& 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;
};
@@ -174,8 +174,10 @@ void SeerParallelStacksVisualizerWidget::createDirectedGraph() {
// Build parallel-stacks tree
QVector<Seer::PSV::Thread> local = _threads; // mutable copy for ptr stability
auto root = Seer::PSV::buildParallelStacks(local);
auto stack = Seer::PSV::fillStack(root);
graphicsView->setStack(stack);
}