// SPDX-FileCopyrightText: 2014 Dax89 // SPDX-License-Identifier: MIT #include "qhexrenderer.h" #include #include #include #include #include #define HEX_UNPRINTABLE_CHAR '.' QHexRenderer::QHexRenderer(QHexDocument *document, const QFontMetricsF &fontmetrics, QObject *parent) : QObject(parent) , m_document(document) , m_fontmetrics(fontmetrics) { m_selectedarea = QHexRenderer::HexArea; m_cursorenabled = false; } void QHexRenderer::renderFrame(QPainter *painter) { QRect rect = painter->window(); int hexx = this->getHexColumnX(); int asciix = this->getAsciiColumnX(); int endx = this->getEndColumnX(); // x coordinates are in absolute space // y coordinates are in viewport space // see QHexView::paintEvent where the painter has been shifted horizontally painter->drawLine(0, this->headerLineCount() * this->lineHeight() - 1, endx, this->headerLineCount() * this->lineHeight() - 1); painter->drawLine(hexx, rect.top(), hexx, rect.bottom()); painter->drawLine(asciix, rect.top(), asciix, rect.bottom()); painter->drawLine(endx, rect.top(), endx, rect.bottom()); } void QHexRenderer::render(QPainter *painter, quint64 begin, quint64 end, quint64 firstline) { QPalette palette = qApp->palette(); this->drawHeader(painter, palette); quint64 documentLines = this->documentLines(); for (quint64 line = begin; line < std::min(end, documentLines); line++) { QRect linerect = this->getLineRect(line, firstline); if (line % 2) painter->fillRect(linerect, palette.brush(QPalette::Window)); else painter->fillRect(linerect, palette.brush(QPalette::Base)); this->drawAddress(painter, palette, linerect, line); this->drawHex(painter, palette, linerect, line); this->drawAscii(painter, palette, linerect, line); } } void QHexRenderer::updateMetrics(const QFontMetricsF &fm) { m_fontmetrics = fm; } void QHexRenderer::enableCursor(bool b) { m_cursorenabled = b; } void QHexRenderer::selectArea(const QPoint &pt) { int area = this->hitTestArea(pt); if (!this->editableArea(area)) return; m_selectedarea = area; } bool QHexRenderer::hitTest(const QPoint &pt, QHexPosition *position, quint64 firstline) const { int area = this->hitTestArea(pt); if (!this->editableArea(area)) return false; position->line = std::min(firstline + (pt.y() / this->lineHeight()) - headerLineCount(), this->documentLastLine()); position->lineWidth = this->hexLineWidth(); if (area == QHexRenderer::HexArea) { int relx = pt.x() - this->getHexColumnX() - this->borderSize(); int column = relx / this->getCellWidth(); position->column = column / 3; // first char is nibble 1, 2nd and space are 0 position->nibbleindex = (column % 3 == 0) ? 1 : 0; } else { int relx = pt.x() - this->getAsciiColumnX() - this->borderSize(); position->column = relx / this->getCellWidth(); position->nibbleindex = 1; } if (position->line == this->documentLastLine()) // Check last line's columns { QByteArray ba = this->getLine(position->line); position->column = std::min(position->column, static_cast(ba.length())); } else position->column = std::min(position->column, hexLineWidth() - 1); return true; } int QHexRenderer::hitTestArea(const QPoint &pt) const { if (pt.y() < headerLineCount() * lineHeight()) return QHexRenderer::HeaderArea; if ((pt.x() >= this->borderSize()) && (pt.x() <= (this->getHexColumnX() - this->borderSize()))) return QHexRenderer::AddressArea; if ((pt.x() > (this->getHexColumnX() + this->borderSize())) && (pt.x() < (this->getAsciiColumnX() - this->borderSize()))) return QHexRenderer::HexArea; if ((pt.x() > (this->getAsciiColumnX() + this->borderSize())) && (pt.x() < (this->getEndColumnX() - this->borderSize()))) return QHexRenderer::AsciiArea; return QHexRenderer::ExtraArea; } int QHexRenderer::selectedArea() const { return m_selectedarea; } bool QHexRenderer::editableArea(int area) const { return (area == QHexRenderer::HexArea || area == QHexRenderer::AsciiArea); } quint64 QHexRenderer::documentLastLine() const { return this->documentLines() - 1; } int QHexRenderer::documentLastColumn() const { return this->getLine(this->documentLastLine()).length(); } quint64 QHexRenderer::documentLines() const { return std::ceil(this->rendererLength() / static_cast(hexLineWidth())); } int QHexRenderer::documentWidth() const { return this->getEndColumnX(); } int QHexRenderer::lineHeight() const { return qRound(m_fontmetrics.height()); } QRect QHexRenderer::getLineRect(quint64 line, quint64 firstline) const { return QRect(0, static_cast((line - firstline + headerLineCount()) * lineHeight()), this->getEndColumnX(), lineHeight()); } int QHexRenderer::headerLineCount() const { return 1; } int QHexRenderer::borderSize() const { if (m_document) return this->getNCellsWidth(m_document->areaIndent()); return this->getNCellsWidth(DEFAULT_AREA_IDENTATION); } int QHexRenderer::hexLineWidth() const { if (m_document) return m_document->hexLineWidth(); return DEFAULT_HEX_LINE_LENGTH; } QString QHexRenderer::hexString(quint64 line, QByteArray *rawline) const { QByteArray lrawline = this->getLine(line); if (rawline) *rawline = lrawline; return QString::fromLatin1(lrawline.toHex(' ').toUpper()) + QStringLiteral(" "); } QString QHexRenderer::asciiString(quint64 line, QByteArray *rawline) const { QByteArray lrawline = this->getLine(line); if (rawline) *rawline = lrawline; QByteArray ascii = lrawline; this->unprintableChars(ascii); return QString::fromLatin1(ascii); } QByteArray QHexRenderer::getLine(quint64 line) const { return m_document->read(line * hexLineWidth(), hexLineWidth()); } void QHexRenderer::blinkCursor() { m_cursorenabled = !m_cursorenabled; } qint64 QHexRenderer::rendererLength() const { return m_document->length() + 1; } int QHexRenderer::getAddressWidth() const { quint64 maxAddr = m_document->baseAddress() + this->rendererLength(); if (maxAddr <= 0xFFFF) return 4; if (maxAddr <= 0xFFFFFFFF) return 8; return QString::number(maxAddr, 16).length(); } int QHexRenderer::getHexColumnX() const { return this->getNCellsWidth(this->getAddressWidth()) + 2 * this->borderSize(); } int QHexRenderer::getAsciiColumnX() const { return this->getHexColumnX() + this->getNCellsWidth(hexLineWidth() * 3) + 2 * this->borderSize(); } int QHexRenderer::getEndColumnX() const { return this->getAsciiColumnX() + this->getNCellsWidth(hexLineWidth()) + 2 * this->borderSize(); } qreal QHexRenderer::getCellWidth() const { return m_fontmetrics.horizontalAdvance(QStringLiteral(" ")); } int QHexRenderer::getNCellsWidth(int n) const { return qRound(n * getCellWidth()); } void QHexRenderer::unprintableChars(QByteArray &ascii) const { for (char &ch : ascii) { if (std::isprint(static_cast(ch))) continue; ch = HEX_UNPRINTABLE_CHAR; } } void QHexRenderer::applyDocumentStyles(QPainter *painter, QTextDocument *textdocument) const { textdocument->setDocumentMargin(0); textdocument->setUndoRedoEnabled(false); textdocument->setDefaultFont(painter->font()); } void QHexRenderer::applyBasicStyle(QTextCursor &textcursor, const QByteArray &rawline, Factor factor) const { QPalette palette = qApp->palette(); QColor color = palette.color(QPalette::WindowText); if (color.lightness() < 50) { if (color == Qt::black) color = Qt::gray; else color = color.darker(); } else color = color.lighter(); QTextCharFormat charformat; charformat.setForeground(color); for (int i = 0; i < rawline.length(); i++) { if ((rawline[i] != 0x00) && (static_cast(rawline[i]) != 0xFF)) continue; textcursor.setPosition(i * factor); textcursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, factor); textcursor.setCharFormat(charformat); } } void QHexRenderer::applyMetadata(QTextCursor &textcursor, quint64 line, Factor factor) const { QHexMetadata *metadata = m_document->metadata(); if (!metadata->hasMetadata(line)) return; const QHexLineMetadata &linemetadata = metadata->get(line); for (const QHexMetadataItem &mi : linemetadata) { QTextCharFormat charformat; if (mi.background.isValid()) charformat.setBackground(mi.background); if (mi.foreground.isValid()) charformat.setForeground(mi.foreground); if (!mi.comment.isEmpty()) charformat.setUnderlineStyle(QTextCharFormat::SingleUnderline); textcursor.setPosition(mi.start * factor); textcursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, (mi.length * factor) - (factor > 1 ? 1 : 0)); textcursor.setCharFormat(charformat); } } void QHexRenderer::applySelection(QTextCursor &textcursor, quint64 line, Factor factor) const { QHexCursor *cursor = m_document->cursor(); if (!cursor->isLineSelected(line)) return; const QHexPosition &startsel = cursor->selectionStart(); const QHexPosition &endsel = cursor->selectionEnd(); if (startsel.line == endsel.line) { textcursor.setPosition(startsel.column * factor); textcursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, ((endsel.column - startsel.column + 1) * factor)); } else { if (line == startsel.line) textcursor.setPosition(startsel.column * factor); else textcursor.setPosition(0); if (line == endsel.line) textcursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, ((endsel.column + 1) * factor)); else textcursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); } if (factor == Hex) textcursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1); QPalette palette = qApp->palette(); QTextCharFormat charformat; charformat.setBackground(palette.color(QPalette::Highlight)); charformat.setForeground(palette.color(QPalette::HighlightedText)); textcursor.setCharFormat(charformat); } void QHexRenderer::applyCursorAscii(QTextCursor &textcursor, quint64 line) const { QHexCursor *cursor = m_document->cursor(); if ((line != cursor->currentLine()) || !m_cursorenabled) return; textcursor.clearSelection(); textcursor.setPosition(m_document->cursor()->currentColumn()); textcursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); QPalette palette = qApp->palette(); QTextCharFormat charformat; if ((cursor->insertionMode() == QHexCursor::OverwriteMode) || (m_selectedarea != QHexRenderer::AsciiArea)) { charformat.setForeground(palette.color(QPalette::Window)); if (m_selectedarea == QHexRenderer::AsciiArea) charformat.setBackground(palette.color(QPalette::WindowText)); else charformat.setBackground(palette.color(QPalette::WindowText).lighter(250)); } else charformat.setUnderlineStyle(QTextCharFormat::UnderlineStyle::SingleUnderline); textcursor.setCharFormat(charformat); } void QHexRenderer::applyCursorHex(QTextCursor &textcursor, quint64 line) const { QHexCursor *cursor = m_document->cursor(); if ((line != cursor->currentLine()) || !m_cursorenabled) return; textcursor.clearSelection(); textcursor.setPosition(m_document->cursor()->currentColumn() * 3); if ((m_selectedarea == QHexRenderer::HexArea) && !m_document->cursor()->currentNibble()) textcursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor); textcursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); if (m_selectedarea == QHexRenderer::AsciiArea) textcursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); QPalette palette = qApp->palette(); QTextCharFormat charformat; if ((cursor->insertionMode() == QHexCursor::OverwriteMode) || (m_selectedarea != QHexRenderer::HexArea)) { charformat.setForeground(palette.color(QPalette::Window)); if (m_selectedarea == QHexRenderer::HexArea) charformat.setBackground(palette.color(QPalette::WindowText)); else charformat.setBackground(palette.color(QPalette::WindowText).lighter(250)); } else charformat.setUnderlineStyle(QTextCharFormat::UnderlineStyle::SingleUnderline); textcursor.setCharFormat(charformat); } void QHexRenderer::drawAddress(QPainter *painter, const QPalette &palette, const QRect &linerect, quint64 line) { quint64 addr = line * hexLineWidth() + m_document->baseAddress(); QString addrStr = QString::number(addr, 16).rightJustified(this->getAddressWidth(), QLatin1Char('0')).toUpper(); QRect addressrect = linerect; addressrect.setWidth(this->getHexColumnX()); painter->save(); painter->setPen(palette.color(QPalette::Highlight)); painter->drawText(addressrect, Qt::AlignHCenter | Qt::AlignVCenter, addrStr); painter->restore(); } void QHexRenderer::drawHex(QPainter *painter, const QPalette &palette, const QRect &linerect, quint64 line) { Q_UNUSED(palette) QTextDocument textdocument; QTextCursor textcursor(&textdocument); QByteArray rawline; textcursor.insertText(this->hexString(line, &rawline)); if (line == this->documentLastLine()) textcursor.insertText(QStringLiteral(" ")); QRect hexrect = linerect; hexrect.setX(this->getHexColumnX() + this->borderSize()); this->applyDocumentStyles(painter, &textdocument); this->applyBasicStyle(textcursor, rawline, Hex); this->applyMetadata(textcursor, line, Hex); this->applySelection(textcursor, line, Hex); this->applyCursorHex(textcursor, line); painter->save(); painter->translate(hexrect.topLeft()); textdocument.drawContents(painter); painter->restore(); } void QHexRenderer::drawAscii(QPainter *painter, const QPalette &palette, const QRect &linerect, quint64 line) { Q_UNUSED(palette) QTextDocument textdocument; QTextCursor textcursor(&textdocument); QByteArray rawline; textcursor.insertText(this->asciiString(line, &rawline)); if (line == this->documentLastLine()) textcursor.insertText(QStringLiteral(" ")); QRect asciirect = linerect; asciirect.setX(this->getAsciiColumnX() + this->borderSize()); this->applyDocumentStyles(painter, &textdocument); this->applyBasicStyle(textcursor, rawline, Ascii); this->applyMetadata(textcursor, line, Ascii); this->applySelection(textcursor, line, Ascii); this->applyCursorAscii(textcursor, line); painter->save(); painter->translate(asciirect.topLeft()); textdocument.drawContents(painter); painter->restore(); } void QHexRenderer::drawHeader(QPainter *painter, const QPalette &palette) { QRect rect = QRect(0, 0, this->getEndColumnX(), this->headerLineCount() * this->lineHeight()); QString hexheader; for (quint8 i = 0; i < this->hexLineWidth(); i++) hexheader.append(QStringLiteral("%1 ").arg(QString::number(i, 16).rightJustified(2, QLatin1Char('0'))).toUpper()); QRect addressrect = rect; addressrect.setWidth(this->getHexColumnX()); QRect hexrect = rect; hexrect.setX(this->getHexColumnX() + this->borderSize()); hexrect.setWidth(this->getNCellsWidth(hexLineWidth() * 3)); QRect asciirect = rect; asciirect.setX(this->getAsciiColumnX()); asciirect.setWidth(this->getEndColumnX() - this->getAsciiColumnX()); painter->save(); painter->setPen(palette.color(QPalette::Highlight)); painter->drawText(addressrect, Qt::AlignHCenter | Qt::AlignVCenter, QStringLiteral("Offset")); // align left for maximum consistency with drawHex() which prints from the left. // so hex and positions are aligned vertically painter->drawText(hexrect, Qt::AlignLeft | Qt::AlignVCenter, hexheader); painter->drawText(asciirect, Qt::AlignHCenter | Qt::AlignVCenter, QStringLiteral("Ascii")); painter->restore(); }