mirror of
https://github.com/rizinorg/cutter.git
synced 2025-01-18 10:35:25 +00:00
Horizontal graph (#2234)
* Create adapter for converting vertical layout into horizontal * Fix edge spacing override for edges going out of switch statement. * Update documentation.
This commit is contained in:
parent
56c2e3741a
commit
837dd63e6b
@ -41,11 +41,17 @@ When Graphviz is installed, the following options are also available:
|
||||
- Graphviz PNG Image
|
||||
- Graphviz JPG Image
|
||||
- Graphviz SVG Image
|
||||
-
|
||||
|
||||
**Steps:** Right click anywhere on the Graph view and choose ``Export Graph``.
|
||||
|
||||
|
||||
Graph Layout Direction
|
||||
----------------------------------------
|
||||
**Description:** Graph layout direction can be either vertical top to bottom or horizontal left to right.
|
||||
|
||||
|
||||
**Steps:** Right click anywhere on the Graph view ``Layout -> Horizontal``.
|
||||
|
||||
Choose Graph Layout
|
||||
----------------------------------------
|
||||
**Description:** Choose the layout to be used by Cutter to display the Graph. Cutter supports the following Graph layout algorithms:
|
||||
@ -56,10 +62,7 @@ Choose Graph Layout
|
||||
|
||||
When Graphviz is installed, the following options are also available:
|
||||
|
||||
- Graphviz polyline
|
||||
- Graphviz polyline LR
|
||||
- Graphviz ortho
|
||||
- Graphviz ortho LR
|
||||
|
||||
- Graphviz polyline
|
||||
- Graphviz ortho
|
||||
|
||||
**Steps:** Right click anywhere on the Graph view and choose a layout from the ``Layout`` sub-menu.
|
@ -424,7 +424,8 @@ SOURCES += \
|
||||
common/IOModesController.cpp \
|
||||
common/SettingsUpgrade.cpp \
|
||||
dialogs/LayoutManager.cpp \
|
||||
common/CutterLayout.cpp
|
||||
common/CutterLayout.cpp \
|
||||
widgets/GraphHorizontalAdapter.cpp
|
||||
|
||||
GRAPHVIZ_SOURCES = \
|
||||
widgets/GraphvizLayout.cpp
|
||||
@ -577,7 +578,8 @@ HEADERS += \
|
||||
dialogs/LayoutManager.h \
|
||||
common/CutterLayout.h \
|
||||
common/BinaryTrees.h \
|
||||
common/LinkedListPool.h
|
||||
common/LinkedListPool.h \
|
||||
widgets/GraphHorizontalAdapter.h
|
||||
|
||||
GRAPHVIZ_HEADERS = widgets/GraphvizLayout.h
|
||||
|
||||
|
@ -42,6 +42,7 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se
|
||||
MainWindow *mainWindow, QList<QAction *> additionalMenuActions)
|
||||
: GraphView(parent),
|
||||
mFontMetrics(nullptr),
|
||||
graphLayout(GraphView::Layout::GridMedium),
|
||||
blockMenu(new DisassemblyContextMenu(this, mainWindow)),
|
||||
contextMenu(new QMenu(this)),
|
||||
seekable(seekable),
|
||||
@ -104,27 +105,29 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se
|
||||
, {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"));
|
||||
horizontalLayoutAction = layoutMenu->addAction(tr("Horizontal"));
|
||||
horizontalLayoutAction->setCheckable(true);
|
||||
layoutMenu->addSeparator();
|
||||
connect(horizontalLayoutAction, &QAction::toggled, this, &DisassemblerGraphView::updateLayout);
|
||||
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
|
||||
this->graphLayout = layout;
|
||||
updateLayout();
|
||||
});
|
||||
if (layout == getGraphLayout()) {
|
||||
if (layout == this->graphLayout) {
|
||||
action->setChecked(true);
|
||||
}
|
||||
}
|
||||
layoutMenu->addActions(layoutGroup->actions());
|
||||
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addActions(additionalMenuActions);
|
||||
|
||||
@ -1155,6 +1158,13 @@ void DisassemblerGraphView::onActionUnhighlightBITriggered()
|
||||
Config()->colorsUpdated();
|
||||
}
|
||||
|
||||
void DisassemblerGraphView::updateLayout()
|
||||
{
|
||||
setGraphLayout(GraphView::makeGraphLayout(graphLayout, horizontalLayoutAction->isChecked()));
|
||||
refreshView();
|
||||
onSeekChanged(this->seekable->getOffset()); // try to keep the view on current block
|
||||
}
|
||||
|
||||
void DisassemblerGraphView::exportGraph(QString filePath, GraphExportType type)
|
||||
{
|
||||
bool graphTransparent = Config()->getBitmapTransparentState();
|
||||
|
@ -159,6 +159,7 @@ private slots:
|
||||
void on_actionExportGraph_triggered();
|
||||
void onActionHighlightBITriggered();
|
||||
void onActionUnhighlightBITriggered();
|
||||
void updateLayout();
|
||||
|
||||
private:
|
||||
bool transition_dont_seek = false;
|
||||
@ -173,8 +174,11 @@ private:
|
||||
bool emptyGraph;
|
||||
ut64 currentBlockAddress = RVA_INVALID;
|
||||
|
||||
GraphView::Layout graphLayout;
|
||||
|
||||
DisassemblyContextMenu *blockMenu;
|
||||
QMenu *contextMenu;
|
||||
QAction* horizontalLayoutAction;
|
||||
|
||||
void connectSeekChanged(bool disconnect);
|
||||
|
||||
|
@ -248,7 +248,7 @@ void GraphGridLayout::CalculateLayout(std::unordered_map<ut64, GraphBlock> &bloc
|
||||
}
|
||||
for (const auto &edgeList : layoutState.edge) {
|
||||
auto &startBlock = layoutState.grid_blocks[edgeList.first];
|
||||
startBlock.outputCount++;
|
||||
startBlock.outputCount = edgeList.second.size();
|
||||
for (auto &edge : edgeList.second) {
|
||||
auto &targetBlock = layoutState.grid_blocks[edge.dest];
|
||||
targetBlock.inputCount++;
|
||||
@ -573,6 +573,9 @@ void GraphGridLayout::calculateEdgeMainColumn(GraphGridLayout::LayoutState &stat
|
||||
void GraphGridLayout::roughRouting(GraphGridLayout::LayoutState &state) const
|
||||
{
|
||||
auto getSpacingOverride = [this](int blockWidth, int edgeCount) {
|
||||
if (edgeCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
int maxSpacing = blockWidth / edgeCount;
|
||||
if (maxSpacing < layoutConfig.edgeHorizontalSpacing) {
|
||||
return std::max(maxSpacing, 1);
|
||||
@ -614,6 +617,13 @@ void GraphGridLayout::roughRouting(GraphGridLayout::LayoutState &state) const
|
||||
target.inputCount);
|
||||
edge.points.front().spacingOverride = startSpacingOverride;
|
||||
edge.points.back().spacingOverride = targetSpacingOverride;
|
||||
if (edge.points.size() <= 2) {
|
||||
if (startSpacingOverride && startSpacingOverride < targetSpacingOverride) {
|
||||
edge.points.back().spacingOverride = startSpacingOverride;
|
||||
}
|
||||
} else {
|
||||
edge.points[1].spacingOverride = startSpacingOverride;
|
||||
}
|
||||
|
||||
|
||||
int length = 0;
|
||||
|
58
src/widgets/GraphHorizontalAdapter.cpp
Normal file
58
src/widgets/GraphHorizontalAdapter.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "GraphHorizontalAdapter.h"
|
||||
|
||||
GraphHorizontalAdapter::GraphHorizontalAdapter(std::unique_ptr<GraphLayout> layout)
|
||||
: GraphLayout({})
|
||||
, layout(std::move(layout))
|
||||
{
|
||||
swapLayoutConfigDirection();
|
||||
}
|
||||
|
||||
void GraphHorizontalAdapter::CalculateLayout(
|
||||
GraphLayout::Graph &blocks,
|
||||
unsigned long long entry,
|
||||
int &width,
|
||||
int &height) const
|
||||
{
|
||||
for (auto &block : blocks) {
|
||||
std::swap(block.second.width, block.second.height);
|
||||
}
|
||||
layout->CalculateLayout(blocks, entry, height, width); // intentionally swapping height and width
|
||||
for (auto &block : blocks) {
|
||||
std::swap(block.second.width, block.second.height);
|
||||
std::swap(block.second.x, block.second.y);
|
||||
for (auto &edge : block.second.edges) {
|
||||
for (auto &point : edge.polyline) {
|
||||
std::swap(point.rx(), point.ry());
|
||||
}
|
||||
switch (edge.arrow) {
|
||||
case GraphEdge::Down:
|
||||
edge.arrow = GraphEdge::Right;
|
||||
break;
|
||||
case GraphEdge::Left:
|
||||
edge.arrow = GraphEdge::Up;
|
||||
break;
|
||||
case GraphEdge::Up:
|
||||
edge.arrow = GraphEdge::Left;
|
||||
break;
|
||||
case GraphEdge::Right:
|
||||
edge.arrow = GraphEdge::Down;
|
||||
break;
|
||||
case GraphEdge::None:
|
||||
edge.arrow = GraphEdge::None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphHorizontalAdapter::setLayoutConfig(const GraphLayout::LayoutConfig &config)
|
||||
{
|
||||
GraphLayout::setLayoutConfig(config);
|
||||
swapLayoutConfigDirection();
|
||||
}
|
||||
|
||||
void GraphHorizontalAdapter::swapLayoutConfigDirection()
|
||||
{
|
||||
std::swap(layoutConfig.edgeVerticalSpacing, layoutConfig.edgeHorizontalSpacing);
|
||||
std::swap(layoutConfig.blockVerticalSpacing, layoutConfig.blockHorizontalSpacing);
|
||||
}
|
24
src/widgets/GraphHorizontalAdapter.h
Normal file
24
src/widgets/GraphHorizontalAdapter.h
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef GRAPH_HORIZONTAL_ADAPTER_H
|
||||
#define GRAPH_HORIZONTAL_ADAPTER_H
|
||||
|
||||
#include "core/Cutter.h"
|
||||
#include "GraphLayout.h"
|
||||
|
||||
/**
|
||||
* @brief Adapter for converting vertical graph layout into horizontal one.
|
||||
*/
|
||||
class GraphHorizontalAdapter : public GraphLayout
|
||||
{
|
||||
public:
|
||||
GraphHorizontalAdapter(std::unique_ptr<GraphLayout> layout);
|
||||
virtual void CalculateLayout(GraphLayout::Graph &blocks,
|
||||
ut64 entry,
|
||||
int &width,
|
||||
int &height) const override;
|
||||
void setLayoutConfig(const LayoutConfig &config) override;
|
||||
private:
|
||||
std::unique_ptr<GraphLayout> layout;
|
||||
void swapLayoutConfigDirection();
|
||||
};
|
||||
|
||||
#endif // GRAPH_HORIZONTAL_ADAPTER_H
|
@ -42,6 +42,10 @@ public:
|
||||
virtual ~GraphLayout() {}
|
||||
virtual void CalculateLayout(Graph &blocks, ut64 entry, int &width,
|
||||
int &height) const = 0;
|
||||
virtual void setLayoutConfig(const LayoutConfig &config)
|
||||
{
|
||||
this->layoutConfig = config;
|
||||
};
|
||||
protected:
|
||||
LayoutConfig layoutConfig;
|
||||
};
|
||||
|
@ -4,6 +4,7 @@
|
||||
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||
#include "GraphvizLayout.h"
|
||||
#endif
|
||||
#include "GraphHorizontalAdapter.h"
|
||||
#include "Helpers.h"
|
||||
|
||||
#include <vector>
|
||||
@ -36,7 +37,7 @@ GraphView::GraphView(QWidget *parent)
|
||||
glWidget = nullptr;
|
||||
}
|
||||
#endif
|
||||
setGraphLayout(Layout::GridMedium);
|
||||
setGraphLayout(makeGraphLayout(Layout::GridMedium));
|
||||
}
|
||||
|
||||
GraphView::~GraphView()
|
||||
@ -513,36 +514,45 @@ QPoint GraphView::viewToLogicalCoordinates(QPoint p)
|
||||
return p / current_scale + offset;
|
||||
}
|
||||
|
||||
void GraphView::setGraphLayout(GraphView::Layout layout)
|
||||
void GraphView::setGraphLayout(std::unique_ptr<GraphLayout> layout)
|
||||
{
|
||||
graphLayout = layout;
|
||||
graphLayoutSystem = std::move(layout);
|
||||
if (!graphLayoutSystem) {
|
||||
graphLayoutSystem = makeGraphLayout(Layout::GridMedium);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<GraphLayout> GraphView::makeGraphLayout(GraphView::Layout layout, bool horizontal)
|
||||
{
|
||||
std::unique_ptr<GraphLayout> result;
|
||||
bool needAdapter = true;
|
||||
switch (layout) {
|
||||
case Layout::GridNarrow:
|
||||
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Narrow));
|
||||
result.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Narrow));
|
||||
break;
|
||||
case Layout::GridMedium:
|
||||
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Medium));
|
||||
result.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Medium));
|
||||
break;
|
||||
case Layout::GridWide:
|
||||
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Wide));
|
||||
result.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));
|
||||
result.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho,
|
||||
horizontal ? GraphvizLayout::Direction::LR : GraphvizLayout::Direction::TB));
|
||||
needAdapter = false;
|
||||
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));
|
||||
result.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline,
|
||||
horizontal ? GraphvizLayout::Direction::LR : GraphvizLayout::Direction::TB));
|
||||
needAdapter = false;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
if (needAdapter && horizontal) {
|
||||
result.reset(new GraphHorizontalAdapter(std::move(result)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void GraphView::addBlock(GraphView::GraphBlock block)
|
||||
|
@ -44,11 +44,10 @@ public:
|
||||
, GridWide
|
||||
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||
, GraphvizOrtho
|
||||
, GraphvizOrthoLR
|
||||
, GraphvizPolyline
|
||||
, GraphvizPolylineLR
|
||||
#endif
|
||||
};
|
||||
static std::unique_ptr<GraphLayout> makeGraphLayout(Layout layout, bool horizontal = false);
|
||||
|
||||
struct EdgeConfiguration {
|
||||
QColor color = QColor(128, 128, 128);
|
||||
@ -77,8 +76,8 @@ public:
|
||||
GraphView::GraphBlock *getBlockContaining(QPoint p);
|
||||
QPoint viewToLogicalCoordinates(QPoint p);
|
||||
|
||||
void setGraphLayout(Layout layout);
|
||||
Layout getGraphLayout() const { return graphLayout; }
|
||||
void setGraphLayout(std::unique_ptr<GraphLayout> layout);
|
||||
GraphLayout& getGraphLayout() const { return *graphLayoutSystem; }
|
||||
|
||||
void paint(QPainter &p, QPoint offset, QRect area, qreal scale = 1.0, bool interactive = true);
|
||||
|
||||
@ -174,7 +173,6 @@ private:
|
||||
QSize cacheSize;
|
||||
QOpenGLWidget *glWidget;
|
||||
#endif
|
||||
Layout graphLayout;
|
||||
|
||||
/**
|
||||
* @brief flag to control if the cache is invalid and should be re-created in the next draw
|
||||
|
Loading…
Reference in New Issue
Block a user