mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-24 13:55:26 +00:00
Graphviz based graph layout (#1691)
This commit is contained in:
parent
1fd06a26c5
commit
2f0c0ddc23
@ -19,7 +19,7 @@ matrix:
|
|||||||
os: linux
|
os: linux
|
||||||
env: BUILD_SYSTEM=cmake
|
env: BUILD_SYSTEM=cmake
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get update && sudo apt-get install ninja-build # because the apt addon is broken on bionic
|
- sudo apt-get update && sudo apt-get install ninja-build libgraphviz-dev # because the apt addon is broken on bionic
|
||||||
- pyenv global 3.7.1
|
- pyenv global 3.7.1
|
||||||
- pip install meson
|
- pip install meson
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ matrix:
|
|||||||
- name: Documentation + Deploy
|
- name: Documentation + Deploy
|
||||||
os: linux
|
os: linux
|
||||||
cache: ~
|
cache: ~
|
||||||
before_install: sudo apt-get update && sudo apt-get install doxygen python3-sphinx python3-breathe python3-sphinx-rtd-theme # because the apt addon is broken on bionic
|
before_install: sudo apt-get update && sudo apt-get install doxygen python3-sphinx python3-breathe python3-sphinx-rtd-theme libgraphviz-dev # because the apt addon is broken on bionic
|
||||||
install: ~
|
install: ~
|
||||||
before_script: ~
|
before_script: ~
|
||||||
after_success: ~
|
after_success: ~
|
||||||
|
@ -16,6 +16,7 @@ option(CUTTER_ENABLE_PYTHON "Enable Python integration. Requires Python >= ${CUT
|
|||||||
option(CUTTER_ENABLE_PYTHON_BINDINGS "Enable generating Python bindings with Shiboken2. Unused if CUTTER_ENABLE_PYTHON=OFF." OFF)
|
option(CUTTER_ENABLE_PYTHON_BINDINGS "Enable generating Python bindings with Shiboken2. Unused if CUTTER_ENABLE_PYTHON=OFF." OFF)
|
||||||
option(CUTTER_ENABLE_CRASH_REPORTS "Enable crash report system. Unused if CUTTER_ENABLE_CRASH_REPORTS=OFF" OFF)
|
option(CUTTER_ENABLE_CRASH_REPORTS "Enable crash report system. Unused if CUTTER_ENABLE_CRASH_REPORTS=OFF" OFF)
|
||||||
tri_option(CUTTER_ENABLE_KSYNTAXHIGHLIGHTING "Use KSyntaxHighlighting" AUTO)
|
tri_option(CUTTER_ENABLE_KSYNTAXHIGHLIGHTING "Use KSyntaxHighlighting" AUTO)
|
||||||
|
tri_option(CUTTER_ENABLE_GRAPHVIZ "Enable use of gprahviz for graph layout" AUTO)
|
||||||
|
|
||||||
if(NOT CUTTER_ENABLE_PYTHON)
|
if(NOT CUTTER_ENABLE_PYTHON)
|
||||||
set(CUTTER_ENABLE_PYTHON_BINDINGS OFF)
|
set(CUTTER_ENABLE_PYTHON_BINDINGS OFF)
|
||||||
@ -102,7 +103,20 @@ else()
|
|||||||
set(KSYNTAXHIGHLIGHTING_STATUS OFF)
|
set(KSYNTAXHIGHLIGHTING_STATUS OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|
||||||
|
if (CUTTER_ENABLE_GRAPHVIZ)
|
||||||
|
if (CUTTER_ENABLE_GRAPHVIZ STREQUAL AUTO)
|
||||||
|
pkg_check_modules(GVC libgvc)
|
||||||
|
if (GVC_FOUND)
|
||||||
|
set(CUTTER_ENABLE_GRAPHVIZ ON)
|
||||||
|
else()
|
||||||
|
set(CUTTER_ENABLE_GRAPHVIZ OFF)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
pkg_check_modules(GVC REQUIRED libgvc)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
message(STATUS "")
|
message(STATUS "")
|
||||||
message(STATUS "Building Cutter version ${CUTTER_VERSION_FULL}")
|
message(STATUS "Building Cutter version ${CUTTER_VERSION_FULL}")
|
||||||
@ -112,6 +126,7 @@ message(STATUS "- Python: ${CUTTER_ENABLE_PYTHON}")
|
|||||||
message(STATUS "- Python Bindings: ${CUTTER_ENABLE_PYTHON_BINDINGS}")
|
message(STATUS "- Python Bindings: ${CUTTER_ENABLE_PYTHON_BINDINGS}")
|
||||||
message(STATUS "- Crash Handling: ${CUTTER_ENABLE_CRASH_REPORTS}")
|
message(STATUS "- Crash Handling: ${CUTTER_ENABLE_CRASH_REPORTS}")
|
||||||
message(STATUS "- KSyntaxHighlighting: ${KSYNTAXHIGHLIGHTING_STATUS}")
|
message(STATUS "- KSyntaxHighlighting: ${KSYNTAXHIGHLIGHTING_STATUS}")
|
||||||
|
message(STATUS "- Graphviz: ${CUTTER_ENABLE_GRAPHVIZ}")
|
||||||
message(STATUS "")
|
message(STATUS "")
|
||||||
|
|
||||||
|
|
||||||
@ -142,6 +157,11 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
if (CUTTER_ENABLE_GRAPHVIZ)
|
||||||
|
list(APPEND SOURCE_FILES ${CUTTER_PRO_GRAPHVIZ_SOURCES})
|
||||||
|
list(APPEND HEADER_FILES ${CUTTER_PRO_GRAPHVIZ_HEADERS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
|
||||||
OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
@ -153,6 +173,13 @@ endif()
|
|||||||
add_executable(Cutter MACOSX_BUNDLE ${UI_FILES} ${QRC_FILES} ${SOURCE_FILES} ${HEADER_FILES} ${BINDINGS_SOURCE})
|
add_executable(Cutter MACOSX_BUNDLE ${UI_FILES} ${QRC_FILES} ${SOURCE_FILES} ${HEADER_FILES} ${BINDINGS_SOURCE})
|
||||||
set_target_properties(Cutter PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist")
|
set_target_properties(Cutter PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist")
|
||||||
|
|
||||||
|
if (CUTTER_ENABLE_GRAPHVIZ)
|
||||||
|
target_link_libraries(Cutter ${GVC_LIBRARIES})
|
||||||
|
target_include_directories(Cutter PUBLIC ${GVC_INCLUDE_DIRS})
|
||||||
|
target_compile_options(Cutter PUBLIC ${GVC_CFLAGS_OTHER})
|
||||||
|
target_compile_definitions(Cutter PRIVATE CUTTER_ENABLE_GRAPHVIZ)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(CUTTER_ENABLE_CRASH_REPORTS)
|
if(CUTTER_ENABLE_CRASH_REPORTS)
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
@ -362,6 +362,9 @@ SOURCES += \
|
|||||||
common/SelectionHighlight.cpp \
|
common/SelectionHighlight.cpp \
|
||||||
common/Decompiler.cpp
|
common/Decompiler.cpp
|
||||||
|
|
||||||
|
GRAPHVIZ_SOURCES = \
|
||||||
|
widgets/GraphvizLayout.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
core/Cutter.h \
|
core/Cutter.h \
|
||||||
core/CutterCommon.h \
|
core/CutterCommon.h \
|
||||||
@ -485,11 +488,12 @@ HEADERS += \
|
|||||||
common/BugReporting.h \
|
common/BugReporting.h \
|
||||||
common/HighDpiPixmap.h \
|
common/HighDpiPixmap.h \
|
||||||
widgets/GraphLayout.h \
|
widgets/GraphLayout.h \
|
||||||
widgets/GraphGridLayout.h \
|
|
||||||
widgets/HexWidget.h \
|
widgets/HexWidget.h \
|
||||||
common/SelectionHighlight.h \
|
common/SelectionHighlight.h \
|
||||||
common/Decompiler.h
|
common/Decompiler.h
|
||||||
|
|
||||||
|
GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
dialogs/AboutDialog.ui \
|
dialogs/AboutDialog.ui \
|
||||||
dialogs/preferences/AsmOptionsWidget.ui \
|
dialogs/preferences/AsmOptionsWidget.ui \
|
||||||
|
@ -103,6 +103,33 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable* se
|
|||||||
|
|
||||||
// Context menu that applies to everything
|
// Context menu that applies to everything
|
||||||
contextMenu->addAction(&actionExportGraph);
|
contextMenu->addAction(&actionExportGraph);
|
||||||
|
static const std::pair<QString, GraphView::Layout> LAYOUT_CONFIG[] = {
|
||||||
|
{tr("Grid narrow"), GraphView::Layout::GridNarrow}
|
||||||
|
,{tr("Grid medium"), GraphView::Layout::GridMedium}
|
||||||
|
,{tr("Grid wide"), GraphView::Layout::GridWide}
|
||||||
|
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||||
|
,{tr("Graphviz polyline"), GraphView::Layout::GraphvizPolyline}
|
||||||
|
,{tr("Graphviz polyline LR"), GraphView::Layout::GraphvizPolylineLR}
|
||||||
|
,{tr("Graphviz ortho"), GraphView::Layout::GraphvizOrtho}
|
||||||
|
,{tr("Graphviz ortho LR"), GraphView::Layout::GraphvizOrthoLR}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
auto layoutMenu = contextMenu->addMenu(tr("Layout"));
|
||||||
|
QActionGroup* layoutGroup = new QActionGroup(layoutMenu);
|
||||||
|
for (auto &item : LAYOUT_CONFIG) {
|
||||||
|
auto action = layoutGroup->addAction(item.first);
|
||||||
|
action->setCheckable(true);
|
||||||
|
GraphView::Layout layout = item.second;
|
||||||
|
connect(action, &QAction::triggered, this, [this, layout]() {
|
||||||
|
setGraphLayout(layout);
|
||||||
|
refreshView();
|
||||||
|
onSeekChanged(this->seekable->getOffset()); // try to keep the view on current block
|
||||||
|
});
|
||||||
|
if (layout == getGraphLayout()) {
|
||||||
|
action->setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layoutMenu->addActions(layoutGroup->actions());
|
||||||
contextMenu->addSeparator();
|
contextMenu->addSeparator();
|
||||||
contextMenu->addAction(&actionSyncOffset);
|
contextMenu->addAction(&actionSyncOffset);
|
||||||
|
|
||||||
|
@ -11,6 +11,10 @@ public:
|
|||||||
struct GraphEdge {
|
struct GraphEdge {
|
||||||
ut64 target;
|
ut64 target;
|
||||||
QPolygonF polyline;
|
QPolygonF polyline;
|
||||||
|
enum ArrowDirection {
|
||||||
|
Down, Left, Up, Right, None
|
||||||
|
};
|
||||||
|
ArrowDirection arrow = ArrowDirection::Down;
|
||||||
|
|
||||||
explicit GraphEdge(ut64 target): target(target) {}
|
explicit GraphEdge(ut64 target): target(target) {}
|
||||||
};
|
};
|
||||||
@ -25,6 +29,7 @@ public:
|
|||||||
// Edges
|
// Edges
|
||||||
std::vector<GraphEdge> edges;
|
std::vector<GraphEdge> edges;
|
||||||
};
|
};
|
||||||
|
using Graph = std::unordered_map<ut64, GraphBlock>;
|
||||||
|
|
||||||
struct LayoutConfig {
|
struct LayoutConfig {
|
||||||
int block_vertical_margin = 40;
|
int block_vertical_margin = 40;
|
||||||
@ -33,7 +38,7 @@ public:
|
|||||||
|
|
||||||
GraphLayout(const LayoutConfig &layout_config) : layoutConfig(layout_config) {}
|
GraphLayout(const LayoutConfig &layout_config) : layoutConfig(layout_config) {}
|
||||||
virtual ~GraphLayout() {}
|
virtual ~GraphLayout() {}
|
||||||
virtual void CalculateLayout(std::unordered_map<ut64, GraphBlock> &blocks, ut64 entry, int &width,
|
virtual void CalculateLayout(Graph &blocks, ut64 entry, int &width,
|
||||||
int &height) const = 0;
|
int &height) const = 0;
|
||||||
protected:
|
protected:
|
||||||
LayoutConfig layoutConfig;
|
LayoutConfig layoutConfig;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
#include "GraphView.h"
|
#include "GraphView.h"
|
||||||
|
|
||||||
#include "GraphGridLayout.h"
|
#include "GraphGridLayout.h"
|
||||||
|
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||||
|
#include "GraphvizLayout.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
@ -17,7 +20,6 @@
|
|||||||
|
|
||||||
GraphView::GraphView(QWidget *parent)
|
GraphView::GraphView(QWidget *parent)
|
||||||
: QAbstractScrollArea(parent)
|
: QAbstractScrollArea(parent)
|
||||||
, graphLayoutSystem(new GraphGridLayout())
|
|
||||||
, useGL(false)
|
, useGL(false)
|
||||||
#ifndef QT_NO_OPENGL
|
#ifndef QT_NO_OPENGL
|
||||||
, cacheTexture(0)
|
, cacheTexture(0)
|
||||||
@ -32,6 +34,7 @@ GraphView::GraphView(QWidget *parent)
|
|||||||
glWidget = nullptr;
|
glWidget = nullptr;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
setGraphLayout(Layout::GridMedium);
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphView::~GraphView()
|
GraphView::~GraphView()
|
||||||
@ -349,22 +352,42 @@ void GraphView::paintGraphCache()
|
|||||||
p.drawPolyline(polyline);
|
p.drawPolyline(polyline);
|
||||||
pen.setStyle(Qt::SolidLine);
|
pen.setStyle(Qt::SolidLine);
|
||||||
p.setPen(pen);
|
p.setPen(pen);
|
||||||
|
|
||||||
|
auto drawArrow = [&](QPointF tip, QPointF dir) {
|
||||||
|
QPolygonF arrow;
|
||||||
|
arrow << tip;
|
||||||
|
QPointF dy(-dir.y(), dir.x());
|
||||||
|
QPointF base = tip - dir * 6;
|
||||||
|
arrow << base + 3 * dy;
|
||||||
|
arrow << base - 3 * dy;
|
||||||
|
p.drawConvexPolygon(recalculatePolygon(arrow));
|
||||||
|
};
|
||||||
|
|
||||||
if (!polyline.empty()) {
|
if (!polyline.empty()) {
|
||||||
if (ec.start_arrow) {
|
if (ec.start_arrow) {
|
||||||
auto firstPt = edge.polyline.first();
|
auto firstPt = edge.polyline.first();
|
||||||
QPolygonF arrowStart;
|
drawArrow(firstPt, QPointF(0, 1));
|
||||||
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) {
|
if (ec.end_arrow) {
|
||||||
auto lastPt = edge.polyline.last();
|
auto lastPt = edge.polyline.last();
|
||||||
QPolygonF arrowEnd;
|
QPointF dir(0, -1);
|
||||||
arrowEnd << QPointF(lastPt.x() - 3, lastPt.y() - 6);
|
switch(edge.arrow) {
|
||||||
arrowEnd << QPointF(lastPt.x() + 3, lastPt.y() - 6);
|
case GraphLayout::GraphEdge::Down:
|
||||||
arrowEnd << QPointF(lastPt);
|
dir = QPointF(0, 1);
|
||||||
p.drawConvexPolygon(recalculatePolygon(arrowEnd));
|
break;
|
||||||
|
case GraphLayout::GraphEdge::Up:
|
||||||
|
dir = QPointF(0, -1);
|
||||||
|
break;
|
||||||
|
case GraphLayout::GraphEdge::Left:
|
||||||
|
dir = QPointF(-1, 0);
|
||||||
|
break;
|
||||||
|
case GraphLayout::GraphEdge::Right:
|
||||||
|
dir = QPointF(1, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
drawArrow(lastPt, dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -443,6 +466,36 @@ void GraphView::showRectangle(const QRect &block, bool anywhere)
|
|||||||
viewport()->update();
|
viewport()->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GraphView::setGraphLayout(GraphView::Layout layout)
|
||||||
|
{
|
||||||
|
graphLayout = layout;
|
||||||
|
switch (layout) {
|
||||||
|
case Layout::GridNarrow:
|
||||||
|
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Narrow));
|
||||||
|
break;
|
||||||
|
case Layout::GridMedium:
|
||||||
|
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Medium));
|
||||||
|
break;
|
||||||
|
case Layout::GridWide:
|
||||||
|
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Wide));
|
||||||
|
break;
|
||||||
|
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||||
|
case Layout::GraphvizOrtho:
|
||||||
|
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho));
|
||||||
|
break;
|
||||||
|
case Layout::GraphvizOrthoLR:
|
||||||
|
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho, GraphvizLayout::Direction::LR));
|
||||||
|
break;
|
||||||
|
case Layout::GraphvizPolyline:
|
||||||
|
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline));
|
||||||
|
break;
|
||||||
|
case Layout::GraphvizPolylineLR:
|
||||||
|
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline, GraphvizLayout::Direction::LR));
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GraphView::addBlock(GraphView::GraphBlock block)
|
void GraphView::addBlock(GraphView::GraphBlock block)
|
||||||
{
|
{
|
||||||
blocks[block.entry] = block;
|
blocks[block.entry] = block;
|
||||||
|
@ -33,6 +33,18 @@ public:
|
|||||||
using GraphBlock = GraphLayout::GraphBlock;
|
using GraphBlock = GraphLayout::GraphBlock;
|
||||||
using GraphEdge = GraphLayout::GraphEdge;
|
using GraphEdge = GraphLayout::GraphEdge;
|
||||||
|
|
||||||
|
enum class Layout {
|
||||||
|
GridNarrow
|
||||||
|
,GridMedium
|
||||||
|
,GridWide
|
||||||
|
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||||
|
,GraphvizOrtho
|
||||||
|
,GraphvizOrthoLR
|
||||||
|
,GraphvizPolyline
|
||||||
|
,GraphvizPolylineLR
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
struct EdgeConfiguration {
|
struct EdgeConfiguration {
|
||||||
QColor color = QColor(128, 128, 128);
|
QColor color = QColor(128, 128, 128);
|
||||||
bool start_arrow = false;
|
bool start_arrow = false;
|
||||||
@ -60,6 +72,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
ut64 currentFcnAddr = RVA_INVALID; // TODO: move application specific code out of graph view
|
ut64 currentFcnAddr = RVA_INVALID; // TODO: move application specific code out of graph view
|
||||||
|
|
||||||
|
void setGraphLayout(Layout layout);
|
||||||
|
Layout getGraphLayout() const { return graphLayout; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::unordered_map<ut64, GraphBlock> blocks;
|
std::unordered_map<ut64, GraphBlock> blocks;
|
||||||
QColor backgroundColor = QColor(Qt::white);
|
QColor backgroundColor = QColor(Qt::white);
|
||||||
@ -140,6 +155,7 @@ private:
|
|||||||
QSize cacheSize;
|
QSize cacheSize;
|
||||||
QOpenGLWidget *glWidget;
|
QOpenGLWidget *glWidget;
|
||||||
#endif
|
#endif
|
||||||
|
Layout graphLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief flag to control if the cache is invalid and should be re-created in the next draw
|
* @brief flag to control if the cache is invalid and should be re-created in the next draw
|
||||||
|
215
src/widgets/GraphvizLayout.cpp
Normal file
215
src/widgets/GraphvizLayout.cpp
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
#include "GraphvizLayout.h"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <queue>
|
||||||
|
#include <stack>
|
||||||
|
#include <cassert>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <gvc.h>
|
||||||
|
|
||||||
|
GraphvizLayout::GraphvizLayout(LineType lineType, Direction direction)
|
||||||
|
: GraphLayout({})
|
||||||
|
, direction(direction)
|
||||||
|
, lineType(lineType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static GraphLayout::GraphEdge::ArrowDirection getArrowDirection(QPointF direction,
|
||||||
|
bool preferVertical)
|
||||||
|
{
|
||||||
|
if (abs(direction.x()) > abs(direction.y()) * (preferVertical ? 3.0 : 1.0)) {
|
||||||
|
if (direction.x() > 0) {
|
||||||
|
return GraphLayout::GraphEdge::Right;
|
||||||
|
} else {
|
||||||
|
return GraphLayout::GraphEdge::Left;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (direction.y() > 0) {
|
||||||
|
return GraphLayout::GraphEdge::Down;
|
||||||
|
} else {
|
||||||
|
return GraphLayout::GraphEdge::Up;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::set<std::pair<ut64, ut64>> SelectLoopEdges(const GraphLayout::Graph &graph, ut64 entry)
|
||||||
|
{
|
||||||
|
std::set<std::pair<ut64, ut64>> result;
|
||||||
|
// Run DFS to select backwards/loop edges
|
||||||
|
// 0 - not visited
|
||||||
|
// 1 - in stack
|
||||||
|
// 2 - visited
|
||||||
|
std::unordered_map<ut64, uint8_t> visited;
|
||||||
|
visited.reserve(graph.size());
|
||||||
|
std::stack<std::pair<ut64, size_t>> stack;
|
||||||
|
auto dfsFragment = [&visited, &graph, &stack, &result](ut64 first) {
|
||||||
|
visited[first] = 1;
|
||||||
|
stack.push({first, 0});
|
||||||
|
while (!stack.empty()) {
|
||||||
|
auto v = stack.top().first;
|
||||||
|
auto edge_index = stack.top().second;
|
||||||
|
auto blockIt = graph.find(v);
|
||||||
|
if (blockIt == graph.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto &block = blockIt->second;
|
||||||
|
if (edge_index < block.edges.size()) {
|
||||||
|
++stack.top().second;
|
||||||
|
auto target = block.edges[edge_index].target;
|
||||||
|
auto &targetState = visited[target];
|
||||||
|
if (targetState == 0) {
|
||||||
|
targetState = 1;
|
||||||
|
stack.push({target, 0});
|
||||||
|
} else if (targetState == 1) {
|
||||||
|
result.insert({v, target});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stack.pop();
|
||||||
|
visited[v] = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dfsFragment(entry);
|
||||||
|
for (auto &blockIt : graph) {
|
||||||
|
if (!visited[blockIt.first]) {
|
||||||
|
dfsFragment(blockIt.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphvizLayout::CalculateLayout(std::unordered_map<ut64, GraphBlock> &blocks, ut64 entry,
|
||||||
|
int &width, int &height) const
|
||||||
|
{
|
||||||
|
//https://gitlab.com/graphviz/graphviz/issues/1441
|
||||||
|
#define STR(v) const_cast<char*>(v)
|
||||||
|
|
||||||
|
width = height = 10;
|
||||||
|
GVC_t *gvc = gvContext();
|
||||||
|
Agraph_t *g = agopen(STR("G"), Agdirected, nullptr);
|
||||||
|
|
||||||
|
std::unordered_map<ut64, Agnode_t *> nodes;
|
||||||
|
for (const auto &block : blocks) {
|
||||||
|
nodes[block.first] = agnode(g, nullptr, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> strc;
|
||||||
|
strc.reserve(2 * blocks.size());
|
||||||
|
std::map<std::pair<ut64, ut64>, Agedge_t *> edges;
|
||||||
|
|
||||||
|
agsafeset(g, STR("splines"), lineType == LineType::Ortho ? STR("ortho") : STR("polyline"), STR(""));
|
||||||
|
switch (direction) {
|
||||||
|
case Direction::LR:
|
||||||
|
agsafeset(g, STR("rankdir"), STR("LR"), STR(""));
|
||||||
|
break;
|
||||||
|
case Direction::TB:
|
||||||
|
agsafeset(g, STR("rankdir"), STR("BT"), STR(""));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
agsafeset(g, STR("newrank"), STR("true"), STR(""));
|
||||||
|
// graphviz has builtin 72 dpi setting for input that differs from output
|
||||||
|
// it's easier to use 72 everywhere
|
||||||
|
const double dpi = 72.0;
|
||||||
|
agsafeset(g, STR("dpi"), STR("72"), STR(""));
|
||||||
|
|
||||||
|
auto widhAttr = agattr(g, AGNODE, STR("width"), STR("1"));
|
||||||
|
auto heightAatr = agattr(g, AGNODE, STR("height"), STR("1"));
|
||||||
|
agattr(g, AGNODE, STR("shape"), STR("box"));
|
||||||
|
agattr(g, AGNODE, STR("fixedsize"), STR("true"));
|
||||||
|
auto constraintAttr = agattr(g, AGEDGE, STR("constraint"), STR("1"));
|
||||||
|
|
||||||
|
std::ostringstream stream;
|
||||||
|
stream.imbue(std::locale::classic());
|
||||||
|
auto setFloatingPointAttr = [&stream](void *obj, Agsym_t *sym, double value) {
|
||||||
|
stream.str({});
|
||||||
|
stream << std::fixed << std::setw(4) << value;
|
||||||
|
auto str = stream.str();
|
||||||
|
agxset(obj, sym, STR(str.c_str()));
|
||||||
|
};
|
||||||
|
|
||||||
|
std::set<std::pair<ut64, ut64>> loopEdges = SelectLoopEdges(blocks, entry);
|
||||||
|
|
||||||
|
for (const auto &blockIt : blocks) {
|
||||||
|
auto u = nodes[blockIt.first];
|
||||||
|
auto &block = blockIt.second;
|
||||||
|
|
||||||
|
for (auto &edge : block.edges) {
|
||||||
|
auto v = nodes.find(edge.target);
|
||||||
|
if (v == nodes.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto e = agedge(g, u, v->second, nullptr, TRUE);
|
||||||
|
edges[{blockIt.first, edge.target}] = e;
|
||||||
|
if (loopEdges.find({blockIt.first, edge.target}) != loopEdges.end()) {
|
||||||
|
agxset(e, constraintAttr, STR("0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFloatingPointAttr(u, widhAttr, block.width / dpi);
|
||||||
|
setFloatingPointAttr(u, heightAatr, block.height / dpi);
|
||||||
|
}
|
||||||
|
|
||||||
|
gvLayout(gvc, g, "dot");
|
||||||
|
|
||||||
|
for (auto &blockIt : blocks) {
|
||||||
|
auto &block = blockIt.second;
|
||||||
|
auto u = nodes[blockIt.first];
|
||||||
|
|
||||||
|
auto pos = ND_coord(u);
|
||||||
|
|
||||||
|
auto w = ND_width(u) * dpi;
|
||||||
|
auto h = ND_height(u) * dpi;
|
||||||
|
block.x = pos.x - w / 2.0;
|
||||||
|
block.y = pos.y - h / 2.0;
|
||||||
|
width = std::max(width, block.x + block.width);
|
||||||
|
height = std::max(height, block.y + block.height);
|
||||||
|
|
||||||
|
for (auto &edge : block.edges) {
|
||||||
|
auto it = edges.find({blockIt.first, edge.target});
|
||||||
|
if (it != edges.end()) {
|
||||||
|
auto e = it->second;
|
||||||
|
if (auto spl = ED_spl(e)) {
|
||||||
|
for (int i = 0; i < 1 && i < spl->size; i++) {
|
||||||
|
auto bz = spl->list[i];
|
||||||
|
edge.polyline.reserve(bz.size + 1);
|
||||||
|
for (int j = 0; j < bz.size; j++) {
|
||||||
|
edge.polyline.push_back(QPointF(bz.list[j].x, bz.list[j].y));
|
||||||
|
}
|
||||||
|
QPointF last(0, 0);
|
||||||
|
if (!edge.polyline.empty()) {
|
||||||
|
last = edge.polyline.back();
|
||||||
|
}
|
||||||
|
if (bz.eflag) {
|
||||||
|
QPointF tip = QPointF(bz.ep.x, bz.ep.y);
|
||||||
|
edge.polyline.push_back(tip);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edge.polyline.size() >= 2) {
|
||||||
|
// make sure self loops go from bottom to top
|
||||||
|
if (edge.target == block.entry && edge.polyline.first().y() < edge.polyline.last().y()) {
|
||||||
|
std::reverse(edge.polyline.begin(), edge.polyline.end());
|
||||||
|
}
|
||||||
|
auto it = edge.polyline.rbegin();
|
||||||
|
QPointF direction = *it;
|
||||||
|
direction -= *(++it);
|
||||||
|
edge.arrow = getArrowDirection(direction, lineType == LineType::Polyline);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
edge.arrow = GraphEdge::Down;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gvFreeLayout(gvc, g);
|
||||||
|
agclose(g);
|
||||||
|
gvFreeContext(gvc);
|
||||||
|
#undef STR
|
||||||
|
}
|
28
src/widgets/GraphvizLayout.h
Normal file
28
src/widgets/GraphvizLayout.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef GRAPHVIZLAYOUT_H
|
||||||
|
#define GRAPHVIZLAYOUT_H
|
||||||
|
|
||||||
|
#include "core/Cutter.h"
|
||||||
|
#include "GraphLayout.h"
|
||||||
|
|
||||||
|
class GraphvizLayout : public GraphLayout
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class LineType {
|
||||||
|
Ortho,
|
||||||
|
Polyline
|
||||||
|
};
|
||||||
|
enum class Direction {
|
||||||
|
TB,
|
||||||
|
LR
|
||||||
|
};
|
||||||
|
GraphvizLayout(LineType lineType, Direction direction = Direction::TB);
|
||||||
|
virtual void CalculateLayout(std::unordered_map<ut64, GraphBlock> &blocks,
|
||||||
|
ut64 entry,
|
||||||
|
int &width,
|
||||||
|
int &height) const override;
|
||||||
|
private:
|
||||||
|
Direction direction;
|
||||||
|
LineType lineType;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GRAPHVIZLAYOUT_H
|
Loading…
Reference in New Issue
Block a user