cutter/src/widgets/DisassemblyWidget.cpp

631 lines
19 KiB
C++
Raw Normal View History

#include "DisassemblyWidget.h"
#include "menus/DisassemblyContextMenu.h"
#include "utils/HexAsciiHighlighter.h"
#include "utils/HexHighlighter.h"
#include "utils/Configuration.h"
#include "utils/Helpers.h"
#include "utils/TempConfig.h"
2017-10-16 19:00:47 +00:00
#include <QScrollBar>
#include <QJsonArray>
#include <QJsonObject>
#include <QVBoxLayout>
#include <QRegularExpression>
#include <QTextBlockUserData>
class DisassemblyTextBlockUserData: public QTextBlockUserData
{
public:
DisassemblyLine line;
explicit DisassemblyTextBlockUserData(const DisassemblyLine &line)
{
this->line = line;
}
};
static DisassemblyTextBlockUserData *getUserData(const QTextBlock &block)
{
QTextBlockUserData *userData = block.userData();
2018-03-21 20:32:32 +00:00
if (!userData) {
return nullptr;
}
return static_cast<DisassemblyTextBlockUserData *>(userData);
}
DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action)
: CutterDockWidget(main, action)
, mCtxMenu(new DisassemblyContextMenu(this))
, mDisasScrollArea(new DisassemblyScrollArea(this))
, mDisasTextEdit(new DisassemblyTextEdit(this))
{
topOffset = bottomOffset = RVA_INVALID;
cursorLineOffset = 0;
seekFromCursor = false;
setWindowTitle(tr("Disassembly"));
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(mDisasTextEdit);
layout->setMargin(0);
mDisasScrollArea->viewport()->setLayout(layout);
mDisasScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setWidget(mDisasScrollArea);
setAllowedAreas(Qt::AllDockWidgetAreas);
setObjectName("DisassemblyWidget");
2017-11-30 14:00:22 +00:00
setupFonts();
setupColors();
maxLines = 0;
updateMaxLines();
mDisasTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mDisasTextEdit->setFont(Config()->getFont());
2017-10-16 19:00:47 +00:00
mDisasTextEdit->setReadOnly(true);
mDisasTextEdit->setLineWrapMode(QPlainTextEdit::WidgetWidth);
// wrapping breaks readCurrentDisassemblyOffset() at the moment :-(
mDisasTextEdit->setWordWrapMode(QTextOption::NoWrap);
// Increase asm text edit margin
QTextDocument *asm_docu = mDisasTextEdit->document();
asm_docu->setDocumentMargin(10);
// Event filter to intercept double clicks in the textbox
mDisasTextEdit->viewport()->installEventFilter(this);
// Set Disas context menu
mDisasTextEdit->setContextMenuPolicy(Qt::CustomContextMenu);
connect(mDisasTextEdit, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showDisasContextMenu(const QPoint &)));
// Space to switch to graph
QShortcut *graphShortcut = new QShortcut(QKeySequence(Qt::Key_Space), this);
graphShortcut->setContext(Qt::WidgetWithChildrenShortcut);
2018-03-21 20:32:32 +00:00
connect(graphShortcut, &QShortcut::activated, this, [] {
Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Graph);
Core()->triggerRaisePrioritizedMemoryWidget();
});
connect(mDisasScrollArea, SIGNAL(scrollLines(int)), this, SLOT(scrollInstructions(int)));
connect(mDisasScrollArea, SIGNAL(disassemblyResized()), this, SLOT(updateMaxLines()));
connectCursorPositionChanged(false);
2018-03-21 20:32:32 +00:00
connect(mDisasTextEdit->verticalScrollBar(), &QScrollBar::valueChanged, this, [ = ](int value) {
if (value != 0) {
mDisasTextEdit->verticalScrollBar()->setValue(0);
}
});
connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(on_seekChanged(RVA)));
2018-03-21 20:32:32 +00:00
connect(Core(), SIGNAL(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType)), this,
SLOT(raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType)));
connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshDisasm()));
connect(Core(), SIGNAL(flagsChanged()), this, SLOT(refreshDisasm()));
connect(Core(), SIGNAL(functionsChanged()), this, SLOT(refreshDisasm()));
2018-03-21 20:32:32 +00:00
connect(Core(), SIGNAL(functionRenamed(const QString &, const QString &)), this,
SLOT(refreshDisasm()));
connect(Core(), SIGNAL(varsChanged()), this, SLOT(refreshDisasm()));
connect(Core(), SIGNAL(asmOptionsChanged()), this, SLOT(refreshDisasm()));
connect(Core(), &CutterCore::instructionChanged, this, [this](RVA offset) {
2018-03-21 20:32:32 +00:00
if (offset >= topOffset && offset <= bottomOffset) {
refreshDisasm();
}
});
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdatedSlot()));
2017-11-20 11:23:37 +00:00
connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot()));
connect(this, &QDockWidget::visibilityChanged, this, [](bool visibility) {
2018-03-21 20:32:32 +00:00
if (visibility) {
Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Disassembly);
}
});
connect(Core(), &CutterCore::refreshAll, this, [this]() {
refreshDisasm(Core()->getOffset());
});
2017-12-02 15:43:21 +00:00
connect(mCtxMenu, SIGNAL(copy()), mDisasTextEdit, SLOT(copy()));
// Dirty
QShortcut *shortcut_escape = new QShortcut(QKeySequence(Qt::Key_Escape), this);
shortcut_escape->setContext(Qt::WidgetShortcut);
connect(shortcut_escape, SIGNAL(activated()), this, SLOT(seekPrev()));
#define ADD_SHORTCUT(ksq, slot) { \
QShortcut *s = new QShortcut((ksq), this); \
s->setContext(Qt::WidgetShortcut); \
connect(s, &QShortcut::activated, this, (slot)); \
}
2018-03-21 20:32:32 +00:00
ADD_SHORTCUT(QKeySequence(Qt::Key_J), [this]() {
moveCursorRelative(false, false);
})
ADD_SHORTCUT(QKeySequence::MoveToNextLine, [this]() {
moveCursorRelative(false, false);
})
ADD_SHORTCUT(QKeySequence(Qt::Key_K), [this]() {
moveCursorRelative(true, false);
})
ADD_SHORTCUT(QKeySequence::MoveToPreviousLine, [this]() {
moveCursorRelative(true, false);
})
ADD_SHORTCUT(QKeySequence::MoveToNextPage, [this]() {
moveCursorRelative(false, true);
})
ADD_SHORTCUT(QKeySequence::MoveToPreviousPage, [this]() {
moveCursorRelative(true, true);
})
#undef ADD_SHORTCUT
}
2018-03-21 20:32:32 +00:00
QWidget *DisassemblyWidget::getTextWidget()
2017-10-22 10:21:44 +00:00
{
return mDisasTextEdit;
}
void DisassemblyWidget::refreshDisasm(RVA offset)
2017-10-22 13:55:42 +00:00
{
2018-03-21 20:32:32 +00:00
if (offset != RVA_INVALID) {
topOffset = offset;
}
2018-03-21 20:32:32 +00:00
if (topOffset == RVA_INVALID) {
return;
}
2018-03-21 20:32:32 +00:00
if (maxLines <= 0) {
2017-11-18 12:56:48 +00:00
connectCursorPositionChanged(true);
mDisasTextEdit->clear();
2017-11-18 12:56:48 +00:00
connectCursorPositionChanged(false);
return;
}
int horizontalScrollValue = mDisasTextEdit->horizontalScrollBar()->value();
mDisasTextEdit->setLockScroll(true); // avoid flicker
2018-03-21 20:32:32 +00:00
QList<DisassemblyLine> disassemblyLines;
{
TempConfig tempConfig;
tempConfig.set("scr.html", true)
2018-03-21 20:32:32 +00:00
.set("scr.color", COLOR_MODE_16M);
disassemblyLines = Core()->disassembleLines(topOffset, maxLines);
}
connectCursorPositionChanged(true);
2017-10-22 13:55:42 +00:00
mDisasTextEdit->document()->clear();
QTextCursor cursor(mDisasTextEdit->document());
2018-03-21 20:32:32 +00:00
for (DisassemblyLine line : disassemblyLines) {
cursor.insertHtml(line.text);
auto a = new DisassemblyTextBlockUserData(line);
cursor.block().setUserData(a);
cursor.insertBlock();
}
// get bottomOffset from last visible line.
// because pd N may return more than N lines, move maxLines lines down from the top
mDisasTextEdit->moveCursor(QTextCursor::Start);
QTextCursor tc = mDisasTextEdit->textCursor();
tc.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, maxLines - 1);
mDisasTextEdit->setTextCursor(tc);
connectCursorPositionChanged(false);
bottomOffset = readCurrentDisassemblyOffset();
2018-03-21 20:32:32 +00:00
if (bottomOffset == RVA_INVALID) {
bottomOffset = topOffset;
}
// remove additional lines
tc.movePosition(QTextCursor::EndOfLine);
tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
tc.removeSelectedText();
updateCursorPosition();
mDisasTextEdit->setLockScroll(false);
mDisasTextEdit->horizontalScrollBar()->setValue(horizontalScrollValue);
}
void DisassemblyWidget::scrollInstructions(int count)
{
2018-03-21 20:32:32 +00:00
if (count == 0) {
return;
2017-10-22 13:55:42 +00:00
}
RVA offset;
2018-03-21 20:32:32 +00:00
if (count > 0) {
offset = Core()->nextOpAddr(topOffset, count);
2018-03-21 20:32:32 +00:00
} else {
offset = Core()->prevOpAddr(topOffset, -count);
}
refreshDisasm(offset);
}
bool DisassemblyWidget::updateMaxLines()
{
int currentMaxLines = qhelpers::getMaxFullyDisplayedLines(mDisasTextEdit);
2018-03-21 20:32:32 +00:00
if (currentMaxLines != maxLines) {
maxLines = currentMaxLines;
refreshDisasm();
return true;
}
return false;
}
2017-10-22 13:55:42 +00:00
void DisassemblyWidget::highlightCurrentLine()
{
2017-10-22 13:55:42 +00:00
QList<QTextEdit::ExtraSelection> extraSelections;
2017-12-02 15:03:55 +00:00
QColor highlightColor = ConfigColor("highlight");
QColor highlightWordColor = ConfigColor("highlight");
highlightWordColor.setAlpha(128);
QColor highlightWordCurrentLineColor = ConfigColor("gui.background");
highlightWordCurrentLineColor.setAlpha(128);
2017-11-20 11:23:37 +00:00
// Highlight the current line
2017-12-02 15:03:55 +00:00
QTextEdit::ExtraSelection selection;
selection.format.setBackground(highlightColor);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = mDisasTextEdit->textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
2017-10-22 13:55:42 +00:00
// Highlight the current word
QTextCursor cursor = mDisasTextEdit->textCursor();
cursor.select(QTextCursor::WordUnderCursor);
2017-12-02 15:03:55 +00:00
QString searchString = cursor.selectedText();
2017-12-02 15:03:55 +00:00
cursor.movePosition(QTextCursor::StartOfLine);
int listStartPos = cursor.position();
cursor.movePosition(QTextCursor::EndOfLine);
int lineEndPos = cursor.position();
2017-10-22 13:55:42 +00:00
2017-12-02 15:03:55 +00:00
// Highlight all the words in the document same as the current one
2017-10-22 13:55:42 +00:00
QTextDocument *document = mDisasTextEdit->document();
QTextEdit::ExtraSelection highlightSelection;
highlightSelection.cursor = cursor;
highlightSelection.cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
2017-12-02 15:03:55 +00:00
2018-03-21 20:32:32 +00:00
while (!highlightSelection.cursor.isNull() && !highlightSelection.cursor.atEnd()) {
highlightSelection.cursor = document->find(searchString, highlightSelection.cursor,
QTextDocument::FindWholeWords);
2018-03-21 20:32:32 +00:00
if (!highlightSelection.cursor.isNull()) {
if (highlightSelection.cursor.position() >= listStartPos
&& highlightSelection.cursor.position() <= lineEndPos) {
2017-12-02 15:03:55 +00:00
highlightSelection.format.setBackground(highlightWordCurrentLineColor);
2018-03-21 20:32:32 +00:00
} else {
2017-12-02 15:03:55 +00:00
highlightSelection.format.setBackground(highlightWordColor);
}
2017-10-22 13:55:42 +00:00
highlightSelection.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
extraSelections.append(highlightSelection);
}
}
2017-10-13 13:53:23 +00:00
2017-10-22 13:55:42 +00:00
mDisasTextEdit->setExtraSelections(extraSelections);
}
void DisassemblyWidget::showDisasContextMenu(const QPoint &pt)
{
mCtxMenu->exec(mDisasTextEdit->mapToGlobal(pt));
2017-10-22 13:55:42 +00:00
}
RVA DisassemblyWidget::readCurrentDisassemblyOffset()
2017-11-28 11:56:38 +00:00
{
QTextCursor tc = mDisasTextEdit->textCursor();
return readDisassemblyOffset(tc);
}
RVA DisassemblyWidget::readDisassemblyOffset(QTextCursor tc)
2017-10-22 13:55:42 +00:00
{
auto userData = getUserData(tc.block());
2018-03-21 20:32:32 +00:00
if (!userData) {
return RVA_INVALID;
2017-10-22 13:55:42 +00:00
}
return userData->line.offset;
2017-10-22 13:55:42 +00:00
}
void DisassemblyWidget::updateCursorPosition()
2017-10-22 13:55:42 +00:00
{
RVA offset = Core()->getOffset();
// already fine where it is?
RVA currentLineOffset = readCurrentDisassemblyOffset();
2018-03-21 20:32:32 +00:00
if (currentLineOffset == offset) {
return;
}
connectCursorPositionChanged(true);
2018-03-21 20:32:32 +00:00
if (offset < topOffset || (offset > bottomOffset && bottomOffset != RVA_INVALID)) {
mDisasTextEdit->moveCursor(QTextCursor::Start);
mDisasTextEdit->setExtraSelections({});
2018-03-21 20:32:32 +00:00
} else {
RVA currentCursorOffset = readCurrentDisassemblyOffset();
QTextCursor originalCursor = mDisasTextEdit->textCursor();
QTextCursor cursor = originalCursor;
cursor.movePosition(QTextCursor::Start);
2018-03-21 20:32:32 +00:00
while (true) {
RVA lineOffset = readDisassemblyOffset(cursor);
2018-03-21 20:32:32 +00:00
if (lineOffset == offset) {
if (cursorLineOffset > 0) {
cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, cursorLineOffset);
}
mDisasTextEdit->setTextCursor(cursor);
highlightCurrentLine();
break;
2018-03-21 20:32:32 +00:00
} else if (lineOffset != RVA_INVALID && lineOffset > offset) {
mDisasTextEdit->moveCursor(QTextCursor::Start);
mDisasTextEdit->setExtraSelections({});
break;
}
cursor.movePosition(QTextCursor::EndOfLine);
2018-03-21 20:32:32 +00:00
if (cursor.atEnd()) {
break;
}
cursor.movePosition(QTextCursor::Down);
}
// this is true if a seek came from the user clicking on a line.
// then the cursor should be restored 1:1 to retain selection and cursor position.
2018-03-21 20:32:32 +00:00
if (currentCursorOffset == offset) {
mDisasTextEdit->setTextCursor(originalCursor);
}
}
connectCursorPositionChanged(false);
}
void DisassemblyWidget::connectCursorPositionChanged(bool disconnect)
{
2018-03-21 20:32:32 +00:00
if (disconnect) {
QObject::disconnect(mDisasTextEdit, SIGNAL(cursorPositionChanged()), this,
SLOT(cursorPositionChanged()));
} else {
connect(mDisasTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged()));
}
}
void DisassemblyWidget::cursorPositionChanged()
{
RVA offset = readCurrentDisassemblyOffset();
cursorLineOffset = 0;
QTextCursor c = mDisasTextEdit->textCursor();
2018-03-21 20:32:32 +00:00
while (c.blockNumber() > 0) {
c.movePosition(QTextCursor::PreviousBlock);
2018-03-21 20:32:32 +00:00
if (readDisassemblyOffset(c) != offset) {
break;
}
cursorLineOffset++;
}
seekFromCursor = true;
Core()->seek(offset);
seekFromCursor = false;
highlightCurrentLine();
2017-12-02 15:43:21 +00:00
mCtxMenu->setCanCopy(mDisasTextEdit->textCursor().hasSelection());
}
void DisassemblyWidget::moveCursorRelative(bool up, bool page)
{
2018-03-21 20:32:32 +00:00
if (page) {
RVA offset;
2018-03-21 20:32:32 +00:00
if (!up) {
offset = Core()->nextOpAddr(bottomOffset, 1);
2018-03-21 20:32:32 +00:00
} else {
offset = Core()->prevOpAddr(topOffset, maxLines);
// disassembly from calculated offset may have more than maxLines lines
// move some instructions down if necessary.
auto lines = Core()->disassembleLines(offset, maxLines).toVector();
int oldTopLine;
2018-03-21 20:32:32 +00:00
for (oldTopLine = lines.length(); oldTopLine > 0; oldTopLine--) {
if (lines[oldTopLine - 1].offset < topOffset) {
break;
}
}
int overflowLines = oldTopLine - maxLines;
2018-03-21 20:32:32 +00:00
if (overflowLines > 0) {
while (lines[overflowLines - 1].offset == lines[overflowLines].offset
&& overflowLines < lines.length() - 1) {
overflowLines++;
}
offset = lines[overflowLines].offset;
}
}
refreshDisasm(offset);
2018-03-21 20:32:32 +00:00
} else { // normal arrow keys
int blockCount = mDisasTextEdit->blockCount();
2018-03-21 20:32:32 +00:00
if (blockCount < 1) {
return;
}
int blockNumber = mDisasTextEdit->textCursor().blockNumber();
2018-03-21 20:32:32 +00:00
if (blockNumber == blockCount - 1 && !up) {
scrollInstructions(1);
2018-03-21 20:32:32 +00:00
} else if (blockNumber == 0 && up) {
scrollInstructions(-1);
}
mDisasTextEdit->moveCursor(up ? QTextCursor::Up : QTextCursor::Down);
// handle cases where top instruction offsets change
RVA offset = readCurrentDisassemblyOffset();
2018-03-21 20:32:32 +00:00
if (offset != Core()->getOffset()) {
Core()->seek(offset);
highlightCurrentLine();
}
}
}
bool DisassemblyWidget::eventFilter(QObject *obj, QEvent *event)
{
2018-03-21 20:32:32 +00:00
if ((obj == mDisasTextEdit || obj == mDisasTextEdit->viewport())
&& event->type() == QEvent::MouseButtonDblClick) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
2017-11-28 11:56:38 +00:00
QTextCursor cursor = mDisasTextEdit->cursorForPosition(QPoint(mouseEvent->x(), mouseEvent->y()));
2017-11-28 11:56:38 +00:00
RVA offset = readDisassemblyOffset(cursor);
RVA jump = Core()->getOffsetJump(offset);
2018-03-21 20:32:32 +00:00
if (jump == RVA_INVALID) {
2017-11-28 11:56:38 +00:00
bool ok;
2018-03-21 20:32:32 +00:00
RVA xref = Core()->cmdj("axfj@" + QString::number(
offset)).array().first().toObject().value("to").toVariant().toULongLong(&ok);
if (ok) {
2017-11-28 11:56:38 +00:00
jump = xref;
}
}
2017-11-28 11:56:38 +00:00
2018-03-21 20:32:32 +00:00
if (jump != RVA_INVALID) {
2018-04-12 06:33:30 +00:00
Core()->seek(jump);
2017-11-28 11:56:38 +00:00
}
return true;
}
return QDockWidget::eventFilter(obj, event);
}
void DisassemblyWidget::on_seekChanged(RVA offset)
{
2018-03-21 20:32:32 +00:00
if (!seekFromCursor) {
cursorLineOffset = 0;
}
if (topOffset != RVA_INVALID && bottomOffset != RVA_INVALID
2018-03-21 20:32:32 +00:00
&& offset >= topOffset && offset <= bottomOffset) {
// if the line with the seek offset is currently visible, just move the cursor there
updateCursorPosition();
2018-03-21 20:32:32 +00:00
} else {
// otherwise scroll there
refreshDisasm(offset);
}
mCtxMenu->setOffset(offset);
}
void DisassemblyWidget::raisePrioritizedMemoryWidget(CutterCore::MemoryWidgetType type)
{
2018-03-21 20:32:32 +00:00
if (type == CutterCore::MemoryWidgetType::Disassembly) {
raise();
setFocus();
}
}
void DisassemblyWidget::fontsUpdatedSlot()
{
2017-11-30 14:00:22 +00:00
setupFonts();
2018-03-21 20:32:32 +00:00
if (!updateMaxLines()) { // updateMaxLines() returns true if it already refreshed.
refreshDisasm();
}
}
2017-10-16 19:00:47 +00:00
2017-11-20 11:23:37 +00:00
void DisassemblyWidget::colorsUpdatedSlot()
{
2017-11-30 14:00:22 +00:00
setupColors();
2017-11-20 11:23:37 +00:00
refreshDisasm();
}
2017-11-30 14:00:22 +00:00
void DisassemblyWidget::setupFonts()
{
mDisasTextEdit->setFont(Config()->getFont());
}
2017-11-30 14:00:22 +00:00
void DisassemblyWidget::setupColors()
{
mDisasTextEdit->setStyleSheet(QString("QPlainTextEdit { background-color: %1; color: %2; }")
2018-03-21 20:32:32 +00:00
.arg(ConfigColor("gui.background").name())
.arg(ConfigColor("btext").name()));
2017-11-30 14:00:22 +00:00
}
DisassemblyScrollArea::DisassemblyScrollArea(QWidget *parent) : QAbstractScrollArea(parent)
{
}
bool DisassemblyScrollArea::viewportEvent(QEvent *event)
{
int dy = verticalScrollBar()->value() - 5;
2018-03-21 20:32:32 +00:00
if (dy != 0) {
emit scrollLines(dy);
}
2018-03-21 20:32:32 +00:00
if (event->type() == QEvent::Resize) {
emit disassemblyResized();
}
resetScrollBars();
return QAbstractScrollArea::viewportEvent(event);
}
void DisassemblyScrollArea::resetScrollBars()
{
verticalScrollBar()->blockSignals(true);
verticalScrollBar()->setRange(0, 10);
verticalScrollBar()->setValue(5);
verticalScrollBar()->blockSignals(false);
}
bool DisassemblyTextEdit::viewportEvent(QEvent *event)
{
2018-03-21 20:32:32 +00:00
switch (event->type()) {
case QEvent::Type::Wheel:
return false;
default:
return QAbstractScrollArea::viewportEvent(event);
}
}
void DisassemblyTextEdit::scrollContentsBy(int dx, int dy)
{
2018-03-21 20:32:32 +00:00
if (!lockScroll) {
QPlainTextEdit::scrollContentsBy(dx, dy);
}
}
2017-11-19 17:49:29 +00:00
void DisassemblyTextEdit::keyPressEvent(QKeyEvent */*event*/)
{
//QPlainTextEdit::keyPressEvent(event);
}
void DisassemblyTextEdit::mousePressEvent(QMouseEvent *event)
{
QPlainTextEdit::mousePressEvent(event);
2018-03-21 20:32:32 +00:00
if (event->button() == Qt::RightButton && !textCursor().hasSelection()) {
setTextCursor(cursorForPosition(event->pos()));
}
}
void DisassemblyWidget::seekPrev()
{
Core()->seekPrev();
}