Multiple graph/disasm/hexdump widgets and independent seeks (#504)

This commit is contained in:
fcasal 2018-05-25 15:30:59 +01:00 committed by xarkes
parent f2dab2d198
commit 0cea9e3287
15 changed files with 329 additions and 49 deletions

View File

@ -55,7 +55,7 @@ CutterCore::CutterCore(QObject *parent) :
// Otherwise r2 may ask the user for input and Cutter would freeze
setConfig("scr.interactive", false);
#if defined(APPIMAGE) || defined(MACOS_R2_BUNDLED)
auto prefix = QDir(QCoreApplication::applicationDirPath());
# ifdef APPIMAGE

View File

@ -155,6 +155,7 @@ SOURCES += \
utils/NestedIPyKernel.cpp \
dialogs/R2PluginsDialog.cpp \
widgets/CutterDockWidget.cpp \
widgets/CutterSeekableWidget.cpp \
widgets/GraphWidget.cpp \
utils/JsonTreeItem.cpp \
utils/JsonModel.cpp \
@ -229,6 +230,7 @@ HEADERS += \
utils/NestedIPyKernel.h \
dialogs/R2PluginsDialog.h \
widgets/CutterDockWidget.h \
widgets/CutterSeekableWidget.h \
widgets/GraphWidget.h \
utils/JsonTreeItem.h \
utils/JsonModel.h \

View File

@ -223,6 +223,32 @@ void MainWindow::initUI()
connect(core, SIGNAL(projectSaved(const QString &)), this, SLOT(projectSaved(const QString &)));
}
void MainWindow::on_actionExtraGraph_triggered()
{
QDockWidget *extraDock = new GraphWidget(this, 0);
addExtraWidget(extraDock);
}
void MainWindow::on_actionExtraHexdump_triggered()
{
QDockWidget *extraDock = new HexdumpWidget(this, 0);
addExtraWidget(extraDock);
}
void MainWindow::on_actionExtraDisassembly_triggered()
{
QDockWidget *extraDock = new DisassemblyWidget(this, 0);
addExtraWidget(extraDock);
}
void MainWindow::addExtraWidget(QDockWidget *extraDock)
{
addDockWidget(Qt::TopDockWidgetArea, extraDock);
auto restoreExtraDock = qhelpers::forceWidth(extraDock->widget(), 600);
qApp->processEvents();
restoreExtraDock.restoreWidth(extraDock->widget());
}
void MainWindow::openNewFile(const QString &fn, int analLevel, QList<QString> advancedOptions)
{
setFilename(fn);
@ -525,6 +551,23 @@ void MainWindow::showDefaultDocks()
updateDockActionsChecked();
}
void MainWindow::showZenDocks()
{
const QList<QDockWidget *> zenDocks = { functionsDock,
stringsDock,
graphDock,
disassemblyDock,
hexdumpDock,
searchDock
};
for (auto w : dockWidgets) {
if (zenDocks.contains(w)) {
w->show();
}
}
updateDockActionsChecked();
}
void MainWindow::resetToDefaultLayout()
{
hideAllDocks();
@ -545,6 +588,24 @@ void MainWindow::resetToDefaultLayout()
Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Disassembly);
}
void MainWindow::resetToZenLayout()
{
hideAllDocks();
restoreDocks();
showZenDocks();
disassemblyDock->raise();
// ugly workaround to set the default widths of functions
// if anyone finds a way to do this cleaner that also works, feel free to change it!
auto restoreFunctionDock = qhelpers::forceWidth(functionsDock->widget(), 200);
qApp->processEvents();
restoreFunctionDock.restoreWidth(functionsDock->widget());
Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Disassembly);
}
void MainWindow::addOutput(const QString &msg)
{
consoleDock->addOutput(msg);
@ -590,6 +651,11 @@ void MainWindow::on_actionDefault_triggered()
resetToDefaultLayout();
}
void MainWindow::on_actionZen_triggered()
{
resetToZenLayout();
}
void MainWindow::on_actionNew_triggered()
{
on_actionOpen_triggered();
@ -761,7 +827,6 @@ bool MainWindow::eventFilter(QObject *, QEvent *event)
return true;
}
}
return false;
}

