GraphView fixes (#214)

This commit is contained in:
Thomas (nezza-_-) Roth 2017-12-14 22:07:48 +01:00 committed by Maijin
parent 273c87eaba
commit ce2557ccbe
5 changed files with 335 additions and 167 deletions

View File

@ -102,6 +102,8 @@ Cutter is developed on OS X, Linux and Windows. The first release for users will
| + | Zoom in |
| - | Zoom out |
| = | Reset zoom |
| J | Next instruction |
| K | Previous instruction |
## Help

View File

@ -17,7 +17,7 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent)
mFontMetrics(nullptr),
mMenu(new DisassemblyContextMenu(this))
{
this->highlight_token = nullptr;
highlight_token = nullptr;
// Signals that require a refresh all
connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshView()));
connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshView()));
@ -31,9 +31,9 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent)
connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(onSeekChanged(RVA)));
// Space to switch to disassembly
QShortcut *disassemblyShortcut = new QShortcut(QKeySequence(Qt::Key_Space), this);
disassemblyShortcut->setContext(Qt::WidgetShortcut);
connect(disassemblyShortcut, &QShortcut::activated, this, []{
QShortcut *shortcut_disassembly = new QShortcut(QKeySequence(Qt::Key_Space), this);
shortcut_disassembly->setContext(Qt::WidgetShortcut);
connect(shortcut_disassembly, &QShortcut::activated, this, []{
Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Disassembly);
Core()->triggerRaisePrioritizedMemoryWidget();
});
@ -61,15 +61,39 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent)
shortcut_take_false->setContext(Qt::WidgetShortcut);
connect(shortcut_take_false, SIGNAL(activated()), this, SLOT(takeFalse()));
// Navigation shortcuts
QShortcut *shortcut_next_instr = new QShortcut(QKeySequence(Qt::Key_J), this);
shortcut_next_instr->setContext(Qt::WidgetShortcut);
connect(shortcut_next_instr, SIGNAL(activated()), this, SLOT(nextInstr()));
QShortcut *shortcut_prev_instr = new QShortcut(QKeySequence(Qt::Key_K), this);
shortcut_prev_instr->setContext(Qt::WidgetShortcut);
connect(shortcut_prev_instr, SIGNAL(activated()), this, SLOT(prevInstr()));
shortcuts.append(shortcut_disassembly);
shortcuts.append(shortcut_escape);
shortcuts.append(shortcut_zoom_in);
shortcuts.append(shortcut_zoom_out);
shortcuts.append(shortcut_zoom_reset);
shortcuts.append(shortcut_next_instr);
shortcuts.append(shortcut_prev_instr);
initFont();
colorsUpdatedSlot();
}
DisassemblerGraphView::~DisassemblerGraphView()
{
for(QShortcut *shortcut : shortcuts)
{
delete shortcut;
}
}
void DisassemblerGraphView::refreshView()
{
initFont();
loadCurrentGraph();
this->viewport()->update();
viewport()->update();
}
void DisassemblerGraphView::loadCurrentGraph()
@ -78,7 +102,7 @@ void DisassemblerGraphView::loadCurrentGraph()
QJsonArray functions = functionsDoc.array();
disassembly_blocks.clear();
this->blocks.clear();
blocks.clear();
Analysis anal;
anal.ready = true;
@ -95,7 +119,7 @@ void DisassemblerGraphView::loadCurrentGraph()
{
windowTitle += " (" + funcName + ")";
}
this->parentWidget()->setWindowTitle(windowTitle);
parentWidget()->setWindowTitle(windowTitle);
RVA entry = func["offset"].toVariant().toULongLong();
@ -157,7 +181,13 @@ void DisassemblerGraphView::loadCurrentGraph()
if(func["blocks"].toArray().size() > 0)
{
computeGraph(entry);
this->viewport()->update();
viewport()->update();
if(first_draw)
{
showBlock(blocks[entry]);
first_draw = false;
}
}
}
@ -187,20 +217,20 @@ void DisassemblerGraphView::prepareGraphNode(GraphBlock &block)
height += 1;
}
}
int extra = 4 * this->charWidth + 4;
block.width = width + extra + this->charWidth;
block.height = (height * this->charHeight) + extra;
int extra = 4 * charWidth + 4;
block.width = width + extra + charWidth;
block.height = (height * charHeight) + extra;
}
void DisassemblerGraphView::initFont()
{
setFont(Config()->getFont());
QFontMetricsF metrics(this->font());
this->baseline = int(metrics.ascent());
this->charWidth = metrics.width('X');
this->charHeight = metrics.height();
this->charOffset = 0;
QFontMetricsF metrics(font());
baseline = int(metrics.ascent());
charWidth = metrics.width('X');
charHeight = metrics.height();
charOffset = 0;
if(mFontMetrics)
delete mFontMetrics;
mFontMetrics = new CachedFontMetrics(this, font());
@ -257,7 +287,7 @@ void DisassemblerGraphView::drawBlock(QPainter & p, GraphView::GraphBlock &block
// Draw different background for selected instruction
if(selected_instruction != RVA_INVALID)
{
int y = block.y + (2 * this->charWidth) + (db.header_text.lines.size() * this->charHeight);
int y = block.y + (2 * charWidth) + (db.header_text.lines.size() * charHeight);
for(Instr & instr : db.instrs)
{
auto selected = instr.addr == selected_instruction;
@ -265,13 +295,13 @@ void DisassemblerGraphView::drawBlock(QPainter & p, GraphView::GraphBlock &block
auto traceCount = 0;
if(selected && traceCount)
{
p.fillRect(QRect(block.x + this->charWidth, y, block.width - (10 + 2 * this->charWidth),
int(instr.text.lines.size()) * this->charHeight), disassemblyTracedSelectionColor);
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth),
int(instr.text.lines.size()) * charHeight), disassemblyTracedSelectionColor);
}
else if(selected)
{
p.fillRect(QRect(block.x + this->charWidth, y, block.width - (10 + 2 * this->charWidth),
int(instr.text.lines.size()) * this->charHeight), disassemblySelectionColor);
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth),
int(instr.text.lines.size()) * charHeight), disassemblySelectionColor);
}
else if(traceCount)
{
@ -285,40 +315,40 @@ void DisassemblerGraphView::drawBlock(QPainter & p, GraphView::GraphBlock &block
if(disassemblyTracedColor.blue() > 160)
colorDiff *= -1;
p.fillRect(QRect(block.x + this->charWidth, y, block.width - (10 + 2 * this->charWidth), int(instr.text.lines.size()) * this->charHeight),
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth), int(instr.text.lines.size()) * charHeight),
QColor(disassemblyTracedColor.red(),
disassemblyTracedColor.green(),
std::max(0, std::min(256, disassemblyTracedColor.blue() + colorDiff))));
}
y += int(instr.text.lines.size()) * this->charHeight;
y += int(instr.text.lines.size()) * charHeight;
}
}
// Render node text
auto x = block.x + (2 * this->charWidth);
int y = block.y + (2 * this->charWidth);
auto x = block.x + (2 * charWidth);
int y = block.y + (2 * charWidth);
for(auto & line : db.header_text.lines)
{
RichTextPainter::paintRichText(&p, x, y, block.width, this->charHeight, 0, line, mFontMetrics);
y += this->charHeight;
RichTextPainter::paintRichText(&p, x, y, block.width, charHeight, 0, line, mFontMetrics);
y += charHeight;
}
for(Instr & instr : db.instrs)
{
for(auto & line : instr.text.lines)
{
int rectSize = qRound(this->charWidth);
int rectSize = qRound(charWidth);
if(rectSize % 2)
{
rectSize++;
}
// Assume charWidth <= charHeight
QRectF bpRect(x - rectSize / 3.0, y + (this->charHeight - rectSize) / 2.0, rectSize, rectSize);
QRectF bpRect(x - rectSize / 3.0, y + (charHeight - rectSize) / 2.0, rectSize, rectSize);
// TODO: Breakpoint/Cip stuff
RichTextPainter::paintRichText(&p, x + this->charWidth, y, block.width - this->charWidth, this->charHeight, 0, line, mFontMetrics);
y += this->charHeight;
RichTextPainter::paintRichText(&p, x + charWidth, y, block.width - charWidth, charHeight, 0, line, mFontMetrics);
y += charHeight;
}
}
@ -348,7 +378,13 @@ GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView:
RVA DisassemblerGraphView::getInstrForMouseEvent(GraphBlock &block, QPoint* point)
{
DisassemblyBlock &db = disassembly_blocks[block.entry];
int mouse_row = ((point->y()-(2*this->charWidth)) / this->charHeight);
// Remove header and margin
int off_y = (2 * charWidth) + (db.header_text.lines.size() * charHeight);
// Get mouse coordinate over the actual text
int text_point_y = point->y() - off_y;
int mouse_row = text_point_y / charHeight;
int cur_row = db.header_text.lines.size();
if (mouse_row < cur_row)
{
@ -389,7 +425,7 @@ void DisassemblerGraphView::colorsUpdatedSlot()
void DisassemblerGraphView::fontsUpdatedSlot()
{
this->initFont();
initFont();
refreshView();
}
@ -440,26 +476,26 @@ void DisassemblerGraphView::onSeekChanged(RVA addr)
void DisassemblerGraphView::zoomIn()
{
current_scale += 0.1;
auto areaSize = this->viewport()->size();
this->adjustSize(areaSize.width(), areaSize.height());
this->viewport()->update();
auto areaSize = viewport()->size();
adjustSize(areaSize.width(), areaSize.height());
viewport()->update();
}
void DisassemblerGraphView::zoomOut()
{
current_scale -= 0.1;
current_scale = std::max(current_scale, 0.3);
auto areaSize = this->viewport()->size();
this->adjustSize(areaSize.width(), areaSize.height());
this->viewport()->update();
auto areaSize = viewport()->size();
adjustSize(areaSize.width(), areaSize.height());
viewport()->update();
}
void DisassemblerGraphView::zoomReset()
{
current_scale = 1.0;
auto areaSize = this->viewport()->size();
this->adjustSize(areaSize.width(), areaSize.height());
this->viewport()->update();
auto areaSize = viewport()->size();
adjustSize(areaSize.width(), areaSize.height());
viewport()->update();
}
void DisassemblerGraphView::takeTrue()
@ -488,13 +524,52 @@ void DisassemblerGraphView::takeFalse()
}
}
void DisassemblerGraphView::seekInstruction(bool previous_instr)
{
RVA addr = Core()->getOffset();
DisassemblyBlock *db = blockForAddress(addr);
if(!db)
{
return;
}
for(size_t i=0; i < db->instrs.size(); i++)
{
Instr &instr = db->instrs[i];
if(!((instr.addr <= addr) && (addr <= instr.addr + instr.size)))
{
continue;
}
// Found the instructon. Check if a next one exists
if(!previous_instr && (i < db->instrs.size()-1))
{
seek(db->instrs[i+1].addr, true);
}
else if(previous_instr && (i > 0))
{
seek(db->instrs[i-1].addr);
}
}
}
void DisassemblerGraphView::nextInstr()
{
seekInstruction(false);
}
void DisassemblerGraphView::prevInstr()
{
seekInstruction(true);
}
void DisassemblerGraphView::seek(RVA addr, bool update_viewport)
{
sent_seek = true;
Core()->seek(addr);
if(update_viewport)
{
this->viewport()->update();
viewport()->update();
}
}
@ -505,11 +580,10 @@ void DisassemblerGraphView::seekPrev()
void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos)
{
RVA instr = this->getInstrForMouseEvent(block, &pos);
RVA instr = getInstrForMouseEvent(block, &pos);
if(instr == RVA_INVALID)
{
return;
//
}
seek(instr, true);
@ -521,6 +595,23 @@ void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEve
}
}
void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos)
{
RVA instr = getInstrForMouseEvent(block, &pos);
if(instr == RVA_INVALID)
{
return;
}
QList<XrefDescription> refs = Core()->getXRefs(instr, false, false);
if (refs.length()) {
sent_seek = false;
Core()->seek(refs.at(0).to);
}
if (refs.length() > 1) {
qWarning() << "Too many references here. Weird behaviour expected.";
}
}
void DisassemblerGraphView::blockTransitionedTo(GraphView::GraphBlock *to)
{
if(transition_dont_seek)

View File

@ -5,6 +5,7 @@
#include <QWidget>
#include <QPainter>
#include <QShortcut>
#include "widgets/GraphView.h"
#include "menus/DisassemblyContextMenu.h"
@ -136,9 +137,11 @@ class DisassemblerGraphView : public GraphView
public:
DisassemblerGraphView(QWidget *parent);
~DisassemblerGraphView();
std::unordered_map<ut64, DisassemblyBlock> disassembly_blocks;
virtual void drawBlock(QPainter & p, GraphView::GraphBlock &block) override;
virtual void blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) override;
virtual void blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) override;
virtual GraphView::EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to) override;
virtual void blockTransitionedTo(GraphView::GraphBlock *to) override;
@ -157,10 +160,14 @@ public slots:
void takeTrue();
void takeFalse();
void nextInstr();
void prevInstr();
private slots:
void seekPrev();
private:
bool first_draw = true;
bool transition_dont_seek = false;
bool sent_seek = false;
@ -168,7 +175,7 @@ private:
// Font data
CachedFontMetrics* mFontMetrics;
qreal charWidth;
qreal charHeight;
int charHeight;
int charOffset;
int baseline;
@ -179,6 +186,9 @@ private:
RVA getInstrForMouseEvent(GraphBlock &block, QPoint* point);
DisassemblyBlock *blockForAddress(RVA addr);
void seek(RVA addr, bool update_viewport=true);
void seekInstruction(bool previous_instr);
QList<QShortcut*> shortcuts;
QColor disassemblyBackgroundColor;
QColor disassemblySelectedBackgroundColor;

