From d55ae67dc0dacc0ad0b99531cb2d88d9fb25b991 Mon Sep 17 00:00:00 2001 From: karliss Date: Thu, 16 May 2019 19:03:48 +0300 Subject: [PATCH] New Hexedit (#1516) --- src/Cutter.pro | 9 +- src/core/Cutter.cpp | 18 + src/core/Cutter.h | 2 + src/core/MainWindow.cpp | 1 + src/widgets/HexTextView.cpp | 999 --------------------------- src/widgets/HexTextView.h | 158 ----- src/widgets/HexTextView.ui | 418 ------------ src/widgets/HexWidget.cpp | 1190 +++++++++++++++++++++++++++++++++ src/widgets/HexWidget.h | 463 +++++++++++++ src/widgets/HexdumpWidget.cpp | 28 +- src/widgets/HexdumpWidget.h | 15 +- src/widgets/HexdumpWidget.ui | 6 +- 12 files changed, 1709 insertions(+), 1598 deletions(-) delete mode 100644 src/widgets/HexTextView.cpp delete mode 100644 src/widgets/HexTextView.h delete mode 100644 src/widgets/HexTextView.ui create mode 100644 src/widgets/HexWidget.cpp create mode 100644 src/widgets/HexWidget.h diff --git a/src/Cutter.pro b/src/Cutter.pro index 80486acc..904107fc 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -272,7 +272,6 @@ SOURCES += \ menus/DisassemblyContextMenu.cpp \ widgets/DisassemblyWidget.cpp \ widgets/HexdumpWidget.cpp \ - widgets/HexTextView.cpp \ common/Configuration.cpp \ common/Colors.cpp \ dialogs/SaveProjectDialog.cpp \ @@ -349,7 +348,8 @@ SOURCES += \ common/CrashHandler.cpp \ common/BugReporting.cpp \ common/HighDpiPixmap.cpp \ - widgets/GraphGridLayout.cpp + widgets/GraphGridLayout.cpp \ + widgets/HexWidget.cpp HEADERS += \ core/Cutter.h \ @@ -392,7 +392,6 @@ HEADERS += \ menus/DisassemblyContextMenu.h \ widgets/DisassemblyWidget.h \ widgets/HexdumpWidget.h \ - widgets/HexTextView.h \ common/Configuration.h \ common/Colors.h \ dialogs/SaveProjectDialog.h \ @@ -475,7 +474,8 @@ HEADERS += \ common/BugReporting.h \ common/HighDpiPixmap.h \ widgets/GraphLayout.h \ - widgets/GraphGridLayout.h + widgets/GraphGridLayout.h \ + widgets/HexWidget.h FORMS += \ dialogs/AboutDialog.ui \ @@ -501,7 +501,6 @@ FORMS += \ widgets/StringsWidget.ui \ widgets/SymbolsWidget.ui \ widgets/HexdumpWidget.ui \ - widgets/HexTextView.ui \ dialogs/SaveProjectDialog.ui \ dialogs/preferences/PreferencesDialog.ui \ dialogs/preferences/AppearanceOptionsWidget.ui \ diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 40deffe6..1db71c52 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -2785,4 +2785,22 @@ QString CutterCore::getHexdumpPreview(RVA address, int size) return ansiEscapeToHtml(hexdump(address, size, HexdumpFormats::Normal)).replace(QLatin1Char('\n'), "
"); } +QByteArray CutterCore::ioRead(RVA addr, int len) +{ + CORE_LOCK(); + + QByteArray array; + + if (len <= 0) + return array; + + /* Zero-copy */ + array.resize(len); + if (!r_io_read_at(core_->io, addr, (uint8_t *)array.data(), len)) { + qWarning() << "Can't read data" << addr << len; + array.fill(0xff); + } + + return array; +} diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 3c2d023c..58c6b73f 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -263,6 +263,8 @@ public: void loadPDB(const QString &file); + QByteArray ioRead(RVA addr, int len); + QList getSeekHistory(); /* Plugins */ diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 880fbef1..9283557e 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -65,6 +65,7 @@ #include "widgets/BacktraceWidget.h" #include "widgets/HexdumpWidget.h" #include "widgets/PseudocodeWidget.h" +#include "widgets/HexWidget.h" // Qt Headers #include diff --git a/src/widgets/HexTextView.cpp b/src/widgets/HexTextView.cpp deleted file mode 100644 index f5180d03..00000000 --- a/src/widgets/HexTextView.cpp +++ /dev/null @@ -1,999 +0,0 @@ -#include "HexTextView.h" -#include "ui_HexTextView.h" - -#include "common/Helpers.h" -#include "common/Configuration.h" -#include "common/TempConfig.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -HexTextView::HexTextView(QWidget *parent) : - QScrollArea(parent), - ui(new Ui::HexTextView) -{ - ui->setupUi(this); - - // Setup hex highlight - //connect(ui->hexHexText, SIGNAL(cursorPositionChanged()), this, SLOT(highlightHexCurrentLine())); - //highlightHexCurrentLine(); - - int margin = static_cast(ui->hexOffsetText->document()->documentMargin()); - ui->offsetHeaderLabel->setContentsMargins(margin, 0, margin, 0); - - margin = static_cast(ui->hexHexText->document()->documentMargin()); - ui->hexHeaderLabel->setContentsMargins(margin, 0, margin, 0); - - margin = static_cast(ui->hexASCIIText->document()->documentMargin()); - ui->asciiHeaderLabel->setContentsMargins(margin, 0, margin, 0); - - setupFonts(); - - updateHeaders(); - - // Set hexdump context menu - ui->hexHexText->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->hexHexText, SIGNAL(customContextMenuRequested(const QPoint &)), - this, SLOT(showHexdumpContextMenu(const QPoint &))); - ui->hexASCIIText->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->hexASCIIText, SIGNAL(customContextMenuRequested(const QPoint &)), - this, SLOT(showHexASCIIContextMenu(const QPoint &))); - - setupScrollSync(); - - // Control Disasm and Hex scroll to add more contents - connectScroll(false); - - connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdated())); - - connect(ui->hexHexText, &QTextEdit::selectionChanged, this, &HexTextView::onSelectionChanged); - connect(ui->hexASCIIText, &QTextEdit::selectionChanged, this, &HexTextView::onSelectionChanged); - connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexTextView::onSelectionChanged); - connect(ui->hexASCIIText, &QTextEdit::cursorPositionChanged, this, - &HexTextView::onSelectionChanged); - connect(&rangeDialog, &QDialog::accepted, this, &HexTextView::on_rangeDialogAccepted); - - addAction(ui->actionResetZoom); - connect(ui->actionResetZoom, &QAction::triggered, this, &HexTextView::zoomReset); - defaultFontSize = ui->hexHexText->font().pointSizeF(); - - addAction(ui->actionCopyAddressAtCursor); - ui->actionCopyAddressAtCursor->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); - - format = Format::Hex; -} - -void HexTextView::setupScrollSync() -{ - /* - * For some reason, QScrollBar::valueChanged is not emitted when - * the scrolling happened from moving the cursor beyond the visible content, - * so QTextEdit::cursorPositionChanged has to be connected as well. - */ - - auto offsetHexFunc = [this]() { - if (!scroll_disabled) { - scroll_disabled = true; - ui->hexHexText->verticalScrollBar()->setValue(ui->hexOffsetText->verticalScrollBar()->value()); - scroll_disabled = false; - } - }; - - auto offsetASCIIFunc = [this]() { - if (!scroll_disabled) { - scroll_disabled = true; - ui->hexASCIIText->verticalScrollBar()->setValue(ui->hexOffsetText->verticalScrollBar()->value()); - scroll_disabled = false; - } - }; - - connect(ui->hexOffsetText->verticalScrollBar(), &QScrollBar::valueChanged, - ui->hexHexText->verticalScrollBar(), offsetHexFunc); - connect(ui->hexOffsetText, &QTextEdit::cursorPositionChanged, ui->hexHexText->verticalScrollBar(), - offsetHexFunc); - connect(ui->hexOffsetText->verticalScrollBar(), &QScrollBar::valueChanged, - ui->hexASCIIText->verticalScrollBar(), offsetASCIIFunc); - connect(ui->hexOffsetText, &QTextEdit::cursorPositionChanged, ui->hexASCIIText->verticalScrollBar(), - offsetASCIIFunc); - - auto hexOffsetFunc = [this]() { - if (!scroll_disabled) { - scroll_disabled = true; - ui->hexOffsetText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value()); - scroll_disabled = false; - } - }; - - auto hexASCIIFunc = [this]() { - if (!scroll_disabled) { - scroll_disabled = true; - ui->hexASCIIText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value()); - scroll_disabled = false; - } - }; - - connect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged, - ui->hexOffsetText->verticalScrollBar(), hexOffsetFunc); - connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, ui->hexOffsetText->verticalScrollBar(), - hexOffsetFunc); - connect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged, - ui->hexASCIIText->verticalScrollBar(), hexASCIIFunc); - connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, ui->hexASCIIText->verticalScrollBar(), - hexASCIIFunc); - - auto asciiOffsetFunc = [this]() { - if (!scroll_disabled) { - scroll_disabled = true; - ui->hexOffsetText->verticalScrollBar()->setValue(ui->hexASCIIText->verticalScrollBar()->value()); - scroll_disabled = false; - } - }; - - auto asciiHexFunc = [this]() { - if (!scroll_disabled) { - scroll_disabled = true; - ui->hexHexText->verticalScrollBar()->setValue(ui->hexASCIIText->verticalScrollBar()->value()); - scroll_disabled = false; - } - }; - - connect(ui->hexASCIIText->verticalScrollBar(), &QScrollBar::valueChanged, - ui->hexOffsetText->verticalScrollBar(), asciiOffsetFunc); - connect(ui->hexASCIIText, &QTextEdit::cursorPositionChanged, ui->hexOffsetText->verticalScrollBar(), - asciiOffsetFunc); - connect(ui->hexASCIIText->verticalScrollBar(), &QScrollBar::valueChanged, - ui->hexHexText->verticalScrollBar(), asciiHexFunc); - connect(ui->hexASCIIText, &QTextEdit::cursorPositionChanged, ui->hexHexText->verticalScrollBar(), - asciiHexFunc); -} - -void HexTextView::connectScroll(bool disconnect_) -{ - scroll_disabled = disconnect_; - if (disconnect_) { - disconnect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged, this, - &HexTextView::scrollChanged); - disconnect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexTextView::scrollChanged); - } else { - connect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged, this, - &HexTextView::scrollChanged); - connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexTextView::scrollChanged); - - } -} - -HexTextView::~HexTextView() {} - -/* - * Text highlight functions - * Currently unused - */ -/* -void HexTextView::highlightHexCurrentLine() -{ - QList extraSelections; - - if (!ui->hexHexText->isReadOnly()) - { - QTextEdit::ExtraSelection selection; - - QColor lineColor = QColor(190, 144, 212); - - selection.format.setBackground(lineColor); - selection.format.setProperty(QTextFormat::FullWidthSelection, true); - selection.cursor = ui->hexHexText->textCursor(); - selection.cursor.clearSelection(); - extraSelections.append(selection); - } - - QTextCursor cursor = ui->hexHexText->textCursor(); - cursor.select(QTextCursor::WordUnderCursor); - - QTextEdit::ExtraSelection currentWord; - - QColor blueColor = QColor(Qt::blue).lighter(160); - currentWord.format.setBackground(blueColor); - - currentWord.cursor = cursor; - extraSelections.append(currentWord); - - ui->hexHexText->setExtraSelections(extraSelections); - - highlightHexWords(cursor.selectedText()); -} - - -void HexTextView::highlightHexWords(const QString &str) -{ - QString searchString = str; - QTextDocument *document = ui->hexHexText->document(); - - document->undo(); - - QTextCursor highlightCursor(document); - QTextCursor cursor(document); - - cursor.beginEditBlock(); - - QColor blueColor = QColor(Qt::blue).lighter(160); - - QTextCharFormat plainFormat(highlightCursor.charFormat()); - QTextCharFormat colorFormat = plainFormat; - colorFormat.setBackground(blueColor); - - while (!highlightCursor.isNull() && !highlightCursor.atEnd()) - { - highlightCursor = document->find(searchString, highlightCursor, QTextDocument::FindWholeWords); - - if (!highlightCursor.isNull()) - { - highlightCursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); - highlightCursor.mergeCharFormat(colorFormat); - } - } - cursor.endEditBlock(); -} -*/ - -void HexTextView::refresh(RVA addr) -{ - if (addr == RVA_INVALID) { - addr = currentPos; - } else { - currentPos = addr; - } - - ut64 loadLines = 0; - ut64 curAddrLineOffset = 0; - connectScroll(true); - - updateHeaders(); - - - - cols = Core()->getConfigi("hex.cols"); - // Avoid divison by 0 - if (cols == 0) - cols = 16; - - // TODO: Figure out how to calculate a sane value for this - bufferLines = qhelpers::getMaxFullyDisplayedLines(ui->hexHexText) * 10; - - if (requestedSelectionEndAddress != 0 && requestedSelectionStartAddress != 0 - && requestedSelectionEndAddress > requestedSelectionStartAddress) { - loadLines = ((requestedSelectionEndAddress - requestedSelectionStartAddress) / cols) + - (bufferLines * 2); - curAddrLineOffset = bufferLines; - } else { - loadLines = bufferLines * 3; // total lines to load - curAddrLineOffset = bufferLines; // line number where seek should be - } - - if (addr < curAddrLineOffset * cols) { - curAddrLineOffset = static_cast(addr / cols); - } - - if (addr > RVA_MAX - curAddrLineOffset * cols) { - curAddrLineOffset = static_cast(loadLines - (RVA_MAX - addr) / cols); - } - - first_loaded_address = addr - curAddrLineOffset * cols; - last_loaded_address = addr + (loadLines - curAddrLineOffset) * cols; - - auto hexdump = fetchHexdump(first_loaded_address, loadLines); - - ui->hexOffsetText->setText(hexdump[0]); - ui->hexHexText->setText(hexdump[1]); - ui->hexASCIIText->setPlainText(hexdump[2]); - - QTextCursor cursor(ui->hexHexText->document()->findBlockByLineNumber(curAddrLineOffset)); - ui->hexHexText->moveCursor(QTextCursor::End); - ui->hexHexText->ensureCursorVisible(); - ui->hexHexText->setTextCursor(cursor); - ui->hexHexText->ensureCursorVisible(); - - // Set the backgorund color of the current seek - QTextCursor offsetCursor(ui->hexOffsetText->document()->findBlockByLineNumber(curAddrLineOffset)); - QTextBlockFormat formatTmp = offsetCursor.blockFormat(); - formatTmp.setBackground(QColor(64, 129, 160)); - offsetCursor.setBlockFormat(formatTmp); - - updateWidths(); - - // Update other text areas scroll - ui->hexOffsetText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value()); - ui->hexASCIIText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value()); - - connectScroll(false); -} - -void HexTextView::updateHeaders() -{ - int cols = Core()->getConfigi("hex.cols"); - int ascii_cols = cols; - bool pairs = Core()->getConfigb("hex.pairs"); - - QString hexHeaderString; - QString asciiHeaderString; - - QTextStream hexHeader(&hexHeaderString); - QTextStream asciiHeader(&asciiHeaderString); - - hexHeader.setIntegerBase(16); - hexHeader.setNumberFlags(QTextStream::UppercaseDigits); - asciiHeader.setIntegerBase(16); - asciiHeader.setNumberFlags(QTextStream::UppercaseDigits); - - // Custom spacing for the header - QString space = " "; - switch (format) { - case Hex: - space = space.repeated(1); - break; - case Octal: - space = space.repeated(2); - break; - default: - qWarning() << "Unknown format in hexdump!"; - break; - } - - for (int i = 0; i < cols; i++) { - if (i > 0 && ((pairs && !(i & 1)) || !pairs)) { - hexHeader << " "; - } - - hexHeader << space << (i & 0xF); - } - - for (int i = 0; i < ascii_cols; i++) { - asciiHeader << (i & 0xF); - } - - hexHeader.flush(); - asciiHeader.flush(); - - ui->hexHeaderLabel->setText(hexHeaderString); - ui->asciiHeaderLabel->setText(asciiHeaderString); -} - - -std::array HexTextView::fetchHexdump(RVA addr, int lines) -{ - // Main bytes to fetch: - int bytes = cols * lines; - - QString command = QString("pxj %1 @%2").arg( - QString::number(bytes), - RAddressString(addr)); - QJsonArray byte_array = Core()->cmdj(command).array(); - - QString hexText = ""; - QString offsetText = ""; - QString asciiText = ""; - RVA cur_addr = addr; - for (int i = 0; i < lines; i++) { - for (int j = 0; j < cols; j++) { - int b = byte_array[(i * cols) + j].toInt(); - if ((j > 0) && (j < cols)) { - hexText += " "; - } - // Non printable - if ((b < 0x20) || (b > 0x7E)) { - asciiText += "."; - } else { - asciiText += (char)b; - } - - switch (format) { - case Octal: - hexText += QString::number(b, 8).rightJustified(3, '0'); - break; - case Hex: - default: - hexText += QString::number(b, 16).rightJustified(2, '0'); - break; - } - } - offsetText += RAddressString(cur_addr) + "\n"; - hexText += "\n"; - asciiText += "\n"; - cur_addr += cols; - } - - return {{offsetText, hexText, asciiText}}; -} - -void HexTextView::onSelectionChanged() -{ - if (scroll_disabled) { - return; - } - connectScroll(true); - - if (sender() == ui->hexHexText) { - QTextCursor textCursor = ui->hexHexText->textCursor(); - if (!textCursor.hasSelection()) { - RVA adr = hexPositionToAddress(textCursor.position()); - int pos = asciiAddressToPosition(adr); - setTextEditPosition(ui->hexASCIIText, pos); - - selection = {true, 0, 0}; - emit selectionChanged(selection); - currentPos = adr; - emit positionChanged(adr); - connectScroll(false); - return; - } - - int selectionStart = textCursor.selectionStart(); - int selectionEnd = textCursor.selectionEnd(); - - QChar start = ui->hexHexText->document()->characterAt(selectionStart); - QChar end = ui->hexHexText->document()->characterAt(selectionEnd); - - // This adjusts the selection to make sense with the chosen format - switch (format) { - case Hex: - // Handle the spaces/newlines (if it's at the start, move forward, - // if it's at the end, move back) - - if (!start.isLetterOrNumber()) { - selectionStart += 1; - } else if (ui->hexHexText->document()->characterAt(selectionStart - 1).isLetterOrNumber()) { - selectionStart += 2; - } - - if (!end.isLetterOrNumber()) { - selectionEnd += 1; - } - break; - case Octal: - if (!start.isLetterOrNumber()) { - selectionStart += 1; - } - if (!end.isLetterOrNumber()) { - selectionEnd += 1; - } - break; - } - - // In hextext we have the spaces that we need to somehow handle. - RVA startAddress = hexPositionToAddress(selectionStart); - RVA endAddress = hexPositionToAddress(selectionEnd); - - int startPosition = asciiAddressToPosition(startAddress); - int endPosition = asciiAddressToPosition(endAddress); - QTextCursor targetTextCursor = ui->hexASCIIText->textCursor(); - targetTextCursor.setPosition(startPosition); - targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor); - ui->hexASCIIText->setTextCursor(targetTextCursor); - - selection = {false, startAddress, endAddress > startAddress ? endAddress - 1 : endAddress}; - emit selectionChanged(selection); - currentPos = startAddress; - emit positionChanged(startAddress); - } else { - QTextCursor textCursor = ui->hexASCIIText->textCursor(); - if (!textCursor.hasSelection()) { - RVA adr = asciiPositionToAddress(textCursor.position()); - int pos = hexAddressToPosition(adr); - setTextEditPosition(ui->hexHexText, pos); - connectScroll(false); - selection = {false, 0, 0}; - emit selectionChanged(selection); - currentPos = adr; - emit positionChanged(adr); - return; - } - RVA startAddress = asciiPositionToAddress(textCursor.selectionStart()); - RVA endAddress = asciiPositionToAddress(textCursor.selectionEnd()); - - int startPosition = hexAddressToPosition(startAddress); - int endPosition = hexAddressToPosition(endAddress); - - // End position -1 because the position we get above is for the next - // entry, so including the space/newline - endPosition -= 1; - QTextCursor targetTextCursor = ui->hexHexText->textCursor(); - targetTextCursor.setPosition(startPosition); - targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor); - ui->hexHexText->setTextCursor(targetTextCursor); - - selection = {false, startAddress, endAddress > startAddress ? endAddress - 1 : endAddress}; - emit selectionChanged(selection); - currentPos = startAddress; - emit positionChanged(startAddress); - } - - connectScroll(false); - return; -} - - - -void HexTextView::showHexdumpContextMenu(const QPoint &pt) -{ - // Set Hexdump popup menu - QMenu *menu = ui->hexHexText->createStandardContextMenu(); - menu->clear(); - - /*menu->addAction(ui->actionHexCopy_Hexpair); - menu->addAction(ui->actionHexCopy_ASCII); - menu->addAction(ui->actionHexCopy_Text); - menu->addSeparator();*/ - QMenu *colSubmenu = menu->addMenu(tr("Columns")); - colSubmenu->addAction(ui->action4columns); - colSubmenu->addAction(ui->action8columns); - colSubmenu->addAction(ui->action16columns); - colSubmenu->addAction(ui->action32columns); - - QMenu *formatSubmenu = menu->addMenu(tr("Format")); - formatSubmenu->addAction(ui->actionFormatHex); - formatSubmenu->addAction(ui->actionFormatOctal); - - menu->addAction(ui->actionSelect_Block); - - menu->addSeparator(); - menu->addActions(this->actions()); - - // TODO: - // formatSubmenu->addAction(ui->actionFormatHalfWord); - // formatSubmenu->addAction(ui->actionFormatWord); - // formatSubmenu->addAction(ui->actionFormatQuadWord); - // formatSubmenu->addAction(ui->actionFormatEmoji); - - // TODO: - // QMenu *signedIntFormatSubmenu = formatSubmenu->addMenu(tr("Signed integer")); - // signedIntFormatSubmenu->addAction(ui->actionFormatSignedInt1); - // signedIntFormatSubmenu->addAction(ui->actionFormatSignedInt2); - // signedIntFormatSubmenu->addAction(ui->actionFormatSignedInt4); - - /*menu->addSeparator(); - menu->addAction(ui->actionHexEdit); - menu->addAction(ui->actionHexPaste); - menu->addSeparator(); - menu->addAction(ui->actionHexInsert_Hex); - menu->addAction(ui->actionHexInsert_String);*/ - - ui->hexHexText->setContextMenuPolicy(Qt::CustomContextMenu); - - menu->exec(ui->hexHexText->mapToGlobal(pt)); - delete menu; -} - -void HexTextView::showHexASCIIContextMenu(const QPoint &pt) -{ - // Set Hex ASCII popup menu - QMenu *menu = ui->hexASCIIText->createStandardContextMenu(); - menu->clear(); - /*menu->addAction(ui->actionHexCopy_Hexpair); - menu->addAction(ui->actionHexCopy_ASCII); - menu->addAction(ui->actionHexCopy_Text); - menu->addSeparator();*/ - QMenu *colSubmenu = menu->addMenu("Columns"); - colSubmenu->addAction(ui->action4columns); - colSubmenu->addAction(ui->action8columns); - colSubmenu->addAction(ui->action16columns); - colSubmenu->addAction(ui->action32columns); - /*menu->addSeparator(); - menu->addAction(ui->actionHexEdit); - menu->addAction(ui->actionHexPaste); - menu->addSeparator(); - menu->addAction(ui->actionHexInsert_Hex); - menu->addAction(ui->actionHexInsert_String);*/ - - ui->hexASCIIText->setContextMenuPolicy(Qt::CustomContextMenu); - - menu->exec(ui->hexASCIIText->mapToGlobal(pt)); - delete menu; -} - -void HexTextView::setupFonts() -{ - QFont font = Config()->getFont(); - - ui->hexOffsetText->setFont(font); - ui->hexHexText->setFont(font); - ui->hexASCIIText->setFont(font); - - ui->offsetHeaderLabel->setFont(font); - ui->hexHeaderLabel->setFont(font); - ui->asciiHeaderLabel->setFont(font); -} - -HexTextView::Selection HexTextView::getSelection() -{ - return selection; -} - -RVA HexTextView::position() -{ - return currentPos; -} - -void HexTextView::fontsUpdated() -{ - setupFonts(); -} - - -RVA HexTextView::hexPositionToAddress(int position) -{ - switch (format) { - - case Octal: - return first_loaded_address + (position / 4); - case Hex: - default: - return first_loaded_address + (position / 3); - } - return RVA_INVALID; - // In hex each byte takes up 2 characters + 1 spacer (including newline as spacer) - -} - -RVA HexTextView::asciiPositionToAddress(int position) -{ - // Each row adds one byte (because of the newline), so cols + 1 gets rid of that offset - return first_loaded_address + (position - (position / (cols + 1))); -} - -int HexTextView::hexAddressToPosition(RVA address) -{ - // This strictly assumes that the address is actually loaded. - switch (format) { - - case Octal: - return (address - first_loaded_address) * 4; - case Hex: - default: - return (address - first_loaded_address) * 3; - } -} - -int HexTextView::asciiAddressToPosition(RVA address) -{ - RVA local_address = address - first_loaded_address; - int position = local_address + (local_address / cols); - return position; -} - -void HexTextView::setTextEditPosition(QTextEdit *textEdit, int position) -{ - QTextCursor textCursor = textEdit->textCursor(); - textCursor.setPosition(position); - textEdit->setTextCursor(textCursor); -} - -int HexTextView::getDisplayedLined(QTextEdit *textEdit, bool bottom) -{ - //int start_pos = textEdit->cursorForPosition(QPoint(0, 0)).position(); - QPoint top_right(textEdit->viewport()->x(), textEdit->viewport()->y()); - QPoint bottom_right(textEdit->viewport()->width(), textEdit->viewport()->height() - 1); - QPoint point = top_right; - if (bottom) { - point = bottom_right; - } - - QTextCursor textCursor = textEdit->cursorForPosition(point); - //QTextBlock textBlock = textCursor.block(); - //QTextLayout *textLayout = textBlock.layout(); - //const int relativePos = textCursor.position() - textBlock.position(); - //int end_pos = textEdit->cursorForPosition(bottom_right).position(); - return textCursor.blockNumber(); -} - -void HexTextView::removeTopLinesWithoutScroll(QTextEdit *textEdit, int lines) -{ - int scroll_val_before = textEdit->verticalScrollBar()->value(); - int height_before = textEdit->document()->size().height(); - - QTextBlock block = textEdit->document()->firstBlock(); - for (int i = 0; i < lines; i++) { - QTextCursor cursor(block); - block = block.next(); - cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor); - cursor.removeSelectedText(); - } - - int height_after = textEdit->document()->size().height(); - textEdit->verticalScrollBar()->setValue(scroll_val_before + (height_after - height_before)); -} - -void HexTextView::removeBottomLinesWithoutScroll(QTextEdit *textEdit, int lines) -{ - QTextBlock block = textEdit->document()->lastBlock().previous(); - QTextCursor textCursor = textEdit->textCursor(); - for (int i = 0; i < lines; i++) { - QTextCursor cursor(block); - block = block.previous(); - cursor.select(QTextCursor::BlockUnderCursor); - cursor.removeSelectedText(); - } -} - -void HexTextView::prependWithoutScroll(QTextEdit *textEdit, QString text) -{ - // TODO: Keep selection (already works for append) - QTextCursor textCursor = textEdit->textCursor(); - int current_positon = textCursor.position(); - - //int scroll_max_before = textEdit->verticalScrollBar()->maximum(); - int scroll_val_before = textEdit->verticalScrollBar()->value(); - int height_before = textEdit->document()->size().height(); - textEdit->moveCursor(QTextCursor::Start); - textEdit->insertPlainText(text); - textCursor.setPosition(text.length() + current_positon); - textEdit->setTextCursor(textCursor); - int height_after = textEdit->document()->size().height(); - //int scroll_max_after = textEdit->verticalScrollBar()->maximum(); - //int scroll_val_after = textEdit->verticalScrollBar()->maximum(); - textEdit->verticalScrollBar()->setValue(scroll_val_before + (height_after - height_before)); -} - -void HexTextView::appendWithoutScroll(QTextEdit *textEdit, QString text) -{ - int scroll_val_before = textEdit->verticalScrollBar()->value(); - QTextCursor textCursor = textEdit->textCursor(); - textEdit->moveCursor(QTextCursor::End); - textEdit->insertPlainText(text); - textEdit->setTextCursor(textCursor); - textEdit->verticalScrollBar()->setValue(scroll_val_before); -} - -void HexTextView::scrollChanged() -{ - connectScroll(true); - - int firstLine = getDisplayedLined(ui->hexHexText); - if (firstLine < (bufferLines / 2)) { - int loadLines = bufferLines; - RVA shift = static_cast(loadLines * cols); - if (shift > first_loaded_address) { - loadLines = static_cast(first_loaded_address / cols); - shift = first_loaded_address; - } - first_loaded_address -= shift; - last_loaded_address -= shift; - - if (loadLines > 0) { - auto hexdump = fetchHexdump(first_loaded_address, loadLines); - prependWithoutScroll(ui->hexOffsetText, hexdump[0]); - prependWithoutScroll(ui->hexHexText, hexdump[1]); - prependWithoutScroll(ui->hexASCIIText, hexdump[2]); - - removeBottomLinesWithoutScroll(ui->hexOffsetText, loadLines); - removeBottomLinesWithoutScroll(ui->hexHexText, loadLines); - removeBottomLinesWithoutScroll(ui->hexASCIIText, loadLines); - - ui->hexOffsetText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value()); - ui->hexASCIIText->verticalScrollBar()->setValue(ui->hexHexText->verticalScrollBar()->value()); - } - } - - int blocks = ui->hexHexText->document()->blockCount(); - int lastLine = getDisplayedLined(ui->hexHexText, true); - if (blocks - lastLine < (bufferLines / 2)) { - int loadLines = bufferLines; - RVA shift = static_cast(loadLines * cols); - if (last_loaded_address > RVA_MAX - shift) { - shift = RVA_MAX - last_loaded_address; - loadLines = static_cast(shift / cols); - } - - if (loadLines > 0) { - auto hexdump = fetchHexdump(last_loaded_address, loadLines); - last_loaded_address += shift; - first_loaded_address += shift; - - removeTopLinesWithoutScroll(ui->hexOffsetText, loadLines); - removeTopLinesWithoutScroll(ui->hexHexText, loadLines); - removeTopLinesWithoutScroll(ui->hexASCIIText, loadLines); - appendWithoutScroll(ui->hexOffsetText, hexdump[0]); - appendWithoutScroll(ui->hexHexText, hexdump[1]); - appendWithoutScroll(ui->hexASCIIText, hexdump[2]); - } - } - connectScroll(false); -} - -/* - * Actions callback functions - */ - -void HexTextView::on_actionCopyAddressAtCursor_triggered() -{ - auto addr = hexPositionToAddress(ui->hexHexText->textCursor().position()); - - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(RAddressString(addr)); -} - - -void HexTextView::on_action8columns_triggered() -{ - Core()->setConfig("hex.cols", 8); - refresh(); -} - -void HexTextView::on_action16columns_triggered() -{ - Core()->setConfig("hex.cols", 16); - refresh(); -} - -void HexTextView::on_action4columns_triggered() -{ - Core()->setConfig("hex.cols", 4); - refresh(); -} - -void HexTextView::on_action32columns_triggered() -{ - Core()->setConfig("hex.cols", 32); - refresh(); -} - -void HexTextView::on_action64columns_triggered() -{ - Core()->setConfig("hex.cols", 64); - refresh(); -} - -void HexTextView::on_action2columns_triggered() -{ - Core()->setConfig("hex.cols", 2); - refresh(); -} - -void HexTextView::on_action1column_triggered() -{ - Core()->setConfig("hex.cols", 1); - refresh(); -} - -void HexTextView::on_actionFormatHex_triggered() -{ - format = Format::Hex; - refresh(); -} - -void HexTextView::on_actionFormatOctal_triggered() -{ - format = Format::Octal; - refresh(); -} - -void HexTextView::on_actionSelect_Block_triggered() -{ - rangeDialog.open(hexPositionToAddress(ui->hexHexText->textCursor().position())); -} - - - -void HexTextView::resizeEvent(QResizeEvent *event) -{ - QScrollArea::resizeEvent(event); - refresh(); -} - -void HexTextView::wheelEvent(QWheelEvent *event) -{ - if ( Qt::ControlModifier == event->modifiers() ) { - const QPoint numDegrees = event->angleDelta() / 8; - if (!numDegrees.isNull()) { - const QPoint numSteps = numDegrees / 15; - if ( 0 != numSteps.y() ) { - zoomIn(numSteps.y() > 0 ? 1 : -1); - } - } - event->accept(); - return; - } - - event->ignore(); -} - -void HexTextView::selectRange(RVA start, RVA end) -{ - int startPosition; - int endPosition; - QTextCursor targetTextCursor; - - requestedSelectionStartAddress = start; - requestedSelectionEndAddress = end; - - //not sure what the accepted user feedback mechanism is, output to console or a QMessageBox alert - if (requestedSelectionEndAddress < requestedSelectionStartAddress) { - Core()->message(tr("Error: Could not select range, end address is less then start address")); - return; - } - - //seek to the start address and create a text cursor to highlight the desired range - refresh(requestedSelectionStartAddress); - - //for large selections, won't be able to calculate the endPosition because hexAddressToPosition assumes the address is loaded? - startPosition = hexAddressToPosition(requestedSelectionStartAddress); - endPosition = hexAddressToPosition(requestedSelectionEndAddress + 1); - - targetTextCursor = ui->hexHexText->textCursor(); - - targetTextCursor.setPosition(startPosition); - targetTextCursor.setPosition(endPosition, QTextCursor::KeepAnchor); - - ui->hexHexText->setTextCursor(targetTextCursor); -} - -void HexTextView::on_rangeDialogAccepted() -{ - if (rangeDialog.empty()) { - refresh(rangeDialog.getStartAddress()); - return; - } - - requestedSelectionStartAddress = rangeDialog.getStartAddress(); - requestedSelectionEndAddress = rangeDialog.getEndAddress(); - - selectRange(requestedSelectionStartAddress, requestedSelectionEndAddress); -} - -void HexTextView::showOffsets(bool show) -{ - if (show) { - ui->hexOffsetText->show(); - Core()->setConfig("asm.offset", 1); - } else { - ui->hexOffsetText->hide(); - Core()->setConfig("asm.offset", 0); - } -} - -void HexTextView::zoomIn(int range) -{ - ui->hexHexText->zoomIn(range); - syncScale(); -} - -void HexTextView::zoomOut(int range) -{ - zoomIn(-range); -} - -void HexTextView::zoomReset() -{ - QFont font(ui->hexHexText->font()); - font.setPointSizeF(defaultFontSize); - ui->hexHexText->setFont(font); - syncScale(); -} - -void HexTextView::updateWidths() -{ - // Update width - auto idealWidth = ui->hexHexText->document()->idealWidth(); - ui->hexHexText->document()->setTextWidth(idealWidth); - - ui->hexOffsetText->document()->adjustSize(); - ui->hexOffsetText->setFixedWidth(ui->hexOffsetText->document()->size().width() + 1); - - ui->hexASCIIText->document()->adjustSize(); - ui->hexASCIIText->setMinimumWidth(ui->hexASCIIText->document()->size().width()); -} - -void HexTextView::syncScale() -{ - ui->hexOffsetText->setFont(ui->hexHexText->font()); - ui->hexASCIIText->setFont(ui->hexHexText->font()); - ui->offsetHeaderLabel->setFont(ui->hexHexText->font()); - ui->hexHeaderLabel->setFont(ui->hexHexText->font()); - ui->asciiHeaderLabel->setFont(ui->hexHexText->font()); - updateWidths(); -} diff --git a/src/widgets/HexTextView.h b/src/widgets/HexTextView.h deleted file mode 100644 index 9384ff25..00000000 --- a/src/widgets/HexTextView.h +++ /dev/null @@ -1,158 +0,0 @@ -#ifndef HexTextView_H -#define HexTextView_H - -#include -#include -#include -#include -#include - -#include -#include - -#include "core/Cutter.h" -#include "MemoryDockWidget.h" -#include "common/CutterSeekable.h" -#include "dialogs/HexdumpRangeDialog.h" -#include "common/Highlighter.h" -#include "common/HexAsciiHighlighter.h" -#include "common/HexHighlighter.h" -#include "common/SvgIconEngine.h" - -#include "Dashboard.h" - -namespace Ui { - class HexTextView; -} - -class RefreshDeferrer; - -class HexTextView : public QScrollArea -{ - Q_OBJECT -public: - explicit HexTextView(QWidget *parent); - ~HexTextView(); - Highlighter *highlighter; - enum Format { - Hex, - Octal, - // TODO: -// HalfWord, -// Word, -// QuadWord, -// Emoji, -// SignedInt1, -// SignedInt2, -// SignedInt4, - }; - void refresh(RVA addr = RVA_INVALID); - /** - * @brief Select non empty inclusive range [start; end] - * @param start - * @param end - */ - void selectRange(RVA start, RVA end); - void setupFonts(); - - struct Selection { - bool empty; - RVA startAddress; - RVA endAddress; - }; - - Selection getSelection(); - RVA position(); -public slots: - void on_rangeDialogAccepted(); - void showOffsets(bool show); - - void zoomIn(int range = 1); - void zoomOut(int range = 1); - void zoomReset(); -signals: - void selectionChanged(Selection selection); - void positionChanged(RVA start); -protected: - virtual void resizeEvent(QResizeEvent *event) override; - virtual void wheelEvent(QWheelEvent *event) override; - -private: - enum Format format = Format::Hex; - - std::unique_ptr ui; - - bool sent_seek = false; - bool scroll_disabled = false; - - RVA first_loaded_address = RVA_INVALID; - RVA last_loaded_address = RVA_INVALID; - - void updateHeaders(); - - std::array fetchHexdump(RVA addr, int lines); - - void connectScroll(bool disconnect_); - void setupScrollSync(); - - - - // If bottom = false gets the FIRST displayed line, otherwise the LAST displayed - // line. - int getDisplayedLined(QTextEdit *textEdit, bool bottom = false); - - static void removeTopLinesWithoutScroll(QTextEdit *textEdit, int lines); - static void removeBottomLinesWithoutScroll(QTextEdit *textEdit, int lines); - static void prependWithoutScroll(QTextEdit *textEdit, QString text); - static void appendWithoutScroll(QTextEdit *textEdit, QString text); - static void setTextEditPosition(QTextEdit *textEdit, int position); - - RVA hexPositionToAddress(int position); - RVA asciiPositionToAddress(int position); - int hexAddressToPosition(RVA address); - int asciiAddressToPosition(RVA address); - void updateWidths(); - void syncScale(); - - void updateParseWindow(RVA start_address, int size); - void clearParseWindow(); - - int bufferLines = 0; - int cols = 0; - ut64 requestedSelectionStartAddress=0; - ut64 requestedSelectionEndAddress=0; - HexdumpRangeDialog rangeDialog; - RVA currentPos = 0; - qreal defaultFontSize; - Selection selection; - -private slots: - // Currently unused/untested - // void highlightHexCurrentLine(); - // void highlightHexWords(const QString &str); - - void showHexdumpContextMenu(const QPoint &pt); - void showHexASCIIContextMenu(const QPoint &pt); - - void onSelectionChanged(); - void scrollChanged(); - - void on_actionCopyAddressAtCursor_triggered(); - - void on_action1column_triggered(); - void on_action2columns_triggered(); - void on_action4columns_triggered(); - void on_action8columns_triggered(); - void on_action16columns_triggered(); - void on_action32columns_triggered(); - void on_action64columns_triggered(); - - void on_actionFormatHex_triggered(); - void on_actionFormatOctal_triggered(); - - void on_actionSelect_Block_triggered(); - - void fontsUpdated(); -}; - -#endif // HexTextView_H diff --git a/src/widgets/HexTextView.ui b/src/widgets/HexTextView.ui deleted file mode 100644 index ebefbd49..00000000 --- a/src/widgets/HexTextView.ui +++ /dev/null @@ -1,418 +0,0 @@ - - - HexTextView - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 515 - 770 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - - - - - 0 - 0 - - - - 0 1 2 3 ... - - - - - - - - 0 - 0 - - - - false - - - QFrame::NoFrame - - - 0 - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 0 - 0 - - - - 0123... - - - - - - - Offset - - - - - - - - 0 - 0 - - - - - Anonymous Pro - 13 - - - - false - - - QFrame::NoFrame - - - 0 - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - QTextEdit::NoWrap - - - 3 - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Qt::Vertical - - - - - - - - 0 - 0 - - - - false - - - QFrame::NoFrame - - - 0 - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - Undefine - - - Undefine - - - - - Copy all - - - Copy all - - - - - Copy bytes - - - Copy bytes - - - - - Copy disasm - - - Copy disasm - - - - - Copy Hexpair - - - Copy Hexpair - - - - - Copy ASCII - - - Copy ASCII - - - - - Copy Text - - - Copy Text - - - - - Copy Address - - - Copy Address at Cursor - - - - - 1 - - - 1 - - - - - 2 - - - 2 - - - - - 4 - - - 4 - - - - - 8 - - - 8 - - - - - 16 - - - 16 - - - - - 32 - - - 32 - - - - - 64 - - - 64 - - - - - Edit - - - Edit - - - - - Paste - - - Paste - - - - - Insert Hex - - - Insert Hex - - - - - Insert String - - - Insert String - - - - - Hex - - - - - Octal - - - - - Half-word - - - - - Word - - - - - Quad-word - - - - - Emoji - - - - - 1 byte - - - 1 byte - - - - - 2 bytes - - - 2 bytes - - - - - 4 bytes - - - 4 bytes - - - - - Select Block... - - - - - true - - - Reset zoom - - - Ctrl+0 - - - Qt::WindowShortcut - - - - - - \ No newline at end of file diff --git a/src/widgets/HexWidget.cpp b/src/widgets/HexWidget.cpp new file mode 100644 index 00000000..fbbcdcc5 --- /dev/null +++ b/src/widgets/HexWidget.cpp @@ -0,0 +1,1190 @@ +#include "HexWidget.h" +#include "Cutter.h" +#include "Configuration.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const uint64_t MAX_COPY_SIZE = 128 * 1024 * 1024; +static const int MAX_LINE_WIDTH_PRESET = 32; +static const int MAX_LINE_WIDTH_BYTES = 128 * 1024; + +HexWidget::HexWidget(QWidget *parent) : + QScrollArea(parent), + cursorEnabled(true), + cursorOnAscii(false), + updatingSelection(false), + itemByteLen(1), + itemGroupSize(1), + rowSizeBytes(16), + columnMode(ColumnMode::PowerOf2), + itemFormat(ItemFormatHex), + itemBigEndian(false), + addrCharLen(AddrWidth64), + showHeader(true), + showAscii(true), + showExHex(true), + showExAddr(true) +{ + setMouseTracking(true); + setFocusPolicy(Qt::FocusPolicy::StrongFocus); + connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, [this]() { viewport()->update(); }); + + connect(Config(), &Configuration::colorsUpdated, this, &HexWidget::updateColors); + connect(Config(), &Configuration::fontsUpdated, this, [this]() { setMonospaceFont(Config()->getFont()); }); + + auto sizeActionGroup = new QActionGroup(this); + for (int i = 1; i <= 8; i *= 2) { + QAction *action = new QAction(QString::number(i), this); + action->setCheckable(true); + action->setActionGroup(sizeActionGroup); + connect(action, &QAction::triggered, this, [=]() { setItemSize(i); }); + actionsItemSize.append(action); + } + actionsItemSize.at(0)->setChecked(true); + + /* Follow the order in ItemFormat enum */ + QStringList names; + names << tr("Hexadecimal"); + names << tr("Octal"); + names << tr("Decimal"); + names << tr("Signed decimal"); + names << tr("Float"); + + auto formatActionGroup = new QActionGroup(this); + for (int i = 0; i < names.length(); ++i) { + QAction *action = new QAction(names.at(i), this); + action->setCheckable(true); + action->setActionGroup(formatActionGroup); + connect(action, &QAction::triggered, this, [=]() { setItemFormat(static_cast(i)); }); + actionsItemFormat.append(action); + } + actionsItemFormat.at(0)->setChecked(true); + actionsItemFormat.at(ItemFormatFloat)->setEnabled(false); + + rowSizeMenu = new QMenu(tr("Bytes per row"), this); + auto columnsActionGroup = new QActionGroup(this); + for (int i = 1; i <= MAX_LINE_WIDTH_PRESET; i *= 2) { + QAction *action = new QAction(QString::number(i), rowSizeMenu); + action->setCheckable(true); + action->setActionGroup(columnsActionGroup); + connect(action, &QAction::triggered, this, [=]() { setFixedLineSize(i); }); + rowSizeMenu->addAction(action); + } + rowSizeMenu->addSeparator(); + actionRowSizePowerOf2 = new QAction(tr("Power of 2"), this); + actionRowSizePowerOf2->setCheckable(true); + actionRowSizePowerOf2->setActionGroup(columnsActionGroup); + connect(actionRowSizePowerOf2, &QAction::triggered, this, [=]() { setColumnMode(ColumnMode::PowerOf2); }); + rowSizeMenu->addAction(actionRowSizePowerOf2); + + actionItemBigEndian = new QAction(tr("Big Endian"), this); + actionItemBigEndian->setCheckable(true); + actionItemBigEndian->setEnabled(false); + connect(actionItemBigEndian, &QAction::triggered, this, &HexWidget::setItemEndianess); + + actionHexPairs = new QAction(tr("Bytes as pairs"), this); + actionHexPairs->setCheckable(true); + connect(actionHexPairs, &QAction::triggered, this, &HexWidget::onHexPairsModeEnabled); + + actionCopy = new QAction(tr("Copy"), this); + addAction(actionCopy); + actionCopy->setShortcut(QKeySequence::Copy); + connect(actionCopy, &QAction::triggered, this, &HexWidget::copy); + + actionCopyAddress = new QAction(tr("Copy address"), this); + actionCopyAddress->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); + connect(actionCopyAddress, &QAction::triggered, this, &HexWidget::copyAddress); + addAction(actionCopyAddress); + + actionSelectRange = new QAction(tr("Select range"), this); + connect(actionSelectRange, &QAction::triggered, this, [this]() { rangeDialog.open(cursor.address); }); + addAction(actionSelectRange); + connect(&rangeDialog, &QDialog::accepted, this, &HexWidget::onRangeDialogAccepted); + + connect(this, &HexWidget::selectionChanged, this, [this](Selection selection) { + actionCopy->setEnabled(!selection.empty); + }); + + updateMetrics(); + updateItemLength(); + + startAddress = 0ULL; + cursor.address = 0ULL; + data = new MemoryData(); + + fetchData(); + updateCursorMeta(); + + connect(&cursor.blinkTimer, &QTimer::timeout, this, &HexWidget::onCursorBlinked); + cursor.setBlinkPeriod(1000); + cursor.startBlinking(); + + updateColors(); +} + +HexWidget::~HexWidget() +{ + +} + +void HexWidget::setMonospaceFont(const QFont &font) +{ + if (!(font.styleHint() & QFont::Monospace)) { + /* FIXME: Use default monospace font + setFont(XXX); */ + } + QScrollArea::setFont(font); + monospaceFont = font; + updateMetrics(); + fetchData(); + updateCursorMeta(); + + viewport()->update(); +} + +void HexWidget::setItemSize(int nbytes) +{ + static const QVector values({1, 2, 4, 8}); + + if (!values.contains(nbytes)) + return; + + itemByteLen = nbytes; + if (itemByteLen > rowSizeBytes) { + rowSizeBytes = itemByteLen; + } + + actionsItemFormat.at(ItemFormatFloat)->setEnabled(nbytes >= 4); + actionItemBigEndian->setEnabled(nbytes != 1); + + updateItemLength(); + fetchData(); + updateCursorMeta(); + + viewport()->update(); +} + +void HexWidget::setItemFormat(ItemFormat format) +{ + itemFormat = format; + + bool sizeEnabled = true; + if (format == ItemFormatFloat) + sizeEnabled = false; + actionsItemSize.at(0)->setEnabled(sizeEnabled); + actionsItemSize.at(1)->setEnabled(sizeEnabled); + + + updateItemLength(); + fetchData(); + updateCursorMeta(); + + viewport()->update(); +} + +void HexWidget::setItemGroupSize(int size) +{ + itemGroupSize = size; + + updateCounts(); + fetchData(); + updateCursorMeta(); + + viewport()->update(); +} + +void HexWidget::updateCounts() +{ + actionHexPairs->setEnabled(rowSizeBytes > 1 && itemByteLen == 1 + && itemFormat == ItemFormat::ItemFormatHex); + if (actionHexPairs->isChecked() && actionHexPairs->isEnabled()) { + itemGroupSize = 2; + } else { + itemGroupSize = 1; + } + + if (columnMode == ColumnMode::PowerOf2) { + int last_good_size = itemGroupByteLen(); + for (int i = itemGroupByteLen(); i <= MAX_LINE_WIDTH_BYTES; i *= 2) { + rowSizeBytes = i; + itemColumns = rowSizeBytes / itemGroupByteLen(); + updateAreasPosition(); + if (horizontalScrollBar()->maximum() == 0) { + last_good_size = rowSizeBytes; + } else { + break; + } + } + rowSizeBytes = last_good_size; + } + + itemColumns = rowSizeBytes / itemGroupByteLen(); + + // ensure correct action is selected when changing line size programmatically + if (columnMode == ColumnMode::Fixed) { + int w = 1; + const auto &actions = rowSizeMenu->actions(); + for (auto action : actions) { + action->setChecked(false); + } + for (auto action : actions) { + if (w > MAX_LINE_WIDTH_PRESET) { + break; + } + if (rowSizeBytes == w) { + action->setChecked(true); + } + w *= 2; + } + } else if (columnMode == ColumnMode::PowerOf2) { + actionRowSizePowerOf2->setChecked(true); + } + + updateAreasPosition(); +} + +void HexWidget::setFixedLineSize(int lineSize) +{ + if (lineSize < 1 || lineSize < itemGroupByteLen() || lineSize % itemGroupByteLen()) { + updateCounts(); + return; + } + rowSizeBytes = lineSize; + columnMode = ColumnMode::Fixed; + + updateCounts(); + fetchData(); + updateCursorMeta(); + + viewport()->update(); +} + +void HexWidget::setColumnMode(ColumnMode mode) +{ + columnMode = mode; + + updateCounts(); + fetchData(); + updateCursorMeta(); + + viewport()->update(); +} + +void HexWidget::selectRange(RVA start, RVA end) +{ + BasicCursor endCursor(end); + endCursor += 1; + setCursorAddr(endCursor); + selection.set(start, end); + cursorEnabled = false; + emit selectionChanged(getSelection()); +} + +void HexWidget::clearSelection() +{ + setCursorAddr(cursor.address, false); + emit selectionChanged(getSelection()); +} + +HexWidget::Selection HexWidget::getSelection() +{ + return Selection{selection.isEmpty(), selection.start(), selection.end()}; +} + +void HexWidget::seek(uint64_t address) +{ + setCursorAddr(address); +} + +void HexWidget::refresh() +{ + fetchData(); + viewport()->update(); +} + +void HexWidget::setItemEndianess(bool bigEndian) +{ + itemBigEndian = bigEndian; + + updateCursorMeta(); // Update cached item character + + viewport()->update(); +} + +void HexWidget::updateColors() +{ + borderColor = Config()->getColor("gui.border"); + backgroundColor = Config()->getColor("gui.background"); + b0x00Color = Config()->getColor("b0x00"); + b0x7fColor = Config()->getColor("b0x7f"); + b0xffColor = Config()->getColor("b0xff"); + printableColor = Config()->getColor("ai.write"); + defColor = Config()->getColor("btext"); + addrColor = Config()->getColor("func_var_addr"); + + updateCursorMeta(); + viewport()->update(); +} + +void HexWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(viewport()); + painter.setFont(monospaceFont); + + int xOffset = horizontalScrollBar()->value(); + if (xOffset > 0) + painter.translate(QPoint(-xOffset, 0)); + + if (event->rect() == cursor.screenPos) { + /* Cursor blink */ + drawCursor(painter); + return; + } + + painter.fillRect(event->rect().translated(xOffset, 0), backgroundColor); + + drawHeader(painter); + + drawAddrArea(painter); + drawItemArea(painter); + drawAsciiArea(painter); + + if (!cursorEnabled) + return; + + drawCursor(painter, true); +} + +void HexWidget::updateWidth() +{ + int max = (showAscii ? asciiArea.right() : itemArea.right()) - viewport()->width(); + if (max < 0) + max = 0; + else + max += charWidth; + horizontalScrollBar()->setMaximum(max); + horizontalScrollBar()->setSingleStep(charWidth); +} + +void HexWidget::resizeEvent(QResizeEvent *event) +{ + int oldByteCount = bytesPerScreen(); + updateCounts(); + + if (event->oldSize().height() == event->size().height() && oldByteCount == bytesPerScreen()) + return; + + updateAreasHeight(); + fetchData(); // rowCount was changed + updateCursorMeta(); + + viewport()->update(); +} + +void HexWidget::mouseMoveEvent(QMouseEvent *event) +{ + QPoint pos = event->pos(); + pos.rx() += horizontalScrollBar()->value(); + + if (!updatingSelection) { + if (itemArea.contains(pos) || asciiArea.contains(pos)) + setCursor(Qt::IBeamCursor); + else + setCursor(Qt::ArrowCursor); + return; + } + + auto &area = currentArea(); + if (pos.x() < area.left()) + pos.setX(area.left()); + else if (pos.x() > area.right()) + pos.setX(area.right()); + auto addr = currentAreaPosToAddr(pos, true); + setCursorAddr(addr, true); + + /* Stop blinking */ + cursorEnabled = false; + + viewport()->update(); +} + +void HexWidget::mousePressEvent(QMouseEvent *event) +{ + QPoint pos(event->pos()); + pos.rx() += horizontalScrollBar()->value(); + + if (event->button() == Qt::LeftButton) { + bool selectingData = itemArea.contains(pos); + bool selecting = selectingData || asciiArea.contains(pos); + if (selecting) { + updatingSelection = true; + setCursorOnAscii(!selectingData); + auto cursorPosition = currentAreaPosToAddr(pos, true); + setCursorAddr(cursorPosition, event->modifiers() == Qt::ShiftModifier); + viewport()->update(); + } + } +} + +void HexWidget::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + if (selection.isEmpty()) + selection.init(cursor.address); + updatingSelection = false; + } +} + +void HexWidget::wheelEvent(QWheelEvent *event) +{ + int dy = event->delta(); + int64_t delta = 3 * itemRowByteLen(); + if (dy > 0) + delta = -delta; + + if (dy == 0) + return; + + if (delta < 0 && startAddress < -delta) { + startAddress = 0; + } else if (delta > 0 && data->maxIndex() < bytesPerScreen()) { + startAddress = 0; + } else if (delta > 0 + && (data->maxIndex() - startAddress) <= static_cast(bytesPerScreen() + delta - 1)) { + startAddress = (data->maxIndex() - bytesPerScreen()) + 1; + } else { + startAddress += delta; + } + fetchData(); + if (cursor.address >= startAddress && cursor.address <= lastVisibleAddr()) { + /* Don't enable cursor blinking if selection isn't empty */ + cursorEnabled = selection.isEmpty(); + updateCursorMeta(); + } else { + cursorEnabled = false; + } + viewport()->update(); +} + +void HexWidget::keyPressEvent(QKeyEvent *event) +{ + bool select = false; + auto moveOrSelect = [event, &select](QKeySequence::StandardKey moveSeq, QKeySequence::StandardKey selectSeq) ->bool { + if (event->matches(moveSeq)) { + select = false; + return true; + } else if (event->matches(selectSeq)) { + select = true; + return true; + } + return false; + }; + if (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) { + moveCursor(itemRowByteLen(), select); + } else if (moveOrSelect(QKeySequence::MoveToPreviousLine, QKeySequence::SelectPreviousLine)) { + moveCursor(-itemRowByteLen(), select); + } else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar)) { + moveCursor(cursorOnAscii ? 1 : itemByteLen, select); + } else if (moveOrSelect(QKeySequence::MoveToPreviousChar, QKeySequence::SelectPreviousChar)) { + moveCursor(cursorOnAscii ? -1 : -itemByteLen, select); + } else if (moveOrSelect(QKeySequence::MoveToNextPage, QKeySequence::SelectNextPage)) { + moveCursor(bytesPerScreen(), select); + } else if (moveOrSelect(QKeySequence::MoveToPreviousPage, QKeySequence::SelectPreviousPage)) { + moveCursor(-bytesPerScreen(), select); + } else if (moveOrSelect(QKeySequence::MoveToStartOfLine, QKeySequence::SelectStartOfLine)) { + int linePos = int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); + moveCursor(-linePos, select); + } else if (moveOrSelect(QKeySequence::MoveToEndOfLine, QKeySequence::SelectEndOfLine)) { + int linePos = int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); + moveCursor(itemRowByteLen() - linePos, select); + } + //viewport()->update(); +} + +void HexWidget::contextMenuEvent(QContextMenuEvent *event) +{ + QPoint pt = event->pos(); + if (event->reason() == QContextMenuEvent::Mouse) { + auto mouseAddr = mousePosToAddr(pt).address; + if (selection.isEmpty() || !(mouseAddr >= selection.start() && mouseAddr <= selection.end())) { + cursorOnAscii = asciiArea.contains(pt); + seek(mouseAddr); + } + } + + QMenu *menu = new QMenu(); + QMenu *sizeMenu = menu->addMenu(tr("Item size:")); + sizeMenu->addActions(actionsItemSize); + QMenu *formatMenu = menu->addMenu(tr("Item format:")); + formatMenu->addActions(actionsItemFormat); + menu->addMenu(rowSizeMenu); + menu->addAction(actionHexPairs); + menu->addAction(actionItemBigEndian); + menu->addSeparator(); + menu->addAction(actionCopy); + menu->addAction(actionCopyAddress); + menu->addActions(this->actions()); + menu->exec(mapToGlobal(pt)); + menu->deleteLater(); +} + +void HexWidget::onCursorBlinked() +{ + if (!cursorEnabled) + return; + cursor.blink(); + viewport()->update(cursor.screenPos.translated(-horizontalScrollBar()->value(), 0)); +} + +void HexWidget::onHexPairsModeEnabled(bool enable) +{ + if (enable) { + setItemGroupSize(2); + } else { + setItemGroupSize(1); + } +} + +void HexWidget::copy() +{ + if (selection.isEmpty() || selection.size() > MAX_COPY_SIZE) + return; + + QClipboard *clipboard = QApplication::clipboard(); + QString range = QString("%1@0x%2").arg(selection.size()).arg(selection.start(), 0, 16); + if (cursorOnAscii) { + clipboard->setText(Core()->cmd("psx " + range)); + } else { + clipboard->setText(Core()->cmd("p8 " + range)); //TODO: copy in the format shown + } +} + +void HexWidget::copyAddress() +{ + uint64_t addr = cursor.address; + if (!selection.isEmpty()) { + addr = selection.start(); + } + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(RAddressString(addr)); +} + +void HexWidget::onRangeDialogAccepted() +{ + if (rangeDialog.empty()) { + seek(rangeDialog.getStartAddress()); + return; + } + selectRange(rangeDialog.getStartAddress(), rangeDialog.getEndAddress()); +} + +void HexWidget::updateItemLength() +{ + itemPrefixLen = 0; + + switch (itemFormat) { + case ItemFormatHex: + itemCharLen = 2 * itemByteLen; + if (itemByteLen > 1 && showExHex) + itemPrefixLen = hexPrefix.length(); + break; + case ItemFormatOct: + itemCharLen = (itemByteLen * 8 + 3) / 3; + break; + case ItemFormatDec: + switch (itemByteLen) { + case 1: + itemCharLen = 3; + break; + case 2: + itemCharLen = 5; + break; + case 4: + itemCharLen = 10; + break; + case 8: + itemCharLen = 20; + break; + } + break; + case ItemFormatSignedDec: + switch (itemByteLen) { + case 1: + itemCharLen = 4; + break; + case 2: + itemCharLen = 6; + break; + case 4: + itemCharLen = 11; + break; + case 8: + itemCharLen = 20; + break; + } + break; + case ItemFormatFloat: + if (itemByteLen < 4) + itemByteLen = 4; + // FIXME + itemCharLen = 3 * itemByteLen; + break; + } + + itemCharLen += itemPrefixLen; + + updateCounts(); +} + +void HexWidget::drawHeader(QPainter &painter) +{ + if (!showHeader) + return; + + int offset = 0; + QRect rect(itemArea.left(), 0, itemWidth(), lineHeight); + + painter.setPen(addrColor); + + for (int j = 0; j < itemColumns; ++j) { + for (int k = 0; k < itemGroupSize; ++k, offset += itemByteLen) { + painter.drawText(rect, Qt::AlignVCenter | Qt::AlignRight, QString::number(offset, 16).toUpper()); + rect.translate(itemWidth(), 0); + } + rect.translate(columnSpacingWidth(), 0); + } + + rect.moveLeft(asciiArea.left()); + rect.setWidth(charWidth); + for (int j = 0; j < itemRowByteLen(); ++j) { + painter.drawText(rect, Qt::AlignVCenter | Qt::AlignRight, QString::number(j % 16, 16).toUpper()); + rect.translate(charWidth, 0); + } +} + +void HexWidget::drawCursor(QPainter &painter, bool shadow) +{ + if (shadow) { + QPen pen(Qt::gray); + pen.setStyle(Qt::DashLine); + painter.setPen(pen); + painter.drawRect(shadowCursor.screenPos); + painter.setPen(Qt::SolidLine); + } + + painter.setPen(cursor.cachedColor); + QRect charRect(cursor.screenPos); + charRect.setWidth(charWidth); + painter.fillRect(charRect, backgroundColor); + painter.drawText(charRect, Qt::AlignVCenter, cursor.cachedChar); + if (cursor.isVisible) { + painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); + painter.fillRect(cursor.screenPos, QColor(0xff, 0xff, 0xff)); + } +} + +void HexWidget::drawAddrArea(QPainter &painter) +{ + uint64_t offset = startAddress; + QString addrString; + QSize areaSize((addrCharLen + (showExAddr ? 2 : 0)) * charWidth, lineHeight); + QRect strRect(addrArea.topLeft(), areaSize); + + painter.setPen(addrColor); + for (int line = 0; + line < visibleLines && offset <= data->maxIndex(); + ++line, strRect.translate(0, lineHeight), offset += itemRowByteLen()) { + addrString = QString("%1").arg(offset, addrCharLen, 16, QLatin1Char('0')); + if (showExAddr) + addrString.prepend(hexPrefix); + painter.drawText(strRect, Qt::AlignVCenter, addrString); + } + + painter.setPen(borderColor); + + int vLineOffset = itemArea.left() - charWidth; + painter.drawLine(vLineOffset, 0, vLineOffset, viewport()->height()); +} + +void HexWidget::drawItemArea(QPainter &painter) +{ + QRect itemRect(itemArea.topLeft(), QSize(itemWidth(), lineHeight)); + QColor itemColor; + QString itemString; + + fillSelectionBackground(painter); + + uint64_t itemAddr = startAddress; + for (int line = 0; line < visibleLines; ++line) { + itemRect.moveLeft(itemArea.left()); + for (int j = 0; j < itemColumns; ++j) { + for (int k = 0; k < itemGroupSize && itemAddr <= data->maxIndex(); ++k, itemAddr += itemByteLen) { + itemString = renderItem(itemAddr - startAddress, &itemColor); + if (selection.contains(itemAddr)) + itemColor = palette().highlightedText().color(); + painter.setPen(itemColor); + painter.drawText(itemRect, Qt::AlignVCenter, itemString); + itemRect.translate(itemWidth(), 0); + if (cursor.address == itemAddr) { + auto &itemCursor = cursorOnAscii ? shadowCursor : cursor; + itemCursor.cachedChar = itemString.at(0); + itemCursor.cachedColor = itemColor; + } + } + itemRect.translate(columnSpacingWidth(), 0); + } + itemRect.translate(0, lineHeight); + } + + painter.setPen(borderColor); + + int vLineOffset = asciiArea.left() - charWidth; + painter.drawLine(vLineOffset, 0, vLineOffset, viewport()->height()); +} + +void HexWidget::drawAsciiArea(QPainter &painter) +{ + QRect charRect(asciiArea.topLeft(), QSize(charWidth, lineHeight)); + + fillSelectionBackground(painter, true); + + uint64_t address = startAddress; + QChar ascii; + QColor color; + for (int line = 0; line < visibleLines; ++line, charRect.translate(0, lineHeight)) { + charRect.moveLeft(asciiArea.left()); + for (int j = 0; j < itemRowByteLen() && address <= data->maxIndex(); ++j, ++address) { + ascii = renderAscii(address - startAddress, &color); + if (selection.contains(address)) + color = palette().highlightedText().color(); + painter.setPen(color); + /* Dots look ugly. Use fillRect() instead of drawText(). */ + if (ascii == '.') { + int a = cursor.screenPos.width(); + int x = charRect.left() + (charWidth - a) / 2 + 1; + int y = charRect.bottom() - 2 * a; + painter.fillRect(x, y, a, a, color); + } else { + painter.drawText(charRect, Qt::AlignVCenter, ascii); + } + charRect.translate(charWidth, 0); + if (cursor.address == address) { + auto &itemCursor = cursorOnAscii ? cursor : shadowCursor; + itemCursor.cachedChar = ascii; + itemCursor.cachedColor = color; + } + } + } +} + +void HexWidget::fillSelectionBackground(QPainter &painter, bool ascii) +{ + QRect rect; + const QRect *area = ascii ? &asciiArea : &itemArea; + + int startOffset = -1; + int endOffset = -1; + + if (!selection.intersects(startAddress, lastVisibleAddr())) { + return; + } + + /* Convert absolute values to relative */ + startOffset = std::max(selection.start(), startAddress) - startAddress; + endOffset = std::min(selection.end(), lastVisibleAddr()) - startAddress; + + /* Align values */ + int startOffset2 = (startOffset + itemRowByteLen()) & ~(itemRowByteLen() - 1); + int endOffset2 = endOffset & ~(itemRowByteLen() - 1); + + QColor highlightColor = palette().color(QPalette::Highlight); + + /* Fill top/bottom parts */ + if (startOffset2 <= endOffset2) { + /* Fill the top part even if it's a whole line */ + rect = ascii ? asciiRectangle(startOffset) : itemRectangle(startOffset); + rect.setRight(area->right()); + painter.fillRect(rect, highlightColor); + /* Fill the bottom part even if it's a whole line */ + rect = ascii ? asciiRectangle(endOffset) : itemRectangle(endOffset); + rect.setLeft(area->left()); + painter.fillRect(rect, highlightColor); + /* Required for calculating the bottomRight() of the main part */ + --endOffset2; + } else { + startOffset2 = startOffset; + endOffset2 = endOffset; + } + + /* Fill the main part */ + if (startOffset2 <= endOffset2) { + if (ascii) { + rect = asciiRectangle(startOffset2); + rect.setBottomRight(asciiRectangle(endOffset2).bottomRight()); + } else { + rect = itemRectangle(startOffset2); + rect.setBottomRight(itemRectangle(endOffset2).bottomRight()); + } + painter.fillRect(rect, highlightColor); + } +} + +void HexWidget::updateMetrics() +{ + lineHeight = fontMetrics().height(); + charWidth = fontMetrics().width(QLatin1Char('F')); + + updateCounts(); + updateAreasHeight(); + + int cursorWidth = charWidth / 3; + if (cursorWidth == 0) + cursorWidth = 1; + cursor.screenPos.setHeight(lineHeight); + shadowCursor.screenPos.setHeight(lineHeight); + + cursor.screenPos.setWidth(cursorWidth); + if (cursorOnAscii) { + cursor.screenPos.moveTopLeft(asciiArea.topLeft()); + + shadowCursor.screenPos.setWidth(itemWidth()); + shadowCursor.screenPos.moveTopLeft(itemArea.topLeft()); + } else { + cursor.screenPos.moveTopLeft(itemArea.topLeft()); + + shadowCursor.screenPos.setWidth(charWidth); + shadowCursor.screenPos.moveTopLeft(asciiArea.topLeft()); + } +} + +void HexWidget::updateAreasPosition() +{ + const int spacingWidth = areaSpacingWidth(); + + int yOffset = showHeader ? lineHeight : 0; + + addrArea.setTopLeft(QPoint(0, yOffset)); + addrArea.setWidth((addrCharLen + (showExAddr ? 2 : 0)) * charWidth); + + itemArea.setTopLeft(QPoint(addrArea.right() + spacingWidth, yOffset)); + itemArea.setWidth(itemRowWidth()); + + asciiArea.setTopLeft(QPoint(itemArea.right() + spacingWidth, yOffset)); + asciiArea.setWidth(asciiRowWidth()); + + updateWidth(); +} + +void HexWidget::updateAreasHeight() +{ + visibleLines = (viewport()->height() - itemArea.top()) / lineHeight; + + int height = visibleLines * lineHeight; + addrArea.setHeight(height); + itemArea.setHeight(height); + asciiArea.setHeight(height); +} + +void HexWidget::moveCursor(int offset, bool select) +{ + BasicCursor addr = cursor.address; + addr += offset; + if (addr.address > data->maxIndex()) { + addr.address = data->maxIndex(); + } + setCursorAddr(addr, select); +} + +void HexWidget::setCursorAddr(BasicCursor addr, bool select) +{ + if (!select) { + bool clearingSelection = !selection.isEmpty(); + selection.init(addr); + if (clearingSelection) + emit selectionChanged(getSelection()); + } + emit positionChanged(addr.address); + + cursor.address = addr.address; + + /* Pause cursor repainting */ + cursorEnabled = false; + + if (select) { + selection.update(addr); + emit selectionChanged(getSelection()); + } + + uint64_t addressValue = cursor.address; + /* Update data cache if necessary */ + if (!(addressValue >= startAddress && addressValue <= lastVisibleAddr())) { + /* Align start address */ + addressValue -= (addressValue % itemRowByteLen()); + + if (addressValue > (data->maxIndex() - bytesPerScreen()) + 1) { + addressValue = (data->maxIndex() - bytesPerScreen()) + 1; + } + + /* FIXME: handling Page Up/Down */ + if (addressValue == startAddress + bytesPerScreen()) { + startAddress += itemRowByteLen(); + } else { + startAddress = addressValue; + } + + fetchData(); + } + + updateCursorMeta(); + + /* Draw cursor */ + cursor.isVisible = !select; + viewport()->update(); + + /* Resume cursor repainting */ + cursorEnabled = selection.isEmpty(); +} + +void HexWidget::updateCursorMeta() +{ + QPoint point; + QPoint pointAscii; + + int offset = cursor.address - startAddress; + int itemOffset = offset; + int asciiOffset; + + /* Calc common Y coordinate */ + point.ry() = (itemOffset / itemRowByteLen()) * lineHeight; + pointAscii.setY(point.y()); + itemOffset %= itemRowByteLen(); + asciiOffset = itemOffset; + + /* Calc X coordinate on the item area */ + point.rx() = (itemOffset / itemGroupByteLen()) * columnExWidth(); + itemOffset %= itemGroupByteLen(); + point.rx() += (itemOffset / itemByteLen) * itemWidth(); + + /* Calc X coordinate on the ascii area */ + pointAscii.rx() = asciiOffset * charWidth; + + point += itemArea.topLeft(); + pointAscii += asciiArea.topLeft(); + + cursor.screenPos.moveTopLeft(cursorOnAscii ? pointAscii : point); + shadowCursor.screenPos.moveTopLeft(cursorOnAscii ? point : pointAscii); +} + +void HexWidget::setCursorOnAscii(bool ascii) +{ + cursorOnAscii = ascii; +} + +const QColor HexWidget::itemColor(uint8_t byte) +{ + QColor color(defColor); + + if (byte == 0x00) + color = b0x00Color; + else if (byte == 0x7f) + color = b0x7fColor; + else if (byte == 0xff) + color = b0xffColor; + else if (IS_PRINTABLE(byte)) { + color = printableColor; + } + + return color; +} + +QVariant HexWidget::readItem(int offset, QColor *color) +{ + quint8 byte; + quint16 word; + quint32 dword; + quint64 qword; + float *ptrFloat32; + double *ptrFloat64; + + const void *dataPtr = data->dataPtr(startAddress + offset); + const bool signedItem = itemFormat == ItemFormatSignedDec; + + switch (itemByteLen) { + case 1: + byte = *static_cast(dataPtr); + if (color) + *color = itemColor(byte); + if (!signedItem) + return QVariant(static_cast(byte)); + return QVariant(static_cast(static_cast(byte))); + case 2: + if (itemBigEndian) + word = qFromBigEndian(dataPtr); + else + word = qFromLittleEndian(dataPtr); + if (color) + *color = defColor; + if (!signedItem) + return QVariant(static_cast(word)); + return QVariant(static_cast(static_cast(word))); + case 4: + if (itemBigEndian) + dword = qFromBigEndian(dataPtr); + else + dword = qFromLittleEndian(dataPtr); + if (color) + *color = defColor; + if (itemFormat == ItemFormatFloat) { + ptrFloat32 = static_cast(static_cast(&dword)); + return QVariant(*ptrFloat32); + } + if (!signedItem) + return QVariant(static_cast(dword)); + return QVariant(static_cast(static_cast(dword))); + case 8: + if (itemBigEndian) + qword = qFromBigEndian(dataPtr); + else + qword = qFromLittleEndian(dataPtr); + if (color) + *color = defColor; + if (itemFormat == ItemFormatFloat) { + ptrFloat64 = static_cast(static_cast(&qword)); + return QVariant(*ptrFloat64); + } + if (!signedItem) + return QVariant(qword); + return QVariant(static_cast(qword)); + } + + return QVariant(); +} + +QString HexWidget::renderItem(int offset, QColor *color) +{ + QString item; + QVariant itemVal = readItem(offset, color); + int itemLen = itemCharLen - itemPrefixLen; /* Reserve space for prefix */ + + //FIXME: handle broken itemVal ( QVariant() ) + switch (itemFormat) { + case ItemFormatHex: + item = QString("%1").arg(itemVal.toULongLong(), itemLen, 16, QLatin1Char('0')); + if (itemByteLen > 1 && showExHex) + item.prepend(hexPrefix); + break; + case ItemFormatOct: + item = QString("%1").arg(itemVal.toULongLong(), itemLen, 8, QLatin1Char('0')); + break; + case ItemFormatDec: + item = QString("%1").arg(itemVal.toULongLong(), itemLen, 10); + break; + case ItemFormatSignedDec: + item = QString("%1").arg(itemVal.toLongLong(), itemLen, 10); + break; + case ItemFormatFloat: + item = QString("%1").arg(itemVal.toDouble(), itemLen); + break; + } + + return item; +} + +QChar HexWidget::renderAscii(int offset, QColor *color) +{ + uchar byte = *static_cast(data->dataPtr(startAddress + offset)); + if (color) { + *color = itemColor(byte); + } + if (!IS_PRINTABLE(byte)) { + byte = '.'; + } + return QChar(byte); +} + +void HexWidget::fetchData() +{ + data->fetch(startAddress, bytesPerScreen()); +} + +BasicCursor HexWidget::screenPosToAddr(const QPoint &point, bool middle) const +{ + QPoint pt = point - itemArea.topLeft(); + + int relativeAddress = 0; + relativeAddress += (pt.y() / lineHeight) * itemRowByteLen(); + relativeAddress += (pt.x() / columnExWidth()) * itemGroupByteLen(); + pt.rx() %= columnExWidth(); + auto roundingOffset = middle ? itemWidth() / 2 : 0; + relativeAddress += ((pt.x() + roundingOffset) / itemWidth()) * itemByteLen; + BasicCursor result(startAddress); + result += relativeAddress; + return result; +} + +BasicCursor HexWidget::asciiPosToAddr(const QPoint &point, bool middle) const +{ + QPoint pt = point - asciiArea.topLeft(); + + int relativeAddress = 0; + relativeAddress += (pt.y() / lineHeight) * itemRowByteLen(); + auto roundingOffset = middle ? (charWidth / 2) : 0; + relativeAddress += (pt.x() + (roundingOffset)) / charWidth; + BasicCursor result(startAddress); + result += relativeAddress; + return result; +} + +BasicCursor HexWidget::currentAreaPosToAddr(const QPoint &point, bool middle) const +{ + return cursorOnAscii ? asciiPosToAddr(point, middle) : screenPosToAddr(point, middle); +} + +BasicCursor HexWidget::mousePosToAddr(const QPoint &point, bool middle) const +{ + return asciiArea.contains(point) ? asciiPosToAddr(point, middle) : screenPosToAddr(point, middle); +} + +QRect HexWidget::itemRectangle(uint offset) +{ + int x; + int y; + + y = (offset / itemRowByteLen()) * lineHeight; + offset %= itemRowByteLen(); + + x = (offset / itemGroupByteLen()) * columnExWidth(); + offset %= itemGroupByteLen(); + x += (offset / itemByteLen) * itemWidth(); + + x += itemArea.x(); + y += itemArea.y(); + + return QRect(x, y, itemWidth(), lineHeight); +} + +QRect HexWidget::asciiRectangle(uint offset) +{ + int x; + int y; + + y = (offset / itemRowByteLen()) * lineHeight; + offset %= itemRowByteLen(); + + x = offset * charWidth; + + x += asciiArea.x(); + y += asciiArea.y(); + + return QRect(x, y, charWidth, lineHeight); +} diff --git a/src/widgets/HexWidget.h b/src/widgets/HexWidget.h new file mode 100644 index 00000000..c19a2c51 --- /dev/null +++ b/src/widgets/HexWidget.h @@ -0,0 +1,463 @@ +#ifndef HEXWIDGET_H +#define HEXWIDGET_H + +#include "Cutter.h" +#include "dialogs/HexdumpRangeDialog.h" +#include +#include +#include + +struct BasicCursor +{ + uint64_t address; + bool pastEnd; + BasicCursor(uint64_t pos) : address(pos), pastEnd(false) {} + BasicCursor() : address(0), pastEnd(false) {} + BasicCursor &operator+=(int64_t offset) + { + if (offset < 0 && uint64_t(-offset) > address) { + address = 0; + pastEnd = false; + } else if (offset > 0 && uint64_t(offset) > (UINT64_MAX - address)) { + address = UINT64_MAX; + pastEnd = true; + } else { + address += uint64_t(offset); + pastEnd = false; + } + return *this; + } + BasicCursor &operator+=(int offset) + { + *this += int64_t(offset); + return *this; + } + BasicCursor &operator+=(uint64_t offset) + { + if (uint64_t(offset) > (UINT64_MAX - address)) { + address = UINT64_MAX; + pastEnd = true; + } else { + address += offset; + pastEnd = false; + } + return *this; + } + bool operator<(const BasicCursor &r) + { + return address < r.address || (pastEnd < r.pastEnd); + } +}; + +struct HexCursor +{ + HexCursor() { isVisible = false; onAsciiArea = false; } + + bool isVisible; + bool onAsciiArea; + QTimer blinkTimer; + QRect screenPos; + uint64_t address; + QString cachedChar; + QColor cachedColor; + + void blink() { isVisible = !isVisible; } + void setBlinkPeriod(int msec) { blinkTimer.setInterval(msec / 2); } + void startBlinking() { blinkTimer.start(); } + void stopBlinking() { blinkTimer.stop(); } +}; + +class AbstractData +{ +public: + virtual ~AbstractData() {} + virtual void fetch(uint64_t addr, int len) = 0; + virtual const void *dataPtr(uint64_t addr) = 0; + virtual uint64_t maxIndex() = 0; +}; + +class BufferData : public AbstractData +{ +public: + BufferData() + { + m_buffer.fill(0, 1); + } + + BufferData(const QByteArray &buffer) + { + if (buffer.isEmpty()) { + m_buffer.fill(0, 1); + } else { + m_buffer = buffer; + } + } + + ~BufferData() override {} + + void fetch(uint64_t addr, int len) override {} + + const void *dataPtr(uint64_t addr) override + { + return m_buffer.constData() + addr; + } + + uint64_t maxIndex() override + { + return m_buffer.size() - 1; + } + +private: + QByteArray m_buffer; +}; + +class MemoryData : public AbstractData +{ +public: + MemoryData() {} + ~MemoryData() override {} + + void fetch(uint64_t address, int length) override + { + // FIXME: reuse data if possible + uint64_t alignedAddr = address & ~(4096ULL - 1); + int offset = address - alignedAddr; + int len = (offset + length + (4096 - 1)) & ~(4096 - 1); + m_firstBlockAddr = alignedAddr; + m_blocks.clear(); + uint64_t addr = alignedAddr; + for (int i = 0; i < len / 4096; ++i, addr += 4096) { + m_blocks.append(Core()->ioRead(addr, 4096)); + } + } + + const void *dataPtr(uint64_t addr) override + { + int totalOffset = addr - m_firstBlockAddr; + int blockId = totalOffset / 4096; + int blockOffset = totalOffset % 4096; + return static_cast(m_blocks.at(blockId).constData() + blockOffset); + } + + virtual uint64_t maxIndex() override + { + return UINT64_MAX; + } + +private: + QVector m_blocks; + uint64_t m_firstBlockAddr; +}; + +class HexSelection +{ +public: + HexSelection() { m_empty = true; } + + inline void init(BasicCursor addr) + { + m_empty = true; + m_init = addr; + } + + void set(uint64_t start, uint64_t end) + { + m_empty = false; + m_init = m_start = start; + m_end = end; + } + + void update(BasicCursor addr) + { + m_empty = false; + if (m_init < addr) { + m_start = m_init.address; + m_end = addr.address; + if (!addr.pastEnd) + m_end -= 1; + } else if (addr < m_init) { + m_start = addr.address; + m_end = m_init.address; + if (!m_init.pastEnd) + m_end -= 1; + } else { + m_start = m_end = m_init.address; + m_empty = true; + } + } + + bool intersects(uint64_t start, uint64_t end) + { + return !m_empty && m_end >= start && m_start <= end; + } + + bool contains(uint64_t pos) const + { + return !m_empty && m_start <= pos && pos <= m_end; + } + + uint64_t size() + { + uint64_t size = 0; + if (!isEmpty()) + size = m_end - m_start + 1; + return size; + } + + inline bool isEmpty() { return m_empty; } + inline uint64_t start() { return m_start; } + inline uint64_t end() { return m_end; } + +private: + BasicCursor m_init; + uint64_t m_start; + uint64_t m_end; + bool m_empty; +}; + +class HexWidget : public QScrollArea +{ + Q_OBJECT + +public: + explicit HexWidget(QWidget *parent = nullptr); + ~HexWidget(); + + void setMonospaceFont(const QFont &font); + + enum AddrWidth { AddrWidth32 = 8, AddrWidth64 = 16 }; + enum ItemSize { ItemSizeByte = 1, ItemSizeWord = 2, ItemSizeDword = 4, ItemSizeQword = 8 }; + enum ItemFormat { ItemFormatHex, ItemFormatOct, ItemFormatDec, ItemFormatSignedDec, ItemFormatFloat }; + enum class ColumnMode { Fixed, PowerOf2 }; + + void setItemSize(int nbytes); + void setItemFormat(ItemFormat format); + void setItemEndianess(bool bigEndian); + void setItemGroupSize(int size); + /** + * @brief Sets line size in bytes. + * Changes column mode to fixed. Command can be rejected if current item format is bigger than requested size. + * @param bytes line size in bytes. + */ + void setFixedLineSize(int bytes); + void setColumnMode(ColumnMode mode); + + /** + * @brief Select non empty inclusive range [start; end] + * @param start + * @param end + */ + void selectRange(RVA start, RVA end); + void clearSelection(); + + struct Selection { + bool empty; + RVA startAddress; + RVA endAddress; + }; + Selection getSelection(); +public slots: + void seek(uint64_t address); + void refresh(); + void updateColors(); +signals: + void selectionChanged(Selection selection); + void positionChanged(RVA start); + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + +private slots: + void onCursorBlinked(); + void onHexPairsModeEnabled(bool enable); + void copy(); + void copyAddress(); + void onRangeDialogAccepted(); + +private: + void updateItemLength(); + void updateCounts(); + void drawHeader(QPainter &painter); + void drawCursor(QPainter &painter, bool shadow = false); + void drawAddrArea(QPainter &painter); + void drawItemArea(QPainter &painter); + void drawAsciiArea(QPainter &painter); + void fillSelectionBackground(QPainter &painter, bool ascii = false); + void updateMetrics(); + void updateAreasPosition(); + void updateAreasHeight(); + void moveCursor(int offset, bool select = false); + void setCursorAddr(BasicCursor addr, bool select = false); + void updateCursorMeta(); + void setCursorOnAscii(bool ascii); + const QColor itemColor(uint8_t byte); + QVariant readItem(int offset, QColor *color = nullptr); + QString renderItem(int offset, QColor *color = nullptr); + QChar renderAscii(int offset, QColor *color = nullptr); + void fetchData(); + /** + * @brief Convert mouse position to address. + * @param point mouse position in widget + * @param middle start next position from middle of symbol. Use middle=true for vertical cursror position between symbols, + * middle=false for insert mode cursor and getting symbol under cursor. + * @return + */ + BasicCursor screenPosToAddr(const QPoint &point, bool middle = false) const; + BasicCursor asciiPosToAddr(const QPoint &point, bool middle = false) const; + BasicCursor currentAreaPosToAddr(const QPoint &point, bool middle = false) const; + BasicCursor mousePosToAddr(const QPoint &point, bool middle = false) const; + QRect itemRectangle(uint offset); + QRect asciiRectangle(uint offset); + void updateWidth(); + + inline int itemWidth() const + { + return itemCharLen * charWidth; + } + + inline int itemGroupCharLen() const + { + return itemCharLen * itemGroupSize; + } + + inline int columnExCharLen() const + { + return itemGroupCharLen() + columnSpacing; + } + + inline int itemGroupByteLen() const + { + return itemByteLen * itemGroupSize; + } + + inline int columnWidth() const + { + return itemGroupCharLen() * charWidth; + } + + inline int columnExWidth() const + { + return columnExCharLen() * charWidth; + } + + inline int columnSpacingWidth() const + { + return columnSpacing * charWidth; + } + + inline int itemRowCharLen() const + { + return itemColumns * columnExCharLen() - columnSpacing; + } + + inline int itemRowByteLen() const + { + return rowSizeBytes; + } + + inline int bytesPerScreen() const + { + return itemRowByteLen() * visibleLines; + } + + inline int itemRowWidth() const + { + return itemRowCharLen() * charWidth; + } + + inline int asciiRowWidth() const + { + return itemRowByteLen() * charWidth; + } + + inline int areaSpacingWidth() const + { + return areaSpacing * charWidth; + } + + inline uint64_t lastVisibleAddr() const + { + return (startAddress - 1) + bytesPerScreen(); + } + + const QRect ¤tArea() const + { + return cursorOnAscii ? asciiArea : itemArea; + } + + bool cursorEnabled; + bool cursorOnAscii; + HexCursor cursor; + HexCursor shadowCursor; + + HexSelection selection; + bool updatingSelection; + + QRect addrArea; + QRect itemArea; + QRect asciiArea; + + int itemByteLen; + int itemGroupSize; ///< Items per group (default: 1), 2 in case of hexpair mode + int rowSizeBytes; ///< Line size in bytes + int itemColumns; ///< Number of columns, single column consists of itemGroupSize items + int itemCharLen; + int itemPrefixLen; + ColumnMode columnMode; + + ItemFormat itemFormat; + + bool itemBigEndian; + + int visibleLines; + uint64_t startAddress; + int charWidth; + int byteWidth; + int lineHeight; + int addrCharLen; + int addrAreaWidth; + QFont monospaceFont; + + bool showHeader; + bool showAscii; + bool showExHex; + bool showExAddr; + + QColor borderColor; + QColor backgroundColor; + QColor defColor; + QColor addrColor; + QColor b0x00Color; + QColor b0x7fColor; + QColor b0xffColor; + QColor printableColor; + + HexdumpRangeDialog rangeDialog; + + /* Spacings in characters */ + const int columnSpacing = 1; + const int areaSpacing = 2; + + const QString hexPrefix = QStringLiteral("0x"); + + QMenu* rowSizeMenu; + QAction* actionRowSizePowerOf2; + QList actionsItemSize; + QList actionsItemFormat; + QAction *actionItemBigEndian; + QAction *actionHexPairs; + QAction *actionCopy; + QAction *actionCopyAddress; + QAction *actionSelectRange; + + AbstractData *data; +}; + +#endif // HEXWIDGET_H diff --git a/src/widgets/HexdumpWidget.cpp b/src/widgets/HexdumpWidget.cpp index 899c1ddb..eb3042ff 100644 --- a/src/widgets/HexdumpWidget.cpp +++ b/src/widgets/HexdumpWidget.cpp @@ -95,19 +95,17 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : } }); - connect(Core(), &CutterCore::refreshAll, this, [this]() { - refresh(seekable->getOffset()); - }); + connect(Core(), &CutterCore::refreshAll, this, [this]() { refresh(); }); connect(seekable, &CutterSeekable::seekableSeekChanged, this, &HexdumpWidget::onSeekChanged); - connect(ui->hexTextView, &HexTextView::positionChanged, this, [this](RVA addr) { + connect(ui->hexTextView, &HexWidget::positionChanged, this, [this](RVA addr) { if (!sent_seek) { sent_seek = true; seekable->seek(addr); sent_seek = false; } }); - connect(ui->hexTextView, &HexTextView::selectionChanged, this, &HexdumpWidget::selectionChanged); + connect(ui->hexTextView, &HexWidget::selectionChanged, this, &HexdumpWidget::selectionChanged); initParsing(); selectHexPreview(); @@ -124,13 +122,23 @@ void HexdumpWidget::onSeekChanged(RVA addr) HexdumpWidget::~HexdumpWidget() {} +void HexdumpWidget::refresh() +{ + refresh(RVA_INVALID); +} + void HexdumpWidget::refresh(RVA addr) { if (!refreshDeferrer->attemptRefresh(addr == RVA_INVALID ? nullptr : new RVA(addr))) { return; } sent_seek = true; - ui->hexTextView->refresh(addr); + if (addr != RVA_INVALID) { + ui->hexTextView->seek(addr); + } else { + ui->hexTextView->refresh(); + refreshSelectionInfo(); + } sent_seek = false; } @@ -143,7 +151,7 @@ void HexdumpWidget::initParsing() ui->parseEndianComboBox->setCurrentIndex(Core()->getConfigb("cfg.bigendian") ? 1 : 0); } -void HexdumpWidget::selectionChanged(HexTextView::Selection selection) +void HexdumpWidget::selectionChanged(HexWidget::Selection selection) { if (selection.empty) { clearParseWindow(); @@ -179,7 +187,7 @@ void HexdumpWidget::setupFonts() { QFont font = Config()->getFont(); ui->hexDisasTextEdit->setFont(font); - ui->hexTextView->setupFonts(); + ui->hexTextView->setMonospaceFont(font); } void HexdumpWidget::refreshSelectionInfo() @@ -318,6 +326,10 @@ void HexdumpWidget::resizeEvent(QResizeEvent *event) refresh(); } +QWidget *HexdumpWidget::widgetToFocusOnRaise() +{ + return ui->hexTextView; +} void HexdumpWidget::on_copyMD5_clicked() { diff --git a/src/widgets/HexdumpWidget.h b/src/widgets/HexdumpWidget.h index 3386943e..939f3e1a 100644 --- a/src/widgets/HexdumpWidget.h +++ b/src/widgets/HexdumpWidget.h @@ -16,12 +16,12 @@ #include "common/HexAsciiHighlighter.h" #include "common/HexHighlighter.h" #include "common/SvgIconEngine.h" -#include "HexTextView.h" +#include "HexWidget.h" #include "Dashboard.h" namespace Ui { - class HexdumpWidget; +class HexdumpWidget; } class RefreshDeferrer; @@ -31,8 +31,8 @@ class HexdumpWidget : public MemoryDockWidget Q_OBJECT public: explicit HexdumpWidget(MainWindow *main, QAction *action = nullptr); - ~HexdumpWidget(); - Highlighter *highlighter; + ~HexdumpWidget() override; + Highlighter *highlighter; public slots: void initParsing(); @@ -40,7 +40,7 @@ public slots: void toggleSync(); protected: virtual void resizeEvent(QResizeEvent *event) override; - + QWidget *widgetToFocusOnRaise() override; private: std::unique_ptr ui; @@ -48,7 +48,8 @@ private: RefreshDeferrer *refreshDeferrer; - void refresh(RVA addr = RVA_INVALID); + void refresh(); + void refresh(RVA addr); void selectHexPreview(); void setupFonts(); @@ -65,7 +66,7 @@ private slots: void on_actionHideHexdump_side_panel_triggered(); - void selectionChanged(HexTextView::Selection selection); + void selectionChanged(HexWidget::Selection selection); void on_parseArchComboBox_currentTextChanged(const QString &arg1); void on_parseBitsComboBox_currentTextChanged(const QString &arg1); diff --git a/src/widgets/HexdumpWidget.ui b/src/widgets/HexdumpWidget.ui index 8e60a342..cd54b900 100644 --- a/src/widgets/HexdumpWidget.ui +++ b/src/widgets/HexdumpWidget.ui @@ -32,7 +32,7 @@ Qt::Horizontal - + QFrame::NoFrame @@ -674,9 +674,9 @@ - HexTextView + HexWidget QScrollArea -
HexTextView.h
+
HexWidget.h
1