1233 lines
38 KiB
C++
1233 lines
38 KiB
C++
/*==============================================================================
|
|
** Copyright (C) 2024-2027 WingSummer
|
|
**
|
|
** This program is free software: you can redistribute it and/or modify it under
|
|
** the terms of the GNU Affero General Public License as published by the Free
|
|
** Software Foundation, version 3.
|
|
**
|
|
** This program is distributed in the hope that it will be useful, but WITHOUT
|
|
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
** details.
|
|
**
|
|
** You should have received a copy of the GNU Affero General Public License
|
|
** along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
**
|
|
** The original License is MIT from Dax89/QHexView. I have modified a lot so I
|
|
** decide to change the Open Source License. You can use the original library
|
|
** under MIT. Thanks for Dax89's efforts.
|
|
** =============================================================================
|
|
*/
|
|
|
|
#include "qhexview.h"
|
|
#include "document/buffer/qmemorybuffer.h"
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QFontDatabase>
|
|
#include <QHelpEvent>
|
|
#include <QMimeData>
|
|
#include <QPaintEvent>
|
|
#include <QPainter>
|
|
#include <QScreen>
|
|
#include <QScrollBar>
|
|
#include <QTextCursor>
|
|
#include <QToolTip>
|
|
|
|
#include <cmath>
|
|
|
|
#define CURSOR_BLINK_INTERVAL 500 // ms
|
|
#define DOCUMENT_WHEEL_LINES 3
|
|
|
|
/*======================*/
|
|
// added by wingsummer
|
|
|
|
bool QHexView::isReadOnly() { return m_document->isReadOnly(); }
|
|
bool QHexView::isKeepSize() { return m_document->isKeepSize(); }
|
|
bool QHexView::isLocked() { return m_document->isLocked(); }
|
|
bool QHexView::setLockedFile(bool b) {
|
|
auto ret = m_document->setLockedFile(b);
|
|
return ret;
|
|
}
|
|
bool QHexView::setKeepSize(bool b) {
|
|
auto ret = m_document->setKeepSize(b);
|
|
if (b) {
|
|
m_cursor->setInsertionMode(QHexCursor::OverwriteMode);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void QHexView::setLockKeepSize(bool b) { m_document->setLockKeepSize(b); }
|
|
|
|
bool QHexView::lockKeepSize() const { return m_document->lockKeepSize(); }
|
|
|
|
qsizetype QHexView::documentLines() { return m_renderer->documentLines(); }
|
|
qsizetype QHexView::documentBytes() { return m_document->length(); }
|
|
qsizetype QHexView::currentRow() { return m_cursor->currentLine(); }
|
|
qsizetype QHexView::currentColumn() { return m_cursor->currentColumn(); }
|
|
qsizetype QHexView::currentOffset() { return m_cursor->position().offset(); }
|
|
|
|
qsizetype QHexView::currentSelectionLength() {
|
|
return m_cursor->currentSelectionLength();
|
|
}
|
|
|
|
qsizetype QHexView::selectionCount() { return m_cursor->selectionCount(); }
|
|
|
|
bool QHexView::hasSelection() { return m_cursor->hasSelection(); }
|
|
|
|
bool QHexView::asciiVisible() { return m_renderer->stringVisible(); }
|
|
|
|
bool QHexView::headerVisible() { return m_renderer->headerVisible(); }
|
|
|
|
bool QHexView::addressVisible() { return m_renderer->addressVisible(); }
|
|
|
|
void QHexView::setAsciiVisible(bool b) {
|
|
m_renderer->setStringVisible(b);
|
|
this->adjustScrollBars();
|
|
this->viewport()->update();
|
|
}
|
|
|
|
void QHexView::setAddressVisible(bool b) {
|
|
m_renderer->setAddressVisible(b);
|
|
this->adjustScrollBars();
|
|
this->viewport()->update();
|
|
}
|
|
|
|
void QHexView::setHeaderVisible(bool b) {
|
|
m_renderer->setHeaderVisible(b);
|
|
this->adjustScrollBars();
|
|
this->viewport()->update();
|
|
}
|
|
|
|
quintptr QHexView::addressBase() { return m_document->baseAddress(); }
|
|
|
|
void QHexView::setAddressBase(quintptr base) {
|
|
m_document->SetBaseAddress(base);
|
|
}
|
|
|
|
bool QHexView::isSaved() { return m_document->isDocSaved(); }
|
|
|
|
QFont QHexView::getHexeditorFont() {
|
|
QFont f = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
|
if (f.styleHint() != QFont::TypeWriter) {
|
|
f.setFamily("Monospace"); // Force Monospaced font
|
|
f.setStyleHint(QFont::TypeWriter);
|
|
}
|
|
return f;
|
|
}
|
|
|
|
void QHexView::getStatus() {
|
|
emit canUndoChanged(m_document->canUndo());
|
|
emit canRedoChanged(m_document->canRedo());
|
|
emit cursorLocationChanged();
|
|
emit documentSaved(m_document->isDocSaved());
|
|
emit documentKeepSize(m_document->isKeepSize());
|
|
emit documentLockedFile(m_document->isLocked());
|
|
emit metafgVisibleChanged(m_document->metafgVisible());
|
|
emit metabgVisibleChanged(m_document->metabgVisible());
|
|
emit metaCommentVisibleChanged(m_document->metaCommentVisible());
|
|
}
|
|
|
|
void QHexView::establishSignal(QHexDocument *doc) {
|
|
connect(doc, &QHexDocument::documentChanged, this, [this]() {
|
|
this->adjustScrollBars();
|
|
this->viewport()->update();
|
|
});
|
|
|
|
connect(doc, &QHexDocument::canUndoChanged, this,
|
|
&QHexView::canUndoChanged);
|
|
connect(doc, &QHexDocument::canRedoChanged, this,
|
|
&QHexView::canRedoChanged);
|
|
connect(doc, &QHexDocument::documentSaved, this, &QHexView::documentSaved);
|
|
connect(doc, &QHexDocument::metabgVisibleChanged, this, [=](bool b) {
|
|
emit metabgVisibleChanged(b);
|
|
emit metaStatusChanged();
|
|
});
|
|
connect(doc, &QHexDocument::metafgVisibleChanged, this, [=](bool b) {
|
|
emit metafgVisibleChanged(b);
|
|
emit metaStatusChanged();
|
|
});
|
|
connect(doc, &QHexDocument::metaCommentVisibleChanged, this, [=](bool b) {
|
|
emit metaCommentVisibleChanged(b);
|
|
emit metaStatusChanged();
|
|
});
|
|
connect(doc, &QHexDocument::metaDataChanged, this,
|
|
[=] { this->viewport()->update(); });
|
|
connect(doc, &QHexDocument::documentKeepSize, this,
|
|
&QHexView::documentKeepSize);
|
|
connect(doc, &QHexDocument::documentLockedFile, this,
|
|
&QHexView::documentLockedFile);
|
|
connect(doc, &QHexDocument::hexLineWidthChanged, this,
|
|
[=] { m_cursor->setLineWidth(doc->hexLineWidth()); });
|
|
}
|
|
|
|
/*======================*/
|
|
|
|
QHexView::QHexView(QWidget *parent)
|
|
: QAbstractScrollArea(parent), m_document(nullptr), m_renderer(nullptr) {
|
|
QFont f = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
|
|
|
if (f.styleHint() != QFont::TypeWriter) {
|
|
f.setFamily("Monospace"); // Force Monospaced font
|
|
f.setStyleHint(QFont::TypeWriter);
|
|
}
|
|
|
|
m_fontSize = f.pointSizeF();
|
|
m_cursor = new QHexCursor(this);
|
|
|
|
this->setFont(f);
|
|
|
|
this->setFocusPolicy(Qt::StrongFocus);
|
|
this->setMouseTracking(true);
|
|
this->verticalScrollBar()->setSingleStep(1);
|
|
this->verticalScrollBar()->setPageStep(1);
|
|
|
|
m_blinktimer = new QTimer(this);
|
|
m_blinktimer->setInterval(CURSOR_BLINK_INTERVAL);
|
|
|
|
connect(m_blinktimer, &QTimer::timeout, this, &QHexView::blinkCursor);
|
|
|
|
this->setDocument(QSharedPointer<QHexDocument>(
|
|
QHexDocument::fromMemory<QMemoryBuffer>(QByteArray(), false)));
|
|
}
|
|
|
|
QHexView::~QHexView() { m_blinktimer->stop(); }
|
|
|
|
QSharedPointer<QHexDocument> QHexView::document() { return m_document; }
|
|
|
|
QHexRenderer *QHexView::renderer() { return m_renderer; }
|
|
|
|
// modified by wingsummer
|
|
void QHexView::setDocument(const QSharedPointer<QHexDocument> &document,
|
|
QHexCursor *cursor) {
|
|
// modified by wingsummer
|
|
if (!document) {
|
|
return;
|
|
}
|
|
|
|
if (m_document) {
|
|
m_document->disconnect(this);
|
|
m_document.clear();
|
|
}
|
|
|
|
m_document = document;
|
|
|
|
if (m_renderer) {
|
|
m_renderer->switchDoc(m_document.get());
|
|
if (cursor) {
|
|
m_cursor->disconnect();
|
|
m_renderer->setCursor(cursor);
|
|
m_cursor = cursor;
|
|
}
|
|
} else {
|
|
if (cursor) {
|
|
m_cursor->disconnect();
|
|
m_cursor = cursor;
|
|
}
|
|
m_renderer = new QHexRenderer(m_document.data(), m_cursor,
|
|
this->fontMetrics(), this);
|
|
}
|
|
|
|
establishSignal(m_document.data());
|
|
|
|
connect(m_cursor, &QHexCursor::positionChanged, this,
|
|
&QHexView::moveToSelection);
|
|
connect(m_cursor, &QHexCursor::selectionChanged, this, [this]() {
|
|
this->viewport()->update();
|
|
emit cursorSelectionChanged();
|
|
});
|
|
connect(m_cursor, &QHexCursor::insertionModeChanged, this,
|
|
&QHexView::renderCurrentLine);
|
|
|
|
emit documentChanged(document.data());
|
|
|
|
this->adjustScrollBars();
|
|
this->viewport()->update();
|
|
}
|
|
|
|
bool QHexView::event(QEvent *e) {
|
|
if (m_renderer && (e->type() == QEvent::FontChange)) {
|
|
m_renderer->updateMetrics(QFontMetricsF(this->font()));
|
|
return true;
|
|
}
|
|
|
|
if (m_document && m_renderer && (e->type() == QEvent::ToolTip)) {
|
|
QHelpEvent *helpevent = static_cast<QHelpEvent *>(e);
|
|
QHexPosition position;
|
|
|
|
QPoint abspos = absolutePosition(helpevent->pos());
|
|
if (m_renderer->hitTest(abspos, &position, this->firstVisibleLine())) {
|
|
QString comments;
|
|
|
|
auto mi = m_document->metadata()->get(position.offset());
|
|
if (mi.has_value()) {
|
|
QTextStream ss(&comments);
|
|
|
|
if (mi->foreground.isValid() && mi->foreground.alpha()) {
|
|
auto color = mi->foreground.name();
|
|
auto bcolor =
|
|
QHexMetadata::generateContrastingColor(mi->foreground)
|
|
.name();
|
|
ss << QStringLiteral("<p>") << tr("Foreground:")
|
|
<< QStringLiteral("<b><a style=\"color:") << color
|
|
<< QStringLiteral(";background-color:") << bcolor
|
|
<< QStringLiteral("\">") << color
|
|
<< QStringLiteral("</a></b></p>");
|
|
}
|
|
if (mi->background.isValid() && mi->background.alpha()) {
|
|
auto color = mi->background.name();
|
|
auto bcolor =
|
|
QHexMetadata::generateContrastingColor(mi->background)
|
|
.name();
|
|
ss << QStringLiteral("<p>") << tr("Background:")
|
|
<< QStringLiteral("<b><a style=\"color:") << color
|
|
<< QStringLiteral(";background-color:") << bcolor
|
|
<< QStringLiteral("\">") << color
|
|
<< QStringLiteral("</a></b></p>");
|
|
}
|
|
if (!mi->comment.isEmpty()) {
|
|
ss << tr("Comment:") << mi->comment;
|
|
}
|
|
}
|
|
|
|
if (!comments.isEmpty())
|
|
QToolTip::showText(helpevent->globalPos(), comments, this);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return QAbstractScrollArea::event(e);
|
|
}
|
|
|
|
void QHexView::keyPressEvent(QKeyEvent *e) {
|
|
if (!m_renderer || !m_document) {
|
|
QAbstractScrollArea::keyPressEvent(e);
|
|
return;
|
|
}
|
|
|
|
QHexCursor *cur = m_cursor;
|
|
|
|
m_blinktimer->stop();
|
|
m_renderer->enableCursor();
|
|
|
|
bool handled = this->processMove(cur, e);
|
|
if (!handled)
|
|
handled = this->processAction(cur, e);
|
|
if (!handled)
|
|
handled = this->processTextInput(cur, e);
|
|
if (!handled)
|
|
QAbstractScrollArea::keyPressEvent(e);
|
|
|
|
m_blinktimer->start();
|
|
}
|
|
|
|
QPoint QHexView::absolutePosition(const QPoint &pos) const {
|
|
QPoint shift(horizontalScrollBar()->value(), 0);
|
|
return pos + shift;
|
|
}
|
|
|
|
QHexCursor *QHexView::cursor() const { return m_cursor; }
|
|
|
|
qsizetype QHexView::copyLimit() const { return m_copylimit; }
|
|
|
|
void QHexView::setCopyLimit(qsizetype newCopylimit) {
|
|
m_copylimit = newCopylimit;
|
|
}
|
|
|
|
qreal QHexView::scaleRate() const { return m_scaleRate; }
|
|
|
|
qsizetype QHexView::findNext(qsizetype begin, const QByteArray &ba) {
|
|
if (begin < 0) {
|
|
begin = m_cursor->position().offset();
|
|
}
|
|
return m_document->findNext(begin, ba);
|
|
}
|
|
|
|
qsizetype QHexView::findPrevious(qsizetype begin, const QByteArray &ba) {
|
|
qsizetype startPos;
|
|
if (begin < 0) {
|
|
startPos = m_cursor->position().offset() - 1;
|
|
} else {
|
|
startPos = begin;
|
|
}
|
|
return m_document->findPrevious(startPos, ba);
|
|
}
|
|
|
|
bool QHexView::RemoveSelection(int nibbleindex) {
|
|
if (!m_cursor->hasSelection())
|
|
return true;
|
|
|
|
auto total = m_cursor->selectionCount();
|
|
m_document->beginMarco(QStringLiteral("RemoveSelection"));
|
|
for (int i = 0; i < total; ++i) {
|
|
auto res =
|
|
m_document->Remove(m_cursor, m_cursor->selectionStart(i).offset(),
|
|
m_cursor->selectionLength(i), nibbleindex);
|
|
if (!res) {
|
|
break;
|
|
}
|
|
}
|
|
m_document->endMarco();
|
|
m_cursor->clearSelection();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QHexView::removeSelection() {
|
|
if (!m_cursor->hasSelection())
|
|
return true;
|
|
|
|
// We essure selections are ordered by desending
|
|
// by selection-start, so it's safe to remove
|
|
auto total = m_cursor->selectionCount();
|
|
bool res = true;
|
|
for (int i = 0; i < total; ++i) {
|
|
res &= m_document->remove(m_cursor->selectionStart(i).offset(),
|
|
m_cursor->selectionLength(i));
|
|
}
|
|
|
|
m_cursor->clearSelection();
|
|
return res;
|
|
}
|
|
|
|
bool QHexView::atEnd() const {
|
|
return m_cursor->position().offset() >= m_document->length();
|
|
}
|
|
|
|
QByteArray QHexView::selectedBytes(qsizetype index) const {
|
|
return m_document->read(m_cursor->selectionStart(index).offset(),
|
|
m_cursor->currentSelectionLength());
|
|
}
|
|
|
|
QByteArray QHexView::previewSelectedBytes() const {
|
|
auto sel = m_cursor->previewSelection();
|
|
return m_document->read(sel.begin.offset(), sel.length());
|
|
}
|
|
|
|
QByteArrayList QHexView::selectedBytes() const {
|
|
if (!m_cursor->hasSelection())
|
|
return {};
|
|
|
|
QByteArrayList res;
|
|
auto total = m_cursor->selectionCount();
|
|
for (int i = 0; i < total; ++i) {
|
|
res << m_document->read(m_cursor->selectionStart(i).offset(),
|
|
m_cursor->selectionLength(i));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool QHexView::paste(bool hex) {
|
|
QClipboard *c = qApp->clipboard();
|
|
|
|
QByteArray data;
|
|
if (hex) {
|
|
data = QByteArray::fromHex(c->text().toUtf8());
|
|
} else {
|
|
auto d = c->mimeData();
|
|
data = d->data(QStringLiteral("application/octet-stream"));
|
|
if (data.isEmpty()) {
|
|
data = d->text().toUtf8();
|
|
}
|
|
}
|
|
|
|
if (data.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
auto ret = this->removeSelection();
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
auto pos = m_cursor->position().offset();
|
|
if (!m_document->isKeepSize()) {
|
|
bool ret = m_document->insert(pos, data);
|
|
m_cursor->moveTo(pos + data.length()); // added by wingsummer
|
|
return ret;
|
|
} else {
|
|
return m_document->replace(pos, data);
|
|
}
|
|
}
|
|
|
|
bool QHexView::Cut(bool hex, int nibbleindex) {
|
|
if (!m_cursor->hasSelection())
|
|
return true;
|
|
|
|
if (m_document->isKeepSize())
|
|
return false;
|
|
|
|
auto res = this->copy(hex);
|
|
if (res) {
|
|
return this->RemoveSelection(nibbleindex);
|
|
} else {
|
|
emit copyLimitRaised();
|
|
return res;
|
|
}
|
|
}
|
|
|
|
bool QHexView::Paste(bool hex, int nibbleindex) {
|
|
QClipboard *c = qApp->clipboard();
|
|
|
|
QByteArray data;
|
|
if (hex) {
|
|
data = QByteArray::fromHex(c->text().toUtf8());
|
|
} else {
|
|
auto d = c->mimeData();
|
|
data = d->data(QStringLiteral("application/octet-stream"));
|
|
if (data.isEmpty()) {
|
|
data = d->text().toUtf8();
|
|
}
|
|
}
|
|
|
|
if (data.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
auto ret = this->RemoveSelection(nibbleindex);
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
auto pos = m_cursor->position().offset();
|
|
if (m_cursor->insertionMode() == QHexCursor::InsertionMode::InsertMode) {
|
|
auto ret = m_document->Insert(m_cursor, pos, data, nibbleindex);
|
|
m_cursor->moveTo(pos + data.length()); // added by wingsummer
|
|
return ret;
|
|
} else {
|
|
return m_document->Replace(m_cursor, pos, data, nibbleindex);
|
|
}
|
|
}
|
|
|
|
void QHexView::Replace(qsizetype offset, uchar b, int nibbleindex) {
|
|
m_document->Replace(m_cursor, offset, b, nibbleindex);
|
|
}
|
|
|
|
void QHexView::Replace(qsizetype offset, const QByteArray &data,
|
|
int nibbleindex) {
|
|
m_document->Replace(m_cursor, offset, data, nibbleindex);
|
|
}
|
|
|
|
bool QHexView::cut(bool hex) {
|
|
if (!m_cursor->hasSelection() || m_document->isKeepSize())
|
|
return false;
|
|
|
|
auto res = this->copy(hex);
|
|
if (res) {
|
|
return this->removeSelection();
|
|
} else {
|
|
emit copyLimitRaised();
|
|
return res;
|
|
}
|
|
}
|
|
|
|
bool QHexView::copy(bool hex) {
|
|
if (!m_cursor->hasSelection())
|
|
return true;
|
|
|
|
QClipboard *c = qApp->clipboard();
|
|
|
|
auto len = currentSelectionLength();
|
|
|
|
// 如果拷贝字节超过 ? MB 阻止
|
|
if (len > 1024 * 1024 * m_copylimit) {
|
|
emit copyLimitRaised();
|
|
return false;
|
|
}
|
|
|
|
QByteArray bytes = this->selectedBytes().join();
|
|
|
|
if (hex) {
|
|
bytes = bytes.toHex(' ').toUpper();
|
|
c->setText(bytes);
|
|
} else {
|
|
auto mime = new QMimeData;
|
|
mime->setData(QStringLiteral("application/octet-stream"),
|
|
bytes); // don't use setText()
|
|
c->setMimeData(mime);
|
|
}
|
|
|
|
// fix the bug by wingsummer
|
|
return true;
|
|
}
|
|
|
|
qreal QHexView::fontSize() const { return m_fontSize; }
|
|
|
|
void QHexView::setScaleRate(qreal rate) {
|
|
if (rate >= 0.2 && rate < 2.01) {
|
|
m_scaleRate = rate;
|
|
setFontSize(fontSize());
|
|
emit scaleRateChanged();
|
|
update();
|
|
}
|
|
}
|
|
|
|
QColor QHexView::selBackgroundColor() const {
|
|
return m_renderer->selBackgroundColor();
|
|
}
|
|
|
|
void QHexView::setSelBackgroundColor(const QColor &newSelBackgroundColor) {
|
|
m_renderer->setSelBackgroundColor(newSelBackgroundColor);
|
|
emit selBackgroundColorChanged();
|
|
}
|
|
|
|
void QHexView::setFontSize(qreal size) {
|
|
Q_ASSERT(size > 0);
|
|
auto font = this->font();
|
|
font.setPointSizeF(size * m_scaleRate);
|
|
this->setFont(font);
|
|
m_fontSize = size;
|
|
}
|
|
|
|
QColor QHexView::selectionColor() const { return m_renderer->selectionColor(); }
|
|
|
|
void QHexView::setSelectionColor(const QColor &newSelectionColor) {
|
|
m_renderer->setSelectionColor(newSelectionColor);
|
|
emit selectionColorChanged();
|
|
}
|
|
|
|
QColor QHexView::bytesAlterBackground() const {
|
|
return m_renderer->bytesAlterBackground();
|
|
}
|
|
|
|
void QHexView::setBytesAlterBackground(const QColor &newBytesAlterBackground) {
|
|
m_renderer->setBytesAlterBackground(newBytesAlterBackground);
|
|
emit bytesAlterBackgroundChanged();
|
|
}
|
|
|
|
QColor QHexView::bytesColor() const { return m_renderer->bytesColor(); }
|
|
|
|
void QHexView::setBytesColor(const QColor &newBytesColor) {
|
|
m_renderer->setBytesColor(newBytesColor);
|
|
emit bytesColorChanged();
|
|
}
|
|
|
|
QColor QHexView::bytesBackground() const {
|
|
return m_renderer->bytesBackground();
|
|
}
|
|
|
|
void QHexView::setBytesBackground(const QColor &newBytesBackground) {
|
|
m_renderer->setBytesBackground(newBytesBackground);
|
|
emit bytesBackgroundChanged();
|
|
}
|
|
|
|
QColor QHexView::addressColor() const { return m_renderer->addressColor(); }
|
|
|
|
void QHexView::setAddressColor(const QColor &newAddressColor) {
|
|
m_renderer->setAddressColor(newAddressColor);
|
|
emit addressColorChanged();
|
|
}
|
|
|
|
QColor QHexView::headerColor() const { return m_renderer->headerColor(); }
|
|
|
|
void QHexView::setHeaderColor(const QColor &newHeaderColor) {
|
|
m_renderer->setHeaderColor(newHeaderColor);
|
|
emit headerColorChanged();
|
|
}
|
|
|
|
void QHexView::mousePressEvent(QMouseEvent *e) {
|
|
QAbstractScrollArea::mousePressEvent(e);
|
|
|
|
if (!m_renderer || (e->buttons() != Qt::LeftButton))
|
|
return;
|
|
|
|
QHexPosition position;
|
|
QPoint abspos = absolutePosition(e->pos());
|
|
|
|
if (!m_renderer->hitTest(abspos, &position, this->firstVisibleLine()))
|
|
return;
|
|
|
|
m_renderer->selectArea(abspos);
|
|
|
|
if (m_renderer->editableArea(m_renderer->selectedArea())) {
|
|
auto m = getSelectionMode();
|
|
bool clearSelection = false;
|
|
if (m == QHexCursor::SelectionNormal) {
|
|
clearSelection = m_cursor->selectionCount() <= 1 ||
|
|
e->modifiers().testFlag(Qt::ControlModifier);
|
|
} else if (m == QHexCursor::SelectionSingle) {
|
|
clearSelection = true;
|
|
}
|
|
|
|
m_cursor->moveTo(position, clearSelection);
|
|
}
|
|
|
|
e->accept();
|
|
}
|
|
|
|
void QHexView::mouseMoveEvent(QMouseEvent *e) {
|
|
QAbstractScrollArea::mouseMoveEvent(e);
|
|
if (!m_renderer || !m_document)
|
|
return;
|
|
|
|
QPoint abspos = absolutePosition(e->pos());
|
|
|
|
if (e->buttons() == Qt::LeftButton) {
|
|
if (m_blinktimer->isActive()) {
|
|
m_blinktimer->stop();
|
|
m_renderer->enableCursor(false);
|
|
}
|
|
|
|
QHexCursor *cursor = m_cursor;
|
|
QHexPosition position;
|
|
|
|
if (!m_renderer->hitTest(abspos, &position, this->firstVisibleLine()))
|
|
return;
|
|
|
|
cursor->select(position.line, position.column,
|
|
QHexCursor::SelectionModes(
|
|
getSelectionMode() | QHexCursor::SelectionPreview));
|
|
e->accept();
|
|
}
|
|
|
|
if (e->buttons() != Qt::NoButton)
|
|
return;
|
|
|
|
int hittest = m_renderer->hitTestArea(abspos);
|
|
|
|
if (m_renderer->editableArea(hittest))
|
|
this->setCursor(Qt::IBeamCursor);
|
|
else
|
|
this->setCursor(Qt::ArrowCursor);
|
|
}
|
|
|
|
void QHexView::mouseReleaseEvent(QMouseEvent *e) {
|
|
QAbstractScrollArea::mouseReleaseEvent(e);
|
|
if (e->button() != Qt::LeftButton)
|
|
return;
|
|
if (!m_blinktimer->isActive())
|
|
m_blinktimer->start();
|
|
|
|
if (m_cursor->hasPreviewSelection()) {
|
|
auto sel = m_cursor->previewSelection();
|
|
m_cursor->mergePreviewSelection();
|
|
m_cursor->moveTo(sel.end);
|
|
}
|
|
|
|
e->accept();
|
|
}
|
|
|
|
void QHexView::focusInEvent(QFocusEvent *e) {
|
|
QAbstractScrollArea::focusInEvent(e);
|
|
if (!m_renderer)
|
|
return;
|
|
|
|
m_renderer->enableCursor();
|
|
m_blinktimer->start();
|
|
}
|
|
|
|
void QHexView::focusOutEvent(QFocusEvent *e) {
|
|
QAbstractScrollArea::focusOutEvent(e);
|
|
if (!m_renderer)
|
|
return;
|
|
|
|
m_blinktimer->stop();
|
|
m_renderer->enableCursor(true); // modified by wingsummer : false -> true
|
|
}
|
|
|
|
void QHexView::wheelEvent(QWheelEvent *e) {
|
|
if (qApp->keyboardModifiers() == Qt::ControlModifier) {
|
|
auto dela = e->angleDelta().y() / 1200.0;
|
|
setScaleRate(scaleRate() + dela);
|
|
return;
|
|
}
|
|
|
|
if (e->angleDelta().y() == 0) {
|
|
int value = this->verticalScrollBar()->value();
|
|
|
|
if (e->angleDelta().x() < 0) // Scroll Down
|
|
this->verticalScrollBar()->setValue(value + DOCUMENT_WHEEL_LINES);
|
|
else if (e->angleDelta().x() > 0) // Scroll Up
|
|
this->verticalScrollBar()->setValue(value - DOCUMENT_WHEEL_LINES);
|
|
|
|
return;
|
|
}
|
|
|
|
QAbstractScrollArea::wheelEvent(e);
|
|
}
|
|
|
|
void QHexView::resizeEvent(QResizeEvent *e) {
|
|
QAbstractScrollArea::resizeEvent(e);
|
|
this->adjustScrollBars();
|
|
}
|
|
|
|
void QHexView::paintEvent(QPaintEvent *e) {
|
|
if (!m_document)
|
|
return;
|
|
|
|
QPainter painter(this->viewport());
|
|
painter.setFont(this->font());
|
|
|
|
const QRect &r = e->rect();
|
|
|
|
const qsizetype firstVisible = this->firstVisibleLine();
|
|
const int lineHeight = m_renderer->lineHeight();
|
|
const int headerCount = m_renderer->headerLineCount();
|
|
|
|
// these are lines from the point of view of the visible rect
|
|
// where the first "headerCount" are taken by the header
|
|
const int first = (r.top() / lineHeight); // included
|
|
const int lastPlusOne = (r.bottom() / lineHeight) + 1; // excluded
|
|
|
|
// compute document lines, adding firstVisible and removing the header
|
|
// the max is necessary if the rect covers the header
|
|
const qsizetype begin = firstVisible + std::max(first - headerCount, 0);
|
|
const qsizetype end = firstVisible + std::max(lastPlusOne - headerCount, 0);
|
|
|
|
painter.save();
|
|
painter.translate(-this->horizontalScrollBar()->value(), 0);
|
|
m_renderer->render(&painter, begin, end, firstVisible);
|
|
m_renderer->renderFrame(&painter);
|
|
painter.restore();
|
|
}
|
|
|
|
void QHexView::moveToSelection() {
|
|
QHexCursor *cur = m_cursor;
|
|
|
|
if (!this->isLineVisible(cur->currentLine())) {
|
|
QScrollBar *vscrollbar = this->verticalScrollBar();
|
|
int scrollPos = static_cast<int>(
|
|
std::max(qsizetype(0),
|
|
cur->currentLine() - this->visibleLines() / 2) /
|
|
documentSizeFactor());
|
|
vscrollbar->setValue(scrollPos);
|
|
} else
|
|
this->viewport()->update();
|
|
|
|
emit cursorLocationChanged(); // added by wingsummer
|
|
}
|
|
|
|
void QHexView::blinkCursor() {
|
|
if (!m_renderer)
|
|
return;
|
|
m_renderer->blinkCursor();
|
|
this->renderCurrentLine();
|
|
}
|
|
|
|
void QHexView::moveNext(bool select) {
|
|
QHexCursor *cur = m_cursor;
|
|
auto line = cur->currentLine();
|
|
auto column = cur->currentColumn();
|
|
bool lastcell = (line >= m_renderer->documentLastLine()) &&
|
|
(column >= m_renderer->documentLastColumn());
|
|
|
|
if ((m_renderer->selectedArea() == QHexRenderer::AsciiArea) && lastcell)
|
|
return;
|
|
|
|
int nibbleindex = cur->currentNibble();
|
|
|
|
if (m_renderer->selectedArea() == QHexRenderer::HexArea) {
|
|
if (lastcell && !nibbleindex)
|
|
return;
|
|
|
|
if (cur->position().offset() >= m_document->length() && nibbleindex)
|
|
return;
|
|
}
|
|
|
|
if ((m_renderer->selectedArea() == QHexRenderer::HexArea)) {
|
|
nibbleindex++;
|
|
nibbleindex %= 2;
|
|
|
|
if (nibbleindex)
|
|
column++;
|
|
} else {
|
|
nibbleindex = 1;
|
|
column++;
|
|
}
|
|
|
|
if (column > m_renderer->hexLineWidth() - 1) {
|
|
line = std::min(m_renderer->documentLastLine(), line + 1);
|
|
column = 0;
|
|
nibbleindex = 1;
|
|
}
|
|
|
|
if (select)
|
|
cur->select(line, std::min(m_renderer->hexLineWidth() - 1, int(column)),
|
|
QHexCursor::SelectionAdd);
|
|
|
|
cur->moveTo(line, std::min(m_renderer->hexLineWidth() - 1, int(column)),
|
|
nibbleindex);
|
|
}
|
|
|
|
void QHexView::movePrevious(bool select) {
|
|
QHexCursor *cur = m_cursor;
|
|
auto line = cur->currentLine();
|
|
auto column = cur->currentColumn();
|
|
bool firstcell = !line && !column;
|
|
|
|
if ((m_renderer->selectedArea() == QHexRenderer::AsciiArea) && firstcell)
|
|
return;
|
|
|
|
int nibbleindex = cur->currentNibble();
|
|
|
|
if ((m_renderer->selectedArea() == QHexRenderer::HexArea) && firstcell &&
|
|
nibbleindex)
|
|
return;
|
|
|
|
if ((m_renderer->selectedArea() == QHexRenderer::HexArea)) {
|
|
nibbleindex--;
|
|
nibbleindex %= 2;
|
|
if (!nibbleindex)
|
|
column--;
|
|
} else {
|
|
nibbleindex = 1;
|
|
column--;
|
|
}
|
|
|
|
if (column < 0) {
|
|
line = std::max(qsizetype(0), line - 1);
|
|
column = m_renderer->hexLineWidth() - 1;
|
|
nibbleindex = 0;
|
|
}
|
|
|
|
if (select)
|
|
cur->select(line, std::max(0, column), QHexCursor::SelectionAdd);
|
|
|
|
cur->moveTo(line, std::max(0, column), nibbleindex);
|
|
}
|
|
|
|
QHexCursor::SelectionMode QHexView::getSelectionMode() const {
|
|
auto mods = qApp->keyboardModifiers();
|
|
|
|
bool pressedShift = mods.testFlag(Qt::ShiftModifier);
|
|
bool pressedAlt = mods.testFlag(Qt::AltModifier);
|
|
bool pressedControl = mods.testFlag(Qt::ControlModifier);
|
|
|
|
if (pressedAlt && pressedShift) {
|
|
pressedShift = false;
|
|
pressedAlt = false;
|
|
pressedControl = true;
|
|
}
|
|
|
|
if (pressedControl) {
|
|
return QHexCursor::SelectionSingle;
|
|
}
|
|
|
|
if (pressedShift) {
|
|
return QHexCursor::SelectionAdd;
|
|
}
|
|
|
|
if (pressedAlt) {
|
|
return QHexCursor::SelectionRemove;
|
|
}
|
|
|
|
return QHexCursor::SelectionNormal;
|
|
}
|
|
|
|
void QHexView::renderCurrentLine() {
|
|
if (m_document)
|
|
this->renderLine(m_cursor->currentLine());
|
|
}
|
|
|
|
bool QHexView::processAction(QHexCursor *cur, QKeyEvent *e) {
|
|
auto canFlag = !(isReadOnly() || isLocked());
|
|
|
|
if (e->modifiers() != Qt::NoModifier) {
|
|
if (e->matches(QKeySequence::SelectAll)) {
|
|
m_cursor->moveTo(0, 0);
|
|
m_cursor->select(m_renderer->documentLastLine(),
|
|
m_renderer->documentLastColumn() - 1);
|
|
} else {
|
|
if (canFlag) {
|
|
if (e->matches(QKeySequence::Undo))
|
|
m_document->undo();
|
|
else if (e->matches(QKeySequence::Redo))
|
|
m_document->redo();
|
|
else if (e->matches(QKeySequence::Cut))
|
|
this->Cut(
|
|
(m_renderer->selectedArea() == QHexRenderer::HexArea));
|
|
else if (e->matches(QKeySequence::Copy))
|
|
this->copy(
|
|
(m_renderer->selectedArea() == QHexRenderer::HexArea));
|
|
else if (e->matches(QKeySequence::Paste))
|
|
this->Paste(
|
|
(m_renderer->selectedArea() == QHexRenderer::HexArea));
|
|
else
|
|
return false;
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (canFlag &&
|
|
((e->key() == Qt::Key_Backspace) || (e->key() == Qt::Key_Delete))) {
|
|
if (!cur->hasSelection()) {
|
|
const QHexPosition &pos = cur->position();
|
|
|
|
if (pos.offset() <= 0)
|
|
return true;
|
|
|
|
// modified by wingsummer
|
|
if (isKeepSize()) {
|
|
if (cur->insertionMode() == QHexCursor::OverwriteMode) {
|
|
if (e->key() == Qt::Key_Backspace)
|
|
m_document->Replace(m_cursor,
|
|
cur->position().offset() - 1,
|
|
uchar(0), 0);
|
|
else
|
|
m_document->Replace(m_cursor, cur->position().offset(),
|
|
uchar(0), 0);
|
|
}
|
|
} else {
|
|
if (e->key() == Qt::Key_Backspace)
|
|
m_document->Remove(m_cursor, cur->position().offset() - 1,
|
|
1, 1);
|
|
else
|
|
m_document->Remove(m_cursor, cur->position().offset(), 1,
|
|
0);
|
|
}
|
|
|
|
} else {
|
|
this->RemoveSelection();
|
|
}
|
|
|
|
} else if (e->key() == Qt::Key_Insert) {
|
|
if (!isKeepSize()) {
|
|
cur->switchInsertionMode();
|
|
}
|
|
} else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QHexView::processMove(QHexCursor *cur, QKeyEvent *e) {
|
|
if (e->matches(QKeySequence::MoveToNextChar) ||
|
|
e->matches(QKeySequence::SelectNextChar))
|
|
this->moveNext(e->matches(QKeySequence::SelectNextChar));
|
|
else if (e->matches(QKeySequence::MoveToPreviousChar) ||
|
|
e->matches(QKeySequence::SelectPreviousChar))
|
|
this->movePrevious(e->matches(QKeySequence::SelectPreviousChar));
|
|
else if (e->matches(QKeySequence::MoveToNextLine) ||
|
|
e->matches(QKeySequence::SelectNextLine)) {
|
|
if (m_renderer->documentLastLine() == cur->currentLine())
|
|
return true;
|
|
|
|
auto nextline = cur->currentLine() + 1;
|
|
|
|
if (e->matches(QKeySequence::MoveToNextLine))
|
|
cur->moveTo(nextline, cur->currentColumn());
|
|
else
|
|
cur->select(nextline, cur->currentColumn());
|
|
} else if (e->matches(QKeySequence::MoveToPreviousLine) ||
|
|
e->matches(QKeySequence::SelectPreviousLine)) {
|
|
if (!cur->currentLine())
|
|
return true;
|
|
|
|
auto prevline = cur->currentLine() - 1;
|
|
|
|
if (e->matches(QKeySequence::MoveToPreviousLine))
|
|
cur->moveTo(prevline, cur->currentColumn());
|
|
else
|
|
cur->select(prevline, cur->currentColumn());
|
|
} else if (e->matches(QKeySequence::MoveToNextPage) ||
|
|
e->matches(QKeySequence::SelectNextPage)) {
|
|
if (m_renderer->documentLastLine() == cur->currentLine())
|
|
return true;
|
|
|
|
auto pageline = std::min(m_renderer->documentLastLine(),
|
|
cur->currentLine() + this->visibleLines());
|
|
|
|
if (e->matches(QKeySequence::MoveToNextPage))
|
|
cur->moveTo(pageline, cur->currentColumn());
|
|
else
|
|
cur->select(pageline, cur->currentColumn());
|
|
} else if (e->matches(QKeySequence::MoveToPreviousPage) ||
|
|
e->matches(QKeySequence::SelectPreviousPage)) {
|
|
if (!cur->currentLine())
|
|
return true;
|
|
|
|
auto pageline =
|
|
std::max(qsizetype(0), cur->currentLine() - this->visibleLines());
|
|
|
|
if (e->matches(QKeySequence::MoveToPreviousPage))
|
|
cur->moveTo(pageline, cur->currentColumn());
|
|
else
|
|
cur->select(pageline, cur->currentColumn());
|
|
} else if (e->matches(QKeySequence::MoveToStartOfDocument) ||
|
|
e->matches(QKeySequence::SelectStartOfDocument)) {
|
|
if (!cur->currentLine())
|
|
return true;
|
|
|
|
if (e->matches(QKeySequence::MoveToStartOfDocument))
|
|
cur->moveTo(0, 0);
|
|
else
|
|
cur->select(0, 0);
|
|
} else if (e->matches(QKeySequence::MoveToEndOfDocument) ||
|
|
e->matches(QKeySequence::SelectEndOfDocument)) {
|
|
if (m_renderer->documentLastLine() == cur->currentLine())
|
|
return true;
|
|
|
|
if (e->matches(QKeySequence::MoveToEndOfDocument))
|
|
cur->moveTo(m_renderer->documentLastLine(),
|
|
int(m_renderer->documentLastColumn()));
|
|
else
|
|
cur->select(m_renderer->documentLastLine(),
|
|
m_renderer->documentLastColumn());
|
|
} else if (e->matches(QKeySequence::MoveToStartOfLine) ||
|
|
e->matches(QKeySequence::SelectStartOfLine)) {
|
|
if (e->matches(QKeySequence::MoveToStartOfLine))
|
|
cur->moveTo(cur->currentLine(), 0);
|
|
else
|
|
cur->select(cur->currentLine(), 0);
|
|
} else if (e->matches(QKeySequence::MoveToEndOfLine) ||
|
|
e->matches(QKeySequence::SelectEndOfLine)) {
|
|
if (e->matches(QKeySequence::MoveToEndOfLine)) {
|
|
if (cur->currentLine() == m_renderer->documentLastLine())
|
|
cur->moveTo(cur->currentLine(),
|
|
int(m_renderer->documentLastColumn()));
|
|
else
|
|
cur->moveTo(cur->currentLine(), m_renderer->hexLineWidth() - 1,
|
|
0);
|
|
} else {
|
|
if (cur->currentLine() == m_renderer->documentLastLine())
|
|
cur->select(cur->currentLine(),
|
|
m_renderer->documentLastColumn());
|
|
else
|
|
cur->select(cur->currentLine(), m_renderer->hexLineWidth() - 1);
|
|
}
|
|
} else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QHexView::processTextInput(QHexCursor *cur, QKeyEvent *e) {
|
|
if (isReadOnly() || isLocked() || (e->modifiers() & Qt::ControlModifier))
|
|
return false;
|
|
|
|
auto text = e->text();
|
|
if (text.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
uchar key = static_cast<uchar>(text[0].toLatin1());
|
|
|
|
if ((m_renderer->selectedArea() == QHexRenderer::HexArea)) {
|
|
if (!((key >= '0' && key <= '9') ||
|
|
(key >= 'a' && key <= 'f'))) // Check if is a Hex Char
|
|
return false;
|
|
|
|
uchar val = uchar(QString(char(key)).toUInt(nullptr, 16));
|
|
this->RemoveSelection();
|
|
|
|
if (this->atEnd() || (cur->currentNibble() && !isKeepSize() &&
|
|
cur->insertionMode() == QHexCursor::InsertMode)) {
|
|
m_document->Insert(cur, cur->position().offset(), uchar(val << 4),
|
|
1); // X0 byte
|
|
return true;
|
|
}
|
|
|
|
uchar ch = uchar(m_document->at(cur->position().offset()));
|
|
|
|
if (cur->currentNibble()) // X0
|
|
val = uchar((ch & 0x0F) | (val << 4));
|
|
else // 0X
|
|
val = (ch & 0xF0) | val;
|
|
|
|
m_document->Replace(cur, cur->position().offset(), val,
|
|
cur->currentNibble());
|
|
return true;
|
|
}
|
|
|
|
if ((m_renderer->selectedArea() == QHexRenderer::AsciiArea)) {
|
|
if (!(key >= 0x20 && key <= 0x7E)) // Check if is a Printable Char
|
|
return false;
|
|
|
|
this->RemoveSelection();
|
|
|
|
if (!this->atEnd() &&
|
|
(cur->insertionMode() == QHexCursor::OverwriteMode))
|
|
m_document->Replace(cur, cur->position().offset(), key, 1);
|
|
else
|
|
m_document->Insert(cur, cur->position().offset(), key, 1);
|
|
|
|
QKeyEvent keyevent(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier);
|
|
qApp->sendEvent(this, &keyevent);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void QHexView::adjustScrollBars() {
|
|
QScrollBar *vscrollbar = this->verticalScrollBar();
|
|
int sizeFactor = this->documentSizeFactor();
|
|
vscrollbar->setSingleStep(sizeFactor);
|
|
|
|
auto docLines = m_renderer->documentLines();
|
|
auto visLines = this->visibleLines();
|
|
|
|
// modified by wingsummer,fix the scrollbar bug
|
|
if (docLines > visLines && !m_document->isEmpty()) {
|
|
this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
vscrollbar->setMaximum(
|
|
int((docLines - visLines) / uint(sizeFactor) + 1));
|
|
} else {
|
|
this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
vscrollbar->setValue(0);
|
|
vscrollbar->setMaximum(0);
|
|
}
|
|
|
|
QScrollBar *hscrollbar = this->horizontalScrollBar();
|
|
int documentWidth = m_renderer->documentWidth();
|
|
int viewportWidth = viewport()->width();
|
|
|
|
if (documentWidth > viewportWidth) {
|
|
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
// +1 to see the rightmost vertical line, +2 seems more pleasant to the
|
|
// eyes
|
|
hscrollbar->setMaximum(documentWidth - viewportWidth + 2);
|
|
} else {
|
|
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
hscrollbar->setValue(0);
|
|
hscrollbar->setMaximum(documentWidth);
|
|
}
|
|
}
|
|
|
|
void QHexView::renderLine(qsizetype line) {
|
|
if (!this->isLineVisible(line))
|
|
return;
|
|
this->viewport()->update(
|
|
/*m_renderer->getLineRect(line, this->firstVisibleLine())*/);
|
|
}
|
|
|
|
qsizetype QHexView::firstVisibleLine() const {
|
|
return qsizetype(this->verticalScrollBar()->value()) * documentSizeFactor();
|
|
}
|
|
qsizetype QHexView::lastVisibleLine() const {
|
|
return this->firstVisibleLine() + this->visibleLines() - 1;
|
|
}
|
|
|
|
qsizetype QHexView::visibleLines() const {
|
|
auto visLines =
|
|
qsizetype(std::ceil(this->height() / m_renderer->lineHeight() -
|
|
m_renderer->headerLineCount()));
|
|
return std::min(visLines, m_renderer->documentLines());
|
|
}
|
|
|
|
bool QHexView::isLineVisible(qsizetype line) const {
|
|
if (!m_document)
|
|
return false;
|
|
if (line < this->firstVisibleLine())
|
|
return false;
|
|
if (line > this->lastVisibleLine())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
int QHexView::documentSizeFactor() const {
|
|
int factor = 1;
|
|
|
|
if (m_document) {
|
|
auto docLines = m_renderer->documentLines();
|
|
if (docLines >= INT_MAX)
|
|
factor = int(docLines / INT_MAX) + 1;
|
|
}
|
|
|
|
return factor;
|
|
}
|