View File

@ -95,6 +95,8 @@ public:
void addToDockWidgetList(QDockWidget *dockWidget);
void addDockWidgetAction(QDockWidget *dockWidget, QAction *action);
void addExtraWidget(QDockWidget *extraDock);
public slots:
@ -119,12 +121,16 @@ public slots:
private slots:
void on_actionAbout_triggered();
void on_actionExtraGraph_triggered();
void on_actionExtraHexdump_triggered();
void on_actionExtraDisassembly_triggered();
void on_actionRefresh_Panels_triggered();
void on_actionDisasAdd_comment_triggered();
void on_actionDefault_triggered();
void on_actionZen_triggered();
void on_actionFunctionsRename_triggered();
@ -211,10 +217,12 @@ private:
#endif
void resetToDefaultLayout();
void resetToZenLayout();
void restoreDocks();
void hideAllDocks();
void showDefaultDocks();
void showZenDocks();
void updateDockActionsChecked();
void toggleDockWidget(QDockWidget *dock_widget, bool show);

View File

@ -201,6 +201,7 @@ border-top: 0px;
<addaction name="actionRefresh_contents"/>
<addaction name="separator"/>
<addaction name="actionDefault"/>
<addaction name="actionZen"/>
<addaction name="actionReset_settings"/>
<addaction name="separator"/>
<addaction name="actionLock"/>
@ -256,6 +257,8 @@ border-top: 0px;
<addaction name="actionComments"/>
<addaction name="actionConsole"/>
<addaction name="actionJupyter"/>
<addaction name="separator"/>
<addaction name="addExtraWidgets"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
@ -322,6 +325,14 @@ background-color: palette(dark);
<string>Reset layout</string>
</property>
</action>
<action name="actionZen">
<property name="text">
<string>Zen Mode</string>
</property>
<property name="toolTip">
<string>Zen mode</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
@ -1093,6 +1104,29 @@ background-color: palette(dark);
<string>Jupyter</string>
</property>
</action>
<action name="actionExtraGraph">
<property name="text">
<string>Graph view</string>
</property>
</action>
<action name="actionExtraHexdump">
<property name="text">
<string>Hexdump view</string>
</property>
</action>
<action name="actionExtraDisassembly">
<property name="text">
<string>Disassembly view</string>
</property>
</action>
<widget class="QMenu" name="addExtraWidgets">
<property name="title">
<string>Add extra...</string>
</property>
<addaction name="actionExtraGraph"/>
<addaction name="actionExtraHexdump"/>
<addaction name="actionExtraDisassembly"/>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>

View File

