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:
karliss 2020-06-06 02:06:38 +03:00 committed by GitHub
parent 56c2e3741a
commit 837dd63e6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 159 additions and 36 deletions

View File

@ -41,11 +41,17 @@ When Graphviz is installed, the following options are also available:
- Graphviz PNG Image - Graphviz PNG Image
- Graphviz JPG Image - Graphviz JPG Image
- Graphviz SVG Image - Graphviz SVG Image
-
**Steps:** Right click anywhere on the Graph view and choose ``Export Graph``. **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 Choose Graph Layout
---------------------------------------- ----------------------------------------
**Description:** Choose the layout to be used by Cutter to display the Graph. Cutter supports the following Graph layout algorithms: **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: When Graphviz is installed, the following options are also available:
- Graphviz polyline - Graphviz polyline
- Graphviz polyline LR - Graphviz ortho
- Graphviz ortho
- Graphviz ortho LR
**Steps:** Right click anywhere on the Graph view and choose a layout from the ``Layout`` sub-menu. **Steps:** Right click anywhere on the Graph view and choose a layout from the ``Layout`` sub-menu.

View File

@ -424,7 +424,8 @@ SOURCES += \
common/IOModesController.cpp \ common/IOModesController.cpp \
common/SettingsUpgrade.cpp \ common/SettingsUpgrade.cpp \
dialogs/LayoutManager.cpp \ dialogs/LayoutManager.cpp \
common/CutterLayout.cpp common/CutterLayout.cpp \
widgets/GraphHorizontalAdapter.cpp
GRAPHVIZ_SOURCES = \ GRAPHVIZ_SOURCES = \
widgets/GraphvizLayout.cpp widgets/GraphvizLayout.cpp
@ -577,7 +578,8 @@ HEADERS += \
dialogs/LayoutManager.h \ dialogs/LayoutManager.h \
common/CutterLayout.h \ common/CutterLayout.h \
common/BinaryTrees.h \ common/BinaryTrees.h \
common/LinkedListPool.h common/LinkedListPool.h \
widgets/GraphHorizontalAdapter.h
GRAPHVIZ_HEADERS = widgets/GraphvizLayout.h GRAPHVIZ_HEADERS = widgets/GraphvizLayout.h

View File

