Add options for tweaking graph layout. (#2246)

* Fix overview refresh when switching layout.
This commit is contained in:
karliss 2020-06-16 13:43:45 +03:00 committed by GitHub
parent 1d8c9de37c
commit e28ee3bebd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 208 additions and 55 deletions

View File

@ -758,6 +758,22 @@ void Configuration::setBitmapExportScaleFactor(double inputValueGraph)
s.setValue("bitmapGraphExportScale", inputValueGraph); s.setValue("bitmapGraphExportScale", inputValueGraph);
} }
void Configuration::setGraphSpacing(QPoint blockSpacing, QPoint edgeSpacing)
{
s.setValue("graph.blockSpacing", blockSpacing);
s.setValue("graph.edgeSpacing", edgeSpacing);
}
QPoint Configuration::getGraphBlockSpacing()
{
return s.value("graph.blockSpacing", QPoint(10, 40)).value<QPoint>();
}
QPoint Configuration::getGraphEdgeSpacing()
{
return s.value("graph.edgeSpacing", QPoint(10, 10)).value<QPoint>();
}
void Configuration::setOutputRedirectionEnabled(bool enabled) void Configuration::setOutputRedirectionEnabled(bool enabled)
{ {
this->outputRedirectEnabled = enabled; this->outputRedirectEnabled = enabled;

View File

@ -43,6 +43,7 @@ private:
#endif #endif
bool outputRedirectEnabled = true; bool outputRedirectEnabled = true;
Configuration();
// Colors // Colors
void loadBaseThemeNative(); void loadBaseThemeNative();
void loadBaseThemeDark(); void loadBaseThemeDark();
@ -60,7 +61,6 @@ public:
static const QHash<QString, QHash<ColorFlags, QColor>> cutterOptionColors; static const QHash<QString, QHash<ColorFlags, QColor>> cutterOptionColors;
// Functions // Functions
Configuration();
static Configuration *instance(); static Configuration *instance();
void loadInitial(); void loadInitial();
@ -129,16 +129,6 @@ public:
// Asm Options // Asm Options
void resetToDefaultAsmOptions(); void resetToDefaultAsmOptions();
// Graph
int getGraphBlockMaxChars() const
{
return s.value("graph.maxcols", 100).toInt();
}
void setGraphBlockMaxChars(int ch)
{
s.setValue("graph.maxcols", ch);
}
QString getColorTheme() const { return s.value("theme", "cutter").toString(); } QString getColorTheme() const { return s.value("theme", "cutter").toString(); }
void setColorTheme(const QString &theme); void setColorTheme(const QString &theme);
/** /**
@ -179,6 +169,16 @@ public:
bool getDecompilerAutoRefreshEnabled(); bool getDecompilerAutoRefreshEnabled();
void setDecompilerAutoRefreshEnabled(bool enabled); void setDecompilerAutoRefreshEnabled(bool enabled);
// Graph
int getGraphBlockMaxChars() const
{
return s.value("graph.maxcols", 100).toInt();
}
void setGraphBlockMaxChars(int ch)
{
s.setValue("graph.maxcols", ch);
}
/** /**
* @brief Getters and setters for the transaparent option state and scale factor for bitmap graph exports. * @brief Getters and setters for the transaparent option state and scale factor for bitmap graph exports.
*/ */
@ -186,6 +186,9 @@ public:
double getBitmapExportScaleFactor(); double getBitmapExportScaleFactor();
void setBitmapTransparentState(bool inputValueGraph); void setBitmapTransparentState(bool inputValueGraph);
void setBitmapExportScaleFactor(double inputValueGraph); void setBitmapExportScaleFactor(double inputValueGraph);
void setGraphSpacing(QPoint blockSpacing, QPoint edgeSpacing);
QPoint getGraphBlockSpacing();
QPoint getGraphEdgeSpacing();
/** /**
* @brief Enable or disable Cutter output redirection. * @brief Enable or disable Cutter output redirection.

View File

@ -21,7 +21,15 @@ GraphOptionsWidget::GraphOptionsWidget(PreferencesDialog *dialog)
connect<void(QDoubleSpinBox::*)(double)>(ui->bitmapGraphScale, (&QDoubleSpinBox::valueChanged), this, &GraphOptionsWidget::bitmapGraphScaleValueChanged); connect<void(QDoubleSpinBox::*)(double)>(ui->bitmapGraphScale, (&QDoubleSpinBox::valueChanged), this, &GraphOptionsWidget::bitmapGraphScaleValueChanged);
connect(ui->checkTransparent, &QCheckBox::stateChanged, this, &GraphOptionsWidget::checkTransparentStateChanged); connect(ui->checkTransparent, &QCheckBox::stateChanged, this, &GraphOptionsWidget::checkTransparentStateChanged);
connect(Core(), SIGNAL(graphOptionsChanged()), this, SLOT(updateOptionsFromVars())); connect(Core(), &CutterCore::graphOptionsChanged, this, &GraphOptionsWidget::updateOptionsFromVars);
QSpinBox* graphSpacingWidgets[] = {
ui->horizontalEdgeSpacing, ui->horizontalBlockSpacing,
ui->verticalEdgeSpacing, ui->verticalBlockSpacing
};
for (auto widget: graphSpacingWidgets) {
connect<void(QSpinBox::*)(int)>(widget, &QSpinBox::valueChanged,
this, &GraphOptionsWidget::layoutSpacingChanged);
}
} }
GraphOptionsWidget::~GraphOptionsWidget() {} GraphOptionsWidget::~GraphOptionsWidget() {}
@ -67,3 +75,11 @@ void GraphOptionsWidget::bitmapGraphScaleValueChanged(double value)
Config()->setBitmapExportScaleFactor(value_decimal); Config()->setBitmapExportScaleFactor(value_decimal);
} }
void GraphOptionsWidget::layoutSpacingChanged()
{
QPoint blockSpacing{ui->horizontalBlockSpacing->value(), ui->verticalBlockSpacing->value()};
QPoint edgeSpacing{ui->horizontalEdgeSpacing->value(), ui->verticalEdgeSpacing->value()};
Config()->setGraphSpacing(blockSpacing, edgeSpacing);
triggerOptionsChanged();
}

View File

@ -34,7 +34,7 @@ private slots:
void checkTransparentStateChanged(int checked); void checkTransparentStateChanged(int checked);
void bitmapGraphScaleValueChanged(double value); void bitmapGraphScaleValueChanged(double value);
void layoutSpacingChanged();
}; };

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>557</width> <width>403</width>
<height>175</height> <height>296</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -65,43 +65,139 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QWidget" name="widget" native="true"> <widget class="QCheckBox" name="graphOffsetCheckBox">
<property name="sizePolicy"> <property name="text">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding"> <string>Show offsets (graph.offset)</string>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<widget class="QCheckBox" name="graphOffsetCheckBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>208</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Show offsets (graph.offset)</string>
</property>
</widget>
<widget class="QCheckBox" name="checkTransparent">
<property name="geometry">
<rect>
<x>0</x>
<y>30</y>
<width>361</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Export Transparent Bitmap Graphs</string>
</property>
</widget>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="checkTransparent">
<property name="text">
<string>Export Transparent Bitmap Graphs</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Layout</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Horizontal</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Block spacing</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Edge spacing</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="horizontalBlockSpacing">
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="verticalBlockSpacing">
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>40</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="horizontalEdgeSpacing">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QSpinBox" name="verticalEdgeSpacing">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Vertical</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
<tabstops>
<tabstop>maxColsSpinBox</tabstop>
<tabstop>bitmapGraphScale</tabstop>
<tabstop>graphOffsetCheckBox</tabstop>
<tabstop>checkTransparent</tabstop>
<tabstop>horizontalBlockSpacing</tabstop>
<tabstop>verticalBlockSpacing</tabstop>
<tabstop>horizontalEdgeSpacing</tabstop>
<tabstop>verticalEdgeSpacing</tabstop>
</tabstops>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@ -215,8 +215,8 @@ DisassemblerGraphView::~DisassemblerGraphView()
void DisassemblerGraphView::refreshView() void DisassemblerGraphView::refreshView()
{ {
initFont(); initFont();
setLayoutConfig(getLayoutConfig());
loadCurrentGraph(); loadCurrentGraph();
viewport()->update();
emit viewRefreshed(); emit viewRefreshed();
} }
@ -355,7 +355,7 @@ void DisassemblerGraphView::loadCurrentGraph()
cleanupEdges(); cleanupEdges();
if (!func["blocks"].toArray().isEmpty()) { if (!func["blocks"].toArray().isEmpty()) {
computeGraph(entry); computeGraphPlacement();
} }
} }
@ -877,6 +877,18 @@ void DisassemblerGraphView::seekInstruction(bool previous_instr)
} }
} }
GraphLayout::LayoutConfig DisassemblerGraphView::getLayoutConfig()
{
auto blockSpacing = Config()->getGraphBlockSpacing();
auto edgeSpacing = Config()->getGraphEdgeSpacing();
GraphLayout::LayoutConfig layoutConfig;
layoutConfig.blockHorizontalSpacing = blockSpacing.x();
layoutConfig.blockVerticalSpacing = blockSpacing.y();
layoutConfig.edgeHorizontalSpacing = edgeSpacing.x();
layoutConfig.edgeVerticalSpacing = edgeSpacing.y();
return layoutConfig;
}
void DisassemblerGraphView::nextInstr() void DisassemblerGraphView::nextInstr()
{ {
seekInstruction(false); seekInstruction(false);
@ -1161,7 +1173,9 @@ void DisassemblerGraphView::onActionUnhighlightBITriggered()
void DisassemblerGraphView::updateLayout() void DisassemblerGraphView::updateLayout()
{ {
setGraphLayout(GraphView::makeGraphLayout(graphLayout, horizontalLayoutAction->isChecked())); setGraphLayout(GraphView::makeGraphLayout(graphLayout, horizontalLayoutAction->isChecked()));
refreshView(); setLayoutConfig(getLayoutConfig());
computeGraphPlacement();
emit viewRefreshed();
onSeekChanged(this->seekable->getOffset()); // try to keep the view on current block onSeekChanged(this->seekable->getOffset()); // try to keep the view on current block
} }

View File

@ -203,6 +203,8 @@ private:
DisassemblyBlock *blockForAddress(RVA addr); DisassemblyBlock *blockForAddress(RVA addr);
void seekLocal(RVA addr, bool update_viewport = true); void seekLocal(RVA addr, bool update_viewport = true);
void seekInstruction(bool previous_instr); void seekInstruction(bool previous_instr);
GraphLayout::LayoutConfig getLayoutConfig();
CutterSeekable *seekable = nullptr; CutterSeekable *seekable = nullptr;
QList<QShortcut *> shortcuts; QList<QShortcut *> shortcuts;
QList<RVA> breakpoints; QList<RVA> breakpoints;

View File

@ -130,12 +130,11 @@ void GraphView::contextMenuEvent(QContextMenuEvent *event)
} }
} }
// This calculates the full graph starting at block entry. void GraphView::computeGraphPlacement()
void GraphView::computeGraph(ut64 entry)
{ {
graphLayoutSystem->CalculateLayout(blocks, entry, width, height); graphLayoutSystem->CalculateLayout(blocks, entry, width, height);
setCacheDirty();
ready = true; ready = true;
viewport()->update(); viewport()->update();
} }
@ -428,7 +427,6 @@ void GraphView::saveAsSvg(QString path)
p.end(); p.end();
} }
void GraphView::center() void GraphView::center()
{ {
centerX(false); centerX(false);
@ -522,6 +520,11 @@ void GraphView::setGraphLayout(std::unique_ptr<GraphLayout> layout)
} }
} }
void GraphView::setLayoutConfig(const GraphLayout::LayoutConfig &config)
{
graphLayoutSystem->setLayoutConfig(config);
}
std::unique_ptr<GraphLayout> GraphView::makeGraphLayout(GraphView::Layout layout, bool horizontal) std::unique_ptr<GraphLayout> GraphView::makeGraphLayout(GraphView::Layout layout, bool horizontal)
{ {
std::unique_ptr<GraphLayout> result; std::unique_ptr<GraphLayout> result;

View File

@ -78,11 +78,14 @@ public:
void setGraphLayout(std::unique_ptr<GraphLayout> layout); void setGraphLayout(std::unique_ptr<GraphLayout> layout);
GraphLayout& getGraphLayout() const { return *graphLayoutSystem; } GraphLayout& getGraphLayout() const { return *graphLayoutSystem; }
void setLayoutConfig(const GraphLayout::LayoutConfig& config);
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);
void saveAsBitmap(QString path, const char *format = nullptr, double scaler = 1.0, bool transparent = false); void saveAsBitmap(QString path, const char *format = nullptr, double scaler = 1.0, bool transparent = false);
void saveAsSvg(QString path); void saveAsSvg(QString path);
void computeGraphPlacement();
protected: protected:
std::unordered_map<ut64, GraphBlock> blocks; std::unordered_map<ut64, GraphBlock> blocks;
QColor backgroundColor = QColor(Qt::white); QColor backgroundColor = QColor(Qt::white);
@ -94,7 +97,6 @@ protected:
void addBlock(GraphView::GraphBlock block); void addBlock(GraphView::GraphBlock block);
void setEntry(ut64 e); void setEntry(ut64 e);
void computeGraph(ut64 entry);
// Callbacks that should be overridden // Callbacks that should be overridden
/** /**

View File

@ -176,6 +176,7 @@ void GraphvizLayout::CalculateLayout(std::unordered_map<ut64, GraphBlock> &block
if (auto spl = ED_spl(e)) { if (auto spl = ED_spl(e)) {
for (int i = 0; i < 1 && i < spl->size; i++) { for (int i = 0; i < 1 && i < spl->size; i++) {
auto bz = spl->list[i]; auto bz = spl->list[i];
edge.polyline.clear();
edge.polyline.reserve(bz.size + 1); edge.polyline.reserve(bz.size + 1);
for (int j = 0; j < bz.size; j++) { for (int j = 0; j < bz.size; j++) {
edge.polyline.push_back(QPointF(bz.list[j].x, bz.list[j].y)); edge.polyline.push_back(QPointF(bz.list[j].x, bz.list[j].y));