diff --git a/parts/CMakeLists.txt b/parts/CMakeLists.txt index 4b1d84a..88d7b21 100644 --- a/parts/CMakeLists.txt +++ b/parts/CMakeLists.txt @@ -2,4 +2,5 @@ # SPDX-License-Identifier: CC0-1.0 add_subdirectory(exd) -add_subdirectory(mdl) \ No newline at end of file +add_subdirectory(hex) +add_subdirectory(mdl) diff --git a/parts/hex/CMakeLists.txt b/parts/hex/CMakeLists.txt new file mode 100644 index 0000000..741a01e --- /dev/null +++ b/parts/hex/CMakeLists.txt @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2023 Joshua Goins +# SPDX-License-Identifier: CC0-1.0 + +add_library(hexpart STATIC) +target_sources(hexpart PRIVATE + document/buffer/qhexbuffer.cpp + document/buffer/qhexbuffer.h + document/buffer/qmemorybuffer.cpp + document/buffer/qmemorybuffer.h + document/buffer/qmemoryrefbuffer.cpp + document/buffer/qmemoryrefbuffer.h + document/qhexcursor.cpp + document/qhexcursor.h + document/qhexdocument.cpp + document/qhexdocument.h + document/qhexmetadata.cpp + document/qhexmetadata.h + document/qhexrenderer.cpp + document/qhexrenderer.h + hexpart.cpp + hexpart.h + qhexview.cpp + qhexview.h) +target_link_libraries(hexpart PUBLIC physis Qt6::Core Qt6::Widgets) +target_include_directories(hexpart PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/parts/hex/document/buffer/qhexbuffer.cpp b/parts/hex/document/buffer/qhexbuffer.cpp new file mode 100644 index 0000000..262bf40 --- /dev/null +++ b/parts/hex/document/buffer/qhexbuffer.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#include "qhexbuffer.h" +#include + +QHexBuffer::QHexBuffer(QObject *parent) + : QObject(parent) +{ +} + +uchar QHexBuffer::at(qint64 idx) +{ + return this->read(idx, 1)[0]; +} +bool QHexBuffer::isEmpty() const +{ + return this->length() <= 0; +} + +void QHexBuffer::replace(qint64 offset, const QByteArray &data) +{ + this->remove(offset, data.length()); + this->insert(offset, data); +} + +void QHexBuffer::read(char *data, int size) +{ + QBuffer *buffer = new QBuffer(this); + buffer->setData(data, size); + + if (!buffer->isOpen()) + buffer->open(QBuffer::ReadWrite); + + this->read(buffer); +} + +void QHexBuffer::read(const QByteArray &ba) +{ + QBuffer *buffer = new QBuffer(this); + + buffer->setData(ba); + if (!buffer->isOpen()) + buffer->open(QBuffer::ReadWrite); + + this->read(buffer); +} diff --git a/parts/hex/document/buffer/qhexbuffer.h b/parts/hex/document/buffer/qhexbuffer.h new file mode 100644 index 0000000..9f89c46 --- /dev/null +++ b/parts/hex/document/buffer/qhexbuffer.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#ifndef QHEXBUFFER_H +#define QHEXBUFFER_H + +#include +#include + +class QHexBuffer : public QObject +{ + Q_OBJECT + +public: + explicit QHexBuffer(QObject *parent = nullptr); + bool isEmpty() const; + +public: + virtual uchar at(qint64 idx); + virtual void replace(qint64 offset, const QByteArray &data); + virtual void read(char *data, int size); + virtual void read(const QByteArray &ba); + +public: + virtual qint64 length() const = 0; + virtual void insert(qint64 offset, const QByteArray &data) = 0; + virtual void remove(qint64 offset, int length) = 0; + virtual QByteArray read(qint64 offset, int length) = 0; + virtual bool read(QIODevice *iodevice) = 0; + virtual void write(QIODevice *iodevice) = 0; + + virtual qint64 indexOf(const QByteArray &ba, qint64 from) = 0; + virtual qint64 lastIndexOf(const QByteArray &ba, qint64 from) = 0; +}; + +#endif // QHEXBUFFER_H diff --git a/parts/hex/document/buffer/qmemorybuffer.cpp b/parts/hex/document/buffer/qmemorybuffer.cpp new file mode 100644 index 0000000..8a45abe --- /dev/null +++ b/parts/hex/document/buffer/qmemorybuffer.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#include "qmemorybuffer.h" + +QMemoryBuffer::QMemoryBuffer(QObject *parent) + : QHexBuffer(parent) +{ +} +uchar QMemoryBuffer::at(qint64 idx) +{ + return static_cast(m_buffer.at(idx)); +} +qint64 QMemoryBuffer::length() const +{ + return static_cast(m_buffer.length()); +} +void QMemoryBuffer::insert(qint64 offset, const QByteArray &data) +{ + m_buffer.insert(static_cast(offset), data); +} +void QMemoryBuffer::remove(qint64 offset, int length) +{ + m_buffer.remove(static_cast(offset), length); +} +QByteArray QMemoryBuffer::read(qint64 offset, int length) +{ + return m_buffer.mid(static_cast(offset), length); +} + +bool QMemoryBuffer::read(QIODevice *device) +{ + m_buffer = device->readAll(); + return true; +} +void QMemoryBuffer::write(QIODevice *device) +{ + device->write(m_buffer); +} + +qint64 QMemoryBuffer::indexOf(const QByteArray &ba, qint64 from) +{ + return m_buffer.indexOf(ba, static_cast(from)); +} +qint64 QMemoryBuffer::lastIndexOf(const QByteArray &ba, qint64 from) +{ + return m_buffer.lastIndexOf(ba, static_cast(from)); +} diff --git a/parts/hex/document/buffer/qmemorybuffer.h b/parts/hex/document/buffer/qmemorybuffer.h new file mode 100644 index 0000000..405db6b --- /dev/null +++ b/parts/hex/document/buffer/qmemorybuffer.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#ifndef QMEMORYBUFFER_H +#define QMEMORYBUFFER_H + +#include "qhexbuffer.h" + +class QMemoryBuffer : public QHexBuffer +{ + Q_OBJECT + +public: + explicit QMemoryBuffer(QObject *parent = nullptr); + uchar at(qint64 idx) override; + qint64 length() const override; + void insert(qint64 offset, const QByteArray &data) override; + void remove(qint64 offset, int length) override; + QByteArray read(qint64 offset, int length) override; + bool read(QIODevice *device) override; + void write(QIODevice *device) override; + + qint64 indexOf(const QByteArray &ba, qint64 from) override; + qint64 lastIndexOf(const QByteArray &ba, qint64 from) override; + +private: + QByteArray m_buffer; +}; + +#endif // QMEMORYBUFFER_H diff --git a/parts/hex/document/buffer/qmemoryrefbuffer.cpp b/parts/hex/document/buffer/qmemoryrefbuffer.cpp new file mode 100644 index 0000000..b40b434 --- /dev/null +++ b/parts/hex/document/buffer/qmemoryrefbuffer.cpp @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#include "qmemoryrefbuffer.h" + +#include +#include + +QMemoryRefBuffer::QMemoryRefBuffer(QObject *parent) + : QHexBuffer(parent) +{ +} +qint64 QMemoryRefBuffer::length() const +{ + return m_buffer->size(); +} +void QMemoryRefBuffer::insert(qint64 offset, const QByteArray &data) +{ + Q_UNUSED(offset) + Q_UNUSED(data) + /* Insertion unsupported */ +} +void QMemoryRefBuffer::remove(qint64 offset, int length) +{ + Q_UNUSED(offset) + Q_UNUSED(length) + /* Deletion unsupported */ +} + +QByteArray QMemoryRefBuffer::read(qint64 offset, int length) +{ + m_buffer->seek(offset); + return m_buffer->read(length); +} + +bool QMemoryRefBuffer::read(QIODevice *device) +{ + m_buffer = qobject_cast(device); + if (m_buffer) { + m_buffer->setParent(this); + return true; + } + return false; +} + +void QMemoryRefBuffer::write(QIODevice *device) +{ + m_buffer->seek(0); + if (m_buffer->size() < INT_MAX) { + device->write(m_buffer->readAll()); + } else { + while (m_buffer->pos() < m_buffer->size()) { + char tmpBuf[4096]; + qint64 chunkLen = m_buffer->read(tmpBuf, 4096); + if (chunkLen == -1) + break; + if (chunkLen > 0) { + device->write(tmpBuf, chunkLen); + m_buffer->seek(m_buffer->pos() + chunkLen); + } + } + } +} + +qint64 QMemoryRefBuffer::indexOf(const QByteArray &ba, qint64 from) +{ + qint64 findPos = -1; + if (from < m_buffer->size()) { + findPos = from; + m_buffer->seek(from); + + while (findPos < m_buffer->size()) { + QByteArray data = m_buffer->read(INT_MAX); + int idx = data.indexOf(ba); + if (idx >= 0) { + findPos += idx; + break; + } + if (findPos + data.size() >= m_buffer->size()) + return -1; + m_buffer->seek(m_buffer->pos() + data.size() - ba.size()); + } + } + return findPos; +} + +qint64 QMemoryRefBuffer::lastIndexOf(const QByteArray &ba, qint64 from) +{ + qint64 findPos = -1; + if (from >= 0 && ba.size() < INT_MAX) { + qint64 currPos = from; + while (currPos >= 0) { + qint64 readPos = (currPos < INT_MAX) ? 0 : currPos - INT_MAX; + m_buffer->seek(readPos); + QByteArray data = m_buffer->read(currPos - readPos); + int idx = data.lastIndexOf(ba, from); + if (idx >= 0) { + findPos = readPos + idx; + break; + } + if (readPos <= 0) + break; + currPos = readPos + ba.size(); + } + } + return findPos; +} diff --git a/parts/hex/document/buffer/qmemoryrefbuffer.h b/parts/hex/document/buffer/qmemoryrefbuffer.h new file mode 100644 index 0000000..818ea3d --- /dev/null +++ b/parts/hex/document/buffer/qmemoryrefbuffer.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#ifndef QMEMORYREFBUFFER_H +#define QMEMORYREFBUFFER_H + +#include "qbuffer.h" +#include "qhexbuffer.h" + +class QMemoryRefBuffer : public QHexBuffer +{ + Q_OBJECT + +public: + explicit QMemoryRefBuffer(QObject *parent = nullptr); + qint64 length() const override; + void insert(qint64 offset, const QByteArray &data) override; + void remove(qint64 offset, int length) override; + QByteArray read(qint64 offset, int length) override; + bool read(QIODevice *device) override; + void write(QIODevice *device) override; + + qint64 indexOf(const QByteArray &ba, qint64 from) override; + qint64 lastIndexOf(const QByteArray &ba, qint64 from) override; + +private: + QBuffer *m_buffer; +}; + +#endif // QMEMORYREFBUFFER_H diff --git a/parts/hex/document/qhexcursor.cpp b/parts/hex/document/qhexcursor.cpp new file mode 100644 index 0000000..0bf163e --- /dev/null +++ b/parts/hex/document/qhexcursor.cpp @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#include "qhexcursor.h" +#include + +QHexCursor::QHexCursor(QObject *parent) + : QObject(parent) + , m_insertionmode(QHexCursor::OverwriteMode) +{ + m_position.line = m_position.column = 0; + m_position.line = m_position.column = 0; + + m_selection.line = m_selection.column = 0; + m_selection.line = m_selection.column = 0; + + m_position.nibbleindex = m_selection.nibbleindex = 1; + setLineWidth(DEFAULT_HEX_LINE_LENGTH); +} + +const QHexPosition &QHexCursor::selectionStart() const +{ + if (m_position.line < m_selection.line) + return m_position; + + if (m_position.line == m_selection.line) { + if (m_position.column < m_selection.column) + return m_position; + } + + return m_selection; +} + +const QHexPosition &QHexCursor::selectionEnd() const +{ + if (m_position.line > m_selection.line) + return m_position; + + if (m_position.line == m_selection.line) { + if (m_position.column > m_selection.column) + return m_position; + } + + return m_selection; +} + +const QHexPosition &QHexCursor::position() const +{ + return m_position; +} +QHexCursor::InsertionMode QHexCursor::insertionMode() const +{ + return m_insertionmode; +} +int QHexCursor::selectionLength() const +{ + return this->selectionEnd() - this->selectionStart() + 1; +} +quint64 QHexCursor::currentLine() const +{ + return m_position.line; +} +int QHexCursor::currentColumn() const +{ + return m_position.column; +} +int QHexCursor::currentNibble() const +{ + return m_position.nibbleindex; +} +quint64 QHexCursor::selectionLine() const +{ + return m_selection.line; +} +int QHexCursor::selectionColumn() const +{ + return m_selection.column; +} +int QHexCursor::selectionNibble() const +{ + return m_selection.nibbleindex; +} + +bool QHexCursor::isLineSelected(quint64 line) const +{ + if (!this->hasSelection()) + return false; + + quint64 first = std::min(m_position.line, m_selection.line); + quint64 last = std::max(m_position.line, m_selection.line); + + if ((line < first) || (line > last)) + return false; + + return true; +} + +bool QHexCursor::hasSelection() const +{ + return m_position != m_selection; +} + +void QHexCursor::clearSelection() +{ + m_selection = m_position; + Q_EMIT positionChanged(); +} + +void QHexCursor::moveTo(const QHexPosition &pos) +{ + this->moveTo(pos.line, pos.column, pos.nibbleindex); +} +void QHexCursor::select(const QHexPosition &pos) +{ + this->select(pos.line, pos.column, pos.nibbleindex); +} + +void QHexCursor::moveTo(quint64 line, int column, int nibbleindex) +{ + m_selection.line = line; + m_selection.column = column; + m_selection.nibbleindex = nibbleindex; + + this->select(line, column, nibbleindex); +} + +void QHexCursor::select(quint64 line, int column, int nibbleindex) +{ + m_position.line = line; + m_position.column = column; + m_position.nibbleindex = nibbleindex; + + Q_EMIT positionChanged(); +} + +void QHexCursor::moveTo(qint64 offset) +{ + quint64 line = offset / m_lineWidth; + this->moveTo(line, offset - (line * m_lineWidth)); +} + +void QHexCursor::select(int length) +{ + this->select(m_position.line, std::min(m_lineWidth - 1, m_position.column + length - 1)); +} + +void QHexCursor::selectOffset(qint64 offset, int length) +{ + this->moveTo(offset); + this->select(length); +} + +void QHexCursor::setInsertionMode(QHexCursor::InsertionMode mode) +{ + bool differentmode = (m_insertionmode != mode); + m_insertionmode = mode; + + if (differentmode) + Q_EMIT insertionModeChanged(); +} + +void QHexCursor::setLineWidth(quint8 width) +{ + m_lineWidth = width; + m_position.lineWidth = width; + m_selection.lineWidth = width; +} + +void QHexCursor::switchInsertionMode() +{ + if (m_insertionmode == QHexCursor::OverwriteMode) + m_insertionmode = QHexCursor::InsertMode; + else + m_insertionmode = QHexCursor::OverwriteMode; + + Q_EMIT insertionModeChanged(); +} diff --git a/parts/hex/document/qhexcursor.h b/parts/hex/document/qhexcursor.h new file mode 100644 index 0000000..2a2a413 --- /dev/null +++ b/parts/hex/document/qhexcursor.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#ifndef QHEXCURSOR_H +#define QHEXCURSOR_H + +#include + +#define DEFAULT_HEX_LINE_LENGTH 0x10 +#define DEFAULT_AREA_IDENTATION 0x01 + +struct QHexPosition { + quint64 line; + int column; + quint8 lineWidth; + int nibbleindex; + + QHexPosition() = default; + inline qint64 offset() const + { + return static_cast(line * lineWidth) + column; + } + inline int operator-(const QHexPosition &rhs) const + { + return this->offset() - rhs.offset(); + } + inline bool operator==(const QHexPosition &rhs) const + { + return (line == rhs.line) && (column == rhs.column) && (nibbleindex == rhs.nibbleindex); + } + inline bool operator!=(const QHexPosition &rhs) const + { + return (line != rhs.line) || (column != rhs.column) || (nibbleindex != rhs.nibbleindex); + } +}; + +class QHexCursor : public QObject +{ + Q_OBJECT + +public: + enum InsertionMode { OverwriteMode, InsertMode }; + +public: + explicit QHexCursor(QObject *parent = nullptr); + +public: + const QHexPosition &selectionStart() const; + const QHexPosition &selectionEnd() const; + const QHexPosition &position() const; + InsertionMode insertionMode() const; + int selectionLength() const; + quint64 currentLine() const; + int currentColumn() const; + int currentNibble() const; + quint64 selectionLine() const; + int selectionColumn() const; + int selectionNibble() const; + bool atEnd() const; + bool isLineSelected(quint64 line) const; + bool hasSelection() const; + void clearSelection(); + +public: + void moveTo(const QHexPosition &pos); + void moveTo(quint64 line, int column, int nibbleindex = 1); + void moveTo(qint64 offset); + void select(const QHexPosition &pos); + void select(quint64 line, int column, int nibbleindex = 1); + void select(int length); + void selectOffset(qint64 offset, int length); + void setInsertionMode(InsertionMode mode); + void setLineWidth(quint8 width); + void switchInsertionMode(); + +Q_SIGNALS: + void positionChanged(); + void insertionModeChanged(); + +private: + InsertionMode m_insertionmode; + quint8 m_lineWidth; + QHexPosition m_position, m_selection; +}; + +#endif // QHEXCURSOR_H diff --git a/parts/hex/document/qhexdocument.cpp b/parts/hex/document/qhexdocument.cpp new file mode 100644 index 0000000..2b6ca48 --- /dev/null +++ b/parts/hex/document/qhexdocument.cpp @@ -0,0 +1,242 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#include "qhexdocument.h" +#include +#include +#include +#include + +QHexDocument::QHexDocument(QHexBuffer *buffer, QObject *parent) + : QObject(parent) + , m_baseaddress(0) +{ + m_buffer = buffer; + m_buffer->setParent(this); // Take Ownership + m_areaindent = DEFAULT_AREA_IDENTATION; + m_hexlinewidth = DEFAULT_HEX_LINE_LENGTH; + + m_cursor = new QHexCursor(this); + m_cursor->setLineWidth(m_hexlinewidth); + m_metadata = new QHexMetadata(this); + m_metadata->setLineWidth(m_hexlinewidth); + + connect(m_metadata, &QHexMetadata::metadataChanged, this, &QHexDocument::lineChanged); + connect(m_metadata, &QHexMetadata::metadataCleared, this, &QHexDocument::documentChanged); + + connect(&m_undostack, &QUndoStack::canUndoChanged, this, &QHexDocument::canUndoChanged); + connect(&m_undostack, &QUndoStack::canRedoChanged, this, &QHexDocument::canRedoChanged); +} + +bool QHexDocument::isEmpty() const +{ + return m_buffer->isEmpty(); +} +bool QHexDocument::atEnd() const +{ + return m_cursor->position().offset() >= m_buffer->length(); +} +bool QHexDocument::canUndo() const +{ + return m_undostack.canUndo(); +} +bool QHexDocument::canRedo() const +{ + return m_undostack.canRedo(); +} +qint64 QHexDocument::length() const +{ + return m_buffer->length(); +} +quint64 QHexDocument::baseAddress() const +{ + return m_baseaddress; +} +QHexCursor *QHexDocument::cursor() const +{ + return m_cursor; +} + +int QHexDocument::areaIndent() const +{ + return m_areaindent; +} +void QHexDocument::setAreaIndent(quint8 value) +{ + m_areaindent = value; +} +int QHexDocument::hexLineWidth() const +{ + return m_hexlinewidth; +} +void QHexDocument::setHexLineWidth(quint8 value) +{ + m_hexlinewidth = value; + m_cursor->setLineWidth(value); + m_metadata->setLineWidth(value); +} + +QHexMetadata *QHexDocument::metadata() const +{ + return m_metadata; +} +QByteArray QHexDocument::read(qint64 offset, int len) +{ + return m_buffer->read(offset, len); +} + +void QHexDocument::removeSelection() +{ + if (!m_cursor->hasSelection()) + return; + + this->remove(m_cursor->selectionStart().offset(), m_cursor->selectionLength()); + m_cursor->clearSelection(); +} + +QByteArray QHexDocument::selectedBytes() const +{ + if (!m_cursor->hasSelection()) + return QByteArray(); + + return m_buffer->read(m_cursor->selectionStart().offset(), m_cursor->selectionLength()); +} + +char QHexDocument::at(int offset) const +{ + return m_buffer->at(offset); +} + +void QHexDocument::setBaseAddress(quint64 baseaddress) +{ + if (m_baseaddress == baseaddress) + return; + + m_baseaddress = baseaddress; + Q_EMIT documentChanged(); +} + +void QHexDocument::sync() +{ + Q_EMIT documentChanged(); +} + +void QHexDocument::undo() +{ + m_undostack.undo(); + Q_EMIT documentChanged(); +} + +void QHexDocument::redo() +{ + m_undostack.redo(); + Q_EMIT documentChanged(); +} + +void QHexDocument::cut(bool hex) +{ + if (!m_cursor->hasSelection()) + return; + + this->copy(hex); + this->removeSelection(); +} + +void QHexDocument::copy(bool hex) +{ + if (!m_cursor->hasSelection()) + return; + + QClipboard *c = qApp->clipboard(); + QByteArray bytes = this->selectedBytes(); + + if (hex) + bytes = bytes.toHex(' ').toUpper(); + + c->setText(QString::fromLatin1(bytes)); +} + +void QHexDocument::paste(bool hex) +{ + QClipboard *c = qApp->clipboard(); + QByteArray data = c->text().toUtf8(); + + if (data.isEmpty()) + return; + + this->removeSelection(); + + if (hex) + data = QByteArray::fromHex(data); + + if (m_cursor->insertionMode() == QHexCursor::InsertMode) + this->insert(m_cursor->position().offset(), data); + else + this->replace(m_cursor->position().offset(), data); +} + +void QHexDocument::insert(qint64 offset, uchar b) +{ + this->insert(offset, QByteArray(1, b)); +} + +void QHexDocument::replace(qint64 offset, uchar b) +{ + this->replace(offset, QByteArray(1, b)); +} + +void QHexDocument::insert(qint64 offset, const QByteArray &data) +{ + Q_EMIT documentChanged(); +} + +void QHexDocument::replace(qint64 offset, const QByteArray &data) +{ + Q_EMIT documentChanged(); +} + +void QHexDocument::remove(qint64 offset, int len) +{ + Q_EMIT documentChanged(); +} + +QByteArray QHexDocument::read(qint64 offset, int len) const +{ + return m_buffer->read(offset, len); +} + +bool QHexDocument::saveTo(QIODevice *device) +{ + if (!device->isWritable()) + return false; + + m_buffer->write(device); + return true; +} + +qint64 QHexDocument::searchForward(const QByteArray &ba) +{ + qint64 startPos = m_cursor->position().offset(); + qint64 findPos = m_buffer->indexOf(ba, startPos); + if (findPos > -1) { + m_cursor->clearSelection(); + m_cursor->moveTo(findPos); + m_cursor->select(ba.length()); + } + return findPos; +} + +qint64 QHexDocument::searchBackward(const QByteArray &ba) +{ + qint64 startPos = m_cursor->position().offset() - 1; + if (m_cursor->hasSelection()) { + startPos = m_cursor->selectionStart().offset() - 1; + } + qint64 findPos = m_buffer->lastIndexOf(ba, startPos); + if (findPos > -1) { + m_cursor->clearSelection(); + m_cursor->moveTo(findPos); + m_cursor->select(ba.length()); + } + return findPos; +} diff --git a/parts/hex/document/qhexdocument.h b/parts/hex/document/qhexdocument.h new file mode 100644 index 0000000..e43ee67 --- /dev/null +++ b/parts/hex/document/qhexdocument.h @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#ifndef QHEXDOCUMENT_H +#define QHEXDOCUMENT_H + +#include "buffer/qhexbuffer.h" +#include "qhexcursor.h" +#include "qhexmetadata.h" +#include +#include + +class QHexDocument : public QObject +{ + Q_OBJECT + +private: + explicit QHexDocument(QHexBuffer *buffer, QObject *parent = nullptr); + +public: + bool isEmpty() const; + bool atEnd() const; + bool canUndo() const; + bool canRedo() const; + qint64 length() const; + quint64 baseAddress() const; + QHexCursor *cursor() const; + QHexMetadata *metadata() const; + int areaIndent() const; + void setAreaIndent(quint8 value); + int hexLineWidth() const; + void setHexLineWidth(quint8 value); + +public: + void removeSelection(); + QByteArray read(qint64 offset, int len = 0); + QByteArray selectedBytes() const; + char at(int offset) const; + void setBaseAddress(quint64 baseaddress); + void sync(); + +public Q_SLOTS: + void undo(); + void redo(); + void cut(bool hex = false); + void copy(bool hex = false); + void paste(bool hex = false); + void insert(qint64 offset, uchar b); + void replace(qint64 offset, uchar b); + void insert(qint64 offset, const QByteArray &data); + void replace(qint64 offset, const QByteArray &data); + void remove(qint64 offset, int len); + QByteArray read(qint64 offset, int len) const; + bool saveTo(QIODevice *device); + + qint64 searchForward(const QByteArray &ba); + qint64 searchBackward(const QByteArray &ba); + +public: + template + static QHexDocument *fromDevice(QIODevice *iodevice, QObject *parent = nullptr); + template + static QHexDocument *fromFile(QString filename, QObject *parent = nullptr); + template + static QHexDocument *fromMemory(char *data, int size, QObject *parent = nullptr); + template + static QHexDocument *fromMemory(const QByteArray &ba, QObject *parent = nullptr); + +Q_SIGNALS: + void canUndoChanged(bool canUndo); + void canRedoChanged(bool canRedo); + void documentChanged(); + void lineChanged(quint64 line); + +private: + QHexBuffer *m_buffer; + QHexMetadata *m_metadata; + QUndoStack m_undostack; + QHexCursor *m_cursor; + quint64 m_baseaddress; + quint8 m_areaindent; + quint8 m_hexlinewidth; +}; + +template +QHexDocument *QHexDocument::fromDevice(QIODevice *iodevice, QObject *parent) +{ + bool needsclose = false; + + if (!iodevice->isOpen()) { + needsclose = true; + iodevice->open(QIODevice::ReadWrite); + } + + QHexBuffer *hexbuffer = new T(); + if (hexbuffer->read(iodevice)) { + if (needsclose) + iodevice->close(); + + return new QHexDocument(hexbuffer, parent); + } else { + delete hexbuffer; + } + + return nullptr; +} + +template +QHexDocument *QHexDocument::fromFile(QString filename, QObject *parent) +{ + QFile f(filename); + f.open(QFile::ReadOnly); + + QHexDocument *doc = QHexDocument::fromDevice(&f, parent); + f.close(); + return doc; +} + +template +QHexDocument *QHexDocument::fromMemory(char *data, int size, QObject *parent) +{ + QHexBuffer *hexbuffer = new T(); + hexbuffer->read(data, size); + return new QHexDocument(hexbuffer, parent); +} + +template +QHexDocument *QHexDocument::fromMemory(const QByteArray &ba, QObject *parent) +{ + QHexBuffer *hexbuffer = new T(); + hexbuffer->read(ba); + return new QHexDocument(hexbuffer, parent); +} + +#endif // QHEXEDITDATA_H diff --git a/parts/hex/document/qhexmetadata.cpp b/parts/hex/document/qhexmetadata.cpp new file mode 100644 index 0000000..95fcebc --- /dev/null +++ b/parts/hex/document/qhexmetadata.cpp @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#include "qhexmetadata.h" + +QHexMetadata::QHexMetadata(QObject *parent) + : QObject(parent) +{ +} + +const QHexLineMetadata &QHexMetadata::get(quint64 line) const +{ + auto it = m_metadata.find(line); + return it.value(); +} + +QString QHexMetadata::comments(quint64 line, int column) const +{ + if (!this->hasMetadata(line)) + return QString(); + + QString s; + + const auto &linemetadata = this->get(line); + + for (auto &mi : linemetadata) { + if (!(mi.start <= column && column < mi.start + mi.length)) + continue; + if (mi.comment.isEmpty()) + continue; + + if (!s.isEmpty()) + s += QStringLiteral("\n"); + + s += mi.comment; + } + + return s; +} + +bool QHexMetadata::hasMetadata(quint64 line) const +{ + return m_metadata.contains(line); +} + +void QHexMetadata::clear(quint64 line) +{ + auto it = m_metadata.find(line); + + if (it == m_metadata.end()) + return; + + m_metadata.erase(it); + Q_EMIT metadataChanged(line); +} + +void QHexMetadata::clear() +{ + m_absoluteMetadata.clear(); + m_metadata.clear(); + Q_EMIT metadataCleared(); +} + +void QHexMetadata::metadata(qint64 begin, qint64 end, const QColor &fgcolor, const QColor &bgcolor, const QString &comment) +{ + m_absoluteMetadata.append({begin, end, fgcolor, bgcolor, comment}); + setAbsoluteMetadata(m_absoluteMetadata.back()); +} + +void QHexMetadata::setAbsoluteMetadata(const QHexMetadataAbsoluteItem &mai) +{ + const quint64 firstRow = mai.begin / m_lineWidth; + const quint64 lastRow = mai.end / m_lineWidth; + + for (quint64 row = firstRow; row <= lastRow; ++row) { + int start, length; + if (row == firstRow) { + start = mai.begin % m_lineWidth; + } else { + start = 0; + } + if (row == lastRow) { + const int lastChar = mai.end % m_lineWidth; + length = lastChar - start; + } else { + length = m_lineWidth; + } + if (length > 0) { + setMetadata({row, start, length, mai.foreground, mai.background, mai.comment}); + } + } +} + +void QHexMetadata::setLineWidth(quint8 width) +{ + if (width != m_lineWidth) { + m_lineWidth = width; + // clean m_metadata + m_metadata.clear(); + // and regenerate with new line width size + for (int i = 0; i < m_absoluteMetadata.size(); ++i) { + setAbsoluteMetadata(m_absoluteMetadata[i]); + } + } +} + +void QHexMetadata::metadata(quint64 line, int start, int length, const QColor &fgcolor, const QColor &bgcolor, const QString &comment) +{ + const qint64 begin = line * m_lineWidth + start; + const qint64 end = begin + length; + // delegate to the new interface + this->metadata(begin, end, fgcolor, bgcolor, comment); +} + +void QHexMetadata::color(quint64 line, int start, int length, const QColor &fgcolor, const QColor &bgcolor) +{ + this->metadata(line, start, length, fgcolor, bgcolor, QString()); +} + +void QHexMetadata::foreground(quint64 line, int start, int length, const QColor &fgcolor) +{ + this->color(line, start, length, fgcolor, QColor()); +} + +void QHexMetadata::background(quint64 line, int start, int length, const QColor &bgcolor) +{ + this->color(line, start, length, QColor(), bgcolor); +} + +void QHexMetadata::comment(quint64 line, int start, int length, const QString &comment) +{ + this->metadata(line, start, length, QColor(), QColor(), comment); +} + +void QHexMetadata::setMetadata(const QHexMetadataItem &mi) +{ + if (!m_metadata.contains(mi.line)) { + QHexLineMetadata linemetadata; + linemetadata.push_back(mi); + m_metadata[mi.line] = linemetadata; + } else { + QHexLineMetadata &linemetadata = m_metadata[mi.line]; + linemetadata.push_back(mi); + } + + Q_EMIT metadataChanged(mi.line); +} diff --git a/parts/hex/document/qhexmetadata.h b/parts/hex/document/qhexmetadata.h new file mode 100644 index 0000000..1bee0e7 --- /dev/null +++ b/parts/hex/document/qhexmetadata.h @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#ifndef QHEXMETADATA_H +#define QHEXMETADATA_H + +#include +#include +#include +#include +#include +#include + +struct QHexMetadataAbsoluteItem { + qint64 begin; + qint64 end; + QColor foreground, background; + QString comment; +}; + +struct QHexMetadataItem { + quint64 line; + int start, length; + QColor foreground, background; + QString comment; +}; + +typedef std::list QHexLineMetadata; + +class QHexMetadata : public QObject +{ + Q_OBJECT + +public: + explicit QHexMetadata(QObject *parent = nullptr); + const QHexLineMetadata &get(quint64 line) const; + QString comments(quint64 line, int column) const; + bool hasMetadata(quint64 line) const; + + void clear(quint64 line); // this is transient till next call to setLineWidth() + + void clear(); + void setLineWidth(quint8 width); + +public: + // new interface with begin, end + void metadata(qint64 begin, qint64 end, const QColor &fgcolor, const QColor &bgcolor, const QString &comment); + + // old interface with line, start, length + void metadata(quint64 line, int start, int length, const QColor &fgcolor, const QColor &bgcolor, const QString &comment); + void color(quint64 line, int start, int length, const QColor &fgcolor, const QColor &bgcolor); + void foreground(quint64 line, int start, int length, const QColor &fgcolor); + void background(quint64 line, int start, int length, const QColor &bgcolor); + void comment(quint64 line, int start, int length, const QString &comment); + +private: + void setMetadata(const QHexMetadataItem &mi); + void setAbsoluteMetadata(const QHexMetadataAbsoluteItem &mi); + +Q_SIGNALS: + void metadataChanged(quint64 line); + void metadataCleared(); + +private: + quint8 m_lineWidth; + QHash m_metadata; + QVector m_absoluteMetadata; +}; + +#endif // QHEXMETADATA_H diff --git a/parts/hex/document/qhexrenderer.cpp b/parts/hex/document/qhexrenderer.cpp new file mode 100644 index 0000000..2471033 --- /dev/null +++ b/parts/hex/document/qhexrenderer.cpp @@ -0,0 +1,500 @@ +// 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(); +} diff --git a/parts/hex/document/qhexrenderer.h b/parts/hex/document/qhexrenderer.h new file mode 100644 index 0000000..98a1174 --- /dev/null +++ b/parts/hex/document/qhexrenderer.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#ifndef QHEXRENDERER_H +#define QHEXRENDERER_H + +/* + * Nibble encoding: + * AB -> [A][B] + * Nibble Index: 1 0 + */ + +#include "qhexdocument.h" +#include +#include + +class QHexRenderer : public QObject +{ + Q_OBJECT + +public: + enum { HeaderArea, AddressArea, HexArea, AsciiArea, ExtraArea }; + +public: + explicit QHexRenderer(QHexDocument *document, const QFontMetricsF &fontmetrics, QObject *parent = nullptr); + void renderFrame(QPainter *painter); + void render(QPainter *painter, quint64 start, quint64 end, quint64 firstline); // begin included, end excluded + void updateMetrics(const QFontMetricsF &fm); + void enableCursor(bool b = true); + void selectArea(const QPoint &pt); + +public: + void blinkCursor(); + bool hitTest(const QPoint &pt, QHexPosition *position, quint64 firstline) const; + int hitTestArea(const QPoint &pt) const; + int selectedArea() const; + bool editableArea(int area) const; + quint64 documentLastLine() const; + int documentLastColumn() const; + quint64 documentLines() const; + int documentWidth() const; + int lineHeight() const; + QRect getLineRect(quint64 line, quint64 firstline) const; + int headerLineCount() const; + int borderSize() const; + int hexLineWidth() const; + +private: + QString hexString(quint64 line, QByteArray *rawline = nullptr) const; + QString asciiString(quint64 line, QByteArray *rawline = nullptr) const; + QByteArray getLine(quint64 line) const; + qint64 rendererLength() const; + int getAddressWidth() const; + int getHexColumnX() const; + int getAsciiColumnX() const; + int getEndColumnX() const; + qreal getCellWidth() const; + int getNCellsWidth(int n) const; + void unprintableChars(QByteArray &ascii) const; + +private: + enum Factor { Ascii = 1, Hex = 3 }; + + void applyDocumentStyles(QPainter *painter, QTextDocument *textdocument) const; + void applyBasicStyle(QTextCursor &textcursor, const QByteArray &rawline, Factor factor) const; + void applyMetadata(QTextCursor &textcursor, quint64 line, Factor factor) const; + void applySelection(QTextCursor &textcursor, quint64 line, Factor factor) const; + void applyCursorAscii(QTextCursor &textcursor, quint64 line) const; + void applyCursorHex(QTextCursor &textcursor, quint64 line) const; + void drawAddress(QPainter *painter, const QPalette &palette, const QRect &linerect, quint64 line); + void drawHex(QPainter *painter, const QPalette &palette, const QRect &linerect, quint64 line); + void drawAscii(QPainter *painter, const QPalette &palette, const QRect &linerect, quint64 line); + void drawHeader(QPainter *painter, const QPalette &palette); + +private: + QHexDocument *m_document; + QFontMetricsF m_fontmetrics; + int m_selectedarea; + bool m_cursorenabled; +}; + +#endif // QHEXRENDERER_H diff --git a/parts/hex/hexpart.cpp b/parts/hex/hexpart.cpp new file mode 100644 index 0000000..f2cf356 --- /dev/null +++ b/parts/hex/hexpart.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#include "hexpart.h" + +#include "document/buffer/qmemoryrefbuffer.h" + +HexPart::HexPart(QWidget *parent) + : QHexView(parent) +{ +} + +void HexPart::loadFile(physis_Buffer buffer) +{ + setDocument(QHexDocument::fromMemory(reinterpret_cast(buffer.data), buffer.size)); + setReadOnly(true); +} diff --git a/parts/hex/hexpart.h b/parts/hex/hexpart.h new file mode 100644 index 0000000..b818640 --- /dev/null +++ b/parts/hex/hexpart.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "physis.hpp" +#include "qhexview.h" + +class HexPart : public QHexView +{ +public: + explicit HexPart(QWidget *parent = nullptr); + + void loadFile(physis_Buffer buffer); +}; \ No newline at end of file diff --git a/parts/hex/qhexview.cpp b/parts/hex/qhexview.cpp new file mode 100644 index 0000000..76c9890 --- /dev/null +++ b/parts/hex/qhexview.cpp @@ -0,0 +1,649 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#include "qhexview.h" +#include "document/buffer/qmemorybuffer.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define CURSOR_BLINK_INTERVAL 500 // ms +#define DOCUMENT_WHEEL_LINES 3 + +QHexView::QHexView(QWidget *parent) + : QAbstractScrollArea(parent) + , m_document(nullptr) + , m_renderer(nullptr) + , m_readonly(false) +{ + QFont f = QFontDatabase::systemFont(QFontDatabase::FixedFont); + + if (f.styleHint() != QFont::TypeWriter) { + f.setFamily(QStringLiteral("Monospace")); // Force Monospaced font + f.setStyleHint(QFont::TypeWriter); + } + + this->setFont(f); + this->setFocusPolicy(Qt::StrongFocus); + this->setMouseTracking(true); + this->verticalScrollBar()->setSingleStep(1); + this->verticalScrollBar()->setPageStep(1); + + m_blinktimer = new QTimer(this); + m_blinktimer->setInterval(CURSOR_BLINK_INTERVAL); + + connect(m_blinktimer, &QTimer::timeout, this, &QHexView::blinkCursor); + + this->setDocument(QHexDocument::fromMemory(QByteArray(), this)); +} + +QHexDocument *QHexView::document() +{ + return m_document; +} + +void QHexView::setDocument(QHexDocument *document) +{ + if (m_renderer) + m_renderer->deleteLater(); + + if (m_document) + m_document->deleteLater(); + + m_document = document; + m_renderer = new QHexRenderer(m_document, this->fontMetrics(), this); + + connect(m_document, &QHexDocument::documentChanged, this, [&]() { + this->adjustScrollBars(); + this->viewport()->update(); + }); + + connect(m_document->cursor(), &QHexCursor::positionChanged, this, &QHexView::moveToSelection); + connect(m_document->cursor(), &QHexCursor::insertionModeChanged, this, &QHexView::renderCurrentLine); + + this->adjustScrollBars(); + this->viewport()->update(); +} + +void QHexView::setReadOnly(bool b) +{ + m_readonly = b; + if (m_document) + m_document->cursor()->setInsertionMode(QHexCursor::OverwriteMode); +} + +bool QHexView::event(QEvent *e) +{ + if (m_renderer && (e->type() == QEvent::FontChange)) { + m_renderer->updateMetrics(QFontMetricsF(this->font())); + return true; + } + + if (m_document && m_renderer && (e->type() == QEvent::ToolTip)) { + QHelpEvent *helpevent = static_cast(e); + QHexPosition position; + + QPoint abspos = absolutePosition(helpevent->pos()); + if (m_renderer->hitTest(abspos, &position, this->firstVisibleLine())) { + QString comments = m_document->metadata()->comments(position.line, position.column); + + if (!comments.isEmpty()) + QToolTip::showText(helpevent->globalPos(), comments, this); + } + + return true; + } + + return QAbstractScrollArea::event(e); +} + +void QHexView::keyPressEvent(QKeyEvent *e) +{ + if (!m_renderer || !m_document) { + QAbstractScrollArea::keyPressEvent(e); + return; + } + + QHexCursor *cur = m_document->cursor(); + + m_blinktimer->stop(); + m_renderer->enableCursor(); + + bool handled = this->processMove(cur, e); + if (!handled) + handled = this->processAction(cur, e); + if (!handled) + handled = this->processTextInput(cur, e); + if (!handled) + QAbstractScrollArea::keyPressEvent(e); + + m_blinktimer->start(); +} + +QPoint QHexView::absolutePosition(const QPoint &pos) const +{ + QPoint shift(horizontalScrollBar()->value(), 0); + return pos + shift; +} + +void QHexView::mousePressEvent(QMouseEvent *e) +{ + QAbstractScrollArea::mousePressEvent(e); + + if (!m_renderer || (e->buttons() != Qt::LeftButton)) + return; + + QHexPosition position; + QPoint abspos = absolutePosition(e->pos()); + + if (!m_renderer->hitTest(abspos, &position, this->firstVisibleLine())) + return; + + m_renderer->selectArea(abspos); + + if (m_renderer->editableArea(m_renderer->selectedArea())) + m_document->cursor()->moveTo(position); + + e->accept(); +} + +void QHexView::mouseMoveEvent(QMouseEvent *e) +{ + QAbstractScrollArea::mouseMoveEvent(e); + if (!m_renderer || !m_document) + return; + + QPoint abspos = absolutePosition(e->pos()); + + if (e->buttons() == Qt::LeftButton) { + if (m_blinktimer->isActive()) { + m_blinktimer->stop(); + m_renderer->enableCursor(false); + } + + QHexCursor *cursor = m_document->cursor(); + QHexPosition position; + + if (!m_renderer->hitTest(abspos, &position, this->firstVisibleLine())) + return; + + cursor->select(position.line, position.column, 0); + e->accept(); + } + + if (e->buttons() != Qt::NoButton) + return; + + int hittest = m_renderer->hitTestArea(abspos); + + if (m_renderer->editableArea(hittest)) + this->setCursor(Qt::IBeamCursor); + else + this->setCursor(Qt::ArrowCursor); +} + +void QHexView::mouseReleaseEvent(QMouseEvent *e) +{ + QAbstractScrollArea::mouseReleaseEvent(e); + if (e->button() != Qt::LeftButton) + return; + if (!m_blinktimer->isActive()) + m_blinktimer->start(); + e->accept(); +} + +void QHexView::focusInEvent(QFocusEvent *e) +{ + QAbstractScrollArea::focusInEvent(e); + if (!m_renderer) + return; + + m_renderer->enableCursor(); + m_blinktimer->start(); +} + +void QHexView::focusOutEvent(QFocusEvent *e) +{ + QAbstractScrollArea::focusOutEvent(e); + if (!m_renderer) + return; + + m_blinktimer->stop(); + m_renderer->enableCursor(false); +} + +void QHexView::wheelEvent(QWheelEvent *e) +{ + if (e->angleDelta().y() == 0) { + int value = this->verticalScrollBar()->value(); + + if (e->angleDelta().x() < 0) // Scroll Down + this->verticalScrollBar()->setValue(value + DOCUMENT_WHEEL_LINES); + else if (e->angleDelta().x() > 0) // Scroll Up + this->verticalScrollBar()->setValue(value - DOCUMENT_WHEEL_LINES); + + return; + } + + QAbstractScrollArea::wheelEvent(e); +} + +void QHexView::resizeEvent(QResizeEvent *e) +{ + QAbstractScrollArea::resizeEvent(e); + this->adjustScrollBars(); +} + +void QHexView::paintEvent(QPaintEvent *e) +{ + if (!m_document) + return; + + QPainter painter(this->viewport()); + painter.setFont(this->font()); + + const QRect &r = e->rect(); + + const quint64 firstVisible = this->firstVisibleLine(); + const int lineHeight = m_renderer->lineHeight(); + const int headerCount = m_renderer->headerLineCount(); + + // these are lines from the point of view of the visible rect + // where the first "headerCount" are taken by the header + const int first = (r.top() / lineHeight); // included + const int lastPlusOne = (r.bottom() / lineHeight) + 1; // excluded + + // compute document lines, adding firstVisible and removing the header + // the max is necessary if the rect covers the header + const quint64 begin = firstVisible + std::max(first - headerCount, 0); + const quint64 end = firstVisible + std::max(lastPlusOne - headerCount, 0); + + painter.save(); + painter.translate(-this->horizontalScrollBar()->value(), 0); + m_renderer->render(&painter, begin, end, firstVisible); + m_renderer->renderFrame(&painter); + painter.restore(); +} + +void QHexView::moveToSelection() +{ + QHexCursor *cur = m_document->cursor(); + + if (!this->isLineVisible(cur->currentLine())) { + QScrollBar *vscrollbar = this->verticalScrollBar(); + int scrollPos = static_cast(std::max(quint64(0), (cur->currentLine() - this->visibleLines() / 2)) / documentSizeFactor()); + vscrollbar->setValue(scrollPos); + } else + this->viewport()->update(); +} + +void QHexView::blinkCursor() +{ + if (!m_renderer) + return; + m_renderer->blinkCursor(); + this->renderCurrentLine(); +} + +void QHexView::moveNext(bool select) +{ + QHexCursor *cur = m_document->cursor(); + quint64 line = cur->currentLine(); + int column = cur->currentColumn(); + bool lastcell = (line >= m_renderer->documentLastLine()) && (column >= m_renderer->documentLastColumn()); + + if ((m_renderer->selectedArea() == QHexRenderer::AsciiArea) && lastcell) + return; + + int nibbleindex = cur->currentNibble(); + + if (m_renderer->selectedArea() == QHexRenderer::HexArea) { + if (lastcell && !nibbleindex) + return; + + if (cur->position().offset() >= m_document->length() && nibbleindex) + return; + } + + if ((m_renderer->selectedArea() == QHexRenderer::HexArea)) { + nibbleindex++; + nibbleindex %= 2; + + if (nibbleindex) + column++; + } else { + nibbleindex = 1; + column++; + } + + if (column > m_renderer->hexLineWidth() - 1) { + line = std::min(m_renderer->documentLastLine(), line + 1); + column = 0; + nibbleindex = 1; + } + + if (select) + cur->select(line, std::min(m_renderer->hexLineWidth() - 1, column), nibbleindex); + else + cur->moveTo(line, std::min(m_renderer->hexLineWidth() - 1, column), nibbleindex); +} + +void QHexView::movePrevious(bool select) +{ + QHexCursor *cur = m_document->cursor(); + quint64 line = cur->currentLine(); + int column = cur->currentColumn(); + bool firstcell = !line && !column; + + if ((m_renderer->selectedArea() == QHexRenderer::AsciiArea) && firstcell) + return; + + int nibbleindex = cur->currentNibble(); + + if ((m_renderer->selectedArea() == QHexRenderer::HexArea) && firstcell && nibbleindex) + return; + + if ((m_renderer->selectedArea() == QHexRenderer::HexArea)) { + nibbleindex--; + nibbleindex %= 2; + if (!nibbleindex) + column--; + } else { + nibbleindex = 1; + column--; + } + + if (column < 0) { + line = std::max(quint64(0), line - 1); + column = m_renderer->hexLineWidth() - 1; + nibbleindex = 0; + } + + if (select) + cur->select(line, std::max(0, column), nibbleindex); + else + cur->moveTo(line, std::max(0, column), nibbleindex); +} + +void QHexView::renderCurrentLine() +{ + if (m_document) + this->renderLine(m_document->cursor()->currentLine()); +} + +bool QHexView::processAction(QHexCursor *cur, QKeyEvent *e) +{ + if (m_readonly) + return false; + + if (e->modifiers() != Qt::NoModifier) { + if (e->matches(QKeySequence::SelectAll)) { + m_document->cursor()->moveTo(0, 0); + m_document->cursor()->select(m_renderer->documentLastLine(), m_renderer->documentLastColumn() - 1); + } else if (e->matches(QKeySequence::Undo)) + m_document->undo(); + else if (e->matches(QKeySequence::Redo)) + m_document->redo(); + else if (e->matches(QKeySequence::Cut)) + m_document->cut((m_renderer->selectedArea() == QHexRenderer::HexArea)); + else if (e->matches(QKeySequence::Copy)) + m_document->copy((m_renderer->selectedArea() == QHexRenderer::HexArea)); + else if (e->matches(QKeySequence::Paste)) + m_document->paste((m_renderer->selectedArea() == QHexRenderer::HexArea)); + else + return false; + + return true; + } + + if ((e->key() == Qt::Key_Backspace) || (e->key() == Qt::Key_Delete)) { + if (!cur->hasSelection()) { + const QHexPosition &pos = cur->position(); + + if (pos.offset() <= 0) + return true; + + if (e->key() == Qt::Key_Backspace) + m_document->remove(cur->position().offset() - 1, 1); + else + m_document->remove(cur->position().offset(), 1); + } else { + QHexPosition oldpos = cur->selectionStart(); + m_document->removeSelection(); + cur->moveTo(oldpos.line, oldpos.column + 1); + } + + if (e->key() == Qt::Key_Backspace) { + if (m_renderer->selectedArea() == QHexRenderer::HexArea) + this->movePrevious(); + + this->movePrevious(); + } + } else if (e->key() == Qt::Key_Insert) + cur->switchInsertionMode(); + else + return false; + + return true; +} + +bool QHexView::processMove(QHexCursor *cur, QKeyEvent *e) +{ + if (e->matches(QKeySequence::MoveToNextChar) || e->matches(QKeySequence::SelectNextChar)) + this->moveNext(e->matches(QKeySequence::SelectNextChar)); + else if (e->matches(QKeySequence::MoveToPreviousChar) || e->matches(QKeySequence::SelectPreviousChar)) + this->movePrevious(e->matches(QKeySequence::SelectPreviousChar)); + else if (e->matches(QKeySequence::MoveToNextLine) || e->matches(QKeySequence::SelectNextLine)) { + if (m_renderer->documentLastLine() == cur->currentLine()) + return true; + + int nextline = cur->currentLine() + 1; + + if (e->matches(QKeySequence::MoveToNextLine)) + cur->moveTo(nextline, cur->currentColumn()); + else + cur->select(nextline, cur->currentColumn()); + } else if (e->matches(QKeySequence::MoveToPreviousLine) || e->matches(QKeySequence::SelectPreviousLine)) { + if (!cur->currentLine()) + return true; + + quint64 prevline = cur->currentLine() - 1; + + if (e->matches(QKeySequence::MoveToPreviousLine)) + cur->moveTo(prevline, cur->currentColumn()); + else + cur->select(prevline, cur->currentColumn()); + } else if (e->matches(QKeySequence::MoveToNextPage) || e->matches(QKeySequence::SelectNextPage)) { + if (m_renderer->documentLastLine() == cur->currentLine()) + return true; + + int pageline = std::min(m_renderer->documentLastLine(), cur->currentLine() + this->visibleLines()); + + if (e->matches(QKeySequence::MoveToNextPage)) + cur->moveTo(pageline, cur->currentColumn()); + else + cur->select(pageline, cur->currentColumn()); + } else if (e->matches(QKeySequence::MoveToPreviousPage) || e->matches(QKeySequence::SelectPreviousPage)) { + if (!cur->currentLine()) + return true; + + quint64 pageline = std::max(quint64(0), cur->currentLine() - this->visibleLines()); + + if (e->matches(QKeySequence::MoveToPreviousPage)) + cur->moveTo(pageline, cur->currentColumn()); + else + cur->select(pageline, cur->currentColumn()); + } else if (e->matches(QKeySequence::MoveToStartOfDocument) || e->matches(QKeySequence::SelectStartOfDocument)) { + if (!cur->currentLine()) + return true; + + if (e->matches(QKeySequence::MoveToStartOfDocument)) + cur->moveTo(0, 0); + else + cur->select(0, 0); + } else if (e->matches(QKeySequence::MoveToEndOfDocument) || e->matches(QKeySequence::SelectEndOfDocument)) { + if (m_renderer->documentLastLine() == cur->currentLine()) + return true; + + if (e->matches(QKeySequence::MoveToEndOfDocument)) + cur->moveTo(m_renderer->documentLastLine(), m_renderer->documentLastColumn()); + else + cur->select(m_renderer->documentLastLine(), m_renderer->documentLastColumn()); + } else if (e->matches(QKeySequence::MoveToStartOfLine) || e->matches(QKeySequence::SelectStartOfLine)) { + if (e->matches(QKeySequence::MoveToStartOfLine)) + cur->moveTo(cur->currentLine(), 0); + else + cur->select(cur->currentLine(), 0); + } else if (e->matches(QKeySequence::MoveToEndOfLine) || e->matches(QKeySequence::SelectEndOfLine)) { + if (e->matches(QKeySequence::MoveToEndOfLine)) { + if (cur->currentLine() == m_renderer->documentLastLine()) + cur->moveTo(cur->currentLine(), m_renderer->documentLastColumn()); + else + cur->moveTo(cur->currentLine(), m_renderer->hexLineWidth() - 1, 0); + } else { + if (cur->currentLine() == m_renderer->documentLastLine()) + cur->select(cur->currentLine(), m_renderer->documentLastColumn()); + else + cur->select(cur->currentLine(), m_renderer->hexLineWidth() - 1, 0); + } + } else + return false; + + return true; +} + +bool QHexView::processTextInput(QHexCursor *cur, QKeyEvent *e) +{ + if (m_readonly || (e->modifiers() & Qt::ControlModifier)) + return false; + + uchar key = static_cast(e->text()[0].toLatin1()); + + if ((m_renderer->selectedArea() == QHexRenderer::HexArea)) { + if (!((key >= '0' && key <= '9') || (key >= 'a' && key <= 'f'))) // Check if is a Hex Char + return false; + + // uchar val = static_cast(QLatin1String(static_cast(key)).toUInt(nullptr, 16)); + uchar val = 1; + m_document->removeSelection(); + + if (m_document->atEnd() || (cur->currentNibble() && (cur->insertionMode() == QHexCursor::InsertMode))) { + m_document->insert(cur->position().offset(), val << 4); // X0 byte + this->moveNext(); + return true; + } + + uchar ch = static_cast(m_document->at(cur->position().offset())); + + if (cur->currentNibble()) // X0 + val = (ch & 0x0F) | (val << 4); + else // 0X + val = (ch & 0xF0) | val; + + m_document->replace(cur->position().offset(), val); + this->moveNext(); + return true; + } + + if ((m_renderer->selectedArea() == QHexRenderer::AsciiArea)) { + if (!(key >= 0x20 && key <= 0x7E)) // Check if is a Printable Char + return false; + + m_document->removeSelection(); + + if (!m_document->atEnd() && (cur->insertionMode() == QHexCursor::OverwriteMode)) + m_document->replace(cur->position().offset(), key); + else + m_document->insert(cur->position().offset(), key); + + QKeyEvent keyevent(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier); + qApp->sendEvent(this, &keyevent); + return true; + } + + return false; +} + +void QHexView::adjustScrollBars() +{ + QScrollBar *vscrollbar = this->verticalScrollBar(); + int sizeFactor = this->documentSizeFactor(); + vscrollbar->setSingleStep(sizeFactor); + + quint64 docLines = m_renderer->documentLines(); + quint64 visLines = this->visibleLines(); + + if (docLines > visLines) { + this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + vscrollbar->setMaximum(static_cast((docLines - visLines) / sizeFactor + 1)); + } else { + this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + vscrollbar->setValue(0); + vscrollbar->setMaximum(static_cast(docLines)); + } + + QScrollBar *hscrollbar = this->horizontalScrollBar(); + int documentWidth = m_renderer->documentWidth(); + int viewportWidth = viewport()->width(); + + if (documentWidth > viewportWidth) { + this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + // +1 to see the rightmost vertical line, +2 seems more pleasant to the eyes + hscrollbar->setMaximum(documentWidth - viewportWidth + 2); + } else { + this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + hscrollbar->setValue(0); + hscrollbar->setMaximum(documentWidth); + } +} + +void QHexView::renderLine(quint64 line) +{ + if (!this->isLineVisible(line)) + return; + this->viewport()->update(/*m_renderer->getLineRect(line, this->firstVisibleLine())*/); +} + +quint64 QHexView::firstVisibleLine() const +{ + return static_cast(this->verticalScrollBar()->value()) * documentSizeFactor(); +} +quint64 QHexView::lastVisibleLine() const +{ + return this->firstVisibleLine() + this->visibleLines() - 1; +} + +quint64 QHexView::visibleLines() const +{ + int visLines = std::ceil(this->height() / m_renderer->lineHeight()) - m_renderer->headerLineCount(); + return std::min(quint64(visLines), m_renderer->documentLines()); +} + +bool QHexView::isLineVisible(quint64 line) const +{ + if (!m_document) + return false; + if (line < this->firstVisibleLine()) + return false; + if (line > this->lastVisibleLine()) + return false; + return true; +} + +int QHexView::documentSizeFactor() const +{ + int factor = 1; + + if (m_document) { + quint64 docLines = m_renderer->documentLines(); + if (docLines >= INT_MAX) + factor = static_cast(docLines / INT_MAX) + 1; + } + + return factor; +} \ No newline at end of file diff --git a/parts/hex/qhexview.h b/parts/hex/qhexview.h new file mode 100644 index 0000000..f0d4d6e --- /dev/null +++ b/parts/hex/qhexview.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2014 Dax89 +// SPDX-License-Identifier: MIT + +#ifndef QHEXVIEW_H +#define QHEXVIEW_H + +#include "document/qhexdocument.h" +#include "document/qhexrenderer.h" +#include +#include + +class QHexView : public QAbstractScrollArea +{ + Q_OBJECT + +public: + explicit QHexView(QWidget *parent = nullptr); + QHexDocument *document(); + void setDocument(QHexDocument *document); + void setReadOnly(bool b); + +protected: + virtual bool event(QEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void focusInEvent(QFocusEvent *e); + virtual void focusOutEvent(QFocusEvent *e); + virtual void wheelEvent(QWheelEvent *e); + virtual void resizeEvent(QResizeEvent *e); + virtual void paintEvent(QPaintEvent *e); + +private Q_SLOTS: + void renderCurrentLine(); + void moveToSelection(); + void blinkCursor(); + +private: + void moveNext(bool select = false); + void movePrevious(bool select = false); + +private: + bool processMove(QHexCursor *cur, QKeyEvent *e); + bool processTextInput(QHexCursor *cur, QKeyEvent *e); + bool processAction(QHexCursor *cur, QKeyEvent *e); + void adjustScrollBars(); + void renderLine(quint64 line); + quint64 firstVisibleLine() const; + quint64 lastVisibleLine() const; + quint64 visibleLines() const; + bool isLineVisible(quint64 line) const; + + int documentSizeFactor() const; + + QPoint absolutePosition(const QPoint &pos) const; + +private: + QHexDocument *m_document; + QHexRenderer *m_renderer; + QTimer *m_blinktimer; + bool m_readonly; +}; + +#endif // QHEXVIEW_H \ No newline at end of file