@ -42,6 +42,7 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se
MainWindow *mainWindow, QList<QAction *> additionalMenuActions) MainWindow *mainWindow, QList<QAction *> additionalMenuActions)
: GraphView(parent), : GraphView(parent),
mFontMetrics(nullptr), mFontMetrics(nullptr),
graphLayout(GraphView::Layout::GridMedium),
blockMenu(new DisassemblyContextMenu(this, mainWindow)), blockMenu(new DisassemblyContextMenu(this, mainWindow)),
contextMenu(new QMenu(this)), contextMenu(new QMenu(this)),
seekable(seekable), seekable(seekable),
@ -104,27 +105,29 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se
, {tr("Grid wide"), GraphView::Layout::GridWide} , {tr("Grid wide"), GraphView::Layout::GridWide}
#ifdef CUTTER_ENABLE_GRAPHVIZ #ifdef CUTTER_ENABLE_GRAPHVIZ
, {tr("Graphviz polyline"), GraphView::Layout::GraphvizPolyline} , {tr("Graphviz polyline"), GraphView::Layout::GraphvizPolyline}
, {tr("Graphviz polyline LR"), GraphView::Layout::GraphvizPolylineLR}
, {tr("Graphviz ortho"), GraphView::Layout::GraphvizOrtho} , {tr("Graphviz ortho"), GraphView::Layout::GraphvizOrtho}
, {tr("Graphviz ortho LR"), GraphView::Layout::GraphvizOrthoLR}
#endif #endif
}; };
auto layoutMenu = contextMenu->addMenu(tr("Layout")); 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); QActionGroup *layoutGroup = new QActionGroup(layoutMenu);
for (auto &item : LAYOUT_CONFIG) { for (auto &item : LAYOUT_CONFIG) {
auto action = layoutGroup->addAction(item.first); auto action = layoutGroup->addAction(item.first);
action->setCheckable(true); action->setCheckable(true);
GraphView::Layout layout = item.second; GraphView::Layout layout = item.second;
connect(action, &QAction::triggered, this, [this, layout]() { connect(action, &QAction::triggered, this, [this, layout]() {
setGraphLayout(layout); this->graphLayout = layout;
refreshView(); updateLayout();
onSeekChanged(this->seekable->getOffset()); // try to keep the view on current block
}); });
if (layout == getGraphLayout()) { if (layout == this->graphLayout) {
action->setChecked(true); action->setChecked(true);
} }
} }
layoutMenu->addActions(layoutGroup->actions()); layoutMenu->addActions(layoutGroup->actions());
contextMenu->addSeparator(); contextMenu->addSeparator();
contextMenu->addActions(additionalMenuActions); contextMenu->addActions(additionalMenuActions);
@ -1155,6 +1158,13 @@ void DisassemblerGraphView::onActionUnhighlightBITriggered()
Config()->colorsUpdated(); 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) void DisassemblerGraphView::exportGraph(QString filePath, GraphExportType type)
{ {
bool graphTransparent = Config()->getBitmapTransparentState(); bool graphTransparent = Config()->getBitmapTransparentState();

View File

@ -159,6 +159,7 @@ private slots:
void on_actionExportGraph_triggered(); void on_actionExportGraph_triggered();
void onActionHighlightBITriggered(); void onActionHighlightBITriggered();
void onActionUnhighlightBITriggered(); void onActionUnhighlightBITriggered();
void updateLayout();
private: private:
bool transition_dont_seek = false; bool transition_dont_seek = false;
@ -173,8 +174,11 @@ private:
bool emptyGraph; bool emptyGraph;
ut64 currentBlockAddress = RVA_INVALID; ut64 currentBlockAddress = RVA_INVALID;
GraphView::Layout graphLayout;
DisassemblyContextMenu *blockMenu; DisassemblyContextMenu *blockMenu;
QMenu *contextMenu; QMenu *contextMenu;
QAction* horizontalLayoutAction;
void connectSeekChanged(bool disconnect); void connectSeekChanged(bool disconnect);

View File

@ -248,7 +248,7 @@ void GraphGridLayout::CalculateLayout(std::unordered_map<ut64, GraphBlock> &bloc
} }
for (const auto &edgeList : layoutState.edge) { for (const auto &edgeList : layoutState.edge) {
auto &startBlock = layoutState.grid_blocks[edgeList.first]; auto &startBlock = layoutState.grid_blocks[edgeList.first];
startBlock.outputCount++; startBlock.outputCount = edgeList.second.size();
for (auto &edge : edgeList.second) { for (auto &edge : edgeList.second) {
auto &targetBlock = layoutState.grid_blocks[edge.dest]; auto &targetBlock = layoutState.grid_blocks[edge.dest];
targetBlock.inputCount++; targetBlock.inputCount++;
@ -573,6 +573,9 @@ void GraphGridLayout::calculateEdgeMainColumn(GraphGridLayout::LayoutState &stat
void GraphGridLayout::roughRouting(GraphGridLayout::LayoutState &state) const void GraphGridLayout::roughRouting(GraphGridLayout::LayoutState &state) const
{ {
auto getSpacingOverride = [this](int blockWidth, int edgeCount) { auto getSpacingOverride = [this](int blockWidth, int edgeCount) {
if (edgeCount == 0) {
return 0;
}
int maxSpacing = blockWidth / edgeCount; int maxSpacing = blockWidth / edgeCount;
if (maxSpacing < layoutConfig.edgeHorizontalSpacing) { if (maxSpacing < layoutConfig.edgeHorizontalSpacing) {
return std::max(maxSpacing, 1); return std::max(maxSpacing, 1);
@ -614,6 +617,13 @@ void GraphGridLayout::roughRouting(GraphGridLayout::LayoutState &state) const
target.inputCount); target.inputCount);
edge.points.front().spacingOverride = startSpacingOverride; edge.points.front().spacingOverride = startSpacingOverride;
edge.points.back().spacingOverride = targetSpacingOverride; 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; int length = 0;

View 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);
}

View 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

View File

@ -42,6 +42,10 @@ public:
virtual ~GraphLayout() {} virtual ~GraphLayout() {}
virtual void CalculateLayout(Graph &blocks, ut64 entry, int &width, virtual void CalculateLayout(Graph &blocks, ut64 entry, int &width,
int &height) const = 0; int &height) const = 0;
virtual void setLayoutConfig(const LayoutConfig &config)
{
this->layoutConfig = config;
};
protected: protected:
LayoutConfig layoutConfig; LayoutConfig layoutConfig;
}; };

View File

@ -4,6 +4,7 @@
#ifdef CUTTER_ENABLE_GRAPHVIZ #ifdef CUTTER_ENABLE_GRAPHVIZ
#include "GraphvizLayout.h" #include "GraphvizLayout.h"
#endif #endif
#include "GraphHorizontalAdapter.h"
#include "Helpers.h" #include "Helpers.h"
#include <vector> #include <vector>
@ -36,7 +37,7 @@ GraphView::GraphView(QWidget *parent)
glWidget = nullptr; glWidget = nullptr;
} }
#endif #endif
setGraphLayout(Layout::GridMedium); setGraphLayout(makeGraphLayout(Layout::GridMedium));
} }
GraphView::~GraphView() GraphView::~GraphView()
@ -513,36 +514,45 @@ QPoint GraphView::viewToLogicalCoordinates(QPoint p)
return p / current_scale + offset; 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) { switch (layout) {
case Layout::GridNarrow: case Layout::GridNarrow:
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Narrow)); result.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Narrow));
break; break;
case Layout::GridMedium: case Layout::GridMedium:
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Medium)); result.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Medium));
break; break;
case Layout::GridWide: case Layout::GridWide:
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Wide)); result.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Wide));
break; break;
#ifdef CUTTER_ENABLE_GRAPHVIZ #ifdef CUTTER_ENABLE_GRAPHVIZ
case Layout::GraphvizOrtho: case Layout::GraphvizOrtho:
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho)); result.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho,
break; horizontal ? GraphvizLayout::Direction::LR : GraphvizLayout::Direction::TB));
case Layout::GraphvizOrthoLR: needAdapter = false;
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho,
GraphvizLayout::Direction::LR));
break; break;
case Layout::GraphvizPolyline: case Layout::GraphvizPolyline:
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline)); result.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline,
break; horizontal ? GraphvizLayout::Direction::LR : GraphvizLayout::Direction::TB));
case Layout::GraphvizPolylineLR: needAdapter = false;
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline,
GraphvizLayout::Direction::LR));
break; break;
#endif #endif
} }
if (needAdapter && horizontal) {
result.reset(new GraphHorizontalAdapter(std::move(result)));
}
return result;
} }
void GraphView::addBlock(GraphView::GraphBlock block) void GraphView::addBlock(GraphView::GraphBlock block)

