├── .github └── FUNDING.yml ├── .gitignore ├── src ├── model │ ├── commands │ │ ├── hexcommand.cpp │ │ ├── insertcommand.cpp │ │ ├── removecommand.cpp │ │ └── replacecommand.cpp │ ├── buffer │ │ ├── qmemoryrefbuffer.cpp │ │ ├── qhexbuffer.cpp │ │ ├── qmemorybuffer.cpp │ │ ├── qmappedfilebuffer.cpp │ │ └── qdevicebuffer.cpp │ ├── qhexdelegate.cpp │ ├── qhexmetadata.cpp │ ├── qhexdocument.cpp │ ├── qhexcursor.cpp │ └── qhexutils.cpp ├── dialogs │ └── hexfinddialog.cpp └── qhexview.cpp ├── example ├── CMakeLists.txt └── main.cpp ├── include └── QHexView │ ├── model │ ├── buffer │ │ ├── qmemoryrefbuffer.h │ │ ├── qmappedfilebuffer.h │ │ ├── qmemorybuffer.h │ │ ├── qdevicebuffer.h │ │ └── qhexbuffer.h │ ├── commands │ │ ├── removecommand.h │ │ ├── insertcommand.h │ │ ├── replacecommand.h │ │ └── hexcommand.h │ ├── qhexdelegate.h │ ├── qhexoptions.h │ ├── qhexutils.h │ ├── qhexcursor.h │ ├── qhexmetadata.h │ └── qhexdocument.h │ ├── dialogs │ └── hexfinddialog.h │ └── qhexview.h ├── LICENSE ├── QHexView.pri ├── CMakeLists.txt └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Dax89 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .automaton 2 | .cache 3 | compile_commands.json 4 | -------------------------------------------------------------------------------- /src/model/commands/hexcommand.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | HexCommand::HexCommand(QHexBuffer* buffer, QHexDocument* document, 4 | QUndoCommand* parent) 5 | : QUndoCommand(parent), m_hexdocument(document), m_buffer(buffer), 6 | m_offset(0), m_length(0) {} 7 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(QHexView_Example) 2 | 3 | add_executable(${PROJECT_NAME} main.cpp) 4 | 5 | set_target_properties(${PROJECT_NAME} 6 | PROPERTIES 7 | WIN32_EXECUTABLE ON 8 | MACOSX_BUNDLE ON 9 | AUTOMOC ON 10 | ) 11 | 12 | target_link_libraries(${PROJECT_NAME} 13 | PRIVATE 14 | QHexView 15 | ) 16 | -------------------------------------------------------------------------------- /include/QHexView/model/buffer/qmemoryrefbuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class QMemoryRefBuffer: public QDeviceBuffer { 6 | Q_OBJECT 7 | 8 | public: 9 | explicit QMemoryRefBuffer(QObject* parent = nullptr); 10 | bool read(QIODevice* device) override; 11 | void write(QIODevice* device) override; 12 | }; 13 | -------------------------------------------------------------------------------- /include/QHexView/model/commands/removecommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RemoveCommand: public HexCommand { 6 | public: 7 | RemoveCommand(QHexBuffer* buffer, QHexDocument* document, qint64 offset, 8 | int length, QUndoCommand* parent = nullptr); 9 | void undo() override; 10 | void redo() override; 11 | }; 12 | -------------------------------------------------------------------------------- /include/QHexView/model/commands/insertcommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class InsertCommand: public HexCommand { 6 | public: 7 | InsertCommand(QHexBuffer* buffer, QHexDocument* document, qint64 offset, 8 | const QByteArray& data, QUndoCommand* parent = nullptr); 9 | void undo() override; 10 | void redo() override; 11 | }; 12 | -------------------------------------------------------------------------------- /include/QHexView/model/commands/replacecommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class ReplaceCommand: public HexCommand { 6 | public: 7 | ReplaceCommand(QHexBuffer* buffer, QHexDocument* document, qint64 offset, 8 | const QByteArray& data, QUndoCommand* parent = nullptr); 9 | void undo() override; 10 | void redo() override; 11 | 12 | private: 13 | QByteArray m_olddata; 14 | }; 15 | -------------------------------------------------------------------------------- /include/QHexView/model/commands/hexcommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class QHexDocument; 7 | 8 | class HexCommand: public QUndoCommand { 9 | public: 10 | HexCommand(QHexBuffer* buffer, QHexDocument* document, 11 | QUndoCommand* parent = nullptr); 12 | 13 | protected: 14 | QHexDocument* m_hexdocument; 15 | QHexBuffer* m_buffer; 16 | qint64 m_offset; 17 | int m_length; 18 | QByteArray m_data; 19 | }; 20 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char** argv) { 5 | QApplication a{argc, argv}; 6 | 7 | QHexDocument* doc = 8 | QHexDocument::fromFile(QApplication::applicationFilePath()); 9 | 10 | QHexView view; 11 | view.setWindowTitle( 12 | QString{"QHexView Example (Qt %1)"}.arg(QT_VERSION_STR)); 13 | view.setDocument(doc); // No parent, take the ownership of 'doc' 14 | view.show(); 15 | 16 | return a.exec(); 17 | } 18 | -------------------------------------------------------------------------------- /include/QHexView/model/buffer/qmappedfilebuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class QMappedFileBuffer: public QDeviceBuffer { 6 | public: 7 | explicit QMappedFileBuffer(QObject* parent = nullptr); 8 | virtual ~QMappedFileBuffer(); 9 | 10 | public: 11 | QByteArray read(qint64 offset, int length) override; 12 | bool read(QIODevice* iodevice) override; 13 | void write(QIODevice* iodevice) override; 14 | 15 | private: 16 | void remap(); 17 | 18 | private: 19 | uchar* m_mappeddata{nullptr}; 20 | }; 21 | -------------------------------------------------------------------------------- /src/model/commands/insertcommand.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | InsertCommand::InsertCommand(QHexBuffer* buffer, QHexDocument* document, 5 | qint64 offset, const QByteArray& data, 6 | QUndoCommand* parent) 7 | : HexCommand(buffer, document, parent) { 8 | m_offset = offset; 9 | m_data = data; 10 | } 11 | 12 | void InsertCommand::undo() { 13 | m_buffer->remove(m_offset, m_data.length()); 14 | Q_EMIT m_hexdocument->dataChanged(m_data, m_offset, 15 | QHexDocument::ChangeReason::Remove); 16 | } 17 | 18 | void InsertCommand::redo() { m_buffer->insert(m_offset, m_data); } 19 | -------------------------------------------------------------------------------- /src/model/buffer/qmemoryrefbuffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | QMemoryRefBuffer::QMemoryRefBuffer(QObject* parent): QDeviceBuffer{parent} {} 5 | 6 | bool QMemoryRefBuffer::read(QIODevice* device) { 7 | m_device = qobject_cast(device); 8 | 9 | if(m_device) { 10 | m_device->setParent(this); 11 | return QDeviceBuffer::read(device); 12 | } 13 | 14 | return false; 15 | } 16 | 17 | void QMemoryRefBuffer::write(QIODevice* device) { 18 | if(!m_device || m_device == device) 19 | return; 20 | 21 | static const int CHUNK_SIZE = 4096; 22 | m_device->seek(0); 23 | 24 | while(!m_device->atEnd()) 25 | device->write(m_device->read(CHUNK_SIZE)); 26 | } 27 | -------------------------------------------------------------------------------- /src/model/commands/removecommand.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | RemoveCommand::RemoveCommand(QHexBuffer* buffer, QHexDocument* document, 5 | qint64 offset, int length, QUndoCommand* parent) 6 | : HexCommand(buffer, document, parent) { 7 | m_offset = offset; 8 | m_length = length; 9 | } 10 | 11 | void RemoveCommand::undo() { 12 | m_buffer->insert(m_offset, m_data); 13 | Q_EMIT m_hexdocument->dataChanged(m_data, m_offset, 14 | QHexDocument::ChangeReason::Insert); 15 | } 16 | 17 | void RemoveCommand::redo() { 18 | m_data = m_buffer->read(m_offset, m_length); // Backup data 19 | m_buffer->remove(m_offset, m_length); 20 | } 21 | -------------------------------------------------------------------------------- /include/QHexView/model/buffer/qmemorybuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class QMemoryBuffer: public QHexBuffer { 6 | Q_OBJECT 7 | 8 | public: 9 | explicit QMemoryBuffer(QObject* parent = nullptr); 10 | uchar at(qint64 idx) override; 11 | qint64 length() const override; 12 | void insert(qint64 offset, const QByteArray& data) override; 13 | void remove(qint64 offset, int length) override; 14 | QByteArray read(qint64 offset, int length) override; 15 | bool read(QIODevice* device) override; 16 | void write(QIODevice* device) override; 17 | qint64 indexOf(const QByteArray& ba, qint64 from) override; 18 | qint64 lastIndexOf(const QByteArray& ba, qint64 from) override; 19 | 20 | private: 21 | QByteArray m_buffer; 22 | }; 23 | -------------------------------------------------------------------------------- /src/model/commands/replacecommand.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | ReplaceCommand::ReplaceCommand(QHexBuffer* buffer, QHexDocument* document, 5 | qint64 offset, const QByteArray& data, 6 | QUndoCommand* parent) 7 | : HexCommand(buffer, document, parent) { 8 | m_offset = offset; 9 | m_data = data; 10 | } 11 | 12 | void ReplaceCommand::undo() { 13 | m_buffer->replace(m_offset, m_olddata); 14 | Q_EMIT m_hexdocument->dataChanged(m_olddata, m_offset, 15 | QHexDocument::ChangeReason::Replace); 16 | } 17 | 18 | void ReplaceCommand::redo() { 19 | m_olddata = m_buffer->read(m_offset, m_data.length()); 20 | m_buffer->replace(m_offset, m_data); 21 | } 22 | -------------------------------------------------------------------------------- /include/QHexView/model/buffer/qdevicebuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class QDeviceBuffer: public QHexBuffer { 6 | Q_OBJECT 7 | 8 | public: 9 | explicit QDeviceBuffer(QObject* parent = nullptr); 10 | virtual ~QDeviceBuffer(); 11 | uchar at(qint64 idx) override; 12 | qint64 length() const override; 13 | void insert(qint64 offset, const QByteArray& data) override; 14 | void replace(qint64 offset, const QByteArray& data) override; 15 | void remove(qint64 offset, int length) override; 16 | QByteArray read(qint64 offset, int length) override; 17 | bool read(QIODevice* device) override; 18 | void write(QIODevice* device) override; 19 | qint64 indexOf(const QByteArray& ba, qint64 from) override; 20 | qint64 lastIndexOf(const QByteArray& ba, qint64 from) override; 21 | 22 | protected: 23 | QIODevice* m_device{nullptr}; 24 | }; 25 | -------------------------------------------------------------------------------- /include/QHexView/model/buffer/qhexbuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class QHexBuffer: public QObject { 7 | Q_OBJECT 8 | 9 | public: 10 | explicit QHexBuffer(QObject* parent = nullptr); 11 | bool isEmpty() const; 12 | 13 | public: 14 | virtual uchar at(qint64 idx); 15 | virtual bool accept(qint64 idx) const; 16 | virtual void replace(qint64 offset, const QByteArray& data); 17 | virtual void read(char* data, int size); 18 | virtual void read(const QByteArray& ba); 19 | 20 | public: 21 | virtual qint64 length() const = 0; 22 | virtual void insert(qint64 offset, const QByteArray& data) = 0; 23 | virtual void remove(qint64 offset, int length) = 0; 24 | virtual QByteArray read(qint64 offset, int length) = 0; 25 | virtual bool read(QIODevice* iodevice) = 0; 26 | virtual void write(QIODevice* iodevice) = 0; 27 | virtual qint64 indexOf(const QByteArray& ba, qint64 from) = 0; 28 | virtual qint64 lastIndexOf(const QByteArray& ba, qint64 from) = 0; 29 | }; 30 | -------------------------------------------------------------------------------- /src/model/buffer/qhexbuffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | QHexBuffer::QHexBuffer(QObject* parent): QObject{parent} {} 5 | uchar QHexBuffer::at(qint64 idx) { return this->read(idx, 1).at(0); } 6 | bool QHexBuffer::isEmpty() const { return this->length() <= 0; } 7 | 8 | void QHexBuffer::replace(qint64 offset, const QByteArray& data) { 9 | this->remove(offset, data.length()); 10 | this->insert(offset, data); 11 | } 12 | 13 | bool QHexBuffer::accept(qint64 idx) const { 14 | Q_UNUSED(idx); 15 | return true; 16 | } 17 | 18 | void QHexBuffer::read(char* data, int size) { 19 | QBuffer* buffer = new QBuffer(this); 20 | buffer->setData(data, size); 21 | 22 | if(!buffer->isOpen()) 23 | buffer->open(QBuffer::ReadWrite); 24 | 25 | this->read(buffer); 26 | } 27 | 28 | void QHexBuffer::read(const QByteArray& ba) { 29 | QBuffer* buffer = new QBuffer(this); 30 | 31 | buffer->setData(ba); 32 | if(!buffer->isOpen()) 33 | buffer->open(QBuffer::ReadWrite); 34 | 35 | this->read(buffer); 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dax89 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/model/buffer/qmemorybuffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | QMemoryBuffer::QMemoryBuffer(QObject* parent): QHexBuffer{parent} {} 5 | 6 | uchar QMemoryBuffer::at(qint64 idx) { 7 | return static_cast(m_buffer.at(idx)); 8 | } 9 | 10 | qint64 QMemoryBuffer::length() const { 11 | return static_cast(m_buffer.length()); 12 | } 13 | 14 | void QMemoryBuffer::insert(qint64 offset, const QByteArray& data) { 15 | m_buffer.insert(static_cast(offset), data); 16 | } 17 | 18 | void QMemoryBuffer::remove(qint64 offset, int length) { 19 | m_buffer.remove(static_cast(offset), length); 20 | } 21 | 22 | QByteArray QMemoryBuffer::read(qint64 offset, int length) { 23 | return m_buffer.mid(static_cast(offset), length); 24 | } 25 | 26 | bool QMemoryBuffer::read(QIODevice* device) { 27 | m_buffer = device->readAll(); 28 | return true; 29 | } 30 | 31 | void QMemoryBuffer::write(QIODevice* device) { device->write(m_buffer); } 32 | 33 | qint64 QMemoryBuffer::indexOf(const QByteArray& ba, qint64 from) { 34 | return m_buffer.indexOf(ba, static_cast(from)); 35 | } 36 | 37 | qint64 QMemoryBuffer::lastIndexOf(const QByteArray& ba, qint64 from) { 38 | return m_buffer.lastIndexOf(ba, static_cast(from)); 39 | } 40 | -------------------------------------------------------------------------------- /include/QHexView/model/qhexdelegate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class QHexView; 8 | 9 | class QHexDelegate: public QObject { 10 | Q_OBJECT 11 | 12 | public: 13 | explicit QHexDelegate(QObject* parent = nullptr); 14 | virtual ~QHexDelegate() = default; 15 | virtual QString addressHeader(const QHexView* hexview) const; 16 | virtual QString hexHeader(const QHexView* hexview) const; 17 | virtual QString asciiHeader(const QHexView* hexview) const; 18 | virtual void renderAddress(quint64 address, QTextCharFormat& cf, 19 | const QHexView* hexview) const; 20 | virtual void renderHeader(QTextBlockFormat& bf, 21 | const QHexView* hexview) const; 22 | virtual void renderHeaderPart(const QString& s, QHexArea area, 23 | QTextCharFormat& cf, 24 | const QHexView* hexview) const; 25 | virtual bool render(quint64 offset, quint8 b, QTextCharFormat& outcf, 26 | const QHexView* hexview) const; 27 | virtual bool paintSeparator(QPainter* painter, QLineF line, 28 | const QHexView* hexview) const; 29 | virtual void paint(QPainter* painter, const QHexView* hexview) const; 30 | }; 31 | -------------------------------------------------------------------------------- /include/QHexView/model/qhexoptions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace QHexFlags { 8 | 9 | enum : unsigned int { 10 | None = (1 << 0), 11 | HSeparator = (1 << 1), 12 | VSeparator = (1 << 2), 13 | StyledHeader = (1 << 3), 14 | StyledAddress = (1 << 4), 15 | NoHeader = (1 << 5), 16 | HighlightAddress = (1 << 6), 17 | HighlightColumn = (1 << 7), 18 | 19 | Separators = HSeparator | VSeparator, 20 | Styled = StyledHeader | StyledAddress, 21 | }; 22 | 23 | } 24 | 25 | struct QHexColor { 26 | QColor foreground; 27 | QColor background; 28 | }; 29 | 30 | struct QHexOptions { 31 | // Appearance 32 | QChar unprintablechar{'.'}; 33 | QChar invalidchar{'?'}; 34 | QString addresslabel{""}; 35 | QString hexlabel; 36 | QString asciilabel; 37 | quint64 baseaddress{0}; 38 | unsigned int flags{QHexFlags::None}; 39 | unsigned int linelength{0x10}; 40 | unsigned int addresswidth{0}; 41 | unsigned int grouplength{1}; 42 | int scrollsteps{1}; 43 | 44 | // Colors & Styles 45 | QHash bytecolors; 46 | QColor linealternatebackground; 47 | QColor linebackground; 48 | QColor headercolor; 49 | QColor commentcolor; 50 | QColor separatorcolor; 51 | 52 | // Misc 53 | bool copybreak{true}; 54 | 55 | inline bool hasFlag(unsigned int flag) const { return flags & flag; } 56 | }; 57 | -------------------------------------------------------------------------------- /src/model/buffer/qmappedfilebuffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | QMappedFileBuffer::QMappedFileBuffer(QObject* parent): QDeviceBuffer{parent} {} 5 | 6 | QMappedFileBuffer::~QMappedFileBuffer() { 7 | if((m_device && (m_device->parent() == this)) && m_mappeddata) { 8 | QFile* f = qobject_cast(m_device); 9 | f->unmap(m_mappeddata); 10 | } 11 | 12 | m_mappeddata = nullptr; 13 | } 14 | 15 | QByteArray QMappedFileBuffer::read(qint64 offset, int length) { 16 | if(offset >= this->length()) 17 | return {}; 18 | 19 | if(offset + length >= this->length()) 20 | length = this->length() - offset; 21 | 22 | return QByteArray::fromRawData( 23 | reinterpret_cast(m_mappeddata + offset), length); 24 | } 25 | 26 | bool QMappedFileBuffer::read(QIODevice* iodevice) { 27 | m_device = qobject_cast(iodevice); 28 | if(!m_device || !QDeviceBuffer::read(iodevice)) 29 | return false; 30 | 31 | this->remap(); 32 | return m_mappeddata; 33 | } 34 | 35 | void QMappedFileBuffer::write(QIODevice* iodevice) { 36 | if(iodevice == m_device) 37 | this->remap(); 38 | else 39 | iodevice->write(reinterpret_cast(m_mappeddata), 40 | m_device->size()); 41 | } 42 | 43 | void QMappedFileBuffer::remap() { 44 | QFile* f = qobject_cast(m_device); 45 | if(!f) 46 | return; 47 | 48 | if(m_mappeddata) 49 | f->unmap(m_mappeddata); 50 | m_mappeddata = f->map(0, f->size()); 51 | } 52 | -------------------------------------------------------------------------------- /include/QHexView/dialogs/hexfinddialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class QRegularExpressionValidator; 7 | class QDoubleValidator; 8 | class QIntValidator; 9 | class QHexView; 10 | 11 | class HexFindDialog: public QDialog { 12 | Q_OBJECT 13 | 14 | public: 15 | enum class Type { Find, Replace }; 16 | 17 | public: 18 | explicit HexFindDialog(HexFindDialog::Type type = Type::Find, 19 | QHexView* parent = nullptr); 20 | QHexView* hexView() const; 21 | 22 | private Q_SLOTS: 23 | void updateFindOptions(int); 24 | void validateActions(); 25 | void replace(); 26 | void find(); 27 | 28 | private: 29 | bool prepareOptions(QString& q, QHexFindMode& mode, QHexFindDirection& fd); 30 | bool validateIntRange(uint v) const; 31 | void checkResult(const QString& q, qint64 offset, QHexFindDirection fd); 32 | void prepareTextMode(QLayout* l); 33 | void prepareHexMode(QLayout* l); 34 | void prepareIntMode(QLayout* l); 35 | void prepareFloatMode(QLayout* l); 36 | 37 | private: 38 | QRegularExpressionValidator *m_hexvalidator, *m_hexpvalidator; 39 | QDoubleValidator* m_dblvalidator; 40 | QIntValidator* m_intvalidator; 41 | int m_oldidxbits{-1}, m_oldidxendian{-1}; 42 | unsigned int m_findoptions{0}; 43 | qint64 m_startoffset{-1}; 44 | Type m_type; 45 | 46 | private: 47 | static const QString BUTTONBOX; 48 | static const QString CBFINDMODE; 49 | static const QString LEFIND; 50 | static const QString LEREPLACE; 51 | static const QString HLAYOUT; 52 | static const QString GBOPTIONS; 53 | static const QString RBALL; 54 | static const QString RBFORWARD; 55 | static const QString RBBACKWARD; 56 | }; 57 | -------------------------------------------------------------------------------- /src/model/qhexdelegate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | QHexDelegate::QHexDelegate(QObject* parent): QObject{parent} {} 5 | 6 | QString QHexDelegate::addressHeader(const QHexView* hexview) const { 7 | Q_UNUSED(hexview); 8 | return QString(); 9 | } 10 | 11 | QString QHexDelegate::hexHeader(const QHexView* hexview) const { 12 | Q_UNUSED(hexview); 13 | return QString(); 14 | } 15 | 16 | QString QHexDelegate::asciiHeader(const QHexView* hexview) const { 17 | Q_UNUSED(hexview); 18 | return QString(); 19 | } 20 | 21 | void QHexDelegate::renderAddress(quint64 address, QTextCharFormat& cf, 22 | const QHexView* hexview) const { 23 | Q_UNUSED(address); 24 | Q_UNUSED(hexview); 25 | Q_UNUSED(cf); 26 | Q_UNUSED(hexview); 27 | } 28 | 29 | void QHexDelegate::renderHeader(QTextBlockFormat& bf, 30 | const QHexView* hexview) const { 31 | Q_UNUSED(bf); 32 | Q_UNUSED(hexview); 33 | } 34 | 35 | void QHexDelegate::renderHeaderPart(const QString& s, QHexArea area, 36 | QTextCharFormat& cf, 37 | const QHexView* hexview) const { 38 | Q_UNUSED(s); 39 | Q_UNUSED(area); 40 | Q_UNUSED(cf); 41 | Q_UNUSED(hexview); 42 | } 43 | 44 | bool QHexDelegate::render(quint64 offset, quint8 b, QTextCharFormat& outcf, 45 | const QHexView* hexview) const { 46 | Q_UNUSED(offset); 47 | Q_UNUSED(b); 48 | Q_UNUSED(outcf); 49 | Q_UNUSED(hexview); 50 | 51 | return false; 52 | } 53 | 54 | bool QHexDelegate::paintSeparator(QPainter* painter, QLineF line, 55 | const QHexView* hexview) const { 56 | Q_UNUSED(painter); 57 | Q_UNUSED(line); 58 | Q_UNUSED(hexview); 59 | return false; 60 | } 61 | 62 | void QHexDelegate::paint(QPainter* painter, const QHexView* hexview) const { 63 | Q_UNUSED(hexview); 64 | hexview->paint(painter); 65 | } 66 | -------------------------------------------------------------------------------- /QHexView.pri: -------------------------------------------------------------------------------- 1 | QT += widgets 2 | 3 | DEFINES += "QHEXVIEW_ENABLE_DIALOGS=1" 4 | 5 | HEADERS += $$PWD/include/QHexView/model/commands/hexcommand.h \ 6 | $$PWD/include/QHexView/model/commands/insertcommand.h \ 7 | $$PWD/include/QHexView/model/commands/removecommand.h \ 8 | $$PWD/include/QHexView/model/commands/replacecommand.h \ 9 | $$PWD/include/QHexView/model/buffer/qdevicebuffer.h \ 10 | $$PWD/include/QHexView/model/buffer/qhexbuffer.h \ 11 | $$PWD/include/QHexView/model/buffer/qmemorybuffer.h \ 12 | $$PWD/include/QHexView/model/buffer/qmemoryrefbuffer.h \ 13 | $$PWD/include/QHexView/model/buffer/qmappedfilebuffer.h \ 14 | $$PWD/include/QHexView/model/qhexdelegate.h \ 15 | $$PWD/include/QHexView/model/qhexutils.h \ 16 | $$PWD/include/QHexView/model/qhexcursor.h \ 17 | $$PWD/include/QHexView/model/qhexmetadata.h \ 18 | $$PWD/include/QHexView/model/qhexoptions.h \ 19 | $$PWD/include/QHexView/model/qhexdocument.h \ 20 | $$PWD/include/QHexView/dialogs/hexfinddialog.h \ 21 | $$PWD/include/QHexView/qhexview.h 22 | 23 | SOURCES += $$PWD/src/model/commands/hexcommand.cpp \ 24 | $$PWD/src/model/commands/insertcommand.cpp \ 25 | $$PWD/src/model/commands/removecommand.cpp \ 26 | $$PWD/src/model/commands/replacecommand.cpp \ 27 | $$PWD/src/model/buffer/qdevicebuffer.cpp \ 28 | $$PWD/src/model/buffer/qhexbuffer.cpp \ 29 | $$PWD/src/model/buffer/qmemorybuffer.cpp \ 30 | $$PWD/src/model/buffer/qmemoryrefbuffer.cpp \ 31 | $$PWD/src/model/buffer/qmappedfilebuffer.cpp \ 32 | $$PWD/src/model/qhexdelegate.cpp \ 33 | $$PWD/src/model/qhexutils.cpp \ 34 | $$PWD/src/model/qhexcursor.cpp \ 35 | $$PWD/src/model/qhexmetadata.cpp \ 36 | $$PWD/src/model/qhexdocument.cpp \ 37 | $$PWD/src/dialogs/hexfinddialog.cpp \ 38 | $$PWD/src/qhexview.cpp 39 | 40 | INCLUDEPATH += $$PWD/include 41 | -------------------------------------------------------------------------------- /include/QHexView/model/qhexutils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct QHexOptions; 9 | class QHexView; 10 | 11 | namespace QHexFindOptions { 12 | 13 | enum : unsigned int { 14 | None = (1 << 0), 15 | CaseSensitive = (1 << 1), 16 | Int8 = (1 << 2), 17 | Int16 = (1 << 3), 18 | Int32 = (1 << 4), 19 | Int64 = (1 << 5), 20 | Float = (1 << 6), 21 | Double = (1 << 7), 22 | 23 | BigEndian = (1 << 11), 24 | }; 25 | 26 | } 27 | 28 | enum class QHexFindMode { Text, Hex, Int, Float }; 29 | enum class QHexFindDirection { All, Forward, Backward }; 30 | enum class QHexArea { Header, Address, Hex, Ascii, Extra }; 31 | 32 | struct QHexPosition { 33 | qint64 line; 34 | qint64 column; 35 | static inline QHexPosition invalid() { return {-1, -1}; } 36 | inline bool isValid() const { return line >= 0 && column >= 0; } 37 | inline bool operator==(const QHexPosition& rhs) const { 38 | return (line == rhs.line) && (column == rhs.column); 39 | } 40 | inline bool operator!=(const QHexPosition& rhs) const { 41 | return (line != rhs.line) || (column != rhs.column); 42 | } 43 | }; 44 | 45 | namespace QHexUtils { 46 | 47 | bool isHex(char ch); 48 | QByteArray toHex(const QByteArray& ba, char sep); 49 | QByteArray toHex(const QByteArray& ba); 50 | qint64 positionToOffset(const QHexOptions* options, QHexPosition pos); 51 | QHexPosition offsetToPosition(const QHexOptions* options, qint64 offset); 52 | bool checkPattern(QString pattern); 53 | 54 | QPair find(const QHexView* hexview, QVariant value, 55 | qint64 startoffset = 0, 56 | QHexFindMode mode = QHexFindMode::Text, 57 | unsigned int options = QHexFindOptions::None, 58 | QHexFindDirection fd = QHexFindDirection::Forward); 59 | 60 | QPair 61 | replace(const QHexView* hexview, QVariant oldvalue, QVariant newvalue, 62 | qint64 startoffset = 0, QHexFindMode mode = QHexFindMode::Text, 63 | unsigned int options = QHexFindOptions::None, 64 | QHexFindDirection fd = QHexFindDirection::Forward); 65 | 66 | } // namespace QHexUtils 67 | -------------------------------------------------------------------------------- /include/QHexView/model/qhexcursor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class QHexView; 8 | 9 | class QHexCursor: public QObject { 10 | Q_OBJECT 11 | 12 | public: 13 | enum class Mode { Overwrite, Insert }; 14 | 15 | private: 16 | explicit QHexCursor(const QHexOptions* options, QHexView* parent = nullptr); 17 | 18 | public: 19 | QHexView* hexView() const; 20 | Mode mode() const; 21 | qint64 line() const; 22 | qint64 column() const; 23 | qint64 offset() const; 24 | qint64 address() const; 25 | quint64 lineAddress() const; 26 | qint64 selectionStartOffset() const; 27 | qint64 selectionEndOffset() const; 28 | qint64 selectionLength() const; 29 | QHexPosition position() const; 30 | QHexPosition selectionStart() const; 31 | QHexPosition selectionEnd() const; 32 | QByteArray selectedBytes() const; 33 | bool hasSelection() const; 34 | bool isSelected(qint64 line, qint64 column) const; 35 | void setMode(Mode m); 36 | void move(qint64 offset); 37 | void move(qint64 line, qint64 column); 38 | void move(QHexPosition pos); 39 | void select(qint64 offset); 40 | void select(qint64 line, qint64 column); 41 | void select(QHexPosition pos); 42 | void selectSize(qint64 length); 43 | qint64 replace(const QVariant& oldvalue, const QVariant& newvalue, 44 | qint64 offset, QHexFindMode mode = QHexFindMode::Text, 45 | unsigned int options = QHexFindOptions::None, 46 | QHexFindDirection fd = QHexFindDirection::Forward) const; 47 | qint64 find(const QVariant& value, qint64 offset, 48 | QHexFindMode mode = QHexFindMode::Text, 49 | unsigned int options = QHexFindOptions::None, 50 | QHexFindDirection fd = QHexFindDirection::Forward) const; 51 | qint64 positionToOffset(QHexPosition pos) const; 52 | QHexPosition offsetToPosition(qint64 offset) const; 53 | 54 | public Q_SLOTS: 55 | void cut(bool hex = false); 56 | void copy(bool hex = false) const; 57 | void paste(bool hex = false); 58 | void selectAll(); 59 | void removeSelection(); 60 | void clearSelection(); 61 | void switchMode(); 62 | 63 | Q_SIGNALS: 64 | void positionChanged(); 65 | void modeChanged(); 66 | 67 | private: 68 | const QHexOptions* m_options; 69 | Mode m_mode{Mode::Overwrite}; 70 | QHexPosition m_position{}, m_selection{}; 71 | 72 | friend class QHexView; 73 | }; 74 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | project(QHexView) 4 | 5 | option(QHEXVIEW_BUILD_EXAMPLE "Build Example Application" ON) 6 | option(QHEXVIEW_USE_QT5 "Enable Qt5 build" OFF) 7 | option(QHEXVIEW_ENABLE_DIALOGS "BuiltIn dialogs" OFF) 8 | 9 | if(QHEXVIEW_USE_QT5) 10 | find_package(Qt5 REQUIRED COMPONENTS Widgets) 11 | else() 12 | find_package(Qt6 COMPONENTS Widgets) 13 | 14 | if(NOT Qt6_FOUND) 15 | find_package(Qt5 REQUIRED COMPONENTS Widgets) 16 | endif() 17 | endif() 18 | 19 | add_library(${PROJECT_NAME} STATIC) 20 | 21 | set_target_properties(${PROJECT_NAME} 22 | PROPERTIES 23 | CXX_STANDARD_REQUIRED YES 24 | CXX_STANDARD 11 25 | AUTOMOC ON 26 | ) 27 | 28 | target_link_libraries(${PROJECT_NAME} 29 | PUBLIC 30 | Qt::Widgets 31 | ) 32 | 33 | target_include_directories(${PROJECT_NAME} 34 | PUBLIC 35 | "${PROJECT_SOURCE_DIR}/include" 36 | ) 37 | 38 | target_sources(${PROJECT_NAME} 39 | PRIVATE 40 | include/QHexView/model/buffer/qdevicebuffer.h 41 | include/QHexView/model/buffer/qhexbuffer.h 42 | include/QHexView/model/buffer/qmappedfilebuffer.h 43 | include/QHexView/model/buffer/qmemorybuffer.h 44 | include/QHexView/model/buffer/qmemoryrefbuffer.h 45 | include/QHexView/model/commands/hexcommand.h 46 | include/QHexView/model/commands/insertcommand.h 47 | include/QHexView/model/commands/removecommand.h 48 | include/QHexView/model/commands/replacecommand.h 49 | include/QHexView/model/commands/replacecommand.h 50 | include/QHexView/model/qhexcursor.h 51 | include/QHexView/model/qhexdelegate.h 52 | include/QHexView/model/qhexdocument.h 53 | include/QHexView/model/qhexmetadata.h 54 | include/QHexView/model/qhexoptions.h 55 | include/QHexView/model/qhexutils.h 56 | include/QHexView/qhexview.h 57 | 58 | PRIVATE 59 | src/model/commands/hexcommand.cpp 60 | src/model/commands/insertcommand.cpp 61 | src/model/commands/removecommand.cpp 62 | src/model/commands/replacecommand.cpp 63 | src/model/buffer/qdevicebuffer.cpp 64 | src/model/buffer/qhexbuffer.cpp 65 | src/model/buffer/qmemorybuffer.cpp 66 | src/model/buffer/qmemoryrefbuffer.cpp 67 | src/model/buffer/qmappedfilebuffer.cpp 68 | src/model/qhexdelegate.cpp 69 | src/model/qhexutils.cpp 70 | src/model/qhexcursor.cpp 71 | src/model/qhexmetadata.cpp 72 | src/model/qhexdocument.cpp 73 | src/qhexview.cpp 74 | ) 75 | 76 | if(QHEXVIEW_ENABLE_DIALOGS) 77 | target_sources(${PROJECT_NAME} 78 | PRIVATE 79 | include/QHexView/dialogs/hexfinddialog.h 80 | src/dialogs/hexfinddialog.cpp 81 | ) 82 | 83 | target_compile_definitions(${PROJECT_NAME} 84 | PUBLIC 85 | QHEXVIEW_ENABLE_DIALOGS 86 | ) 87 | endif() 88 | 89 | if(QHEXVIEW_BUILD_EXAMPLE) 90 | add_subdirectory(example) 91 | endif() 92 | -------------------------------------------------------------------------------- /include/QHexView/model/qhexmetadata.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct QHexMetadataItem { 11 | qint64 begin, end; 12 | QColor foreground, background; 13 | QString comment; 14 | }; 15 | 16 | using QHexMetadataLine = QList; 17 | 18 | class QHexMetadata: public QObject { 19 | Q_OBJECT 20 | 21 | private: 22 | using ClearMetadataCallback = std::function; 23 | 24 | private: 25 | explicit QHexMetadata(const QHexOptions* options, 26 | QObject* parent = nullptr); 27 | 28 | public: 29 | const QHexMetadataLine* find(qint64 line) const; 30 | QString getComment(qint64 line, qint64 column) const; 31 | void removeMetadata(qint64 line); 32 | void removeBackground(qint64 line); 33 | void removeForeground(qint64 line); 34 | void removeComments(qint64 line); 35 | void unhighlight(qint64 line); 36 | void clear(); 37 | 38 | public: 39 | inline void setMetadata(qint64 begin, qint64 end, const QColor& fgcolor, 40 | const QColor& bgcolor, const QString& comment) { 41 | this->setMetadata({begin, end, fgcolor, bgcolor, comment}); 42 | } 43 | 44 | inline void setForeground(qint64 begin, qint64 end, const QColor& fgcolor) { 45 | this->setMetadata(begin, end, fgcolor, QColor(), QString()); 46 | } 47 | 48 | inline void setBackground(qint64 begin, qint64 end, const QColor& bgcolor) { 49 | this->setMetadata(begin, end, QColor(), bgcolor, QString()); 50 | } 51 | 52 | inline void setComment(qint64 begin, qint64 end, const QString& comment) { 53 | this->setMetadata(begin, end, QColor(), QColor(), comment); 54 | }; 55 | 56 | inline void setMetadataSize(qint64 begin, qint64 length, 57 | const QColor& fgcolor, const QColor& bgcolor, 58 | const QString& comment) { 59 | this->setMetadata({begin, begin + length, fgcolor, bgcolor, comment}); 60 | } 61 | 62 | inline void setForegroundSize(qint64 begin, qint64 length, 63 | const QColor& fgcolor) { 64 | this->setForeground(begin, begin + length, fgcolor); 65 | } 66 | 67 | inline void setBackgroundSize(qint64 begin, qint64 length, 68 | const QColor& bgcolor) { 69 | this->setBackground(begin, begin + length, bgcolor); 70 | } 71 | 72 | inline void setCommentSize(qint64 begin, qint64 length, 73 | const QString& comment) { 74 | this->setComment(begin, begin + length, comment); 75 | }; 76 | 77 | private: 78 | void copy(const QHexMetadata* metadata); 79 | void clearMetadata(qint64 line, ClearMetadataCallback&& cb); 80 | void setMetadata(const QHexMetadataItem& mi); 81 | void invalidate(); 82 | 83 | Q_SIGNALS: 84 | void changed(); 85 | void cleared(); 86 | 87 | private: 88 | QHash m_metadata; 89 | const QHexOptions* m_options; 90 | 91 | friend class QHexView; 92 | }; 93 | -------------------------------------------------------------------------------- /src/model/buffer/qdevicebuffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | QDeviceBuffer::QDeviceBuffer(QObject* parent): QHexBuffer{parent} {} 6 | 7 | QDeviceBuffer::~QDeviceBuffer() { 8 | if(!m_device) 9 | return; 10 | 11 | if(m_device->parent() == this) { 12 | if(m_device->isOpen()) 13 | m_device->close(); 14 | m_device->deleteLater(); 15 | } 16 | 17 | m_device = nullptr; 18 | } 19 | 20 | uchar QDeviceBuffer::at(qint64 idx) { 21 | m_device->seek(idx); 22 | 23 | char c = '\0'; 24 | m_device->getChar(&c); 25 | return static_cast(c); 26 | } 27 | 28 | qint64 QDeviceBuffer::length() const { return m_device->size(); } 29 | 30 | void QDeviceBuffer::insert(qint64 offset, const QByteArray& data) { 31 | Q_UNUSED(offset) 32 | Q_UNUSED(data) 33 | // Not implemented 34 | } 35 | 36 | void QDeviceBuffer::replace(qint64 offset, const QByteArray& data) { 37 | m_device->seek(offset); 38 | m_device->write(data); 39 | } 40 | 41 | void QDeviceBuffer::remove(qint64 offset, int length) { 42 | Q_UNUSED(offset) 43 | Q_UNUSED(length) 44 | // Not implemented 45 | } 46 | 47 | QByteArray QDeviceBuffer::read(qint64 offset, int length) { 48 | m_device->seek(offset); 49 | return m_device->read(length); 50 | } 51 | 52 | bool QDeviceBuffer::read(QIODevice* device) { 53 | m_device = device; 54 | if(!m_device) 55 | return false; 56 | if(!m_device->isOpen()) 57 | m_device->open(QIODevice::ReadWrite); 58 | return m_device->isOpen(); 59 | } 60 | 61 | void QDeviceBuffer::write(QIODevice* device) { 62 | Q_UNUSED(device) 63 | // Not implemented 64 | } 65 | 66 | qint64 QDeviceBuffer::indexOf(const QByteArray& ba, qint64 from) { 67 | const auto MAX = std::numeric_limits::max(); 68 | qint64 idx = -1; 69 | 70 | if(from < m_device->size()) { 71 | idx = from; 72 | m_device->seek(from); 73 | 74 | while(idx < m_device->size()) { 75 | QByteArray data = m_device->read(MAX); 76 | int sidx = data.indexOf(ba); 77 | 78 | if(sidx >= 0) { 79 | idx += sidx; 80 | break; 81 | } 82 | 83 | if(idx + data.size() >= m_device->size()) 84 | return -1; 85 | m_device->seek(m_device->pos() + data.size() - ba.size()); 86 | } 87 | } 88 | 89 | return idx; 90 | } 91 | 92 | qint64 QDeviceBuffer::lastIndexOf(const QByteArray& ba, qint64 from) { 93 | const auto MAX = std::numeric_limits::max(); 94 | qint64 idx = -1; 95 | 96 | if(from >= 0 && ba.size() < MAX) { 97 | qint64 currpos = from; 98 | 99 | while(currpos >= 0) { 100 | qint64 readpos = (currpos < MAX) ? 0 : currpos - MAX; 101 | m_device->seek(readpos); 102 | 103 | QByteArray data = m_device->read(currpos - readpos); 104 | int lidx = data.lastIndexOf(ba, from); 105 | 106 | if(lidx >= 0) { 107 | idx = readpos + lidx; 108 | break; 109 | } 110 | 111 | if(readpos <= 0) 112 | break; 113 | currpos = readpos + ba.size(); 114 | } 115 | } 116 | 117 | return idx; 118 | } 119 | -------------------------------------------------------------------------------- /include/QHexView/model/qhexdocument.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class QHexCursor; 8 | 9 | class QHexDocument: public QObject { 10 | Q_OBJECT 11 | 12 | public: 13 | enum class ChangeReason { Insert, Remove, Replace }; 14 | enum class FindDirection { Forward, Backward }; 15 | Q_ENUM(ChangeReason); 16 | Q_ENUM(FindDirection); 17 | 18 | private: 19 | explicit QHexDocument(QHexBuffer* buffer, QObject* parent = nullptr); 20 | bool accept(qint64 idx) const; 21 | 22 | public: 23 | bool isEmpty() const; 24 | bool isModified() const; 25 | bool canUndo() const; 26 | bool canRedo() const; 27 | void setData(const QByteArray& ba); 28 | void setData(QHexBuffer* buffer); 29 | qint64 length() const; 30 | qint64 indexOf(const QByteArray& ba, qint64 from = 0); 31 | qint64 lastIndexOf(const QByteArray& ba, qint64 from = 0); 32 | QByteArray read(qint64 offset, int len = 0) const; 33 | uchar at(int offset) const; 34 | 35 | public Q_SLOTS: 36 | void clearModified(); 37 | void undo(); 38 | void redo(); 39 | void insert(qint64 offset, uchar b); 40 | void replace(qint64 offset, uchar b); 41 | void insert(qint64 offset, const QByteArray& data); 42 | void replace(qint64 offset, const QByteArray& data); 43 | void remove(qint64 offset, int len); 44 | bool saveTo(QIODevice* device); 45 | 46 | public: 47 | template 48 | static QHexDocument* fromDevice(QIODevice* iodevice, 49 | QObject* parent = nullptr); 50 | template 51 | static QHexDocument* fromMemory(char* data, int size, 52 | QObject* parent = nullptr); 53 | template 54 | static QHexDocument* fromMemory(const QByteArray& ba, 55 | QObject* parent = nullptr); 56 | static QHexDocument* fromBuffer(QHexBuffer* buffer, 57 | QObject* parent = nullptr); 58 | static QHexDocument* fromLargeFile(QString filename, 59 | QObject* parent = nullptr); 60 | static QHexDocument* fromMappedFile(QString filename, 61 | QObject* parent = nullptr); 62 | static QHexDocument* fromFile(QString filename, QObject* parent = nullptr); 63 | static QHexDocument* create(QObject* parent = nullptr); 64 | 65 | Q_SIGNALS: 66 | void modifiedChanged(bool modified); 67 | void canUndoChanged(bool canundo); 68 | void canRedoChanged(bool canredo); 69 | void dataChanged(const QByteArray& data, quint64 offset, 70 | QHexDocument::ChangeReason reason); 71 | void changed(); 72 | void reset(); 73 | 74 | private: 75 | QHexBuffer* m_buffer; 76 | QUndoStack m_undostack; 77 | 78 | friend class QHexView; 79 | }; 80 | 81 | template 82 | QHexDocument* QHexDocument::fromDevice(QIODevice* iodevice, QObject* parent) { 83 | QHexBuffer* hexbuffer = new T(parent); 84 | if(Owned) 85 | iodevice->setParent(hexbuffer); 86 | return hexbuffer->read(iodevice) ? new QHexDocument(hexbuffer, parent) 87 | : nullptr; 88 | } 89 | 90 | template 91 | QHexDocument* QHexDocument::fromMemory(char* data, int size, QObject* parent) { 92 | QHexBuffer* hexbuffer = new T(); 93 | hexbuffer->read(data, size); 94 | return new QHexDocument(hexbuffer, parent); 95 | } 96 | 97 | template 98 | QHexDocument* QHexDocument::fromMemory(const QByteArray& ba, QObject* parent) { 99 | QHexBuffer* hexbuffer = new T(); 100 | hexbuffer->read(ba); 101 | return new QHexDocument(hexbuffer, parent); 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | QHexView logo 3 |
4 |
5 | An hexadecimal widget for Qt5 and Qt6 6 |
7 |
8 |
9 | 10 |
11 | 12 | 13 | 14 |
15 |
16 | 17 | QHexView is a free, independent, MIT-licensed open-source project.
18 | It began years ago as a simple viewer for hexadecimal data and has since evolved 19 | into a highly customizable widget for managing binary data, thanks to valuable feedback from users. QHexView hides the complexity of 20 | drawing and input, allowing users to focus on providing the data as a model. 21 | 22 | ## Table Of Content 23 | - [Features](#features) 24 | - [Sponsors](#sponsors) 25 | - [Usage](#usage) 26 | - [Loading Data](#loading-data) 27 | - [Backends](#backends) 28 | 29 | ## Features 30 | - **Document/View Architecture**: Built on a robust document/view design pattern. 31 | - **Unlimited Undo/Redo**: Seamlessly revert or reapply changes without limits. 32 | - **Fully Customizable**: Tailor every aspect to your specific needs. 33 | - **Fast rendering**: Optimized for speed and efficiency. 34 | - **Developer Friendly**: Intuitive and easy to integrate in your codebase. 35 | 36 | ## Sponsors 37 | If you enjoy using QHexView, consider supporting its development by [sponsoring me](https://github.com/sponsors/Dax89).
38 | Your support helps keep the project alive and thriving! 39 | 40 | ## Usage 41 | 42 | ### Loading Data 43 | QHexView manages data through the `QHexDocument` class.
44 | You can load a generic `QIODevice` using the `QHexDocument` method `fromDevice()` with various buffer backends.
45 | Additionally, helper methods are available to load a `QFile` as an in-memory buffer:
46 | ```cpp 47 | #include 48 | 49 | QHexOptions options; 50 | options.grouplength = 2; // Pack bytes as AABB 51 | options.bytecolors[0x00] = {Qt::lightGray, QColor()}; // Highlight '00's 52 | options.bytecolors[0xFF] = {Qt::darkBlue, QColor()}; // Highlight 'FF's 53 | hexview.setOptions(options); 54 | 55 | QHexDocument* document = QHexDocument::fromMemory(bytearray); /* Load data from In-Memory Buffer... */ 56 | //QHexDocument* document = QHexDocument::fromDevice(iodevice); /* ...from a generic I/O device... */ 57 | //QHexDocument* document = QHexDocument::fromFile("data.bin"); /* ...or from File... */ 58 | 59 | QHexView* hexview = new QHexView(); 60 | hexview->setDocument(document); // Associate QHexEditData with this QHexEdit (ownership is not changed) 61 | 62 | // Document editing 63 | QByteArray data = document->read(24, 78); // Read 78 bytes starting to offset 24 64 | document->insert(4, "Hello QHexEdit"); // Insert a string to offset 4 65 | document->remove(6, 10); // Delete bytes from offset 6 to offset 10 66 | document->replace(30, "New Data"); // Replace bytes from offset 30 with the string "New Data" 67 | 68 | // Metatadata management (available from QHexView too) 69 | hexview->setBackground(5, 10, Qt::Red); // Highlight background at offset range [5, 10) 70 | hexview->setForeground(15, 30, Qt::darkBLue); // Highlight background at offset range [15, 30) 71 | hexview->setComment(12, 42, "I'm a comment!"); // Add a comment at offset range [12, 42) 72 | hexview->unhighlight(); // Reset highlighting 73 | hexview->clearMetadata(); // Reset all styles 74 | ``` 75 | 76 | ### Backends 77 | These are the available buffer backends: 78 | - **QMemoryBuffer**: A simple, flat memory. 79 | - **QMemoryRefBuffer**: QHexView just display the referenced data, editing is disabled. 80 | - **QDeviceBuffer**: A read-only view for QIODevice. 81 | - **QMappedFileBuffer**: MMIO wrapper for QFile. 82 | 83 | *It's also possible to create new data backends from scratch!* 84 | -------------------------------------------------------------------------------- /src/model/qhexmetadata.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | QHexMetadata::QHexMetadata(const QHexOptions* options, QObject* parent) 5 | : QObject(parent), m_options(options) {} 6 | 7 | const QHexMetadataLine* QHexMetadata::find(qint64 line) const { 8 | auto it = m_metadata.find(line); 9 | return it != m_metadata.end() ? std::addressof(it.value()) : nullptr; 10 | } 11 | 12 | QString QHexMetadata::getComment(qint64 line, qint64 column) const { 13 | auto* metadataline = this->find(line); 14 | if(!metadataline) 15 | return QString(); 16 | 17 | auto offset = QHexUtils::positionToOffset(m_options, {line, column}); 18 | QStringList comments; 19 | 20 | for(auto& mi : *metadataline) { 21 | if((offset < mi.begin || offset > mi.end) || mi.comment.isEmpty()) 22 | continue; 23 | comments.push_back(mi.comment); 24 | } 25 | 26 | return comments.join("\n"); 27 | } 28 | 29 | void QHexMetadata::removeMetadata(qint64 line) { 30 | auto it = m_metadata.find(line); 31 | if(it == m_metadata.end()) 32 | return; 33 | 34 | m_metadata.erase(it); 35 | Q_EMIT changed(); 36 | } 37 | 38 | void QHexMetadata::removeBackground(qint64 line) { 39 | this->clearMetadata(line, [](QHexMetadataItem& mi) -> bool { 40 | if(!mi.background.isValid()) 41 | return false; 42 | 43 | if(mi.foreground.isValid() || !mi.comment.isEmpty()) { 44 | mi.background = QColor(); 45 | return false; 46 | } 47 | 48 | return true; 49 | }); 50 | } 51 | 52 | void QHexMetadata::removeForeground(qint64 line) { 53 | this->clearMetadata(line, [](QHexMetadataItem& mi) -> bool { 54 | if(!mi.foreground.isValid()) 55 | return false; 56 | 57 | if(mi.background.isValid() || !mi.comment.isEmpty()) { 58 | mi.foreground = QColor(); 59 | return false; 60 | } 61 | 62 | return true; 63 | }); 64 | } 65 | 66 | void QHexMetadata::removeComments(qint64 line) { 67 | this->clearMetadata(line, [](QHexMetadataItem& mi) -> bool { 68 | if(mi.comment.isEmpty()) 69 | return false; 70 | 71 | if(mi.foreground.isValid() || mi.background.isValid()) { 72 | mi.comment.clear(); 73 | return false; 74 | } 75 | 76 | return true; 77 | }); 78 | } 79 | 80 | void QHexMetadata::unhighlight(qint64 line) { 81 | this->clearMetadata(line, [](QHexMetadataItem& mi) -> bool { 82 | if(!mi.foreground.isValid() && !mi.background.isValid()) 83 | return false; 84 | 85 | if(!mi.comment.isEmpty()) { 86 | mi.foreground = QColor(); 87 | mi.background = QColor(); 88 | return false; 89 | } 90 | 91 | return true; 92 | }); 93 | } 94 | 95 | void QHexMetadata::clear() { 96 | m_metadata.clear(); 97 | Q_EMIT changed(); 98 | } 99 | 100 | void QHexMetadata::copy(const QHexMetadata* metadata) { 101 | m_metadata = metadata->m_metadata; 102 | } 103 | 104 | void QHexMetadata::clearMetadata(qint64 line, ClearMetadataCallback&& cb) { 105 | auto iit = m_metadata.find(line); 106 | if(iit == m_metadata.end()) 107 | return; 108 | 109 | auto oldsize = iit->size(); 110 | 111 | for(auto it = iit->begin(); it != iit->end();) { 112 | if(cb(*it)) 113 | it = iit->erase(it); 114 | else 115 | it++; 116 | } 117 | 118 | if(iit->empty()) { 119 | this->removeMetadata(line); 120 | return; 121 | } 122 | 123 | if(oldsize != iit->size()) 124 | Q_EMIT changed(); 125 | } 126 | 127 | void QHexMetadata::setMetadata(const QHexMetadataItem& mi) { 128 | if(!m_options->linelength) 129 | return; 130 | 131 | const qint64 firstline = mi.begin / m_options->linelength; 132 | const qint64 lastline = mi.end / m_options->linelength; 133 | bool notify = false; 134 | 135 | for(auto line = firstline; line <= lastline; line++) { 136 | auto start = line == firstline ? mi.begin % m_options->linelength : 0; 137 | auto length = line == lastline 138 | ? (mi.end % m_options->linelength) - start 139 | : m_options->linelength; 140 | if(length <= 0) 141 | continue; 142 | 143 | notify = true; 144 | m_metadata[line].push_back(mi); 145 | } 146 | 147 | if(notify) 148 | Q_EMIT changed(); 149 | } 150 | 151 | void QHexMetadata::invalidate() { 152 | auto oldmetadata = m_metadata; 153 | m_metadata.clear(); 154 | 155 | for(const QHexMetadataLine& line : oldmetadata) 156 | for(const QHexMetadataItem& mi : line) 157 | this->setMetadata(mi); 158 | } 159 | -------------------------------------------------------------------------------- /src/model/qhexdocument.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | QHexDocument::QHexDocument(QHexBuffer* buffer, QObject* parent) 13 | : QObject(parent) { 14 | m_buffer = buffer; 15 | m_buffer->setParent(this); // Take Ownership 16 | 17 | connect(&m_undostack, &QUndoStack::canUndoChanged, this, 18 | &QHexDocument::canUndoChanged); 19 | connect(&m_undostack, &QUndoStack::canRedoChanged, this, 20 | &QHexDocument::canRedoChanged); 21 | connect(&m_undostack, &QUndoStack::cleanChanged, this, 22 | [&](bool clean) { Q_EMIT modifiedChanged(!clean); }); 23 | } 24 | 25 | qint64 QHexDocument::indexOf(const QByteArray& ba, qint64 from) { 26 | return m_buffer->indexOf(ba, from); 27 | } 28 | 29 | qint64 QHexDocument::lastIndexOf(const QByteArray& ba, qint64 from) { 30 | return m_buffer->lastIndexOf(ba, from); 31 | } 32 | 33 | bool QHexDocument::accept(qint64 idx) const { return m_buffer->accept(idx); } 34 | bool QHexDocument::isEmpty() const { return m_buffer->isEmpty(); } 35 | bool QHexDocument::isModified() const { return !m_undostack.isClean(); } 36 | bool QHexDocument::canUndo() const { return m_undostack.canUndo(); } 37 | bool QHexDocument::canRedo() const { return m_undostack.canRedo(); } 38 | 39 | void QHexDocument::setData(const QByteArray& ba) { 40 | QHexBuffer* mb = new QMemoryBuffer(); 41 | mb->read(ba); 42 | this->setData(mb); 43 | } 44 | 45 | void QHexDocument::setData(QHexBuffer* buffer) { 46 | if(!buffer) 47 | return; 48 | 49 | m_undostack.clear(); 50 | buffer->setParent(this); 51 | 52 | auto* oldbuffer = m_buffer; 53 | m_buffer = buffer; 54 | if(oldbuffer) 55 | oldbuffer->deleteLater(); 56 | 57 | Q_EMIT canUndoChanged(false); 58 | Q_EMIT canRedoChanged(false); 59 | Q_EMIT changed(); 60 | Q_EMIT reset(); 61 | } 62 | 63 | void QHexDocument::clearModified() { m_undostack.setClean(); } 64 | 65 | qint64 QHexDocument::length() const { 66 | return m_buffer ? m_buffer->length() : 0; 67 | } 68 | 69 | uchar QHexDocument::at(int offset) const { return m_buffer->at(offset); } 70 | 71 | QHexDocument* QHexDocument::fromFile(QString filename, QObject* parent) { 72 | QFile f(filename); 73 | f.open(QFile::ReadOnly); 74 | return QHexDocument::fromMemory(f.readAll(), parent); 75 | } 76 | 77 | void QHexDocument::undo() { 78 | m_undostack.undo(); 79 | Q_EMIT changed(); 80 | } 81 | 82 | void QHexDocument::redo() { 83 | m_undostack.redo(); 84 | Q_EMIT changed(); 85 | } 86 | 87 | void QHexDocument::insert(qint64 offset, uchar b) { 88 | this->insert(offset, QByteArray(1, b)); 89 | } 90 | 91 | void QHexDocument::replace(qint64 offset, uchar b) { 92 | this->replace(offset, QByteArray(1, b)); 93 | } 94 | 95 | void QHexDocument::insert(qint64 offset, const QByteArray& data) { 96 | m_undostack.push(new InsertCommand(m_buffer, this, offset, data)); 97 | 98 | Q_EMIT changed(); 99 | Q_EMIT dataChanged(data, offset, ChangeReason::Insert); 100 | } 101 | 102 | void QHexDocument::replace(qint64 offset, const QByteArray& data) { 103 | m_undostack.push(new ReplaceCommand(m_buffer, this, offset, data)); 104 | Q_EMIT changed(); 105 | Q_EMIT dataChanged(data, offset, ChangeReason::Replace); 106 | } 107 | 108 | void QHexDocument::remove(qint64 offset, int len) { 109 | QByteArray data = m_buffer->read(offset, len); 110 | 111 | m_undostack.push(new RemoveCommand(m_buffer, this, offset, len)); 112 | Q_EMIT changed(); 113 | Q_EMIT dataChanged(data, offset, ChangeReason::Remove); 114 | } 115 | 116 | QByteArray QHexDocument::read(qint64 offset, int len) const { 117 | return m_buffer->read(offset, len); 118 | } 119 | 120 | bool QHexDocument::saveTo(QIODevice* device) { 121 | if(!device->isWritable()) 122 | return false; 123 | m_buffer->write(device); 124 | return true; 125 | } 126 | 127 | QHexDocument* QHexDocument::fromBuffer(QHexBuffer* buffer, QObject* parent) { 128 | return new QHexDocument(buffer, parent); 129 | } 130 | 131 | QHexDocument* QHexDocument::fromLargeFile(QString filename, QObject* parent) { 132 | return QHexDocument::fromDevice(new QFile(filename), parent); 133 | } 134 | 135 | QHexDocument* QHexDocument::fromMappedFile(QString filename, QObject* parent) { 136 | return QHexDocument::fromDevice(new QFile(filename), 137 | parent); 138 | } 139 | 140 | QHexDocument* QHexDocument::create(QObject* parent) { 141 | return QHexDocument::fromMemory({}, parent); 142 | } 143 | -------------------------------------------------------------------------------- /src/model/qhexcursor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* 6 | * https://stackoverflow.com/questions/10803043/inverse-column-row-major-order-transformation 7 | * 8 | * If the index is calculated as: 9 | * offset = row + column*NUMROWS 10 | * then the inverse would be: 11 | * row = offset % NUMROWS 12 | * column = offset / NUMROWS 13 | * where % is modulus, and / is integer division. 14 | */ 15 | 16 | QHexCursor::QHexCursor(const QHexOptions* options, QHexView* parent) 17 | : QObject(parent), m_options(options) {} 18 | 19 | QHexView* QHexCursor::hexView() const { 20 | return qobject_cast(this->parent()); 21 | } 22 | 23 | QHexCursor::Mode QHexCursor::mode() const { return m_mode; } 24 | qint64 QHexCursor::offset() const { return this->positionToOffset(m_position); } 25 | 26 | qint64 QHexCursor::address() const { 27 | return m_options->baseaddress + this->offset(); 28 | } 29 | 30 | quint64 QHexCursor::lineAddress() const { 31 | return m_options->baseaddress + (m_position.line * m_options->linelength); 32 | } 33 | 34 | qint64 QHexCursor::selectionStartOffset() const { 35 | return this->positionToOffset(this->selectionStart()); 36 | } 37 | 38 | qint64 QHexCursor::selectionEndOffset() const { 39 | return this->positionToOffset(this->selectionEnd()); 40 | } 41 | 42 | qint64 QHexCursor::line() const { return m_position.line; } 43 | qint64 QHexCursor::column() const { return m_position.column; } 44 | 45 | QHexPosition QHexCursor::selectionStart() const { 46 | if(m_position.line < m_selection.line) 47 | return m_position; 48 | 49 | if(m_position.line == m_selection.line) { 50 | if(m_position.column < m_selection.column) 51 | return m_position; 52 | } 53 | 54 | return m_selection; 55 | } 56 | 57 | QHexPosition QHexCursor::selectionEnd() const { 58 | if(m_position.line > m_selection.line) 59 | return m_position; 60 | 61 | if(m_position.line == m_selection.line) { 62 | if(m_position.column > m_selection.column) 63 | return m_position; 64 | } 65 | 66 | return m_selection; 67 | } 68 | 69 | qint64 QHexCursor::selectionLength() const { 70 | auto selstart = this->selectionStartOffset(), 71 | selend = this->selectionEndOffset(); 72 | return selstart == selend ? 0 : selend - selstart + 1; 73 | } 74 | 75 | QHexPosition QHexCursor::position() const { return m_position; } 76 | 77 | QByteArray QHexCursor::selectedBytes() const { 78 | return this->hexView()->selectedBytes(); 79 | } 80 | 81 | bool QHexCursor::hasSelection() const { return m_position != m_selection; } 82 | 83 | bool QHexCursor::isSelected(qint64 line, qint64 column) const { 84 | if(!this->hasSelection()) 85 | return false; 86 | 87 | auto selstart = this->selectionStart(), selend = this->selectionEnd(); 88 | if(line > selstart.line && line < selend.line) 89 | return true; 90 | if(line == selstart.line && line == selend.line) 91 | return column >= selstart.column && column <= selend.column; 92 | if(line == selstart.line) 93 | return column >= selstart.column; 94 | if(line == selend.line) 95 | return column <= selend.column; 96 | return false; 97 | } 98 | 99 | void QHexCursor::setMode(Mode m) { 100 | if(m_mode == m) 101 | return; 102 | m_mode = m; 103 | Q_EMIT modeChanged(); 104 | } 105 | 106 | void QHexCursor::switchMode() { 107 | switch(m_mode) { 108 | case Mode::Insert: this->setMode(Mode::Overwrite); break; 109 | case Mode::Overwrite: this->setMode(Mode::Insert); break; 110 | } 111 | } 112 | 113 | void QHexCursor::move(qint64 offset) { 114 | this->move(this->offsetToPosition(offset)); 115 | } 116 | 117 | void QHexCursor::move(qint64 line, qint64 column) { 118 | return this->move({line, column}); 119 | } 120 | 121 | void QHexCursor::move(QHexPosition pos) { 122 | if(pos.line >= 0) 123 | m_selection.line = pos.line; 124 | if(pos.column >= 0) 125 | m_selection.column = pos.column; 126 | this->select(pos); 127 | } 128 | 129 | void QHexCursor::select(qint64 offset) { 130 | this->select(this->offsetToPosition(offset)); 131 | } 132 | 133 | void QHexCursor::select(qint64 line, qint64 column) { 134 | this->select({line, column}); 135 | } 136 | 137 | void QHexCursor::select(QHexPosition pos) { 138 | if(pos.line >= 0) 139 | m_position.line = pos.line; 140 | if(pos.column >= 0) 141 | m_position.column = pos.column; 142 | Q_EMIT positionChanged(); 143 | } 144 | 145 | void QHexCursor::selectSize(qint64 length) { 146 | if(length > 0) 147 | length--; 148 | else if(length < 0) 149 | length++; 150 | if(length) 151 | this->select(this->offset() + length); 152 | } 153 | 154 | qint64 QHexCursor::replace(const QVariant& oldvalue, const QVariant& newvalue, 155 | qint64 offset, QHexFindMode mode, 156 | unsigned int options, QHexFindDirection fd) const { 157 | return this->hexView()->replace(oldvalue, newvalue, offset, mode, options, 158 | fd); 159 | } 160 | qint64 QHexCursor::find(const QVariant& value, qint64 offset, QHexFindMode mode, 161 | unsigned int options, QHexFindDirection fd) const { 162 | return this->hexView()->find(value, offset, mode, options, fd); 163 | } 164 | 165 | void QHexCursor::cut(bool hex) { this->hexView()->cut(hex); } 166 | void QHexCursor::copy(bool hex) const { this->hexView()->copy(hex); } 167 | void QHexCursor::paste(bool hex) { this->hexView()->paste(hex); } 168 | void QHexCursor::selectAll() { this->hexView()->selectAll(); } 169 | void QHexCursor::removeSelection() { this->hexView()->removeSelection(); } 170 | 171 | void QHexCursor::clearSelection() { 172 | m_position = m_selection; 173 | Q_EMIT positionChanged(); 174 | } 175 | 176 | qint64 QHexCursor::positionToOffset(QHexPosition pos) const { 177 | return QHexUtils::positionToOffset(m_options, pos); 178 | } 179 | 180 | QHexPosition QHexCursor::offsetToPosition(qint64 offset) const { 181 | return QHexUtils::offsetToPosition(m_options, offset); 182 | } 183 | -------------------------------------------------------------------------------- /include/QHexView/qhexview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define QHEXVIEW_VERSION 5.0 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #if defined(QHEXVIEW_ENABLE_DIALOGS) 15 | class HexFindDialog; 16 | #endif 17 | 18 | class QHexView: public QAbstractScrollArea { 19 | Q_OBJECT 20 | 21 | public: 22 | enum class CopyMode { Visual, HexArraySquare, HexArrayCurly, HexArrayChar }; 23 | Q_ENUM(CopyMode); 24 | 25 | public: 26 | explicit QHexView(QWidget* parent = nullptr); 27 | QRectF headerRect() const; 28 | QRectF addressRect() const; 29 | QRectF hexRect() const; 30 | QRectF asciiRect() const; 31 | QHexDocument* hexDocument() const; 32 | QHexCursor* hexCursor() const; 33 | const QHexMetadata* hexMetadata() const; 34 | QHexOptions options() const; 35 | QColor getReadableColor(QColor c) const; 36 | QByteArray selectedBytes() const; 37 | QByteArray getLine(qint64 line) const; 38 | unsigned int addressWidth() const; 39 | unsigned int lineLength() const; 40 | bool isModified() const; 41 | bool canUndo() const; 42 | bool canRedo() const; 43 | quint64 offset() const; 44 | quint64 address() const; 45 | QHexPosition positionFromOffset(quint64 offset) const; 46 | QHexPosition positionFromAddress(quint64 address) const; 47 | QHexPosition position() const; 48 | QHexPosition selectionStart() const; 49 | QHexPosition selectionEnd() const; 50 | quint64 selectionStartOffset() const; 51 | quint64 selectionEndOffset() const; 52 | quint64 baseAddress() const; 53 | quint64 lines() const; 54 | qint64 replace(const QVariant& oldvalue, const QVariant& newvalue, 55 | qint64 offset, QHexFindMode mode = QHexFindMode::Text, 56 | unsigned int options = QHexFindOptions::None, 57 | QHexFindDirection fd = QHexFindDirection::Forward) const; 58 | qint64 find(const QVariant& value, qint64 offset, 59 | QHexFindMode mode = QHexFindMode::Text, 60 | unsigned int options = QHexFindOptions::None, 61 | QHexFindDirection fd = QHexFindDirection::Forward) const; 62 | void setOptions(const QHexOptions& options); 63 | void setBaseAddress(quint64 baseaddress); 64 | void setDelegate(QHexDelegate* rd); 65 | void setDocument(QHexDocument* doc); 66 | void setData(const QByteArray& ba); 67 | void setData(QHexBuffer* buffer); 68 | void setCursorMode(QHexCursor::Mode mode); 69 | void setByteColor(quint8 b, QHexColor c); 70 | void setByteForeground(quint8 b, QColor c); 71 | void setByteBackground(quint8 b, QColor c); 72 | void setMetadata(qint64 begin, qint64 end, const QColor& fgcolor, 73 | const QColor& bgcolor, const QString& comment); 74 | void setForeground(qint64 begin, qint64 end, const QColor& fgcolor); 75 | void setBackground(qint64 begin, qint64 end, const QColor& bgcolor); 76 | void setComment(qint64 begin, qint64 end, const QString& comment); 77 | void setMetadataSize(qint64 begin, qint64 length, const QColor& fgcolor, 78 | const QColor& bgcolor, const QString& comment); 79 | void setForegroundSize(qint64 begin, qint64 length, const QColor& fgcolor); 80 | void setBackgroundSize(qint64 begin, qint64 length, const QColor& bgcolor); 81 | void setCommentSize(qint64 begin, qint64 length, const QString& comment); 82 | void removeMetadata(qint64 line); 83 | void removeBackground(qint64 line); 84 | void removeForeground(qint64 line); 85 | void removeComments(qint64 line); 86 | void unhighlight(qint64 line); 87 | void clearMetadata(); 88 | 89 | public Q_SLOTS: 90 | #if defined(QHEXVIEW_ENABLE_DIALOGS) 91 | void showFind(); 92 | void showReplace(); 93 | #endif 94 | void undo(); 95 | void redo(); 96 | void cut(bool hex = false); 97 | void copyAs(CopyMode mode = CopyMode::Visual) const; 98 | void copy(bool hex = false) const; 99 | void paste(bool hex = false); 100 | void clearModified(); 101 | void selectAll(); 102 | void removeSelection(); 103 | void switchMode(); 104 | void setAddressWidth(unsigned int w); 105 | void setLineLength(unsigned int l); 106 | void setGroupLength(unsigned int l); 107 | void setScrollSteps(int scrollsteps); 108 | void setReadOnly(bool r); 109 | void setAutoWidth(bool r); 110 | 111 | private: 112 | void paint(QPainter* painter) const; 113 | void checkOptions(); 114 | void checkState(); 115 | void checkAndUpdate(bool calccolumns = false); 116 | void calcColumns(); 117 | void ensureVisible(); 118 | void drawSeparators(QPainter* p) const; 119 | void drawHeader(QTextCursor& c) const; 120 | void drawDocument(QTextCursor& c) const; 121 | QTextCharFormat drawFormat(QTextCursor& c, quint8 b, const QString& s, 122 | QHexArea area, qint64 line, qint64 column, 123 | bool applyformat) const; 124 | unsigned int calcAddressWidth() const; 125 | int visibleLines(bool absolute = false) const; 126 | qint64 getLastColumn(qint64 line) const; 127 | qint64 lastLine() const; 128 | qreal getNCellsWidth(int n) const; 129 | qreal hexColumnWidth() const; 130 | qreal hexColumnX() const; 131 | qreal asciiColumnX() const; 132 | qreal endColumnX() const; 133 | qreal cellWidth() const; 134 | qreal lineHeight() const; 135 | qint64 positionFromLineCol(qint64 line, qint64 col) const; 136 | QHexPosition positionFromPoint(QPoint pt) const; 137 | QPoint absolutePoint(QPoint pt) const; 138 | QHexArea areaFromPoint(QPoint pt) const; 139 | void moveNext(bool select = false); 140 | void movePrevious(bool select = false); 141 | bool keyPressMove(QKeyEvent* e); 142 | bool keyPressTextInput(QKeyEvent* e); 143 | bool keyPressAction(QKeyEvent* e); 144 | 145 | protected: 146 | bool event(QEvent* e) override; 147 | void showEvent(QShowEvent* e) override; 148 | void paintEvent(QPaintEvent*) override; 149 | void resizeEvent(QResizeEvent* e) override; 150 | void focusInEvent(QFocusEvent* e) override; 151 | void focusOutEvent(QFocusEvent* e) override; 152 | void mousePressEvent(QMouseEvent* e) override; 153 | void mouseMoveEvent(QMouseEvent* e) override; 154 | void wheelEvent(QWheelEvent* e) override; 155 | void keyPressEvent(QKeyEvent* e) override; 156 | 157 | private: 158 | static QString reduced(const QString& s, int maxlen); 159 | static bool isColorLight(QColor c); 160 | 161 | Q_SIGNALS: 162 | void dataChanged(const QByteArray& data, quint64 offset, 163 | QHexDocument::ChangeReason reason); 164 | void modifiedChanged(bool modified); 165 | void positionChanged(); 166 | void modeChanged(); 167 | 168 | private: 169 | bool m_readonly{false}, m_writing{false}, m_autowidth{false}; 170 | QHexArea m_currentarea{QHexArea::Ascii}; 171 | QList m_hexcolumns; 172 | QFontMetricsF m_fontmetrics; 173 | QHexOptions m_options; 174 | QHexCursor* m_hexcursor{nullptr}; 175 | QHexDocument* m_hexdocument{nullptr}; 176 | QHexMetadata* m_hexmetadata{nullptr}; 177 | QHexDelegate* m_hexdelegate{nullptr}; 178 | #if defined(QHEXVIEW_ENABLE_DIALOGS) 179 | HexFindDialog *m_hexdlgfind{nullptr}, *m_hexdlgreplace{nullptr}; 180 | #endif 181 | 182 | friend class QHexDelegate; 183 | friend class QHexCursor; 184 | }; 185 | -------------------------------------------------------------------------------- /src/model/qhexutils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if defined(_WIN32) && _MSC_VER <= 1916 // v141_xp 12 | #include 13 | namespace std { 14 | using ::tolower; 15 | } 16 | #else 17 | #include 18 | #endif 19 | 20 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 21 | #define QHEXVIEW_VARIANT_EQ(x, t) ((x).metaType().id() == QMetaType::Q##t) 22 | #else 23 | #define QHEXVIEW_VARIANT_EQ(x, t) ((x).type() == QVariant::t) 24 | #endif 25 | 26 | namespace QHexUtils { 27 | 28 | Q_GLOBAL_STATIC_WITH_ARGS(QList, HEXMAP, 29 | ({'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 30 | 'a', 'b', 'c', 'd', 'e', 'f'})); 31 | 32 | bool isHex(char ch) { 33 | return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || 34 | (ch >= 'a' && ch <= 'f'); 35 | } 36 | 37 | namespace PatternUtils { 38 | 39 | Q_GLOBAL_STATIC_WITH_ARGS(QString, WILDCARD_BYTE, ("??")) 40 | 41 | bool check(QString& p, qint64& len) { 42 | static QHash> 43 | processed; // Cache processed patterns 44 | 45 | auto it = processed.find(p); 46 | 47 | if(it != processed.end()) { 48 | p = it.value().first; 49 | len = it.value().second; 50 | return true; 51 | } 52 | 53 | QString op = p; // Store unprocessed pattern 54 | p = p.simplified().replace(" ", ""); 55 | if(p.isEmpty() || (p.size() % 2)) 56 | return false; 57 | 58 | int wccount = 0; 59 | 60 | for(auto i = 0; i < p.size() - 2; i += 2) { 61 | const auto& hexb = p.mid(i, 2); 62 | 63 | if(hexb == *WILDCARD_BYTE) { 64 | wccount++; 65 | continue; 66 | } 67 | 68 | if(!QHexUtils::isHex(hexb.at(0).toLatin1()) || 69 | !QHexUtils::isHex(hexb.at(1).toLatin1())) 70 | return false; 71 | } 72 | 73 | if(wccount >= p.size()) 74 | return false; 75 | len = p.size() / 2; 76 | processed[op] = qMakePair(p, len); // Cache processed pattern 77 | return true; 78 | } 79 | 80 | bool match(const QByteArray& data, const QString& pattern) { 81 | for(qint64 i = 0, idx = 0; (i <= (pattern.size() - 2)); i += 2, idx++) { 82 | if(idx >= data.size()) 83 | return false; 84 | 85 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 86 | QStringView hexb = QStringView{pattern}.mid(i, 2); 87 | #else 88 | const QStringRef& hexb = pattern.midRef(i, 2); 89 | #endif 90 | 91 | if(hexb == *WILDCARD_BYTE) 92 | continue; 93 | 94 | bool ok = false; 95 | auto b = static_cast(hexb.toUInt(&ok, 16)); 96 | if(!ok || (b != data.at(idx))) 97 | return false; 98 | } 99 | 100 | return true; 101 | } 102 | 103 | } // namespace PatternUtils 104 | 105 | namespace { 106 | 107 | unsigned int countBits(uint val) { 108 | if(val <= std::numeric_limits::max()) 109 | return QHexFindOptions::Int8; 110 | if(val <= std::numeric_limits::max()) 111 | return QHexFindOptions::Int16; 112 | if(val <= std::numeric_limits::max()) 113 | return QHexFindOptions::Int32; 114 | 115 | return QHexFindOptions::Int64; 116 | } 117 | 118 | template 119 | qint64 findIter(qint64 startoffset, QHexFindDirection fd, 120 | const QHexView* hexview, Function&& f) { 121 | QHexDocument* hexdocument = hexview->hexDocument(); 122 | qint64 offset = -1; 123 | 124 | QHexFindDirection cfd = fd; 125 | if(cfd == QHexFindDirection::All) 126 | cfd = QHexFindDirection::Forward; 127 | 128 | qint64 i = startoffset; 129 | 130 | bool restartLoopOnce = true; 131 | 132 | while(offset == -1 && 133 | (cfd == QHexFindDirection::Backward ? (i >= 0) 134 | : (i < hexdocument->length()))) { 135 | if(!f(i, offset)) 136 | break; 137 | 138 | if(cfd == QHexFindDirection::Backward) 139 | i--; 140 | else 141 | i++; 142 | 143 | if(fd == QHexFindDirection::All && i >= hexdocument->length() && 144 | restartLoopOnce) { 145 | i = 0; 146 | restartLoopOnce = false; 147 | } 148 | } 149 | 150 | return offset; 151 | } 152 | 153 | qint64 findDefault(const QByteArray& value, qint64 startoffset, 154 | const QHexView* hexview, unsigned int options, 155 | QHexFindDirection fd) { 156 | QHexDocument* hexdocument = hexview->hexDocument(); 157 | if(value.size() > hexdocument->length()) 158 | return -1; 159 | 160 | return findIter( 161 | startoffset, fd, hexview, 162 | [options, value, hexdocument](qint64 idx, qint64& offset) -> bool { 163 | for(auto i = 0; i < value.size(); i++) { 164 | qint64 curroffset = idx + i; 165 | 166 | if(curroffset >= hexdocument->length()) { 167 | offset = -1; 168 | return false; 169 | } 170 | 171 | uchar ch1 = hexdocument->at(curroffset); 172 | uchar ch2 = value.at(i); 173 | 174 | if(!(options & QHexFindOptions::CaseSensitive)) { 175 | ch1 = std::tolower(ch1); 176 | ch2 = std::tolower(ch2); 177 | } 178 | 179 | if(ch1 != ch2) 180 | break; 181 | if(i == value.size() - 1) 182 | offset = idx; 183 | } 184 | 185 | return true; 186 | }); 187 | } 188 | 189 | qint64 findWildcard(QString pattern, qint64 startoffset, 190 | const QHexView* hexview, QHexFindDirection fd, 191 | qint64& patternlen) { 192 | QHexDocument* hexdocument = hexview->hexDocument(); 193 | if(!PatternUtils::check(pattern, patternlen) || 194 | (patternlen >= hexdocument->length())) 195 | return -1; 196 | 197 | return findIter( 198 | startoffset, fd, hexview, 199 | [hexdocument, pattern, patternlen](qint64 idx, qint64& offset) -> bool { 200 | if(PatternUtils::match(hexdocument->read(idx, patternlen), pattern)) 201 | offset = idx; 202 | return true; 203 | }); 204 | } 205 | 206 | QByteArray variantToByteArray(QVariant value, QHexFindMode mode, 207 | unsigned int options) { 208 | QByteArray v; 209 | 210 | switch(mode) { 211 | case QHexFindMode::Text: 212 | if(QHEXVIEW_VARIANT_EQ(value, String)) 213 | v = value.toString().toUtf8(); 214 | else if(QHEXVIEW_VARIANT_EQ(value, ByteArray)) 215 | v = value.toByteArray(); 216 | break; 217 | 218 | case QHexFindMode::Hex: { 219 | if(QHEXVIEW_VARIANT_EQ(value, String)) { 220 | qint64 len = 0; 221 | auto s = value.toString(); 222 | if(!PatternUtils::check(s, len)) 223 | return {}; 224 | 225 | bool ok = true; 226 | 227 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 228 | for(auto i = 0; ok && i < s.size(); i += 2) 229 | v.push_back(static_cast( 230 | QStringView{s}.mid(i, 2).toUInt(&ok, 16))); 231 | #else 232 | for(auto i = 0; ok && i < s.size(); i += 2) 233 | v.push_back( 234 | static_cast(s.midRef(i, 2).toUInt(&ok, 16))); 235 | #endif 236 | 237 | if(!ok) 238 | return {}; 239 | } 240 | else if(QHEXVIEW_VARIANT_EQ(value, ByteArray)) 241 | v = value.toByteArray(); 242 | break; 243 | } 244 | 245 | case QHexFindMode::Int: { 246 | bool ok = false; 247 | uint val = value.toUInt(&ok); 248 | if(!ok) 249 | return QByteArray{}; 250 | 251 | QDataStream ds(&v, QIODevice::WriteOnly); 252 | 253 | if(options & QHexFindOptions::BigEndian) { 254 | if(options & QHexFindOptions::Int8) 255 | ds << qToBigEndian(val); 256 | else if(options & QHexFindOptions::Int16) 257 | ds << qToBigEndian(val); 258 | else if(options & QHexFindOptions::Int32) 259 | ds << qToBigEndian(val); 260 | else if(options & QHexFindOptions::Int64) 261 | ds << qToBigEndian(val); 262 | else 263 | return variantToByteArray(value, mode, 264 | options | countBits(val)); 265 | } 266 | else { 267 | if(options & QHexFindOptions::Int8) 268 | ds << static_cast(val); 269 | else if(options & QHexFindOptions::Int16) 270 | ds << static_cast(val); 271 | else if(options & QHexFindOptions::Int32) 272 | ds << static_cast(val); 273 | else if(options & QHexFindOptions::Int64) 274 | ds << static_cast(val); 275 | else 276 | return variantToByteArray(value, mode, 277 | options | countBits(val)); 278 | } 279 | 280 | break; 281 | } 282 | 283 | case QHexFindMode::Float: { 284 | bool ok = false; 285 | QDataStream ds(&v, QIODevice::WriteOnly); 286 | if(options & QHexFindOptions::Float) 287 | ds << value.toFloat(&ok); 288 | else if(options & QHexFindOptions::Double) 289 | ds << value.toDouble(&ok); 290 | if(!ok) 291 | return {}; 292 | } 293 | 294 | default: break; 295 | } 296 | 297 | return v; 298 | } 299 | 300 | } // namespace 301 | 302 | QByteArray toHex(const QByteArray& ba, char sep) { 303 | if(ba.isEmpty()) { 304 | return QByteArray(); 305 | } 306 | 307 | QByteArray hex(sep ? (ba.size() * 3 - 1) : (ba.size() * 2), 308 | Qt::Uninitialized); 309 | 310 | for(auto i = 0, o = 0; i < ba.size(); i++) { 311 | if(sep && i) 312 | hex[o++] = static_cast(sep); 313 | hex[o++] = HEXMAP->at((ba.at(i) & 0xf0) >> 4); 314 | hex[o++] = HEXMAP->at(ba.at(i) & 0x0f); 315 | } 316 | 317 | return hex; 318 | } 319 | 320 | QByteArray toHex(const QByteArray& ba) { return QHexUtils::toHex(ba, '\0'); } 321 | qint64 positionToOffset(const QHexOptions* options, QHexPosition pos) { 322 | return options->linelength * pos.line + pos.column; 323 | } 324 | QHexPosition offsetToPosition(const QHexOptions* options, qint64 offset) { 325 | return {offset / options->linelength, offset % options->linelength}; 326 | } 327 | 328 | QPair find(const QHexView* hexview, QVariant value, 329 | qint64 startoffset, QHexFindMode mode, 330 | unsigned int options, QHexFindDirection fd) { 331 | qint64 offset = -1, size = 0; 332 | if(startoffset == -1) 333 | startoffset = static_cast(hexview->offset()); 334 | 335 | if(mode == QHexFindMode::Hex && QHEXVIEW_VARIANT_EQ(value, String)) { 336 | offset = QHexUtils::findWildcard(value.toString(), startoffset, hexview, 337 | fd, size); 338 | } 339 | else { 340 | auto ba = variantToByteArray(value, mode, options); 341 | 342 | if(!ba.isEmpty()) { 343 | offset = 344 | QHexUtils::findDefault(ba, startoffset, hexview, options, fd); 345 | size = ba.size(); 346 | } 347 | else 348 | offset = -1; 349 | } 350 | 351 | return {offset, offset > -1 ? size : 0}; 352 | } 353 | 354 | bool checkPattern(QString pattern) { 355 | qint64 len = 0; 356 | return PatternUtils::check(pattern, len); 357 | } 358 | 359 | QPair replace(const QHexView* hexview, QVariant oldvalue, 360 | QVariant newvalue, qint64 startoffset, 361 | QHexFindMode mode, unsigned int options, 362 | QHexFindDirection fd) { 363 | auto res = 364 | QHexUtils::find(hexview, oldvalue, startoffset, mode, options, fd); 365 | 366 | if(res.first != -1 && res.second > 0) { 367 | QHexDocument* hexdocument = hexview->hexDocument(); 368 | auto ba = variantToByteArray(newvalue, mode, options); 369 | 370 | if(!ba.isEmpty()) { 371 | hexdocument->remove(res.first, res.second); 372 | hexdocument->insert(res.first, ba); 373 | res.second = ba.size(); 374 | } 375 | else { 376 | res.first = -1; 377 | res.second = 0; 378 | } 379 | } 380 | 381 | return res; 382 | } 383 | 384 | } // namespace QHexUtils 385 | -------------------------------------------------------------------------------- /src/dialogs/hexfinddialog.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | const QString HexFindDialog::BUTTONBOX = "qhexview_buttonbox"; 24 | const QString HexFindDialog::CBFINDMODE = "qhexview_cbfindmode"; 25 | const QString HexFindDialog::LEFIND = "qhexview_lefind"; 26 | const QString HexFindDialog::LEREPLACE = "qhexview_lereplace"; 27 | const QString HexFindDialog::HLAYOUT = "qhexview_hlayout"; 28 | const QString HexFindDialog::GBOPTIONS = "qhexview_gboptions"; 29 | const QString HexFindDialog::RBALL = "qhexview_rball"; 30 | const QString HexFindDialog::RBFORWARD = "qhexview_rbforward"; 31 | const QString HexFindDialog::RBBACKWARD = "qhexview_rbbackward"; 32 | 33 | HexFindDialog::HexFindDialog(Type type, QHexView* parent) 34 | : QDialog{parent}, m_type{type} { 35 | m_hexvalidator = new QRegularExpressionValidator( 36 | QRegularExpression{"[0-9A-Fa-f ]+"}, this); 37 | m_hexpvalidator = new QRegularExpressionValidator( 38 | QRegularExpression{"[0-9A-Fa-f \\?]+"}, this); 39 | m_dblvalidator = new QDoubleValidator(this); 40 | m_intvalidator = new QIntValidator(this); 41 | 42 | this->setWindowTitle(type == Type::Replace ? tr("Replace...") 43 | : tr("Find...")); 44 | 45 | auto* vlayout = new QVBoxLayout(this); 46 | auto* gridlayout = new QGridLayout(); 47 | 48 | auto* cbfindmode = new QComboBox(this); 49 | cbfindmode->setObjectName(HexFindDialog::CBFINDMODE); 50 | cbfindmode->addItem("Text", static_cast(QHexFindMode::Text)); 51 | cbfindmode->addItem("Hex", static_cast(QHexFindMode::Hex)); 52 | cbfindmode->addItem("Int", static_cast(QHexFindMode::Int)); 53 | cbfindmode->addItem("Float", static_cast(QHexFindMode::Float)); 54 | 55 | QLineEdit *lereplace = nullptr, *lefind = new QLineEdit(this); 56 | lefind->setObjectName(HexFindDialog::LEFIND); 57 | 58 | gridlayout->addWidget(new QLabel(tr("Mode:"), this), 0, 0, Qt::AlignRight); 59 | gridlayout->addWidget(cbfindmode, 0, 1); 60 | gridlayout->addWidget(new QLabel(tr("Find:"), this), 1, 0, Qt::AlignRight); 61 | gridlayout->addWidget(lefind, 1, 1); 62 | 63 | if(type == Type::Replace) { 64 | lereplace = new QLineEdit(this); 65 | lereplace->setObjectName(HexFindDialog::LEREPLACE); 66 | 67 | gridlayout->addWidget(new QLabel(tr("Replace:"), this), 2, 0, 68 | Qt::AlignRight); 69 | gridlayout->addWidget(lereplace, 2, 1); 70 | } 71 | 72 | vlayout->addLayout(gridlayout); 73 | 74 | auto* gboptions = new QGroupBox(this); 75 | gboptions->setObjectName(HexFindDialog::GBOPTIONS); 76 | gboptions->setTitle(tr("Options")); 77 | gboptions->setLayout(new QStackedLayout()); 78 | 79 | QGroupBox* gbdirection = new QGroupBox(this); 80 | gbdirection->setTitle(tr("Find direction")); 81 | auto* gbvlayout = new QVBoxLayout(gbdirection); 82 | 83 | auto* rball = new QRadioButton("All", gbdirection); 84 | rball->setObjectName(HexFindDialog::RBALL); 85 | auto* rbforward = new QRadioButton("Forward", gbdirection); 86 | rbforward->setObjectName(HexFindDialog::RBFORWARD); 87 | rbforward->setChecked(true); 88 | auto* rbbackward = new QRadioButton("Backward", gbdirection); 89 | rbbackward->setObjectName(HexFindDialog::RBBACKWARD); 90 | 91 | gbvlayout->addWidget(rball); 92 | gbvlayout->addWidget(rbforward); 93 | gbvlayout->addWidget(rbbackward); 94 | gbvlayout->addSpacerItem( 95 | new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); 96 | 97 | auto* hlayout = new QHBoxLayout(); 98 | hlayout->setObjectName(HexFindDialog::HLAYOUT); 99 | hlayout->addWidget(gboptions, 1); 100 | hlayout->addWidget(gbdirection); 101 | vlayout->addLayout(hlayout, 1); 102 | 103 | auto* buttonbox = new QDialogButtonBox(this); 104 | buttonbox->setOrientation(Qt::Horizontal); 105 | 106 | if(type == Type::Replace) 107 | buttonbox->setStandardButtons(QDialogButtonBox::Ok | 108 | QDialogButtonBox::Apply | 109 | QDialogButtonBox::Cancel); 110 | else 111 | buttonbox->setStandardButtons(QDialogButtonBox::Ok | 112 | QDialogButtonBox::Cancel); 113 | 114 | buttonbox->setObjectName(HexFindDialog::BUTTONBOX); 115 | buttonbox->button(QDialogButtonBox::Ok)->setEnabled(false); 116 | buttonbox->button(QDialogButtonBox::Ok)->setText(tr("Find")); 117 | 118 | if(type == Type::Replace) { 119 | buttonbox->button(QDialogButtonBox::Apply)->setEnabled(false); 120 | buttonbox->button(QDialogButtonBox::Apply)->setText(tr("Replace")); 121 | } 122 | 123 | vlayout->addWidget(buttonbox); 124 | 125 | connect(lefind, &QLineEdit::textChanged, this, 126 | &HexFindDialog::validateActions); 127 | connect(cbfindmode, QOverload::of(&QComboBox::currentIndexChanged), 128 | this, &HexFindDialog::updateFindOptions); 129 | connect(buttonbox, &QDialogButtonBox::accepted, this, &HexFindDialog::find); 130 | connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject); 131 | connect(parent, &QHexView::positionChanged, this, 132 | [this]() { m_startoffset = -1; }); 133 | 134 | if(lereplace) { 135 | connect(buttonbox->button(QDialogButtonBox::Apply), 136 | &QPushButton::clicked, this, &HexFindDialog::replace); 137 | connect(lereplace, &QLineEdit::textChanged, this, 138 | &HexFindDialog::validateActions); 139 | } 140 | 141 | this->prepareTextMode(gboptions->layout()); 142 | this->prepareHexMode(gboptions->layout()); 143 | this->prepareIntMode(gboptions->layout()); 144 | this->prepareFloatMode(gboptions->layout()); 145 | this->updateFindOptions(-1); 146 | } 147 | 148 | QHexView* HexFindDialog::hexView() const { 149 | return qobject_cast(this->parentWidget()); 150 | } 151 | 152 | void HexFindDialog::updateFindOptions(int) { 153 | QGroupBox* gboptions = 154 | this->findChild(HexFindDialog::GBOPTIONS); 155 | 156 | QLineEdit* lefind = this->findChild(HexFindDialog::LEFIND); 157 | QLineEdit* lereplace = 158 | this->findChild(HexFindDialog::LEREPLACE); 159 | lefind->clear(); 160 | if(lereplace) 161 | lereplace->clear(); 162 | 163 | bool ok = false; 164 | QHexFindMode mode = static_cast( 165 | this->findChild(HexFindDialog::CBFINDMODE) 166 | ->currentData() 167 | .toInt(&ok)); 168 | if(!ok) 169 | return; 170 | 171 | m_findoptions = QHexFindOptions::None; 172 | m_oldidxbits = m_oldidxendian = -1; 173 | 174 | auto* stack = qobject_cast(gboptions->layout()); 175 | 176 | switch(mode) { 177 | case QHexFindMode::Text: { 178 | lefind->setValidator(nullptr); 179 | if(lereplace) 180 | lereplace->setValidator(nullptr); 181 | 182 | stack->setCurrentIndex(0); 183 | gboptions->setVisible(true); 184 | break; 185 | } 186 | 187 | case QHexFindMode::Hex: { 188 | lefind->setValidator(m_hexpvalidator); 189 | if(lereplace) 190 | lereplace->setValidator(m_hexvalidator); 191 | stack->setCurrentIndex(1); 192 | gboptions->setVisible(false); 193 | break; 194 | } 195 | 196 | case QHexFindMode::Int: { 197 | lefind->setValidator(m_intvalidator); 198 | if(lereplace) 199 | lereplace->setValidator(m_intvalidator); 200 | 201 | stack->setCurrentIndex(2); 202 | gboptions->setVisible(true); 203 | break; 204 | } 205 | 206 | case QHexFindMode::Float: { 207 | lefind->setValidator(m_dblvalidator); 208 | if(lereplace) 209 | lereplace->setValidator(m_dblvalidator); 210 | 211 | stack->setCurrentIndex(3); 212 | gboptions->setVisible(true); 213 | break; 214 | } 215 | } 216 | } 217 | 218 | bool HexFindDialog::validateIntRange(uint v) const { 219 | if(m_findoptions & QHexFindOptions::Int8) 220 | return !(v > std::numeric_limits::max()); 221 | if(m_findoptions & QHexFindOptions::Int16) 222 | return !(v > std::numeric_limits::max()); 223 | if(m_findoptions & QHexFindOptions::Int32) 224 | return !(v > std::numeric_limits::max()); 225 | return true; 226 | } 227 | 228 | void HexFindDialog::checkResult(const QString& q, qint64 offset, 229 | QHexFindDirection fd) { 230 | if(offset == -1) { 231 | QMessageBox::information(this, tr("Not found"), 232 | tr("Cannot find '%1'").arg(q)); 233 | return; 234 | } 235 | 236 | if(fd == QHexFindDirection::Backward) 237 | m_startoffset = this->hexView()->selectionStartOffset() - 1; 238 | else 239 | m_startoffset = this->hexView()->selectionEndOffset() + 1; 240 | } 241 | 242 | void HexFindDialog::validateActions() { 243 | auto mode = static_cast( 244 | this->findChild(HexFindDialog::CBFINDMODE) 245 | ->currentData() 246 | .toUInt()); 247 | auto* lefind = this->findChild(HexFindDialog::LEFIND); 248 | auto* lereplace = this->findChild(HexFindDialog::LEREPLACE); 249 | auto* buttonbox = 250 | this->findChild(HexFindDialog::BUTTONBOX); 251 | 252 | bool findenable = false, replaceenable = false; 253 | 254 | switch(mode) { 255 | case QHexFindMode::Hex: 256 | findenable = QHexUtils::checkPattern(lefind->text()); 257 | replaceenable = findenable; 258 | break; 259 | 260 | case QHexFindMode::Float: { 261 | lefind->text().toFloat(&findenable); 262 | if(lereplace && findenable) 263 | lereplace->text().toFloat(&replaceenable); 264 | break; 265 | } 266 | 267 | case QHexFindMode::Int: { 268 | auto v = lefind->text().toUInt(&findenable); 269 | if(findenable && !this->validateIntRange(v)) 270 | findenable = false; 271 | if(lereplace && findenable) 272 | lereplace->text().toUInt(&replaceenable); 273 | break; 274 | } 275 | 276 | default: 277 | findenable = !lefind->text().isEmpty(); 278 | replaceenable = findenable; 279 | break; 280 | } 281 | 282 | if(lereplace) 283 | buttonbox->button(QDialogButtonBox::Apply)->setEnabled(replaceenable); 284 | buttonbox->button(QDialogButtonBox::Ok)->setEnabled(findenable); 285 | } 286 | 287 | void HexFindDialog::replace() { 288 | QString q1; 289 | QHexFindMode mode; 290 | QHexFindDirection fd; 291 | 292 | if(!this->prepareOptions(q1, mode, fd)) 293 | return; 294 | 295 | QString q2 = this->findChild(HexFindDialog::LEREPLACE)->text(); 296 | auto offset = this->hexView()->hexCursor()->replace( 297 | q1, q2, m_startoffset > -1 ? m_startoffset : this->hexView()->offset(), 298 | mode, m_findoptions, fd); 299 | this->checkResult(q1, offset, fd); 300 | } 301 | 302 | void HexFindDialog::find() { 303 | QString q; 304 | QHexFindMode mode; 305 | QHexFindDirection fd; 306 | 307 | if(!this->prepareOptions(q, mode, fd)) 308 | return; 309 | 310 | auto offset = this->hexView()->hexCursor()->find( 311 | q, m_startoffset > -1 ? m_startoffset : this->hexView()->offset(), mode, 312 | m_findoptions, fd); 313 | this->checkResult(q, offset, fd); 314 | } 315 | 316 | bool HexFindDialog::prepareOptions(QString& q, QHexFindMode& mode, 317 | QHexFindDirection& fd) { 318 | q = this->findChild(HexFindDialog::LEFIND)->text(); 319 | mode = static_cast( 320 | this->findChild(HexFindDialog::CBFINDMODE) 321 | ->currentData() 322 | .toUInt()); 323 | 324 | if(mode == QHexFindMode::Hex && !QHexUtils::checkPattern(q)) { 325 | QMessageBox::warning(this, tr("Pattern Error"), 326 | tr("Hex pattern '%1' is not valid").arg(q)); 327 | return false; 328 | } 329 | 330 | if(this->findChild(HexFindDialog::RBBACKWARD)->isChecked()) 331 | fd = QHexFindDirection::Backward; 332 | else if(this->findChild(HexFindDialog::RBFORWARD) 333 | ->isChecked()) 334 | fd = QHexFindDirection::Forward; 335 | else 336 | fd = QHexFindDirection::All; 337 | return true; 338 | } 339 | 340 | void HexFindDialog::prepareTextMode(QLayout* l) { 341 | auto* cbcasesensitive = new QCheckBox("Case sensitive"); 342 | 343 | connect(cbcasesensitive, &QCheckBox::stateChanged, this, [this](int state) { 344 | if(state == Qt::Checked) 345 | m_findoptions |= QHexFindOptions::CaseSensitive; 346 | else 347 | m_findoptions &= ~QHexFindOptions::CaseSensitive; 348 | }); 349 | 350 | auto* vlayout = new QVBoxLayout(new QWidget()); 351 | vlayout->addWidget(cbcasesensitive); 352 | l->addWidget(vlayout->parentWidget()); 353 | } 354 | 355 | void HexFindDialog::prepareHexMode(QLayout* l) { l->addWidget(new QWidget()); } 356 | 357 | void HexFindDialog::prepareIntMode(QLayout* l) { 358 | static const QList> INT_TYPES = { 359 | qMakePair("(any)", 0), 360 | qMakePair("8", QHexFindOptions::Int8), 361 | qMakePair("16", QHexFindOptions::Int16), 362 | qMakePair("32", QHexFindOptions::Int32), 363 | qMakePair("64", QHexFindOptions::Int64)}; 364 | 365 | auto* cbbits = new QComboBox(); 366 | for(const auto& it : INT_TYPES) 367 | cbbits->addItem(it.first, it.second); 368 | 369 | connect(cbbits, QOverload::of(&QComboBox::currentIndexChanged), this, 370 | [this, cbbits](int index) { 371 | if(m_oldidxbits > -1) 372 | m_findoptions &= ~cbbits->itemData(m_oldidxbits).toUInt(); 373 | m_findoptions |= cbbits->itemData(index).toUInt(); 374 | m_oldidxbits = index; 375 | }); 376 | 377 | auto* cbendian = new QComboBox(); 378 | cbendian->addItem("Little Endian", 0); 379 | cbendian->addItem("Big Endian", QHexFindOptions::BigEndian); 380 | 381 | connect(cbendian, QOverload::of(&QComboBox::currentIndexChanged), this, 382 | [this, cbendian](int index) { 383 | if(m_oldidxendian > -1) 384 | m_findoptions &= 385 | ~cbendian->itemData(m_oldidxendian).toUInt(); 386 | m_findoptions |= cbendian->itemData(index).toUInt(); 387 | m_oldidxendian = index; 388 | }); 389 | 390 | auto* vlayout = new QVBoxLayout(new QWidget()); 391 | 392 | QGridLayout* gl = new QGridLayout(); 393 | gl->addWidget(new QLabel("Type:"), 0, 0, Qt::AlignRight); 394 | gl->addWidget(cbbits, 0, 1); 395 | gl->addWidget(new QLabel("Endian:"), 1, 0, Qt::AlignRight); 396 | gl->addWidget(cbendian, 1, 1); 397 | vlayout->addLayout(gl); 398 | 399 | l->addWidget(vlayout->parentWidget()); 400 | } 401 | 402 | void HexFindDialog::prepareFloatMode(QLayout* l) { 403 | static const QList> FLOAT_TYPES = { 404 | qMakePair("float", QHexFindOptions::Float), 405 | qMakePair("double", QHexFindOptions::Double)}; 406 | 407 | bool first = true; 408 | auto* vlayout = new QVBoxLayout(new QWidget()); 409 | 410 | for(const auto& ft : FLOAT_TYPES) { 411 | auto* rb = new QRadioButton(ft.first); 412 | rb->setChecked(first); 413 | vlayout->addWidget(rb); 414 | first = false; 415 | } 416 | 417 | l->addWidget(vlayout->parentWidget()); 418 | } 419 | -------------------------------------------------------------------------------- /src/qhexview.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #if defined(QHEXVIEW_ENABLE_DIALOGS) 21 | #include 22 | #endif 23 | 24 | #if defined(QHEXVIEW_DEBUG) 25 | #include 26 | #define qhexview_fmtprint(fmt, ...) qDebug("%s " fmt, __func__, __VA_ARGS__) 27 | #else 28 | #define qhexview_fmtprint(fmt, ...) 29 | #endif 30 | 31 | QHexView::QHexView(QWidget* parent) 32 | : QAbstractScrollArea(parent), m_fontmetrics(this->font()) { 33 | QFont f = QFontDatabase::systemFont(QFontDatabase::FixedFont); 34 | 35 | if(f.styleHint() != QFont::TypeWriter) { 36 | f.setFamily("Monospace"); // Force Monospaced font 37 | f.setStyleHint(QFont::TypeWriter); 38 | } 39 | 40 | this->setFont(f); 41 | this->setMouseTracking(true); 42 | this->setFocusPolicy(Qt::StrongFocus); 43 | this->viewport()->setCursor(Qt::IBeamCursor); 44 | 45 | QPalette p = this->palette(); 46 | p.setBrush(QPalette::Window, p.base()); 47 | 48 | connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this, 49 | [this](int) { this->viewport()->update(); }); 50 | 51 | m_hexmetadata = new QHexMetadata(&m_options, this); 52 | connect(m_hexmetadata, &QHexMetadata::changed, this, 53 | [this]() { this->viewport()->update(); }); 54 | 55 | m_hexcursor = new QHexCursor(&m_options, this); 56 | this->setDocument( 57 | QHexDocument::fromMemory(QByteArray(), this)); 58 | this->checkState(); 59 | 60 | connect(m_hexcursor, &QHexCursor::positionChanged, this, [this]() { 61 | m_writing = false; 62 | this->ensureVisible(); 63 | Q_EMIT positionChanged(); 64 | }); 65 | 66 | connect(m_hexcursor, &QHexCursor::modeChanged, this, [this]() { 67 | m_writing = false; 68 | this->viewport()->update(); 69 | Q_EMIT modeChanged(); 70 | }); 71 | } 72 | 73 | QRectF QHexView::headerRect() const { 74 | if(m_options.hasFlag(QHexFlags::NoHeader)) 75 | return QRectF(); 76 | 77 | return QRectF(0, 0, this->endColumnX(), this->lineHeight()); 78 | } 79 | 80 | QRectF QHexView::addressRect() const { 81 | qreal y = m_options.hasFlag(QHexFlags::NoHeader) ? 0 : this->lineHeight(); 82 | 83 | return QRectF(0, y, this->endColumnX(), this->height() - y); 84 | } 85 | 86 | QRectF QHexView::hexRect() const { 87 | qreal y = m_options.hasFlag(QHexFlags::NoHeader) ? 0 : this->lineHeight(); 88 | 89 | return QRectF(this->hexColumnX(), y, 90 | this->asciiColumnX() - this->hexColumnX(), 91 | this->height() - y); 92 | } 93 | 94 | QRectF QHexView::asciiRect() const { 95 | qreal y = m_options.hasFlag(QHexFlags::NoHeader) ? 0 : this->lineHeight(); 96 | 97 | return QRectF(this->asciiColumnX(), y, 98 | this->endColumnX() - this->asciiColumnX(), 99 | this->height() - y); 100 | } 101 | 102 | QHexDocument* QHexView::hexDocument() const { return m_hexdocument; } 103 | QHexCursor* QHexView::hexCursor() const { 104 | return m_hexdocument ? m_hexcursor : nullptr; 105 | } 106 | const QHexMetadata* QHexView::hexMetadata() const { return m_hexmetadata; } 107 | QHexOptions QHexView::options() const { return m_options; } 108 | 109 | void QHexView::setOptions(const QHexOptions& options) { 110 | auto oldlinelength = m_options.linelength; 111 | m_options = options; 112 | 113 | if(oldlinelength != m_options.linelength) 114 | m_hexmetadata->invalidate(); 115 | 116 | this->checkAndUpdate(); 117 | } 118 | 119 | void QHexView::setBaseAddress(quint64 baseaddress) { 120 | if(m_options.baseaddress == baseaddress) 121 | return; 122 | 123 | m_options.baseaddress = baseaddress; 124 | this->checkAndUpdate(); 125 | } 126 | 127 | void QHexView::setDelegate(QHexDelegate* rd) { 128 | if(m_hexdelegate == rd) 129 | return; 130 | m_hexdelegate = rd; 131 | this->checkAndUpdate(); 132 | } 133 | 134 | void QHexView::setDocument(QHexDocument* doc) { 135 | if(!doc) 136 | doc = QHexDocument::fromMemory(QByteArray(), this); 137 | if(!doc->parent()) 138 | doc->setParent(this); 139 | 140 | m_writing = false; 141 | m_hexmetadata->clear(); 142 | m_hexcursor->move(0); 143 | 144 | if(m_hexdocument) { 145 | disconnect(m_hexdocument, &QHexDocument::changed, this, nullptr); 146 | disconnect(m_hexdocument, &QHexDocument::dataChanged, this, nullptr); 147 | disconnect(m_hexdocument, &QHexDocument::reset, this, nullptr); 148 | disconnect(m_hexdocument, &QHexDocument::modifiedChanged, this, 149 | nullptr); 150 | } 151 | 152 | m_hexdocument = doc; 153 | 154 | connect(m_hexdocument, &QHexDocument::reset, this, [this]() { 155 | m_writing = false; 156 | m_hexcursor->move(0); 157 | this->checkAndUpdate(true); 158 | }); 159 | 160 | connect(m_hexdocument, &QHexDocument::dataChanged, this, 161 | &QHexView::dataChanged); 162 | 163 | connect(m_hexdocument, &QHexDocument::modifiedChanged, this, 164 | &QHexView::modifiedChanged); 165 | 166 | connect(m_hexdocument, &QHexDocument::changed, this, 167 | [this]() { this->checkAndUpdate(true); }); 168 | 169 | this->checkAndUpdate(true); 170 | } 171 | 172 | void QHexView::setData(const QByteArray& ba) { m_hexdocument->setData(ba); } 173 | void QHexView::setData(QHexBuffer* buffer) { m_hexdocument->setData(buffer); } 174 | 175 | void QHexView::setCursorMode(QHexCursor::Mode mode) { 176 | m_hexcursor->setMode(mode); 177 | } 178 | 179 | void QHexView::setByteColor(quint8 b, QHexColor c) { 180 | m_options.bytecolors[b] = c; 181 | this->checkAndUpdate(); 182 | } 183 | 184 | void QHexView::setByteForeground(quint8 b, QColor c) { 185 | m_options.bytecolors[b].foreground = c; 186 | this->checkAndUpdate(); 187 | } 188 | 189 | void QHexView::setByteBackground(quint8 b, QColor c) { 190 | m_options.bytecolors[b].background = c; 191 | this->checkAndUpdate(); 192 | } 193 | 194 | void QHexView::setMetadata(qint64 begin, qint64 end, const QColor& fgcolor, 195 | const QColor& bgcolor, const QString& comment) { 196 | m_hexmetadata->setMetadata(begin, end, fgcolor, bgcolor, comment); 197 | } 198 | void QHexView::setForeground(qint64 begin, qint64 end, const QColor& fgcolor) { 199 | m_hexmetadata->setForeground(begin, end, fgcolor); 200 | } 201 | void QHexView::setBackground(qint64 begin, qint64 end, const QColor& bgcolor) { 202 | m_hexmetadata->setBackground(begin, end, bgcolor); 203 | } 204 | void QHexView::setComment(qint64 begin, qint64 end, const QString& comment) { 205 | m_hexmetadata->setComment(begin, end, comment); 206 | } 207 | void QHexView::setMetadataSize(qint64 begin, qint64 length, 208 | const QColor& fgcolor, const QColor& bgcolor, 209 | const QString& comment) { 210 | m_hexmetadata->setMetadataSize(begin, length, fgcolor, bgcolor, comment); 211 | } 212 | void QHexView::setForegroundSize(qint64 begin, qint64 length, 213 | const QColor& fgcolor) { 214 | m_hexmetadata->setForegroundSize(begin, length, fgcolor); 215 | } 216 | void QHexView::setBackgroundSize(qint64 begin, qint64 length, 217 | const QColor& bgcolor) { 218 | m_hexmetadata->setBackgroundSize(begin, length, bgcolor); 219 | } 220 | void QHexView::setCommentSize(qint64 begin, qint64 length, 221 | const QString& comment) { 222 | m_hexmetadata->setCommentSize(begin, length, comment); 223 | } 224 | void QHexView::removeMetadata(qint64 line) { 225 | m_hexmetadata->removeMetadata(line); 226 | } 227 | void QHexView::removeBackground(qint64 line) { 228 | m_hexmetadata->removeBackground(line); 229 | } 230 | void QHexView::removeForeground(qint64 line) { 231 | m_hexmetadata->removeForeground(line); 232 | } 233 | void QHexView::removeComments(qint64 line) { 234 | m_hexmetadata->removeComments(line); 235 | } 236 | void QHexView::unhighlight(qint64 line) { m_hexmetadata->unhighlight(line); } 237 | void QHexView::clearMetadata() { m_hexmetadata->clear(); } 238 | 239 | #if defined(QHEXVIEW_ENABLE_DIALOGS) 240 | void QHexView::showFind() { 241 | if(!m_hexdlgfind) 242 | m_hexdlgfind = new HexFindDialog(HexFindDialog::Type::Find, this); 243 | m_hexdlgfind->show(); 244 | } 245 | 246 | void QHexView::showReplace() { 247 | if(!m_hexdlgreplace) 248 | m_hexdlgreplace = new HexFindDialog(HexFindDialog::Type::Replace, this); 249 | m_hexdlgreplace->show(); 250 | } 251 | #endif 252 | 253 | void QHexView::undo() { 254 | if(m_hexdocument) 255 | m_hexdocument->undo(); 256 | } 257 | void QHexView::redo() { 258 | if(m_hexdocument) 259 | m_hexdocument->redo(); 260 | } 261 | 262 | void QHexView::cut(bool hex) { 263 | this->copy(hex); 264 | if(m_readonly) 265 | return; 266 | 267 | if(m_hexcursor->hasSelection()) 268 | this->removeSelection(); 269 | else 270 | m_hexdocument->remove(m_hexcursor->offset(), 1); 271 | } 272 | 273 | void QHexView::copyAs(CopyMode mode) const { 274 | QClipboard* c = qApp->clipboard(); 275 | 276 | QByteArray bytes = m_hexcursor->hasSelection() 277 | ? m_hexcursor->selectedBytes() 278 | : m_hexdocument->read(m_hexcursor->offset(), 1); 279 | 280 | switch(mode) { 281 | case CopyMode::HexArrayCurly: 282 | case CopyMode::HexArraySquare: { 283 | QString hexchar; 284 | int i = 0; 285 | 286 | for(char b : bytes) { 287 | if(!hexchar.isEmpty()) { 288 | hexchar += ", "; 289 | if(m_options.copybreak && !(++i % m_options.linelength)) 290 | hexchar += "\n"; 291 | } 292 | 293 | hexchar += 294 | "0x" + QString::number(static_cast(b), 16).toUpper(); 295 | } 296 | 297 | c->setText( 298 | QString(mode == CopyMode::HexArraySquare ? "[%1]" : "{%1}") 299 | .arg(hexchar)); 300 | break; 301 | } 302 | 303 | case CopyMode::HexArrayChar: { 304 | QString hexchar; 305 | 306 | for(char b : bytes) 307 | hexchar += 308 | "\\x" + QString::number(static_cast(b), 16).toUpper(); 309 | 310 | c->setText(QString("\"%1\"").arg(hexchar)); 311 | break; 312 | } 313 | 314 | default: { 315 | QString hexchar; 316 | 317 | for(int i = 0; i < bytes.size(); i++) { 318 | if(!(i % m_options.grouplength)) { 319 | if(!hexchar.isEmpty()) { 320 | hexchar += ", "; 321 | if(m_options.copybreak && !(i % m_options.linelength)) 322 | hexchar += "\n"; 323 | } 324 | 325 | hexchar += "0x"; 326 | } 327 | 328 | hexchar += QString("%1") 329 | .arg(static_cast(bytes[i]), 2, 16, 330 | QLatin1Char('0')) 331 | .toUpper(); 332 | } 333 | 334 | c->setText(hexchar); 335 | break; 336 | } 337 | } 338 | } 339 | 340 | void QHexView::copy(bool hex) const { 341 | QClipboard* c = qApp->clipboard(); 342 | 343 | QByteArray bytes = m_hexcursor->hasSelection() 344 | ? m_hexcursor->selectedBytes() 345 | : m_hexdocument->read(m_hexcursor->offset(), 1); 346 | 347 | if(hex) 348 | bytes = QHexUtils::toHex(bytes, ' ').toUpper(); 349 | c->setText(bytes); 350 | } 351 | 352 | void QHexView::paste(bool hex) { 353 | if(m_readonly) 354 | return; 355 | 356 | QClipboard* c = qApp->clipboard(); 357 | QByteArray pastedata = c->text().toUtf8(); 358 | if(pastedata.isEmpty()) 359 | return; 360 | 361 | this->removeSelection(); 362 | if(hex) 363 | pastedata = QByteArray::fromHex(pastedata); 364 | 365 | if(m_hexcursor->mode() == QHexCursor::Mode::Insert) 366 | m_hexdocument->insert(m_hexcursor->offset(), pastedata); 367 | else 368 | m_hexdocument->replace(m_hexcursor->offset(), pastedata); 369 | } 370 | 371 | void QHexView::clearModified() { 372 | if(m_hexdocument) 373 | m_hexdocument->clearModified(); 374 | } 375 | 376 | void QHexView::selectAll() { 377 | m_hexcursor->move(0); 378 | m_hexcursor->select(m_hexdocument->length()); 379 | } 380 | 381 | void QHexView::removeSelection() { 382 | if(!m_hexcursor->hasSelection()) 383 | return; 384 | if(!m_readonly) 385 | m_hexdocument->remove(m_hexcursor->selectionStartOffset(), 386 | m_hexcursor->selectionLength() - 1); 387 | m_hexcursor->clearSelection(); 388 | } 389 | 390 | void QHexView::switchMode() { m_hexcursor->switchMode(); } 391 | 392 | void QHexView::setAddressWidth(unsigned int w) { 393 | if(w == m_options.addresswidth) 394 | return; 395 | m_options.addresswidth = w; 396 | this->checkState(); 397 | } 398 | 399 | void QHexView::setScrollSteps(int scrollsteps) { 400 | m_options.scrollsteps = scrollsteps; 401 | } 402 | 403 | void QHexView::setReadOnly(bool r) { m_readonly = r; } 404 | 405 | void QHexView::setAutoWidth(bool r) { 406 | if(m_autowidth == r) 407 | return; 408 | m_autowidth = r; 409 | this->checkState(); 410 | } 411 | 412 | void QHexView::paint(QPainter* painter) const { 413 | QTextDocument doc; 414 | doc.setDocumentMargin(0); 415 | doc.setUndoRedoEnabled(false); 416 | doc.setDefaultFont(this->font()); 417 | 418 | QTextCursor c(&doc); 419 | 420 | this->drawHeader(c); 421 | this->drawDocument(c); 422 | 423 | painter->translate(-this->horizontalScrollBar()->value(), 0); 424 | doc.drawContents(painter); 425 | this->drawSeparators(painter); 426 | } 427 | 428 | void QHexView::checkOptions() { 429 | if(m_options.grouplength > m_options.linelength) 430 | m_options.grouplength = m_options.linelength; 431 | 432 | m_options.addresswidth = 433 | qMax(m_options.addresswidth, this->calcAddressWidth()); 434 | 435 | // Round to nearest multiple of 2 436 | m_options.grouplength = 437 | 1u << (static_cast(qFloor(m_options.grouplength / 2.0))); 438 | 439 | if(m_options.grouplength <= 1) 440 | m_options.grouplength = 1; 441 | 442 | if(!m_options.headercolor.isValid()) 443 | m_options.headercolor = 444 | this->palette().color(QPalette::Normal, QPalette::Highlight); 445 | } 446 | 447 | void QHexView::setLineLength(unsigned int l) { 448 | if(l == m_options.linelength) 449 | return; 450 | m_options.linelength = l; 451 | m_hexmetadata->invalidate(); 452 | this->checkAndUpdate(true); 453 | } 454 | 455 | void QHexView::setGroupLength(unsigned int l) { 456 | if(l == m_options.grouplength) 457 | return; 458 | m_options.grouplength = l; 459 | this->checkAndUpdate(true); 460 | } 461 | 462 | void QHexView::checkState() { 463 | if(!m_hexdocument) 464 | return; 465 | this->checkOptions(); 466 | 467 | int doclines = static_cast(this->lines()), 468 | vislines = this->visibleLines(true); 469 | qint64 vscrollmax = doclines - vislines; 470 | if(doclines >= vislines) 471 | vscrollmax++; 472 | 473 | this->verticalScrollBar()->setRange(0, qMax(0, vscrollmax)); 474 | this->verticalScrollBar()->setPageStep(vislines - 1); 475 | this->verticalScrollBar()->setSingleStep(m_options.scrollsteps); 476 | 477 | int vw = this->verticalScrollBar()->isVisible() 478 | ? this->verticalScrollBar()->width() 479 | : 0; 480 | 481 | static int oldmw = 0; 482 | if(!oldmw) 483 | oldmw = this->maximumWidth(); 484 | this->setMaximumWidth(m_autowidth ? qCeil(this->endColumnX() + vw + 3) 485 | : oldmw); 486 | 487 | this->horizontalScrollBar()->setRange( 488 | 0, qMax(0, this->endColumnX() - this->width() + vw + 3)); 489 | this->horizontalScrollBar()->setPageStep(this->width()); 490 | } 491 | 492 | void QHexView::checkAndUpdate(bool calccolumns) { 493 | this->checkState(); 494 | if(calccolumns) 495 | this->calcColumns(); 496 | this->viewport()->update(); 497 | } 498 | 499 | void QHexView::calcColumns() { 500 | if(!m_hexdocument) 501 | return; 502 | 503 | m_hexcolumns.clear(); 504 | m_hexcolumns.reserve(m_options.linelength); 505 | 506 | auto x = this->hexColumnX(), cw = this->cellWidth() * 2; 507 | 508 | for(auto i = 0u; i < m_options.linelength; i++) { 509 | for(auto j = 0u; j < m_options.grouplength; j++, x += cw) 510 | m_hexcolumns.push_back(QRect(x, 0, cw, 0)); 511 | 512 | x += this->cellWidth(); 513 | } 514 | } 515 | 516 | void QHexView::ensureVisible() { 517 | if(!m_hexdocument) 518 | return; 519 | 520 | auto pos = m_hexcursor->position(); 521 | auto vlines = this->visibleLines(); 522 | 523 | if(pos.line >= (this->verticalScrollBar()->value() + vlines)) 524 | this->verticalScrollBar()->setValue(pos.line - vlines + 1); 525 | else if(pos.line < this->verticalScrollBar()->value()) 526 | this->verticalScrollBar()->setValue(pos.line); 527 | else 528 | this->viewport()->update(); 529 | } 530 | 531 | void QHexView::drawSeparators(QPainter* p) const { 532 | if(!m_options.hasFlag(QHexFlags::Separators)) 533 | return; 534 | 535 | auto oldpen = p->pen(); 536 | p->setPen(m_options.separatorcolor.isValid() 537 | ? m_options.separatorcolor 538 | : this->palette().color(QPalette::Dark)); 539 | 540 | if(m_options.hasFlag(QHexFlags::HSeparator)) { 541 | QLineF l(0, m_fontmetrics.lineSpacing(), this->endColumnX(), 542 | m_fontmetrics.lineSpacing()); 543 | if(!m_hexdelegate || !m_hexdelegate->paintSeparator(p, l, this)) 544 | p->drawLine(l); 545 | } 546 | 547 | if(m_options.hasFlag(QHexFlags::VSeparator)) { 548 | QLineF l1(this->hexColumnX(), 0, this->hexColumnX(), this->height()); 549 | QLineF l2(this->asciiColumnX(), 0, this->asciiColumnX(), 550 | this->height()); 551 | 552 | if(!m_hexdelegate || 553 | (m_hexdelegate && !m_hexdelegate->paintSeparator(p, l1, this))) 554 | p->drawLine(l1); 555 | 556 | if(!m_hexdelegate || 557 | (m_hexdelegate && !m_hexdelegate->paintSeparator(p, l2, this))) 558 | p->drawLine(l2); 559 | } 560 | 561 | p->setPen(oldpen); 562 | } 563 | 564 | void QHexView::drawHeader(QTextCursor& c) const { 565 | if(m_options.hasFlag(QHexFlags::NoHeader)) 566 | return; 567 | 568 | static const auto RESET_FORMAT = [](const QHexOptions& options, 569 | QTextCharFormat& cf) { 570 | cf = {}; 571 | cf.setForeground(options.headercolor); 572 | }; 573 | 574 | QString addresslabel; 575 | if(m_hexdelegate) 576 | addresslabel = m_hexdelegate->addressHeader(this); 577 | if(addresslabel.isEmpty() && !m_options.addresslabel.isEmpty()) 578 | addresslabel = m_options.addresslabel; 579 | 580 | QTextCharFormat cf; 581 | RESET_FORMAT(m_options, cf); 582 | if(m_hexdelegate) 583 | m_hexdelegate->renderHeaderPart(addresslabel, QHexArea::Address, cf, 584 | this); 585 | c.insertText( 586 | " " + QHexView::reduced(addresslabel, this->addressWidth()) + " ", cf); 587 | 588 | if(m_hexdelegate) 589 | RESET_FORMAT(m_options, cf); 590 | 591 | QString hexlabel; 592 | if(m_hexdelegate) 593 | hexlabel = m_hexdelegate->hexHeader(this); 594 | if(hexlabel.isEmpty()) 595 | hexlabel = m_options.hexlabel; 596 | 597 | if(hexlabel.isNull()) { 598 | c.insertText(" ", {}); 599 | 600 | for(auto i = 0u; i < m_options.linelength; i += m_options.grouplength) { 601 | QString h = QString::number(i, 16) 602 | .rightJustified(m_options.grouplength * 2, '0') 603 | .toUpper(); 604 | 605 | if(m_hexdelegate) { 606 | RESET_FORMAT(m_options, cf); 607 | m_hexdelegate->renderHeaderPart(h, QHexArea::Hex, cf, this); 608 | } 609 | 610 | if(m_hexcursor->column() == static_cast(i) && 611 | m_options.hasFlag(QHexFlags::HighlightColumn)) { 612 | cf.setBackground(this->palette().color(QPalette::Highlight)); 613 | cf.setForeground( 614 | this->palette().color(QPalette::HighlightedText)); 615 | } 616 | 617 | c.insertText(h, cf); 618 | c.insertText(" ", {}); 619 | RESET_FORMAT(m_options, cf); 620 | } 621 | } 622 | else { 623 | if(m_hexdelegate) 624 | m_hexdelegate->renderHeaderPart(hexlabel, QHexArea::Hex, cf, this); 625 | c.insertText( 626 | " " + 627 | QHexView::reduced( 628 | hexlabel, (this->hexColumnWidth() / this->cellWidth()) - 1) + 629 | " "); 630 | } 631 | 632 | if(m_hexdelegate) 633 | RESET_FORMAT(m_options, cf); 634 | 635 | QString asciilabel; 636 | if(m_hexdelegate) 637 | asciilabel = m_hexdelegate->asciiHeader(this); 638 | if(asciilabel.isEmpty()) 639 | asciilabel = m_options.asciilabel; 640 | 641 | if(asciilabel.isNull()) { 642 | c.insertText(" ", {}); 643 | 644 | for(unsigned int i = 0; i < m_options.linelength; i++) { 645 | QString a = QString::number(i, 16).toUpper(); 646 | 647 | if(m_hexdelegate) { 648 | RESET_FORMAT(m_options, cf); 649 | m_hexdelegate->renderHeaderPart(a, QHexArea::Ascii, cf, this); 650 | } 651 | 652 | if(m_hexcursor->column() == static_cast(i) && 653 | m_options.hasFlag(QHexFlags::HighlightColumn)) { 654 | cf.setBackground(this->palette().color(QPalette::Highlight)); 655 | cf.setForeground( 656 | this->palette().color(QPalette::HighlightedText)); 657 | } 658 | 659 | c.insertText(a, cf); 660 | RESET_FORMAT(m_options, cf); 661 | } 662 | 663 | c.insertText(" ", {}); 664 | } 665 | else { 666 | if(m_hexdelegate) 667 | m_hexdelegate->renderHeaderPart(asciilabel, QHexArea::Ascii, cf, 668 | this); 669 | c.insertText(" " + 670 | QHexView::reduced(asciilabel, ((this->endColumnX() - 671 | this->asciiColumnX() - 672 | this->cellWidth()) / 673 | this->cellWidth()) - 674 | 1) + 675 | " "); 676 | } 677 | 678 | QTextBlockFormat bf; 679 | if(m_options.hasFlag(QHexFlags::StyledHeader)) 680 | bf.setBackground(this->palette().color(QPalette::Window)); 681 | if(m_hexdelegate) 682 | m_hexdelegate->renderHeader(bf, this); 683 | c.setBlockFormat(bf); 684 | c.insertBlock(); 685 | } 686 | 687 | void QHexView::drawDocument(QTextCursor& c) const { 688 | if(!m_hexdocument) 689 | return; 690 | 691 | qreal y = !m_options.hasFlag(QHexFlags::NoHeader) ? this->lineHeight() : 0; 692 | quint64 line = static_cast(this->verticalScrollBar()->value()); 693 | 694 | QTextCharFormat addrformat; 695 | addrformat.setForeground( 696 | this->palette().color(QPalette::Normal, QPalette::Highlight)); 697 | 698 | for(qint64 l = 0; m_hexdocument->isEmpty() || 699 | (line < this->lines() && l < this->visibleLines()); 700 | l++, line++, y += this->lineHeight()) { 701 | quint64 address = line * m_options.linelength + this->baseAddress(); 702 | QString addrstr = QString::number(address, 16) 703 | .rightJustified(this->addressWidth(), '0') 704 | .toUpper(); 705 | 706 | // Address Part 707 | QTextCharFormat acf; 708 | acf.setForeground(m_options.headercolor); 709 | 710 | if(m_options.hasFlag(QHexFlags::StyledAddress)) 711 | acf.setBackground(this->palette().color(QPalette::Window)); 712 | 713 | if(m_hexdelegate) 714 | m_hexdelegate->renderAddress(address, acf, this); 715 | 716 | if(m_hexcursor->line() == static_cast(line) && 717 | m_options.hasFlag(QHexFlags::HighlightAddress)) { 718 | acf.setBackground(this->palette().color(QPalette::Highlight)); 719 | acf.setForeground(this->palette().color(QPalette::HighlightedText)); 720 | } 721 | 722 | c.insertText(" " + addrstr + " ", acf); 723 | 724 | QByteArray linebytes = this->getLine(line); 725 | c.insertText(" ", {}); 726 | 727 | // Hex Part 728 | for(unsigned int column = 0u; column < m_options.linelength;) { 729 | QTextCharFormat cf; 730 | 731 | for(unsigned int byteidx = 0u; byteidx < m_options.grouplength; 732 | byteidx++, column++) { 733 | QString s; 734 | quint8 b{}; 735 | 736 | if(m_hexdocument->accept( 737 | this->positionFromLineCol(line, column))) { 738 | s = linebytes.isEmpty() || 739 | column >= static_cast(linebytes.size()) 740 | ? " " 741 | : QString(QHexUtils::toHex(linebytes.mid(column, 1)) 742 | .toUpper()); 743 | b = static_cast(column) < linebytes.size() 744 | ? linebytes.at(column) 745 | : 0x00; 746 | } 747 | else 748 | s = QString(m_options.invalidchar).repeated(2); 749 | 750 | cf = this->drawFormat(c, b, s, QHexArea::Hex, line, column, 751 | static_cast(column) < 752 | linebytes.size()); 753 | } 754 | 755 | c.insertText(" ", cf); 756 | } 757 | 758 | c.insertText(" ", {}); 759 | 760 | // Ascii Part 761 | for(unsigned int column = 0u; column < m_options.linelength; column++) { 762 | QString s; 763 | quint8 b{}; 764 | 765 | if(m_hexdocument->accept(this->positionFromLineCol(line, column))) { 766 | s = linebytes.isEmpty() || 767 | column >= static_cast(linebytes.size()) 768 | ? QChar(' ') 769 | : (QChar::isPrint(linebytes.at(column)) 770 | ? QChar(linebytes.at(column)) 771 | : m_options.unprintablechar); 772 | 773 | b = static_cast(column) < linebytes.size() 774 | ? linebytes.at(column) 775 | : 0x00; 776 | } 777 | else 778 | s = m_options.invalidchar; 779 | 780 | this->drawFormat(c, b, s, QHexArea::Ascii, line, column, 781 | static_cast(column) < linebytes.size()); 782 | } 783 | 784 | QTextBlockFormat bf; 785 | 786 | if(m_options.linealternatebackground.isValid() && line % 2) 787 | bf.setBackground(m_options.linealternatebackground); 788 | else if(m_options.linebackground.isValid() && !(line % 2)) 789 | bf.setBackground(m_options.linebackground); 790 | 791 | c.setBlockFormat(bf); 792 | c.insertBlock({}); 793 | if(m_hexdocument->isEmpty()) 794 | break; 795 | } 796 | } 797 | 798 | unsigned int QHexView::calcAddressWidth() const { 799 | if(!m_hexdocument) 800 | return 0; 801 | 802 | auto maxaddr = 803 | static_cast(m_options.baseaddress + m_hexdocument->length()); 804 | if(maxaddr <= std::numeric_limits::max()) 805 | return 8; 806 | return QString::number(maxaddr, 16).size(); 807 | } 808 | 809 | int QHexView::visibleLines(bool absolute) const { 810 | int vl = static_cast( 811 | qCeil(this->viewport()->height() / this->lineHeight())); 812 | if(!m_options.hasFlag(QHexFlags::NoHeader)) 813 | vl--; 814 | return absolute ? vl : qMin(this->lines(), vl); 815 | } 816 | 817 | qint64 QHexView::getLastColumn(qint64 line) const { 818 | return this->getLine(line).size() - 1; 819 | } 820 | qint64 QHexView::lastLine() const { return qMax(0, this->lines() - 1); } 821 | 822 | qreal QHexView::hexColumnWidth() const { 823 | int l = 0; 824 | 825 | for(auto i = 0u; i < m_options.linelength; i += m_options.grouplength) 826 | l += (2 * m_options.grouplength) + 1; 827 | 828 | return this->getNCellsWidth(l); 829 | } 830 | 831 | unsigned int QHexView::addressWidth() const { 832 | if(!m_hexdocument || m_options.addresswidth) 833 | return m_options.addresswidth; 834 | return this->calcAddressWidth(); 835 | } 836 | 837 | unsigned int QHexView::lineLength() const { return m_options.linelength; } 838 | 839 | bool QHexView::isModified() const { 840 | return m_hexdocument && m_hexdocument->isModified(); 841 | } 842 | 843 | bool QHexView::canUndo() const { 844 | return m_hexdocument && m_hexdocument->canUndo(); 845 | } 846 | 847 | bool QHexView::canRedo() const { 848 | return m_hexdocument && m_hexdocument->canRedo(); 849 | } 850 | 851 | quint64 QHexView::offset() const { return m_hexcursor->offset(); } 852 | quint64 QHexView::address() const { return m_hexcursor->address(); } 853 | 854 | QHexPosition QHexView::positionFromOffset(quint64 offset) const { 855 | QHexPosition opt = QHexPosition::invalid(); 856 | 857 | if(offset < static_cast(m_hexdocument->length())) { 858 | opt.line = offset / m_options.linelength; 859 | opt.column = offset % m_options.linelength; 860 | } 861 | 862 | return opt; 863 | } 864 | 865 | QHexPosition QHexView::positionFromAddress(quint64 address) const { 866 | return this->positionFromOffset(address - m_options.baseaddress); 867 | } 868 | 869 | QHexPosition QHexView::position() const { return m_hexcursor->position(); } 870 | 871 | QHexPosition QHexView::selectionStart() const { 872 | return m_hexcursor->selectionStart(); 873 | } 874 | 875 | QHexPosition QHexView::selectionEnd() const { 876 | return m_hexcursor->selectionEnd(); 877 | } 878 | 879 | quint64 QHexView::selectionStartOffset() const { 880 | return m_hexcursor->selectionStartOffset(); 881 | } 882 | 883 | quint64 QHexView::selectionEndOffset() const { 884 | return m_hexcursor->selectionEndOffset(); 885 | } 886 | 887 | quint64 QHexView::baseAddress() const { return m_options.baseaddress; } 888 | 889 | quint64 QHexView::lines() const { 890 | if(!m_hexdocument) 891 | return 0; 892 | 893 | auto lines = static_cast(qCeil( 894 | m_hexdocument->length() / static_cast(m_options.linelength))); 895 | return !m_hexdocument->isEmpty() && !lines ? 1 : lines; 896 | } 897 | 898 | qint64 QHexView::replace(const QVariant& oldvalue, const QVariant& newvalue, 899 | qint64 offset, QHexFindMode mode, unsigned int options, 900 | QHexFindDirection fd) const { 901 | auto res = 902 | QHexUtils::replace(this, oldvalue, newvalue, offset, mode, options, fd); 903 | 904 | if(res.first > -1) { 905 | m_hexcursor->move(res.first); 906 | m_hexcursor->selectSize(res.second); 907 | } 908 | 909 | return res.first; 910 | } 911 | 912 | qint64 QHexView::find(const QVariant& value, qint64 offset, QHexFindMode mode, 913 | unsigned int options, QHexFindDirection fd) const { 914 | auto res = QHexUtils::find(this, value, offset, mode, options, fd); 915 | 916 | if(res.first > -1) { 917 | m_hexcursor->move(res.first); 918 | m_hexcursor->selectSize(res.second); 919 | } 920 | 921 | return res.first; 922 | } 923 | 924 | qreal QHexView::hexColumnX() const { 925 | return this->getNCellsWidth(this->addressWidth() + 2); 926 | } 927 | qreal QHexView::asciiColumnX() const { 928 | return this->hexColumnX() + this->hexColumnWidth() + this->cellWidth(); 929 | } 930 | qreal QHexView::endColumnX() const { 931 | return this->asciiColumnX() + 932 | this->getNCellsWidth(m_options.linelength + 1) + this->cellWidth(); 933 | } 934 | qreal QHexView::getNCellsWidth(int n) const { return n * this->cellWidth(); } 935 | 936 | qreal QHexView::cellWidth() const { 937 | #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) 938 | return m_fontmetrics.horizontalAdvance(" "); 939 | #else 940 | return m_fontmetrics.width(" "); 941 | #endif 942 | } 943 | 944 | qreal QHexView::lineHeight() const { return m_fontmetrics.height(); } 945 | 946 | qint64 QHexView::positionFromLineCol(qint64 line, qint64 col) const { 947 | if(m_hexdocument) { 948 | return qMin((line * m_options.linelength) + col, 949 | m_hexdocument->length()); 950 | } 951 | 952 | return 0; 953 | } 954 | 955 | QHexPosition QHexView::positionFromPoint(QPoint pt) const { 956 | QHexPosition pos = QHexPosition::invalid(); 957 | auto abspt = this->absolutePoint(pt); 958 | 959 | switch(this->areaFromPoint(pt)) { 960 | case QHexArea::Hex: { 961 | pos.column = -1; 962 | 963 | for(qint64 i = 0; i < m_hexcolumns.size(); i++) { 964 | if(m_hexcolumns.at(i).left() > abspt.x()) 965 | break; 966 | pos.column = i; 967 | } 968 | 969 | break; 970 | } 971 | 972 | case QHexArea::Ascii: 973 | pos.column = qMax( 974 | qFloor((abspt.x() - this->asciiColumnX()) / this->cellWidth()) - 975 | 1, 976 | 0); 977 | break; 978 | case QHexArea::Address: pos.column = 0; break; 979 | case QHexArea::Header: return QHexPosition::invalid(); 980 | default: break; 981 | } 982 | 983 | pos.line = qMin(this->verticalScrollBar()->value() + 984 | (abspt.y() / this->lineHeight()), 985 | this->lines()); 986 | if(!m_options.hasFlag(QHexFlags::NoHeader)) 987 | pos.line = qMax(0, pos.line - 1); 988 | 989 | auto docline = this->getLine(pos.line); 990 | pos.column = 991 | qMin(pos.column, docline.isEmpty() ? 0 : docline.size()); 992 | 993 | qhexview_fmtprint("line: %lld, col: %lld", pos.line, pos.column); 994 | return pos; 995 | } 996 | 997 | QPoint QHexView::absolutePoint(QPoint pt) const { 998 | return pt + QPoint(this->horizontalScrollBar()->value(), 0); 999 | } 1000 | 1001 | QHexArea QHexView::areaFromPoint(QPoint pt) const { 1002 | pt = this->absolutePoint(pt); 1003 | qreal line = 1004 | this->verticalScrollBar()->value() + pt.y() / this->lineHeight(); 1005 | 1006 | if(!m_options.hasFlag(QHexFlags::NoHeader) && !qFloor(line)) 1007 | return QHexArea::Header; 1008 | if(pt.x() < this->hexColumnX()) 1009 | return QHexArea::Address; 1010 | if(pt.x() < this->asciiColumnX()) 1011 | return QHexArea::Hex; 1012 | if(pt.x() < this->endColumnX()) 1013 | return QHexArea::Ascii; 1014 | return QHexArea::Extra; 1015 | } 1016 | 1017 | QTextCharFormat QHexView::drawFormat(QTextCursor& c, quint8 b, const QString& s, 1018 | QHexArea area, qint64 line, qint64 column, 1019 | bool applyformat) const { 1020 | QTextCharFormat cf, selcf; 1021 | QHexPosition pos{line, column}; 1022 | 1023 | if(applyformat) { 1024 | auto offset = m_hexcursor->positionToOffset(pos); 1025 | bool hasdelegate = 1026 | m_hexdelegate && m_hexdelegate->render(offset, b, cf, this); 1027 | 1028 | if(!hasdelegate) { 1029 | auto it = m_options.bytecolors.find(b); 1030 | 1031 | if(it != m_options.bytecolors.end()) { 1032 | if(it->background.isValid()) 1033 | cf.setBackground(it->background); 1034 | if(it->foreground.isValid()) 1035 | cf.setForeground(it->foreground); 1036 | } 1037 | } 1038 | 1039 | const auto* metadataline = m_hexmetadata->find(line); 1040 | 1041 | if(metadataline) { 1042 | for(const auto& metadata : *metadataline) { 1043 | if(offset < metadata.begin || offset >= metadata.end) 1044 | continue; 1045 | 1046 | if(!hasdelegate) { 1047 | if(metadata.foreground.isValid()) 1048 | cf.setForeground(metadata.foreground); 1049 | 1050 | if(metadata.background.isValid()) { 1051 | cf.setBackground(metadata.background); 1052 | 1053 | if(!metadata.foreground.isValid()) 1054 | cf.setForeground( 1055 | this->getReadableColor(metadata.background)); 1056 | } 1057 | } 1058 | 1059 | if(!metadata.comment.isEmpty()) { 1060 | cf.setUnderlineColor( 1061 | m_options.commentcolor.isValid() 1062 | ? m_options.commentcolor 1063 | : this->palette().color(QPalette::WindowText)); 1064 | cf.setUnderlineStyle( 1065 | QTextCharFormat::UnderlineStyle::SingleUnderline); 1066 | } 1067 | 1068 | if(offset == metadata.begin) // Remove previous metadata's 1069 | // style, if needed 1070 | { 1071 | if(metadata.comment.isEmpty()) 1072 | selcf.setUnderlineStyle( 1073 | QTextCharFormat::UnderlineStyle::NoUnderline); 1074 | if(!metadata.foreground.isValid()) 1075 | selcf.setForeground(Qt::color1); 1076 | if(!metadata.background.isValid()) 1077 | selcf.setBackground(Qt::transparent); 1078 | } 1079 | 1080 | if(offset < metadata.end - 1 && 1081 | column < this->getLastColumn(line)) 1082 | selcf = cf; 1083 | } 1084 | } 1085 | 1086 | if(hasdelegate && column < this->getLastColumn(line)) 1087 | selcf = cf; 1088 | } 1089 | 1090 | if(this->hexCursor()->isSelected(line, column)) { 1091 | auto offset = this->hexCursor()->positionToOffset(pos); 1092 | auto selend = this->hexCursor()->selectionEndOffset(); 1093 | 1094 | cf.setBackground( 1095 | this->palette().color(QPalette::Normal, QPalette::Highlight)); 1096 | cf.setForeground( 1097 | this->palette().color(QPalette::Normal, QPalette::HighlightedText)); 1098 | if(offset < selend && column < this->getLastColumn(line)) 1099 | selcf = cf; 1100 | } 1101 | 1102 | if(this->hexCursor()->position() == pos) { 1103 | auto cursorbg = this->palette().color( 1104 | this->hasFocus() ? QPalette::Normal : QPalette::Disabled, 1105 | QPalette::WindowText); 1106 | auto cursorfg = this->palette().color( 1107 | this->hasFocus() ? QPalette::Normal : QPalette::Disabled, 1108 | QPalette::Base); 1109 | auto discursorbg = 1110 | this->palette().color(QPalette::Disabled, QPalette::WindowText); 1111 | auto discursorfg = 1112 | this->palette().color(QPalette::Disabled, QPalette::Base); 1113 | 1114 | switch(m_hexcursor->mode()) { 1115 | case QHexCursor::Mode::Insert: 1116 | cf.setUnderlineColor(m_currentarea == area ? cursorbg 1117 | : discursorbg); 1118 | cf.setUnderlineStyle( 1119 | QTextCharFormat::UnderlineStyle::SingleUnderline); 1120 | break; 1121 | 1122 | case QHexCursor::Mode::Overwrite: 1123 | cf.setBackground(m_currentarea == area ? cursorbg 1124 | : discursorbg); 1125 | cf.setForeground(m_currentarea == area ? cursorfg 1126 | : discursorfg); 1127 | break; 1128 | } 1129 | } 1130 | 1131 | c.insertText(s, cf); 1132 | return selcf; 1133 | } 1134 | 1135 | void QHexView::moveNext(bool select) { 1136 | auto line = this->hexCursor()->line(), column = this->hexCursor()->column(); 1137 | 1138 | if(column >= m_options.linelength - 1) { 1139 | line++; 1140 | column = 0; 1141 | } 1142 | else 1143 | column++; 1144 | 1145 | qint64 offset = 1146 | this->hexCursor()->mode() == QHexCursor::Mode::Insert ? 1 : 0; 1147 | if(select) 1148 | this->hexCursor()->select( 1149 | qMin(line, this->lines()), 1150 | qMin(column, this->getLastColumn(line) + offset)); 1151 | else 1152 | this->hexCursor()->move( 1153 | qMin(line, this->lines()), 1154 | qMin(column, this->getLastColumn(line) + offset)); 1155 | } 1156 | 1157 | void QHexView::movePrevious(bool select) { 1158 | auto line = this->hexCursor()->line(), column = this->hexCursor()->column(); 1159 | 1160 | if(column <= 0) { 1161 | if(!line) 1162 | return; 1163 | column = this->getLine(--line).size() - 1; 1164 | } 1165 | else 1166 | column--; 1167 | 1168 | if(select) 1169 | this->hexCursor()->select( 1170 | qMin(line, this->lines()), 1171 | qMin(column, this->getLastColumn(line))); 1172 | else 1173 | this->hexCursor()->move( 1174 | qMin(line, this->lines()), 1175 | qMin(column, this->getLastColumn(line))); 1176 | } 1177 | 1178 | bool QHexView::keyPressMove(QKeyEvent* e) { 1179 | if(e->matches(QKeySequence::MoveToNextChar) || 1180 | e->matches(QKeySequence::SelectNextChar)) 1181 | this->moveNext(e->matches(QKeySequence::SelectNextChar)); 1182 | else if(e->matches(QKeySequence::MoveToPreviousChar) || 1183 | e->matches(QKeySequence::SelectPreviousChar)) 1184 | this->movePrevious(e->matches(QKeySequence::SelectPreviousChar)); 1185 | else if(e->matches(QKeySequence::MoveToNextLine) || 1186 | e->matches(QKeySequence::SelectNextLine)) { 1187 | if(this->hexCursor()->line() == this->lastLine()) 1188 | return true; 1189 | auto nextline = this->hexCursor()->line() + 1; 1190 | if(e->matches(QKeySequence::MoveToNextLine)) 1191 | this->hexCursor()->move(nextline, this->hexCursor()->column()); 1192 | else 1193 | this->hexCursor()->select(nextline, this->hexCursor()->column()); 1194 | } 1195 | else if(e->matches(QKeySequence::MoveToPreviousLine) || 1196 | e->matches(QKeySequence::SelectPreviousLine)) { 1197 | if(!this->hexCursor()->line()) 1198 | return true; 1199 | auto prevline = this->hexCursor()->line() - 1; 1200 | if(e->matches(QKeySequence::MoveToPreviousLine)) 1201 | this->hexCursor()->move(prevline, this->hexCursor()->column()); 1202 | else 1203 | this->hexCursor()->select(prevline, this->hexCursor()->column()); 1204 | } 1205 | else if(e->matches(QKeySequence::MoveToNextPage) || 1206 | e->matches(QKeySequence::SelectNextPage)) { 1207 | if(this->lastLine() == this->hexCursor()->line()) 1208 | return true; 1209 | auto pageline = qMin(this->lastLine(), 1210 | this->hexCursor()->line() + this->visibleLines()); 1211 | if(e->matches(QKeySequence::MoveToNextPage)) 1212 | this->hexCursor()->move(pageline, this->hexCursor()->column()); 1213 | else 1214 | this->hexCursor()->select(pageline, this->hexCursor()->column()); 1215 | } 1216 | else if(e->matches(QKeySequence::MoveToPreviousPage) || 1217 | e->matches(QKeySequence::SelectPreviousPage)) { 1218 | if(!this->hexCursor()->line()) 1219 | return true; 1220 | auto pageline = 1221 | qMax(0, this->hexCursor()->line() - this->visibleLines()); 1222 | if(e->matches(QKeySequence::MoveToPreviousPage)) 1223 | this->hexCursor()->move(pageline, this->hexCursor()->column()); 1224 | else 1225 | this->hexCursor()->select(pageline, this->hexCursor()->column()); 1226 | } 1227 | else if(e->matches(QKeySequence::MoveToStartOfDocument) || 1228 | e->matches(QKeySequence::SelectStartOfDocument)) { 1229 | if(!this->hexCursor()->line()) 1230 | return true; 1231 | if(e->matches(QKeySequence::MoveToStartOfDocument)) 1232 | this->hexCursor()->move(0, 0); 1233 | else 1234 | this->hexCursor()->select(0, 0); 1235 | } 1236 | else if(e->matches(QKeySequence::MoveToEndOfDocument) || 1237 | e->matches(QKeySequence::SelectEndOfDocument)) { 1238 | if(this->lastLine() == this->hexCursor()->line()) 1239 | return true; 1240 | if(e->matches(QKeySequence::MoveToEndOfDocument)) 1241 | this->hexCursor()->move( 1242 | this->lastLine(), 1243 | this->getLastColumn(this->hexCursor()->line())); 1244 | else 1245 | this->hexCursor()->select(this->lastLine(), 1246 | this->getLastColumn(this->lastLine())); 1247 | } 1248 | else if(e->matches(QKeySequence::MoveToStartOfLine) || 1249 | e->matches(QKeySequence::SelectStartOfLine)) { 1250 | auto offset = 1251 | this->hexCursor()->positionToOffset({this->hexCursor()->line(), 0}); 1252 | if(e->matches(QKeySequence::MoveToStartOfLine)) 1253 | this->hexCursor()->move(offset); 1254 | else 1255 | this->hexCursor()->select(offset); 1256 | } 1257 | else if(e->matches(QKeySequence::SelectEndOfLine) || 1258 | e->matches(QKeySequence::MoveToEndOfLine)) { 1259 | auto offset = this->hexCursor()->positionToOffset( 1260 | {this->hexCursor()->line(), 1261 | this->getLastColumn(this->hexCursor()->line())}); 1262 | if(e->matches(QKeySequence::SelectEndOfLine)) 1263 | this->hexCursor()->select(offset); 1264 | else 1265 | this->hexCursor()->move(offset); 1266 | } 1267 | else 1268 | return false; 1269 | 1270 | return true; 1271 | } 1272 | 1273 | bool QHexView::keyPressTextInput(QKeyEvent* e) { 1274 | if(m_readonly || e->text().isEmpty() || 1275 | (e->modifiers() & Qt::ControlModifier)) 1276 | return false; 1277 | 1278 | bool atend = m_hexcursor->offset() >= m_hexdocument->length(); 1279 | if(atend && m_hexcursor->mode() == QHexCursor::Mode::Overwrite) 1280 | return false; 1281 | 1282 | char key = e->text().at(0).toLatin1(); 1283 | 1284 | switch(m_currentarea) { 1285 | case QHexArea::Hex: { 1286 | if(!QHexUtils::isHex(key)) 1287 | return false; 1288 | 1289 | bool ok = false; 1290 | auto val = static_cast(QString(key).toUInt(&ok, 16)); 1291 | if(!ok) 1292 | return false; 1293 | m_hexcursor->removeSelection(); 1294 | 1295 | quint8 ch = m_hexdocument->isEmpty() || 1296 | m_hexcursor->offset() >= m_hexdocument->length() 1297 | ? '\x00' 1298 | : m_hexdocument->at(m_hexcursor->offset()); 1299 | ch = m_writing ? (ch << 4) | val : val; 1300 | 1301 | if(!m_writing && (m_hexcursor->mode() == QHexCursor::Mode::Insert)) 1302 | m_hexdocument->insert(m_hexcursor->offset(), val); 1303 | else 1304 | m_hexdocument->replace(m_hexcursor->offset(), ch); 1305 | 1306 | m_writing = !m_writing; 1307 | if(!m_writing) 1308 | this->moveNext(); 1309 | 1310 | break; 1311 | } 1312 | 1313 | case QHexArea::Ascii: { 1314 | if(!QChar::isPrint(key)) 1315 | return false; 1316 | m_hexcursor->removeSelection(); 1317 | if(m_hexcursor->mode() == QHexCursor::Mode::Insert) 1318 | m_hexdocument->insert(m_hexcursor->offset(), key); 1319 | else 1320 | m_hexdocument->replace(m_hexcursor->offset(), key); 1321 | this->moveNext(); 1322 | break; 1323 | } 1324 | 1325 | default: return false; 1326 | } 1327 | 1328 | return true; 1329 | } 1330 | 1331 | bool QHexView::keyPressAction(QKeyEvent* e) { 1332 | if(e->modifiers() != Qt::NoModifier) { 1333 | if(e->matches(QKeySequence::SelectAll)) 1334 | this->selectAll(); 1335 | else if(!m_readonly && e->matches(QKeySequence::Undo)) 1336 | this->undo(); 1337 | else if(!m_readonly && e->matches(QKeySequence::Redo)) 1338 | this->redo(); 1339 | else if(!m_readonly && e->matches(QKeySequence::Cut)) 1340 | this->cut(m_currentarea != QHexArea::Ascii); 1341 | else if(e->matches(QKeySequence::Copy)) 1342 | this->copy(m_currentarea != QHexArea::Ascii); 1343 | else if(!m_readonly && e->matches(QKeySequence::Paste)) 1344 | this->paste(m_currentarea != QHexArea::Ascii); 1345 | else 1346 | return false; 1347 | 1348 | return true; 1349 | } 1350 | 1351 | if(m_readonly) 1352 | return false; 1353 | 1354 | switch(e->key()) { 1355 | case Qt::Key_Backspace: 1356 | case Qt::Key_Delete: { 1357 | if(!m_hexcursor->hasSelection()) { 1358 | auto offset = m_hexcursor->offset(); 1359 | if(offset <= 0) 1360 | return true; 1361 | 1362 | if(e->key() == Qt::Key_Backspace) 1363 | m_hexdocument->remove(offset - 1, 1); 1364 | else 1365 | m_hexdocument->remove(offset, 1); 1366 | } 1367 | else { 1368 | auto oldpos = m_hexcursor->selectionStart(); 1369 | m_hexcursor->removeSelection(); 1370 | m_hexcursor->move(oldpos); 1371 | } 1372 | 1373 | if(e->key() == Qt::Key_Backspace) 1374 | this->movePrevious(); 1375 | m_writing = false; 1376 | break; 1377 | } 1378 | 1379 | case Qt::Key_Insert: 1380 | m_writing = false; 1381 | m_hexcursor->switchMode(); 1382 | break; 1383 | 1384 | default: return false; 1385 | } 1386 | 1387 | return true; 1388 | } 1389 | 1390 | bool QHexView::event(QEvent* e) { 1391 | switch(e->type()) { 1392 | case QEvent::FontChange: 1393 | m_fontmetrics = QFontMetricsF(this->font()); 1394 | this->checkAndUpdate(true); 1395 | return true; 1396 | 1397 | case QEvent::ToolTip: { 1398 | if(m_hexdocument && (m_currentarea == QHexArea::Hex || 1399 | m_currentarea == QHexArea::Ascii)) { 1400 | auto* helpevent = static_cast(e); 1401 | auto pos = this->positionFromPoint(helpevent->pos()); 1402 | auto comment = m_hexmetadata->getComment(pos.line, pos.column); 1403 | if(!comment.isEmpty()) 1404 | QToolTip::showText(helpevent->globalPos(), comment); 1405 | return true; 1406 | } 1407 | 1408 | break; 1409 | } 1410 | 1411 | default: break; 1412 | } 1413 | 1414 | return QAbstractScrollArea::event(e); 1415 | } 1416 | 1417 | void QHexView::showEvent(QShowEvent* e) { 1418 | QAbstractScrollArea::showEvent(e); 1419 | this->checkAndUpdate(true); 1420 | } 1421 | 1422 | void QHexView::paintEvent(QPaintEvent*) { 1423 | if(!m_hexdocument) 1424 | return; 1425 | 1426 | QPainter painter(this->viewport()); 1427 | if(m_hexdelegate) 1428 | m_hexdelegate->paint(&painter, this); 1429 | else 1430 | this->paint(&painter); 1431 | } 1432 | 1433 | void QHexView::resizeEvent(QResizeEvent* e) { 1434 | this->checkState(); 1435 | QAbstractScrollArea::resizeEvent(e); 1436 | } 1437 | 1438 | void QHexView::focusInEvent(QFocusEvent* e) { 1439 | QAbstractScrollArea::focusInEvent(e); 1440 | if(m_hexdocument) 1441 | this->viewport()->update(); 1442 | } 1443 | 1444 | void QHexView::focusOutEvent(QFocusEvent* e) { 1445 | QAbstractScrollArea::focusOutEvent(e); 1446 | if(m_hexdocument) 1447 | this->viewport()->update(); 1448 | } 1449 | 1450 | void QHexView::mousePressEvent(QMouseEvent* e) { 1451 | QAbstractScrollArea::mousePressEvent(e); 1452 | if(!m_hexdocument || e->button() != Qt::LeftButton) 1453 | return; 1454 | 1455 | auto pos = this->positionFromPoint(e->pos()); 1456 | if(!pos.isValid()) 1457 | return; 1458 | 1459 | auto area = this->areaFromPoint(e->pos()); 1460 | qhexview_fmtprint("%d", static_cast(area)); 1461 | 1462 | switch(area) { 1463 | case QHexArea::Address: this->hexCursor()->move(pos.line, 0); break; 1464 | case QHexArea::Hex: 1465 | m_currentarea = area; 1466 | this->hexCursor()->move(pos); 1467 | break; 1468 | case QHexArea::Ascii: 1469 | m_currentarea = area; 1470 | this->hexCursor()->move(pos.line, pos.column); 1471 | break; 1472 | default: return; 1473 | } 1474 | 1475 | this->viewport()->update(); 1476 | } 1477 | 1478 | void QHexView::mouseMoveEvent(QMouseEvent* e) { 1479 | QAbstractScrollArea::mouseMoveEvent(e); 1480 | if(!this->hexCursor()) 1481 | return; 1482 | 1483 | e->accept(); 1484 | auto area = this->areaFromPoint(e->pos()); 1485 | 1486 | switch(area) { 1487 | case QHexArea::Header: 1488 | this->viewport()->setCursor(Qt::ArrowCursor); 1489 | return; 1490 | case QHexArea::Address: 1491 | this->viewport()->setCursor(Qt::ArrowCursor); 1492 | break; 1493 | default: this->viewport()->setCursor(Qt::IBeamCursor); break; 1494 | } 1495 | 1496 | if(e->buttons() == Qt::LeftButton) { 1497 | auto pos = this->positionFromPoint(e->pos()); 1498 | if(!pos.isValid()) 1499 | return; 1500 | if(area == QHexArea::Ascii || area == QHexArea::Hex) 1501 | m_currentarea = area; 1502 | this->hexCursor()->select(pos); 1503 | } 1504 | } 1505 | 1506 | void QHexView::wheelEvent(QWheelEvent* e) { 1507 | e->ignore(); 1508 | 1509 | #if defined(Q_OS_OSX) 1510 | // In macOS scrollbar invisibility should not prevent scrolling from working 1511 | if(!m_hexdocument) 1512 | return; 1513 | #else 1514 | if(!m_hexdocument || !this->verticalScrollBar()->isVisible()) 1515 | return; 1516 | #endif 1517 | 1518 | // https://doc.qt.io/qt-6/qwheelevent.html 1519 | // "Returns the relative amount that the wheel was rotated, in eighths of a 1520 | // degree." "Most mouse types work in steps of 15 degrees, in which case the 1521 | // delta value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees." 1522 | int const ydelta = e->angleDelta().y(); 1523 | if(0 != ydelta) { 1524 | int const ydeltaAbsolute = qAbs(ydelta); 1525 | int const numberOfLinesToMove = 1526 | (ydeltaAbsolute * m_options.scrollsteps + 119) / 1527 | 120; // always move at least 1 line 1528 | int const ydeltaSign = ydelta / ydeltaAbsolute; 1529 | 1530 | int const oldValue = this->verticalScrollBar()->value(); 1531 | int const newValue = oldValue - ydeltaSign * numberOfLinesToMove; 1532 | this->verticalScrollBar()->setValue(newValue); 1533 | } 1534 | } 1535 | 1536 | void QHexView::keyPressEvent(QKeyEvent* e) { 1537 | bool handled = false; 1538 | 1539 | if(this->hexCursor()) { 1540 | handled = this->keyPressMove(e); 1541 | if(!handled) 1542 | handled = this->keyPressAction(e); 1543 | if(!handled) 1544 | handled = this->keyPressTextInput(e); 1545 | } 1546 | 1547 | if(handled) 1548 | e->accept(); 1549 | else 1550 | QAbstractScrollArea::keyPressEvent(e); 1551 | } 1552 | 1553 | QString QHexView::reduced(const QString& s, int maxlen) { 1554 | if(s.length() <= maxlen) 1555 | return s.leftJustified(maxlen); 1556 | return s.mid(0, maxlen - 1) + "\u2026"; 1557 | } 1558 | 1559 | bool QHexView::isColorLight(QColor c) { 1560 | return std::sqrt(0.299 * std::pow(c.red(), 2) + 1561 | 0.587 * std::pow(c.green(), 2) + 1562 | 0.114 * std::pow(c.blue(), 2)) > 127.5; 1563 | } 1564 | 1565 | QColor QHexView::getReadableColor(QColor c) const { 1566 | QPalette palette = this->palette(); 1567 | return QHexView::isColorLight(c) 1568 | ? palette.color(QPalette::Normal, QPalette::WindowText) 1569 | : palette.color(QPalette::Normal, QPalette::HighlightedText); 1570 | } 1571 | 1572 | QByteArray QHexView::selectedBytes() const { 1573 | return m_hexcursor->hasSelection() 1574 | ? m_hexdocument->read(m_hexcursor->selectionStartOffset(), 1575 | m_hexcursor->selectionLength()) 1576 | : QByteArray{}; 1577 | } 1578 | QByteArray QHexView::getLine(qint64 line) const { 1579 | return m_hexdocument ? m_hexdocument->read(line * m_options.linelength, 1580 | m_options.linelength) 1581 | : QByteArray{}; 1582 | } 1583 | --------------------------------------------------------------------------------