mirror of
https://github.com/rizinorg/cutter.git
synced 2025-02-12 09:52: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 \
|
common/Decompiler.cpp \
|
||||||
menus/AddressableItemContextMenu.cpp \
|
menus/AddressableItemContextMenu.cpp \
|
||||||
common/AddressableItemModel.cpp \
|
common/AddressableItemModel.cpp \
|
||||||
widgets/ListDockWidget.cpp
|
widgets/ListDockWidget.cpp \
|
||||||
|
dialogs/MultitypeFileSaveDialog.cpp
|
||||||
|
|
||||||
GRAPHVIZ_SOURCES = \
|
GRAPHVIZ_SOURCES = \
|
||||||
widgets/GraphvizLayout.cpp
|
widgets/GraphvizLayout.cpp
|
||||||
@ -508,7 +509,8 @@ HEADERS += \
|
|||||||
menus/AddressableItemContextMenu.h \
|
menus/AddressableItemContextMenu.h \
|
||||||
common/AddressableItemModel.h \
|
common/AddressableItemModel.h \
|
||||||
widgets/ListDockWidget.h \
|
widgets/ListDockWidget.h \
|
||||||
widgets/AddressableItemList.h
|
widgets/AddressableItemList.h \
|
||||||
|
dialogs/MultitypeFileSaveDialog.h
|
||||||
|
|
||||||
GRAPHVIZ_HEADERS = widgets/GraphGridLayout.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/TempConfig.h"
|
||||||
#include "common/SyntaxHighlighter.h"
|
#include "common/SyntaxHighlighter.h"
|
||||||
#include "common/BasicBlockHighlighter.h"
|
#include "common/BasicBlockHighlighter.h"
|
||||||
|
#include "dialogs/MultitypeFileSaveDialog.h"
|
||||||
|
|
||||||
#include <QColorDialog>
|
#include <QColorDialog>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
@ -29,7 +30,8 @@
|
|||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable* seekable, MainWindow* mainWindow)
|
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable,
|
||||||
|
MainWindow *mainWindow)
|
||||||
: GraphView(parent),
|
: GraphView(parent),
|
||||||
mFontMetrics(nullptr),
|
mFontMetrics(nullptr),
|
||||||
blockMenu(new DisassemblyContextMenu(this, mainWindow)),
|
blockMenu(new DisassemblyContextMenu(this, mainWindow)),
|
||||||
@ -105,17 +107,17 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable* se
|
|||||||
contextMenu->addAction(&actionExportGraph);
|
contextMenu->addAction(&actionExportGraph);
|
||||||
static const std::pair<QString, GraphView::Layout> LAYOUT_CONFIG[] = {
|
static const std::pair<QString, GraphView::Layout> LAYOUT_CONFIG[] = {
|
||||||
{tr("Grid narrow"), GraphView::Layout::GridNarrow}
|
{tr("Grid narrow"), GraphView::Layout::GridNarrow}
|
||||||
,{tr("Grid medium"), GraphView::Layout::GridMedium}
|
, {tr("Grid medium"), GraphView::Layout::GridMedium}
|
||||||
,{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 polyline LR"), GraphView::Layout::GraphvizPolylineLR}
|
||||||
,{tr("Graphviz ortho"), GraphView::Layout::GraphvizOrtho}
|
, {tr("Graphviz ortho"), GraphView::Layout::GraphvizOrtho}
|
||||||
,{tr("Graphviz ortho LR"), GraphView::Layout::GraphvizOrthoLR}
|
, {tr("Graphviz ortho LR"), GraphView::Layout::GraphvizOrthoLR}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
auto layoutMenu = contextMenu->addMenu(tr("Layout"));
|
auto layoutMenu = contextMenu->addMenu(tr("Layout"));
|
||||||
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);
|
||||||
@ -353,7 +355,7 @@ DisassemblerGraphView::EdgeConfigurationMapping DisassemblerGraphView::getEdgeCo
|
|||||||
EdgeConfigurationMapping result;
|
EdgeConfigurationMapping result;
|
||||||
for (auto &block : blocks) {
|
for (auto &block : blocks) {
|
||||||
for (const auto &edge : block.second.edges) {
|
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;
|
return result;
|
||||||
@ -417,17 +419,16 @@ void DisassemblerGraphView::initFont()
|
|||||||
mFontMetrics.reset(new CachedFontMetrics<qreal>(font()));
|
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();
|
QRectF blockRect(block.x, block.y, block.width, block.height);
|
||||||
int blockY = block.y - getViewOffset().y();
|
|
||||||
|
|
||||||
const qreal padding = 2 * charWidth;
|
const qreal padding = 2 * charWidth;
|
||||||
|
|
||||||
p.setPen(Qt::black);
|
p.setPen(Qt::black);
|
||||||
p.setBrush(Qt::gray);
|
p.setBrush(Qt::gray);
|
||||||
p.setFont(Config()->getFont());
|
p.setFont(Config()->getFont());
|
||||||
p.drawRect(blockX, blockY, block.width, block.height);
|
p.drawRect(blockRect);
|
||||||
|
|
||||||
breakpoints = Core()->getBreakpointsAddresses();
|
breakpoints = Core()->getBreakpointsAddresses();
|
||||||
|
|
||||||
@ -441,7 +442,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
|||||||
RVA addr = seekable->getOffset();
|
RVA addr = seekable->getOffset();
|
||||||
RVA PCAddr = Core()->getProgramCounterValue();
|
RVA PCAddr = Core()->getProgramCounterValue();
|
||||||
for (const Instr &instr : db.instrs) {
|
for (const Instr &instr : db.instrs) {
|
||||||
if (instr.contains(addr)) {
|
if (instr.contains(addr) && interactive) {
|
||||||
block_selected = true;
|
block_selected = true;
|
||||||
selected_instruction = instr.addr;
|
selected_instruction = instr.addr;
|
||||||
}
|
}
|
||||||
@ -470,17 +471,22 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw basic block background
|
// Draw basic block background
|
||||||
p.drawRect(blockX, blockY,
|
p.drawRect(blockRect);
|
||||||
block.width, block.height);
|
|
||||||
auto bb = Core()->getBBHighlighter()->getBasicBlock(block.entry);
|
auto bb = Core()->getBBHighlighter()->getBasicBlock(block.entry);
|
||||||
if (bb) {
|
if (bb) {
|
||||||
QColor color(bb->color);
|
QColor color(bb->color);
|
||||||
p.setBrush(color);
|
p.setBrush(color);
|
||||||
p.drawRect(blockX, blockY,
|
p.drawRect(blockRect);
|
||||||
block.width, block.height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Draw different background for selected instruction
|
||||||
if (selected_instruction != RVA_INVALID) {
|
if (selected_instruction != RVA_INVALID) {
|
||||||
@ -491,7 +497,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
|||||||
}
|
}
|
||||||
auto selected = instr.addr == selected_instruction;
|
auto selected = instr.addr == selected_instruction;
|
||||||
if (selected) {
|
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)),
|
static_cast<int>(block.width - (10 + padding)),
|
||||||
int(instr.text.lines.size()) * charHeight), disassemblySelectionColor);
|
int(instr.text.lines.size()) * charHeight), disassemblySelectionColor);
|
||||||
}
|
}
|
||||||
@ -527,8 +533,8 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
|||||||
|
|
||||||
QColor selectionColor = ConfigColor("wordHighlight");
|
QColor selectionColor = ConfigColor("wordHighlight");
|
||||||
|
|
||||||
p.fillRect(QRectF(blockX + charWidth * 3 + widthBefore, y, highlightWidth,
|
p.fillRect(QRectF(block.x + charWidth * 3 + widthBefore, y, highlightWidth,
|
||||||
charHeight), selectionColor);
|
charHeight), selectionColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
y += int(instr.text.lines.size()) * charHeight;
|
y += int(instr.text.lines.size()) * charHeight;
|
||||||
@ -544,7 +550,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
|||||||
}
|
}
|
||||||
auto PC = instr.addr == PCAddr;
|
auto PC = instr.addr == PCAddr;
|
||||||
if (PC) {
|
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)),
|
static_cast<int>(block.width - (10 + padding)),
|
||||||
int(instr.text.lines.size()) * charHeight), PCSelectionColor);
|
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
|
// Render node text
|
||||||
auto x = blockX + padding;
|
auto x = block.x + padding;
|
||||||
int y = blockY + getTextOffset(0).y();
|
int y = block.y + getTextOffset(0).y();
|
||||||
qreal lineHeightRender = charHeight;
|
|
||||||
for (auto &line : db.header_text.lines) {
|
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,
|
RichTextPainter::paintRichText<qreal>(&p, x, y, block.width, charHeight, 0, line,
|
||||||
mFontMetrics.get());
|
mFontMetrics.get());
|
||||||
y += charHeight;
|
y += charHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const Instr &instr : db.instrs) {
|
for (const Instr &instr : db.instrs) {
|
||||||
if (Core()->isBreakpoint(breakpoints, instr.addr)) {
|
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)),
|
static_cast<int>(block.width - (10 + padding)),
|
||||||
int(instr.text.lines.size()) * charHeight), ConfigColor("gui.breakpoint_background"));
|
int(instr.text.lines.size()) * charHeight), ConfigColor("gui.breakpoint_background"));
|
||||||
if (instr.addr == selected_instruction) {
|
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)),
|
static_cast<int>(block.width - (10 + padding)),
|
||||||
int(instr.text.lines.size()) * charHeight), disassemblySelectionColor);
|
int(instr.text.lines.size()) * charHeight), disassemblySelectionColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto &line : instr.text.lines) {
|
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);
|
int rectSize = qRound(charWidth);
|
||||||
if (rectSize % 2) {
|
if (rectSize % 2) {
|
||||||
rectSize++;
|
rectSize++;
|
||||||
@ -608,8 +589,8 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
|||||||
Q_UNUSED(bpRect);
|
Q_UNUSED(bpRect);
|
||||||
|
|
||||||
RichTextPainter::paintRichText<qreal>(&p, x + charWidth, y,
|
RichTextPainter::paintRichText<qreal>(&p, x + charWidth, y,
|
||||||
block.width - charWidth, charHeight, 0, line,
|
block.width - charWidth, charHeight, 0, line,
|
||||||
mFontMetrics.get());
|
mFontMetrics.get());
|
||||||
y += charHeight;
|
y += charHeight;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -617,7 +598,8 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
|||||||
}
|
}
|
||||||
|
|
||||||
GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView::GraphBlock &from,
|
GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView::GraphBlock &from,
|
||||||
GraphView::GraphBlock *to)
|
GraphView::GraphBlock *to,
|
||||||
|
bool interactive)
|
||||||
{
|
{
|
||||||
EdgeConfiguration ec;
|
EdgeConfiguration ec;
|
||||||
DisassemblyBlock &db = disassembly_blocks[from.entry];
|
DisassemblyBlock &db = disassembly_blocks[from.entry];
|
||||||
@ -630,10 +612,12 @@ GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView:
|
|||||||
}
|
}
|
||||||
ec.start_arrow = false;
|
ec.start_arrow = false;
|
||||||
ec.end_arrow = true;
|
ec.end_arrow = true;
|
||||||
if (from.entry == currentBlockAddress) {
|
if (interactive) {
|
||||||
ec.width_scale = 2.0;
|
if (from.entry == currentBlockAddress) {
|
||||||
} else if (to->entry == currentBlockAddress) {
|
ec.width_scale = 2.0;
|
||||||
ec.width_scale = 2.0;
|
} else if (to->entry == currentBlockAddress) {
|
||||||
|
ec.width_scale = 2.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ec;
|
return ec;
|
||||||
}
|
}
|
||||||
@ -1028,45 +1012,107 @@ void DisassemblerGraphView::blockTransitionedTo(GraphView::GraphBlock *to)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(DisassemblerGraphView::GraphExportType);
|
||||||
|
|
||||||
void DisassemblerGraphView::on_actionExportGraph_triggered()
|
void DisassemblerGraphView::on_actionExportGraph_triggered()
|
||||||
{
|
{
|
||||||
QStringList filters;
|
QVector<MultitypeFileSaveDialog::TypeDescription> types = {
|
||||||
filters.append(tr("Graphiz dot (*.dot)"));
|
{tr("PNG (*.png)"), "png", QVariant::fromValue(GraphExportType::Png)},
|
||||||
if (!QStandardPaths::findExecutable("dot").isEmpty()
|
{tr("JPEG (*.jpg)"), "jpg", QVariant::fromValue(GraphExportType::Jpeg)},
|
||||||
|| !QStandardPaths::findExecutable("xdot").isEmpty()) {
|
{tr("SVG (*.svg)"), "svg", QVariant::fromValue(GraphExportType::Svg)}
|
||||||
filters.append(tr("GIF (*.gif)"));
|
};
|
||||||
filters.append(tr("PNG (*.png)"));
|
bool hasGraphviz = !QStandardPaths::findExecutable("dot").isEmpty()
|
||||||
filters.append(tr("JPEG (*.jpg)"));
|
|| !QStandardPaths::findExecutable("xdot").isEmpty();
|
||||||
filters.append(tr("PostScript (*.ps)"));
|
if (hasGraphviz) {
|
||||||
filters.append(tr("SVG (*.svg)"));
|
types.append({
|
||||||
filters.append(tr("JSON (*.json)"));
|
{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"));
|
QString defaultName = "graph";
|
||||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
if (auto f = Core()->functionAt(currentFcnAddr)) {
|
||||||
dialog.setFileMode(QFileDialog::AnyFile);
|
QString functionName = f->name;
|
||||||
dialog.setNameFilters(filters);
|
// don't confuse image type guessing and make c++ names somewhat usable
|
||||||
dialog.selectFile("graph");
|
functionName.replace(QRegularExpression("[.:]"), "_");
|
||||||
dialog.setDefaultSuffix("dot");
|
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())
|
if (!dialog.exec())
|
||||||
return;
|
return;
|
||||||
int startIdx = dialog.selectedNameFilter().lastIndexOf("*.") + 2;
|
|
||||||
int count = dialog.selectedNameFilter().length() - startIdx - 1;
|
auto selectedType = dialog.selectedType();
|
||||||
QString format = dialog.selectedNameFilter().mid(startIdx, count);
|
if (!selectedType.data.canConvert<GraphExportType>()) {
|
||||||
QString fileName = dialog.selectedFiles()[0];
|
qWarning() << "Bad selected type, should not happen.";
|
||||||
if (format != "dot") {
|
|
||||||
TempConfig tempConfig;
|
|
||||||
tempConfig.set("graph.gv.format", format);
|
|
||||||
qWarning() << Core()->cmd(QString("agfw \"%1\" @ $FB").arg(fileName));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QFile file(fileName);
|
QString filePath = dialog.selectedFiles().first();
|
||||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
exportGraph(filePath, selectedType.data.value<GraphExportType>());
|
||||||
qWarning() << "Can't open file";
|
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
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);
|
break;
|
||||||
fileOut << Core()->cmd("agfd $FB");
|
|
||||||
|
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)
|
void DisassemblerGraphView::mousePressEvent(QMouseEvent *event)
|
||||||
|
@ -87,17 +87,18 @@ class DisassemblerGraphView : public GraphView
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DisassemblerGraphView(QWidget *parent, CutterSeekable* seekable, MainWindow* mainWindow);
|
DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable, MainWindow *mainWindow);
|
||||||
~DisassemblerGraphView() override;
|
~DisassemblerGraphView() override;
|
||||||
std::unordered_map<ut64, DisassemblyBlock> disassembly_blocks;
|
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 blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) override;
|
||||||
virtual void blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event,
|
virtual void blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event,
|
||||||
QPoint pos) override;
|
QPoint pos) override;
|
||||||
virtual bool helpEvent(QHelpEvent *event) override;
|
virtual bool helpEvent(QHelpEvent *event) override;
|
||||||
virtual void blockHelpEvent(GraphView::GraphBlock &block, QHelpEvent *event, QPoint pos) override;
|
virtual void blockHelpEvent(GraphView::GraphBlock &block, QHelpEvent *event, QPoint pos) override;
|
||||||
virtual GraphView::EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from,
|
virtual GraphView::EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from,
|
||||||
GraphView::GraphBlock *to) override;
|
GraphView::GraphBlock *to,
|
||||||
|
bool interactive) override;
|
||||||
virtual void blockTransitionedTo(GraphView::GraphBlock *to) override;
|
virtual void blockTransitionedTo(GraphView::GraphBlock *to) override;
|
||||||
|
|
||||||
void loadCurrentGraph();
|
void loadCurrentGraph();
|
||||||
@ -109,6 +110,19 @@ public:
|
|||||||
using EdgeConfigurationMapping = std::map<std::pair<ut64, ut64>, EdgeConfiguration>;
|
using EdgeConfigurationMapping = std::map<std::pair<ut64, ut64>, EdgeConfiguration>;
|
||||||
EdgeConfigurationMapping getEdgeConfigurations();
|
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:
|
public slots:
|
||||||
void refreshView();
|
void refreshView();
|
||||||
void colorsUpdatedSlot();
|
void colorsUpdatedSlot();
|
||||||
@ -210,7 +224,7 @@ signals:
|
|||||||
void viewZoomed();
|
void viewZoomed();
|
||||||
void graphMoved();
|
void graphMoved();
|
||||||
void resized();
|
void resized();
|
||||||
void nameChanged(const QString& name);
|
void nameChanged(const QString &name);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool isGraphEmpty() { return emptyGraph; }
|
bool isGraphEmpty() { return emptyGraph; }
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QPropertyAnimation>
|
#include <QPropertyAnimation>
|
||||||
|
#include <QSvgGenerator>
|
||||||
|
|
||||||
#ifndef QT_NO_OPENGL
|
#ifndef QT_NO_OPENGL
|
||||||
#include <QOpenGLContext>
|
#include <QOpenGLContext>
|
||||||
@ -39,14 +40,14 @@ GraphView::GraphView(QWidget *parent)
|
|||||||
|
|
||||||
GraphView::~GraphView()
|
GraphView::~GraphView()
|
||||||
{
|
{
|
||||||
// TODO: Cleanups
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
void GraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
void GraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive)
|
||||||
{
|
{
|
||||||
Q_UNUSED(p);
|
Q_UNUSED(p)
|
||||||
Q_UNUSED(block);
|
Q_UNUSED(block)
|
||||||
|
Q_UNUSED(interactive)
|
||||||
qWarning() << "Draw block not overriden!";
|
qWarning() << "Draw block not overriden!";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,10 +100,12 @@ void GraphView::blockTransitionedTo(GraphView::GraphBlock *to)
|
|||||||
}
|
}
|
||||||
|
|
||||||
GraphView::EdgeConfiguration GraphView::edgeConfiguration(GraphView::GraphBlock &from,
|
GraphView::EdgeConfiguration GraphView::edgeConfiguration(GraphView::GraphBlock &from,
|
||||||
GraphView::GraphBlock *to)
|
GraphView::GraphBlock *to,
|
||||||
|
bool interactive)
|
||||||
{
|
{
|
||||||
Q_UNUSED(from);
|
Q_UNUSED(from)
|
||||||
Q_UNUSED(to);
|
Q_UNUSED(to)
|
||||||
|
Q_UNUSED(interactive)
|
||||||
qWarning() << "Edge configuration not overridden!";
|
qWarning() << "Edge configuration not overridden!";
|
||||||
EdgeConfiguration ec;
|
EdgeConfiguration ec;
|
||||||
return ec;
|
return ec;
|
||||||
@ -128,15 +131,6 @@ void GraphView::computeGraph(ut64 entry)
|
|||||||
viewport()->update();
|
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)
|
void GraphView::beginMouseDrag(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
scroll_base_x = event->x();
|
scroll_base_x = event->x();
|
||||||
@ -302,29 +296,37 @@ void GraphView::paintGraphCache()
|
|||||||
p.setRenderHint(QPainter::Antialiasing);
|
p.setRenderHint(QPainter::Antialiasing);
|
||||||
}
|
}
|
||||||
|
|
||||||
int render_width = viewport()->width();
|
paint(p, offset, this->viewport()->rect(), current_scale);
|
||||||
int render_height = viewport()->height();
|
|
||||||
|
|
||||||
|
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.setBrush(backgroundColor);
|
||||||
p.drawRect(viewport()->rect());
|
p.drawRect(viewport);
|
||||||
p.setBrush(Qt::black);
|
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) {
|
for (auto &blockIt : blocks) {
|
||||||
GraphBlock &block = blockIt.second;
|
GraphBlock &block = blockIt.second;
|
||||||
|
|
||||||
qreal blockX = block.x * current_scale;
|
QRectF blockRect(block.x, block.y, block.width, block.height);
|
||||||
qreal blockY = block.y * current_scale;
|
|
||||||
qreal blockWidth = block.width * current_scale;
|
|
||||||
qreal blockHeight = block.height * current_scale;
|
|
||||||
|
|
||||||
// Check if block is visible by checking if block intersects with view area
|
// Check if block is visible by checking if block intersects with view area
|
||||||
if (offset.x() * current_scale < blockX + blockWidth
|
if (blockRect.intersects(windowF)) {
|
||||||
&& blockX < offset.x() * current_scale + render_width
|
drawBlock(p, block, interactive);
|
||||||
&& offset.y() * current_scale < blockY + blockHeight
|
|
||||||
&& blockY < offset.y() * current_scale + render_height) {
|
|
||||||
drawBlock(p, block);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.setBrush(Qt::gray);
|
p.setBrush(Qt::gray);
|
||||||
@ -336,15 +338,15 @@ void GraphView::paintGraphCache()
|
|||||||
if (edge.polyline.empty()) {
|
if (edge.polyline.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QPolygonF polyline = recalculatePolygon(edge.polyline);
|
QPolygonF polyline = edge.polyline;
|
||||||
EdgeConfiguration ec = edgeConfiguration(block, &blocks[edge.target]);
|
EdgeConfiguration ec = edgeConfiguration(block, &blocks[edge.target]);
|
||||||
QPen pen(ec.color);
|
QPen pen(ec.color);
|
||||||
pen.setStyle(ec.lineStyle);
|
pen.setStyle(ec.lineStyle);
|
||||||
pen.setWidthF(pen.width() * ec.width_scale);
|
pen.setWidthF(pen.width() * ec.width_scale);
|
||||||
if (scale_thickness_multiplier * ec.width_scale > 1.01 && pen.widthF() * current_scale < 2) {
|
if (scale_thickness_multiplier * ec.width_scale > 1.01 && pen.widthF() * scale < 2) {
|
||||||
pen.setWidthF(ec.width_scale / current_scale);
|
pen.setWidthF(ec.width_scale / scale);
|
||||||
}
|
}
|
||||||
if (pen.widthF() * current_scale < 2) {
|
if (pen.widthF() * scale < 2) {
|
||||||
pen.setWidth(0);
|
pen.setWidth(0);
|
||||||
}
|
}
|
||||||
p.setPen(pen);
|
p.setPen(pen);
|
||||||
@ -360,7 +362,7 @@ void GraphView::paintGraphCache()
|
|||||||
QPointF base = tip - dir * 6;
|
QPointF base = tip - dir * 6;
|
||||||
arrow << base + 3 * dy;
|
arrow << base + 3 * dy;
|
||||||
arrow << base - 3 * dy;
|
arrow << base - 3 * dy;
|
||||||
p.drawConvexPolygon(recalculatePolygon(arrow));
|
p.drawConvexPolygon(arrow);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!polyline.empty()) {
|
if (!polyline.empty()) {
|
||||||
@ -371,32 +373,52 @@ void GraphView::paintGraphCache()
|
|||||||
if (ec.end_arrow) {
|
if (ec.end_arrow) {
|
||||||
auto lastPt = edge.polyline.last();
|
auto lastPt = edge.polyline.last();
|
||||||
QPointF dir(0, -1);
|
QPointF dir(0, -1);
|
||||||
switch(edge.arrow) {
|
switch (edge.arrow) {
|
||||||
case GraphLayout::GraphEdge::Down:
|
case GraphLayout::GraphEdge::Down:
|
||||||
dir = QPointF(0, 1);
|
dir = QPointF(0, 1);
|
||||||
break;
|
break;
|
||||||
case GraphLayout::GraphEdge::Up:
|
case GraphLayout::GraphEdge::Up:
|
||||||
dir = QPointF(0, -1);
|
dir = QPointF(0, -1);
|
||||||
break;
|
break;
|
||||||
case GraphLayout::GraphEdge::Left:
|
case GraphLayout::GraphEdge::Left:
|
||||||
dir = QPointF(-1, 0);
|
dir = QPointF(-1, 0);
|
||||||
break;
|
break;
|
||||||
case GraphLayout::GraphEdge::Right:
|
case GraphLayout::GraphEdge::Right:
|
||||||
dir = QPointF(1, 0);
|
dir = QPointF(1, 0);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
drawArrow(lastPt, dir);
|
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();
|
p.end();
|
||||||
#ifndef QT_NO_OPENGL
|
|
||||||
delete paintDevice;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -470,28 +492,30 @@ void GraphView::setGraphLayout(GraphView::Layout layout)
|
|||||||
{
|
{
|
||||||
graphLayout = layout;
|
graphLayout = layout;
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
case Layout::GridNarrow:
|
case Layout::GridNarrow:
|
||||||
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Narrow));
|
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Narrow));
|
||||||
break;
|
break;
|
||||||
case Layout::GridMedium:
|
case Layout::GridMedium:
|
||||||
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Medium));
|
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Medium));
|
||||||
break;
|
break;
|
||||||
case Layout::GridWide:
|
case Layout::GridWide:
|
||||||
this->graphLayoutSystem.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Wide));
|
this->graphLayoutSystem.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));
|
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho));
|
||||||
break;
|
break;
|
||||||
case Layout::GraphvizOrthoLR:
|
case Layout::GraphvizOrthoLR:
|
||||||
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho, GraphvizLayout::Direction::LR));
|
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho,
|
||||||
break;
|
GraphvizLayout::Direction::LR));
|
||||||
case Layout::GraphvizPolyline:
|
break;
|
||||||
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline));
|
case Layout::GraphvizPolyline:
|
||||||
break;
|
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline));
|
||||||
case Layout::GraphvizPolylineLR:
|
break;
|
||||||
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline, GraphvizLayout::Direction::LR));
|
case Layout::GraphvizPolylineLR:
|
||||||
break;
|
this->graphLayoutSystem.reset(new GraphvizLayout(GraphvizLayout::LineType::Polyline,
|
||||||
|
GraphvizLayout::Direction::LR));
|
||||||
|
break;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -635,12 +659,6 @@ void GraphView::keyPressEvent(QKeyEvent *event)
|
|||||||
|
|
||||||
void GraphView::mouseReleaseEvent(QMouseEvent *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) {
|
if (scroll_mode && (event->buttons() & (Qt::LeftButton | Qt::MiddleButton)) == 0) {
|
||||||
scroll_mode = false;
|
scroll_mode = false;
|
||||||
setCursor(Qt::ArrowCursor);
|
setCursor(Qt::ArrowCursor);
|
||||||
|
@ -35,13 +35,13 @@ public:
|
|||||||
|
|
||||||
enum class Layout {
|
enum class Layout {
|
||||||
GridNarrow
|
GridNarrow
|
||||||
,GridMedium
|
, GridMedium
|
||||||
,GridWide
|
, GridWide
|
||||||
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||||
,GraphvizOrtho
|
, GraphvizOrtho
|
||||||
,GraphvizOrthoLR
|
, GraphvizOrthoLR
|
||||||
,GraphvizPolyline
|
, GraphvizPolyline
|
||||||
,GraphvizPolylineLR
|
, GraphvizPolylineLR
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,16 +65,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
void showRectangle(const QRect &rect, bool anywhere = false);
|
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);
|
void setGraphLayout(Layout layout);
|
||||||
Layout getGraphLayout() const { return graphLayout; }
|
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:
|
protected:
|
||||||
std::unordered_map<ut64, GraphBlock> blocks;
|
std::unordered_map<ut64, GraphBlock> blocks;
|
||||||
QColor backgroundColor = QColor(Qt::white);
|
QColor backgroundColor = QColor(Qt::white);
|
||||||
@ -89,14 +86,21 @@ protected:
|
|||||||
void computeGraph(ut64 entry);
|
void computeGraph(ut64 entry);
|
||||||
|
|
||||||
// Callbacks that should be overridden
|
// 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 blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos);
|
||||||
virtual void blockDoubleClicked(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 void blockHelpEvent(GraphView::GraphBlock &block, QHelpEvent *event, QPoint pos);
|
||||||
virtual bool helpEvent(QHelpEvent *event);
|
virtual bool helpEvent(QHelpEvent *event);
|
||||||
virtual void blockTransitionedTo(GraphView::GraphBlock *to);
|
virtual void blockTransitionedTo(GraphView::GraphBlock *to);
|
||||||
virtual void wheelEvent(QWheelEvent *event) override;
|
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;
|
bool event(QEvent *event) override;
|
||||||
|
|
||||||
@ -166,9 +170,7 @@ private:
|
|||||||
QSize getRequiredCacheSize();
|
QSize getRequiredCacheSize();
|
||||||
qreal getRequiredCacheDevicePixelRatioF();
|
qreal getRequiredCacheDevicePixelRatioF();
|
||||||
|
|
||||||
QPolygonF recalculatePolygon(QPolygonF polygon);
|
|
||||||
void beginMouseDrag(QMouseEvent *event);
|
void beginMouseDrag(QMouseEvent *event);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QPoint getViewOffset() const { return offset; }
|
QPoint getViewOffset() const { return offset; }
|
||||||
void setViewOffset(QPoint offset);
|
void setViewOffset(QPoint offset);
|
||||||
|
@ -52,17 +52,16 @@ void OverviewView::refreshView()
|
|||||||
viewport()->update();
|
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();
|
Q_UNUSED(interactive)
|
||||||
int blockY = block.y - getViewOffset().y();
|
QRectF blockRect(block.x, block.y, block.width, block.height);
|
||||||
|
|
||||||
p.setPen(Qt::black);
|
p.setPen(Qt::black);
|
||||||
p.setBrush(Qt::gray);
|
p.setBrush(Qt::gray);
|
||||||
p.drawRect(blockX, blockY, block.width, block.height);
|
p.drawRect(blockRect);
|
||||||
p.setBrush(QColor(0, 0, 0, 100));
|
p.setBrush(QColor(0, 0, 0, 100));
|
||||||
p.drawRect(blockX + 2, blockY + 2,
|
p.drawRect(blockRect.translated(2, 2));
|
||||||
block.width, block.height);
|
|
||||||
|
|
||||||
// Draw basic block highlighting/tracing
|
// Draw basic block highlighting/tracing
|
||||||
auto bb = Core()->getBBHighlighter()->getBasicBlock(block.entry);
|
auto bb = Core()->getBBHighlighter()->getBasicBlock(block.entry);
|
||||||
@ -74,8 +73,7 @@ void OverviewView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
|
|||||||
p.setBrush(disassemblyBackgroundColor);
|
p.setBrush(disassemblyBackgroundColor);
|
||||||
}
|
}
|
||||||
p.setPen(QPen(graphNodeColor, 1));
|
p.setPen(QPen(graphNodeColor, 1));
|
||||||
p.drawRect(blockX, blockY,
|
p.drawRect(blockRect);
|
||||||
block.width, block.height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverviewView::paintEvent(QPaintEvent *event)
|
void OverviewView::paintEvent(QPaintEvent *event)
|
||||||
@ -131,8 +129,10 @@ void OverviewView::wheelEvent(QWheelEvent *event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
GraphView::EdgeConfiguration OverviewView::edgeConfiguration(GraphView::GraphBlock &from,
|
GraphView::EdgeConfiguration OverviewView::edgeConfiguration(GraphView::GraphBlock &from,
|
||||||
GraphView::GraphBlock *to)
|
GraphView::GraphBlock *to,
|
||||||
|
bool interactive)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(interactive)
|
||||||
EdgeConfiguration ec;
|
EdgeConfiguration ec;
|
||||||
auto baseEcIt = edgeConfigurations.find({from.entry, to->entry});
|
auto baseEcIt = edgeConfigurations.find({from.entry, to->entry});
|
||||||
if (baseEcIt != edgeConfigurations.end())
|
if (baseEcIt != edgeConfigurations.end())
|
||||||
|
@ -34,6 +34,12 @@ public:
|
|||||||
|
|
||||||
void centreRect();
|
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:
|
public slots:
|
||||||
/**
|
/**
|
||||||
* @brief scale and center all nodes in, then run update
|
* @brief scale and center all nodes in, then run update
|
||||||
@ -97,7 +103,7 @@ private:
|
|||||||
/**
|
/**
|
||||||
* @brief draw the computed blocks passed by Graph
|
* @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
|
* @brief override the edgeConfiguration so as to
|
||||||
@ -105,7 +111,8 @@ private:
|
|||||||
* @return EdgeConfiguration
|
* @return EdgeConfiguration
|
||||||
*/
|
*/
|
||||||
virtual GraphView::EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from,
|
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
|
* @brief base background color changing depending on the theme
|
||||||
|
Loading…
Reference in New Issue
Block a user