diff --git a/source/frontends/qapple/chunks.cpp b/source/frontends/qapple/chunks.cpp new file mode 100644 index 00000000..a4e420bf --- /dev/null +++ b/source/frontends/qapple/chunks.cpp @@ -0,0 +1,323 @@ +#include "chunks.h" +#include + +#define NORMAL 0 +#define HIGHLIGHTED 1 + +#define BUFFER_SIZE 0x10000 +#define CHUNK_SIZE 0x1000 +#define READ_CHUNK_MASK Q_INT64_C(0xfffffffffffff000) + +// ***************************************** Constructors and file settings + +Chunks::Chunks(QObject *parent): QObject(parent) +{ + QBuffer *buf = new QBuffer(this); + setIODevice(*buf); +} + +Chunks::Chunks(QIODevice &ioDevice, QObject *parent): QObject(parent) +{ + setIODevice(ioDevice); +} + +bool Chunks::setIODevice(QIODevice &ioDevice) +{ + _ioDevice = &ioDevice; + bool ok = _ioDevice->open(QIODevice::ReadOnly); + if (ok) // Try to open IODevice + { + _size = _ioDevice->size(); + _ioDevice->close(); + } + else // Fallback is an empty buffer + { + QBuffer *buf = new QBuffer(this); + _ioDevice = buf; + _size = 0; + } + _chunks.clear(); + _pos = 0; + return ok; +} + + +// ***************************************** Getting data out of Chunks + +QByteArray Chunks::data(qint64 pos, qint64 maxSize, QByteArray *highlighted) +{ + qint64 ioDelta = 0; + int chunkIdx = 0; + + Chunk chunk; + QByteArray buffer; + + // Do some checks and some arrangements + if (highlighted) + highlighted->clear(); + + if (pos >= _size) + return buffer; + + if (maxSize < 0) + maxSize = _size; + else + if ((pos + maxSize) > _size) + maxSize = _size - pos; + + _ioDevice->open(QIODevice::ReadOnly); + + while (maxSize > 0) + { + chunk.absPos = LLONG_MAX; + bool chunksLoopOngoing = true; + while ((chunkIdx < _chunks.count()) && chunksLoopOngoing) + { + // In this section, we track changes before our required data and + // we take the editdet data, if availible. ioDelta is a difference + // counter to justify the read pointer to the original data, if + // data in between was deleted or inserted. + + chunk = _chunks[chunkIdx]; + if (chunk.absPos > pos) + chunksLoopOngoing = false; + else + { + chunkIdx += 1; + qint64 count; + qint64 chunkOfs = pos - chunk.absPos; + if (maxSize > ((qint64)chunk.data.size() - chunkOfs)) + { + count = (qint64)chunk.data.size() - chunkOfs; + ioDelta += CHUNK_SIZE - chunk.data.size(); + } + else + count = maxSize; + if (count > 0) + { + buffer += chunk.data.mid(chunkOfs, (int)count); + maxSize -= count; + pos += count; + if (highlighted) + *highlighted += chunk.dataChanged.mid(chunkOfs, (int)count); + } + } + } + + if ((maxSize > 0) && (pos < chunk.absPos)) + { + // In this section, we read data from the original source. This only will + // happen, whe no copied data is available + + qint64 byteCount; + QByteArray readBuffer; + if ((chunk.absPos - pos) > maxSize) + byteCount = maxSize; + else + byteCount = chunk.absPos - pos; + + maxSize -= byteCount; + _ioDevice->seek(pos + ioDelta); + readBuffer = _ioDevice->read(byteCount); + buffer += readBuffer; + if (highlighted) + *highlighted += QByteArray(readBuffer.size(), NORMAL); + pos += readBuffer.size(); + } + } + _ioDevice->close(); + return buffer; +} + +bool Chunks::write(QIODevice &iODevice, qint64 pos, qint64 count) +{ + if (count == -1) + count = _size; + bool ok = iODevice.open(QIODevice::WriteOnly); + if (ok) + { + for (qint64 idx=pos; idx < count; idx += BUFFER_SIZE) + { + QByteArray ba = data(idx, BUFFER_SIZE); + iODevice.write(ba); + } + iODevice.close(); + } + return ok; +} + + +// ***************************************** Set and get highlighting infos + +void Chunks::setDataChanged(qint64 pos, bool dataChanged) +{ + if ((pos < 0) || (pos >= _size)) + return; + int chunkIdx = getChunkIndex(pos); + qint64 posInBa = pos - _chunks[chunkIdx].absPos; + _chunks[chunkIdx].dataChanged[(int)posInBa] = char(dataChanged); +} + +bool Chunks::dataChanged(qint64 pos) +{ + QByteArray highlighted; + data(pos, 1, &highlighted); + return bool(highlighted.at(0)); +} + + +// ***************************************** Search API + +qint64 Chunks::indexOf(const QByteArray &ba, qint64 from) +{ + qint64 result = -1; + QByteArray buffer; + + for (qint64 pos=from; (pos < _size) && (result < 0); pos += BUFFER_SIZE) + { + buffer = data(pos, BUFFER_SIZE + ba.size() - 1); + int findPos = buffer.indexOf(ba); + if (findPos >= 0) + result = pos + (qint64)findPos; + } + return result; +} + +qint64 Chunks::lastIndexOf(const QByteArray &ba, qint64 from) +{ + qint64 result = -1; + QByteArray buffer; + + for (qint64 pos=from; (pos > 0) && (result < 0); pos -= BUFFER_SIZE) + { + qint64 sPos = pos - BUFFER_SIZE - (qint64)ba.size() + 1; + if (sPos < 0) + sPos = 0; + buffer = data(sPos, pos - sPos); + int findPos = buffer.lastIndexOf(ba); + if (findPos >= 0) + result = sPos + (qint64)findPos; + } + return result; +} + + +// ***************************************** Char manipulations + +bool Chunks::insert(qint64 pos, char b) +{ + if ((pos < 0) || (pos > _size)) + return false; + int chunkIdx; + if (pos == _size) + chunkIdx = getChunkIndex(pos-1); + else + chunkIdx = getChunkIndex(pos); + qint64 posInBa = pos - _chunks[chunkIdx].absPos; + _chunks[chunkIdx].data.insert(posInBa, b); + _chunks[chunkIdx].dataChanged.insert(posInBa, char(1)); + for (int idx=chunkIdx+1; idx < _chunks.size(); idx++) + _chunks[idx].absPos += 1; + _size += 1; + _pos = pos; + return true; +} + +bool Chunks::overwrite(qint64 pos, char b) +{ + if ((pos < 0) || (pos >= _size)) + return false; + int chunkIdx = getChunkIndex(pos); + qint64 posInBa = pos - _chunks[chunkIdx].absPos; + _chunks[chunkIdx].data[(int)posInBa] = b; + _chunks[chunkIdx].dataChanged[(int)posInBa] = char(1); + _pos = pos; + return true; +} + +bool Chunks::removeAt(qint64 pos) +{ + if ((pos < 0) || (pos >= _size)) + return false; + int chunkIdx = getChunkIndex(pos); + qint64 posInBa = pos - _chunks[chunkIdx].absPos; + _chunks[chunkIdx].data.remove(posInBa, 1); + _chunks[chunkIdx].dataChanged.remove(posInBa, 1); + for (int idx=chunkIdx+1; idx < _chunks.size(); idx++) + _chunks[idx].absPos -= 1; + _size -= 1; + _pos = pos; + return true; +} + + +// ***************************************** Utility functions + +char Chunks::operator[](qint64 pos) +{ + return data(pos, 1)[0]; +} + +qint64 Chunks::pos() +{ + return _pos; +} + +qint64 Chunks::size() +{ + return _size; +} + +int Chunks::getChunkIndex(qint64 absPos) +{ + // This routine checks, if there is already a copied chunk available. If os, it + // returns a reference to it. If there is no copied chunk available, original + // data will be copied into a new chunk. + + int foundIdx = -1; + int insertIdx = 0; + qint64 ioDelta = 0; + + + for (int idx=0; idx < _chunks.size(); idx++) + { + Chunk chunk = _chunks[idx]; + if ((absPos >= chunk.absPos) && (absPos < (chunk.absPos + chunk.data.size()))) + { + foundIdx = idx; + break; + } + if (absPos < chunk.absPos) + { + insertIdx = idx; + break; + } + ioDelta += chunk.data.size() - CHUNK_SIZE; + insertIdx = idx + 1; + } + + if (foundIdx == -1) + { + Chunk newChunk; + qint64 readAbsPos = absPos - ioDelta; + qint64 readPos = (readAbsPos & READ_CHUNK_MASK); + _ioDevice->open(QIODevice::ReadOnly); + _ioDevice->seek(readPos); + newChunk.data = _ioDevice->read(CHUNK_SIZE); + _ioDevice->close(); + newChunk.absPos = absPos - (readAbsPos - readPos); + newChunk.dataChanged = QByteArray(newChunk.data.size(), char(0)); + _chunks.insert(insertIdx, newChunk); + foundIdx = insertIdx; + } + return foundIdx; +} + + +#ifdef MODUL_TEST +int Chunks::chunkSize() +{ + return _chunks.size(); +} + +#endif diff --git a/source/frontends/qapple/chunks.h b/source/frontends/qapple/chunks.h new file mode 100644 index 00000000..43df76ca --- /dev/null +++ b/source/frontends/qapple/chunks.h @@ -0,0 +1,77 @@ +#ifndef CHUNKS_H +#define CHUNKS_H + +/** \cond docNever */ + +/*! The Chunks class is the storage backend for QHexEdit. + * + * When QHexEdit loads data, Chunks access them using a QIODevice interface. When the app uses + * a QByteArray interface, QBuffer is used to provide again a QIODevice like interface. No data + * will be changed, therefore Chunks opens the QIODevice in QIODevice::ReadOnly mode. After every + * access Chunks closes the QIODevice, that's why external applications can overwrite files while + * QHexEdit shows them. + * + * When the the user starts to edit the data, Chunks creates a local copy of a chunk of data (4 + * kilobytes) and notes all changes there. Parallel to that chunk, there is a second chunk, + * which keep track of which bytes are changed and which not. + * + */ + +#include + +struct Chunk +{ + QByteArray data; + QByteArray dataChanged; + qint64 absPos; +}; + +class Chunks: public QObject +{ +Q_OBJECT +public: + // Constructors and file settings + Chunks(QObject *parent); + Chunks(QIODevice &ioDevice, QObject *parent); + bool setIODevice(QIODevice &ioDevice); + + // Getting data out of Chunks + QByteArray data(qint64 pos=0, qint64 count=-1, QByteArray *highlighted=0); + bool write(QIODevice &iODevice, qint64 pos=0, qint64 count=-1); + + // Set and get highlighting infos + void setDataChanged(qint64 pos, bool dataChanged); + bool dataChanged(qint64 pos); + + // Search API + qint64 indexOf(const QByteArray &ba, qint64 from); + qint64 lastIndexOf(const QByteArray &ba, qint64 from); + + // Char manipulations + bool insert(qint64 pos, char b); + bool overwrite(qint64 pos, char b); + bool removeAt(qint64 pos); + + // Utility functions + char operator[](qint64 pos); + qint64 pos(); + qint64 size(); + + +private: + int getChunkIndex(qint64 absPos); + + QIODevice * _ioDevice; + qint64 _pos; + qint64 _size; + QList _chunks; + +#ifdef MODUL_TEST +public: + int chunkSize(); +#endif +}; + +/** \endcond docNever */ + +#endif // CHUNKS_H diff --git a/source/frontends/qapple/commands.cpp b/source/frontends/qapple/commands.cpp new file mode 100644 index 00000000..e9530d56 --- /dev/null +++ b/source/frontends/qapple/commands.cpp @@ -0,0 +1,165 @@ +#include "commands.h" +#include + + +// Helper class to store single byte commands +class CharCommand : public QUndoCommand +{ +public: + enum CCmd {insert, removeAt, overwrite}; + + CharCommand(Chunks * chunks, CCmd cmd, qint64 charPos, char newChar, + QUndoCommand *parent=0); + + void undo(); + void redo(); + bool mergeWith(const QUndoCommand *command); + int id() const { return 1234; } + +private: + Chunks * _chunks; + qint64 _charPos; + bool _wasChanged; + char _newChar; + char _oldChar; + CCmd _cmd; +}; + +CharCommand::CharCommand(Chunks * chunks, CCmd cmd, qint64 charPos, char newChar, QUndoCommand *parent) + : QUndoCommand(parent) +{ + _chunks = chunks; + _charPos = charPos; + _newChar = newChar; + _cmd = cmd; +} + +bool CharCommand::mergeWith(const QUndoCommand *command) +{ + const CharCommand *nextCommand = static_cast(command); + bool result = false; + + if (_cmd != CharCommand::removeAt) + { + if (nextCommand->_cmd == overwrite) + if (nextCommand->_charPos == _charPos) + { + _newChar = nextCommand->_newChar; + result = true; + } + } + return result; +} + +void CharCommand::undo() +{ + switch (_cmd) + { + case insert: + _chunks->removeAt(_charPos); + break; + case overwrite: + _chunks->overwrite(_charPos, _oldChar); + _chunks->setDataChanged(_charPos, _wasChanged); + break; + case removeAt: + _chunks->insert(_charPos, _oldChar); + _chunks->setDataChanged(_charPos, _wasChanged); + break; + } +} + +void CharCommand::redo() +{ + switch (_cmd) + { + case insert: + _chunks->insert(_charPos, _newChar); + break; + case overwrite: + _oldChar = (*_chunks)[_charPos]; + _wasChanged = _chunks->dataChanged(_charPos); + _chunks->overwrite(_charPos, _newChar); + break; + case removeAt: + _oldChar = (*_chunks)[_charPos]; + _wasChanged = _chunks->dataChanged(_charPos); + _chunks->removeAt(_charPos); + break; + } +} + +UndoStack::UndoStack(Chunks * chunks, QObject * parent) + : QUndoStack(parent) +{ + _chunks = chunks; + _parent = parent; +} + +void UndoStack::insert(qint64 pos, char c) +{ + if ((pos >= 0) && (pos <= _chunks->size())) + { + QUndoCommand *cc = new CharCommand(_chunks, CharCommand::insert, pos, c); + this->push(cc); + } +} + +void UndoStack::insert(qint64 pos, const QByteArray &ba) +{ + if ((pos >= 0) && (pos <= _chunks->size())) + { + QString txt = QString(tr("Inserting %1 bytes")).arg(ba.size()); + beginMacro(txt); + for (int idx=0; idx < ba.size(); idx++) + { + QUndoCommand *cc = new CharCommand(_chunks, CharCommand::insert, pos + idx, ba.at(idx)); + this->push(cc); + } + endMacro(); + } +} + +void UndoStack::removeAt(qint64 pos, qint64 len) +{ + if ((pos >= 0) && (pos < _chunks->size())) + { + if (len==1) + { + QUndoCommand *cc = new CharCommand(_chunks, CharCommand::removeAt, pos, char(0)); + this->push(cc); + } + else + { + QString txt = QString(tr("Delete %1 chars")).arg(len); + beginMacro(txt); + for (qint64 cnt=0; cnt= 0) && (pos < _chunks->size())) + { + QUndoCommand *cc = new CharCommand(_chunks, CharCommand::overwrite, pos, c); + this->push(cc); + } +} + +void UndoStack::overwrite(qint64 pos, int len, const QByteArray &ba) +{ + if ((pos >= 0) && (pos < _chunks->size())) + { + QString txt = QString(tr("Overwrite %1 chars")).arg(len); + beginMacro(txt); + removeAt(pos, len); + insert(pos, ba); + endMacro(); + } +} diff --git a/source/frontends/qapple/commands.h b/source/frontends/qapple/commands.h new file mode 100644 index 00000000..9c34683a --- /dev/null +++ b/source/frontends/qapple/commands.h @@ -0,0 +1,47 @@ +#ifndef COMMANDS_H +#define COMMANDS_H + +/** \cond docNever */ + +#include + +#include "chunks.h" + +/*! CharCommand is a class to provid undo/redo functionality in QHexEdit. +A QUndoCommand represents a single editing action on a document. CharCommand +is responsable for manipulations on single chars. It can insert. overwrite and +remove characters. A manipulation stores allways two actions +1. redo (or do) action +2. undo action. + +CharCommand also supports command compression via mergeWidht(). This allows +the user to execute a undo command contation e.g. 3 steps in a single command. +If you for example insert a new byt "34" this means for the editor doing 3 +steps: insert a "00", overwrite it with "03" and the overwrite it with "34". These +3 steps are combined into a single step, insert a "34". + +The byte array oriented commands are just put into a set of single byte commands, +which are pooled together with the macroBegin() and macroEnd() functionality of +Qt's QUndoStack. +*/ + +class UndoStack : public QUndoStack +{ + Q_OBJECT + +public: + UndoStack(Chunks *chunks, QObject * parent=0); + void insert(qint64 pos, char c); + void insert(qint64 pos, const QByteArray &ba); + void removeAt(qint64 pos, qint64 len=1); + void overwrite(qint64 pos, char c); + void overwrite(qint64 pos, int len, const QByteArray &ba); + +private: + Chunks * _chunks; + QObject * _parent; +}; + +/** \endcond docNever */ + +#endif // COMMANDS_H diff --git a/source/frontends/qapple/memorycontainer.cpp b/source/frontends/qapple/memorycontainer.cpp new file mode 100644 index 00000000..c87d17ae --- /dev/null +++ b/source/frontends/qapple/memorycontainer.cpp @@ -0,0 +1,22 @@ +#include "memorycontainer.h" + +#include "StdAfx.h" +#include "Memory.h" + +MemoryContainer::MemoryContainer(QWidget *parent) : + QTabWidget(parent) +{ + setupUi(this); + + const char * mainBase = (const char *)MemGetMainPtr(0); + QByteArray mainMemory = QByteArray::fromRawData(mainBase, 0x10000); + + main->setReadOnly(true); + main->setData(mainMemory); + + const char * auxBase = (const char *)MemGetAuxPtr(0); + QByteArray auxMemory = QByteArray::fromRawData(auxBase, 0x10000); + + aux->setReadOnly(true); + aux->setData(auxMemory); +} diff --git a/source/frontends/qapple/memorycontainer.h b/source/frontends/qapple/memorycontainer.h new file mode 100644 index 00000000..b968b89e --- /dev/null +++ b/source/frontends/qapple/memorycontainer.h @@ -0,0 +1,14 @@ +#ifndef MEMORYCONTAINER_H +#define MEMORYCONTAINER_H + +#include "ui_memorycontainer.h" + +class MemoryContainer : public QTabWidget, private Ui::MemoryContainer +{ + Q_OBJECT + +public: + explicit MemoryContainer(QWidget *parent = 0); +}; + +#endif // MEMORYCONTAINER_H diff --git a/source/frontends/qapple/memorycontainer.ui b/source/frontends/qapple/memorycontainer.ui new file mode 100644 index 00000000..8223ee2f --- /dev/null +++ b/source/frontends/qapple/memorycontainer.ui @@ -0,0 +1,50 @@ + + + MemoryContainer + + + + 0 + 0 + 400 + 300 + + + + Memory + + + 0 + + + + Main + + + + + + + + + + Aux + + + + + + + + + + + QHexEdit + QWidget +
qhexedit.h
+ 1 +
+
+ + +
diff --git a/source/frontends/qapple/qapple.cpp b/source/frontends/qapple/qapple.cpp index a8005631..080e265a 100644 --- a/source/frontends/qapple/qapple.cpp +++ b/source/frontends/qapple/qapple.cpp @@ -19,6 +19,7 @@ #include "linux/joy_input.h" #include "emulator.h" +#include "memorycontainer.h" #include #include @@ -54,6 +55,11 @@ namespace VideoInitialize(); } + void stopEmulator() + { + MemDestroy(); + } + void uninitialiseEmulator() { HD_Destroy(); @@ -214,6 +220,8 @@ void QApple::on_actionDisk_2_triggered() void QApple::on_actionReboot_triggered() { + emit endEmulator(); + stopEmulator(); startEmulator(); } @@ -227,3 +235,15 @@ void QApple::timerEvent(QTimerEvent *) { on_timer(); } + +void QApple::on_actionMemory_triggered() +{ + MemoryContainer * container = new MemoryContainer(mdiArea); + QMdiSubWindow * window = mdiArea->addSubWindow(container); + + // need to close as it points to old memory + connect(this, SIGNAL(endEmulator()), window, SLOT(close())); + + window->setWindowTitle("Memory viewer"); + window->show(); +} diff --git a/source/frontends/qapple/qapple.h b/source/frontends/qapple/qapple.h index 1c510d33..b6e0b21c 100644 --- a/source/frontends/qapple/qapple.h +++ b/source/frontends/qapple/qapple.h @@ -15,6 +15,9 @@ class QApple : public QMainWindow, private Ui::QApple public: explicit QApple(QWidget *parent = 0); +signals: + void endEmulator(); + protected: virtual void closeEvent(QCloseEvent * event); virtual void timerEvent(QTimerEvent *event); @@ -40,6 +43,8 @@ private slots: void on_timer(); + void on_actionMemory_triggered(); + private: void stopTimer(); diff --git a/source/frontends/qapple/qapple.pro b/source/frontends/qapple/qapple.pro index aab612d7..d41c41d4 100644 --- a/source/frontends/qapple/qapple.pro +++ b/source/frontends/qapple/qapple.pro @@ -17,15 +17,24 @@ SOURCES += main.cpp\ qresources.cpp \ emulator.cpp \ video.cpp \ - graphicscache.cpp + graphicscache.cpp \ + chunks.cpp \ + commands.cpp \ + qhexedit.cpp \ + memorycontainer.cpp HEADERS += qapple.h \ emulator.h \ video.h \ - graphicscache.h + graphicscache.h \ + chunks.h \ + commands.h \ + qhexedit.h \ + memorycontainer.h FORMS += qapple.ui \ - emulator.ui + emulator.ui \ + memorycontainer.ui RESOURCES += \ qapple.qrc diff --git a/source/frontends/qapple/qapple.ui b/source/frontends/qapple/qapple.ui index 5e1b6608..43b30a61 100644 --- a/source/frontends/qapple/qapple.ui +++ b/source/frontends/qapple/qapple.ui @@ -33,12 +33,12 @@ 0 0 1032 - 25 + 20 - System + S&ystem @@ -46,20 +46,27 @@ - Disks + Dis&ks - Video + &Video + + + Wi&ndow + + + + @@ -75,7 +82,7 @@ - Start + &Start @@ -83,7 +90,7 @@ false - Pause + &Pause @@ -103,22 +110,27 @@ - Disk 1... + &Disk 1... - Disk 2... + Disk &2... - Reboot + &Reboot - Benchmark + &Benchmark + + + + + &Memory diff --git a/source/frontends/qapple/qhexedit.cpp b/source/frontends/qapple/qhexedit.cpp new file mode 100644 index 00000000..f4e586a4 --- /dev/null +++ b/source/frontends/qapple/qhexedit.cpp @@ -0,0 +1,1135 @@ +#include +#include +#include +#include +#include + +#include "qhexedit.h" +#include +#include + + +// ********************************************************************** Constructor, destructor + +QHexEdit::QHexEdit(QWidget *parent) : QAbstractScrollArea(parent) +{ + _addressArea = true; + _addressWidth = 4; + _asciiArea = true; + _overwriteMode = true; + _highlighting = true; + _readOnly = false; + _cursorPosition = 0; + _lastEventSize = 0; + _hexCharsInLine = 47; + _bytesPerLine = 16; + _editAreaIsAscii = false; + _hexCaps = false; + _dynamicBytesPerLine = false; + + _chunks = new Chunks(this); + _undoStack = new UndoStack(_chunks, this); +#ifdef Q_OS_WIN32 + setFont(QFont("Courier", 10)); +#else + setFont(QFont("Monospace", 10)); +#endif + setAddressAreaColor(this->palette().alternateBase().color()); + setHighlightingColor(QColor(0xff, 0xff, 0x99, 0xff)); + setSelectionColor(this->palette().highlight().color()); + + connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor())); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(adjust())); + connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(adjust())); + connect(_undoStack, SIGNAL(indexChanged(int)), this, SLOT(dataChangedPrivate(int))); + + _cursorTimer.setInterval(500); + _cursorTimer.start(); + + setAddressWidth(4); + setAddressArea(true); + setAsciiArea(true); + setOverwriteMode(true); + setHighlighting(true); + setReadOnly(false); + + init(); + +} + +QHexEdit::~QHexEdit() +{ +} + +// ********************************************************************** Properties + +void QHexEdit::setAddressArea(bool addressArea) +{ + _addressArea = addressArea; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +bool QHexEdit::addressArea() +{ + return _addressArea; +} + +void QHexEdit::setAddressAreaColor(const QColor &color) +{ + _addressAreaColor = color; + viewport()->update(); +} + +QColor QHexEdit::addressAreaColor() +{ + return _addressAreaColor; +} + +void QHexEdit::setAddressOffset(qint64 addressOffset) +{ + _addressOffset = addressOffset; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +qint64 QHexEdit::addressOffset() +{ + return _addressOffset; +} + +void QHexEdit::setAddressWidth(int addressWidth) +{ + _addressWidth = addressWidth; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +int QHexEdit::addressWidth() +{ + qint64 size = _chunks->size(); + int n = 1; + if (size > Q_INT64_C(0x100000000)){ n += 8; size /= Q_INT64_C(0x100000000);} + if (size > 0x10000){ n += 4; size /= 0x10000;} + if (size > 0x100){ n += 2; size /= 0x100;} + if (size > 0x10){ n += 1; size /= 0x10;} + + if (n > _addressWidth) + return n; + else + return _addressWidth; +} + +void QHexEdit::setAsciiArea(bool asciiArea) +{ + if (!asciiArea) + _editAreaIsAscii = false; + _asciiArea = asciiArea; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +bool QHexEdit::asciiArea() +{ + return _asciiArea; +} + +void QHexEdit::setBytesPerLine(int count) +{ + _bytesPerLine = count; + _hexCharsInLine = count * 3 - 1; + + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +int QHexEdit::bytesPerLine() +{ + return _bytesPerLine; +} + +void QHexEdit::setCursorPosition(qint64 position) +{ + // 1. delete old cursor + _blink = false; + viewport()->update(_cursorRect); + + // 2. Check, if cursor in range? + if (position > (_chunks->size() * 2 - 1)) + position = _chunks->size() * 2 - (_overwriteMode ? 1 : 0); + + if (position < 0) + position = 0; + + // 3. Calc new position of cursor + _bPosCurrent = position / 2; + _pxCursorY = ((position / 2 - _bPosFirst) / _bytesPerLine + 1) * _pxCharHeight; + int x = (position % (2 * _bytesPerLine)); + if (_editAreaIsAscii) + { + _pxCursorX = x / 2 * _pxCharWidth + _pxPosAsciiX; + _cursorPosition = position & 0xFFFFFFFFFFFFFFFE; + } else { + _pxCursorX = (((x / 2) * 3) + (x % 2)) * _pxCharWidth + _pxPosHexX; + _cursorPosition = position; + } + + if (_overwriteMode) + _cursorRect = QRect(_pxCursorX - horizontalScrollBar()->value(), _pxCursorY + _pxCursorWidth, _pxCharWidth, _pxCursorWidth); + else + _cursorRect = QRect(_pxCursorX - horizontalScrollBar()->value(), _pxCursorY - _pxCharHeight + 4, _pxCursorWidth, _pxCharHeight); + + // 4. Immediately draw new cursor + _blink = true; + viewport()->update(_cursorRect); + emit currentAddressChanged(_bPosCurrent); +} + +qint64 QHexEdit::cursorPosition(QPoint pos) +{ + // Calc cursor position depending on a graphical position + qint64 result = -1; + int posX = pos.x() + horizontalScrollBar()->value(); + int posY = pos.y() - 3; + if ((posX >= _pxPosHexX) && (posX < (_pxPosHexX + (1 + _hexCharsInLine) * _pxCharWidth))) + { + _editAreaIsAscii = false; + int x = (posX - _pxPosHexX) / _pxCharWidth; + x = (x / 3) * 2 + x % 3; + int y = (posY / _pxCharHeight) * 2 * _bytesPerLine; + result = _bPosFirst * 2 + x + y; + } + else + if (_asciiArea && (posX >= _pxPosAsciiX) && (posX < (_pxPosAsciiX + (1 + _bytesPerLine) * _pxCharWidth))) + { + _editAreaIsAscii = true; + int x = 2 * (posX - _pxPosAsciiX) / _pxCharWidth; + int y = (posY / _pxCharHeight) * 2 * _bytesPerLine; + result = _bPosFirst * 2 + x + y; + } + return result; +} + +qint64 QHexEdit::cursorPosition() +{ + return _cursorPosition; +} + +void QHexEdit::setData(const QByteArray &ba) +{ + _data = ba; + _bData.setData(_data); + setData(_bData); +} + +QByteArray QHexEdit::data() +{ + return _chunks->data(0, -1); +} + +void QHexEdit::setHighlighting(bool highlighting) +{ + _highlighting = highlighting; + viewport()->update(); +} + +bool QHexEdit::highlighting() +{ + return _highlighting; +} + +void QHexEdit::setHighlightingColor(const QColor &color) +{ + _brushHighlighted = QBrush(color); + _penHighlighted = QPen(viewport()->palette().color(QPalette::WindowText)); + viewport()->update(); +} + +QColor QHexEdit::highlightingColor() +{ + return _brushHighlighted.color(); +} + +void QHexEdit::setOverwriteMode(bool overwriteMode) +{ + _overwriteMode = overwriteMode; + emit overwriteModeChanged(overwriteMode); +} + +bool QHexEdit::overwriteMode() +{ + return _overwriteMode; +} + +void QHexEdit::setSelectionColor(const QColor &color) +{ + _brushSelection = QBrush(color); + _penSelection = QPen(Qt::white); + viewport()->update(); +} + +QColor QHexEdit::selectionColor() +{ + return _brushSelection.color(); +} + +bool QHexEdit::isReadOnly() +{ + return _readOnly; +} + +void QHexEdit::setReadOnly(bool readOnly) +{ + _readOnly = readOnly; +} + +void QHexEdit::setHexCaps(const bool isCaps) +{ + if (_hexCaps != isCaps) + { + _hexCaps = isCaps; + viewport()->update(); + } +} + +bool QHexEdit::hexCaps() +{ + return _hexCaps; +} + +void QHexEdit::setDynamicBytesPerLine(const bool isDynamic) +{ + _dynamicBytesPerLine = isDynamic; + resizeEvent(NULL); +} + +bool QHexEdit::dynamicBytesPerLine() +{ + return _dynamicBytesPerLine; +} + +// ********************************************************************** Access to data of qhexedit +bool QHexEdit::setData(QIODevice &iODevice) +{ + bool ok = _chunks->setIODevice(iODevice); + init(); + dataChangedPrivate(); + return ok; +} + +QByteArray QHexEdit::dataAt(qint64 pos, qint64 count) +{ + return _chunks->data(pos, count); +} + +bool QHexEdit::write(QIODevice &iODevice, qint64 pos, qint64 count) +{ + return _chunks->write(iODevice, pos, count); +} + +// ********************************************************************** Char handling +void QHexEdit::insert(qint64 index, char ch) +{ + _undoStack->insert(index, ch); + refresh(); +} + +void QHexEdit::remove(qint64 index, qint64 len) +{ + _undoStack->removeAt(index, len); + refresh(); +} + +void QHexEdit::replace(qint64 index, char ch) +{ + _undoStack->overwrite(index, ch); + refresh(); +} + +// ********************************************************************** ByteArray handling +void QHexEdit::insert(qint64 pos, const QByteArray &ba) +{ + _undoStack->insert(pos, ba); + refresh(); +} + +void QHexEdit::replace(qint64 pos, qint64 len, const QByteArray &ba) +{ + _undoStack->overwrite(pos, len, ba); + refresh(); +} + +// ********************************************************************** Utility functions +void QHexEdit::ensureVisible() +{ + if (_cursorPosition < (_bPosFirst * 2)) + verticalScrollBar()->setValue((int)(_cursorPosition / 2 / _bytesPerLine)); + if (_cursorPosition > ((_bPosFirst + (_rowsShown - 1)*_bytesPerLine) * 2)) + verticalScrollBar()->setValue((int)(_cursorPosition / 2 / _bytesPerLine) - _rowsShown + 1); + if (_pxCursorX < horizontalScrollBar()->value()) + horizontalScrollBar()->setValue(_pxCursorX); + if ((_pxCursorX + _pxCharWidth) > (horizontalScrollBar()->value() + viewport()->width())) + horizontalScrollBar()->setValue(_pxCursorX + _pxCharWidth - viewport()->width()); + viewport()->update(); +} + +qint64 QHexEdit::indexOf(const QByteArray &ba, qint64 from) +{ + qint64 pos = _chunks->indexOf(ba, from); + if (pos > -1) + { + qint64 curPos = pos*2; + setCursorPosition(curPos + ba.length()*2); + resetSelection(curPos); + setSelection(curPos + ba.length()*2); + ensureVisible(); + } + return pos; +} + +bool QHexEdit::isModified() +{ + return _modified; +} + +qint64 QHexEdit::lastIndexOf(const QByteArray &ba, qint64 from) +{ + qint64 pos = _chunks->lastIndexOf(ba, from); + if (pos > -1) + { + qint64 curPos = pos*2; + setCursorPosition(curPos - 1); + resetSelection(curPos); + setSelection(curPos + ba.length()*2); + ensureVisible(); + } + return pos; +} + +void QHexEdit::redo() +{ + _undoStack->redo(); + setCursorPosition(_chunks->pos()*(_editAreaIsAscii ? 1 : 2)); + refresh(); +} + +QString QHexEdit::selectionToReadableString() +{ + QByteArray ba = _chunks->data(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + return toReadable(ba); +} + +void QHexEdit::setFont(const QFont &font) +{ + QWidget::setFont(font); + _pxCharWidth = fontMetrics().width(QLatin1Char('2')); + _pxCharHeight = fontMetrics().height(); + _pxGapAdr = _pxCharWidth / 2; + _pxGapAdrHex = _pxCharWidth; + _pxGapHexAscii = 2 * _pxCharWidth; + _pxCursorWidth = _pxCharHeight / 7; + _pxSelectionSub = _pxCharHeight / 5; + viewport()->update(); +} + +QString QHexEdit::toReadableString() +{ + QByteArray ba = _chunks->data(); + return toReadable(ba); +} + +void QHexEdit::undo() +{ + _undoStack->undo(); + setCursorPosition(_chunks->pos()*(_editAreaIsAscii ? 1 : 2)); + refresh(); +} + +// ********************************************************************** Handle events +void QHexEdit::keyPressEvent(QKeyEvent *event) +{ + // Cursor movements + if (event->matches(QKeySequence::MoveToNextChar)) + { + qint64 pos = _cursorPosition + 1; + if (_editAreaIsAscii) + pos += 1; + setCursorPosition(pos); + resetSelection(pos); + } + if (event->matches(QKeySequence::MoveToPreviousChar)) + { + qint64 pos = _cursorPosition - 1; + if (_editAreaIsAscii) + pos -= 1; + setCursorPosition(pos); + resetSelection(pos); + } + if (event->matches(QKeySequence::MoveToEndOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)) + (2 * _bytesPerLine) - 1; + setCursorPosition(pos); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)); + setCursorPosition(pos); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousLine)) + { + setCursorPosition(_cursorPosition - (2 * _bytesPerLine)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToNextLine)) + { + setCursorPosition(_cursorPosition + (2 * _bytesPerLine)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToNextPage)) + { + setCursorPosition(_cursorPosition + (((_rowsShown - 1) * 2 * _bytesPerLine))); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousPage)) + { + setCursorPosition(_cursorPosition - (((_rowsShown - 1) * 2 * _bytesPerLine))); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToEndOfDocument)) + { + setCursorPosition(_chunks->size() * 2 ); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfDocument)) + { + setCursorPosition(0); + resetSelection(_cursorPosition); + } + + // Select commands + if (event->matches(QKeySequence::SelectAll)) + { + resetSelection(0); + setSelection(2 * _chunks->size() + 1); + } + if (event->matches(QKeySequence::SelectNextChar)) + { + qint64 pos = _cursorPosition + 1; + if (_editAreaIsAscii) + pos += 1; + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousChar)) + { + qint64 pos = _cursorPosition - 1; + if (_editAreaIsAscii) + pos -= 1; + setSelection(pos); + setCursorPosition(pos); + } + if (event->matches(QKeySequence::SelectEndOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)) + (2 * _bytesPerLine) - 1; + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousLine)) + { + qint64 pos = _cursorPosition - (2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectNextLine)) + { + qint64 pos = _cursorPosition + (2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectNextPage)) + { + qint64 pos = _cursorPosition + (((viewport()->height() / _pxCharHeight) - 1) * 2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousPage)) + { + qint64 pos = _cursorPosition - (((viewport()->height() / _pxCharHeight) - 1) * 2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectEndOfDocument)) + { + qint64 pos = _chunks->size() * 2; + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfDocument)) + { + qint64 pos = 0; + setCursorPosition(pos); + setSelection(pos); + } + + // Edit Commands + if (!_readOnly) + { + /* Cut */ + if (event->matches(QKeySequence::Cut)) + { + QByteArray ba = _chunks->data(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()).toHex(); + for (qint64 idx = 32; idx < ba.size(); idx +=33) + ba.insert(idx, "\n"); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(ba); + if (_overwriteMode) + { + qint64 len = getSelectionEnd() - getSelectionBegin(); + replace(getSelectionBegin(), (int)len, QByteArray((int)len, char(0))); + } + else + { + remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + } + setCursorPosition(2 * getSelectionBegin()); + resetSelection(2 * getSelectionBegin()); + } else + + /* Paste */ + if (event->matches(QKeySequence::Paste)) + { + QClipboard *clipboard = QApplication::clipboard(); + QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1()); + if (_overwriteMode) + { + ba = ba.left(std::min(ba.size(), (_chunks->size() - _bPosCurrent))); + replace(_bPosCurrent, ba.size(), ba); + } + else + insert(_bPosCurrent, ba); + setCursorPosition(_cursorPosition + 2 * ba.size()); + resetSelection(getSelectionBegin()); + } else + + /* Delete char */ + if (event->matches(QKeySequence::Delete)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + _bPosCurrent = getSelectionBegin(); + if (_overwriteMode) + { + QByteArray ba = QByteArray(getSelectionEnd() - getSelectionBegin(), char(0)); + replace(_bPosCurrent, ba.size(), ba); + } + else + { + remove(_bPosCurrent, getSelectionEnd() - getSelectionBegin()); + } + } + else + { + if (_overwriteMode) + replace(_bPosCurrent, char(0)); + else + remove(_bPosCurrent, 1); + } + setCursorPosition(2 * _bPosCurrent); + resetSelection(2 * _bPosCurrent); + } else + + /* Backspace */ + if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + _bPosCurrent = getSelectionBegin(); + setCursorPosition(2 * _bPosCurrent); + if (_overwriteMode) + { + QByteArray ba = QByteArray(getSelectionEnd() - getSelectionBegin(), char(0)); + replace(_bPosCurrent, ba.size(), ba); + } + else + { + remove(_bPosCurrent, getSelectionEnd() - getSelectionBegin()); + } + resetSelection(2 * _bPosCurrent); + } + else + { + bool behindLastByte = false; + if ((_cursorPosition / 2) == _chunks->size()) + behindLastByte = true; + + _bPosCurrent -= 1; + if (_overwriteMode) + replace(_bPosCurrent, char(0)); + else + remove(_bPosCurrent, 1); + + if (!behindLastByte) + _bPosCurrent -= 1; + + setCursorPosition(2 * _bPosCurrent); + resetSelection(2 * _bPosCurrent); + } + } else + + /* undo */ + if (event->matches(QKeySequence::Undo)) + { + undo(); + } else + + /* redo */ + if (event->matches(QKeySequence::Redo)) + { + redo(); + } else + + if ((QApplication::keyboardModifiers() == Qt::NoModifier) || + (QApplication::keyboardModifiers() == Qt::KeypadModifier) || + (QApplication::keyboardModifiers() == Qt::ShiftModifier) || + (QApplication::keyboardModifiers() == (Qt::AltModifier | Qt::ControlModifier)) || + (QApplication::keyboardModifiers() == Qt::GroupSwitchModifier)) + { + /* Hex and ascii input */ + int key; + if (_editAreaIsAscii) + key = (uchar)event->text()[0].toLatin1(); + else + key = int(event->text()[0].toLower().toLatin1()); + + if ((((key >= '0' && key <= '9') || (key >= 'a' && key <= 'f')) && _editAreaIsAscii == false) + || (key >= ' ' && _editAreaIsAscii)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + if (_overwriteMode) + { + qint64 len = getSelectionEnd() - getSelectionBegin(); + replace(getSelectionBegin(), (int)len, QByteArray((int)len, char(0))); + } else + { + remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + _bPosCurrent = getSelectionBegin(); + } + setCursorPosition(2 * _bPosCurrent); + resetSelection(2 * _bPosCurrent); + } + + // If insert mode, then insert a byte + if (_overwriteMode == false) + if ((_cursorPosition % 2) == 0) + insert(_bPosCurrent, char(0)); + + // Change content + if (_chunks->size() > 0) + { + char ch = key; + if (!_editAreaIsAscii){ + QByteArray hexValue = _chunks->data(_bPosCurrent, 1).toHex(); + if ((_cursorPosition % 2) == 0) + hexValue[0] = key; + else + hexValue[1] = key; + ch = QByteArray().fromHex(hexValue)[0]; + } + replace(_bPosCurrent, ch); + if (_editAreaIsAscii) + setCursorPosition(_cursorPosition + 2); + else + setCursorPosition(_cursorPosition + 1); + resetSelection(_cursorPosition); + } + } + } + + + } + + /* Copy */ + if (event->matches(QKeySequence::Copy)) + { + QByteArray ba = _chunks->data(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()).toHex(); + for (qint64 idx = 32; idx < ba.size(); idx +=33) + ba.insert(idx, "\n"); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(ba); + } + + // Switch between insert/overwrite mode + if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier)) + { + setOverwriteMode(!overwriteMode()); + setCursorPosition(_cursorPosition); + } + + // switch from hex to ascii edit + if (event->key() == Qt::Key_Tab && !_editAreaIsAscii){ + _editAreaIsAscii = true; + setCursorPosition(_cursorPosition); + } + + // switch from ascii to hex edit + if (event->key() == Qt::Key_Backtab && _editAreaIsAscii){ + _editAreaIsAscii = false; + setCursorPosition(_cursorPosition); + } + + refresh(); +} + +void QHexEdit::mouseMoveEvent(QMouseEvent * event) +{ + _blink = false; + viewport()->update(); + qint64 actPos = cursorPosition(event->pos()); + if (actPos >= 0) + { + setCursorPosition(actPos); + setSelection(actPos); + } +} + +void QHexEdit::mousePressEvent(QMouseEvent * event) +{ + _blink = false; + viewport()->update(); + qint64 cPos = cursorPosition(event->pos()); + if (cPos >= 0) + { + resetSelection(cPos); + setCursorPosition(cPos); + } +} + +void QHexEdit::paintEvent(QPaintEvent *event) +{ + QPainter painter(viewport()); + int pxOfsX = horizontalScrollBar()->value(); + + if (event->rect() != _cursorRect) + { + int pxPosStartY = _pxCharHeight; + + // draw some patterns if needed + painter.fillRect(event->rect(), viewport()->palette().color(QPalette::Base)); + if (_addressArea) + painter.fillRect(QRect(-pxOfsX, event->rect().top(), _pxPosHexX - _pxGapAdrHex/2, height()), _addressAreaColor); + if (_asciiArea) + { + int linePos = _pxPosAsciiX - (_pxGapHexAscii / 2); + painter.setPen(Qt::gray); + painter.drawLine(linePos - pxOfsX, event->rect().top(), linePos - pxOfsX, height()); + } + + painter.setPen(viewport()->palette().color(QPalette::WindowText)); + + // paint address area + if (_addressArea) + { + QString address; + for (int row=0, pxPosY = _pxCharHeight; row <= (_dataShown.size()/_bytesPerLine); row++, pxPosY +=_pxCharHeight) + { + address = QString("%1").arg(_bPosFirst + row*_bytesPerLine + _addressOffset, _addrDigits, 16, QChar('0')); + painter.drawText(_pxPosAdrX - pxOfsX, pxPosY, address); + } + } + + // paint hex and ascii area + QPen colStandard = QPen(viewport()->palette().color(QPalette::WindowText)); + + painter.setBackgroundMode(Qt::TransparentMode); + + for (int row = 0, pxPosY = pxPosStartY; row <= _rowsShown; row++, pxPosY +=_pxCharHeight) + { + QByteArray hex; + int pxPosX = _pxPosHexX - pxOfsX; + int pxPosAsciiX2 = _pxPosAsciiX - pxOfsX; + qint64 bPosLine = row * _bytesPerLine; + for (int colIdx = 0; ((bPosLine + colIdx) < _dataShown.size() && (colIdx < _bytesPerLine)); colIdx++) + { + QColor c = viewport()->palette().color(QPalette::Base); + painter.setPen(colStandard); + + qint64 posBa = _bPosFirst + bPosLine + colIdx; + if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa)) + { + c = _brushSelection.color(); + painter.setPen(_penSelection); + } + else + { + if (_highlighting) + if (_markedShown.at((int)(posBa - _bPosFirst))) + { + c = _brushHighlighted.color(); + painter.setPen(_penHighlighted); + } + } + + // render hex value + QRect r; + if (colIdx == 0) + r.setRect(pxPosX, pxPosY - _pxCharHeight + _pxSelectionSub, 2*_pxCharWidth, _pxCharHeight); + else + r.setRect(pxPosX - _pxCharWidth, pxPosY - _pxCharHeight + _pxSelectionSub, 3*_pxCharWidth, _pxCharHeight); + painter.fillRect(r, c); + hex = _hexDataShown.mid((bPosLine + colIdx) * 2, 2); + painter.drawText(pxPosX, pxPosY, hexCaps()?hex.toUpper():hex); + pxPosX += 3*_pxCharWidth; + + // render ascii value + if (_asciiArea) + { + int ch = (uchar)_dataShown.at(bPosLine + colIdx); + if ( ch < 0x20 ) + ch = '.'; + r.setRect(pxPosAsciiX2, pxPosY - _pxCharHeight + _pxSelectionSub, _pxCharWidth, _pxCharHeight); + painter.fillRect(r, c); + painter.drawText(pxPosAsciiX2, pxPosY, QChar(ch)); + pxPosAsciiX2 += _pxCharWidth; + } + } + } + painter.setBackgroundMode(Qt::TransparentMode); + painter.setPen(viewport()->palette().color(QPalette::WindowText)); + } + + // _cursorPosition counts in 2, _bPosFirst counts in 1 + int hexPositionInShowData = _cursorPosition - 2 * _bPosFirst; + + // due to scrolling the cursor can go out of the currently displayed data + if ((hexPositionInShowData >= 0) && (hexPositionInShowData < _hexDataShown.size())) + { + // paint cursor + if (_blink && !_readOnly && hasFocus()) + painter.fillRect(_cursorRect, this->palette().color(QPalette::WindowText)); + else + { + // make the background stick out + QColor color = viewport()->palette().dark().color(); + painter.fillRect(QRect(_pxCursorX - pxOfsX, _pxCursorY - _pxCharHeight + _pxSelectionSub, _pxCharWidth, _pxCharHeight), color); + if (_editAreaIsAscii) + { + // every 2 hex there is 1 ascii + int asciiPositionInShowData = hexPositionInShowData / 2; + int ch = (uchar)_dataShown.at(asciiPositionInShowData); + if ( ch < 0x20 ) + ch = '.'; + painter.drawText(_pxCursorX - pxOfsX, _pxCursorY, QChar(ch)); + } + else + { + painter.drawText(_pxCursorX - pxOfsX, _pxCursorY, _hexDataShown.mid(hexPositionInShowData, 1)); + } + } + } + + // emit event, if size has changed + if (_lastEventSize != _chunks->size()) + { + _lastEventSize = _chunks->size(); + emit currentSizeChanged(_lastEventSize); + } +} + +void QHexEdit::resizeEvent(QResizeEvent *) +{ + if (_dynamicBytesPerLine) + { + int pxFixGaps = 0; + if (_addressArea) + pxFixGaps = addressWidth() * _pxCharWidth + _pxGapAdr; + pxFixGaps += _pxGapAdrHex; + if (_asciiArea) + pxFixGaps += _pxGapHexAscii; + + // +1 because the last hex value do not have space. so it is effective one char more + int charWidth = (viewport()->width() - pxFixGaps ) / _pxCharWidth + 1; + + // 2 hex alfa-digits 1 space 1 ascii per byte = 4; if ascii is disabled then 3 + // to prevent devision by zero use the min value 1 + setBytesPerLine(std::max(charWidth / (_asciiArea ? 4 : 3),1)); + } + adjust(); +} + +bool QHexEdit::focusNextPrevChild(bool next) +{ + if (_addressArea) + { + if ( (next && _editAreaIsAscii) || (!next && !_editAreaIsAscii )) + return QWidget::focusNextPrevChild(next); + else + return false; + } + else + { + return QWidget::focusNextPrevChild(next); + } +} + +// ********************************************************************** Handle selections +void QHexEdit::resetSelection() +{ + _bSelectionBegin = _bSelectionInit; + _bSelectionEnd = _bSelectionInit; +} + +void QHexEdit::resetSelection(qint64 pos) +{ + pos = pos / 2 ; + if (pos < 0) + pos = 0; + if (pos > _chunks->size()) + pos = _chunks->size(); + + _bSelectionInit = pos; + _bSelectionBegin = pos; + _bSelectionEnd = pos; +} + +void QHexEdit::setSelection(qint64 pos) +{ + pos = pos / 2; + if (pos < 0) + pos = 0; + if (pos > _chunks->size()) + pos = _chunks->size(); + + if (pos >= _bSelectionInit) + { + _bSelectionEnd = pos; + _bSelectionBegin = _bSelectionInit; + } + else + { + _bSelectionBegin = pos; + _bSelectionEnd = _bSelectionInit; + } +} + +int QHexEdit::getSelectionBegin() +{ + return _bSelectionBegin; +} + +int QHexEdit::getSelectionEnd() +{ + return _bSelectionEnd; +} + +// ********************************************************************** Private utility functions +void QHexEdit::init() +{ + _undoStack->clear(); + setAddressOffset(0); + resetSelection(0); + setCursorPosition(0); + verticalScrollBar()->setValue(0); + _modified = false; +} + +void QHexEdit::adjust() +{ + // recalc Graphics + if (_addressArea) + { + _addrDigits = addressWidth(); + _pxPosHexX = _pxGapAdr + _addrDigits*_pxCharWidth + _pxGapAdrHex; + } + else + _pxPosHexX = _pxGapAdrHex; + _pxPosAdrX = _pxGapAdr; + _pxPosAsciiX = _pxPosHexX + _hexCharsInLine * _pxCharWidth + _pxGapHexAscii; + + // set horizontalScrollBar() + int pxWidth = _pxPosAsciiX; + if (_asciiArea) + pxWidth += _bytesPerLine*_pxCharWidth; + horizontalScrollBar()->setRange(0, pxWidth - viewport()->width()); + horizontalScrollBar()->setPageStep(viewport()->width()); + + // set verticalScrollbar() + _rowsShown = ((viewport()->height()-4)/_pxCharHeight); + int lineCount = (int)(_chunks->size() / (qint64)_bytesPerLine) + 1; + verticalScrollBar()->setRange(0, lineCount - _rowsShown); + verticalScrollBar()->setPageStep(_rowsShown); + + int value = verticalScrollBar()->value(); + _bPosFirst = (qint64)value * _bytesPerLine; + _bPosLast = _bPosFirst + (qint64)(_rowsShown * _bytesPerLine) - 1; + if (_bPosLast >= _chunks->size()) + _bPosLast = _chunks->size() - 1; + readBuffers(); + setCursorPosition(_cursorPosition); +} + +void QHexEdit::dataChangedPrivate(int) +{ + _modified = _undoStack->index() != 0; + adjust(); + emit dataChanged(); +} + +void QHexEdit::refresh() +{ + ensureVisible(); + readBuffers(); +} + +void QHexEdit::readBuffers() +{ + _dataShown = _chunks->data(_bPosFirst, _bPosLast - _bPosFirst + _bytesPerLine + 1, &_markedShown); + _hexDataShown = QByteArray(_dataShown.toHex()); +} + +QString QHexEdit::toReadable(const QByteArray &ba) +{ + QString result; + + for (int i=0; i < ba.size(); i += 16) + { + QString addrStr = QString("%1").arg(_addressOffset + i, addressWidth(), 16, QChar('0')); + QString hexStr; + QString ascStr; + for (int j=0; j<16; j++) + { + if ((i + j) < ba.size()) + { + hexStr.append(" ").append(ba.mid(i+j, 1).toHex()); + char ch = ba[i + j]; + if ((ch < 0x20) || (ch > 0x7e)) + ch = '.'; + ascStr.append(QChar(ch)); + } + } + result += addrStr + " " + QString("%1").arg(hexStr, -48) + " " + QString("%1").arg(ascStr, -17) + "\n"; + } + return result; +} + +void QHexEdit::updateCursor() +{ + if (_blink) + _blink = false; + else + _blink = true; + viewport()->update(_cursorRect); +} diff --git a/source/frontends/qapple/qhexedit.h b/source/frontends/qapple/qhexedit.h new file mode 100644 index 00000000..0bac26fb --- /dev/null +++ b/source/frontends/qapple/qhexedit.h @@ -0,0 +1,419 @@ +#ifndef QHEXEDIT_H +#define QHEXEDIT_H + +#include +#include +#include + +#include "chunks.h" +#include "commands.h" + +#ifdef QHEXEDIT_EXPORTS +#define QHEXEDIT_API Q_DECL_EXPORT +#elif QHEXEDIT_IMPORTS +#define QHEXEDIT_API Q_DECL_IMPORT +#else +#define QHEXEDIT_API +#endif + +/** \mainpage +QHexEdit is a binary editor widget for Qt. + +\version Version 0.8.3 +\image html qhexedit.png +*/ + + +/** QHexEdit is a hex editor widget written in C++ for the Qt (Qt4, Qt5) framework. +It is a simple editor for binary data, just like QPlainTextEdit is for text +data. There are sip configuration files included, so it is easy to create +bindings for PyQt and you can use this widget also in python 2 and 3. + +QHexEdit takes the data of a QByteArray (setData()) and shows it. You can use +the mouse or the keyboard to navigate inside the widget. If you hit the keys +(0..9, a..f) you will change the data. Changed data is highlighted and can be +accessed via data(). + +Normaly QHexEdit works in the overwrite Mode. You can set overwriteMode(false) +and insert data. In this case the size of data() increases. It is also possible +to delete bytes (del or backspace), here the size of data decreases. + +You can select data with keyboard hits or mouse movements. The copy-key will +copy the selected data into the clipboard. The cut-key copies also but delets +it afterwards. In overwrite mode, the paste function overwrites the content of +the (does not change the length) data. In insert mode, clipboard data will be +inserted. The clipboard content is expected in ASCII Hex notation. Unknown +characters will be ignored. + +QHexEdit comes with undo/redo functionality. All changes can be undone, by +pressing the undo-key (usually ctr-z). They can also be redone afterwards. +The undo/redo framework is cleared, when setData() sets up a new +content for the editor. You can search data inside the content with indexOf() +and lastIndexOf(). The replace() function is to change located subdata. This +'replaced' data can also be undone by the undo/redo framework. + +QHexEdit is based on QIODevice, that's why QHexEdit can handle big amounts of +data. The size of edited data can be more then two gigabytes without any +restrictions. +*/ +class QHEXEDIT_API QHexEdit : public QAbstractScrollArea +{ + Q_OBJECT + + /*! Property address area switch the address area on or off. Set addressArea true + (show it), false (hide it). + */ + Q_PROPERTY(bool addressArea READ addressArea WRITE setAddressArea) + + /*! Property address area color sets (setAddressAreaColor()) the backgorund + color of address areas. You can also read the color (addressaAreaColor()). + */ + Q_PROPERTY(QColor addressAreaColor READ addressAreaColor WRITE setAddressAreaColor) + + /*! Property addressOffset is added to the Numbers of the Address Area. + A offset in the address area (left side) is sometimes usefull, whe you show + only a segment of a complete memory picture. With setAddressOffset() you set + this property - with addressOffset() you get the current value. + */ + Q_PROPERTY(qint64 addressOffset READ addressOffset WRITE setAddressOffset) + + /*! Set and get the minimum width of the address area, width in characters. + */ + Q_PROPERTY(int addressWidth READ addressWidth WRITE setAddressWidth) + + /*! Switch the ascii area on (true, show it) or off (false, hide it). + */ + Q_PROPERTY(bool asciiArea READ asciiArea WRITE setAsciiArea) + + /*! Set and get bytes number per line.*/ + Q_PROPERTY(int bytesPerLine READ bytesPerLine WRITE setBytesPerLine) + + /*! Porperty cursorPosition sets or gets the position of the editor cursor + in QHexEdit. Every byte in data has to cursor positions: the lower and upper + Nibble. Maximum cursor position is factor two of data.size(). + */ + Q_PROPERTY(qint64 cursorPosition READ cursorPosition WRITE setCursorPosition) + + /*! Property data holds the content of QHexEdit. Call setData() to set the + content of QHexEdit, data() returns the actual content. When calling setData() + with a QByteArray as argument, QHexEdit creates a internal copy of the data + If you want to edit big files please use setData(), based on QIODevice. + */ + Q_PROPERTY(QByteArray data READ data WRITE setData NOTIFY dataChanged) + + /*! That property defines if the hex values looks as a-f if the value is false(default) + or A-F if value is true. + */ + Q_PROPERTY(bool hexCaps READ hexCaps WRITE setHexCaps) + + /*! Property defines the dynamic calculation of bytesPerLine parameter depends of width of widget. + set this property true to avoid horizontal scrollbars and show the maximal possible data. defalut value is false*/ + Q_PROPERTY(bool dynamicBytesPerLine READ dynamicBytesPerLine WRITE setDynamicBytesPerLine) + + /*! Switch the highlighting feature on or of: true (show it), false (hide it). + */ + Q_PROPERTY(bool highlighting READ highlighting WRITE setHighlighting) + + /*! Property highlighting color sets (setHighlightingColor()) the backgorund + color of highlighted text areas. You can also read the color + (highlightingColor()). + */ + Q_PROPERTY(QColor highlightingColor READ highlightingColor WRITE setHighlightingColor) + + /*! Porperty overwrite mode sets (setOverwriteMode()) or gets (overwriteMode()) the mode + in which the editor works. In overwrite mode the user will overwrite existing data. The + size of data will be constant. In insert mode the size will grow, when inserting + new data. + */ + Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode) + + /*! Property selection color sets (setSelectionColor()) the backgorund + color of selected text areas. You can also read the color + (selectionColor()). + */ + Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor) + + /*! Porperty readOnly sets (setReadOnly()) or gets (isReadOnly) the mode + in which the editor works. In readonly mode the the user can only navigate + through the data and select data; modifying is not possible. This + property's default is false. + */ + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + + /*! Set the font of the widget. Please use fixed width fonts like Mono or Courier.*/ + Q_PROPERTY(QFont font READ font WRITE setFont) + +public: + /*! Creates an instance of QHexEdit. + \param parent Parent widget of QHexEdit. + */ + QHexEdit(QWidget *parent=0); + + // Access to data of qhexedit + + /*! Sets the data of QHexEdit. The QIODevice will be opend just before reading + and closed immediately afterwards. This is to allow other programs to rewrite + the file while editing it. + */ + bool setData(QIODevice &iODevice); + + /*! Givs back the data as a QByteArray starting at position \param pos and + delivering \param count bytes. + */ + QByteArray dataAt(qint64 pos, qint64 count=-1); + + /*! Givs back the data into a \param iODevice starting at position \param pos + and delivering \param count bytes. + */ + bool write(QIODevice &iODevice, qint64 pos=0, qint64 count=-1); + + + // Char handling + + /*! Inserts a char. + \param pos Index position, where to insert + \param ch Char, which is to insert + The char will be inserted and size of data grows. + */ + void insert(qint64 pos, char ch); + + /*! Removes len bytes from the content. + \param pos Index position, where to remove + \param len Amount of bytes to remove + */ + void remove(qint64 pos, qint64 len=1); + + /*! Replaces a char. + \param pos Index position, where to overwrite + \param ch Char, which is to insert + The char will be overwritten and size remains constant. + */ + void replace(qint64 pos, char ch); + + + // ByteArray handling + + /*! Inserts a byte array. + \param pos Index position, where to insert + \param ba QByteArray, which is to insert + The QByteArray will be inserted and size of data grows. + */ + void insert(qint64 pos, const QByteArray &ba); + + /*! Replaces \param len bytes with a byte array \param ba + \param pos Index position, where to overwrite + \param ba QByteArray, which is inserted + \param len count of bytes to overwrite + The data is overwritten and size of data may change. + */ + void replace(qint64 pos, qint64 len, const QByteArray &ba); + + + // Utility functioins + /*! Calc cursor position from graphics position + * \param point from where the cursor position should be calculated + * \return Cursor postioin + */ + qint64 cursorPosition(QPoint point); + + /*! Ensure the cursor to be visble + */ + void ensureVisible(); + + /*! Find first occurence of ba in QHexEdit data + * \param ba Data to find + * \param from Point where the search starts + * \return pos if fond, else -1 + */ + qint64 indexOf(const QByteArray &ba, qint64 from); + + /*! Returns if any changes where done on document + * \return true when document is modified else false + */ + bool isModified(); + + /*! Find last occurence of ba in QHexEdit data + * \param ba Data to find + * \param from Point where the search starts + * \return pos if fond, else -1 + */ + qint64 lastIndexOf(const QByteArray &ba, qint64 from); + + /*! Gives back a formatted image of the selected content of QHexEdit + */ + QString selectionToReadableString(); + + /*! Set Font of QHexEdit + * \param font + */ + void setFont(const QFont &font); + + /*! Gives back a formatted image of the content of QHexEdit + */ + QString toReadableString(); + + +public slots: + /*! Redoes the last operation. If there is no operation to redo, i.e. + there is no redo step in the undo/redo history, nothing happens. + */ + void redo(); + + /*! Undoes the last operation. If there is no operation to undo, i.e. + there is no undo step in the undo/redo history, nothing happens. + */ + void undo(); + +signals: + + /*! Contains the address, where the cursor is located. */ + void currentAddressChanged(qint64 address); + + /*! Contains the size of the data to edit. */ + void currentSizeChanged(qint64 size); + + /*! The signal is emitted every time, the data is changed. */ + void dataChanged(); + + /*! The signal is emitted every time, the overwrite mode is changed. */ + void overwriteModeChanged(bool state); + + +/*! \cond docNever */ +public: + ~QHexEdit(); + + // Properties + bool addressArea(); + void setAddressArea(bool addressArea); + + QColor addressAreaColor(); + void setAddressAreaColor(const QColor &color); + + qint64 addressOffset(); + void setAddressOffset(qint64 addressArea); + + int addressWidth(); + void setAddressWidth(int addressWidth); + + bool asciiArea(); + void setAsciiArea(bool asciiArea); + + int bytesPerLine(); + void setBytesPerLine(int count); + + qint64 cursorPosition(); + void setCursorPosition(qint64 position); + + QByteArray data(); + void setData(const QByteArray &ba); + + void setHexCaps(const bool isCaps); + bool hexCaps(); + + void setDynamicBytesPerLine(const bool isDynamic); + bool dynamicBytesPerLine(); + + bool highlighting(); + void setHighlighting(bool mode); + + QColor highlightingColor(); + void setHighlightingColor(const QColor &color); + + bool overwriteMode(); + void setOverwriteMode(bool overwriteMode); + + bool isReadOnly(); + void setReadOnly(bool readOnly); + + QColor selectionColor(); + void setSelectionColor(const QColor &color); + +protected: + // Handle events + void keyPressEvent(QKeyEvent *event); + void mouseMoveEvent(QMouseEvent * event); + void mousePressEvent(QMouseEvent * event); + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent *); + virtual bool focusNextPrevChild(bool next); +private: + // Handle selections + void resetSelection(qint64 pos); // set selectionStart and selectionEnd to pos + void resetSelection(); // set selectionEnd to selectionStart + void setSelection(qint64 pos); // set min (if below init) or max (if greater init) + int getSelectionBegin(); + int getSelectionEnd(); + + // Private utility functions + void init(); + void readBuffers(); + QString toReadable(const QByteArray &ba); + +private slots: + void adjust(); // recalc pixel positions + void dataChangedPrivate(int idx=0); // emit dataChanged() signal + void refresh(); // ensureVisible() and readBuffers() + void updateCursor(); // update blinking cursor + +private: + // Name convention: pixel positions start with _px + int _pxCharWidth, _pxCharHeight; // char dimensions (dpendend on font) + int _pxPosHexX; // X-Pos of HeaxArea + int _pxPosAdrX; // X-Pos of Address Area + int _pxPosAsciiX; // X-Pos of Ascii Area + int _pxGapAdr; // gap left from AddressArea + int _pxGapAdrHex; // gap between AddressArea and HexAerea + int _pxGapHexAscii; // gap between HexArea and AsciiArea + int _pxCursorWidth; // cursor width + int _pxSelectionSub; // offset selection rect + int _pxCursorX; // current cursor pos + int _pxCursorY; // current cursor pos + + // Name convention: absolute byte positions in chunks start with _b + qint64 _bSelectionBegin; // first position of Selection + qint64 _bSelectionEnd; // end of Selection + qint64 _bSelectionInit; // memory position of Selection + qint64 _bPosFirst; // position of first byte shown + qint64 _bPosLast; // position of last byte shown + qint64 _bPosCurrent; // current position + + // variables to store the property values + bool _addressArea; // left area of QHexEdit + QColor _addressAreaColor; + int _addressWidth; + bool _asciiArea; + qint64 _addressOffset; + int _bytesPerLine; + int _hexCharsInLine; + bool _highlighting; + bool _overwriteMode; + QBrush _brushSelection; + QPen _penSelection; + QBrush _brushHighlighted; + QPen _penHighlighted; + bool _readOnly; + bool _hexCaps; + bool _dynamicBytesPerLine; + + // other variables + bool _editAreaIsAscii; // flag about the ascii mode edited + int _addrDigits; // real no of addressdigits, may be > addressWidth + bool _blink; // help get cursor blinking + QBuffer _bData; // buffer, when setup with QByteArray + Chunks *_chunks; // IODevice based access to data + QTimer _cursorTimer; // for blinking cursor + qint64 _cursorPosition; // absolute positioin of cursor, 1 Byte == 2 tics + QRect _cursorRect; // physical dimensions of cursor + QByteArray _data; // QHexEdit's data, when setup with QByteArray + QByteArray _dataShown; // data in the current View + QByteArray _hexDataShown; // data in view, transformed to hex + qint64 _lastEventSize; // size, which was emitted last time + QByteArray _markedShown; // marked data in view + bool _modified; // Is any data in editor modified? + int _rowsShown; // lines of text shown + UndoStack * _undoStack; // Stack to store edit actions for undo/redo + /*! \endcond docNever */ +}; + +#endif // QHEXEDIT_H