From cda24bfc5320cc01e60d73932839a8e0514e5a92 Mon Sep 17 00:00:00 2001 From: karliss Date: Fri, 10 May 2019 14:15:57 +0300 Subject: [PATCH] Seperate Widget showing grid of bytes from rest of the hexdump panel. (#1504) * Seperate Widget showing grid of bytes from rest of the hexdump panel. --- src/Cutter.pro | 3 + src/dialogs/HexdumpRangeDialog.cpp | 83 ++- src/dialogs/HexdumpRangeDialog.h | 20 +- src/dialogs/HexdumpRangeDialog.ui | 6 + src/widgets/HexTextView.cpp | 999 +++++++++++++++++++++++++++++ src/widgets/HexTextView.h | 158 +++++ src/widgets/HexTextView.ui | 418 ++++++++++++ src/widgets/HexdumpWidget.cpp | 960 +-------------------------- src/widgets/HexdumpWidget.h | 87 +-- src/widgets/HexdumpWidget.ui | 272 +------- 10 files changed, 1698 insertions(+), 1308 deletions(-) create mode 100644 src/widgets/HexTextView.cpp create mode 100644 src/widgets/HexTextView.h create mode 100644 src/widgets/HexTextView.ui diff --git a/src/Cutter.pro b/src/Cutter.pro index 5af70774..80486acc 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -272,6 +272,7 @@ SOURCES += \ menus/DisassemblyContextMenu.cpp \ widgets/DisassemblyWidget.cpp \ widgets/HexdumpWidget.cpp \ + widgets/HexTextView.cpp \ common/Configuration.cpp \ common/Colors.cpp \ dialogs/SaveProjectDialog.cpp \ @@ -391,6 +392,7 @@ HEADERS += \ menus/DisassemblyContextMenu.h \ widgets/DisassemblyWidget.h \ widgets/HexdumpWidget.h \ + widgets/HexTextView.h \ common/Configuration.h \ common/Colors.h \ dialogs/SaveProjectDialog.h \ @@ -499,6 +501,7 @@ 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/dialogs/HexdumpRangeDialog.cpp b/src/dialogs/HexdumpRangeDialog.cpp index b810346e..008b0be2 100644 --- a/src/dialogs/HexdumpRangeDialog.cpp +++ b/src/dialogs/HexdumpRangeDialog.cpp @@ -2,11 +2,14 @@ #include "ui_HexdumpRangeDialog.h" #include +#include +#include #include "core/Cutter.h" -HexdumpRangeDialog::HexdumpRangeDialog(QWidget *parent) : +HexdumpRangeDialog::HexdumpRangeDialog(QWidget *parent, bool allowEmpty) : QDialog(parent), - ui(new Ui::HexdumpRangeDialog) + ui(new Ui::HexdumpRangeDialog), + allowEmpty(allowEmpty) { ui->setupUi(this); QRegExpValidator *v = new QRegExpValidator(QRegExp("(?:0[xX])?[0-9a-fA-F]+"), this); @@ -15,6 +18,7 @@ HexdumpRangeDialog::HexdumpRangeDialog(QWidget *parent) : ui->endAddressLineEdit->setValidator(v); //subscribe to a text change slot + connect(ui->startAddressLineEdit, &QLineEdit::textEdited, this, &HexdumpRangeDialog::textEdited); connect(ui->endAddressLineEdit, &QLineEdit::textEdited, this, &HexdumpRangeDialog::textEdited); connect(ui->lengthLineEdit, &QLineEdit::textEdited, this, &HexdumpRangeDialog::textEdited); connect(ui->endAddressRadioButton, &QRadioButton::clicked, this, @@ -29,19 +33,19 @@ HexdumpRangeDialog::~HexdumpRangeDialog() delete ui; } -QString HexdumpRangeDialog::getStartAddress() const +bool HexdumpRangeDialog::empty() { - return ui->startAddressLineEdit->text(); + return emptyRange; } -QString HexdumpRangeDialog::getEndAddress() const +unsigned long long HexdumpRangeDialog::getStartAddress() const { - return ui->endAddressLineEdit->text(); + return startAddress; } -QString HexdumpRangeDialog::getLength() const +unsigned long long HexdumpRangeDialog::getEndAddress() const { - return ui->lengthLineEdit->text(); + return endAddress; } bool HexdumpRangeDialog::getEndAddressRadioButtonChecked() const @@ -60,44 +64,76 @@ void HexdumpRangeDialog::setStartAddress(ut64 start) QString("0x%1").arg(start, 0, 16)); } -void HexdumpRangeDialog::textEdited() +void HexdumpRangeDialog::open(ut64 start) +{ + setStartAddress(start); + setModal(false); + show(); + activateWindow(); + raise(); +} + +bool HexdumpRangeDialog::validate() { bool warningVisibile = false; - ut64 startAddress = Core()->math(ui->startAddressLineEdit->text()); - ut64 endAddress = 0; + startAddress = Core()->math(ui->startAddressLineEdit->text()); + endAddress = 0; ut64 length = 0; - if (sender() == ui->endAddressLineEdit) { - endAddress = Core()->math(getEndAddress()); + emptyRange = true; + if (ui->endAddressRadioButton->isChecked()) { + endAddress = Core()->math(ui->endAddressLineEdit->text()); if (endAddress > startAddress) { length = endAddress - startAddress; ui->lengthLineEdit->setText( QString("0x%1").arg(length, 0, 16)); - } - else { + this->endAddress = endAddress - 1; + emptyRange = false; + } else if (endAddress == startAddress) { + ui->lengthLineEdit->setText("0"); + return allowEmpty; + } else { ui->lengthLineEdit->setText("Invalid"); + return false; } - } else if ( sender() == ui->lengthLineEdit) { + } else { //we edited the length, so update the end address to be start address + length - length = Core()->math(getLength()); - endAddress = startAddress + length; - ui->endAddressLineEdit->setText( - QString("0x%1").arg(endAddress, 0, 16)); + length = Core()->math(ui->lengthLineEdit->text()); + if (length == 0) { + ui->endAddressLineEdit->setText("Empty"); + return allowEmpty; + } else if (UINT64_MAX - startAddress < length - 1) { + ui->endAddressLineEdit->setText("Invalid"); + return false; + } else { + endAddress = startAddress + length - 1; + emptyRange = false; + if (endAddress == UINT64_MAX) { + ui->endAddressLineEdit->setText( + QString("2^64")); + } else { + ui->endAddressLineEdit->setText( + QString("0x%1").arg(endAddress + 1, 0, 16)); + } + } } - length = Core()->math(getLength()); - // Warn the user for potentially heavy operation if (length > 0x25000) { warningVisibile = true; } ui->selectionWarningLabel->setVisible(warningVisibile); + return true; +} +void HexdumpRangeDialog::textEdited() +{ + bool valid = validate(); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); } void HexdumpRangeDialog::on_radioButtonClicked(bool checked) { - if (sender() == ui->endAddressRadioButton && checked == true) { ui->lengthLineEdit->setEnabled(false); ui->endAddressLineEdit->setEnabled(true); @@ -107,5 +143,4 @@ void HexdumpRangeDialog::on_radioButtonClicked(bool checked) ui->endAddressLineEdit->setEnabled(false); ui->lengthLineEdit->setFocus(); } - } diff --git a/src/dialogs/HexdumpRangeDialog.h b/src/dialogs/HexdumpRangeDialog.h index d332e6c2..3ba04465 100644 --- a/src/dialogs/HexdumpRangeDialog.h +++ b/src/dialogs/HexdumpRangeDialog.h @@ -14,20 +14,28 @@ class HexdumpRangeDialog : public QDialog Q_OBJECT public: - explicit HexdumpRangeDialog(QWidget *parent = nullptr); + explicit HexdumpRangeDialog(QWidget *parent = nullptr, bool allowEmpty = false); ~HexdumpRangeDialog(); - QString getStartAddress() const; - QString getEndAddress() const; - QString getLength() const; - bool getEndAddressRadioButtonChecked() const; - bool getLengthRadioButtonChecked() const; + bool empty(); + ut64 getStartAddress() const; + ut64 getEndAddress() const; + void setStartAddress(ut64 start); + void open(ut64 start); public slots: void textEdited(); private: + bool getEndAddressRadioButtonChecked() const; + bool getLengthRadioButtonChecked() const; + bool validate(); + Ui::HexdumpRangeDialog *ui; + bool emptyRange = true; + ut64 startAddress; + ut64 endAddress; + bool allowEmpty = false; private slots: void on_radioButtonClicked(bool checked); diff --git a/src/dialogs/HexdumpRangeDialog.ui b/src/dialogs/HexdumpRangeDialog.ui index eda48670..f7c700e0 100644 --- a/src/dialogs/HexdumpRangeDialog.ui +++ b/src/dialogs/HexdumpRangeDialog.ui @@ -43,6 +43,9 @@ + + Exclusive end address + End Address: @@ -72,6 +75,9 @@ + + Exclusive end address + 18 diff --git a/src/widgets/HexTextView.cpp b/src/widgets/HexTextView.cpp new file mode 100644 index 00000000..f5180d03 --- /dev/null +++ b/src/widgets/HexTextView.cpp @@ -0,0 +1,999 @@ +#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 new file mode 100644 index 00000000..9384ff25 --- /dev/null +++ b/src/widgets/HexTextView.h @@ -0,0 +1,158 @@ +#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 new file mode 100644 index 00000000..ebefbd49 --- /dev/null +++ b/src/widgets/HexTextView.ui @@ -0,0 +1,418 @@ + + + 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/HexdumpWidget.cpp b/src/widgets/HexdumpWidget.cpp index 3a0c6001..899c1ddb 100644 --- a/src/widgets/HexdumpWidget.cpp +++ b/src/widgets/HexdumpWidget.cpp @@ -34,26 +34,9 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : } setObjectName(name); - // Setup hex highlight - //connect(ui->hexHexText, SIGNAL(cursorPositionChanged()), this, SLOT(highlightHexCurrentLine())); - //highlightHexCurrentLine(); - - auto cpyAddrShortcut = new QShortcut(QKeySequence{Qt::CTRL + Qt::SHIFT + Qt::Key_C}, this); - cpyAddrShortcut->setContext(Qt::WidgetWithChildrenShortcut); - ui->actionCopyAddressAtCursor->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); - connect(cpyAddrShortcut, &QShortcut::activated, this, &HexdumpWidget::on_actionCopyAddressAtCursor_triggered); - ui->copyMD5->setIcon(QIcon(":/img/icons/copy.svg")); ui->copySHA1->setIcon(QIcon(":/img/icons/copy.svg")); - 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); ui->splitter->setChildrenCollapsible(false); @@ -94,9 +77,6 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : " border-color : #3daee9" "}"); - colorsUpdatedSlot(); - updateHeaders(); - this->setWindowTitle(tr("Hexdump")); refreshDeferrer = createReplacingRefreshDeferrer(false, [this](const RVA *offset) { @@ -104,22 +84,10 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : }); connect(&syncAction, SIGNAL(triggered(bool)), this, SLOT(toggleSync())); - - // 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); + syncAction.setText(tr("Sync/unsync offset")); + this->ui->hexTextView->addAction(&syncAction); connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdated())); - connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot())); connect(this, &QDockWidget::visibilityChanged, this, [](bool visibility) { if (visibility) { @@ -131,327 +99,41 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : refresh(seekable->getOffset()); }); - connect(ui->hexHexText, &QTextEdit::selectionChanged, this, &HexdumpWidget::selectionChanged); - connect(ui->hexASCIIText, &QTextEdit::selectionChanged, this, &HexdumpWidget::selectionChanged); - connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexdumpWidget::selectionChanged); - connect(ui->hexASCIIText, &QTextEdit::cursorPositionChanged, this, - &HexdumpWidget::selectionChanged); connect(seekable, &CutterSeekable::seekableSeekChanged, this, &HexdumpWidget::onSeekChanged); - connect(&rangeDialog, &QDialog::accepted, this, &HexdumpWidget::on_rangeDialogAccepted); + connect(ui->hexTextView, &HexTextView::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); - addAction(ui->actionResetZoom); - connect(ui->actionResetZoom, &QAction::triggered, this, &HexdumpWidget::zoomReset); - defaultFontSize = ui->hexHexText->font().pointSizeF(); - - format = Format::Hex; initParsing(); selectHexPreview(); } -void HexdumpWidget::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 HexdumpWidget::onSeekChanged(RVA) +void HexdumpWidget::onSeekChanged(RVA addr) { if (sent_seek) { sent_seek = false; return; } - refresh(); -} - -void HexdumpWidget::connectScroll(bool disconnect_) -{ - scroll_disabled = disconnect_; - if (disconnect_) { - disconnect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged, this, - &HexdumpWidget::scrollChanged); - disconnect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexdumpWidget::scrollChanged); - } else { - connect(ui->hexHexText->verticalScrollBar(), &QScrollBar::valueChanged, this, - &HexdumpWidget::scrollChanged); - connect(ui->hexHexText, &QTextEdit::cursorPositionChanged, this, &HexdumpWidget::scrollChanged); - - } + refresh(addr); } HexdumpWidget::~HexdumpWidget() {} -/* - * Text highlight functions - * Currently unused - */ -/* -void HexdumpWidget::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 HexdumpWidget::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 HexdumpWidget::refresh(RVA addr) { if (!refreshDeferrer->attemptRefresh(addr == RVA_INVALID ? nullptr : new RVA(addr))) { return; } - - ut64 loadLines = 0; - ut64 curAddrLineOffset = 0; - connectScroll(true); - - updateHeaders(); - - if (addr == RVA_INVALID) { - addr = seekable->getOffset(); - } - - 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()); - - selectHexPreview(); - - connectScroll(false); + sent_seek = true; + ui->hexTextView->refresh(addr); + sent_seek = false; } -void HexdumpWidget::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); -} void HexdumpWidget::initParsing() { @@ -461,221 +143,25 @@ void HexdumpWidget::initParsing() ui->parseEndianComboBox->setCurrentIndex(Core()->getConfigb("cfg.bigendian") ? 1 : 0); } -std::array HexdumpWidget::fetchHexdump(RVA addr, int lines) +void HexdumpWidget::selectionChanged(HexTextView::Selection selection) { - // 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 HexdumpWidget::selectionChanged() -{ - if (scroll_disabled) { - return; - } - connectScroll(true); - - if (sender() == ui->hexHexText) { - QTextCursor textCursor = ui->hexHexText->textCursor(); - if (!textCursor.hasSelection()) { - clearParseWindow(); - RVA adr = hexPositionToAddress(textCursor.position()); - int pos = asciiAddressToPosition(adr); - setTextEditPosition(ui->hexASCIIText, pos); - sent_seek = true; - seekable->seek(adr); - sent_seek = false; - 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); - - updateParseWindow(startAddress, endAddress - startAddress); - - 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); - sent_seek = true; - seekable->seek(startAddress); - sent_seek = false; + if (selection.empty) { + clearParseWindow(); } else { - QTextCursor textCursor = ui->hexASCIIText->textCursor(); - if (!textCursor.hasSelection()) { - clearParseWindow(); - RVA adr = asciiPositionToAddress(textCursor.position()); - int pos = hexAddressToPosition(adr); - setTextEditPosition(ui->hexHexText, pos); - connectScroll(false); - sent_seek = true; - seekable->seek(adr); - sent_seek = false; - return; - } - RVA startAddress = asciiPositionToAddress(textCursor.selectionStart()); - RVA endAddress = asciiPositionToAddress(textCursor.selectionEnd()); - - updateParseWindow(startAddress, endAddress - startAddress); - - 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); - sent_seek = true; - seekable->seek(startAddress); - sent_seek = false; + updateParseWindow(selection.startAddress, selection.endAddress - selection.startAddress + 1); } - - connectScroll(false); - return; } void HexdumpWidget::on_parseArchComboBox_currentTextChanged(const QString &/*arg1*/) { - selectionChanged(); + refreshSelectionInfo(); } void HexdumpWidget::on_parseBitsComboBox_currentTextChanged(const QString &/*arg1*/) { - selectionChanged(); + refreshSelectionInfo(); } -void HexdumpWidget::showHexdumpContextMenu(const QPoint &pt) -{ - // Set Hexdump popup menu - QMenu *menu = ui->hexHexText->createStandardContextMenu(); - menu->clear(); - - menu->addAction(ui->actionCopyAddressAtCursor); - - menu->addSeparator(); - - /*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(); - syncAction.setText(tr("Sync/unsync offset")); - menu->addAction(&syncAction); - - // 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 HexdumpWidget::toggleSync() { @@ -688,46 +174,17 @@ void HexdumpWidget::toggleSync() } } -void HexdumpWidget::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 HexdumpWidget::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); - ui->hexDisasTextEdit->setFont(font); + ui->hexTextView->setupFonts(); +} + +void HexdumpWidget::refreshSelectionInfo() +{ + selectionChanged(ui->hexTextView->getSelection()); } void HexdumpWidget::fontsUpdated() @@ -735,10 +192,6 @@ void HexdumpWidget::fontsUpdated() setupFonts(); } -void HexdumpWidget::colorsUpdatedSlot() -{ -} - void HexdumpWidget::clearParseWindow() { ui->hexDisasTextEdit->setPlainText(""); @@ -810,198 +263,10 @@ void HexdumpWidget::updateParseWindow(RVA start_address, int size) ui->bytesSHA1->setCursorPosition(0); } -RVA HexdumpWidget::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 HexdumpWidget::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 HexdumpWidget::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 HexdumpWidget::asciiAddressToPosition(RVA address) -{ - RVA local_address = address - first_loaded_address; - int position = local_address + (local_address / cols); - return position; -} - -void HexdumpWidget::setTextEditPosition(QTextEdit *textEdit, int position) -{ - QTextCursor textCursor = textEdit->textCursor(); - textCursor.setPosition(position); - textEdit->setTextCursor(textCursor); -} - -int HexdumpWidget::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 HexdumpWidget::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 HexdumpWidget::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 HexdumpWidget::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 HexdumpWidget::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 HexdumpWidget::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 HexdumpWidget::on_actionCopyAddressAtCursor_triggered() -{ - auto addr = hexPositionToAddress(ui->hexHexText->textCursor().position()); - - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(RAddressString(addr)); -} - void HexdumpWidget::on_actionHideHexdump_side_panel_triggered() { if (ui->hexSideTab_2->isVisible()) { @@ -1011,72 +276,6 @@ void HexdumpWidget::on_actionHideHexdump_side_panel_triggered() } } -void HexdumpWidget::on_action8columns_triggered() -{ - Core()->setConfig("hex.cols", 8); - refresh(); -} - -void HexdumpWidget::on_action16columns_triggered() -{ - Core()->setConfig("hex.cols", 16); - refresh(); -} - -void HexdumpWidget::on_action4columns_triggered() -{ - Core()->setConfig("hex.cols", 4); - refresh(); -} - -void HexdumpWidget::on_action32columns_triggered() -{ - Core()->setConfig("hex.cols", 32); - refresh(); -} - -void HexdumpWidget::on_action64columns_triggered() -{ - Core()->setConfig("hex.cols", 64); - refresh(); -} - -void HexdumpWidget::on_action2columns_triggered() -{ - Core()->setConfig("hex.cols", 2); - refresh(); -} - -void HexdumpWidget::on_action1column_triggered() -{ - Core()->setConfig("hex.cols", 1); - refresh(); -} - -void HexdumpWidget::on_actionFormatHex_triggered() -{ - format = Format::Hex; - refresh(); -} - -void HexdumpWidget::on_actionFormatOctal_triggered() -{ - format = Format::Octal; - refresh(); -} - -void HexdumpWidget::on_actionSelect_Block_triggered() -{ - - //get the current hex address from current cursor location - rangeDialog.setStartAddress( - hexPositionToAddress(ui->hexHexText->textCursor().position())); - rangeDialog.setModal(false); - rangeDialog.show(); - rangeDialog.activateWindow(); - rangeDialog.raise(); - -} void HexdumpWidget::on_parseTypeComboBox_currentTextChanged(const QString &) { @@ -1085,12 +284,12 @@ void HexdumpWidget::on_parseTypeComboBox_currentTextChanged(const QString &) } else { ui->hexSideFrame_2->hide(); } - selectionChanged(); + refreshSelectionInfo(); } void HexdumpWidget::on_parseEndianComboBox_currentTextChanged(const QString &) { - selectionChanged(); + refreshSelectionInfo(); } void HexdumpWidget::on_hexSideTab_2_currentChanged(int /*index*/) @@ -1119,22 +318,6 @@ void HexdumpWidget::resizeEvent(QResizeEvent *event) refresh(); } -void HexdumpWidget::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 HexdumpWidget::on_copyMD5_clicked() { @@ -1169,88 +352,3 @@ void HexdumpWidget::selectHexPreview() ui->parseBitsComboBox->setCurrentIndex(ui->parseBitsComboBox->findText(bits)); } } - -void HexdumpWidget::on_rangeDialogAccepted() -{ - int startPosition; - int endPosition; - QTextCursor targetTextCursor; - - requestedSelectionStartAddress = Core()->math(rangeDialog.getStartAddress()); - requestedSelectionEndAddress = rangeDialog.getEndAddressRadioButtonChecked() ? - Core()->math(rangeDialog.getEndAddress()) : - requestedSelectionStartAddress + Core()->math(rangeDialog.getLength()); - - //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 HexdumpWidget::showOffsets(bool show) -{ - if (show) { - ui->hexOffsetText->show(); - Core()->setConfig("asm.offset", 1); - } else { - ui->hexOffsetText->hide(); - Core()->setConfig("asm.offset", 0); - } -} - -void HexdumpWidget::zoomIn(int range) -{ - ui->hexHexText->zoomIn(range); - syncScale(); -} - -void HexdumpWidget::zoomOut(int range) -{ - zoomIn(-range); -} - -void HexdumpWidget::zoomReset() -{ - QFont font(ui->hexHexText->font()); - font.setPointSizeF(defaultFontSize); - ui->hexHexText->setFont(font); - syncScale(); -} - -void HexdumpWidget::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 HexdumpWidget::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/HexdumpWidget.h b/src/widgets/HexdumpWidget.h index 9768642d..3386943e 100644 --- a/src/widgets/HexdumpWidget.h +++ b/src/widgets/HexdumpWidget.h @@ -12,11 +12,11 @@ #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 "HexTextView.h" #include "Dashboard.h" @@ -33,127 +33,46 @@ public: explicit HexdumpWidget(MainWindow *main, QAction *action = nullptr); ~HexdumpWidget(); Highlighter *highlighter; - enum Format { - Hex, - Octal, - // TODO: -// HalfWord, -// Word, -// QuadWord, -// Emoji, -// SignedInt1, -// SignedInt2, -// SignedInt4, - }; public slots: void initParsing(); - void on_rangeDialogAccepted(); - void showOffsets(bool show); - void zoomIn(int range = 1); - void zoomOut(int range = 1); - void zoomReset(); void toggleSync(); - protected: virtual void resizeEvent(QResizeEvent *event) override; - virtual void wheelEvent(QWheelEvent *event) override; private: - static const int linesMarginMin = 32; - static const int linesMarginDefault = 48; - static const int linesMarginMax = 64; - - 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; RefreshDeferrer *refreshDeferrer; void refresh(RVA addr = RVA_INVALID); void selectHexPreview(); - void updateHeaders(); - - std::array fetchHexdump(RVA addr, int lines); - - void connectScroll(bool disconnect_); - void setupScrollSync(); void setupFonts(); - // 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 refreshSelectionInfo(); void updateParseWindow(RVA start_address, int size); void clearParseWindow(); - int bufferLines = 0; - int cols = 0; - ut64 requestedSelectionStartAddress=0; - ut64 requestedSelectionEndAddress=0; - HexdumpRangeDialog rangeDialog; QAction syncAction; CutterSeekable *seekable; - qreal defaultFontSize; private slots: void onSeekChanged(RVA addr); - // Currently unused/untested - // void highlightHexCurrentLine(); - // void highlightHexWords(const QString &str); - void on_actionHideHexdump_side_panel_triggered(); - void showHexdumpContextMenu(const QPoint &pt); - void showHexASCIIContextMenu(const QPoint &pt); - - void selectionChanged(); - void scrollChanged(); + void selectionChanged(HexTextView::Selection selection); void on_parseArchComboBox_currentTextChanged(const QString &arg1); void on_parseBitsComboBox_currentTextChanged(const QString &arg1); void on_parseTypeComboBox_currentTextChanged(const QString &arg1); void on_parseEndianComboBox_currentTextChanged(const QString &arg1); - 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(); - void colorsUpdatedSlot(); void on_hexSideTab_2_currentChanged(int index); void on_copyMD5_clicked(); diff --git a/src/widgets/HexdumpWidget.ui b/src/widgets/HexdumpWidget.ui index 9806a315..8e60a342 100644 --- a/src/widgets/HexdumpWidget.ui +++ b/src/widgets/HexdumpWidget.ui @@ -32,192 +32,13 @@ Qt::Horizontal - + 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 - - - - - @@ -764,70 +585,6 @@ Copy Text - - - Copy Address - - - Copy Address at Cursor - - - - - 1 - - - 1 - - - - - 2 - - - 2 - - - - - 4 - - - 4 - - - - - 8 - - - 8 - - - - - 16 - - - 16 - - - - - 32 - - - 32 - - - - - 64 - - - 64 - - Edit @@ -914,26 +671,15 @@ 4 bytes - - - Select Block... - - - - - true - - - Reset zoom - - - Ctrl+0 - - - Qt::WindowShortcut - - + + + HexTextView + QScrollArea +
HexTextView.h
+ 1 +
+