@ -6,8 +6,8 @@ CutterDockWidget::CutterDockWidget(MainWindow *main, QAction *action) :
QDockWidget(main),
action(action)
{
main->addToDockWidgetList(this);
if (action) {
main->addToDockWidgetList(this);
main->addDockWidgetAction(this, action);
connect(action, &QAction::triggered, this, &CutterDockWidget::toggleDockWidget);
}

View File

@ -0,0 +1,67 @@
#include "MainWindow.h"
#include "CutterSeekableWidget.h"
CutterSeekableWidget::CutterSeekableWidget(QObject *parent)
:
QObject(parent)
{
connect(Core(), &CutterCore::seekChanged, this, &CutterSeekableWidget::onSeekChanged);
}
void CutterSeekableWidget::onSeekChanged(RVA addr)
{
if (isInSyncWithCore) {
emit seekChanged(addr);
}
}
void CutterSeekableWidget::seek(RVA addr)
{
if (isInSyncWithCore) {
Core()->seek(addr);
}
else {
prevIdenpendentOffset = independentOffset;
independentOffset = addr;
emit seekChanged(addr);
}
}
RVA CutterSeekableWidget::getOffset()
{
RVA addr;
if (isInSyncWithCore) {
addr = Core()->getOffset();
}
else {
addr = independentOffset;
}
return addr;
}
void CutterSeekableWidget::toggleSyncWithCore()
{
isInSyncWithCore = !isInSyncWithCore;
}
RVA CutterSeekableWidget::getIndependentOffset()
{
return independentOffset;
}
RVA CutterSeekableWidget::getPrevIndependentOffset()
{
return prevIdenpendentOffset;
}
bool CutterSeekableWidget::getSyncWithCore()
{
return isInSyncWithCore;
}
void CutterSeekableWidget::setIndependentOffset(RVA addr)
{
independentOffset = addr;
}
CutterSeekableWidget::~CutterSeekableWidget() {}

View File

@ -0,0 +1,32 @@
#pragma once
#include "Cutter.h"
class MainWindow;
class CutterSeekableWidget : public QObject
{
Q_OBJECT
public:
explicit CutterSeekableWidget(QObject *parent = nullptr);
~CutterSeekableWidget();
void seek(RVA addr);
void toggleSyncWithCore();
RVA getOffset();
RVA getIndependentOffset();
RVA getPrevIndependentOffset();
bool getSyncWithCore();
void setIndependentOffset(RVA addr);
void onSeekChanged(RVA addr);
private:
RVA independentOffset = RVA_INVALID;
RVA prevIdenpendentOffset = RVA_INVALID;
bool isInSyncWithCore = true;
signals:
void seekChanged(RVA addr);
};

View File

@ -1,5 +1,5 @@
#include "DisassemblerGraphView.h"
#include "CutterSeekableWidget.h"
#include <QPainter>
#include <QJsonObject>
#include <QJsonArray>
@ -20,7 +20,8 @@
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent)
: GraphView(parent),
mFontMetrics(nullptr),
mMenu(new DisassemblyContextMenu(this))
mMenu(new DisassemblyContextMenu(this)),
seekable(new CutterSeekableWidget(this))
{
highlight_token = nullptr;
// Signals that require a refresh all
@ -99,16 +100,21 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent)
mMenu->addAction(&actionExportGraph);
connect(&actionExportGraph, SIGNAL(triggered(bool)), this, SLOT(on_actionExportGraph_triggered()));
mMenu->addSeparator();
actionSyncOffset.setText(tr("Sync/unsync offset"));
mMenu->addAction(&actionSyncOffset);
connect(&actionSyncOffset, SIGNAL(triggered(bool)), this, SLOT(toggleSync()));
initFont();
colorsUpdatedSlot();
}
void DisassemblerGraphView::connectSeekChanged(bool disconnect)
void DisassemblerGraphView::connectSeekChanged(bool disconn)
{
if (disconnect) {
QObject::disconnect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(onSeekChanged(RVA)));
if (disconn) {
disconnect(seekable, &CutterSeekableWidget::seekChanged, this, &DisassemblerGraphView::onSeekChanged);
} else {
connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(onSeekChanged(RVA)));
connect(seekable, &CutterSeekableWidget::seekChanged, this, &DisassemblerGraphView::onSeekChanged);
}
}
@ -119,6 +125,17 @@ DisassemblerGraphView::~DisassemblerGraphView()
}
}
void DisassemblerGraphView::toggleSync()
{
seekable->toggleSyncWithCore();
if (seekable->getSyncWithCore()) {
parentWidget()->setWindowTitle(windowTitle);
} else {
parentWidget()->setWindowTitle(windowTitle + " (not synced)");
seekable->setIndependentOffset(Core()->getOffset());
}
}
void DisassemblerGraphView::refreshView()
{
initFont();
@ -134,7 +151,7 @@ void DisassemblerGraphView::loadCurrentGraph()
.set("asm.bbline", false)
.set("asm.lines", false)
.set("asm.lines.fcn", false);
QJsonDocument functionsDoc = Core()->cmdj("agJ");
QJsonDocument functionsDoc = Core()->cmdj("agJ " + RAddressString(seekable->getOffset()));
QJsonArray functions = functionsDoc.array();
disassembly_blocks.clear();
@ -149,12 +166,16 @@ void DisassemblerGraphView::loadCurrentGraph()
f.ready = true;
f.entry = func["offset"].toVariant().toULongLong();
QString windowTitle = tr("Graph");
windowTitle = tr("Graph");
QString funcName = func["name"].toString().trimmed();
if (!funcName.isEmpty()) {
windowTitle += " (" + funcName + ")";
}
parentWidget()->setWindowTitle(windowTitle);
if (!seekable->getSyncWithCore()) {
parentWidget()->setWindowTitle(windowTitle + " (not synced)");
} else {
parentWidget()->setWindowTitle(windowTitle);
}
RVA entry = func["offset"].toVariant().toULongLong();
@ -297,7 +318,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
// Figure out if the current block is selected
for (const Instr &instr : db.instrs) {
RVA addr = Core()->getOffset();
RVA addr = seekable->getOffset();
if ((instr.addr <= addr) && (addr <= instr.addr + instr.size)) {
block_selected = true;
selected_instruction = instr.addr;
@ -543,27 +564,27 @@ void DisassemblerGraphView::zoomReset()
void DisassemblerGraphView::takeTrue()
{
DisassemblyBlock *db = blockForAddress(Core()->getOffset());
DisassemblyBlock *db = blockForAddress(seekable->getOffset());
if (db->true_path != RVA_INVALID) {
Core()->seek(db->true_path);
seekable->seek(db->true_path);
} else if (blocks[db->entry].exits.size()) {
Core()->seek(blocks[db->entry].exits[0]);
seekable->seek(blocks[db->entry].exits[0]);
}
}
void DisassemblerGraphView::takeFalse()
{
DisassemblyBlock *db = blockForAddress(Core()->getOffset());
DisassemblyBlock *db = blockForAddress(seekable->getOffset());
if (db->false_path != RVA_INVALID) {
Core()->seek(db->false_path);
seekable->seek(db->false_path);
} else if (blocks[db->entry].exits.size()) {
Core()->seek(blocks[db->entry].exits[0]);
seekable->seek(blocks[db->entry].exits[0]);
}
}
void DisassemblerGraphView::seekInstruction(bool previous_instr)
{
RVA addr = Core()->getOffset();
RVA addr = seekable->getOffset();
DisassemblyBlock *db = blockForAddress(addr);
if (!db) {
return;
@ -577,9 +598,9 @@ void DisassemblerGraphView::seekInstruction(bool previous_instr)
// Found the instructon. Check if a next one exists
if (!previous_instr && (i < db->instrs.size() - 1)) {
seek(db->instrs[i + 1].addr, true);
seekable->seek(db->instrs[i + 1].addr);
} else if (previous_instr && (i > 0)) {
seek(db->instrs[i - 1].addr);
seekable->seek(db->instrs[i - 1].addr);
}
}
}
@ -594,10 +615,10 @@ void DisassemblerGraphView::prevInstr()
seekInstruction(true);
}
void DisassemblerGraphView::seek(RVA addr, bool update_viewport)
void DisassemblerGraphView::seekLocal(RVA addr, bool update_viewport)
{
connectSeekChanged(true);
Core()->seek(addr);
seekable->seek(addr);
connectSeekChanged(false);
if (update_viewport) {
viewport()->update();
@ -606,7 +627,12 @@ void DisassemblerGraphView::seek(RVA addr, bool update_viewport)
void DisassemblerGraphView::seekPrev()
{
Core()->seekPrev();
if (seekable->getSyncWithCore()) {
Core()->seekPrev();
}
else {
seekable->seek(seekable->getPrevIndependentOffset());
}
}
void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event,
@ -617,7 +643,7 @@ void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEve
return;
}
seek(instr, true);
seekLocal(instr);
if (event->button() == Qt::RightButton) {
mMenu->setOffset(instr);
@ -629,13 +655,14 @@ void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMo
QPoint pos)
{
Q_UNUSED(event);
RVA instr = getAddrForMouseEvent(block, &pos);
if (instr == RVA_INVALID) {
return;
}
QList<XrefDescription> refs = Core()->getXRefs(instr, false, false);
if (refs.length()) {
Core()->seek(refs.at(0).to);
seekable->seek(refs.at(0).to);
}
if (refs.length() > 1) {
qWarning() << "Too many references here. Weird behaviour expected.";
@ -671,7 +698,7 @@ void DisassemblerGraphView::blockTransitionedTo(GraphView::GraphBlock *to)
transition_dont_seek = false;
return;
}
seek(to->entry);
seekLocal(to->entry);
}

View File

@ -10,6 +10,7 @@
#include "widgets/GraphView.h"
#include "menus/DisassemblyContextMenu.h"
#include "utils/RichTextPainter.h"
#include "CutterSeekableWidget.h"
class DisassemblerGraphView : public GraphView
{
@ -142,6 +143,7 @@ public:
virtual void blockTransitionedTo(GraphView::GraphBlock *to) override;
void loadCurrentGraph();
QString windowTitle;
// bool navigate(ut64 addr);
public slots:
@ -149,6 +151,7 @@ public slots:
void colorsUpdatedSlot();
void fontsUpdatedSlot();
void onSeekChanged(RVA addr);
void toggleSync();
void zoomIn(QPoint mouse = QPoint(0, 0));
void zoomOut(QPoint mouse = QPoint(0, 0));
@ -189,9 +192,9 @@ private:
RVA getAddrForMouseEvent(GraphBlock &block, QPoint *point);
Instr *getInstrForMouseEvent(GraphBlock &block, QPoint *point);
DisassemblyBlock *blockForAddress(RVA addr);
void seek(RVA addr, bool update_viewport = true);
void seekLocal(RVA addr, bool update_viewport = true);
void seekInstruction(bool previous_instr);
CutterSeekableWidget *seekable = nullptr;
QList<QShortcut *> shortcuts;
QColor disassemblyBackgroundColor;
@ -218,6 +221,7 @@ private:
QColor mDisabledBreakpointColor;
QAction actionExportGraph;
QAction actionSyncOffset;
};
#endif // DISASSEMBLERGRAPHVIEW_H

View File

@ -41,6 +41,7 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action)
, mCtxMenu(new DisassemblyContextMenu(this))
, mDisasScrollArea(new DisassemblyScrollArea(this))
, mDisasTextEdit(new DisassemblyTextEdit(this))
, seekable(new CutterSeekableWidget(this))
{
topOffset = bottomOffset = RVA_INVALID;
cursorLineOffset = 0;
@ -104,7 +105,6 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action)
}
});
connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(on_seekChanged(RVA)));
connect(Core(), SIGNAL(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType)), this,
SLOT(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType)));
connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshDisasm()));
@ -140,6 +140,11 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action)
shortcut_escape->setContext(Qt::WidgetShortcut);
connect(shortcut_escape, SIGNAL(activated()), this, SLOT(seekPrev()));
mCtxMenu->addSeparator();
syncIt.setText(tr("Sync/unsync offset"));
mCtxMenu->addAction(&syncIt);
connect(&syncIt, SIGNAL(triggered(bool)), this, SLOT(toggleSync()));
connect(seekable, &CutterSeekableWidget::seekChanged, this, &DisassemblyWidget::on_seekChanged);
#define ADD_SHORTCUT(ksq, slot) { \
QShortcut *s = new QShortcut((ksq), this); \
@ -167,6 +172,18 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action)
#undef ADD_SHORTCUT
}
void DisassemblyWidget::toggleSync()
{
QString windowTitle = tr("Disassembly");
seekable->toggleSyncWithCore();
if (seekable->getSyncWithCore()) {
setWindowTitle(windowTitle);
} else {
setWindowTitle(windowTitle + " (not synced)");
seekable->setIndependentOffset(Core()->getOffset());
}
}
QWidget *DisassemblyWidget::getTextWidget()
{
return mDisasTextEdit;
@ -354,7 +371,7 @@ RVA DisassemblyWidget::readDisassemblyOffset(QTextCursor tc)
void DisassemblyWidget::updateCursorPosition()
{
RVA offset = Core()->getOffset();
RVA offset = seekable->getOffset();
// already fine where it is?
RVA currentLineOffset = readCurrentDisassemblyOffset();
@ -432,7 +449,7 @@ void DisassemblyWidget::cursorPositionChanged()
}
seekFromCursor = true;
Core()->seek(offset);
seekable->seek(offset);
seekFromCursor = false;
highlightCurrentLine();
mCtxMenu->setCanCopy(mDisasTextEdit->textCursor().hasSelection());
@ -492,8 +509,8 @@ void DisassemblyWidget::moveCursorRelative(bool up, bool page)
// handle cases where top instruction offsets change
RVA offset = readCurrentDisassemblyOffset();
if (offset != Core()->getOffset()) {
Core()->seek(offset);
if (offset != seekable->getOffset()) {
seekable->seek(offset);
highlightCurrentLine();
}
}
@ -520,7 +537,7 @@ bool DisassemblyWidget::eventFilter(QObject *obj, QEvent *event)
}
if (jump != RVA_INVALID) {
Core()->seek(jump);
seekable->seek(jump);
}
return true;