View File

@ -44,11 +44,10 @@ public:
, GridWide , GridWide
#ifdef CUTTER_ENABLE_GRAPHVIZ #ifdef CUTTER_ENABLE_GRAPHVIZ
, GraphvizOrtho , GraphvizOrtho
, GraphvizOrthoLR
, GraphvizPolyline , GraphvizPolyline
, GraphvizPolylineLR
#endif #endif
}; };
static std::unique_ptr<GraphLayout> makeGraphLayout(Layout layout, bool horizontal = false);
struct EdgeConfiguration { struct EdgeConfiguration {
QColor color = QColor(128, 128, 128); QColor color = QColor(128, 128, 128);
@ -77,8 +76,8 @@ public:
GraphView::GraphBlock *getBlockContaining(QPoint p); GraphView::GraphBlock *getBlockContaining(QPoint p);
QPoint viewToLogicalCoordinates(QPoint p); QPoint viewToLogicalCoordinates(QPoint p);
void setGraphLayout(Layout layout); void setGraphLayout(std::unique_ptr<GraphLayout> layout);
Layout getGraphLayout() const { return graphLayout; } GraphLayout& getGraphLayout() const { return *graphLayoutSystem; }
void paint(QPainter &p, QPoint offset, QRect area, qreal scale = 1.0, bool interactive = true); void paint(QPainter &p, QPoint offset, QRect area, qreal scale = 1.0, bool interactive = true);
@ -174,7 +173,6 @@ 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