mirror of
https://github.com/rizinorg/cutter.git
synced 2024-11-23 12:59:59 +00:00
DisassemblerGraphView: lots of additions.. (#213)
* GraphView: Add refactored graphview with animations etc. * GraphView: Use new graphview and render background color. * DisassemblerGraphView: t/f navigation, zoom with +/-, bugfixes.
This commit is contained in:
parent
d34b45ab4a
commit
86d467daf9
22
README.md
22
README.md
@ -79,6 +79,28 @@ If you encounter the `Project ERROR: r_core development package not found` try t
|
||||
|
||||
Cutter is developed on OS X, Linux and Windows. The first release for users will include installers for all three platforms.
|
||||
|
||||
## Keyboard shortcuts
|
||||
|
||||
| Shortcut | Function |
|
||||
| --- | --- |
|
||||
| Global shortcuts: ||
|
||||
| . | Focus console input |
|
||||
| G & S | Focus search bar |
|
||||
| : | Show commands |
|
||||
| F5 | Refresh contents |
|
||||
| Disassembly view: ||
|
||||
| Esc | Seek to previous position |
|
||||
| Space | Switch to disassembly graph view |
|
||||
| Ctrl/Cmd+C | Copy |
|
||||
| ; | Add comment |
|
||||
| N | Rename current function/flag |
|
||||
| Shift+N | Rename flag/function used here |
|
||||
| X | Show Xrefs |
|
||||
| Disassembly graph view: ||
|
||||
| Esc | Seek to previous position |
|
||||
| Space | Switch to disassembly view |
|
||||
|
||||
|
||||
## Help
|
||||
|
||||
Right now the best place to obtain help from *cutter* developers and community is joining this telegram group:
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "utils/Helpers.h"
|
||||
#include "dialogs/NewFileDialog.h"
|
||||
|
||||
#include "widgets/DisassemblerGraphView.h"
|
||||
#include "widgets/FunctionsWidget.h"
|
||||
#include "widgets/SectionsWidget.h"
|
||||
#include "widgets/CommentsWidget.h"
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
#include "cutter.h" // only needed for ut64
|
||||
#include "widgets/DisassemblyWidget.h"
|
||||
#include "widgets/DisassemblerGraphView.h"
|
||||
#include "widgets/SidebarWidget.h"
|
||||
#include "widgets/HexdumpWidget.h"
|
||||
#include "widgets/PseudocodeWidget.h"
|
||||
|
@ -79,7 +79,8 @@ SOURCES += \
|
||||
utils/TempConfig.cpp \
|
||||
utils/SvgIconEngine.cpp \
|
||||
widgets/PseudocodeWidget.cpp \
|
||||
widgets/VisualNavbar.cpp
|
||||
widgets/VisualNavbar.cpp \
|
||||
widgets/GraphView.cpp
|
||||
|
||||
HEADERS += \
|
||||
cutter.h \
|
||||
@ -129,7 +130,8 @@ HEADERS += \
|
||||
utils/TempConfig.h \
|
||||
utils/SvgIconEngine.h \
|
||||
widgets/PseudocodeWidget.h \
|
||||
widgets/VisualNavbar.h
|
||||
widgets/VisualNavbar.h \
|
||||
widgets/GraphView.h
|
||||
|
||||
FORMS += \
|
||||
dialogs/AboutDialog.ui \
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,366 +1,206 @@
|
||||
/* x64dbg DisassemblerGraphView */
|
||||
#ifndef DISASSEMBLERGRAPHVIEW_H
|
||||
#define DISASSEMBLERGRAPHVIEW_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QAbstractScrollArea>
|
||||
#include <QPaintEvent>
|
||||
#include <QTimer>
|
||||
#include <QSize>
|
||||
#include <QResizeEvent>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <queue>
|
||||
#include <QMutex>
|
||||
#include <QMenu>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include "cutter.h"
|
||||
#include "MainWindow.h"
|
||||
#include "utils/RichTextPainter.h"
|
||||
#include "utils/CachedFontMetrics.h"
|
||||
|
||||
#define duint ut64
|
||||
|
||||
class MenuBuilder;
|
||||
class CachedFontMetrics;
|
||||
class GotoDialog;
|
||||
class XrefBrowseDialog;
|
||||
class DisassemblyContextMenu;
|
||||
|
||||
class DisassemblerGraphView : public QAbstractScrollArea
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct DisassemblerBlock;
|
||||
|
||||
struct Point
|
||||
{
|
||||
int row; //point[0]
|
||||
int col; //point[1]
|
||||
int index; //point[2]
|
||||
};
|
||||
|
||||
struct DisassemblerEdge
|
||||
{
|
||||
QColor color;
|
||||
DisassemblerBlock* dest;
|
||||
std::vector<Point> points;
|
||||
int start_index = 0;
|
||||
|
||||
QPolygonF polyline;
|
||||
QPolygonF arrow;
|
||||
|
||||
void addPoint(int row, int col, int index = 0)
|
||||
{
|
||||
Point point = {row, col, 0};
|
||||
this->points.push_back(point);
|
||||
if(int(this->points.size()) > 1)
|
||||
this->points[this->points.size() - 2].index = index;
|
||||
}
|
||||
};
|
||||
|
||||
struct Token
|
||||
{
|
||||
int start; //token[0]
|
||||
int length; //token[1]
|
||||
QString type; //token[2]
|
||||
duint addr; //token[3]
|
||||
QString name; //token[4]
|
||||
};
|
||||
|
||||
struct HighlightToken
|
||||
{
|
||||
QString type; //highlight_token[0]
|
||||
duint addr; //highlight_token[1]
|
||||
QString name; //highlight_token[2]
|
||||
|
||||
bool equalsToken(const Token & token)
|
||||
{
|
||||
return this->type == token.type &&
|
||||
this->addr == token.addr &&
|
||||
this->name == token.name;
|
||||
}
|
||||
|
||||
static HighlightToken* fromToken(const Token & token)
|
||||
{
|
||||
//TODO: memory leaks
|
||||
auto result = new HighlightToken();
|
||||
result->type = token.type;
|
||||
result->addr = token.addr;
|
||||
result->name = token.name;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct Text
|
||||
{
|
||||
std::vector<RichTextPainter::List> lines;
|
||||
|
||||
Text() {}
|
||||
|
||||
Text(const QString & text, QColor color, QColor background)
|
||||
{
|
||||
RichTextPainter::List richText;
|
||||
RichTextPainter::CustomRichText_t rt;
|
||||
rt.highlight = false;
|
||||
rt.text = text;
|
||||
rt.textColor = color;
|
||||
rt.textBackground = background;
|
||||
rt.flags = rt.textBackground.alpha() ? RichTextPainter::FlagAll : RichTextPainter::FlagColor;
|
||||
richText.push_back(rt);
|
||||
lines.push_back(richText);
|
||||
}
|
||||
|
||||
Text(const RichTextPainter::List & richText)
|
||||
{
|
||||
lines.push_back(richText);
|
||||
}
|
||||
|
||||
QString ToQString() const
|
||||
{
|
||||
QString result;
|
||||
for(auto & line : lines)
|
||||
{
|
||||
for(auto & t : line)
|
||||
{
|
||||
result += t.text;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct Instr
|
||||
{
|
||||
duint addr = 0;
|
||||
Text text;
|
||||
std::vector<unsigned char> opcode; //instruction bytes
|
||||
};
|
||||
|
||||
struct Block
|
||||
{
|
||||
Text header_text;
|
||||
std::vector<Instr> instrs;
|
||||
std::vector<duint> exits;
|
||||
duint entry = 0;
|
||||
duint true_path = 0;
|
||||
duint false_path = 0;
|
||||
bool terminal = false;
|
||||
bool indirectcall = false;
|
||||
};
|
||||
|
||||
struct DisassemblerBlock
|
||||
{
|
||||
DisassemblerBlock() {}
|
||||
explicit DisassemblerBlock(Block & block)
|
||||
: block(block) {}
|
||||
|
||||
Block block;
|
||||
std::vector<DisassemblerEdge> edges;
|
||||
std::vector<duint> incoming;
|
||||
std::vector<duint> new_exits;
|
||||
|
||||
qreal x = 0.0;
|
||||
qreal y = 0.0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int col = 0;
|
||||
int col_count = 0;
|
||||
int row = 0;
|
||||
int row_count = 0;
|
||||
};
|
||||
|
||||
struct Function
|
||||
{
|
||||
bool ready;
|
||||
duint entry;
|
||||
duint update_id;
|
||||
std::vector<Block> blocks;
|
||||
};
|
||||
|
||||
struct Analysis
|
||||
{
|
||||
duint entry = 0;
|
||||
std::unordered_map<duint, Function> functions;
|
||||
bool ready = false;
|
||||
duint update_id = 0;
|
||||
QString status = "Analyzing...";
|
||||
|
||||
bool find_instr(duint addr, duint & func, duint & instr)
|
||||
{
|
||||
//TODO implement
|
||||
Q_UNUSED(addr);
|
||||
Q_UNUSED(func);
|
||||
Q_UNUSED(instr);
|
||||
return false;
|
||||
}
|
||||
|
||||
//dummy class
|
||||
};
|
||||
|
||||
enum class LayoutType
|
||||
{
|
||||
Wide,
|
||||
Medium,
|
||||
Narrow,
|
||||
};
|
||||
|
||||
DisassemblerGraphView(QWidget *parent);
|
||||
~DisassemblerGraphView();
|
||||
void initFont();
|
||||
void adjustSize(int width, int height);
|
||||
void resizeEvent(QResizeEvent* event);
|
||||
duint get_cursor_pos();
|
||||
void set_cursor_pos(duint addr);
|
||||
std::tuple<duint, duint> get_selection_range();
|
||||
void set_selection_range(std::tuple<duint, duint> range);
|
||||
void copy_address();
|
||||
//void analysis_thread_proc();
|
||||
//void closeRequest();
|
||||
void paintNormal(QPainter & p, QRect & viewportRect, int xofs, int yofs);
|
||||
void paintOverview(QPainter & p, QRect & viewportRect, int xofs, int yofs);
|
||||
void paintEvent(QPaintEvent* event);
|
||||
bool isMouseEventInBlock(QMouseEvent* event);
|
||||
duint getInstrForMouseEvent(QMouseEvent* event);
|
||||
bool getTokenForMouseEvent(QMouseEvent* event, Token & token);
|
||||
bool find_instr(duint addr, Instr & instr);
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
void mouseMoveEvent(QMouseEvent* event);
|
||||
void mouseReleaseEvent(QMouseEvent* event);
|
||||
void mouseDoubleClickEvent(QMouseEvent* event);
|
||||
void prepareGraphNode(DisassemblerBlock & block);
|
||||
void adjustGraphLayout(DisassemblerBlock & block, int col, int row);
|
||||
void computeGraphLayout(DisassemblerBlock & block);
|
||||
void setupContextMenu();
|
||||
void keyPressEvent(QKeyEvent* event);
|
||||
|
||||
template<typename T>
|
||||
using Matrix = std::vector<std::vector<T>>;
|
||||
using EdgesVector = Matrix<std::vector<bool>>;
|
||||
bool isEdgeMarked(EdgesVector & edges, int row, int col, int index);
|
||||
void markEdge(EdgesVector & edges, int row, int col, int index, bool used = true);
|
||||
int findHorizEdgeIndex(EdgesVector & edges, int row, int min_col, int max_col);
|
||||
int findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int max_row);
|
||||
DisassemblerEdge routeEdge(EdgesVector & horiz_edges, EdgesVector & vert_edges, Matrix<bool> & edge_valid, DisassemblerBlock & start, DisassemblerBlock & end, QColor color);
|
||||
void renderFunction(Function & func);
|
||||
void show_cur_instr(bool force = false);
|
||||
bool navigate(duint addr);
|
||||
void fontChanged();
|
||||
void setGraphLayout(LayoutType layout);
|
||||
|
||||
//VaHistory mHistory;
|
||||
|
||||
signals:
|
||||
void displaySnowmanWidget();
|
||||
void currentInstructionUpdated(duint);
|
||||
|
||||
public slots:
|
||||
void refreshView();
|
||||
void onSeekChanged(RVA);
|
||||
//void loadGraphSlot(BridgeCFGraphList* graph, duint addr);
|
||||
void graphAtSlot(duint addr);
|
||||
void updateGraphSlot();
|
||||
void followDisassemblerSlot();
|
||||
void colorsUpdatedSlot();
|
||||
void fontsUpdatedSlot();
|
||||
void shortcutsUpdatedSlot();
|
||||
void toggleOverviewSlot();
|
||||
void toggleSummarySlot();
|
||||
//void selectionGetSlot(SELECTIONDATA* selection);
|
||||
void tokenizerConfigUpdatedSlot();
|
||||
void loadCurrentGraph();
|
||||
//void disassembleAtSlot(dsint va, dsint cip);
|
||||
void gotoExpressionSlot();
|
||||
void gotoOriginSlot();
|
||||
void gotoPreviousSlot();
|
||||
void gotoNextSlot();
|
||||
void toggleSyncOriginSlot();
|
||||
void followActionSlot();
|
||||
void refreshSlot();
|
||||
void saveImageSlot();
|
||||
void setCommentSlot();
|
||||
void setLabelSlot();
|
||||
void decompileSlot();
|
||||
|
||||
void seekPrev();
|
||||
|
||||
private:
|
||||
QString status;
|
||||
Analysis analysis;
|
||||
duint function;
|
||||
int baseline;
|
||||
qreal charWidth;
|
||||
int charHeight;
|
||||
int charOffset;
|
||||
int width;
|
||||
int height;
|
||||
int renderWidth;
|
||||
int renderHeight;
|
||||
int renderXOfs;
|
||||
int renderYOfs;
|
||||
duint cur_instr;
|
||||
int scroll_base_x;
|
||||
int scroll_base_y;
|
||||
duint update_id;
|
||||
bool scroll_mode;
|
||||
bool ready;
|
||||
int* desired_pos;
|
||||
std::unordered_map<duint, DisassemblerBlock> blocks;
|
||||
HighlightToken* highlight_token;
|
||||
std::vector<int> col_edge_x;
|
||||
std::vector<int> row_edge_y;
|
||||
CachedFontMetrics* mFontMetrics;
|
||||
MenuBuilder* mMenuBuilder;
|
||||
bool drawOverview;
|
||||
bool onlySummary;
|
||||
bool syncOrigin;
|
||||
int overviewXOfs;
|
||||
int overviewYOfs;
|
||||
qreal overviewScale;
|
||||
duint mCip;
|
||||
bool forceCenter;
|
||||
bool saveGraph;
|
||||
bool mHistoryLock; //Don't add a history while going to previous/next
|
||||
LayoutType layoutType;
|
||||
|
||||
QAction* mToggleOverview;
|
||||
QAction* mToggleSummary;
|
||||
QAction* mToggleSyncOrigin;
|
||||
|
||||
QColor disassemblyBackgroundColor;
|
||||
QColor disassemblySelectionColor;
|
||||
QColor disassemblyTracedColor;
|
||||
QColor disassemblyTracedSelectionColor;
|
||||
QColor jmpColor;
|
||||
QColor brtrueColor;
|
||||
QColor brfalseColor;
|
||||
QColor retShadowColor;
|
||||
QColor indirectcallShadowColor;
|
||||
QColor backgroundColor;
|
||||
QColor mAutoCommentColor;
|
||||
QColor mAutoCommentBackgroundColor;
|
||||
QColor mCommentColor;
|
||||
QColor mCommentBackgroundColor;
|
||||
QColor mLabelColor;
|
||||
QColor mLabelBackgroundColor;
|
||||
QColor graphNodeColor;
|
||||
QColor mAddressColor;
|
||||
QColor mAddressBackgroundColor;
|
||||
QColor mCipColor;
|
||||
QColor mBreakpointColor;
|
||||
QColor mDisabledBreakpointColor;
|
||||
|
||||
//BridgeCFGraph currentGraph;
|
||||
std::unordered_map<duint, duint> currentBlockMap;
|
||||
//QBeaEngine disasm;
|
||||
GotoDialog* mGoto;
|
||||
DisassemblyContextMenu* mMenu;
|
||||
|
||||
void addReferenceAction(QMenu* menu, duint addr);
|
||||
};
|
||||
|
||||
#endif // DISASSEMBLERGRAPHVIEW_H
|
||||
#ifndef DISASSEMBLERGRAPHVIEW_H
|
||||
#define DISASSEMBLERGRAPHVIEW_H
|
||||
|
||||
// Based on the DisassemblerGraphView from x64dbg
|
||||
|
||||
#include <QWidget>
|
||||
#include <QPainter>
|
||||
|
||||
#include "widgets/GraphView.h"
|
||||
#include "menus/DisassemblyContextMenu.h"
|
||||
#include "utils/RichTextPainter.h"
|
||||
|
||||
class DisassemblerGraphView : public GraphView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
struct Token
|
||||
{
|
||||
int start; //token[0]
|
||||
int length; //token[1]
|
||||
QString type; //token[2]
|
||||
ut64 addr; //token[3]
|
||||
QString name; //token[4]
|
||||
};
|
||||
|
||||
struct HighlightToken
|
||||
{
|
||||
QString type; //highlight_token[0]
|
||||
ut64 addr; //highlight_token[1]
|
||||
QString name; //highlight_token[2]
|
||||
|
||||
bool equalsToken(const Token & token)
|
||||
{
|
||||
return this->type == token.type &&
|
||||
this->addr == token.addr &&
|
||||
this->name == token.name;
|
||||
}
|
||||
|
||||
static HighlightToken* fromToken(const Token & token)
|
||||
{
|
||||
//TODO: memory leaks
|
||||
auto result = new HighlightToken();
|
||||
result->type = token.type;
|
||||
result->addr = token.addr;
|
||||
result->name = token.name;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct Text
|
||||
{
|
||||
std::vector<RichTextPainter::List> lines;
|
||||
|
||||
Text() {}
|
||||
|
||||
Text(const QString & text, QColor color, QColor background)
|
||||
{
|
||||
RichTextPainter::List richText;
|
||||
RichTextPainter::CustomRichText_t rt;
|
||||
rt.highlight = false;
|
||||
rt.text = text;
|
||||
rt.textColor = color;
|
||||
rt.textBackground = background;
|
||||
rt.flags = rt.textBackground.alpha() ? RichTextPainter::FlagAll : RichTextPainter::FlagColor;
|
||||
richText.push_back(rt);
|
||||
lines.push_back(richText);
|
||||
}
|
||||
|
||||
Text(const RichTextPainter::List & richText)
|
||||
{
|
||||
lines.push_back(richText);
|
||||
}
|
||||
|
||||
QString ToQString() const
|
||||
{
|
||||
QString result;
|
||||
for(auto & line : lines)
|
||||
{
|
||||
for(auto & t : line)
|
||||
{
|
||||
result += t.text;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct Instr
|
||||
{
|
||||
ut64 addr = 0;
|
||||
ut64 size = 0;
|
||||
Text text;
|
||||
std::vector<unsigned char> opcode; //instruction bytes
|
||||
};
|
||||
|
||||
|
||||
struct DisassemblyBlock
|
||||
{
|
||||
Text header_text;
|
||||
std::vector<Instr> instrs;
|
||||
ut64 entry = 0;
|
||||
ut64 true_path = 0;
|
||||
ut64 false_path = 0;
|
||||
bool terminal = false;
|
||||
bool indirectcall = false;
|
||||
};
|
||||
|
||||
struct Function
|
||||
{
|
||||
bool ready;
|
||||
ut64 entry;
|
||||
ut64 update_id;
|
||||
std::vector<DisassemblyBlock> blocks;
|
||||
};
|
||||
|
||||
struct Analysis
|
||||
{
|
||||
ut64 entry = 0;
|
||||
std::unordered_map<ut64, Function> functions;
|
||||
bool ready = false;
|
||||
ut64 update_id = 0;
|
||||
QString status = "Analyzing...";
|
||||
|
||||
bool find_instr(ut64 addr, ut64 & func, ut64 & instr)
|
||||
{
|
||||
//TODO implement
|
||||
Q_UNUSED(addr);
|
||||
Q_UNUSED(func);
|
||||
Q_UNUSED(instr);
|
||||
return false;
|
||||
}
|
||||
|
||||
//dummy class
|
||||
};
|
||||
|
||||
public:
|
||||
DisassemblerGraphView(QWidget *parent);
|
||||
std::unordered_map<ut64, DisassemblyBlock> disassembly_blocks;
|
||||
virtual void drawBlock(QPainter & p, GraphView::GraphBlock &block) override;
|
||||
virtual void blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) override;
|
||||
virtual GraphView::EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to) override;
|
||||
virtual void blockTransitionedTo(GraphView::GraphBlock *to) override;
|
||||
|
||||
void loadCurrentGraph();
|
||||
// bool navigate(ut64 addr);
|
||||
|
||||
public slots:
|
||||
void refreshView();
|
||||
void colorsUpdatedSlot();
|
||||
void fontsUpdatedSlot();
|
||||
void onSeekChanged(RVA addr);
|
||||
|
||||
void zoomIn();
|
||||
void zoomOut();
|
||||
|
||||
void takeTrue();
|
||||
void takeFalse();
|
||||
private slots:
|
||||
void seekPrev();
|
||||
|
||||
private:
|
||||
bool transition_dont_seek = false;
|
||||
bool sent_seek = false;
|
||||
|
||||
HighlightToken* highlight_token;
|
||||
// Font data
|
||||
CachedFontMetrics* mFontMetrics;
|
||||
qreal charWidth;
|
||||
qreal charHeight;
|
||||
int charOffset;
|
||||
int baseline;
|
||||
|
||||
DisassemblyContextMenu* mMenu;
|
||||
|
||||
void initFont();
|
||||
void prepareGraphNode(GraphBlock &block);
|
||||
RVA getInstrForMouseEvent(GraphBlock &block, QPoint* point);
|
||||
DisassemblyBlock *blockForAddress(RVA addr);
|
||||
void seek(RVA addr, bool update_viewport=true);
|
||||
|
||||
QColor disassemblyBackgroundColor;
|
||||
QColor disassemblySelectedBackgroundColor;
|
||||
QColor disassemblySelectionColor;
|
||||
QColor disassemblyTracedColor;
|
||||
QColor disassemblyTracedSelectionColor;
|
||||
QColor jmpColor;
|
||||
QColor brtrueColor;
|
||||
QColor brfalseColor;
|
||||
QColor retShadowColor;
|
||||
QColor indirectcallShadowColor;
|
||||
QColor mAutoCommentColor;
|
||||
QColor mAutoCommentBackgroundColor;
|
||||
QColor mCommentColor;
|
||||
QColor mCommentBackgroundColor;
|
||||
QColor mLabelColor;
|
||||
QColor mLabelBackgroundColor;
|
||||
QColor graphNodeColor;
|
||||
QColor mAddressColor;
|
||||
QColor mAddressBackgroundColor;
|
||||
QColor mCipColor;
|
||||
QColor mBreakpointColor;
|
||||
QColor mDisabledBreakpointColor;
|
||||
};
|
||||
|
||||
#endif // DISASSEMBLERGRAPHVIEW_H
|
||||
|
892
src/widgets/GraphView.cpp
Normal file
892
src/widgets/GraphView.cpp
Normal file
@ -0,0 +1,892 @@
|
||||
#include "GraphView.h"
|
||||
|
||||
#include <vector>
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
|
||||
GraphView::GraphView(QWidget *parent)
|
||||
: QAbstractScrollArea(parent)
|
||||
{
|
||||
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
this->horizontalScrollBar()->setSingleStep(this->charWidth);
|
||||
this->verticalScrollBar()->setSingleStep(this->charWidth);
|
||||
QSize areaSize = this->viewport()->size();
|
||||
this->adjustSize(areaSize.width(), areaSize.height());
|
||||
}
|
||||
|
||||
GraphView::~GraphView()
|
||||
{
|
||||
// TODO: Cleanups
|
||||
}
|
||||
|
||||
// Vector functions
|
||||
template<class T>
|
||||
static void removeFromVec(std::vector<T> & vec, T elem)
|
||||
{
|
||||
vec.erase(std::remove(vec.begin(), vec.end(), elem), vec.end());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void initVec(std::vector<T> & vec, size_t size, T value)
|
||||
{
|
||||
vec.resize(size);
|
||||
for(size_t i = 0; i < size; i++)
|
||||
vec[i] = value;
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
void GraphView::drawBlock(QPainter & p, GraphView::GraphBlock &block)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
Q_UNUSED(block);
|
||||
qWarning() << "Draw block not overriden!";
|
||||
}
|
||||
|
||||
void GraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos)
|
||||
{
|
||||
Q_UNUSED(block);
|
||||
Q_UNUSED(event);
|
||||
Q_UNUSED(pos);
|
||||
qWarning() << "Block clicked not overridden!";
|
||||
}
|
||||
|
||||
void GraphView::blockTransitionedTo(GraphView::GraphBlock *to)
|
||||
{
|
||||
Q_UNUSED(to);
|
||||
qWarning() << "blockTransitionedTo not overridden!";
|
||||
}
|
||||
|
||||
GraphView::EdgeConfiguration GraphView::edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to)
|
||||
{
|
||||
Q_UNUSED(from);
|
||||
Q_UNUSED(to);
|
||||
qWarning() << "Edge configuration not overridden!";
|
||||
EdgeConfiguration ec;
|
||||
return ec;
|
||||
}
|
||||
|
||||
void GraphView::adjustSize(int new_width, int new_height)
|
||||
{
|
||||
double hfactor = 0.0;
|
||||
double vfactor = 0.0;
|
||||
if(horizontalScrollBar()->maximum())
|
||||
{
|
||||
hfactor = (double)horizontalScrollBar()->value() / (double)horizontalScrollBar()->maximum();
|
||||
}
|
||||
if(verticalScrollBar()->maximum())
|
||||
{
|
||||
vfactor = (double)verticalScrollBar()->value() / (double)verticalScrollBar()->maximum();
|
||||
}
|
||||
|
||||
//Update scroll bar information
|
||||
horizontalScrollBar()->setPageStep(new_width);
|
||||
horizontalScrollBar()->setRange(0, this->width - (new_width/current_scale));
|
||||
verticalScrollBar()->setPageStep(new_height);
|
||||
verticalScrollBar()->setRange(0, this->height - (new_height/current_scale));
|
||||
horizontalScrollBar()->setValue((int)((double)horizontalScrollBar()->maximum() * hfactor));
|
||||
verticalScrollBar()->setValue((int)((double)verticalScrollBar()->maximum() * vfactor));
|
||||
}
|
||||
|
||||
// This calculates the full graph starting at block entry.
|
||||
void GraphView::computeGraph(ut64 entry)
|
||||
{
|
||||
QSize areaSize = this->viewport()->size();
|
||||
|
||||
// Populate incoming lists
|
||||
for(auto &blockIt : this->blocks)
|
||||
{
|
||||
GraphBlock &block = blockIt.second;
|
||||
for(auto & edge : block.exits)
|
||||
{
|
||||
this->blocks[edge].incoming.push_back(block.entry);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<ut64> visited;
|
||||
visited.insert(entry);
|
||||
std::queue<ut64> queue;
|
||||
std::vector<ut64> block_order;
|
||||
queue.push(entry);
|
||||
|
||||
bool changed = true;
|
||||
while(changed)
|
||||
{
|
||||
changed = false;
|
||||
|
||||
// Pick nodes with single entrypoints
|
||||
while(!queue.empty())
|
||||
{
|
||||
GraphBlock &block = this->blocks[queue.front()];
|
||||
queue.pop();
|
||||
block_order.push_back(block.entry);
|
||||
for(ut64 edge : block.exits)
|
||||
{
|
||||
// Skip edge if we already visited it
|
||||
if(visited.count(edge))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some edges might not be available
|
||||
if(!this->blocks.count(edge))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this node has no other incoming edges, add it to the graph layout
|
||||
if(this->blocks[edge].incoming.size() == 1)
|
||||
{
|
||||
removeFromVec(this->blocks[edge].incoming, block.entry);
|
||||
block.new_exits.push_back(edge);
|
||||
queue.push(this->blocks[edge].entry);
|
||||
visited.insert(edge);
|
||||
changed = true;
|
||||
} else {
|
||||
// Remove from incoming edges
|
||||
removeFromVec(this->blocks[edge].incoming, block.entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No more nodes satisfy constraints, pick a node to continue constructing the graph
|
||||
ut64 best = 0;
|
||||
int best_edges;
|
||||
ut64 best_parent;
|
||||
for(auto & blockIt : this->blocks)
|
||||
{
|
||||
GraphBlock &block = blockIt.second;
|
||||
// Skip blocks we haven't visited yet
|
||||
if(!visited.count(block.entry))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for(ut64 edge : block.exits)
|
||||
{
|
||||
// If we already visited the exit, skip it
|
||||
if(visited.count(edge))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(!this->blocks.count(edge))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// find best edge
|
||||
if((best == 0) || ((int)this->blocks[edge].incoming.size() < best_edges) || (
|
||||
((int)this->blocks[edge].incoming.size() == best_edges) && (edge < best)))
|
||||
{
|
||||
best = edge;
|
||||
best_edges = this->blocks[edge].incoming.size();
|
||||
best_parent = block.entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(best != 0)
|
||||
{
|
||||
GraphBlock &best_parentb = this->blocks[best_parent];
|
||||
removeFromVec(this->blocks[best].incoming, best_parentb.entry);
|
||||
best_parentb.new_exits.push_back(best);
|
||||
visited.insert(best);
|
||||
queue.push(best);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
this->computeGraphLayout(this->blocks[entry]);
|
||||
|
||||
// Prepare edge routing
|
||||
GraphBlock &entryb = this->blocks[entry];
|
||||
EdgesVector horiz_edges, vert_edges;
|
||||
horiz_edges.resize(entryb.row_count + 1);
|
||||
vert_edges.resize(entryb.row_count + 1);
|
||||
Matrix<bool> edge_valid;
|
||||
edge_valid.resize(entryb.row_count + 1);
|
||||
for(int row = 0; row < entryb.row_count + 1; row++)
|
||||
{
|
||||
horiz_edges[row].resize(entryb.col_count +1);
|
||||
vert_edges[row].resize(entryb.col_count + 1);
|
||||
initVec(edge_valid[row], entryb.col_count + 1, true);
|
||||
for(int col = 0; col < entryb.col_count + 1; col++)
|
||||
{
|
||||
horiz_edges[row][col].clear();
|
||||
vert_edges[row][col].clear();
|
||||
}
|
||||
}
|
||||
|
||||
for(auto & blockIt : this->blocks)
|
||||
{
|
||||
GraphBlock &block = blockIt.second;
|
||||
edge_valid[block.row][block.col + 1] = false;
|
||||
}
|
||||
|
||||
// Perform edge routing
|
||||
for(ut64 block_id : block_order)
|
||||
{
|
||||
GraphBlock &block = blocks[block_id];
|
||||
GraphBlock &start = block;
|
||||
for(ut64 edge : block.exits)
|
||||
{
|
||||
GraphBlock &end = this->blocks[edge];
|
||||
start.edges.push_back(this->routeEdge(horiz_edges, vert_edges, edge_valid, start, end, QColor(255, 0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// Compute edge counts for each row and column
|
||||
std::vector<int> col_edge_count, row_edge_count;
|
||||
initVec(col_edge_count, entryb.col_count + 1, 0);
|
||||
initVec(row_edge_count, entryb.row_count + 1, 0);
|
||||
for(int row = 0; row < entryb.row_count + 1; row++)
|
||||
{
|
||||
for(int col = 0; col < entryb.col_count + 1; col++)
|
||||
{
|
||||
if(int(horiz_edges[row][col].size()) > row_edge_count[row])
|
||||
row_edge_count[row] = int(horiz_edges[row][col].size());
|
||||
if(int(vert_edges[row][col].size()) > col_edge_count[col])
|
||||
col_edge_count[col] = int(vert_edges[row][col].size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Compute row and column sizes
|
||||
std::vector<int> col_width, row_height;
|
||||
initVec(col_width, entryb.col_count + 1, 0);
|
||||
initVec(row_height, entryb.row_count + 1, 0);
|
||||
for(auto & blockIt : this->blocks)
|
||||
{
|
||||
GraphBlock &block = blockIt.second;
|
||||
if((int(block.width / 2)) > col_width[block.col])
|
||||
col_width[block.col] = int(block.width / 2);
|
||||
if((int(block.width / 2)) > col_width[block.col + 1])
|
||||
col_width[block.col + 1] = int(block.width / 2);
|
||||
if(int(block.height) > row_height[block.row])
|
||||
row_height[block.row] = int(block.height);
|
||||
}
|
||||
|
||||
// Compute row and column positions
|
||||
std::vector<int> col_x, row_y;
|
||||
initVec(col_x, entryb.col_count, 0);
|
||||
initVec(row_y, entryb.row_count, 0);
|
||||
initVec(this->col_edge_x, entryb.col_count + 1, 0);
|
||||
initVec(this->row_edge_y, entryb.row_count + 1, 0);
|
||||
int x = 16;
|
||||
for(int i = 0; i < entryb.col_count; i++)
|
||||
{
|
||||
this->col_edge_x[i] = x;
|
||||
x += 8 * col_edge_count[i];
|
||||
col_x[i] = x;
|
||||
x += col_width[i];
|
||||
}
|
||||
int y = 16;
|
||||
for(int i = 0; i < entryb.row_count; i++)
|
||||
{
|
||||
this->row_edge_y[i] = y;
|
||||
y += block_vertical_margin * row_edge_count[i];
|
||||
row_y[i] = y;
|
||||
y += row_height[i];
|
||||
}
|
||||
this->col_edge_x[entryb.col_count] = x;
|
||||
this->row_edge_y[entryb.row_count] = y;
|
||||
this->width = x + 16 + (8 * col_edge_count[entryb.col_count]);
|
||||
this->height = y + 16 + (8 * row_edge_count[entryb.row_count]);
|
||||
|
||||
//Compute node positions
|
||||
for(auto & blockIt : this->blocks)
|
||||
{
|
||||
GraphBlock &block = blockIt.second;
|
||||
block.x = int(
|
||||
(col_x[block.col] + col_width[block.col] + 4 * col_edge_count[block.col + 1]) - (block.width / 2));
|
||||
if((block.x + block.width) > (
|
||||
col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[
|
||||
block.col + 1]))
|
||||
{
|
||||
block.x = int((col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[
|
||||
block.col + 1]) - block.width);
|
||||
}
|
||||
block.y = row_y[block.row];
|
||||
}
|
||||
|
||||
// Precompute coordinates for edges
|
||||
for(auto & blockIt : this->blocks)
|
||||
{
|
||||
GraphBlock &block = blockIt.second;
|
||||
|
||||
for(GraphEdge & edge : block.edges)
|
||||
{
|
||||
auto start = edge.points[0];
|
||||
auto start_col = start.col;
|
||||
auto last_index = edge.start_index;
|
||||
auto first_pt = QPoint(this->col_edge_x[start_col] + (8 * last_index) + 4,
|
||||
block.y + block.height);
|
||||
auto last_pt = first_pt;
|
||||
QPolygonF pts;
|
||||
pts.append(last_pt);
|
||||
|
||||
for(int i = 0; i < int(edge.points.size()); i++)
|
||||
{
|
||||
auto end = edge.points[i];
|
||||
auto end_row = end.row;
|
||||
auto end_col = end.col;
|
||||
auto last_index = end.index;
|
||||
QPoint new_pt;
|
||||
// block_vertical_margin/2 gives the margin from block to the horizontal lines
|
||||
if(start_col == end_col)
|
||||
new_pt = QPoint(last_pt.x(), this->row_edge_y[end_row] + (8 * last_index) + (block_vertical_margin/2));
|
||||
else
|
||||
new_pt = QPoint(this->col_edge_x[end_col] + (8 * last_index) + 4, last_pt.y());
|
||||
pts.push_back(new_pt);
|
||||
last_pt = new_pt;
|
||||
start_col = end_col;
|
||||
}
|
||||
|
||||
EdgeConfiguration ec = edgeConfiguration(block, edge.dest);
|
||||
|
||||
auto new_pt = QPoint(last_pt.x(), edge.dest->y - 1);
|
||||
pts.push_back(new_pt);
|
||||
edge.polyline = pts;
|
||||
edge.color = ec.color;
|
||||
if(ec.start_arrow)
|
||||
{
|
||||
pts.clear();
|
||||
pts.append(QPoint(first_pt.x() - 3, first_pt.y() + 6));
|
||||
pts.append(QPoint(first_pt.x() + 3, first_pt.y() + 6));
|
||||
pts.append(first_pt);
|
||||
edge.arrow_start = pts;
|
||||
}
|
||||
pts.clear();
|
||||
pts.append(QPoint(new_pt.x() - 3, new_pt.y() - 6));
|
||||
pts.append(QPoint(new_pt.x() + 3, new_pt.y() - 6));
|
||||
pts.append(new_pt);
|
||||
edge.arrow_end = pts;
|
||||
}
|
||||
}
|
||||
|
||||
this->ready = true;
|
||||
|
||||
this->viewport()->update();
|
||||
areaSize = this->viewport()->size();
|
||||
this->adjustSize(areaSize.width(), areaSize.height());
|
||||
}
|
||||
|
||||
void GraphView::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
QPainter p(this->viewport());
|
||||
int render_offset_x = -this->horizontalScrollBar()->value() * current_scale;
|
||||
int render_offset_y = -this->verticalScrollBar()->value() * current_scale;
|
||||
int render_width = this->viewport()->size().width() / current_scale;
|
||||
int render_height = this->viewport()->size().height() / current_scale;
|
||||
|
||||
// Draw background
|
||||
QRect viewportRect(this->viewport()->rect().topLeft(), this->viewport()->rect().bottomRight() - QPoint(1, 1));
|
||||
p.setBrush(backgroundColor);
|
||||
p.drawRect(viewportRect);
|
||||
p.setBrush(Qt::black);
|
||||
|
||||
p.translate(render_offset_x, render_offset_y);
|
||||
p.scale(current_scale, current_scale);
|
||||
|
||||
|
||||
// Draw blocks
|
||||
for(auto & blockIt : this->blocks)
|
||||
{
|
||||
GraphBlock &block = blockIt.second;
|
||||
|
||||
// Check if block is visible
|
||||
if((block.x + block.width > -render_offset_x) ||
|
||||
(block.y + block.height > -render_offset_y) ||
|
||||
(-render_offset_x + render_width > block.x) ||
|
||||
(-render_offset_y + render_height > block.y))
|
||||
{
|
||||
// Only draw block if it is visible
|
||||
drawBlock(p, block);
|
||||
}
|
||||
|
||||
p.setBrush(Qt::gray);
|
||||
|
||||
// Always draw edges
|
||||
// TODO: Only draw edges if they are actually visible ...
|
||||
// Draw edges
|
||||
for(GraphEdge & edge : block.edges)
|
||||
{
|
||||
EdgeConfiguration ec = edgeConfiguration(block, edge.dest);
|
||||
QPen pen(edge.color);
|
||||
// if(blockSelected)
|
||||
// pen.setStyle(Qt::DashLine);
|
||||
p.setPen(pen);
|
||||
p.setBrush(edge.color);
|
||||
p.drawPolyline(edge.polyline);
|
||||
pen.setStyle(Qt::SolidLine);
|
||||
p.setPen(pen);
|
||||
if(ec.start_arrow)
|
||||
{
|
||||
p.drawConvexPolygon(edge.arrow_start);
|
||||
}
|
||||
if(ec.end_arrow)
|
||||
{
|
||||
p.drawConvexPolygon(edge.arrow_end);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare graph
|
||||
// This computes the position and (row/col based) size of the block
|
||||
// Recursively calls itself for each child of the GraphBlock
|
||||
void GraphView::computeGraphLayout(GraphBlock &block)
|
||||
{
|
||||
int col = 0;
|
||||
int row_count = 1;
|
||||
int childColumn = 0;
|
||||
bool singleChild = block.new_exits.size() == 1;
|
||||
// Compute all children nodes
|
||||
for(size_t i = 0; i < block.new_exits.size(); i++)
|
||||
{
|
||||
ut64 edge = block.new_exits[i];
|
||||
GraphBlock &edgeb = this->blocks[edge];
|
||||
this->computeGraphLayout(edgeb);
|
||||
row_count = std::max(edgeb.row_count + 1, row_count);
|
||||
childColumn = edgeb.col;
|
||||
}
|
||||
|
||||
if(this->layoutType != LayoutType::Wide && block.new_exits.size() == 2)
|
||||
{
|
||||
GraphBlock &left = this->blocks[block.new_exits[0]];
|
||||
GraphBlock &right= this->blocks[block.new_exits[1]];
|
||||
if(left.new_exits.size() == 0)
|
||||
{
|
||||
left.col = right.col - 2;
|
||||
int add = left.col < 0 ? - left.col : 0;
|
||||
this->adjustGraphLayout(right, add, 1);
|
||||
this->adjustGraphLayout(left, add, 1);
|
||||
col = right.col_count + add;
|
||||
}
|
||||
else if(right.new_exits.size() == 0)
|
||||
{
|
||||
this->adjustGraphLayout(left, 0, 1);
|
||||
this->adjustGraphLayout(right, left.col + 2, 1);
|
||||
col = std::max(left.col_count, right.col + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->adjustGraphLayout(left, 0, 1);
|
||||
this->adjustGraphLayout(right, left.col_count, 1);
|
||||
col = left.col_count + right.col_count;
|
||||
}
|
||||
block.col_count = std::max(2, col);
|
||||
if(layoutType == LayoutType::Medium)
|
||||
{
|
||||
block.col = (left.col + right.col) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
block.col = singleChild ? childColumn : (col - 2) / 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(ut64 edge : block.new_exits)
|
||||
{
|
||||
this->adjustGraphLayout(this->blocks[edge], col, 1);
|
||||
col += this->blocks[edge].col_count;
|
||||
}
|
||||
if(col >= 2)
|
||||
{
|
||||
// Place this node centered over the child nodes
|
||||
block.col = singleChild ? childColumn : (col - 2) / 2;
|
||||
block.col_count = col;
|
||||
}
|
||||
else
|
||||
{
|
||||
//No child nodes, set single node's width (nodes are 2 columns wide to allow
|
||||
//centering over a branch)
|
||||
block.col = 0;
|
||||
block.col_count = 2;
|
||||
}
|
||||
}
|
||||
block.row = 0;
|
||||
block.row_count = row_count;
|
||||
}
|
||||
|
||||
// Edge computing stuff
|
||||
bool GraphView::isEdgeMarked(EdgesVector & edges, int row, int col, int index)
|
||||
{
|
||||
if(index >= int(edges[row][col].size()))
|
||||
return false;
|
||||
return edges[row][col][index];
|
||||
}
|
||||
|
||||
void GraphView::markEdge(EdgesVector & edges, int row, int col, int index, bool used)
|
||||
{
|
||||
while(int(edges[row][col].size()) <= index)
|
||||
edges[row][col].push_back(false);
|
||||
edges[row][col][index] = used;
|
||||
}
|
||||
|
||||
GraphView::GraphEdge GraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector & vert_edges, Matrix<bool> & edge_valid, GraphBlock &start, GraphBlock &end, QColor color)
|
||||
{
|
||||
GraphEdge edge;
|
||||
edge.color = color;
|
||||
edge.dest = &end;
|
||||
|
||||
//Find edge index for initial outgoing line
|
||||
int i = 0;
|
||||
while(true)
|
||||
{
|
||||
if(!this->isEdgeMarked(vert_edges, start.row + 1, start.col + 1, i))
|
||||
break;
|
||||
i += 1;
|
||||
}
|
||||
this->markEdge(vert_edges, start.row + 1, start.col + 1, i);
|
||||
edge.addPoint(start.row + 1, start.col + 1);
|
||||
edge.start_index = i;
|
||||
bool horiz = false;
|
||||
|
||||
//Find valid column for moving vertically to the target node
|
||||
int min_row, max_row;
|
||||
if(end.row < (start.row + 1))
|
||||
{
|
||||
min_row = end.row;
|
||||
max_row = start.row + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
min_row = start.row + 1;
|
||||
max_row = end.row;
|
||||
}
|
||||
int col = start.col + 1;
|
||||
if(min_row != max_row)
|
||||
{
|
||||
auto checkColumn = [min_row, max_row, &edge_valid](int column)
|
||||
{
|
||||
if(column < 0 || column >= int(edge_valid[min_row].size()))
|
||||
return false;
|
||||
for(int row = min_row; row < max_row; row++)
|
||||
{
|
||||
if(!edge_valid[row][column])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if(!checkColumn(col))
|
||||
{
|
||||
if(checkColumn(end.col + 1))
|
||||
{
|
||||
col = end.col + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int ofs = 0;
|
||||
while(true)
|
||||
{
|
||||
col = start.col + 1 - ofs;
|
||||
if(checkColumn(col))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
col = start.col + 1 + ofs;
|
||||
if(checkColumn(col))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ofs += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(col != (start.col + 1))
|
||||
{
|
||||
//Not in same column, need to generate a line for moving to the correct column
|
||||
int min_col, max_col;
|
||||
if(col < (start.col + 1))
|
||||
{
|
||||
min_col = col;
|
||||
max_col = start.col + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
min_col = start.col + 1;
|
||||
max_col = col;
|
||||
}
|
||||
int index = this->findHorizEdgeIndex(horiz_edges, start.row + 1, min_col, max_col);
|
||||
edge.addPoint(start.row + 1, col, index);
|
||||
horiz = true;
|
||||
}
|
||||
|
||||
if(end.row != (start.row + 1))
|
||||
{
|
||||
//Not in same row, need to generate a line for moving to the correct row
|
||||
if(col == (start.col + 1))
|
||||
this->markEdge(vert_edges, start.row + 1, start.col + 1, i, false);
|
||||
int index = this->findVertEdgeIndex(vert_edges, col, min_row, max_row);
|
||||
if(col == (start.col + 1))
|
||||
edge.start_index = index;
|
||||
edge.addPoint(end.row, col, index);
|
||||
horiz = false;
|
||||
}
|
||||
|
||||
if(col != (end.col + 1))
|
||||
{
|
||||
//Not in ending column, need to generate a line for moving to the correct column
|
||||
int min_col, max_col;
|
||||
if(col < (end.col + 1))
|
||||
{
|
||||
min_col = col;
|
||||
max_col = end.col + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
min_col = end.col + 1;
|
||||
max_col = col;
|
||||
}
|
||||
int index = this->findHorizEdgeIndex(horiz_edges, end.row, min_col, max_col);
|
||||
edge.addPoint(end.row, end.col + 1, index);
|
||||
horiz = true;
|
||||
}
|
||||
|
||||
//If last line was horizontal, choose the ending edge index for the incoming edge
|
||||
if(horiz)
|
||||
{
|
||||
int index = this->findVertEdgeIndex(vert_edges, end.col + 1, end.row, end.row);
|
||||
edge.points[int(edge.points.size()) - 1].index = index;
|
||||
}
|
||||
|
||||
return edge;
|
||||
}
|
||||
|
||||
int GraphView::findHorizEdgeIndex(EdgesVector & edges, int row, int min_col, int max_col)
|
||||
{
|
||||
//Find a valid index
|
||||
int i = 0;
|
||||
while(true)
|
||||
{
|
||||
bool valid = true;
|
||||
for(int col = min_col; col < max_col + 1; col++)
|
||||
if(isEdgeMarked(edges, row, col, i))
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if(valid)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
//Mark chosen index as used
|
||||
for(int col = min_col; col < max_col + 1; col++)
|
||||
this->markEdge(edges, row, col, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
int GraphView::findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int max_row)
|
||||
{
|
||||
//Find a valid index
|
||||
int i = 0;
|
||||
while(true)
|
||||
{
|
||||
bool valid = true;
|
||||
for(int row = min_row; row < max_row + 1; row++)
|
||||
if(isEdgeMarked(edges, row, col, i))
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if(valid)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
//Mark chosen index as used
|
||||
for(int row = min_row; row < max_row + 1; row++)
|
||||
this->markEdge(edges, row, col, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
void GraphView::showBlock(GraphBlock &block, bool animated)
|
||||
{
|
||||
showBlock(&block, animated);
|
||||
}
|
||||
|
||||
void GraphView::showBlock(GraphBlock *block, bool animated)
|
||||
{
|
||||
int render_width = viewport()->size().width() / current_scale;
|
||||
|
||||
|
||||
// Show block middle of X
|
||||
int target_x = (block->x + (block->width / 2)) - (render_width/2);
|
||||
int show_block_offset_y = 30;
|
||||
// But beginning of Y (so we show the top of the block)
|
||||
int target_y = block->y - show_block_offset_y;
|
||||
|
||||
target_x = std::max(0, target_x);
|
||||
target_y = std::max(0, target_y);
|
||||
target_x = std::min(this->horizontalScrollBar()->maximum(), target_x);
|
||||
target_y = std::min(this->verticalScrollBar()->maximum(), target_y);
|
||||
if(animated)
|
||||
{
|
||||
QPropertyAnimation *animation_x = new QPropertyAnimation(this->horizontalScrollBar(), "value");
|
||||
animation_x->setDuration(500);
|
||||
animation_x->setStartValue(this->horizontalScrollBar()->value());
|
||||
animation_x->setEndValue(target_x);
|
||||
animation_x->setEasingCurve(QEasingCurve::InOutQuad);
|
||||
animation_x->start();
|
||||
QPropertyAnimation *animation_y = new QPropertyAnimation(this->verticalScrollBar(), "value");
|
||||
animation_y->setDuration(500);
|
||||
animation_y->setStartValue(this->verticalScrollBar()->value());
|
||||
animation_y->setEndValue(target_y);
|
||||
animation_y->setEasingCurve(QEasingCurve::InOutQuad);
|
||||
animation_y->start();
|
||||
} else {
|
||||
this->horizontalScrollBar()->setValue(target_x);
|
||||
this->verticalScrollBar()->setValue(target_y);
|
||||
}
|
||||
|
||||
blockTransitionedTo(block);
|
||||
|
||||
this->viewport()->update();
|
||||
}
|
||||
|
||||
void GraphView::adjustGraphLayout(GraphBlock &block, int col, int row)
|
||||
{
|
||||
block.col += col;
|
||||
block.row += row;
|
||||
for(ut64 edge : block.new_exits)
|
||||
{
|
||||
this->adjustGraphLayout(this->blocks[edge], col, row);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphView::addBlock(GraphView::GraphBlock block)
|
||||
{
|
||||
this->blocks[block.entry] = block;
|
||||
}
|
||||
|
||||
void GraphView::setEntry(ut64 e)
|
||||
{
|
||||
this->entry = e;
|
||||
}
|
||||
|
||||
bool GraphView::checkPointClicked(QPointF &point, int x, int y, bool above_y)
|
||||
{
|
||||
int half_target_size = 5;
|
||||
if((point.x() - half_target_size < x) &&
|
||||
(point.y() - (above_y ? (2 * half_target_size) : 0) < y) &&
|
||||
(x < point.x() + half_target_size) &&
|
||||
(y < point.y() + (above_y ? 0 : (2 * half_target_size))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void GraphView::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
adjustSize(event->size().width(), event->size().height());
|
||||
}
|
||||
|
||||
// Mouse events
|
||||
void GraphView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
int x = (event->pos().x() / current_scale) + horizontalScrollBar()->value();
|
||||
int y = (event->pos().y() / current_scale) + verticalScrollBar()->value();
|
||||
|
||||
// Check if a block was clicked
|
||||
for(auto & blockIt : blocks)
|
||||
{
|
||||
GraphBlock &block = blockIt.second;
|
||||
|
||||
if((block.x <= x) && (block.y <= y) &&
|
||||
(x <= block.x + block.width) & (y <= block.y + block.height))
|
||||
{
|
||||
QPoint pos = QPoint(x - block.x, y - block.y);
|
||||
blockClicked(block, event, pos);
|
||||
// Don't do anything else here! blockClicked might seek and
|
||||
// all our data is invalid then.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a line beginning/end was clicked
|
||||
for(auto & blockIt : this->blocks)
|
||||
{
|
||||
GraphBlock &block = blockIt.second;
|
||||
for(GraphEdge & edge : block.edges)
|
||||
{
|
||||
if(edge.polyline.length() < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
QPointF start = edge.polyline.first();
|
||||
QPointF end = edge.polyline.last();
|
||||
if(checkPointClicked(start, x, y))
|
||||
{
|
||||
showBlock(edge.dest, true);
|
||||
// TODO: Callback to child
|
||||
return;
|
||||
break;
|
||||
}
|
||||
if(checkPointClicked(end, x, y, true))
|
||||
{
|
||||
showBlock(block, true);
|
||||
// TODO: Callback to child
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No block was clicked
|
||||
if(event->button() == Qt::LeftButton)
|
||||
{
|
||||
//Left click outside any block, enter scrolling mode
|
||||
this->scroll_base_x = event->x();
|
||||
this->scroll_base_y = event->y();
|
||||
this->scroll_mode = true;
|
||||
this->setCursor(Qt::ClosedHandCursor);
|
||||
this->viewport()->grabMouse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GraphView::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
if(this->scroll_mode)
|
||||
{
|
||||
int x_delta = this->scroll_base_x - event->x();
|
||||
int y_delta = this->scroll_base_y - event->y();
|
||||
this->scroll_base_x = event->x();
|
||||
this->scroll_base_y = event->y();
|
||||
this->horizontalScrollBar()->setValue(this->horizontalScrollBar()->value() + x_delta);
|
||||
this->verticalScrollBar()->setValue(this->verticalScrollBar()->value() + y_delta);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphView::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
// TODO
|
||||
// if(event->button() == Qt::ForwardButton)
|
||||
// gotoNextSlot();
|
||||
// else if(event->button() == Qt::BackButton)
|
||||
// gotoPreviousSlot();
|
||||
|
||||
if(event->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
if(this->scroll_mode)
|
||||
{
|
||||
this->scroll_mode = false;
|
||||
this->setCursor(Qt::ArrowCursor);
|
||||
this->viewport()->releaseMouse();
|
||||
}
|
||||
}
|
164
src/widgets/GraphView.h
Normal file
164
src/widgets/GraphView.h
Normal file
@ -0,0 +1,164 @@
|
||||
#ifndef GRAPHVIEW_H
|
||||
#define GRAPHVIEW_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPainter>
|
||||
#include <QWidget>
|
||||
#include <QAbstractScrollArea>
|
||||
#include <QScrollBar>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <queue>
|
||||
|
||||
#include "cutter.h"
|
||||
|
||||
class GraphView : public QAbstractScrollArea
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum class LayoutType
|
||||
{
|
||||
Wide,
|
||||
Medium,
|
||||
Narrow,
|
||||
};
|
||||
public:
|
||||
struct GraphBlock;
|
||||
|
||||
struct Point
|
||||
{
|
||||
int row; //point[0]
|
||||
int col; //point[1]
|
||||
int index; //point[2]
|
||||
};
|
||||
|
||||
struct GraphEdge
|
||||
{
|
||||
QColor color;
|
||||
GraphBlock *dest;
|
||||
std::vector<Point> points;
|
||||
int start_index = 0;
|
||||
|
||||
QPolygonF polyline;
|
||||
QPolygonF arrow_start;
|
||||
QPolygonF arrow_end;
|
||||
|
||||
void addPoint(int row, int col, int index = 0)
|
||||
{
|
||||
Point point = {row, col, 0};
|
||||
this->points.push_back(point);
|
||||
if(int(this->points.size()) > 1)
|
||||
this->points[this->points.size() - 2].index = index;
|
||||
}
|
||||
};
|
||||
|
||||
struct GraphBlock {
|
||||
qreal x = 0.0;
|
||||
qreal y = 0.0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
// This is a unique identifier, e.g. offset in the case of r2 blocks
|
||||
ut64 entry;
|
||||
// This contains unique identifiers to entries
|
||||
// Outgoing edges
|
||||
std::vector<ut64> exits;
|
||||
// Incoming edges
|
||||
std::vector<ut64> incoming;
|
||||
// TODO what is this
|
||||
std::vector<ut64> new_exits;
|
||||
|
||||
// Number of rows in block
|
||||
int row_count;
|
||||
// Number of columns in block
|
||||
int col_count;
|
||||
// Column in which the block is
|
||||
int col;
|
||||
// Row in which the block is
|
||||
int row;
|
||||
|
||||
// Edges
|
||||
std::vector<GraphEdge> edges;
|
||||
};
|
||||
|
||||
struct EdgeConfiguration
|
||||
{
|
||||
QColor color = QColor(128, 128, 128);
|
||||
bool start_arrow = false;
|
||||
bool end_arrow = true;
|
||||
};
|
||||
|
||||
GraphView(QWidget *parent);
|
||||
~GraphView();
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
// Show a block centered. Animates to it if animated=true
|
||||
void showBlock(GraphBlock &block, bool animated=false);
|
||||
void showBlock(GraphBlock *block, bool animated=false);
|
||||
|
||||
protected:
|
||||
std::unordered_map<ut64, GraphBlock> blocks;
|
||||
QColor backgroundColor = QColor(Qt::white);
|
||||
// The vertical margin between blocks
|
||||
int block_vertical_margin = 32;
|
||||
|
||||
// Zoom data
|
||||
double current_scale = 1.0;
|
||||
|
||||
void addBlock(GraphView::GraphBlock block);
|
||||
void setEntry(ut64 e);
|
||||
void computeGraph(ut64 entry);
|
||||
|
||||
// Callbacks that should be overridden
|
||||
virtual void drawBlock(QPainter & p, GraphView::GraphBlock &block);
|
||||
virtual void blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos);
|
||||
virtual void blockTransitionedTo(GraphView::GraphBlock *to);
|
||||
virtual EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to);
|
||||
|
||||
void adjustSize(int new_width, int new_height);
|
||||
private:
|
||||
bool checkPointClicked(QPointF &point, int x, int y, bool above_y=false);
|
||||
|
||||
ut64 entry;
|
||||
|
||||
void computeGraphLayout(GraphBlock &block);
|
||||
void adjustGraphLayout(GraphBlock &block, int col, int row);
|
||||
|
||||
// Layout type
|
||||
LayoutType layoutType;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
bool ready;
|
||||
|
||||
// Scrolling data
|
||||
int scroll_base_x;
|
||||
int scroll_base_y;
|
||||
bool scroll_mode;
|
||||
|
||||
|
||||
// Todo: remove charheight/charwidth cause it should be handled in child class
|
||||
qreal charWidth = 10.0;
|
||||
|
||||
// Edge computing stuff
|
||||
template<typename T>
|
||||
using Matrix = std::vector<std::vector<T>>;
|
||||
using EdgesVector = Matrix<std::vector<bool>>;
|
||||
std::vector<int> col_edge_x;
|
||||
std::vector<int> row_edge_y;
|
||||
bool isEdgeMarked(EdgesVector & edges, int row, int col, int index);
|
||||
void markEdge(EdgesVector & edges, int row, int col, int index, bool used = true);
|
||||
int findHorizEdgeIndex(EdgesVector & edges, int row, int min_col, int max_col);
|
||||
int findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int max_row);
|
||||
GraphEdge routeEdge(EdgesVector & horiz_edges, EdgesVector & vert_edges, Matrix<bool> & edge_valid, GraphBlock &start, GraphBlock &end, QColor color);
|
||||
|
||||
private slots:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
// Mouse events
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // GRAPHVIEW_H
|
Loading…
Reference in New Issue
Block a user