View File

@ -3,9 +3,11 @@
#include "Cutter.h"
#include "CutterDockWidget.h"
#include "CutterSeekableWidget.h"
#include <QTextEdit>
#include <QPlainTextEdit>
#include <QShortcut>
#include <QAction>
class DisassemblyTextEdit;
@ -26,6 +28,7 @@ public slots:
void fontsUpdatedSlot();
void colorsUpdatedSlot();
void seekPrev();
void toggleSync();
private slots:
void on_seekChanged(RVA offset);
@ -63,6 +66,8 @@ private:
void connectCursorPositionChanged(bool disconnect);
void moveCursorRelative(bool up, bool page);
QAction syncIt;
CutterSeekableWidget *seekable;
};
class DisassemblyScrollArea : public QAbstractScrollArea

View File

@ -23,9 +23,6 @@ GraphWidget::GraphWidget(MainWindow *main, QAction *action) :
this->graphView->setFocus();
}
});
}
GraphWidget::~GraphWidget() {}

View File

@ -15,7 +15,8 @@
HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) :
CutterDockWidget(main, action),
ui(new Ui::HexdumpWidget)
ui(new Ui::HexdumpWidget),
seekable(new CutterSeekableWidget(this))
{
ui->setupUi(this);
@ -44,6 +45,8 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) :
colorsUpdatedSlot();
updateHeaders();
this->setWindowTitle(tr("Hexdump"));
connect(&syncAction, SIGNAL(triggered(bool)), this, SLOT(toggleSync()));
// Set hexdump context menu
ui->hexHexText->setContextMenuPolicy(Qt::CustomContextMenu);
@ -61,7 +64,6 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) :
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdated()));
connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot()));
connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(on_seekChanged(RVA)));
connect(Core(), SIGNAL(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType)), this,
SLOT(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType)));
@ -80,6 +82,7 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) :
connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexdumpWidget::selectionChanged);
connect(ui->hexASCIIText, &QTextEdit::cursorPositionChanged, this,
&HexdumpWidget::selectionChanged);
connect(seekable, &CutterSeekableWidget::seekChanged, this, &HexdumpWidget::on_seekChanged);
format = Format::Hex;
initParsing();
@ -281,7 +284,7 @@ void HexdumpWidget::refresh(RVA addr)
updateHeaders();
if (addr == RVA_INVALID) {
addr = Core()->getOffset();
addr = seekable->getOffset();
}
cols = Core()->getConfigi("hex.cols");
@ -323,7 +326,7 @@ void HexdumpWidget::refresh(RVA addr)
QTextBlockFormat formatTmp = offsetCursor.blockFormat();
formatTmp.setBackground(QColor(64, 129, 160));
offsetCursor.setBlockFormat(formatTmp);
updateWidths();
// Update other text areas scroll
@ -452,7 +455,7 @@ void HexdumpWidget::selectionChanged()
int pos = asciiAddressToPosition(adr);
setTextEditPosition(ui->hexASCIIText, pos);
sent_seek = true;
Core()->seek(adr);
seekable->seek(adr);
sent_seek = false;
connectScroll(false);
return;
@ -503,7 +506,7 @@ void HexdumpWidget::selectionChanged()
targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor);
ui->hexASCIIText->setTextCursor(targetTextCursor);
sent_seek = true;
Core()->seek(startAddress);
seekable->seek(startAddress);
sent_seek = false;
} else {
QTextCursor textCursor = ui->hexASCIIText->textCursor();
@ -514,7 +517,7 @@ void HexdumpWidget::selectionChanged()
setTextEditPosition(ui->hexHexText, pos);
connectScroll(false);
sent_seek = true;
Core()->seek(adr);
seekable->seek(adr);
sent_seek = false;
return;
}
@ -534,7 +537,7 @@ void HexdumpWidget::selectionChanged()
targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor);
ui->hexHexText->setTextCursor(targetTextCursor);
sent_seek = true;
Core()->seek(startAddress);
seekable->seek(startAddress);
sent_seek = false;
}
@ -570,6 +573,11 @@ void HexdumpWidget::showHexdumpContextMenu(const QPoint &pt)
QMenu *formatSubmenu = menu->addMenu(tr("Format"));
formatSubmenu->addAction(ui->actionFormatHex);
formatSubmenu->addAction(ui->actionFormatOctal);
menu->addSeparator();
syncAction.setText(tr("Sync/unsync offset"));
menu->addAction(&syncAction);
// TODO:
// formatSubmenu->addAction(ui->actionFormatHalfWord);
// formatSubmenu->addAction(ui->actionFormatWord);
@ -595,6 +603,18 @@ void HexdumpWidget::showHexdumpContextMenu(const QPoint &pt)
delete menu;
}
void HexdumpWidget::toggleSync()
{
QString windowTitle = tr("Hexdump");
seekable->toggleSyncWithCore();
if (seekable->getSyncWithCore()) {
setWindowTitle(windowTitle);
} else {
setWindowTitle(windowTitle + " (not synced)");
seekable->setIndependentOffset(Core()->getOffset());
}
}
void HexdumpWidget::showHexASCIIContextMenu(const QPoint &pt)
{
// Set Hex ASCII popup menu

View File

@ -10,6 +10,7 @@
#include "Cutter.h"
#include "CutterDockWidget.h"
#include "CutterSeekableWidget.h"
#include "utils/Highlighter.h"
#include "utils/HexAsciiHighlighter.h"
#include "utils/HexHighlighter.h"
@ -27,9 +28,7 @@ class HexdumpWidget : public CutterDockWidget
public:
explicit HexdumpWidget(MainWindow *main, QAction *action = nullptr);
~HexdumpWidget();
Highlighter *highlighter;
enum Format {
Hex,
Octal,
@ -50,6 +49,7 @@ public slots:
void zoomIn(int range = 1);
void zoomOut(int range = 1);
void toggleSync();
protected:
virtual void resizeEvent(QResizeEvent *event) override;
@ -102,6 +102,8 @@ private:
int bufferLines;
int cols;
QAction syncAction;
CutterSeekableWidget *seekable;
private slots:
void on_seekChanged(RVA addr);