View File

@ -9,12 +9,12 @@
GraphView::GraphView(QWidget *parent)
: QAbstractScrollArea(parent)
{
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
this->horizontalScrollBar()->setSingleStep(this->charWidth);
this->verticalScrollBar()->setSingleStep(this->charWidth);
QSize areaSize = this->viewport()->size();
this->adjustSize(areaSize.width(), areaSize.height());
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
horizontalScrollBar()->setSingleStep(charWidth);
verticalScrollBar()->setSingleStep(charWidth);
QSize areaSize = viewport()->size();
adjustSize(areaSize.width(), areaSize.height());
}
GraphView::~GraphView()
@ -53,6 +53,14 @@ void GraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, Q
qWarning() << "Block clicked not overridden!";
}
void GraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos)
{
Q_UNUSED(block);
Q_UNUSED(event);
Q_UNUSED(pos);
qWarning() << "Block double clicked not overridden!";
}
void GraphView::blockTransitionedTo(GraphView::GraphBlock *to)
{
Q_UNUSED(to);
@ -83,9 +91,9 @@ void GraphView::adjustSize(int new_width, int new_height)
//Update scroll bar information
horizontalScrollBar()->setPageStep(new_width);
horizontalScrollBar()->setRange(0, this->width - (new_width/current_scale));
horizontalScrollBar()->setRange(0, width - (new_width/current_scale));
verticalScrollBar()->setPageStep(new_height);
verticalScrollBar()->setRange(0, this->height - (new_height/current_scale));
verticalScrollBar()->setRange(0, height - (new_height/current_scale));
horizontalScrollBar()->setValue((int)((double)horizontalScrollBar()->maximum() * hfactor));
verticalScrollBar()->setValue((int)((double)verticalScrollBar()->maximum() * vfactor));
}
@ -93,15 +101,15 @@ void GraphView::adjustSize(int new_width, int new_height)
// This calculates the full graph starting at block entry.
void GraphView::computeGraph(ut64 entry)
{
QSize areaSize = this->viewport()->size();
QSize areaSize = viewport()->size();
// Populate incoming lists
for(auto &blockIt : this->blocks)
for(auto &blockIt : blocks)
{
GraphBlock &block = blockIt.second;
for(auto & edge : block.exits)
{
this->blocks[edge].incoming.push_back(block.entry);
blocks[edge].incoming.push_back(block.entry);
}
}
@ -119,7 +127,7 @@ void GraphView::computeGraph(ut64 entry)
// Pick nodes with single entrypoints
while(!queue.empty())
{
GraphBlock &block = this->blocks[queue.front()];
GraphBlock &block = blocks[queue.front()];
queue.pop();
block_order.push_back(block.entry);
for(ut64 edge : block.exits)
@ -131,22 +139,22 @@ void GraphView::computeGraph(ut64 entry)
}
// Some edges might not be available
if(!this->blocks.count(edge))
if(!blocks.count(edge))
{
continue;
}
// If this node has no other incoming edges, add it to the graph layout
if(this->blocks[edge].incoming.size() == 1)
if(blocks[edge].incoming.size() == 1)
{
removeFromVec(this->blocks[edge].incoming, block.entry);
removeFromVec(blocks[edge].incoming, block.entry);
block.new_exits.push_back(edge);
queue.push(this->blocks[edge].entry);
queue.push(blocks[edge].entry);
visited.insert(edge);
changed = true;
} else {
// Remove from incoming edges
removeFromVec(this->blocks[edge].incoming, block.entry);
removeFromVec(blocks[edge].incoming, block.entry);
}
}
}
@ -155,7 +163,7 @@ void GraphView::computeGraph(ut64 entry)
ut64 best = 0;
int best_edges;
ut64 best_parent;
for(auto & blockIt : this->blocks)
for(auto & blockIt : blocks)
{
GraphBlock &block = blockIt.second;
// Skip blocks we haven't visited yet
@ -170,24 +178,24 @@ void GraphView::computeGraph(ut64 entry)
{
continue;
}
if(!this->blocks.count(edge))
if(!blocks.count(edge))
{
continue;
}
// find best edge
if((best == 0) || ((int)this->blocks[edge].incoming.size() < best_edges) || (
((int)this->blocks[edge].incoming.size() == best_edges) && (edge < best)))
if((best == 0) || ((int)blocks[edge].incoming.size() < best_edges) || (
((int)blocks[edge].incoming.size() == best_edges) && (edge < best)))
{
best = edge;
best_edges = this->blocks[edge].incoming.size();
best_edges = blocks[edge].incoming.size();
best_parent = block.entry;
}
}
}
if(best != 0)
{
GraphBlock &best_parentb = this->blocks[best_parent];
removeFromVec(this->blocks[best].incoming, best_parentb.entry);
GraphBlock &best_parentb = blocks[best_parent];
removeFromVec(blocks[best].incoming, best_parentb.entry);
best_parentb.new_exits.push_back(best);
visited.insert(best);
queue.push(best);
@ -195,10 +203,10 @@ void GraphView::computeGraph(ut64 entry)
}
}
this->computeGraphLayout(this->blocks[entry]);
computeGraphLayout(blocks[entry]);
// Prepare edge routing
GraphBlock &entryb = this->blocks[entry];
GraphBlock &entryb = blocks[entry];
EdgesVector horiz_edges, vert_edges;
horiz_edges.resize(entryb.row_count + 1);
vert_edges.resize(entryb.row_count + 1);
@ -216,7 +224,7 @@ void GraphView::computeGraph(ut64 entry)
}
}
for(auto & blockIt : this->blocks)
for(auto & blockIt : blocks)
{
GraphBlock &block = blockIt.second;
edge_valid[block.row][block.col + 1] = false;
@ -229,8 +237,8 @@ void GraphView::computeGraph(ut64 entry)
GraphBlock &start = block;
for(ut64 edge : block.exits)
{
GraphBlock &end = this->blocks[edge];
start.edges.push_back(this->routeEdge(horiz_edges, vert_edges, edge_valid, start, end, QColor(255, 0, 0)));
GraphBlock &end = blocks[edge];
start.edges.push_back(routeEdge(horiz_edges, vert_edges, edge_valid, start, end, QColor(255, 0, 0)));
}
}
@ -254,7 +262,7 @@ void GraphView::computeGraph(ut64 entry)
std::vector<int> col_width, row_height;
initVec(col_width, entryb.col_count + 1, 0);
initVec(row_height, entryb.row_count + 1, 0);
for(auto & blockIt : this->blocks)
for(auto & blockIt : blocks)
{
GraphBlock &block = blockIt.second;
if((int(block.width / 2)) > col_width[block.col])
@ -269,47 +277,52 @@ void GraphView::computeGraph(ut64 entry)
std::vector<int> col_x, row_y;
initVec(col_x, entryb.col_count, 0);
initVec(row_y, entryb.row_count, 0);
initVec(this->col_edge_x, entryb.col_count + 1, 0);
initVec(this->row_edge_y, entryb.row_count + 1, 0);
int x = 16;
initVec(col_edge_x, entryb.col_count + 1, 0);
initVec(row_edge_y, entryb.row_count + 1, 0);
int x = block_horizontal_margin * 2;
for(int i = 0; i < entryb.col_count; i++)
{
this->col_edge_x[i] = x;
x += 8 * col_edge_count[i];
col_edge_x[i] = x;
x += block_horizontal_margin * col_edge_count[i];
col_x[i] = x;
x += col_width[i];
}
int y = 16;
int y = block_vertical_margin * 2;
for(int i = 0; i < entryb.row_count; i++)
{
this->row_edge_y[i] = y;
row_edge_y[i] = y;
// TODO: The 1 when row_edge_count is 0 is not needed on the original.. not sure why it's required for us
if(!row_edge_count[i])
{
row_edge_count[i] = 1;
}
y += block_vertical_margin * row_edge_count[i];
row_y[i] = y;
y += row_height[i];
}
this->col_edge_x[entryb.col_count] = x;
this->row_edge_y[entryb.row_count] = y;
this->width = x + 16 + (8 * col_edge_count[entryb.col_count]);
this->height = y + 16 + (8 * row_edge_count[entryb.row_count]);
col_edge_x[entryb.col_count] = x;
row_edge_y[entryb.row_count] = y;
width = x + (block_horizontal_margin * 2) + (block_horizontal_margin * col_edge_count[entryb.col_count]);
height = y + (block_vertical_margin * 2) + (block_vertical_margin * row_edge_count[entryb.row_count]);
//Compute node positions
for(auto & blockIt : this->blocks)
for(auto & blockIt : blocks)
{
GraphBlock &block = blockIt.second;
block.x = int(
(col_x[block.col] + col_width[block.col] + 4 * col_edge_count[block.col + 1]) - (block.width / 2));
(col_x[block.col] + col_width[block.col] + ((block_horizontal_margin / 2) * col_edge_count[block.col + 1])) - (block.width / 2));
if((block.x + block.width) > (
col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[
col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + block_horizontal_margin * col_edge_count[
block.col + 1]))
{
block.x = int((col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[
block.x = int((col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + block_horizontal_margin * col_edge_count[
block.col + 1]) - block.width);
}
block.y = row_y[block.row];
}
// Precompute coordinates for edges
for(auto & blockIt : this->blocks)
for(auto & blockIt : blocks)
{
GraphBlock &block = blockIt.second;
@ -318,7 +331,8 @@ void GraphView::computeGraph(ut64 entry)
auto start = edge.points[0];
auto start_col = start.col;
auto last_index = edge.start_index;
auto first_pt = QPoint(this->col_edge_x[start_col] + (8 * last_index) + 4,
// This is the start point of the edge.
auto first_pt = QPoint(col_edge_x[start_col] + (block_horizontal_margin * last_index) + (block_horizontal_margin / 2),
block.y + block.height);
auto last_pt = first_pt;
QPolygonF pts;
@ -333,9 +347,9 @@ void GraphView::computeGraph(ut64 entry)
QPoint new_pt;
// block_vertical_margin/2 gives the margin from block to the horizontal lines
if(start_col == end_col)
new_pt = QPoint(last_pt.x(), this->row_edge_y[end_row] + (8 * last_index) + (block_vertical_margin/2));
new_pt = QPoint(last_pt.x(), row_edge_y[end_row] + (block_vertical_margin * last_index) + (block_vertical_margin/2));
else
new_pt = QPoint(this->col_edge_x[end_col] + (8 * last_index) + 4, last_pt.y());
new_pt = QPoint(col_edge_x[end_col] + (block_horizontal_margin * last_index) + (block_horizontal_margin/2), last_pt.y());
pts.push_back(new_pt);
last_pt = new_pt;
start_col = end_col;
@ -355,42 +369,64 @@ void GraphView::computeGraph(ut64 entry)
pts.append(first_pt);
edge.arrow_start = pts;
}
pts.clear();
pts.append(QPoint(new_pt.x() - 3, new_pt.y() - 6));
pts.append(QPoint(new_pt.x() + 3, new_pt.y() - 6));
pts.append(new_pt);
edge.arrow_end = pts;
if(ec.end_arrow)
{
pts.clear();
pts.append(QPoint(new_pt.x() - 3, new_pt.y() - 6));
pts.append(QPoint(new_pt.x() + 3, new_pt.y() - 6));
pts.append(new_pt);
edge.arrow_end = pts;
}
}
}
this->ready = true;
ready = true;
this->viewport()->update();
areaSize = this->viewport()->size();
this->adjustSize(areaSize.width(), areaSize.height());
viewport()->update();
areaSize = viewport()->size();
adjustSize(areaSize.width(), areaSize.height());
}
void GraphView::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter p(this->viewport());
int render_offset_x = -this->horizontalScrollBar()->value() * current_scale;
int render_offset_y = -this->verticalScrollBar()->value() * current_scale;
int render_width = this->viewport()->size().width() / current_scale;
int render_height = this->viewport()->size().height() / current_scale;
QPainter p(viewport());
int render_offset_x = -horizontalScrollBar()->value() * current_scale;
int render_offset_y = -verticalScrollBar()->value() * current_scale;
int render_width = viewport()->size().width() / current_scale;
int render_height = viewport()->size().height() / current_scale;
// Do we have scrollbars?
bool hscrollbar = horizontalScrollBar()->pageStep() < width;
bool vscrollbar = verticalScrollBar()->pageStep() < height;
// Draw background
QRect viewportRect(this->viewport()->rect().topLeft(), this->viewport()->rect().bottomRight() - QPoint(1, 1));
QRect viewportRect(viewport()->rect().topLeft(), viewport()->rect().bottomRight() - QPoint(1, 1));
p.setBrush(backgroundColor);
p.drawRect(viewportRect);
p.setBrush(Qt::black);
unscrolled_render_offset_x = 0;
unscrolled_render_offset_y = 0;
// We do not have a scrollbar on this axis, so we center the view
if(!hscrollbar)
{
unscrolled_render_offset_x = (viewport()->size().width() - (width * current_scale)) / 2;
render_offset_x += unscrolled_render_offset_x;
}
if(!vscrollbar)
{
unscrolled_render_offset_y = (viewport()->size().height() - (height * current_scale)) / 2;
render_offset_y += unscrolled_render_offset_y;
}
p.translate(render_offset_x, render_offset_y);
p.scale(current_scale, current_scale);
// Draw blocks
for(auto & blockIt : this->blocks)
for(auto & blockIt : blocks)
{
GraphBlock &block = blockIt.second;
@ -429,7 +465,6 @@ void GraphView::paintEvent(QPaintEvent* event)
p.drawConvexPolygon(edge.arrow_end);
}
}
}
}
@ -446,34 +481,34 @@ void GraphView::computeGraphLayout(GraphBlock &block)
for(size_t i = 0; i < block.new_exits.size(); i++)
{
ut64 edge = block.new_exits[i];
GraphBlock &edgeb = this->blocks[edge];
this->computeGraphLayout(edgeb);
GraphBlock &edgeb = blocks[edge];
computeGraphLayout(edgeb);
row_count = std::max(edgeb.row_count + 1, row_count);
childColumn = edgeb.col;
}
if(this->layoutType != LayoutType::Wide && block.new_exits.size() == 2)
if(layoutType != LayoutType::Wide && block.new_exits.size() == 2)
{
GraphBlock &left = this->blocks[block.new_exits[0]];
GraphBlock &right= this->blocks[block.new_exits[1]];
GraphBlock &left = blocks[block.new_exits[0]];
GraphBlock &right= blocks[block.new_exits[1]];
if(left.new_exits.size() == 0)
{
left.col = right.col - 2;
int add = left.col < 0 ? - left.col : 0;
this->adjustGraphLayout(right, add, 1);
this->adjustGraphLayout(left, add, 1);
adjustGraphLayout(right, add, 1);
adjustGraphLayout(left, add, 1);
col = right.col_count + add;
}
else if(right.new_exits.size() == 0)
{
this->adjustGraphLayout(left, 0, 1);
this->adjustGraphLayout(right, left.col + 2, 1);
adjustGraphLayout(left, 0, 1);
adjustGraphLayout(right, left.col + 2, 1);
col = std::max(left.col_count, right.col + 2);
}
else
{
this->adjustGraphLayout(left, 0, 1);
this->adjustGraphLayout(right, left.col_count, 1);
adjustGraphLayout(left, 0, 1);
adjustGraphLayout(right, left.col_count, 1);
col = left.col_count + right.col_count;
}
block.col_count = std::max(2, col);
@ -490,8 +525,8 @@ void GraphView::computeGraphLayout(GraphBlock &block)
{
for(ut64 edge : block.new_exits)
{
this->adjustGraphLayout(this->blocks[edge], col, 1);
col += this->blocks[edge].col_count;
adjustGraphLayout(blocks[edge], col, 1);
col += blocks[edge].col_count;
}
if(col >= 2)
{
@ -536,11 +571,11 @@ GraphView::GraphEdge GraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector
int i = 0;
while(true)
{
if(!this->isEdgeMarked(vert_edges, start.row + 1, start.col + 1, i))
if(!isEdgeMarked(vert_edges, start.row + 1, start.col + 1, i))
break;
i += 1;
}
this->markEdge(vert_edges, start.row + 1, start.col + 1, i);
markEdge(vert_edges, start.row + 1, start.col + 1, i);
edge.addPoint(start.row + 1, start.col + 1);
edge.start_index = i;
bool horiz = false;
@ -617,7 +652,7 @@ GraphView::GraphEdge GraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector
min_col = start.col + 1;
max_col = col;
}
int index = this->findHorizEdgeIndex(horiz_edges, start.row + 1, min_col, max_col);
int index = findHorizEdgeIndex(horiz_edges, start.row + 1, min_col, max_col);
edge.addPoint(start.row + 1, col, index);
horiz = true;
}
@ -626,8 +661,8 @@ GraphView::GraphEdge GraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector
{
//Not in same row, need to generate a line for moving to the correct row
if(col == (start.col + 1))
this->markEdge(vert_edges, start.row + 1, start.col + 1, i, false);
int index = this->findVertEdgeIndex(vert_edges, col, min_row, max_row);
markEdge(vert_edges, start.row + 1, start.col + 1, i, false);
int index = findVertEdgeIndex(vert_edges, col, min_row, max_row);
if(col == (start.col + 1))
edge.start_index = index;
edge.addPoint(end.row, col, index);
@ -648,7 +683,7 @@ GraphView::GraphEdge GraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector
min_col = end.col + 1;
max_col = col;
}
int index = this->findHorizEdgeIndex(horiz_edges, end.row, min_col, max_col);
int index = findHorizEdgeIndex(horiz_edges, end.row, min_col, max_col);
edge.addPoint(end.row, end.col + 1, index);
horiz = true;
}
@ -656,7 +691,7 @@ GraphView::GraphEdge GraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector
//If last line was horizontal, choose the ending edge index for the incoming edge
if(horiz)
{
int index = this->findVertEdgeIndex(vert_edges, end.col + 1, end.row, end.row);
int index = findVertEdgeIndex(vert_edges, end.col + 1, end.row, end.row);
edge.points[int(edge.points.size()) - 1].index = index;
}
@ -683,7 +718,7 @@ int GraphView::findHorizEdgeIndex(EdgesVector & edges, int row, int min_col, int
//Mark chosen index as used
for(int col = min_col; col < max_col + 1; col++)
this->markEdge(edges, row, col, i);
markEdge(edges, row, col, i);
return i;
}
@ -707,7 +742,7 @@ int GraphView::findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int
//Mark chosen index as used
for(int row = min_row; row < max_row + 1; row++)
this->markEdge(edges, row, col, i);
markEdge(edges, row, col, i);
return i;
}
@ -730,30 +765,30 @@ void GraphView::showBlock(GraphBlock *block, bool animated)
target_x = std::max(0, target_x);
target_y = std::max(0, target_y);
target_x = std::min(this->horizontalScrollBar()->maximum(), target_x);
target_y = std::min(this->verticalScrollBar()->maximum(), target_y);
target_x = std::min(horizontalScrollBar()->maximum(), target_x);
target_y = std::min(verticalScrollBar()->maximum(), target_y);
if(animated)
{
QPropertyAnimation *animation_x = new QPropertyAnimation(this->horizontalScrollBar(), "value");
QPropertyAnimation *animation_x = new QPropertyAnimation(horizontalScrollBar(), "value");
animation_x->setDuration(500);
animation_x->setStartValue(this->horizontalScrollBar()->value());
animation_x->setStartValue(horizontalScrollBar()->value());
animation_x->setEndValue(target_x);
animation_x->setEasingCurve(QEasingCurve::InOutQuad);
animation_x->start();
QPropertyAnimation *animation_y = new QPropertyAnimation(this->verticalScrollBar(), "value");
QPropertyAnimation *animation_y = new QPropertyAnimation(verticalScrollBar(), "value");
animation_y->setDuration(500);
animation_y->setStartValue(this->verticalScrollBar()->value());
animation_y->setStartValue(verticalScrollBar()->value());
animation_y->setEndValue(target_y);
animation_y->setEasingCurve(QEasingCurve::InOutQuad);
animation_y->start();
} else {
this->horizontalScrollBar()->setValue(target_x);
this->verticalScrollBar()->setValue(target_y);
horizontalScrollBar()->setValue(target_x);
verticalScrollBar()->setValue(target_y);
}
blockTransitionedTo(block);
this->viewport()->update();
viewport()->update();
}
void GraphView::adjustGraphLayout(GraphBlock &block, int col, int row)
@ -762,18 +797,18 @@ void GraphView::adjustGraphLayout(GraphBlock &block, int col, int row)
block.row += row;
for(ut64 edge : block.new_exits)
{
this->adjustGraphLayout(this->blocks[edge], col, row);
adjustGraphLayout(blocks[edge], col, row);
}
}
void GraphView::addBlock(GraphView::GraphBlock block)
{
this->blocks[block.entry] = block;
blocks[block.entry] = block;
}
void GraphView::setEntry(ut64 e)
{
this->entry = e;
entry = e;
}
bool GraphView::checkPointClicked(QPointF &point, int x, int y, bool above_y)
@ -782,7 +817,7 @@ bool GraphView::checkPointClicked(QPointF &point, int x, int y, bool above_y)
if((point.x() - half_target_size < x) &&
(point.y() - (above_y ? (2 * half_target_size) : 0) < y) &&
(x < point.x() + half_target_size) &&
(y < point.y() + (above_y ? 0 : (2 * half_target_size))))
(y < point.y() + (above_y ? 0 : (3 * half_target_size))))
{
return true;
}
@ -798,8 +833,8 @@ void GraphView::resizeEvent(QResizeEvent* event)
// Mouse events
void GraphView::mousePressEvent(QMouseEvent *event)
{
int x = (event->pos().x() / current_scale) + horizontalScrollBar()->value();
int y = (event->pos().y() / current_scale) + verticalScrollBar()->value();
int x = ((event->pos().x() - unscrolled_render_offset_x) / current_scale) + horizontalScrollBar()->value();
int y = ((event->pos().y() - unscrolled_render_offset_y) / current_scale) + verticalScrollBar()->value();
// Check if a block was clicked
for(auto & blockIt : blocks)
@ -818,7 +853,7 @@ void GraphView::mousePressEvent(QMouseEvent *event)
}
// Check if a line beginning/end was clicked
for(auto & blockIt : this->blocks)
for(auto & blockIt : blocks)
{
GraphBlock &block = blockIt.second;
for(GraphEdge & edge : block.edges)
@ -850,25 +885,45 @@ void GraphView::mousePressEvent(QMouseEvent *event)
if(event->button() == Qt::LeftButton)
{
//Left click outside any block, enter scrolling mode
this->scroll_base_x = event->x();
this->scroll_base_y = event->y();
this->scroll_mode = true;
this->setCursor(Qt::ClosedHandCursor);
this->viewport()->grabMouse();
scroll_base_x = event->x();
scroll_base_y = event->y();
scroll_mode = true;
setCursor(Qt::ClosedHandCursor);
viewport()->grabMouse();
}
}
void GraphView::mouseMoveEvent(QMouseEvent* event)
{
if(this->scroll_mode)
if(scroll_mode)
{
int x_delta = this->scroll_base_x - event->x();
int y_delta = this->scroll_base_y - event->y();
this->scroll_base_x = event->x();
this->scroll_base_y = event->y();
this->horizontalScrollBar()->setValue(this->horizontalScrollBar()->value() + x_delta);
this->verticalScrollBar()->setValue(this->verticalScrollBar()->value() + y_delta);
int x_delta = scroll_base_x - event->x();
int y_delta = scroll_base_y - event->y();
scroll_base_x = event->x();
scroll_base_y = event->y();
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + x_delta);
verticalScrollBar()->setValue(verticalScrollBar()->value() + y_delta);
}
}
void GraphView::mouseDoubleClickEvent(QMouseEvent *event)
{
int x = ((event->pos().x() - unscrolled_render_offset_x) / current_scale) + horizontalScrollBar()->value();
int y = ((event->pos().y() - unscrolled_render_offset_y) / current_scale) + verticalScrollBar()->value();
// Check if a block was clicked
for(auto & blockIt : blocks)
{
GraphBlock &block = blockIt.second;
if((block.x <= x) && (block.y <= y) &&
(x <= block.x + block.width) & (y <= block.y + block.height))
{
QPoint pos = QPoint(x - block.x, y - block.y);
blockDoubleClicked(block, event, pos);
return;
}
}
}
@ -883,10 +938,10 @@ void GraphView::mouseReleaseEvent(QMouseEvent* event)
if(event->button() != Qt::LeftButton)
return;
if(this->scroll_mode)
if(scroll_mode)
{
this->scroll_mode = false;
this->setCursor(Qt::ArrowCursor);
this->viewport()->releaseMouse();
scroll_mode = false;
setCursor(Qt::ArrowCursor);
viewport()->releaseMouse();
}
}

View File

@ -6,6 +6,7 @@
#include <QWidget>
#include <QAbstractScrollArea>
#include <QScrollBar>
#include <QElapsedTimer>
#include <unordered_map>
#include <unordered_set>
@ -19,8 +20,8 @@ class GraphView : public QAbstractScrollArea
enum class LayoutType
{
Wide,
Medium,
Wide,
Narrow,
};
public:
@ -101,10 +102,17 @@ protected:
QColor backgroundColor = QColor(Qt::white);
// The vertical margin between blocks
int block_vertical_margin = 32;
int block_horizontal_margin = 10;
// Padding inside the block
int block_padding = 16;
// Zoom data
double current_scale = 1.0;
int unscrolled_render_offset_x = 0;
int unscrolled_render_offset_y = 0;
void addBlock(GraphView::GraphBlock block);
void setEntry(ut64 e);
void computeGraph(ut64 entry);
@ -112,6 +120,7 @@ protected:
// Callbacks that should be overridden
virtual void drawBlock(QPainter & p, GraphView::GraphBlock &block);
virtual void blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos);
virtual void blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos);
virtual void blockTransitionedTo(GraphView::GraphBlock *to);
virtual EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to);
@ -158,6 +167,7 @@ private slots:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
};