mirror of
https://github.com/rizinorg/cutter.git
synced 2025-02-20 13:46:06 +00:00
Separate graph layout code from GraphView. (#1414)
* Separate graph layout code from GraphView. * Remove formatting and grid layout specific code from GraphLayout. * Copy edge styles from from main graph view to overview.
This commit is contained in:
parent
67f865af71
commit
cb51496e4f
@ -312,7 +312,8 @@ SOURCES += \
|
||||
dialogs/LinkTypeDialog.cpp \
|
||||
common/UpdateWorker.cpp \
|
||||
widgets/MemoryDockWidget.cpp \
|
||||
common/HighDpiPixmap.cpp
|
||||
common/HighDpiPixmap.cpp \
|
||||
widgets/GraphGridLayout.cpp
|
||||
|
||||
HEADERS += \
|
||||
core/Cutter.h \
|
||||
@ -430,7 +431,9 @@ HEADERS += \
|
||||
common/UpdateWorker.h \
|
||||
dialogs/LinkTypeDialog.h \
|
||||
widgets/MemoryDockWidget.h \
|
||||
common/HighDpiPixmap.h
|
||||
common/HighDpiPixmap.h \
|
||||
widgets/GraphLayout.h \
|
||||
widgets/GraphGridLayout.h
|
||||
|
||||
FORMS += \
|
||||
dialogs/AboutDialog.ui \
|
||||
|
@ -338,8 +338,9 @@ void MainWindow::disconnectOverview()
|
||||
|
||||
void MainWindow::setOverviewData()
|
||||
{
|
||||
overviewDock->getGraphView()->setData(targetGraphDock->getGraphView()->getWidth(),
|
||||
targetGraphDock->getGraphView()->getHeight(), targetGraphDock->getGraphView()->getBlocks());
|
||||
auto &mainGraphView = *targetGraphDock->getGraphView();
|
||||
overviewDock->getGraphView()->setData(mainGraphView.getWidth(), mainGraphView.getHeight(),
|
||||
mainGraphView.getBlocks(), mainGraphView.getEdgeConfigurations());
|
||||
}
|
||||
|
||||
bool MainWindow::isOverviewActive()
|
||||
|
@ -249,13 +249,13 @@ void DisassemblerGraphView::loadCurrentGraph()
|
||||
db.false_path = RVA_INVALID;
|
||||
if (block_fail) {
|
||||
db.false_path = block_fail;
|
||||
gb.exits.push_back(block_fail);
|
||||
gb.edges.push_back({block_fail});
|
||||
}
|
||||
if (block_jump) {
|
||||
if (block_fail) {
|
||||
db.true_path = block_jump;
|
||||
}
|
||||
gb.exits.push_back(block_jump);
|
||||
gb.edges.push_back({block_jump});
|
||||
}
|
||||
|
||||
QJsonObject switchOp = block["switchop"].toObject();
|
||||
@ -268,7 +268,7 @@ void DisassemblerGraphView::loadCurrentGraph()
|
||||
if (!ok) {
|
||||
continue;
|
||||
}
|
||||
gb.exits.push_back(caseJump);
|
||||
gb.edges.push_back({caseJump});
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,6 +319,17 @@ void DisassemblerGraphView::loadCurrentGraph()
|
||||
}
|
||||
}
|
||||
|
||||
DisassemblerGraphView::EdgeConfigurationMapping DisassemblerGraphView::getEdgeConfigurations()
|
||||
{
|
||||
EdgeConfigurationMapping result;
|
||||
for (auto &block : blocks) {
|
||||
for (const auto &edge : block.second.edges) {
|
||||
result[ {block.first, edge.target}] = edgeConfiguration(block.second, &blocks[edge.target]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void DisassemblerGraphView::prepareGraphNode(GraphBlock &block)
|
||||
{
|
||||
DisassemblyBlock &db = disassembly_blocks[block.entry];
|
||||
@ -733,8 +744,8 @@ void DisassemblerGraphView::takeTrue()
|
||||
|
||||
if (db->true_path != RVA_INVALID) {
|
||||
seekable->seek(db->true_path);
|
||||
} else if (!blocks[db->entry].exits.empty()) {
|
||||
seekable->seek(blocks[db->entry].exits[0]);
|
||||
} else if (!blocks[db->entry].edges.empty()) {
|
||||
seekable->seek(blocks[db->entry].edges[0].target);
|
||||
}
|
||||
}
|
||||
|
||||
@ -747,8 +758,8 @@ void DisassemblerGraphView::takeFalse()
|
||||
|
||||
if (db->false_path != RVA_INVALID) {
|
||||
seekable->seek(db->false_path);
|
||||
} else if (!blocks[db->entry].exits.empty()) {
|
||||
seekable->seek(blocks[db->entry].exits[0]);
|
||||
} else if (!blocks[db->entry].edges.empty()) {
|
||||
seekable->seek(blocks[db->entry].edges[0].target);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,8 @@ public:
|
||||
int getWidth() { return width; }
|
||||
int getHeight() { return height; }
|
||||
std::unordered_map<ut64, GraphBlock> getBlocks() { return blocks; }
|
||||
using EdgeConfigurationMapping = std::map<std::pair<ut64, ut64>, EdgeConfiguration>;
|
||||
EdgeConfigurationMapping getEdgeConfigurations();
|
||||
|
||||
public slots:
|
||||
void refreshView();
|
||||
|
523
src/widgets/GraphGridLayout.cpp
Normal file
523
src/widgets/GraphGridLayout.cpp
Normal file
@ -0,0 +1,523 @@
|
||||
#include "GraphGridLayout.h"
|
||||
|
||||
#include <unordered_set>
|
||||
#include <queue>
|
||||
|
||||
// Vector functions
|
||||
template<class T>
|
||||
static void removeFromVec(std::vector<T> &vec, T elem)
|
||||
{
|
||||
vec.erase(std::remove(vec.begin(), vec.end(), elem), vec.end());
|
||||
}
|
||||
|
||||
GraphGridLayout::GraphGridLayout(GraphGridLayout::LayoutType layoutType)
|
||||
: GraphLayout({})
|
||||
, layoutType(layoutType)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<ut64> GraphGridLayout::topoSort(LayoutState &state, unsigned long long entry)
|
||||
{
|
||||
auto &blocks = *state.blocks;
|
||||
// Populate incoming lists
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
for (auto &edge : block.edges) {
|
||||
state.grid_blocks[edge.target].incoming.push_back(blockIt.first);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<ut64> visited;
|
||||
visited.insert(entry);
|
||||
std::queue<ut64> queue;
|
||||
std::vector<ut64> block_order;
|
||||
queue.push(entry);
|
||||
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
|
||||
// Pick nodes with single entrypoints
|
||||
while (!queue.empty()) {
|
||||
GraphBlock &block = blocks[queue.front()];
|
||||
queue.pop();
|
||||
block_order.push_back(block.entry);
|
||||
for (const auto &edgeDescr : block.edges) {
|
||||
ut64 edge = edgeDescr.target;
|
||||
// Skip edge if we already visited it
|
||||
if (visited.count(edge)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some edges might not be available
|
||||
if (!blocks.count(edge)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this node has no other incoming edges, add it to the graph layout
|
||||
if (state.grid_blocks[edge].incoming.size() == 1) {
|
||||
removeFromVec(state.grid_blocks[edge].incoming, block.entry);
|
||||
state.grid_blocks[block.entry].tree_edge.push_back(edge);
|
||||
queue.push(blocks[edge].entry);
|
||||
visited.insert(edge);
|
||||
changed = true;
|
||||
} else {
|
||||
// Remove from incoming edges
|
||||
removeFromVec(state.grid_blocks[edge].incoming, block.entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No more nodes satisfy constraints, pick a node to continue constructing the graph
|
||||
ut64 best = 0;
|
||||
int best_edges;
|
||||
ut64 best_parent = 0;
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
// Skip blocks we haven't visited yet
|
||||
if (!visited.count(block.entry)) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &edgeDescr : block.edges) {
|
||||
ut64 edge = edgeDescr.target;
|
||||
// If we already visited the exit, skip it
|
||||
if (visited.count(edge)) {
|
||||
continue;
|
||||
}
|
||||
if (!blocks.count(edge)) {
|
||||
continue;
|
||||
}
|
||||
// find best edge
|
||||
if ((best == 0) || ((int)state.grid_blocks[edge].incoming.size() < best_edges) || (
|
||||
((int)state.grid_blocks[edge].incoming.size() == best_edges) && (edge < best))) {
|
||||
best = edge;
|
||||
best_edges = state.grid_blocks[edge].incoming.size();
|
||||
best_parent = block.entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best != 0) {
|
||||
auto &best_parentb = state.grid_blocks[best_parent];
|
||||
removeFromVec(state.grid_blocks[best].incoming, best_parent);
|
||||
best_parentb.tree_edge.push_back(best);
|
||||
visited.insert(best);
|
||||
queue.push(best);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return block_order;
|
||||
}
|
||||
|
||||
void GraphGridLayout::CalculateLayout(std::unordered_map<ut64, GraphBlock> &blocks, ut64 entry,
|
||||
int &width, int &height) const
|
||||
{
|
||||
LayoutState layoutState;
|
||||
layoutState.blocks = &blocks;
|
||||
|
||||
for (auto &it : blocks) {
|
||||
GridBlock block;
|
||||
block.id = it.first;
|
||||
layoutState.grid_blocks[it.first] = block;
|
||||
}
|
||||
|
||||
auto block_order = topoSort(layoutState, entry);
|
||||
computeBlockPlacement(entry, layoutState);
|
||||
|
||||
// Prepare edge routing
|
||||
auto &entryb = layoutState.grid_blocks[entry];
|
||||
EdgesVector horiz_edges, vert_edges;
|
||||
horiz_edges.resize(entryb.row_count + 1);
|
||||
vert_edges.resize(entryb.row_count + 1);
|
||||
Matrix<bool> edge_valid;
|
||||
edge_valid.resize(entryb.row_count + 1);
|
||||
for (int row = 0; row < entryb.row_count + 1; row++) {
|
||||
horiz_edges[row].resize(entryb.col_count + 1);
|
||||
vert_edges[row].resize(entryb.col_count + 1);
|
||||
edge_valid[row].assign(entryb.col_count + 1, true);
|
||||
}
|
||||
|
||||
for (auto &blockIt : layoutState.grid_blocks) {
|
||||
auto &block = blockIt.second;
|
||||
edge_valid[block.row][block.col + 1] = false;
|
||||
}
|
||||
|
||||
// Perform edge routing
|
||||
for (ut64 blockId : block_order) {
|
||||
GraphBlock &block = blocks[blockId];
|
||||
GridBlock &start = layoutState.grid_blocks[blockId];
|
||||
for (const auto &edge : block.edges) {
|
||||
GridBlock &end = layoutState.grid_blocks[edge.target];
|
||||
layoutState.edge[blockId].push_back(routeEdge(horiz_edges, vert_edges, edge_valid, start, end));
|
||||
}
|
||||
}
|
||||
|
||||
// Compute edge counts for each row and column
|
||||
std::vector<int> col_edge_count, row_edge_count;
|
||||
col_edge_count.assign(entryb.col_count + 1, 0);
|
||||
row_edge_count.assign(entryb.row_count + 1, 0);
|
||||
for (int row = 0; row < entryb.row_count + 1; row++) {
|
||||
for (int col = 0; col < entryb.col_count + 1; col++) {
|
||||
if (int(horiz_edges[row][col].size()) > row_edge_count[row])
|
||||
row_edge_count[row] = int(horiz_edges[row][col].size());
|
||||
if (int(vert_edges[row][col].size()) > col_edge_count[col])
|
||||
col_edge_count[col] = int(vert_edges[row][col].size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Compute row and column sizes
|
||||
std::vector<int> col_width, row_height;
|
||||
col_width.assign(entryb.col_count + 1, 0);
|
||||
row_height.assign(entryb.row_count + 1, 0);
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
GridBlock &grid_block = layoutState.grid_blocks[blockIt.first];
|
||||
if ((int(block.width / 2)) > col_width[grid_block.col])
|
||||
col_width[grid_block.col] = int(block.width / 2);
|
||||
if ((int(block.width / 2)) > col_width[grid_block.col + 1])
|
||||
col_width[grid_block.col + 1] = int(block.width / 2);
|
||||
if (int(block.height) > row_height[grid_block.row])
|
||||
row_height[grid_block.row] = int(block.height);
|
||||
}
|
||||
|
||||
// Compute row and column positions
|
||||
std::vector<int> col_x, row_y;
|
||||
col_x.assign(entryb.col_count, 0);
|
||||
row_y.assign(entryb.row_count, 0);
|
||||
std::vector<int> col_edge_x(entryb.col_count + 1);
|
||||
std::vector<int> row_edge_y(entryb.row_count + 1);
|
||||
int x = layoutConfig.block_horizontal_margin * 2;
|
||||
for (int i = 0; i < entryb.col_count; i++) {
|
||||
col_edge_x[i] = x;
|
||||
x += layoutConfig.block_horizontal_margin * col_edge_count[i];
|
||||
col_x[i] = x;
|
||||
x += col_width[i];
|
||||
}
|
||||
int y = layoutConfig.block_vertical_margin * 2;
|
||||
for (int i = 0; i < entryb.row_count; i++) {
|
||||
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 += layoutConfig.block_vertical_margin * row_edge_count[i];
|
||||
row_y[i] = y;
|
||||
y += row_height[i];
|
||||
}
|
||||
col_edge_x[entryb.col_count] = x;
|
||||
row_edge_y[entryb.row_count] = y;
|
||||
width = x + (layoutConfig.block_horizontal_margin * 2) + (layoutConfig.block_horizontal_margin *
|
||||
col_edge_count[entryb.col_count]);
|
||||
height = y + (layoutConfig.block_vertical_margin * 2) + (layoutConfig.block_vertical_margin *
|
||||
row_edge_count[entryb.row_count]);
|
||||
|
||||
//Compute node positions
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
GridBlock &grid_block = layoutState.grid_blocks[blockIt.first];
|
||||
auto column = grid_block.col;
|
||||
auto row = grid_block.row;
|
||||
block.x = int(col_x[column] + col_width[column] +
|
||||
((layoutConfig.block_horizontal_margin / 2) * col_edge_count[column + 1])
|
||||
- (block.width / 2));
|
||||
if ((block.x + block.width) > (
|
||||
col_x[column] + col_width[column] + col_width[column + 1] +
|
||||
layoutConfig.block_horizontal_margin *
|
||||
col_edge_count[column + 1])) {
|
||||
block.x = int((col_x[column] + col_width[column] + col_width[column + 1] +
|
||||
layoutConfig.block_horizontal_margin * col_edge_count[column + 1]) - block.width);
|
||||
}
|
||||
block.y = row_y[row];
|
||||
}
|
||||
|
||||
// Compute coordinates for edges
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
|
||||
size_t index = 0;
|
||||
assert(block.edges.size() == layoutState.edge[block.entry].size());
|
||||
for (GridEdge &edge : layoutState.edge[block.entry]) {
|
||||
auto start = edge.points[0];
|
||||
auto start_col = start.col;
|
||||
auto last_index = edge.start_index;
|
||||
// This is the start point of the edge.
|
||||
auto first_pt = QPoint(col_edge_x[start_col] + (layoutConfig.block_horizontal_margin * last_index) +
|
||||
(layoutConfig.block_horizontal_margin / 2),
|
||||
block.y + block.height);
|
||||
auto last_pt = first_pt;
|
||||
QPolygonF pts;
|
||||
pts.append(last_pt);
|
||||
|
||||
for (int i = 0; i < int(edge.points.size()); i++) {
|
||||
auto end = edge.points[i];
|
||||
auto end_row = end.row;
|
||||
auto end_col = end.col;
|
||||
auto last_index = end.index;
|
||||
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(), row_edge_y[end_row] + (layoutConfig.block_vertical_margin * last_index)
|
||||
+
|
||||
(layoutConfig.block_vertical_margin / 2));
|
||||
else
|
||||
new_pt = QPoint(col_edge_x[end_col] + (layoutConfig.block_horizontal_margin * last_index) +
|
||||
(layoutConfig.block_horizontal_margin / 2), last_pt.y());
|
||||
pts.push_back(new_pt);
|
||||
last_pt = new_pt;
|
||||
start_col = end_col;
|
||||
}
|
||||
|
||||
const auto &target = blocks[edge.dest];
|
||||
auto new_pt = QPoint(last_pt.x(), target.y - 1);
|
||||
pts.push_back(new_pt);
|
||||
block.edges[index].polyline = pts;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Prepare graph
|
||||
// This computes the position and (row/col based) size of the block
|
||||
// Recursively calls itself for each child of the GraphBlock
|
||||
void GraphGridLayout::computeBlockPlacement(ut64 blockId, LayoutState &layoutState) const
|
||||
{
|
||||
auto &block = layoutState.grid_blocks[blockId];
|
||||
auto &blocks = layoutState.grid_blocks;
|
||||
int col = 0;
|
||||
int row_count = 1;
|
||||
int childColumn = 0;
|
||||
bool singleChild = block.tree_edge.size() == 1;
|
||||
// Compute all children nodes
|
||||
for (size_t i = 0; i < block.tree_edge.size(); i++) {
|
||||
ut64 edge = block.tree_edge[i];
|
||||
auto &edgeb = blocks[edge];
|
||||
computeBlockPlacement(edge, layoutState);
|
||||
row_count = std::max(edgeb.row_count + 1, row_count);
|
||||
childColumn = edgeb.col;
|
||||
}
|
||||
|
||||
if (layoutType != LayoutType::Wide && block.tree_edge.size() == 2) {
|
||||
auto &left = blocks[block.tree_edge[0]];
|
||||
auto &right = blocks[block.tree_edge[1]];
|
||||
if (left.tree_edge.size() == 0) {
|
||||
left.col = right.col - 2;
|
||||
int add = left.col < 0 ? - left.col : 0;
|
||||
adjustGraphLayout(right, blocks, add, 1);
|
||||
adjustGraphLayout(left, blocks, add, 1);
|
||||
col = right.col_count + add;
|
||||
} else if (right.tree_edge.size() == 0) {
|
||||
adjustGraphLayout(left, blocks, 0, 1);
|
||||
adjustGraphLayout(right, blocks, left.col + 2, 1);
|
||||
col = std::max(left.col_count, right.col + 2);
|
||||
} else {
|
||||
adjustGraphLayout(left, blocks, 0, 1);
|
||||
adjustGraphLayout(right, blocks, left.col_count, 1);
|
||||
col = left.col_count + right.col_count;
|
||||
}
|
||||
block.col_count = std::max(2, col);
|
||||
if (layoutType == LayoutType::Medium) {
|
||||
block.col = (left.col + right.col) / 2;
|
||||
} else {
|
||||
block.col = singleChild ? childColumn : (col - 2) / 2;
|
||||
}
|
||||
} else {
|
||||
for (ut64 edge : block.tree_edge) {
|
||||
adjustGraphLayout(blocks[edge], blocks, col, 1);
|
||||
col += blocks[edge].col_count;
|
||||
}
|
||||
if (col >= 2) {
|
||||
// Place this node centered over the child nodes
|
||||
block.col = singleChild ? childColumn : (col - 2) / 2;
|
||||
block.col_count = col;
|
||||
} else {
|
||||
//No child nodes, set single node's width (nodes are 2 columns wide to allow
|
||||
//centering over a branch)
|
||||
block.col = 0;
|
||||
block.col_count = 2;
|
||||
}
|
||||
}
|
||||
block.row = 0;
|
||||
block.row_count = row_count;
|
||||
}
|
||||
|
||||
void GraphGridLayout::adjustGraphLayout(GridBlock &block,
|
||||
std::unordered_map<ut64, GridBlock> &blocks, int col, int row) const
|
||||
{
|
||||
block.col += col;
|
||||
block.row += row;
|
||||
for (ut64 edge : block.tree_edge) {
|
||||
adjustGraphLayout(blocks[edge], blocks, col, row);
|
||||
}
|
||||
}
|
||||
|
||||
// Edge computing stuff
|
||||
bool GraphGridLayout::isEdgeMarked(EdgesVector &edges, int row, int col, int index)
|
||||
{
|
||||
if (index >= int(edges[row][col].size()))
|
||||
return false;
|
||||
return edges[row][col][index];
|
||||
}
|
||||
|
||||
void GraphGridLayout::markEdge(EdgesVector &edges, int row, int col, int index, bool used)
|
||||
{
|
||||
while (int(edges[row][col].size()) <= index)
|
||||
edges[row][col].push_back(false);
|
||||
edges[row][col][index] = used;
|
||||
}
|
||||
|
||||
GraphGridLayout::GridEdge GraphGridLayout::routeEdge(EdgesVector &horiz_edges,
|
||||
EdgesVector &vert_edges,
|
||||
Matrix<bool> &edge_valid, GridBlock &start, GridBlock &end) const
|
||||
{
|
||||
GridEdge edge;
|
||||
edge.dest = end.id;
|
||||
|
||||
//Find edge index for initial outgoing line
|
||||
int i = 0;
|
||||
while (isEdgeMarked(vert_edges, start.row + 1, start.col + 1, i)) {
|
||||
i += 1;
|
||||
}
|
||||
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;
|
||||
|
||||
//Find valid column for moving vertically to the target node
|
||||
int min_row, max_row;
|
||||
if (end.row < (start.row + 1)) {
|
||||
min_row = end.row;
|
||||
max_row = start.row + 1;
|
||||
} else {
|
||||
min_row = start.row + 1;
|
||||
max_row = end.row;
|
||||
}
|
||||
int col = start.col + 1;
|
||||
if (min_row != max_row) {
|
||||
auto checkColumn = [min_row, max_row, &edge_valid](int column) {
|
||||
if (column < 0 || column >= int(edge_valid[min_row].size()))
|
||||
return false;
|
||||
for (int row = min_row; row < max_row; row++) {
|
||||
if (!edge_valid[row][column]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!checkColumn(col)) {
|
||||
if (checkColumn(end.col + 1)) {
|
||||
col = end.col + 1;
|
||||
} else {
|
||||
int ofs = 0;
|
||||
while (true) {
|
||||
col = start.col + 1 - ofs;
|
||||
if (checkColumn(col)) {
|
||||
break;
|
||||
}
|
||||
|
||||
col = start.col + 1 + ofs;
|
||||
if (checkColumn(col)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ofs += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (col != (start.col + 1)) {
|
||||
//Not in same column, need to generate a line for moving to the correct column
|
||||
int min_col, max_col;
|
||||
if (col < (start.col + 1)) {
|
||||
min_col = col;
|
||||
max_col = start.col + 1;
|
||||
} else {
|
||||
min_col = start.col + 1;
|
||||
max_col = col;
|
||||
}
|
||||
int index = findHorizEdgeIndex(horiz_edges, start.row + 1, min_col, max_col);
|
||||
edge.addPoint(start.row + 1, col, index);
|
||||
horiz = true;
|
||||
}
|
||||
|
||||
if (end.row != (start.row + 1)) {
|
||||
//Not in same row, need to generate a line for moving to the correct row
|
||||
if (col == (start.col + 1))
|
||||
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);
|
||||
horiz = false;
|
||||
}
|
||||
|
||||
if (col != (end.col + 1)) {
|
||||
//Not in ending column, need to generate a line for moving to the correct column
|
||||
int min_col, max_col;
|
||||
if (col < (end.col + 1)) {
|
||||
min_col = col;
|
||||
max_col = end.col + 1;
|
||||
} else {
|
||||
min_col = end.col + 1;
|
||||
max_col = col;
|
||||
}
|
||||
int index = findHorizEdgeIndex(horiz_edges, end.row, min_col, max_col);
|
||||
edge.addPoint(end.row, end.col + 1, index);
|
||||
horiz = true;
|
||||
}
|
||||
|
||||
//If last line was horizontal, choose the ending edge index for the incoming edge
|
||||
if (horiz) {
|
||||
int index = findVertEdgeIndex(vert_edges, end.col + 1, end.row, end.row);
|
||||
edge.points[int(edge.points.size()) - 1].index = index;
|
||||
}
|
||||
|
||||
return edge;
|
||||
}
|
||||
|
||||
|
||||
int GraphGridLayout::findHorizEdgeIndex(EdgesVector &edges, int row, int min_col, int max_col)
|
||||
{
|
||||
//Find a valid index
|
||||
int i = 0;
|
||||
while (true) {
|
||||
bool valid = true;
|
||||
for (int col = min_col; col < max_col + 1; col++)
|
||||
if (isEdgeMarked(edges, row, col, i)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if (valid)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
//Mark chosen index as used
|
||||
for (int col = min_col; col < max_col + 1; col++)
|
||||
markEdge(edges, row, col, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
int GraphGridLayout::findVertEdgeIndex(EdgesVector &edges, int col, int min_row, int max_row)
|
||||
{
|
||||
//Find a valid index
|
||||
int i = 0;
|
||||
while (true) {
|
||||
bool valid = true;
|
||||
for (int row = min_row; row < max_row + 1; row++)
|
||||
if (isEdgeMarked(edges, row, col, i)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if (valid)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
//Mark chosen index as used
|
||||
for (int row = min_row; row < max_row + 1; row++)
|
||||
markEdge(edges, row, col, i);
|
||||
return i;
|
||||
}
|
85
src/widgets/GraphGridLayout.h
Normal file
85
src/widgets/GraphGridLayout.h
Normal file
@ -0,0 +1,85 @@
|
||||
#ifndef GRAPHGRIDLAYOUT_H
|
||||
#define GRAPHGRIDLAYOUT_H
|
||||
|
||||
#include "core/Cutter.h"
|
||||
#include "GraphLayout.h"
|
||||
|
||||
class GraphGridLayout : public GraphLayout
|
||||
{
|
||||
public:
|
||||
enum class LayoutType {
|
||||
Medium,
|
||||
Wide,
|
||||
Narrow,
|
||||
};
|
||||
|
||||
GraphGridLayout(LayoutType layoutType = LayoutType::Medium);
|
||||
virtual void CalculateLayout(std::unordered_map<ut64, GraphBlock> &blocks,
|
||||
ut64 entry,
|
||||
int &width,
|
||||
int &height) const override;
|
||||
private:
|
||||
LayoutType layoutType;
|
||||
|
||||
struct GridBlock {
|
||||
ut64 id;
|
||||
std::vector<ut64> incoming;
|
||||
std::vector<ut64> tree_edge; // subset of outgoing edges that form a tree
|
||||
|
||||
// Number of rows in block
|
||||
int row_count = 0;
|
||||
// Number of columns in block
|
||||
int col_count = 0;
|
||||
// Column in which the block is
|
||||
int col = 0;
|
||||
// Row in which the block is
|
||||
int row = 0;
|
||||
};
|
||||
|
||||
struct Point {
|
||||
int row; //point[0]
|
||||
int col; //point[1]
|
||||
int index; //point[2]
|
||||
};
|
||||
|
||||
struct GridEdge {
|
||||
ut64 dest;
|
||||
std::vector<Point> points;
|
||||
int start_index = 0;
|
||||
QPolygonF polyline;
|
||||
|
||||
void addPoint(int row, int col, int index = 0)
|
||||
{
|
||||
Point point = {row, col, 0};
|
||||
this->points.push_back(point);
|
||||
if (int(this->points.size()) > 1)
|
||||
this->points[this->points.size() - 2].index = index;
|
||||
}
|
||||
};
|
||||
|
||||
struct LayoutState {
|
||||
std::unordered_map<ut64, GridBlock> grid_blocks;
|
||||
std::unordered_map<ut64, GraphBlock> *blocks = nullptr;
|
||||
std::unordered_map<ut64, std::vector<GridEdge>> edge;
|
||||
};
|
||||
|
||||
void computeBlockPlacement(ut64 blockId,
|
||||
LayoutState &layoutState) const;
|
||||
void adjustGraphLayout(GridBlock &block, std::unordered_map<ut64, GridBlock> &blocks,
|
||||
int col, int row) const;
|
||||
static std::vector<ut64> topoSort(LayoutState &state, ut64 entry);
|
||||
|
||||
// Edge computing stuff
|
||||
template<typename T>
|
||||
using Matrix = std::vector<std::vector<T>>;
|
||||
using EdgesVector = Matrix<std::vector<bool>>;
|
||||
|
||||
GridEdge routeEdge(EdgesVector &horiz_edges, EdgesVector &vert_edges,
|
||||
Matrix<bool> &edge_valid, GridBlock &start, GridBlock &end) const;
|
||||
static int findVertEdgeIndex(EdgesVector &edges, int col, int min_row, int max_row);
|
||||
static bool isEdgeMarked(EdgesVector &edges, int row, int col, int index);
|
||||
static void markEdge(EdgesVector &edges, int row, int col, int index, bool used = true);
|
||||
static int findHorizEdgeIndex(EdgesVector &edges, int row, int min_col, int max_col);
|
||||
};
|
||||
|
||||
#endif // GRAPHGRIDLAYOUT_H
|
40
src/widgets/GraphLayout.h
Normal file
40
src/widgets/GraphLayout.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef GRAPHLAYOUT_H
|
||||
#define GRAPHLAYOUT_H
|
||||
|
||||
#include "core/Cutter.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
class GraphLayout
|
||||
{
|
||||
public:
|
||||
struct GraphEdge {
|
||||
ut64 target;
|
||||
QPolygonF polyline;
|
||||
};
|
||||
|
||||
struct GraphBlock {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
// This is a unique identifier, e.g. offset in the case of r2 blocks
|
||||
ut64 entry;
|
||||
// Edges
|
||||
std::vector<GraphEdge> edges;
|
||||
};
|
||||
|
||||
struct LayoutConfig {
|
||||
int block_vertical_margin = 40;
|
||||
int block_horizontal_margin = 10;
|
||||
};
|
||||
|
||||
GraphLayout(const LayoutConfig &layout_config) : layoutConfig(layout_config) {}
|
||||
virtual ~GraphLayout() {}
|
||||
virtual void CalculateLayout(std::unordered_map<ut64, GraphBlock> &blocks, ut64 entry, int &width,
|
||||
int &height) const = 0;
|
||||
protected:
|
||||
LayoutConfig layoutConfig;
|
||||
};
|
||||
|
||||
#endif // GRAPHLAYOUT_H
|
@ -1,13 +1,15 @@
|
||||
#include "GraphView.h"
|
||||
|
||||
#include "GraphGridLayout.h"
|
||||
|
||||
#include <vector>
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
|
||||
GraphView::GraphView(QWidget *parent)
|
||||
: QAbstractScrollArea(parent)
|
||||
, graphLayoutSystem(new GraphGridLayout())
|
||||
{
|
||||
}
|
||||
|
||||
@ -16,21 +18,6 @@ GraphView::~GraphView()
|
||||
// TODO: Cleanups
|
||||
}
|
||||
|
||||
// Vector functions
|
||||
template<class T>
|
||||
static void removeFromVec(std::vector<T> &vec, T elem)
|
||||
{
|
||||
vec.erase(std::remove(vec.begin(), vec.end(), elem), vec.end());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void initVec(std::vector<T> &vec, size_t size, T value)
|
||||
{
|
||||
vec.resize(size);
|
||||
for (size_t i = 0; i < size; i++)
|
||||
vec[i] = value;
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
void GraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
{
|
||||
@ -111,259 +98,7 @@ bool GraphView::event(QEvent *event)
|
||||
// This calculates the full graph starting at block entry.
|
||||
void GraphView::computeGraph(ut64 entry)
|
||||
{
|
||||
// Populate incoming lists
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
for (auto &edge : block.exits) {
|
||||
blocks[edge].incoming.push_back(block.entry);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<ut64> visited;
|
||||
visited.insert(entry);
|
||||
std::queue<ut64> queue;
|
||||
std::vector<ut64> block_order;
|
||||
queue.push(entry);
|
||||
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
|
||||
// Pick nodes with single entrypoints
|
||||
while (!queue.empty()) {
|
||||
GraphBlock &block = blocks[queue.front()];
|
||||
queue.pop();
|
||||
block_order.push_back(block.entry);
|
||||
for (ut64 edge : block.exits) {
|
||||
// Skip edge if we already visited it
|
||||
if (visited.count(edge)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some edges might not be available
|
||||
if (!blocks.count(edge)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this node has no other incoming edges, add it to the graph layout
|
||||
if (blocks[edge].incoming.size() == 1) {
|
||||
removeFromVec(blocks[edge].incoming, block.entry);
|
||||
block.new_exits.push_back(edge);
|
||||
queue.push(blocks[edge].entry);
|
||||
visited.insert(edge);
|
||||
changed = true;
|
||||
} else {
|
||||
// Remove from incoming edges
|
||||
removeFromVec(blocks[edge].incoming, block.entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No more nodes satisfy constraints, pick a node to continue constructing the graph
|
||||
ut64 best = 0;
|
||||
int best_edges;
|
||||
ut64 best_parent;
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
// Skip blocks we haven't visited yet
|
||||
if (!visited.count(block.entry)) {
|
||||
continue;
|
||||
}
|
||||
for (ut64 edge : block.exits) {
|
||||
// If we already visited the exit, skip it
|
||||
if (visited.count(edge)) {
|
||||
continue;
|
||||
}
|
||||
if (!blocks.count(edge)) {
|
||||
continue;
|
||||
}
|
||||
// find best edge
|
||||
if ((best == 0) || ((int)blocks[edge].incoming.size() < best_edges) || (
|
||||
((int)blocks[edge].incoming.size() == best_edges) && (edge < best))) {
|
||||
best = edge;
|
||||
best_edges = blocks[edge].incoming.size();
|
||||
best_parent = block.entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best != 0) {
|
||||
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);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
computeGraphLayout(blocks[entry]);
|
||||
|
||||
// Prepare edge routing
|
||||
GraphBlock &entryb = blocks[entry];
|
||||
EdgesVector horiz_edges, vert_edges;
|
||||
horiz_edges.resize(entryb.row_count + 1);
|
||||
vert_edges.resize(entryb.row_count + 1);
|
||||
Matrix<bool> edge_valid;
|
||||
edge_valid.resize(entryb.row_count + 1);
|
||||
for (int row = 0; row < entryb.row_count + 1; row++) {
|
||||
horiz_edges[row].resize(entryb.col_count + 1);
|
||||
vert_edges[row].resize(entryb.col_count + 1);
|
||||
initVec(edge_valid[row], entryb.col_count + 1, true);
|
||||
for (int col = 0; col < entryb.col_count + 1; col++) {
|
||||
horiz_edges[row][col].clear();
|
||||
vert_edges[row][col].clear();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
edge_valid[block.row][block.col + 1] = false;
|
||||
}
|
||||
|
||||
// Perform edge routing
|
||||
for (ut64 block_id : block_order) {
|
||||
GraphBlock &block = blocks[block_id];
|
||||
GraphBlock &start = block;
|
||||
for (ut64 edge : block.exits) {
|
||||
GraphBlock &end = blocks[edge];
|
||||
start.edges.push_back(routeEdge(horiz_edges, vert_edges, edge_valid, start, end, QColor(255, 0,
|
||||
0)));
|
||||
}
|
||||
}
|
||||
|
||||
// Compute edge counts for each row and column
|
||||
std::vector<int> col_edge_count, row_edge_count;
|
||||
initVec(col_edge_count, entryb.col_count + 1, 0);
|
||||
initVec(row_edge_count, entryb.row_count + 1, 0);
|
||||
for (int row = 0; row < entryb.row_count + 1; row++) {
|
||||
for (int col = 0; col < entryb.col_count + 1; col++) {
|
||||
if (int(horiz_edges[row][col].size()) > row_edge_count[row])
|
||||
row_edge_count[row] = int(horiz_edges[row][col].size());
|
||||
if (int(vert_edges[row][col].size()) > col_edge_count[col])
|
||||
col_edge_count[col] = int(vert_edges[row][col].size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Compute row and column sizes
|
||||
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 : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
if ((int(block.width / 2)) > col_width[block.col])
|
||||
col_width[block.col] = int(block.width / 2);
|
||||
if ((int(block.width / 2)) > col_width[block.col + 1])
|
||||
col_width[block.col + 1] = int(block.width / 2);
|
||||
if (int(block.height) > row_height[block.row])
|
||||
row_height[block.row] = int(block.height);
|
||||
}
|
||||
|
||||
// Compute row and column positions
|
||||
std::vector<int> col_x, row_y;
|
||||
initVec(col_x, entryb.col_count, 0);
|
||||
initVec(row_y, entryb.row_count, 0);
|
||||
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++) {
|
||||
col_edge_x[i] = x;
|
||||
x += block_horizontal_margin * col_edge_count[i];
|
||||
col_x[i] = x;
|
||||
x += col_width[i];
|
||||
}
|
||||
int y = block_vertical_margin * 2;
|
||||
for (int i = 0; i < entryb.row_count; i++) {
|
||||
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];
|
||||
}
|
||||
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 : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
block.x = int(
|
||||
(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] + 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] +
|
||||
block_horizontal_margin * col_edge_count[
|
||||
block.col + 1]) - block.width);
|
||||
}
|
||||
block.y = row_y[block.row];
|
||||
}
|
||||
|
||||
// Precompute coordinates for edges
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
|
||||
for (GraphEdge &edge : block.edges) {
|
||||
auto start = edge.points[0];
|
||||
auto start_col = start.col;
|
||||
auto last_index = edge.start_index;
|
||||
// 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;
|
||||
pts.append(last_pt);
|
||||
|
||||
for (int i = 0; i < int(edge.points.size()); i++) {
|
||||
auto end = edge.points[i];
|
||||
auto end_row = end.row;
|
||||
auto end_col = end.col;
|
||||
auto last_index = end.index;
|
||||
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(), row_edge_y[end_row] + (block_vertical_margin * last_index) +
|
||||
(block_vertical_margin / 2));
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
||||
EdgeConfiguration ec = edgeConfiguration(block, edge.dest);
|
||||
|
||||
auto new_pt = QPoint(last_pt.x(), edge.dest->y - 1);
|
||||
pts.push_back(new_pt);
|
||||
edge.polyline = pts;
|
||||
edge.color = ec.color;
|
||||
if (ec.start_arrow) {
|
||||
pts.clear();
|
||||
pts.append(QPoint(first_pt.x() - 3, first_pt.y() + 6));
|
||||
pts.append(QPoint(first_pt.x() + 3, first_pt.y() + 6));
|
||||
pts.append(first_pt);
|
||||
edge.arrow_start = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
graphLayoutSystem->CalculateLayout(blocks, entry, width, height);
|
||||
ready = true;
|
||||
|
||||
viewport()->update();
|
||||
@ -424,21 +159,31 @@ void GraphView::paintEvent(QPaintEvent *event)
|
||||
// Draw edges
|
||||
for (GraphEdge &edge : block.edges) {
|
||||
QPolygonF polyline = recalculatePolygon(edge.polyline);
|
||||
QPolygonF arrow_start = recalculatePolygon(edge.arrow_start);
|
||||
QPolygonF arrow_end = recalculatePolygon(edge.arrow_end);
|
||||
EdgeConfiguration ec = edgeConfiguration(block, edge.dest);
|
||||
QPen pen(edge.color);
|
||||
EdgeConfiguration ec = edgeConfiguration(block, &blocks[edge.target]);
|
||||
QPen pen(ec.color);
|
||||
pen.setWidth(pen.width() / ec.width_scale);
|
||||
p.setPen(pen);
|
||||
p.setBrush(edge.color);
|
||||
p.setBrush(ec.color);
|
||||
p.drawPolyline(polyline);
|
||||
pen.setStyle(Qt::SolidLine);
|
||||
p.setPen(pen);
|
||||
if (ec.start_arrow) {
|
||||
p.drawConvexPolygon(arrow_start);
|
||||
}
|
||||
if (ec.end_arrow) {
|
||||
p.drawConvexPolygon(arrow_end);
|
||||
if (!polyline.empty()) {
|
||||
if (ec.start_arrow) {
|
||||
auto firstPt = edge.polyline.first();
|
||||
QPolygonF arrowStart;
|
||||
arrowStart << QPointF(firstPt.x() - 3, firstPt.y() + 6);
|
||||
arrowStart << QPointF(firstPt.x() + 3, firstPt.y() + 6);
|
||||
arrowStart << QPointF(firstPt);
|
||||
p.drawConvexPolygon(recalculatePolygon(arrowStart));
|
||||
}
|
||||
if (ec.end_arrow) {
|
||||
auto lastPt = edge.polyline.last();
|
||||
QPolygonF arrowEnd;
|
||||
arrowEnd << QPointF(lastPt.x() - 3, lastPt.y() - 6);
|
||||
arrowEnd << QPointF(lastPt.x() + 3, lastPt.y() - 6);
|
||||
arrowEnd << QPointF(lastPt);
|
||||
p.drawConvexPolygon(recalculatePolygon(arrowEnd));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -455,240 +200,6 @@ void GraphView::drawGraph()
|
||||
p.drawPixmap(target, pixmap, source);
|
||||
}
|
||||
|
||||
// Prepare graph
|
||||
// This computes the position and (row/col based) size of the block
|
||||
// Recursively calls itself for each child of the GraphBlock
|
||||
void GraphView::computeGraphLayout(GraphBlock &block)
|
||||
{
|
||||
int col = 0;
|
||||
int row_count = 1;
|
||||
int childColumn = 0;
|
||||
bool singleChild = block.new_exits.size() == 1;
|
||||
// Compute all children nodes
|
||||
for (size_t i = 0; i < block.new_exits.size(); i++) {
|
||||
ut64 edge = block.new_exits[i];
|
||||
GraphBlock &edgeb = blocks[edge];
|
||||
computeGraphLayout(edgeb);
|
||||
row_count = std::max(edgeb.row_count + 1, row_count);
|
||||
childColumn = edgeb.col;
|
||||
}
|
||||
|
||||
if (layoutType != LayoutType::Wide && block.new_exits.size() == 2) {
|
||||
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;
|
||||
adjustGraphLayout(right, add, 1);
|
||||
adjustGraphLayout(left, add, 1);
|
||||
col = right.col_count + add;
|
||||
} else if (right.new_exits.size() == 0) {
|
||||
adjustGraphLayout(left, 0, 1);
|
||||
adjustGraphLayout(right, left.col + 2, 1);
|
||||
col = std::max(left.col_count, right.col + 2);
|
||||
} else {
|
||||
adjustGraphLayout(left, 0, 1);
|
||||
adjustGraphLayout(right, left.col_count, 1);
|
||||
col = left.col_count + right.col_count;
|
||||
}
|
||||
block.col_count = std::max(2, col);
|
||||
if (layoutType == LayoutType::Medium) {
|
||||
block.col = (left.col + right.col) / 2;
|
||||
} else {
|
||||
block.col = singleChild ? childColumn : (col - 2) / 2;
|
||||
}
|
||||
} else {
|
||||
for (ut64 edge : block.new_exits) {
|
||||
adjustGraphLayout(blocks[edge], col, 1);
|
||||
col += blocks[edge].col_count;
|
||||
}
|
||||
if (col >= 2) {
|
||||
// Place this node centered over the child nodes
|
||||
block.col = singleChild ? childColumn : (col - 2) / 2;
|
||||
block.col_count = col;
|
||||
} else {
|
||||
//No child nodes, set single node's width (nodes are 2 columns wide to allow
|
||||
//centering over a branch)
|
||||
block.col = 0;
|
||||
block.col_count = 2;
|
||||
}
|
||||
}
|
||||
block.row = 0;
|
||||
block.row_count = row_count;
|
||||
}
|
||||
|
||||
// Edge computing stuff
|
||||
bool GraphView::isEdgeMarked(EdgesVector &edges, int row, int col, int index)
|
||||
{
|
||||
if (index >= int(edges[row][col].size()))
|
||||
return false;
|
||||
return edges[row][col][index];
|
||||
}
|
||||
|
||||
void GraphView::markEdge(EdgesVector &edges, int row, int col, int index, bool used)
|
||||
{
|
||||
while (int(edges[row][col].size()) <= index)
|
||||
edges[row][col].push_back(false);
|
||||
edges[row][col][index] = used;
|
||||
}
|
||||
|
||||
GraphView::GraphEdge GraphView::routeEdge(EdgesVector &horiz_edges, EdgesVector &vert_edges,
|
||||
Matrix<bool> &edge_valid, GraphBlock &start, GraphBlock &end, QColor color)
|
||||
{
|
||||
GraphEdge edge;
|
||||
edge.color = color;
|
||||
edge.dest = &end;
|
||||
|
||||
//Find edge index for initial outgoing line
|
||||
int i = 0;
|
||||
while (true) {
|
||||
if (!isEdgeMarked(vert_edges, start.row + 1, start.col + 1, i))
|
||||
break;
|
||||
i += 1;
|
||||
}
|
||||
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;
|
||||
|
||||
//Find valid column for moving vertically to the target node
|
||||
int min_row, max_row;
|
||||
if (end.row < (start.row + 1)) {
|
||||
min_row = end.row;
|
||||
max_row = start.row + 1;
|
||||
} else {
|
||||
min_row = start.row + 1;
|
||||
max_row = end.row;
|
||||
}
|
||||
int col = start.col + 1;
|
||||
if (min_row != max_row) {
|
||||
auto checkColumn = [min_row, max_row, &edge_valid](int column) {
|
||||
if (column < 0 || column >= int(edge_valid[min_row].size()))
|
||||
return false;
|
||||
for (int row = min_row; row < max_row; row++) {
|
||||
if (!edge_valid[row][column]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!checkColumn(col)) {
|
||||
if (checkColumn(end.col + 1)) {
|
||||
col = end.col + 1;
|
||||
} else {
|
||||
int ofs = 0;
|
||||
while (true) {
|
||||
col = start.col + 1 - ofs;
|
||||
if (checkColumn(col)) {
|
||||
break;
|
||||
}
|
||||
|
||||
col = start.col + 1 + ofs;
|
||||
if (checkColumn(col)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ofs += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (col != (start.col + 1)) {
|
||||
//Not in same column, need to generate a line for moving to the correct column
|
||||
int min_col, max_col;
|
||||
if (col < (start.col + 1)) {
|
||||
min_col = col;
|
||||
max_col = start.col + 1;
|
||||
} else {
|
||||
min_col = start.col + 1;
|
||||
max_col = col;
|
||||
}
|
||||
int index = findHorizEdgeIndex(horiz_edges, start.row + 1, min_col, max_col);
|
||||
edge.addPoint(start.row + 1, col, index);
|
||||
horiz = true;
|
||||
}
|
||||
|
||||
if (end.row != (start.row + 1)) {
|
||||
//Not in same row, need to generate a line for moving to the correct row
|
||||
if (col == (start.col + 1))
|
||||
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);
|
||||
horiz = false;
|
||||
}
|
||||
|
||||
if (col != (end.col + 1)) {
|
||||
//Not in ending column, need to generate a line for moving to the correct column
|
||||
int min_col, max_col;
|
||||
if (col < (end.col + 1)) {
|
||||
min_col = col;
|
||||
max_col = end.col + 1;
|
||||
} else {
|
||||
min_col = end.col + 1;
|
||||
max_col = col;
|
||||
}
|
||||
int index = findHorizEdgeIndex(horiz_edges, end.row, min_col, max_col);
|
||||
edge.addPoint(end.row, end.col + 1, index);
|
||||
horiz = true;
|
||||
}
|
||||
|
||||
//If last line was horizontal, choose the ending edge index for the incoming edge
|
||||
if (horiz) {
|
||||
int index = findVertEdgeIndex(vert_edges, end.col + 1, end.row, end.row);
|
||||
edge.points[int(edge.points.size()) - 1].index = index;
|
||||
}
|
||||
|
||||
return edge;
|
||||
}
|
||||
|
||||
|
||||
int GraphView::findHorizEdgeIndex(EdgesVector &edges, int row, int min_col, int max_col)
|
||||
{
|
||||
//Find a valid index
|
||||
int i = 0;
|
||||
while (true) {
|
||||
bool valid = true;
|
||||
for (int col = min_col; col < max_col + 1; col++)
|
||||
if (isEdgeMarked(edges, row, col, i)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if (valid)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
//Mark chosen index as used
|
||||
for (int col = min_col; col < max_col + 1; col++)
|
||||
markEdge(edges, row, col, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
int GraphView::findVertEdgeIndex(EdgesVector &edges, int col, int min_row, int max_row)
|
||||
{
|
||||
//Find a valid index
|
||||
int i = 0;
|
||||
while (true) {
|
||||
bool valid = true;
|
||||
for (int row = min_row; row < max_row + 1; row++)
|
||||
if (isEdgeMarked(edges, row, col, i)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if (valid)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
//Mark chosen index as used
|
||||
for (int row = min_row; row < max_row + 1; row++)
|
||||
markEdge(edges, row, col, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
void GraphView::center()
|
||||
{
|
||||
@ -730,21 +241,11 @@ void GraphView::showBlock(GraphBlock *block)
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void GraphView::adjustGraphLayout(GraphBlock &block, int col, int row)
|
||||
{
|
||||
block.col += col;
|
||||
block.row += row;
|
||||
for (ut64 edge : block.new_exits) {
|
||||
adjustGraphLayout(blocks[edge], col, row);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphView::addBlock(GraphView::GraphBlock block)
|
||||
{
|
||||
blocks[block.entry] = block;
|
||||
}
|
||||
|
||||
|
||||
void GraphView::setEntry(ut64 e)
|
||||
{
|
||||
entry = e;
|
||||
@ -792,7 +293,7 @@ void GraphView::mousePressEvent(QMouseEvent *event)
|
||||
QPointF start = edge.polyline.first();
|
||||
QPointF end = edge.polyline.last();
|
||||
if (checkPointClicked(start, x, y)) {
|
||||
showBlock(edge.dest);
|
||||
showBlock(blocks[edge.target]);
|
||||
// TODO: Callback to child
|
||||
return;
|
||||
break;
|
||||
|
@ -12,77 +12,20 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
|
||||
#include "core/Cutter.h"
|
||||
#include "widgets/GraphLayout.h"
|
||||
|
||||
class GraphView : public QAbstractScrollArea
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum class LayoutType {
|
||||
Medium,
|
||||
Wide,
|
||||
Narrow,
|
||||
};
|
||||
|
||||
signals:
|
||||
void refreshBlock();
|
||||
|
||||
public:
|
||||
struct GraphBlock;
|
||||
|
||||
struct Point {
|
||||
int row; //point[0]
|
||||
int col; //point[1]
|
||||
int index; //point[2]
|
||||
};
|
||||
|
||||
struct GraphEdge {
|
||||
QColor color;
|
||||
GraphBlock *dest;
|
||||
std::vector<Point> points;
|
||||
int start_index = 0;
|
||||
|
||||
QPolygonF polyline;
|
||||
QPolygonF arrow_start;
|
||||
QPolygonF arrow_end;
|
||||
|
||||
void addPoint(int row, int col, int index = 0)
|
||||
{
|
||||
Point point = {row, col, 0};
|
||||
this->points.push_back(point);
|
||||
if (int(this->points.size()) > 1)
|
||||
this->points[this->points.size() - 2].index = index;
|
||||
}
|
||||
};
|
||||
|
||||
struct GraphBlock {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
// This is a unique identifier, e.g. offset in the case of r2 blocks
|
||||
ut64 entry;
|
||||
// This contains unique identifiers to entries
|
||||
// Outgoing edges
|
||||
std::vector<ut64> exits;
|
||||
// Incoming edges
|
||||
std::vector<ut64> incoming;
|
||||
// TODO what is this
|
||||
std::vector<ut64> new_exits;
|
||||
|
||||
// Number of rows in block
|
||||
int row_count = 0;
|
||||
// Number of columns in block
|
||||
int col_count = 0;
|
||||
// Column in which the block is
|
||||
int col = 0;
|
||||
// Row in which the block is
|
||||
int row = 0;
|
||||
|
||||
// Edges
|
||||
std::vector<GraphEdge> edges;
|
||||
};
|
||||
using GraphBlock = GraphLayout::GraphBlock;
|
||||
using GraphEdge = GraphLayout::GraphEdge;
|
||||
|
||||
struct EdgeConfiguration {
|
||||
QColor color = QColor(128, 128, 128);
|
||||
@ -113,14 +56,11 @@ public:
|
||||
* Everytime overview updates its contents, it compares this value with the one in Graph
|
||||
* if they aren't same, then Overview needs to update the pixmap cache.
|
||||
*/
|
||||
ut64 currentFcnAddr = 0;
|
||||
ut64 currentFcnAddr = 0; // TODO: move application specific code out of graph view
|
||||
|
||||
protected:
|
||||
std::unordered_map<ut64, GraphBlock> blocks;
|
||||
QColor backgroundColor = QColor(Qt::white);
|
||||
// The vertical margin between blocks
|
||||
int block_vertical_margin = 40;
|
||||
int block_horizontal_margin = 10;
|
||||
|
||||
// Padding inside the block
|
||||
int block_padding = 16;
|
||||
@ -165,11 +105,7 @@ private:
|
||||
|
||||
ut64 entry;
|
||||
|
||||
void computeGraphLayout(GraphBlock &block);
|
||||
void adjustGraphLayout(GraphBlock &block, int col, int row);
|
||||
|
||||
// Layout type
|
||||
LayoutType layoutType = LayoutType::Medium;
|
||||
std::unique_ptr<GraphLayout> graphLayoutSystem;
|
||||
|
||||
bool ready = false;
|
||||
|
||||
@ -178,22 +114,9 @@ private:
|
||||
int scroll_base_y = 0;
|
||||
bool scroll_mode = false;
|
||||
|
||||
|
||||
// Todo: remove charheight/charwidth cause it should be handled in child class
|
||||
qreal charWidth = 10.0;
|
||||
|
||||
// Edge computing stuff
|
||||
template<typename T>
|
||||
using Matrix = std::vector<std::vector<T>>;
|
||||
using EdgesVector = Matrix<std::vector<bool>>;
|
||||
std::vector<int> col_edge_x;
|
||||
std::vector<int> row_edge_y;
|
||||
bool isEdgeMarked(EdgesVector &edges, int row, int col, int index);
|
||||
void markEdge(EdgesVector &edges, int row, int col, int index, bool used = true);
|
||||
int findHorizEdgeIndex(EdgesVector &edges, int row, int min_col, int max_col);
|
||||
int findVertEdgeIndex(EdgesVector &edges, int col, int min_row, int max_row);
|
||||
GraphEdge routeEdge(EdgesVector &horiz_edges, EdgesVector &vert_edges, Matrix<bool> &edge_valid,
|
||||
GraphBlock &start, GraphBlock &end, QColor color);
|
||||
QPolygonF recalculatePolygon(QPolygonF polygon);
|
||||
};
|
||||
|
||||
|
@ -14,11 +14,14 @@ OverviewView::OverviewView(QWidget *parent)
|
||||
colorsUpdatedSlot();
|
||||
}
|
||||
|
||||
void OverviewView::setData(int baseWidth, int baseHeight, std::unordered_map<ut64, GraphBlock> baseBlocks)
|
||||
void OverviewView::setData(int baseWidth, int baseHeight,
|
||||
std::unordered_map<ut64, GraphBlock> baseBlocks,
|
||||
DisassemblerGraphView::EdgeConfigurationMapping baseEdgeConfigurations)
|
||||
{
|
||||
width = baseWidth;
|
||||
height = baseHeight;
|
||||
blocks = baseBlocks;
|
||||
edgeConfigurations = baseEdgeConfigurations;
|
||||
scaleAndCenter();
|
||||
}
|
||||
|
||||
@ -130,11 +133,12 @@ void OverviewView::wheelEvent(QWheelEvent *event)
|
||||
}
|
||||
|
||||
GraphView::EdgeConfiguration OverviewView::edgeConfiguration(GraphView::GraphBlock &from,
|
||||
GraphView::GraphBlock *to)
|
||||
GraphView::GraphBlock *to)
|
||||
{
|
||||
Q_UNUSED(from);
|
||||
Q_UNUSED(to);
|
||||
EdgeConfiguration ec;
|
||||
auto baseEcIt = edgeConfigurations.find({from.entry, to->entry});
|
||||
if (baseEcIt != edgeConfigurations.end())
|
||||
ec = baseEcIt->second;
|
||||
ec.width_scale = current_scale;
|
||||
return ec;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include "widgets/GraphView.h"
|
||||
#include "widgets/DisassemblerGraphView.h"
|
||||
|
||||
class OverviewView : public GraphView
|
||||
{
|
||||
@ -31,8 +32,10 @@ public:
|
||||
* @param baseWidth width of Graph when it computed the blocks
|
||||
* @param baseHeigh height of Graph when it computed the blocks
|
||||
* @param baseBlocks computed blocks passed by Graph
|
||||
* @param baseEdgeConfigurations computed by DisassamblerGraphview
|
||||
*/
|
||||
void setData(int baseWidth, int baseHeight, std::unordered_map<ut64, GraphBlock> baseBlocks);
|
||||
void setData(int baseWidth, int baseHeight, std::unordered_map<ut64, GraphBlock> baseBlocks,
|
||||
DisassemblerGraphView::EdgeConfigurationMapping baseEdgeConfigurations);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
@ -116,6 +119,11 @@ private:
|
||||
* @brief color for each node changing depending on the theme
|
||||
*/
|
||||
QColor graphNodeColor;
|
||||
|
||||
/**
|
||||
* @brief edgeConfigurations edge styles computed by DisassemblerGraphView
|
||||
*/
|
||||
DisassemblerGraphView::EdgeConfigurationMapping edgeConfigurations;
|
||||
};
|
||||
|
||||
#endif // OVERVIEWVIEW_H
|
||||
|
Loading…
Reference in New Issue
Block a user