diff --git a/src/Cutter.pro b/src/Cutter.pro index 84216db6..4c935e6c 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -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 \ diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 0fd90b91..92f4ede8 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -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() diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index 6a7eb1a3..d8ae3bea 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -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); } } diff --git a/src/widgets/DisassemblerGraphView.h b/src/widgets/DisassemblerGraphView.h index aa2afe5d..cf2a2adb 100644 --- a/src/widgets/DisassemblerGraphView.h +++ b/src/widgets/DisassemblerGraphView.h @@ -104,6 +104,8 @@ public: int getWidth() { return width; } int getHeight() { return height; } std::unordered_map getBlocks() { return blocks; } + using EdgeConfigurationMapping = std::map, EdgeConfiguration>; + EdgeConfigurationMapping getEdgeConfigurations(); public slots: void refreshView(); diff --git a/src/widgets/GraphGridLayout.cpp b/src/widgets/GraphGridLayout.cpp new file mode 100644 index 00000000..b3b6848a --- /dev/null +++ b/src/widgets/GraphGridLayout.cpp @@ -0,0 +1,523 @@ +#include "GraphGridLayout.h" + +#include +#include + +// Vector functions +template +static void removeFromVec(std::vector &vec, T elem) +{ + vec.erase(std::remove(vec.begin(), vec.end(), elem), vec.end()); +} + +GraphGridLayout::GraphGridLayout(GraphGridLayout::LayoutType layoutType) + : GraphLayout({}) +, layoutType(layoutType) +{ +} + +std::vector 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 visited; + visited.insert(entry); + std::queue queue; + std::vector 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 &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 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 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 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 col_x, row_y; + col_x.assign(entryb.col_count, 0); + row_y.assign(entryb.row_count, 0); + std::vector col_edge_x(entryb.col_count + 1); + std::vector 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 &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 &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; +} diff --git a/src/widgets/GraphGridLayout.h b/src/widgets/GraphGridLayout.h new file mode 100644 index 00000000..24fd30e5 --- /dev/null +++ b/src/widgets/GraphGridLayout.h @@ -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 &blocks, + ut64 entry, + int &width, + int &height) const override; +private: + LayoutType layoutType; + + struct GridBlock { + ut64 id; + std::vector incoming; + std::vector 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 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 grid_blocks; + std::unordered_map *blocks = nullptr; + std::unordered_map> edge; + }; + + void computeBlockPlacement(ut64 blockId, + LayoutState &layoutState) const; + void adjustGraphLayout(GridBlock &block, std::unordered_map &blocks, + int col, int row) const; + static std::vector topoSort(LayoutState &state, ut64 entry); + + // Edge computing stuff + template + using Matrix = std::vector>; + using EdgesVector = Matrix>; + + GridEdge routeEdge(EdgesVector &horiz_edges, EdgesVector &vert_edges, + Matrix &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 diff --git a/src/widgets/GraphLayout.h b/src/widgets/GraphLayout.h new file mode 100644 index 00000000..2357c3c4 --- /dev/null +++ b/src/widgets/GraphLayout.h @@ -0,0 +1,40 @@ +#ifndef GRAPHLAYOUT_H +#define GRAPHLAYOUT_H + +#include "core/Cutter.h" + +#include + +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 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 &blocks, ut64 entry, int &width, + int &height) const = 0; +protected: + LayoutConfig layoutConfig; +}; + +#endif // GRAPHLAYOUT_H diff --git a/src/widgets/GraphView.cpp b/src/widgets/GraphView.cpp index 5e089b69..f9a6462d 100644 --- a/src/widgets/GraphView.cpp +++ b/src/widgets/GraphView.cpp @@ -1,13 +1,15 @@ #include "GraphView.h" +#include "GraphGridLayout.h" + #include #include #include #include - GraphView::GraphView(QWidget *parent) : QAbstractScrollArea(parent) + , graphLayoutSystem(new GraphGridLayout()) { } @@ -16,21 +18,6 @@ GraphView::~GraphView() // TODO: Cleanups } -// Vector functions -template -static void removeFromVec(std::vector &vec, T elem) -{ - vec.erase(std::remove(vec.begin(), vec.end(), elem), vec.end()); -} - -template -static void initVec(std::vector &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 visited; - visited.insert(entry); - std::queue queue; - std::vector 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 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 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 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 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 &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; diff --git a/src/widgets/GraphView.h b/src/widgets/GraphView.h index 1cfdb2c7..ff10622f 100644 --- a/src/widgets/GraphView.h +++ b/src/widgets/GraphView.h @@ -12,77 +12,20 @@ #include #include #include +#include #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 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 exits; - // Incoming edges - std::vector incoming; - // TODO what is this - std::vector 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 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 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 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 - using Matrix = std::vector>; - using EdgesVector = Matrix>; - std::vector col_edge_x; - std::vector 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 &edge_valid, - GraphBlock &start, GraphBlock &end, QColor color); QPolygonF recalculatePolygon(QPolygonF polygon); }; diff --git a/src/widgets/OverviewView.cpp b/src/widgets/OverviewView.cpp index 484112b3..52c73b1a 100644 --- a/src/widgets/OverviewView.cpp +++ b/src/widgets/OverviewView.cpp @@ -14,11 +14,14 @@ OverviewView::OverviewView(QWidget *parent) colorsUpdatedSlot(); } -void OverviewView::setData(int baseWidth, int baseHeight, std::unordered_map baseBlocks) +void OverviewView::setData(int baseWidth, int baseHeight, + std::unordered_map 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; } diff --git a/src/widgets/OverviewView.h b/src/widgets/OverviewView.h index 21848432..d2c52389 100644 --- a/src/widgets/OverviewView.h +++ b/src/widgets/OverviewView.h @@ -5,6 +5,7 @@ #include #include #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 baseBlocks); + void setData(int baseWidth, int baseHeight, std::unordered_map 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