mirror of
https://github.com/rizinorg/cutter.git
synced 2025-01-31 00:35:05 +00:00
Graph export without graphviz (#1773)
This commit is contained in:
parent
9e680d772b
commit
8287e426ba
@ -374,7 +374,8 @@ SOURCES += \
|
||||
common/Decompiler.cpp \
|
||||
menus/AddressableItemContextMenu.cpp \
|
||||
common/AddressableItemModel.cpp \
|
||||
widgets/ListDockWidget.cpp
|
||||
widgets/ListDockWidget.cpp \
|
||||
dialogs/MultitypeFileSaveDialog.cpp
|
||||
|
||||
GRAPHVIZ_SOURCES = \
|
||||
widgets/GraphvizLayout.cpp
|
||||
@ -508,7 +509,8 @@ HEADERS += \
|
||||
menus/AddressableItemContextMenu.h \
|
||||
common/AddressableItemModel.h \
|
||||
widgets/ListDockWidget.h \
|
||||
widgets/AddressableItemList.h
|
||||
widgets/AddressableItemList.h \
|
||||
dialogs/MultitypeFileSaveDialog.h
|
||||
|
||||
GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h
|
||||
|
||||
|
103
src/dialogs/MultitypeFileSaveDialog.cpp
Normal file
103
src/dialogs/MultitypeFileSaveDialog.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include "CutterConfig.h"
|
||||
|
||||
#include "MultitypeFileSaveDialog.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
|
||||
MultitypeFileSaveDialog::MultitypeFileSaveDialog(QWidget *parent,
|
||||
const QString &caption,
|
||||
const QString &directory)
|
||||
: QFileDialog(parent, caption, directory)
|
||||
{
|
||||
this->setAcceptMode(AcceptMode::AcceptSave);
|
||||
this->setFileMode(QFileDialog::AnyFile);
|
||||
|
||||
connect(this, &QFileDialog::filterSelected, this, &MultitypeFileSaveDialog::onFilterSelected);
|
||||
}
|
||||
|
||||
void MultitypeFileSaveDialog::setTypes(const QVector<MultitypeFileSaveDialog::TypeDescription>
|
||||
types, bool useDetection)
|
||||
{
|
||||
this->hasTypeDetection = useDetection;
|
||||
this->types.clear();
|
||||
this->types.reserve(types.size() + (useDetection ? 1 : 0));
|
||||
if (useDetection) {
|
||||
this->types.push_back(TypeDescription{tr("Detect type (*)"), "", QVariant()});
|
||||
}
|
||||
this->types.append(types);
|
||||
QStringList filters;
|
||||
for (auto &type : this->types) {
|
||||
filters.append(type.description);
|
||||
}
|
||||
setNameFilters(filters);
|
||||
onFilterSelected(this->types.first().description);
|
||||
}
|
||||
|
||||
MultitypeFileSaveDialog::TypeDescription MultitypeFileSaveDialog::selectedType() const
|
||||
{
|
||||
auto filterIt = findType(this->selectedNameFilter());
|
||||
if (filterIt == this->types.end()) {
|
||||
return {};
|
||||
}
|
||||
if (hasTypeDetection && filterIt == this->types.begin()) {
|
||||
QFileInfo info(this->selectedFiles().first());
|
||||
QString currentSuffix = info.suffix();
|
||||
filterIt = std::find_if(types.begin(), types.end(), [¤tSuffix](const TypeDescription & v) {
|
||||
return currentSuffix == v.extension;
|
||||
});
|
||||
if (filterIt != types.end()) {
|
||||
return *filterIt;
|
||||
}
|
||||
return {};
|
||||
} else {
|
||||
return *filterIt;
|
||||
}
|
||||
}
|
||||
|
||||
void MultitypeFileSaveDialog::done(int r)
|
||||
{
|
||||
if (r == QDialog::Accepted) {
|
||||
QFileInfo info(selectedFiles().first());
|
||||
auto selectedType = this->selectedType();
|
||||
if (selectedType.extension.isEmpty()) {
|
||||
QMessageBox::warning(this, tr("File save error"),
|
||||
tr("Unrecognized extension '%1'").arg(info.suffix()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
QFileDialog::done(r);
|
||||
}
|
||||
|
||||
void MultitypeFileSaveDialog::onFilterSelected(const QString &filter)
|
||||
{
|
||||
auto it = findType(filter);
|
||||
if (it == types.end()) {
|
||||
return;
|
||||
}
|
||||
bool detectionSelected = hasTypeDetection && it == types.begin();
|
||||
if (detectionSelected) {
|
||||
setDefaultSuffix(types[1].extension);
|
||||
} else {
|
||||
setDefaultSuffix(it->extension);
|
||||
}
|
||||
if (!this->selectedFiles().empty()) {
|
||||
QString currentSelection = this->selectedFiles().first();
|
||||
QFileInfo info(currentSelection);
|
||||
if (!detectionSelected) {
|
||||
QString currentSuffix = info.suffix();
|
||||
if (currentSuffix != it->extension) {
|
||||
selectFile(info.dir().filePath(info.completeBaseName() + "." + it->extension));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector<MultitypeFileSaveDialog::TypeDescription>::const_iterator
|
||||
MultitypeFileSaveDialog::findType(const QString &description) const
|
||||
{
|
||||
return std::find_if(types.begin(), types.end(),
|
||||
[&description](const TypeDescription & v) {
|
||||
return v.description == description;
|
||||
});
|
||||
}
|
35
src/dialogs/MultitypeFileSaveDialog.h
Normal file
35
src/dialogs/MultitypeFileSaveDialog.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef MULTITYPEFILESAVEDIALOG_H
|
||||
#define MULTITYPEFILESAVEDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFileDialog>
|
||||
#include <QVariant>
|
||||
|
||||
class MultitypeFileSaveDialog : public QFileDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct TypeDescription {
|
||||
QString description;
|
||||
QString extension;
|
||||
QVariant data;
|
||||
};
|
||||
|
||||
explicit MultitypeFileSaveDialog(QWidget *parent = nullptr,
|
||||
const QString &caption = QString(),
|
||||
const QString &directory = QString());
|
||||
|
||||
void setTypes(const QVector<TypeDescription> types, bool useDetection = true);
|
||||
TypeDescription selectedType() const;
|
||||
protected:
|
||||
void done(int r) override;
|
||||
private:
|
||||
void onFilterSelected(const QString &filter);
|
||||
QVector<TypeDescription>::const_iterator findType(const QString &description) const;
|
||||
|
||||
QVector<TypeDescription> types;
|
||||
bool hasTypeDetection;
|
||||
};
|
||||
|
||||
#endif // MULTITYPEFILESAVEDIALOG_H
|
@ -8,6 +8,7 @@
|
||||
#include "common/TempConfig.h"
|
||||
#include "common/SyntaxHighlighter.h"
|
||||
#include "common/BasicBlockHighlighter.h"
|
||||
#include "dialogs/MultitypeFileSaveDialog.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QPainter>
|
||||
@ -29,7 +30,8 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable* seekable, MainWindow* mainWindow)
|
||||
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable,
|
||||
MainWindow *mainWindow)
|
||||
: GraphView(parent),
|
||||
mFontMetrics(nullptr),
|
||||
blockMenu(new DisassemblyContextMenu(this, mainWindow)),
|
||||
@ -105,17 +107,17 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable* se
|
||||
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}
|
||||
, {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}
|
||||
, {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);
|
||||
QActionGroup *layoutGroup = new QActionGroup(layoutMenu);
|
||||
for (auto &item : LAYOUT_CONFIG) {
|
||||
auto action = layoutGroup->addAction(item.first);
|
||||
action->setCheckable(true);
|
||||
@ -353,7 +355,7 @@ DisassemblerGraphView::EdgeConfigurationMapping DisassemblerGraphView::getEdgeCo
|
||||
EdgeConfigurationMapping result;
|
||||
for (auto &block : blocks) {
|
||||
for (const auto &edge : block.second.edges) {
|
||||
result[ {block.first, edge.target}] = edgeConfiguration(block.second, &blocks[edge.target]);
|
||||
result[ {block.first, edge.target}] = edgeConfiguration(block.second, &blocks[edge.target], false);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -417,17 +419,16 @@ void DisassemblerGraphView::initFont()
|
||||
mFontMetrics.reset(new CachedFontMetrics<qreal>(font()));
|
||||
}
|
||||
|
||||
void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive)
|
||||
{
|
||||
int blockX = block.x - getViewOffset().x();
|
||||
int blockY = block.y - getViewOffset().y();
|
||||
QRectF blockRect(block.x, block.y, block.width, block.height);
|
||||
|
||||
const qreal padding = 2 * charWidth;
|
||||
|
||||
p.setPen(Qt::black);
|
||||
p.setBrush(Qt::gray);
|
||||
p.setFont(Config()->getFont());
|
||||
p.drawRect(blockX, blockY, block.width, block.height);
|
||||
p.drawRect(blockRect);
|
||||
|
||||
breakpoints = Core()->getBreakpointsAddresses();
|
||||
|
||||
@ -441,7 +442,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
RVA addr = seekable->getOffset();
|
||||
RVA PCAddr = Core()->getProgramCounterValue();
|
||||
for (const Instr &instr : db.instrs) {
|
||||
if (instr.contains(addr)) {
|
||||
if (instr.contains(addr) && interactive) {
|
||||
block_selected = true;
|
||||
selected_instruction = instr.addr;
|
||||
}
|
||||
@ -470,17 +471,22 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
}
|
||||
|
||||
// Draw basic block background
|
||||
p.drawRect(blockX, blockY,
|
||||
block.width, block.height);
|
||||
p.drawRect(blockRect);
|
||||
auto bb = Core()->getBBHighlighter()->getBasicBlock(block.entry);
|
||||
if (bb) {
|
||||
QColor color(bb->color);
|
||||
p.setBrush(color);
|
||||
p.drawRect(blockX, blockY,
|
||||
block.width, block.height);
|
||||
p.drawRect(blockRect);
|
||||
}
|
||||
|
||||
const int firstInstructionY = blockY + getInstructionOffset(db, 0).y();
|
||||
const int firstInstructionY = block.y + getInstructionOffset(db, 0).y();
|
||||
|
||||
// Stop rendering text when it's too small
|
||||
auto transform = p.combinedTransform();
|
||||
QRect screenChar = transform.mapRect(QRect(0, 0, charWidth, charHeight));
|
||||
if (screenChar.width() * p.device()->devicePixelRatioF() < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw different background for selected instruction
|
||||
if (selected_instruction != RVA_INVALID) {
|
||||
@ -491,7 +497,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
}
|
||||
auto selected = instr.addr == selected_instruction;
|
||||
if (selected) {
|
||||
p.fillRect(QRect(static_cast<int>(blockX + charWidth), y,
|
||||
p.fillRect(QRect(static_cast<int>(block.x + charWidth), y,
|
||||
static_cast<int>(block.width - (10 + padding)),
|
||||
int(instr.text.lines.size()) * charHeight), disassemblySelectionColor);
|
||||
}
|
||||
@ -527,8 +533,8 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
|
||||
QColor selectionColor = ConfigColor("wordHighlight");
|
||||
|
||||
p.fillRect(QRectF(blockX + charWidth * 3 + widthBefore, y, highlightWidth,
|
||||
charHeight), selectionColor);
|
||||
p.fillRect(QRectF(block.x + charWidth * 3 + widthBefore, y, highlightWidth,
|
||||
charHeight), selectionColor);
|
||||
}
|
||||
|
||||
y += int(instr.text.lines.size()) * charHeight;
|
||||
@ -544,7 +550,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
}
|
||||
auto PC = instr.addr == PCAddr;
|
||||
if (PC) {
|
||||
p.fillRect(QRect(static_cast<int>(blockX + charWidth), y,
|
||||
p.fillRect(QRect(static_cast<int>(block.x + charWidth), y,
|
||||
static_cast<int>(block.width - (10 + padding)),
|
||||
int(instr.text.lines.size()) * charHeight), PCSelectionColor);
|
||||
}
|
||||
@ -552,52 +558,27 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
}
|
||||
}
|
||||
|
||||
qreal render_height = viewport()->size().height();
|
||||
|
||||
// Stop rendering text when it's too small
|
||||
if (charHeight * getViewScale() * p.device()->devicePixelRatioF() < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render node text
|
||||
auto x = blockX + padding;
|
||||
int y = blockY + getTextOffset(0).y();
|
||||
qreal lineHeightRender = charHeight;
|
||||
auto x = block.x + padding;
|
||||
int y = block.y + getTextOffset(0).y();
|
||||
for (auto &line : db.header_text.lines) {
|
||||
qreal lineYRender = y;
|
||||
lineYRender *= getViewScale();
|
||||
// Check if line does NOT intersects with view area
|
||||
if (0 > lineYRender + lineHeightRender
|
||||
|| render_height < lineYRender) {
|
||||
y += charHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
RichTextPainter::paintRichText<qreal>(&p, x, y, block.width, charHeight, 0, line,
|
||||
mFontMetrics.get());
|
||||
mFontMetrics.get());
|
||||
y += charHeight;
|
||||
}
|
||||
|
||||
for (const Instr &instr : db.instrs) {
|
||||
if (Core()->isBreakpoint(breakpoints, instr.addr)) {
|
||||
p.fillRect(QRect(static_cast<int>(blockX + charWidth), y,
|
||||
p.fillRect(QRect(static_cast<int>(block.x + charWidth), y,
|
||||
static_cast<int>(block.width - (10 + padding)),
|
||||
int(instr.text.lines.size()) * charHeight), ConfigColor("gui.breakpoint_background"));
|
||||
if (instr.addr == selected_instruction) {
|
||||
p.fillRect(QRect(static_cast<int>(blockX + charWidth), y,
|
||||
p.fillRect(QRect(static_cast<int>(block.x + charWidth), y,
|
||||
static_cast<int>(block.width - (10 + padding)),
|
||||
int(instr.text.lines.size()) * charHeight), disassemblySelectionColor);
|
||||
}
|
||||
}
|
||||
for (auto &line : instr.text.lines) {
|
||||
qreal lineYRender = y;
|
||||
lineYRender *= getViewScale();
|
||||
if (0 > lineYRender + lineHeightRender
|
||||
|| render_height < lineYRender) {
|
||||
y += charHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
int rectSize = qRound(charWidth);
|
||||
if (rectSize % 2) {
|
||||
rectSize++;
|
||||
@ -608,8 +589,8 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
Q_UNUSED(bpRect);
|
||||
|
||||
RichTextPainter::paintRichText<qreal>(&p, x + charWidth, y,
|
||||
block.width - charWidth, charHeight, 0, line,
|
||||
mFontMetrics.get());
|
||||
block.width - charWidth, charHeight, 0, line,
|
||||
mFontMetrics.get());
|
||||
y += charHeight;
|
||||
|
||||
}
|
||||
@ -617,7 +598,8 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
}
|
||||
|
||||
GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView::GraphBlock &from,
|
||||
GraphView::GraphBlock *to)
|
||||
GraphView::GraphBlock *to,
|
||||
bool interactive)
|
||||
{
|
||||
EdgeConfiguration ec;
|
||||
DisassemblyBlock &db = disassembly_blocks[from.entry];
|
||||
@ -630,10 +612,12 @@ GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView:
|
||||
}
|
||||
ec.start_arrow = false;
|
||||
ec.end_arrow = true;
|
||||
if (from.entry == currentBlockAddress) {
|
||||
ec.width_scale = 2.0;
|
||||
} else if (to->entry == currentBlockAddress) {
|
||||
ec.width_scale = 2.0;
|
||||
if (interactive) {
|
||||
if (from.entry == currentBlockAddress) {
|
||||
ec.width_scale = 2.0;
|
||||
} else if (to->entry == currentBlockAddress) {
|
||||
ec.width_scale = 2.0;
|
||||
}
|
||||
}
|
||||
return ec;
|
||||
}
|
||||
@ -1028,45 +1012,107 @@ void DisassemblerGraphView::blockTransitionedTo(GraphView::GraphBlock *to)
|
||||
}
|
||||
|
||||
|
||||
Q_DECLARE_METATYPE(DisassemblerGraphView::GraphExportType);
|
||||
|
||||
void DisassemblerGraphView::on_actionExportGraph_triggered()
|
||||
{
|
||||
QStringList filters;
|
||||
filters.append(tr("Graphiz dot (*.dot)"));
|
||||
if (!QStandardPaths::findExecutable("dot").isEmpty()
|
||||
|| !QStandardPaths::findExecutable("xdot").isEmpty()) {
|
||||
filters.append(tr("GIF (*.gif)"));
|
||||
filters.append(tr("PNG (*.png)"));
|
||||
filters.append(tr("JPEG (*.jpg)"));
|
||||
filters.append(tr("PostScript (*.ps)"));
|
||||
filters.append(tr("SVG (*.svg)"));
|
||||
filters.append(tr("JSON (*.json)"));
|
||||
QVector<MultitypeFileSaveDialog::TypeDescription> types = {
|
||||
{tr("PNG (*.png)"), "png", QVariant::fromValue(GraphExportType::Png)},
|
||||
{tr("JPEG (*.jpg)"), "jpg", QVariant::fromValue(GraphExportType::Jpeg)},
|
||||
{tr("SVG (*.svg)"), "svg", QVariant::fromValue(GraphExportType::Svg)}
|
||||
};
|
||||
bool hasGraphviz = !QStandardPaths::findExecutable("dot").isEmpty()
|
||||
|| !QStandardPaths::findExecutable("xdot").isEmpty();
|
||||
if (hasGraphviz) {
|
||||
types.append({
|
||||
{tr("Graphviz dot (*.dot)"), "dot", QVariant::fromValue(GraphExportType::GVDot)},
|
||||
{tr("Graphviz json (*.json)"), "json", QVariant::fromValue(GraphExportType::GVJson)},
|
||||
{tr("Graphviz gif (*.gif)"), "gif", QVariant::fromValue(GraphExportType::GVGif)},
|
||||
{tr("Graphviz png (*.png)"), "png", QVariant::fromValue(GraphExportType::GVPng)},
|
||||
{tr("Graphviz jpg (*.jpg)"), "jpg", QVariant::fromValue(GraphExportType::GVJpeg)},
|
||||
{tr("Graphviz PostScript (*.ps)"), "ps", QVariant::fromValue(GraphExportType::GVPostScript)},
|
||||
{tr("Graphviz svg (*.svg)"), "svg", QVariant::fromValue(GraphExportType::GVSvg)}
|
||||
});
|
||||
}
|
||||
|
||||
QFileDialog dialog(this, tr("Export Graph"));
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setNameFilters(filters);
|
||||
dialog.selectFile("graph");
|
||||
dialog.setDefaultSuffix("dot");
|
||||
QString defaultName = "graph";
|
||||
if (auto f = Core()->functionAt(currentFcnAddr)) {
|
||||
QString functionName = f->name;
|
||||
// don't confuse image type guessing and make c++ names somewhat usable
|
||||
functionName.replace(QRegularExpression("[.:]"), "_");
|
||||
functionName.remove(QRegularExpression("[^a-zA-Z0-9_].*"));
|
||||
if (!functionName.isEmpty()) {
|
||||
defaultName = functionName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MultitypeFileSaveDialog dialog(this, tr("Export Graph"));
|
||||
dialog.setTypes(types);
|
||||
dialog.selectFile(defaultName);
|
||||
if (!dialog.exec())
|
||||
return;
|
||||
int startIdx = dialog.selectedNameFilter().lastIndexOf("*.") + 2;
|
||||
int count = dialog.selectedNameFilter().length() - startIdx - 1;
|
||||
QString format = dialog.selectedNameFilter().mid(startIdx, count);
|
||||
QString fileName = dialog.selectedFiles()[0];
|
||||
if (format != "dot") {
|
||||
TempConfig tempConfig;
|
||||
tempConfig.set("graph.gv.format", format);
|
||||
qWarning() << Core()->cmd(QString("agfw \"%1\" @ $FB").arg(fileName));
|
||||
|
||||
auto selectedType = dialog.selectedType();
|
||||
if (!selectedType.data.canConvert<GraphExportType>()) {
|
||||
qWarning() << "Bad selected type, should not happen.";
|
||||
return;
|
||||
}
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
qWarning() << "Can't open file";
|
||||
return;
|
||||
QString filePath = dialog.selectedFiles().first();
|
||||
exportGraph(filePath, selectedType.data.value<GraphExportType>());
|
||||
|
||||
}
|
||||
|
||||
void DisassemblerGraphView::exportGraph(QString filePath, GraphExportType type)
|
||||
{
|
||||
switch (type) {
|
||||
case GraphExportType::Png:
|
||||
this->saveAsBitmap(filePath, "png");
|
||||
break;
|
||||
case GraphExportType::Jpeg:
|
||||
this->saveAsBitmap(filePath, "jpg");
|
||||
break;
|
||||
case GraphExportType::Svg:
|
||||
this->saveAsSvg(filePath);
|
||||
break;
|
||||
|
||||
case GraphExportType::GVDot: {
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
qWarning() << "Can't open file";
|
||||
return;
|
||||
}
|
||||
QTextStream fileOut(&file);
|
||||
fileOut << Core()->cmd(QString("agfd 0x%1").arg(currentFcnAddr, 0, 16));
|
||||
}
|
||||
QTextStream fileOut(&file);
|
||||
fileOut << Core()->cmd("agfd $FB");
|
||||
break;
|
||||
|
||||
case GraphExportType::GVJson:
|
||||
exportR2GraphvizGraph(filePath, "json");
|
||||
break;
|
||||
case GraphExportType::GVGif:
|
||||
exportR2GraphvizGraph(filePath, "gif");
|
||||
break;
|
||||
case GraphExportType::GVPng:
|
||||
exportR2GraphvizGraph(filePath, "png");
|
||||
break;
|
||||
case GraphExportType::GVJpeg:
|
||||
exportR2GraphvizGraph(filePath, "jpg");
|
||||
break;
|
||||
case GraphExportType::GVPostScript:
|
||||
exportR2GraphvizGraph(filePath, "ps");
|
||||
break;
|
||||
case GraphExportType::GVSvg:
|
||||
exportR2GraphvizGraph(filePath, "svg");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DisassemblerGraphView::exportR2GraphvizGraph(QString filePath, QString type)
|
||||
{
|
||||
TempConfig tempConfig;
|
||||
tempConfig.set("graph.gv.format", type);
|
||||
qWarning() << Core()->cmdRaw(QString("agfw \"%1\" @ 0x%2").arg(filePath).arg(currentFcnAddr, 0, 16));
|
||||
}
|
||||
|
||||
void DisassemblerGraphView::mousePressEvent(QMouseEvent *event)
|
||||
|
@ -87,17 +87,18 @@ class DisassemblerGraphView : public GraphView
|
||||
};
|
||||
|
||||
public:
|
||||
DisassemblerGraphView(QWidget *parent, CutterSeekable* seekable, MainWindow* mainWindow);
|
||||
DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable, MainWindow *mainWindow);
|
||||
~DisassemblerGraphView() override;
|
||||
std::unordered_map<ut64, DisassemblyBlock> disassembly_blocks;
|
||||
virtual void drawBlock(QPainter &p, GraphView::GraphBlock &block) override;
|
||||
virtual void drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive) override;
|
||||
virtual void blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) override;
|
||||
virtual void blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event,
|
||||
QPoint pos) override;
|
||||
virtual bool helpEvent(QHelpEvent *event) override;
|
||||
virtual void blockHelpEvent(GraphView::GraphBlock &block, QHelpEvent *event, QPoint pos) override;
|
||||
virtual GraphView::EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from,
|
||||
GraphView::GraphBlock *to) override;
|
||||
GraphView::GraphBlock *to,
|
||||
bool interactive) override;
|
||||
virtual void blockTransitionedTo(GraphView::GraphBlock *to) override;
|
||||
|
||||
void loadCurrentGraph();
|
||||
@ -109,6 +110,19 @@ public:
|
||||
using EdgeConfigurationMapping = std::map<std::pair<ut64, ut64>, EdgeConfiguration>;
|
||||
EdgeConfigurationMapping getEdgeConfigurations();
|
||||
|
||||
enum class GraphExportType {
|
||||
Png, Jpeg, Svg, GVDot, GVJson,
|
||||
GVGif, GVPng, GVJpeg, GVPostScript, GVSvg
|
||||
};
|
||||
void exportGraph(QString filePath, GraphExportType type);
|
||||
void exportR2GraphvizGraph(QString filePath, QString type);
|
||||
|
||||
/**
|
||||
* @brief keep the current addr of the fcn of Graph
|
||||
* 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 = RVA_INVALID; // TODO: make this less public
|
||||
public slots:
|
||||
void refreshView();
|
||||
void colorsUpdatedSlot();
|
||||
@ -210,7 +224,7 @@ signals:
|
||||
void viewZoomed();
|
||||
void graphMoved();
|
||||
void resized();
|
||||
void nameChanged(const QString& name);
|
||||
void nameChanged(const QString &name);
|
||||
|
||||
public:
|
||||
bool isGraphEmpty() { return emptyGraph; }
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <QMouseEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QSvgGenerator>
|
||||
|
||||
#ifndef QT_NO_OPENGL
|
||||
#include <QOpenGLContext>
|
||||
@ -39,14 +40,14 @@ GraphView::GraphView(QWidget *parent)
|
||||
|
||||
GraphView::~GraphView()
|
||||
{
|
||||
// TODO: Cleanups
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
void GraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
void GraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
Q_UNUSED(block);
|
||||
Q_UNUSED(p)
|
||||
Q_UNUSED(block)
|
||||
Q_UNUSED(interactive)
|
||||
qWarning() << "Draw block not overriden!";
|
||||
}
|
||||
|
||||
@ -99,10 +100,12 @@ void GraphView::blockTransitionedTo(GraphView::GraphBlock *to)
|
||||
}
|
||||
|
||||
GraphView::EdgeConfiguration GraphView::edgeConfiguration(GraphView::GraphBlock &from,
|
||||
GraphView::GraphBlock *to)
|
||||
GraphView::GraphBlock *to,
|
||||
bool interactive)
|
||||
{
|
||||
Q_UNUSED(from);
|
||||
Q_UNUSED(to);
|
||||
Q_UNUSED(from)
|
||||
Q_UNUSED(to)
|
||||
Q_UNUSED(interactive)
|
||||
qWarning() << "Edge configuration not overridden!";
|
||||
EdgeConfiguration ec;
|
||||
return ec;
|
||||
@ -128,15 +131,6 @@ void GraphView::computeGraph(ut64 entry)
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
QPolygonF GraphView::recalculatePolygon(QPolygonF polygon)
|
||||
{
|
||||
QPolygonF ret;
|
||||
for (int i = 0; i < polygon.size(); i++) {
|
||||
ret << QPointF(polygon[i].x() - offset.x(), polygon[i].y() - offset.y());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GraphView::beginMouseDrag(QMouseEvent *event)
|
||||
{
|
||||
scroll_base_x = event->x();
|
||||
@ -302,29 +296,37 @@ void GraphView::paintGraphCache()
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
}
|
||||
|
||||
int render_width = viewport()->width();
|
||||
int render_height = viewport()->height();
|
||||
paint(p, offset, this->viewport()->rect(), current_scale);
|
||||
|
||||
p.end();
|
||||
#ifndef QT_NO_OPENGL
|
||||
delete paintDevice;
|
||||
#endif
|
||||
}
|
||||
|
||||
void GraphView::paint(QPainter &p, QPoint offset, QRect viewport, qreal scale, bool interactive)
|
||||
{
|
||||
QPointF offsetF(offset.x(), offset.y());
|
||||
p.setBrush(backgroundColor);
|
||||
p.drawRect(viewport()->rect());
|
||||
p.drawRect(viewport);
|
||||
p.setBrush(Qt::black);
|
||||
|
||||
p.scale(current_scale, current_scale);
|
||||
int render_width = viewport.width();
|
||||
int render_height = viewport.height();
|
||||
|
||||
// window - rectangle in logical coordinates
|
||||
QRect window = QRect(offset, QSize(qRound(render_width / scale), qRound(render_height / scale)));
|
||||
p.setWindow(window);
|
||||
QRect windowF(window.x(), window.y(), window.width(), window.height());
|
||||
|
||||
for (auto &blockIt : blocks) {
|
||||
GraphBlock &block = blockIt.second;
|
||||
|
||||
qreal blockX = block.x * current_scale;
|
||||
qreal blockY = block.y * current_scale;
|
||||
qreal blockWidth = block.width * current_scale;
|
||||
qreal blockHeight = block.height * current_scale;
|
||||
QRectF blockRect(block.x, block.y, block.width, block.height);
|
||||
|
||||
// Check if block is visible by checking if block intersects with view area
|
||||
if (offset.x() * current_scale < blockX + blockWidth
|
||||
&& blockX < offset.x() * current_scale + render_width
|
||||
&& offset.y() * current_scale < blockY + blockHeight
|
||||
&& blockY < offset.y() * current_scale + render_height) {
|
||||
drawBlock(p, block);
|
||||
if (blockRect.intersects(windowF)) {
|
||||
drawBlock(p, block, interactive);
|
||||
}
|
||||
|
||||
p.setBrush(Qt::gray);
|
||||
@ -336,15 +338,15 @@ void GraphView::paintGraphCache()
|
||||
if (edge.polyline.empty()) {
|
||||
continue;
|
||||
}
|
||||
QPolygonF polyline = recalculatePolygon(edge.polyline);
|
||||
QPolygonF polyline = edge.polyline;
|
||||
EdgeConfiguration ec = edgeConfiguration(block, &blocks[edge.target]);
|
||||
QPen pen(ec.color);
|
||||
pen.setStyle(ec.lineStyle);
|
||||
pen.setWidthF(pen.width() * ec.width_scale);
|
||||
if (scale_thickness_multiplier * ec.width_scale > 1.01 && pen.widthF() * current_scale < 2) {
|
||||
pen.setWidthF(ec.width_scale / current_scale);
|
||||
if (scale_thickness_multiplier * ec.width_scale > 1.01 && pen.widthF() * scale < 2) {
|
||||
pen.setWidthF(ec.width_scale / scale);
|
||||
}
|
||||
if (pen.widthF() * current_scale < 2) {
|
||||
if (pen.widthF() * scale < 2) {
|
||||
pen.setWidth(0);
|
||||
}
|
||||
p.setPen(pen);
|
||||
@ -360,7 +362,7 @@ void GraphView::paintGraphCache()
|
||||
QPointF base = tip - dir * 6;
|
||||
arrow << base + 3 * dy;
|
||||
arrow << base - 3 * dy;
|
||||
p.drawConvexPolygon(recalculatePolygon(arrow));
|
||||
p.drawConvexPolygon(arrow);
|
||||
};
|
||||
|
||||
if (!polyline.empty()) {
|
||||
@ -371,32 +373,52 @@ void GraphView::paintGraphCache()
|
||||
if (ec.end_arrow) {
|
||||
auto lastPt = edge.polyline.last();
|
||||
QPointF dir(0, -1);
|
||||
switch(edge.arrow) {
|
||||
case GraphLayout::GraphEdge::Down:
|
||||
dir = QPointF(0, 1);
|
||||
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;
|
||||
switch (edge.arrow) {
|
||||
case GraphLayout::GraphEdge::Down:
|
||||
dir = QPointF(0, 1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphView::saveAsBitmap(QString path, const char *format)
|
||||
{
|
||||
QImage image(width, height, QImage::Format_RGB32);
|
||||
QPainter p;
|
||||
p.begin(&image);
|
||||
paint(p, QPoint(0, 0), image.rect(), 1.0, false);
|
||||
p.end();
|
||||
if (!image.save(path, format)) {
|
||||
qWarning() << "Could not save image";
|
||||
}
|
||||
}
|
||||
|
||||
void GraphView::saveAsSvg(QString path)
|
||||
{
|
||||
QSvgGenerator generator;
|
||||
generator.setFileName(path);
|
||||
generator.setSize(QSize(width, height));
|
||||
generator.setViewBox(QRect(0, 0, width, height));
|
||||
generator.setTitle("Cutter graph export");
|
||||
QPainter p;
|
||||
p.begin(&generator);
|
||||
paint(p, QPoint(0, 0), QRect(0, 0, width, height), 1.0, false);
|
||||
p.end();
|
||||
#ifndef QT_NO_OPENGL
|
||||
delete paintDevice;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@ -470,28 +492,30 @@ 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;
|
||||
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;
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -635,12 +659,6 @@ void GraphView::keyPressEvent(QKeyEvent *event)
|
||||
|
||||
void GraphView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
// TODO
|
||||
// if(event->button() == Qt::ForwardButton)
|
||||
// gotoNextSlot();
|
||||
// else if(event->button() == Qt::BackButton)
|
||||
// gotoPreviousSlot();
|
||||
|
||||
if (scroll_mode && (event->buttons() & (Qt::LeftButton | Qt::MiddleButton)) == 0) {
|
||||
scroll_mode = false;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
|
@ -35,13 +35,13 @@ public:
|
||||
|
||||
enum class Layout {
|
||||
GridNarrow
|
||||
,GridMedium
|
||||
,GridWide
|
||||
, GridMedium
|
||||
, GridWide
|
||||
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||
,GraphvizOrtho
|
||||
,GraphvizOrthoLR
|
||||
,GraphvizPolyline
|
||||
,GraphvizPolylineLR
|
||||
, GraphvizOrtho
|
||||
, GraphvizOrthoLR
|
||||
, GraphvizPolyline
|
||||
, GraphvizPolylineLR
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -65,16 +65,13 @@ public:
|
||||
*/
|
||||
void showRectangle(const QRect &rect, bool anywhere = false);
|
||||
|
||||
/**
|
||||
* @brief keep the current addr of the fcn of Graph
|
||||
* 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 = RVA_INVALID; // TODO: move application specific code out of graph view
|
||||
|
||||
void setGraphLayout(Layout layout);
|
||||
Layout getGraphLayout() const { return graphLayout; }
|
||||
|
||||
void paint(QPainter &p, QPoint offset, QRect area, qreal scale = 1.0, bool interactive = true);
|
||||
|
||||
void saveAsBitmap(QString path, const char *format = nullptr);
|
||||
void saveAsSvg(QString path);
|
||||
protected:
|
||||
std::unordered_map<ut64, GraphBlock> blocks;
|
||||
QColor backgroundColor = QColor(Qt::white);
|
||||
@ -89,14 +86,21 @@ protected:
|
||||
void computeGraph(ut64 entry);
|
||||
|
||||
// Callbacks that should be overridden
|
||||
virtual void drawBlock(QPainter &p, GraphView::GraphBlock &block);
|
||||
/**
|
||||
* @brief drawBlock
|
||||
* @param p painter object, not necesarily current widget
|
||||
* @param block
|
||||
* @param interactive - can be used for disabling elemnts during export
|
||||
*/
|
||||
virtual void drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive = true);
|
||||
virtual void blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos);
|
||||
virtual void blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos);
|
||||
virtual void blockHelpEvent(GraphView::GraphBlock &block, QHelpEvent *event, QPoint pos);
|
||||
virtual bool helpEvent(QHelpEvent *event);
|
||||
virtual void blockTransitionedTo(GraphView::GraphBlock *to);
|
||||
virtual void wheelEvent(QWheelEvent *event) override;
|
||||
virtual EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to);
|
||||
virtual EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to,
|
||||
bool interactive = true);
|
||||
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
@ -166,9 +170,7 @@ private:
|
||||
QSize getRequiredCacheSize();
|
||||
qreal getRequiredCacheDevicePixelRatioF();
|
||||
|
||||
QPolygonF recalculatePolygon(QPolygonF polygon);
|
||||
void beginMouseDrag(QMouseEvent *event);
|
||||
|
||||
public:
|
||||
QPoint getViewOffset() const { return offset; }
|
||||
void setViewOffset(QPoint offset);
|
||||
|
@ -52,17 +52,16 @@ void OverviewView::refreshView()
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void OverviewView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
void OverviewView::drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive)
|
||||
{
|
||||
int blockX = block.x - getViewOffset().x();
|
||||
int blockY = block.y - getViewOffset().y();
|
||||
Q_UNUSED(interactive)
|
||||
QRectF blockRect(block.x, block.y, block.width, block.height);
|
||||
|
||||
p.setPen(Qt::black);
|
||||
p.setBrush(Qt::gray);
|
||||
p.drawRect(blockX, blockY, block.width, block.height);
|
||||
p.drawRect(blockRect);
|
||||
p.setBrush(QColor(0, 0, 0, 100));
|
||||
p.drawRect(blockX + 2, blockY + 2,
|
||||
block.width, block.height);
|
||||
p.drawRect(blockRect.translated(2, 2));
|
||||
|
||||
// Draw basic block highlighting/tracing
|
||||
auto bb = Core()->getBBHighlighter()->getBasicBlock(block.entry);
|
||||
@ -74,8 +73,7 @@ void OverviewView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
||||
p.setBrush(disassemblyBackgroundColor);
|
||||
}
|
||||
p.setPen(QPen(graphNodeColor, 1));
|
||||
p.drawRect(blockX, blockY,
|
||||
block.width, block.height);
|
||||
p.drawRect(blockRect);
|
||||
}
|
||||
|
||||
void OverviewView::paintEvent(QPaintEvent *event)
|
||||
@ -131,8 +129,10 @@ void OverviewView::wheelEvent(QWheelEvent *event)
|
||||
}
|
||||
|
||||
GraphView::EdgeConfiguration OverviewView::edgeConfiguration(GraphView::GraphBlock &from,
|
||||
GraphView::GraphBlock *to)
|
||||
GraphView::GraphBlock *to,
|
||||
bool interactive)
|
||||
{
|
||||
Q_UNUSED(interactive)
|
||||
EdgeConfiguration ec;
|
||||
auto baseEcIt = edgeConfigurations.find({from.entry, to->entry});
|
||||
if (baseEcIt != edgeConfigurations.end())
|
||||
|
@ -34,6 +34,12 @@ public:
|
||||
|
||||
void centreRect();
|
||||
|
||||
/**
|
||||
* @brief keep the current addr of the fcn of Graph
|
||||
* 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 = RVA_INVALID; // TODO: make this less public
|
||||
public slots:
|
||||
/**
|
||||
* @brief scale and center all nodes in, then run update
|
||||
@ -97,7 +103,7 @@ private:
|
||||
/**
|
||||
* @brief draw the computed blocks passed by Graph
|
||||
*/
|
||||
virtual void drawBlock(QPainter &p, GraphView::GraphBlock &block) override;
|
||||
virtual void drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive) override;
|
||||
|
||||
/**
|
||||
* @brief override the edgeConfiguration so as to
|
||||
@ -105,7 +111,8 @@ private:
|
||||
* @return EdgeConfiguration
|
||||
*/
|
||||
virtual GraphView::EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from,
|
||||
GraphView::GraphBlock *to) override;
|
||||
GraphView::GraphBlock *to,
|
||||
bool interactive) override;
|
||||
|
||||
/**
|
||||
* @brief base background color changing depending on the theme
|
||||
|
Loading…
Reference in New Issue
Block a user