mirror of
https://github.com/epasveer/seer.git
synced 2026-08-29 08:34:42 +08:00
Clean up code to my style.
This commit is contained in:
@@ -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 ¤tFunction, 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user