Graph export without graphviz (#1773)

This commit is contained in:
karliss 2019-09-19 08:19:50 +03:00 committed by GitHub
parent 9e680d772b
commit 8287e426ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 430 additions and 203 deletions

View File

@ -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

View 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(), [&currentSuffix](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;
});
}

View 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

View File

@ -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)

View File

@ -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; }

View File

@ -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);

View File

@ -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);

View File

@ -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())

View File

@ -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