#include <QApplication>
#include <QClipboard>
#include <QFontDatabase>
#include <QHexView/model/buffer/qmemorybuffer.h>
#include <QHexView/model/qhexcursor.h>
#include <QHexView/model/qhexutils.h>
#include <QHexView/qhexview.h>
#include <QMouseEvent>
#include <QPainter>
#include <QPalette>
#include <QScrollBar>
#include <QTextCursor>
#include <QTextDocument>
#include <QToolTip>
#include <QWheelEvent>
#include <QtGlobal>
#include <QtMath>
#include <limits>

#if defined(QHEXVIEW_ENABLE_DIALOGS)
#include <QHexView/dialogs/hexfinddialog.h>
#endif

#if defined(QHEXVIEW_DEBUG)
#include <QDebug>
#define qhexview_fmtprint(fmt, ...) qDebug("%s " fmt, __func__, __VA_ARGS__)
#else
#define qhexview_fmtprint(fmt, ...)
#endif

QHexView::QHexView(QWidget* parent)
    : QAbstractScrollArea(parent), m_fontmetrics(this->font()) {
    QFont f = QFontDatabase::systemFont(QFontDatabase::FixedFont);

    if(f.styleHint() != QFont::TypeWriter) {
        f.setFamily("Monospace"); // Force Monospaced font
        f.setStyleHint(QFont::TypeWriter);
    }

    this->setFont(f);
    this->setMouseTracking(true);
    this->setFocusPolicy(Qt::StrongFocus);
    this->viewport()->setCursor(Qt::IBeamCursor);

    QPalette p = this->palette();
    p.setBrush(QPalette::Window, p.base());

    connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this,
            [=](int) { this->viewport()->update(); });

    m_hexmetadata = new QHexMetadata(&m_options, this);
    connect(m_hexmetadata, &QHexMetadata::changed, this,
            [=]() { this->viewport()->update(); });

    m_hexcursor = new QHexCursor(&m_options, this);
    this->setDocument(
        QHexDocument::fromMemory<QMemoryBuffer>(QByteArray(), this));
    this->checkState();

    connect(m_hexcursor, &QHexCursor::positionChanged, this, [=]() {
        m_writing = false;
        this->ensureVisible();
        Q_EMIT positionChanged();
    });

    connect(m_hexcursor, &QHexCursor::modeChanged, this, [=]() {
        m_writing = false;
        this->viewport()->update();
        Q_EMIT modeChanged();
    });
}

QRectF QHexView::headerRect() const {
    if(m_options.hasFlag(QHexFlags::NoHeader))
        return QRectF();

    return QRectF(0, 0, this->endColumnX(), this->lineHeight());
}

QRectF QHexView::addressRect() const {
    qreal y = m_options.hasFlag(QHexFlags::NoHeader) ? 0 : this->lineHeight();

    return QRectF(0, y, this->endColumnX(), this->height() - y);
}

QRectF QHexView::hexRect() const {
    qreal y = m_options.hasFlag(QHexFlags::NoHeader) ? 0 : this->lineHeight();

    return QRectF(this->hexColumnX(), y,
                  this->asciiColumnX() - this->hexColumnX(),
                  this->height() - y);
}

QRectF QHexView::asciiRect() const {
    qreal y = m_options.hasFlag(QHexFlags::NoHeader) ? 0 : this->lineHeight();

    return QRectF(this->asciiColumnX(), y,
                  this->endColumnX() - this->asciiColumnX(),
                  this->height() - y);
}

QHexDocument* QHexView::hexDocument() const { return m_hexdocument; }
QHexCursor* QHexView::hexCursor() const {
    return m_hexdocument ? m_hexcursor : nullptr;
}
const QHexMetadata* QHexView::hexMetadata() const { return m_hexmetadata; }
QHexOptions QHexView::options() const { return m_options; }

void QHexView::setOptions(const QHexOptions& options) {
    auto oldlinelength = m_options.linelength;
    m_options = options;

    if(oldlinelength != m_options.linelength)
        m_hexmetadata->invalidate();

    this->checkAndUpdate();
}

void QHexView::setBaseAddress(quint64 baseaddress) {
    if(m_options.baseaddress == baseaddress)
        return;

    m_options.baseaddress = baseaddress;
    this->checkAndUpdate();
}

void QHexView::setDelegate(QHexDelegate* rd) {
    if(m_hexdelegate == rd)
        return;
    m_hexdelegate = rd;
    this->checkAndUpdate();
}

void QHexView::setDocument(QHexDocument* doc) {
    if(!doc)
        doc = QHexDocument::fromMemory<QMemoryBuffer>(QByteArray(), this);
    if(!doc->parent())
        doc->setParent(this);

    m_writing = false;
    m_hexmetadata->clear();
    m_hexcursor->move(0);

    if(m_hexdocument) {
        disconnect(m_hexdocument, &QHexDocument::changed, this, nullptr);
        disconnect(m_hexdocument, &QHexDocument::dataChanged, this, nullptr);
        disconnect(m_hexdocument, &QHexDocument::reset, this, nullptr);
        disconnect(m_hexdocument, &QHexDocument::modifiedChanged, this,
                   nullptr);
    }

    m_hexdocument = doc;

    connect(m_hexdocument, &QHexDocument::reset, this, [=]() {
        m_writing = false;
        m_hexcursor->move(0);
        this->checkAndUpdate(true);
    });

    connect(m_hexdocument, &QHexDocument::dataChanged, this,
            &QHexView::dataChanged);

    connect(m_hexdocument, &QHexDocument::modifiedChanged, this,
            &QHexView::modifiedChanged);

    connect(m_hexdocument, &QHexDocument::changed, this,
            [=]() { this->checkAndUpdate(true); });

    this->checkAndUpdate(true);
}

void QHexView::setData(const QByteArray& ba) { m_hexdocument->setData(ba); }
void QHexView::setData(QHexBuffer* buffer) { m_hexdocument->setData(buffer); }

void QHexView::setCursorMode(QHexCursor::Mode mode) {
    m_hexcursor->setMode(mode);
}

void QHexView::setByteColor(quint8 b, QHexColor c) {
    m_options.bytecolors[b] = c;
    this->checkAndUpdate();
}

void QHexView::setByteForeground(quint8 b, QColor c) {
    m_options.bytecolors[b].foreground = c;
    this->checkAndUpdate();
}

void QHexView::setByteBackground(quint8 b, QColor c) {
    m_options.bytecolors[b].background = c;
    this->checkAndUpdate();
}

void QHexView::setMetadata(qint64 begin, qint64 end, const QColor& fgcolor,
                           const QColor& bgcolor, const QString& comment) {
    m_hexmetadata->setMetadata(begin, end, fgcolor, bgcolor, comment);
}
void QHexView::setForeground(qint64 begin, qint64 end, const QColor& fgcolor) {
    m_hexmetadata->setForeground(begin, end, fgcolor);
}
void QHexView::setBackground(qint64 begin, qint64 end, const QColor& bgcolor) {
    m_hexmetadata->setBackground(begin, end, bgcolor);
}
void QHexView::setComment(qint64 begin, qint64 end, const QString& comment) {
    m_hexmetadata->setComment(begin, end, comment);
}
void QHexView::setMetadataSize(qint64 begin, qint64 length,
                               const QColor& fgcolor, const QColor& bgcolor,
                               const QString& comment) {
    m_hexmetadata->setMetadataSize(begin, length, fgcolor, bgcolor, comment);
}
void QHexView::setForegroundSize(qint64 begin, qint64 length,
                                 const QColor& fgcolor) {
    m_hexmetadata->setForegroundSize(begin, length, fgcolor);
}
void QHexView::setBackgroundSize(qint64 begin, qint64 length,
                                 const QColor& bgcolor) {
    m_hexmetadata->setBackgroundSize(begin, length, bgcolor);
}
void QHexView::setCommentSize(qint64 begin, qint64 length,
                              const QString& comment) {
    m_hexmetadata->setCommentSize(begin, length, comment);
}
void QHexView::removeMetadata(qint64 line) {
    m_hexmetadata->removeMetadata(line);
}
void QHexView::removeBackground(qint64 line) {
    m_hexmetadata->removeBackground(line);
}
void QHexView::removeForeground(qint64 line) {
    m_hexmetadata->removeForeground(line);
}
void QHexView::removeComments(qint64 line) {
    m_hexmetadata->removeComments(line);
}
void QHexView::unhighlight(qint64 line) { m_hexmetadata->unhighlight(line); }
void QHexView::clearMetadata() { m_hexmetadata->clear(); }

#if defined(QHEXVIEW_ENABLE_DIALOGS)
void QHexView::showFind() {
    if(!m_hexdlgfind)
        m_hexdlgfind = new HexFindDialog(HexFindDialog::Type::Find, this);
    m_hexdlgfind->show();
}

void QHexView::showReplace() {
    if(!m_hexdlgreplace)
        m_hexdlgreplace = new HexFindDialog(HexFindDialog::Type::Replace, this);
    m_hexdlgreplace->show();
}
#endif

void QHexView::undo() {
    if(m_hexdocument)
        m_hexdocument->undo();
}
void QHexView::redo() {
    if(m_hexdocument)
        m_hexdocument->redo();
}

void QHexView::cut(bool hex) {
    this->copy(hex);
    if(m_readonly)
        return;

    if(m_hexcursor->hasSelection())
        this->removeSelection();
    else
        m_hexdocument->remove(m_hexcursor->offset(), 1);
}

void QHexView::copyAs(CopyMode mode) const {
    QClipboard* c = qApp->clipboard();

    QByteArray bytes = m_hexcursor->hasSelection()
                           ? m_hexcursor->selectedBytes()
                           : m_hexdocument->read(m_hexcursor->offset(), 1);

    switch(mode) {
        case CopyMode::HexArrayCurly:
        case CopyMode::HexArraySquare: {
            QString hexchar;
            int i = 0;

            for(char b : bytes) {
                if(!hexchar.isEmpty()) {
                    hexchar += ", ";
                    if(m_options.copybreak && !(++i % m_options.linelength))
                        hexchar += "\n";
                }

                hexchar +=
                    "0x" + QString::number(static_cast<uint>(b), 16).toUpper();
            }

            c->setText(
                QString(mode == CopyMode::HexArraySquare ? "[%1]" : "{%1}")
                    .arg(hexchar));
            break;
        }

        case CopyMode::HexArrayChar: {
            QString hexchar;

            for(char b : bytes)
                hexchar +=
                    "\\x" + QString::number(static_cast<uint>(b), 16).toUpper();

            c->setText(QString("\"%1\"").arg(hexchar));
            break;
        }

        default: {
            QString hexchar;

            for(int i = 0; i < bytes.size(); i++) {
                if(!(i % m_options.grouplength)) {
                    if(!hexchar.isEmpty()) {
                        hexchar += ", ";
                        if(m_options.copybreak && !(i % m_options.linelength))
                            hexchar += "\n";
                    }

                    hexchar += "0x";
                }

                hexchar += QString("%1")
                               .arg(static_cast<uint>(bytes[i]), 2, 16,
                                    QLatin1Char('0'))
                               .toUpper();
            }

            c->setText(hexchar);
            break;
        }
    }
}

void QHexView::copy(bool hex) const {
    QClipboard* c = qApp->clipboard();

    QByteArray bytes = m_hexcursor->hasSelection()
                           ? m_hexcursor->selectedBytes()
                           : m_hexdocument->read(m_hexcursor->offset(), 1);

    if(hex)
        bytes = QHexUtils::toHex(bytes, ' ').toUpper();
    c->setText(bytes);
}

void QHexView::paste(bool hex) {
    if(m_readonly)
        return;

    QClipboard* c = qApp->clipboard();
    QByteArray pastedata = c->text().toUtf8();
    if(pastedata.isEmpty())
        return;

    this->removeSelection();
    if(hex)
        pastedata = QByteArray::fromHex(pastedata);

    if(m_hexcursor->mode() == QHexCursor::Mode::Insert)
        m_hexdocument->insert(m_hexcursor->offset(), pastedata);
    else
        m_hexdocument->replace(m_hexcursor->offset(), pastedata);
}

void QHexView::clearModified() {
    if(m_hexdocument)
        m_hexdocument->clearModified();
}

void QHexView::selectAll() {
    m_hexcursor->move(0);
    m_hexcursor->select(m_hexdocument->length());
}

void QHexView::removeSelection() {
    if(!m_hexcursor->hasSelection())
        return;
    if(!m_readonly)
        m_hexdocument->remove(m_hexcursor->selectionStartOffset(),
                              m_hexcursor->selectionLength() - 1);
    m_hexcursor->clearSelection();
}

void QHexView::switchMode() { m_hexcursor->switchMode(); }

void QHexView::setAddressWidth(unsigned int w) {
    if(w == m_options.addresswidth)
        return;
    m_options.addresswidth = w;
    this->checkState();
}

void QHexView::setScrollSteps(int scrollsteps) {
    m_options.scrollsteps = scrollsteps;
}

void QHexView::setReadOnly(bool r) { m_readonly = r; }

void QHexView::setAutoWidth(bool r) {
    if(m_autowidth == r)
        return;
    m_autowidth = r;
    this->checkState();
}

void QHexView::paint(QPainter* painter) const {
    QTextDocument doc;
    doc.setDocumentMargin(0);
    doc.setUndoRedoEnabled(false);
    doc.setDefaultFont(this->font());

    QTextCursor c(&doc);

    this->drawHeader(c);
    this->drawDocument(c);

    painter->translate(-this->horizontalScrollBar()->value(), 0);
    doc.drawContents(painter);
    this->drawSeparators(painter);
}

void QHexView::checkOptions() {
    if(m_options.grouplength > m_options.linelength)
        m_options.grouplength = m_options.linelength;

    m_options.addresswidth =
        qMax<unsigned int>(m_options.addresswidth, this->calcAddressWidth());

    // Round to nearest multiple of 2
    m_options.grouplength =
        1u << (static_cast<unsigned int>(qFloor(m_options.grouplength / 2.0)));

    if(m_options.grouplength <= 1)
        m_options.grouplength = 1;

    if(!m_options.headercolor.isValid())
        m_options.headercolor =
            this->palette().color(QPalette::Normal, QPalette::Highlight);
}

void QHexView::setLineLength(unsigned int l) {
    if(l == m_options.linelength)
        return;
    m_options.linelength = l;
    m_hexmetadata->invalidate();
    this->checkAndUpdate(true);
}

void QHexView::setGroupLength(unsigned int l) {
    if(l == m_options.grouplength)
        return;
    m_options.grouplength = l;
    this->checkAndUpdate(true);
}

void QHexView::checkState() {
    if(!m_hexdocument)
        return;
    this->checkOptions();

    int doclines = static_cast<int>(this->lines()),
        vislines = this->visibleLines(true);
    qint64 vscrollmax = doclines - vislines;
    if(doclines >= vislines)
        vscrollmax++;

    this->verticalScrollBar()->setRange(0, qMax<qint64>(0, vscrollmax));
    this->verticalScrollBar()->setPageStep(vislines - 1);
    this->verticalScrollBar()->setSingleStep(m_options.scrollsteps);

    int vw = this->verticalScrollBar()->isVisible()
                 ? this->verticalScrollBar()->width()
                 : 0;

    static int oldmw = 0;
    if(!oldmw)
        oldmw = this->maximumWidth();
    this->setMaximumWidth(m_autowidth ? qCeil(this->endColumnX() + vw + 3)
                                      : oldmw);

    this->horizontalScrollBar()->setRange(
        0, qMax<int>(0, this->endColumnX() - this->width() + vw + 3));
    this->horizontalScrollBar()->setPageStep(this->width());
}

void QHexView::checkAndUpdate(bool calccolumns) {
    this->checkState();
    if(calccolumns)
        this->calcColumns();
    this->viewport()->update();
}

void QHexView::calcColumns() {
    if(!m_hexdocument)
        return;

    m_hexcolumns.clear();
    m_hexcolumns.reserve(m_options.linelength);

    auto x = this->hexColumnX(), cw = this->cellWidth() * 2;

    for(auto i = 0u; i < m_options.linelength; i++) {
        for(auto j = 0u; j < m_options.grouplength; j++, x += cw)
            m_hexcolumns.push_back(QRect(x, 0, cw, 0));

        x += this->cellWidth();
    }
}

void QHexView::ensureVisible() {
    if(!m_hexdocument)
        return;

    auto pos = m_hexcursor->position();
    auto vlines = this->visibleLines();

    if(pos.line >= (this->verticalScrollBar()->value() + vlines))
        this->verticalScrollBar()->setValue(pos.line - vlines + 1);
    else if(pos.line < this->verticalScrollBar()->value())
        this->verticalScrollBar()->setValue(pos.line);
    else
        this->viewport()->update();
}

void QHexView::drawSeparators(QPainter* p) const {
    if(!m_options.hasFlag(QHexFlags::Separators))
        return;

    auto oldpen = p->pen();
    p->setPen(m_options.separatorcolor.isValid()
                  ? m_options.separatorcolor
                  : this->palette().color(QPalette::Dark));

    if(m_options.hasFlag(QHexFlags::HSeparator)) {
        QLineF l(0, m_fontmetrics.lineSpacing(), this->endColumnX(),
                 m_fontmetrics.lineSpacing());
        if(!m_hexdelegate || !m_hexdelegate->paintSeparator(p, l, this))
            p->drawLine(l);
    }

    if(m_options.hasFlag(QHexFlags::VSeparator)) {
        QLineF l1(this->hexColumnX(), 0, this->hexColumnX(), this->height());
        QLineF l2(this->asciiColumnX(), 0, this->asciiColumnX(),
                  this->height());

        if(!m_hexdelegate ||
           (m_hexdelegate && !m_hexdelegate->paintSeparator(p, l1, this)))
            p->drawLine(l1);

        if(!m_hexdelegate ||
           (m_hexdelegate && !m_hexdelegate->paintSeparator(p, l2, this)))
            p->drawLine(l2);
    }

    p->setPen(oldpen);
}

void QHexView::drawHeader(QTextCursor& c) const {
    if(m_options.hasFlag(QHexFlags::NoHeader))
        return;

    static const auto RESET_FORMAT = [](const QHexOptions& options,
                                        QTextCharFormat& cf) {
        cf = {};
        cf.setForeground(options.headercolor);
    };

    QString addresslabel;
    if(m_hexdelegate)
        addresslabel = m_hexdelegate->addressHeader(this);
    if(addresslabel.isEmpty() && !m_options.addresslabel.isEmpty())
        addresslabel = m_options.addresslabel;

    QTextCharFormat cf;
    RESET_FORMAT(m_options, cf);
    if(m_hexdelegate)
        m_hexdelegate->renderHeaderPart(addresslabel, QHexArea::Address, cf,
                                        this);
    c.insertText(
        " " + QHexView::reduced(addresslabel, this->addressWidth()) + " ", cf);

    if(m_hexdelegate)
        RESET_FORMAT(m_options, cf);

    QString hexlabel;
    if(m_hexdelegate)
        hexlabel = m_hexdelegate->hexHeader(this);
    if(hexlabel.isEmpty())
        hexlabel = m_options.hexlabel;

    if(hexlabel.isNull()) {
        c.insertText(" ", {});

        for(auto i = 0u; i < m_options.linelength; i += m_options.grouplength) {
            QString h = QString::number(i, 16)
                            .rightJustified(m_options.grouplength * 2, '0')
                            .toUpper();

            if(m_hexdelegate) {
                RESET_FORMAT(m_options, cf);
                m_hexdelegate->renderHeaderPart(h, QHexArea::Hex, cf, this);
            }

            if(m_hexcursor->column() == static_cast<qint64>(i) &&
               m_options.hasFlag(QHexFlags::HighlightColumn)) {
                cf.setBackground(this->palette().color(QPalette::Highlight));
                cf.setForeground(
                    this->palette().color(QPalette::HighlightedText));
            }

            c.insertText(h, cf);
            c.insertText(" ", {});
            RESET_FORMAT(m_options, cf);
        }
    }
    else {
        if(m_hexdelegate)
            m_hexdelegate->renderHeaderPart(hexlabel, QHexArea::Hex, cf, this);
        c.insertText(
            " " +
            QHexView::reduced(
                hexlabel, (this->hexColumnWidth() / this->cellWidth()) - 1) +
            " ");
    }

    if(m_hexdelegate)
        RESET_FORMAT(m_options, cf);

    QString asciilabel;
    if(m_hexdelegate)
        asciilabel = m_hexdelegate->asciiHeader(this);
    if(asciilabel.isEmpty())
        asciilabel = m_options.asciilabel;

    if(asciilabel.isNull()) {
        c.insertText(" ", {});

        for(unsigned int i = 0; i < m_options.linelength; i++) {
            QString a = QString::number(i, 16).toUpper();

            if(m_hexdelegate) {
                RESET_FORMAT(m_options, cf);
                m_hexdelegate->renderHeaderPart(a, QHexArea::Ascii, cf, this);
            }

            if(m_hexcursor->column() == static_cast<qint64>(i) &&
               m_options.hasFlag(QHexFlags::HighlightColumn)) {
                cf.setBackground(this->palette().color(QPalette::Highlight));
                cf.setForeground(
                    this->palette().color(QPalette::HighlightedText));
            }

            c.insertText(a, cf);
            RESET_FORMAT(m_options, cf);
        }

        c.insertText(" ", {});
    }
    else {
        if(m_hexdelegate)
            m_hexdelegate->renderHeaderPart(asciilabel, QHexArea::Ascii, cf,
                                            this);
        c.insertText(" " +
                     QHexView::reduced(asciilabel, ((this->endColumnX() -
                                                     this->asciiColumnX() -
                                                     this->cellWidth()) /
                                                    this->cellWidth()) -
                                                       1) +
                     " ");
    }

    QTextBlockFormat bf;
    if(m_options.hasFlag(QHexFlags::StyledHeader))
        bf.setBackground(this->palette().color(QPalette::Window));
    if(m_hexdelegate)
        m_hexdelegate->renderHeader(bf, this);
    c.setBlockFormat(bf);
    c.insertBlock();
}

void QHexView::drawDocument(QTextCursor& c) const {
    if(!m_hexdocument)
        return;

    qreal y = !m_options.hasFlag(QHexFlags::NoHeader) ? this->lineHeight() : 0;
    quint64 line = static_cast<quint64>(this->verticalScrollBar()->value());

    QTextCharFormat addrformat;
    addrformat.setForeground(
        this->palette().color(QPalette::Normal, QPalette::Highlight));

    for(qint64 l = 0; m_hexdocument->isEmpty() ||
                      (line < this->lines() && l < this->visibleLines());
        l++, line++, y += this->lineHeight()) {
        quint64 address = line * m_options.linelength + this->baseAddress();
        QString addrstr = QString::number(address, 16)
                              .rightJustified(this->addressWidth(), '0')
                              .toUpper();

        // Address Part
        QTextCharFormat acf;
        acf.setForeground(m_options.headercolor);

        if(m_options.hasFlag(QHexFlags::StyledAddress))
            acf.setBackground(this->palette().color(QPalette::Window));

        if(m_hexdelegate)
            m_hexdelegate->renderAddress(address, acf, this);

        if(m_hexcursor->line() == static_cast<qint64>(line) &&
           m_options.hasFlag(QHexFlags::HighlightAddress)) {
            acf.setBackground(this->palette().color(QPalette::Highlight));
            acf.setForeground(this->palette().color(QPalette::HighlightedText));
        }

        c.insertText(" " + addrstr + " ", acf);

        QByteArray linebytes = this->getLine(line);
        c.insertText(" ", {});

        // Hex Part
        for(unsigned int column = 0u; column < m_options.linelength;) {
            QTextCharFormat cf;

            for(unsigned int byteidx = 0u; byteidx < m_options.grouplength;
                byteidx++, column++) {
                QString s;
                quint8 b{};

                if(m_hexdocument->accept(
                       this->positionFromLineCol(line, column))) {
                    s = linebytes.isEmpty() ||
                                column >= static_cast<qint64>(linebytes.size())
                            ? "  "
                            : QString(QHexUtils::toHex(linebytes.mid(column, 1))
                                          .toUpper());
                    b = static_cast<int>(column) < linebytes.size()
                            ? linebytes.at(column)
                            : 0x00;
                }
                else
                    s = QString(m_options.invalidchar).repeated(2);

                cf = this->drawFormat(c, b, s, QHexArea::Hex, line, column,
                                      static_cast<int>(column) <
                                          linebytes.size());
            }

            c.insertText(" ", cf);
        }

        c.insertText(" ", {});

        // Ascii Part
        for(unsigned int column = 0u; column < m_options.linelength; column++) {
            QString s;
            quint8 b{};

            if(m_hexdocument->accept(this->positionFromLineCol(line, column))) {
                s = linebytes.isEmpty() ||
                            column >= static_cast<qint64>(linebytes.size())
                        ? QChar(' ')
                        : (QChar::isPrint(linebytes.at(column))
                               ? QChar(linebytes.at(column))
                               : m_options.unprintablechar);

                b = static_cast<int>(column) < linebytes.size()
                        ? linebytes.at(column)
                        : 0x00;
            }
            else
                s = m_options.invalidchar;

            this->drawFormat(c, b, s, QHexArea::Ascii, line, column,
                             static_cast<int>(column) < linebytes.size());
        }

        QTextBlockFormat bf;

        if(m_options.linealternatebackground.isValid() && line % 2)
            bf.setBackground(m_options.linealternatebackground);
        else if(m_options.linebackground.isValid() && !(line % 2))
            bf.setBackground(m_options.linebackground);

        bf.setLineHeight(this->lineHeight(), QTextBlockFormat::FixedHeight); // UEFITool: added to fix an issue with Hi-DPI displays on Windows
        c.setBlockFormat(bf);
        c.insertBlock({});
        if(m_hexdocument->isEmpty())
            break;
    }
}

unsigned int QHexView::calcAddressWidth() const {
    if(!m_hexdocument)
        return 0;

    auto maxaddr =
        static_cast<quint64>(m_options.baseaddress + m_hexdocument->length());
    if(maxaddr <= std::numeric_limits<quint32>::max())
        return 8;
    return QString::number(maxaddr, 16).size();
}

int QHexView::visibleLines(bool absolute) const {
    int vl = static_cast<int>(
        qCeil(this->viewport()->height() / this->lineHeight()));
    if(!m_options.hasFlag(QHexFlags::NoHeader))
        vl--;
    return absolute ? vl : qMin<int>(this->lines(), vl);
}

qint64 QHexView::getLastColumn(qint64 line) const {
    return this->getLine(line).size() - 1;
}
qint64 QHexView::lastLine() const { return qMax<qint64>(0, this->lines() - 1); }

qreal QHexView::hexColumnWidth() const {
    int l = 0;

    for(auto i = 0u; i < m_options.linelength; i += m_options.grouplength)
        l += (2 * m_options.grouplength) + 1;

    return this->getNCellsWidth(l);
}

unsigned int QHexView::addressWidth() const {
    if(!m_hexdocument || m_options.addresswidth)
        return m_options.addresswidth;
    return this->calcAddressWidth();
}

unsigned int QHexView::lineLength() const { return m_options.linelength; }

bool QHexView::isModified() const {
    return m_hexdocument && m_hexdocument->isModified();
}

bool QHexView::canUndo() const {
    return m_hexdocument && m_hexdocument->canUndo();
}

bool QHexView::canRedo() const {
    return m_hexdocument && m_hexdocument->canRedo();
}

quint64 QHexView::offset() const { return m_hexcursor->offset(); }
quint64 QHexView::address() const { return m_hexcursor->address(); }

QHexPosition QHexView::positionFromOffset(quint64 offset) const {
    QHexPosition opt = QHexPosition::invalid();

    if(offset < static_cast<quint64>(m_hexdocument->length())) {
        opt.line = offset / m_options.linelength;
        opt.column = offset % m_options.linelength;
    }

    return opt;
}

QHexPosition QHexView::positionFromAddress(quint64 address) const {
    return this->positionFromOffset(address - m_options.baseaddress);
}

QHexPosition QHexView::position() const { return m_hexcursor->position(); }

QHexPosition QHexView::selectionStart() const {
    return m_hexcursor->selectionStart();
}

QHexPosition QHexView::selectionEnd() const {
    return m_hexcursor->selectionEnd();
}

quint64 QHexView::selectionStartOffset() const {
    return m_hexcursor->selectionStartOffset();
}

quint64 QHexView::selectionEndOffset() const {
    return m_hexcursor->selectionEndOffset();
}

quint64 QHexView::baseAddress() const { return m_options.baseaddress; }

quint64 QHexView::lines() const {
    if(!m_hexdocument)
        return 0;

    auto lines = static_cast<quint64>(qCeil(
        m_hexdocument->length() / static_cast<double>(m_options.linelength)));
    return !m_hexdocument->isEmpty() && !lines ? 1 : lines;
}

qint64 QHexView::replace(const QVariant& oldvalue, const QVariant& newvalue,
                         qint64 offset, QHexFindMode mode, unsigned int options,
                         QHexFindDirection fd) const {
    auto res =
        QHexUtils::replace(this, oldvalue, newvalue, offset, mode, options, fd);

    if(res.first > -1) {
        m_hexcursor->move(res.first);
        m_hexcursor->selectSize(res.second);
    }

    return res.first;
}

qint64 QHexView::find(const QVariant& value, qint64 offset, QHexFindMode mode,
                      unsigned int options, QHexFindDirection fd) const {
    auto res = QHexUtils::find(this, value, offset, mode, options, fd);

    if(res.first > -1) {
        m_hexcursor->move(res.first);
        m_hexcursor->selectSize(res.second);
    }

    return res.first;
}

qreal QHexView::hexColumnX() const {
    return this->getNCellsWidth(this->addressWidth() + 2);
}
qreal QHexView::asciiColumnX() const {
    return this->hexColumnX() + this->hexColumnWidth() + this->cellWidth();
}
qreal QHexView::endColumnX() const {
    return this->asciiColumnX() +
           this->getNCellsWidth(m_options.linelength + 1) + this->cellWidth();
}
qreal QHexView::getNCellsWidth(int n) const { return n * this->cellWidth(); }

qreal QHexView::cellWidth() const {
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
    return m_fontmetrics.horizontalAdvance(" ");
#else
    return m_fontmetrics.width(" ");
#endif
}

qreal QHexView::lineHeight() const { return m_fontmetrics.height(); }

qint64 QHexView::positionFromLineCol(qint64 line, qint64 col) const {
    if(m_hexdocument) {
        return qMin((line * m_options.linelength) + col,
                    m_hexdocument->length());
    }

    return 0;
}

QHexPosition QHexView::positionFromPoint(QPoint pt) const {
    QHexPosition pos = QHexPosition::invalid();
    auto abspt = this->absolutePoint(pt);

    switch(this->areaFromPoint(pt)) {
        case QHexArea::Hex: {
            pos.column = -1;

            for(qint64 i = 0; i < m_hexcolumns.size(); i++) {
                if(m_hexcolumns.at(i).left() > abspt.x())
                    break;
                pos.column = i;
            }

            break;
        }

        case QHexArea::Ascii:
            pos.column = qMax<qint64>(
                qFloor((abspt.x() - this->asciiColumnX()) / this->cellWidth()) -
                    1,
                0);
            break;
        case QHexArea::Address: pos.column = 0; break;
        case QHexArea::Header: return QHexPosition::invalid();
        default: break;
    }

    pos.line = qMin<qint64>(this->verticalScrollBar()->value() +
                                (abspt.y() / this->lineHeight()),
                            this->lines());
    if(!m_options.hasFlag(QHexFlags::NoHeader))
        pos.line = qMax<qint64>(0, pos.line - 1);

    auto docline = this->getLine(pos.line);
    pos.column =
        qMin<qint64>(pos.column, docline.isEmpty() ? 0 : docline.size());

    qhexview_fmtprint("line: %lld, col: %lld", pos.line, pos.column);
    return pos;
}

QPoint QHexView::absolutePoint(QPoint pt) const {
    return pt + QPoint(this->horizontalScrollBar()->value(), 0);
}

QHexArea QHexView::areaFromPoint(QPoint pt) const {
    pt = this->absolutePoint(pt);
    qreal line =
        this->verticalScrollBar()->value() + pt.y() / this->lineHeight();

    if(!m_options.hasFlag(QHexFlags::NoHeader) && !qFloor(line))
        return QHexArea::Header;
    if(pt.x() < this->hexColumnX())
        return QHexArea::Address;
    if(pt.x() < this->asciiColumnX())
        return QHexArea::Hex;
    if(pt.x() < this->endColumnX())
        return QHexArea::Ascii;
    return QHexArea::Extra;
}

QTextCharFormat QHexView::drawFormat(QTextCursor& c, quint8 b, const QString& s,
                                     QHexArea area, qint64 line, qint64 column,
                                     bool applyformat) const {
    QTextCharFormat cf, selcf;
    QHexPosition pos{line, column};

    if(applyformat) {
        auto offset = m_hexcursor->positionToOffset(pos);
        bool hasdelegate =
            m_hexdelegate && m_hexdelegate->render(offset, b, cf, this);

        if(!hasdelegate) {
            auto it = m_options.bytecolors.find(b);

            if(it != m_options.bytecolors.end()) {
                if(it->background.isValid())
                    cf.setBackground(it->background);
                if(it->foreground.isValid())
                    cf.setForeground(it->foreground);
            }
        }

        const auto* metadataline = m_hexmetadata->find(line);

        if(metadataline) {
            for(const auto& metadata : *metadataline) {
                if(offset < metadata.begin || offset >= metadata.end)
                    continue;

                if(!hasdelegate) {
                    if(metadata.foreground.isValid())
                        cf.setForeground(metadata.foreground);

                    if(metadata.background.isValid()) {
                        cf.setBackground(metadata.background);

                        if(!metadata.foreground.isValid())
                            cf.setForeground(
                                this->getReadableColor(metadata.background));
                    }
                }

                if(!metadata.comment.isEmpty()) {
                    cf.setUnderlineColor(
                        m_options.commentcolor.isValid()
                            ? m_options.commentcolor
                            : this->palette().color(QPalette::WindowText));
                    cf.setUnderlineStyle(
                        QTextCharFormat::UnderlineStyle::SingleUnderline);
                }

                if(offset == metadata.begin) // Remove previous metadata's
                                             // style, if needed
                {
                    if(metadata.comment.isEmpty())
                        selcf.setUnderlineStyle(
                            QTextCharFormat::UnderlineStyle::NoUnderline);
                    if(!metadata.foreground.isValid())
                        selcf.setForeground(Qt::color1);
                    if(!metadata.background.isValid())
                        selcf.setBackground(Qt::transparent);
                }

                if(offset < metadata.end - 1 &&
                   column < this->getLastColumn(line))
                    selcf = cf;
            }
        }

        if(hasdelegate && column < this->getLastColumn(line))
            selcf = cf;
    }

    if(this->hexCursor()->isSelected(line, column)) {
        auto offset = this->hexCursor()->positionToOffset(pos);
        auto selend = this->hexCursor()->selectionEndOffset();

        cf.setBackground(
            this->palette().color(QPalette::Normal, QPalette::Highlight));
        cf.setForeground(
            this->palette().color(QPalette::Normal, QPalette::HighlightedText));
        if(offset < selend && column < this->getLastColumn(line))
            selcf = cf;
    }

    if(this->hexCursor()->position() == pos) {
        auto cursorbg = this->palette().color(
            this->hasFocus() ? QPalette::Normal : QPalette::Disabled,
            QPalette::WindowText);
        auto cursorfg = this->palette().color(
            this->hasFocus() ? QPalette::Normal : QPalette::Disabled,
            QPalette::Base);
        auto discursorbg =
            this->palette().color(QPalette::Disabled, QPalette::WindowText);
        auto discursorfg =
            this->palette().color(QPalette::Disabled, QPalette::Base);

        switch(m_hexcursor->mode()) {
            case QHexCursor::Mode::Insert:
                cf.setUnderlineColor(m_currentarea == area ? cursorbg
                                                           : discursorbg);
                cf.setUnderlineStyle(
                    QTextCharFormat::UnderlineStyle::SingleUnderline);
                break;

            case QHexCursor::Mode::Overwrite:
                cf.setBackground(m_currentarea == area ? cursorbg
                                                       : discursorbg);
                cf.setForeground(m_currentarea == area ? cursorfg
                                                       : discursorfg);
                break;
        }
    }

    c.insertText(s, cf);
    return selcf;
}

void QHexView::moveNext(bool select) {
    auto line = this->hexCursor()->line(), column = this->hexCursor()->column();

    if(column >= m_options.linelength - 1) {
        line++;
        column = 0;
    }
    else
        column++;

    qint64 offset =
        this->hexCursor()->mode() == QHexCursor::Mode::Insert ? 1 : 0;
    if(select)
        this->hexCursor()->select(
            qMin<qint64>(line, this->lines()),
            qMin<qint64>(column, this->getLastColumn(line) + offset));
    else
        this->hexCursor()->move(
            qMin<qint64>(line, this->lines()),
            qMin<qint64>(column, this->getLastColumn(line) + offset));
}

void QHexView::movePrevious(bool select) {
    auto line = this->hexCursor()->line(), column = this->hexCursor()->column();

    if(column <= 0) {
        if(!line)
            return;
        column = this->getLine(--line).size() - 1;
    }
    else
        column--;

    if(select)
        this->hexCursor()->select(
            qMin<qint64>(line, this->lines()),
            qMin<qint64>(column, this->getLastColumn(line)));
    else
        this->hexCursor()->move(
            qMin<qint64>(line, this->lines()),
            qMin<qint64>(column, this->getLastColumn(line)));
}

bool QHexView::keyPressMove(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(this->hexCursor()->line() == this->lastLine())
            return true;
        auto nextline = this->hexCursor()->line() + 1;
        if(e->matches(QKeySequence::MoveToNextLine))
            this->hexCursor()->move(nextline, this->hexCursor()->column());
        else
            this->hexCursor()->select(nextline, this->hexCursor()->column());
    }
    else if(e->matches(QKeySequence::MoveToPreviousLine) ||
            e->matches(QKeySequence::SelectPreviousLine)) {
        if(!this->hexCursor()->line())
            return true;
        auto prevline = this->hexCursor()->line() - 1;
        if(e->matches(QKeySequence::MoveToPreviousLine))
            this->hexCursor()->move(prevline, this->hexCursor()->column());
        else
            this->hexCursor()->select(prevline, this->hexCursor()->column());
    }
    else if(e->matches(QKeySequence::MoveToNextPage) ||
            e->matches(QKeySequence::SelectNextPage)) {
        if(this->lastLine() == this->hexCursor()->line())
            return true;
        auto pageline = qMin(this->lastLine(),
                             this->hexCursor()->line() + this->visibleLines());
        if(e->matches(QKeySequence::MoveToNextPage))
            this->hexCursor()->move(pageline, this->hexCursor()->column());
        else
            this->hexCursor()->select(pageline, this->hexCursor()->column());
    }
    else if(e->matches(QKeySequence::MoveToPreviousPage) ||
            e->matches(QKeySequence::SelectPreviousPage)) {
        if(!this->hexCursor()->line())
            return true;
        auto pageline =
            qMax<qint64>(0, this->hexCursor()->line() - this->visibleLines());
        if(e->matches(QKeySequence::MoveToPreviousPage))
            this->hexCursor()->move(pageline, this->hexCursor()->column());
        else
            this->hexCursor()->select(pageline, this->hexCursor()->column());
    }
    else if(e->matches(QKeySequence::MoveToStartOfDocument) ||
            e->matches(QKeySequence::SelectStartOfDocument)) {
        if(!this->hexCursor()->line())
            return true;
        if(e->matches(QKeySequence::MoveToStartOfDocument))
            this->hexCursor()->move(0, 0);
        else
            this->hexCursor()->select(0, 0);
    }
    else if(e->matches(QKeySequence::MoveToEndOfDocument) ||
            e->matches(QKeySequence::SelectEndOfDocument)) {
        if(this->lastLine() == this->hexCursor()->line())
            return true;
        if(e->matches(QKeySequence::MoveToEndOfDocument))
            this->hexCursor()->move(
                this->lastLine(),
                this->getLastColumn(this->hexCursor()->line()));
        else
            this->hexCursor()->select(this->lastLine(),
                                      this->getLastColumn(this->lastLine()));
    }
    else if(e->matches(QKeySequence::MoveToStartOfLine) ||
            e->matches(QKeySequence::SelectStartOfLine)) {
        auto offset =
            this->hexCursor()->positionToOffset({this->hexCursor()->line(), 0});
        if(e->matches(QKeySequence::MoveToStartOfLine))
            this->hexCursor()->move(offset);
        else
            this->hexCursor()->select(offset);
    }
    else if(e->matches(QKeySequence::SelectEndOfLine) ||
            e->matches(QKeySequence::MoveToEndOfLine)) {
        auto offset = this->hexCursor()->positionToOffset(
            {this->hexCursor()->line(),
             this->getLastColumn(this->hexCursor()->line())});
        if(e->matches(QKeySequence::SelectEndOfLine))
            this->hexCursor()->select(offset);
        else
            this->hexCursor()->move(offset);
    }
    else
        return false;

    return true;
}

bool QHexView::keyPressTextInput(QKeyEvent* e) {
    if(m_readonly || e->text().isEmpty() ||
       (e->modifiers() & Qt::ControlModifier))
        return false;

    bool atend = m_hexcursor->offset() >= m_hexdocument->length();
    if(atend && m_hexcursor->mode() == QHexCursor::Mode::Overwrite)
        return false;

    char key = e->text().at(0).toLatin1();

    switch(m_currentarea) {
        case QHexArea::Hex: {
            if(!QHexUtils::isHex(key))
                return false;

            bool ok = false;
            auto val = static_cast<quint8>(QString(key).toUInt(&ok, 16));
            if(!ok)
                return false;
            m_hexcursor->removeSelection();

            quint8 ch = m_hexdocument->isEmpty() ||
                                m_hexcursor->offset() >= m_hexdocument->length()
                            ? '\x00'
                            : m_hexdocument->at(m_hexcursor->offset());
            ch = m_writing ? (ch << 4) | val : val;

            if(!m_writing && (m_hexcursor->mode() == QHexCursor::Mode::Insert))
                m_hexdocument->insert(m_hexcursor->offset(), val);
            else
                m_hexdocument->replace(m_hexcursor->offset(), ch);

            m_writing = !m_writing;
            if(!m_writing)
                this->moveNext();

            break;
        }

        case QHexArea::Ascii: {
            if(!QChar::isPrint(key))
                return false;
            m_hexcursor->removeSelection();
            if(m_hexcursor->mode() == QHexCursor::Mode::Insert)
                m_hexdocument->insert(m_hexcursor->offset(), key);
            else
                m_hexdocument->replace(m_hexcursor->offset(), key);
            this->moveNext();
            break;
        }

        default: return false;
    }

    return true;
}

bool QHexView::keyPressAction(QKeyEvent* e) {
    if(e->modifiers() != Qt::NoModifier) {
        if(e->matches(QKeySequence::SelectAll))
            this->selectAll();
        else if(!m_readonly && e->matches(QKeySequence::Undo))
            this->undo();
        else if(!m_readonly && e->matches(QKeySequence::Redo))
            this->redo();
        else if(!m_readonly && e->matches(QKeySequence::Cut))
            this->cut(m_currentarea != QHexArea::Ascii);
        else if(e->matches(QKeySequence::Copy))
            this->copy(m_currentarea != QHexArea::Ascii);
        else if(!m_readonly && e->matches(QKeySequence::Paste))
            this->paste(m_currentarea != QHexArea::Ascii);
        else
            return false;

        return true;
    }

    if(m_readonly)
        return false;

    switch(e->key()) {
        case Qt::Key_Backspace:
        case Qt::Key_Delete: {
            if(!m_hexcursor->hasSelection()) {
                auto offset = m_hexcursor->offset();
                if(offset <= 0)
                    return true;

                if(e->key() == Qt::Key_Backspace)
                    m_hexdocument->remove(offset - 1, 1);
                else
                    m_hexdocument->remove(offset, 1);
            }
            else {
                auto oldpos = m_hexcursor->selectionStart();
                m_hexcursor->removeSelection();
                m_hexcursor->move(oldpos);
            }

            if(e->key() == Qt::Key_Backspace)
                this->movePrevious();
            m_writing = false;
            break;
        }

        case Qt::Key_Insert:
            m_writing = false;
            m_hexcursor->switchMode();
            break;

        default: return false;
    }

    return true;
}

bool QHexView::event(QEvent* e) {
    switch(e->type()) {
        case QEvent::FontChange:
            m_fontmetrics = QFontMetricsF(this->font());
            this->checkAndUpdate(true);
            return true;

        case QEvent::ToolTip: {
            if(m_hexdocument && (m_currentarea == QHexArea::Hex ||
                                 m_currentarea == QHexArea::Ascii)) {
                auto* helpevent = static_cast<QHelpEvent*>(e);
                auto pos = this->positionFromPoint(helpevent->pos());
                auto comment = m_hexmetadata->getComment(pos.line, pos.column);
                if(!comment.isEmpty())
                    QToolTip::showText(helpevent->globalPos(), comment);
                return true;
            }

            break;
        }

        default: break;
    }

    return QAbstractScrollArea::event(e);
}

void QHexView::showEvent(QShowEvent* e) {
    QAbstractScrollArea::showEvent(e);
    this->checkAndUpdate(true);
}

void QHexView::paintEvent(QPaintEvent*) {
    if(!m_hexdocument)
        return;

    QPainter painter(this->viewport());
    if(m_hexdelegate)
        m_hexdelegate->paint(&painter, this);
    else
        this->paint(&painter);
}

void QHexView::resizeEvent(QResizeEvent* e) {
    this->checkState();
    QAbstractScrollArea::resizeEvent(e);
}

void QHexView::focusInEvent(QFocusEvent* e) {
    QAbstractScrollArea::focusInEvent(e);
    if(m_hexdocument)
        this->viewport()->update();
}

void QHexView::focusOutEvent(QFocusEvent* e) {
    QAbstractScrollArea::focusOutEvent(e);
    if(m_hexdocument)
        this->viewport()->update();
}

void QHexView::mousePressEvent(QMouseEvent* e) {
    QAbstractScrollArea::mousePressEvent(e);
    if(!m_hexdocument || e->button() != Qt::LeftButton)
        return;

    auto pos = this->positionFromPoint(e->pos());
    if(!pos.isValid())
        return;

    auto area = this->areaFromPoint(e->pos());
    qhexview_fmtprint("%d", static_cast<int>(area));

    switch(area) {
        case QHexArea::Address: this->hexCursor()->move(pos.line, 0); break;
        case QHexArea::Hex:
            m_currentarea = area;
            this->hexCursor()->move(pos);
            break;
        case QHexArea::Ascii:
            m_currentarea = area;
            this->hexCursor()->move(pos.line, pos.column);
            break;
        default: return;
    }

    this->viewport()->update();
}

void QHexView::mouseMoveEvent(QMouseEvent* e) {
    QAbstractScrollArea::mouseMoveEvent(e);
    if(!this->hexCursor())
        return;

    e->accept();
    auto area = this->areaFromPoint(e->pos());

    switch(area) {
        case QHexArea::Header:
            this->viewport()->setCursor(Qt::ArrowCursor);
            return;
        case QHexArea::Address:
            this->viewport()->setCursor(Qt::ArrowCursor);
            break;
        default: this->viewport()->setCursor(Qt::IBeamCursor); break;
    }

    if(e->buttons() == Qt::LeftButton) {
        auto pos = this->positionFromPoint(e->pos());
        if(!pos.isValid())
            return;
        if(area == QHexArea::Ascii || area == QHexArea::Hex)
            m_currentarea = area;
        this->hexCursor()->select(pos);
    }
}

void QHexView::wheelEvent(QWheelEvent* e) {
    e->ignore();

#if defined(Q_OS_OSX)
    // In macOS scrollbar invisibility should not prevent scrolling from working
    if(!m_hexdocument)
        return;
#else
    if(!m_hexdocument || !this->verticalScrollBar()->isVisible())
        return;
#endif

    // https://doc.qt.io/qt-6/qwheelevent.html
    // "Returns the relative amount that the wheel was rotated, in eighths of a
    // degree." "Most mouse types work in steps of 15 degrees, in which case the
    // delta value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees."
    int const ydelta = e->angleDelta().y();
    if(0 != ydelta) {
        int const ydeltaAbsolute = qAbs(ydelta);
        int const numberOfLinesToMove =
            (ydeltaAbsolute * m_options.scrollsteps + 119) /
            120; // always move at least 1 line
        int const ydeltaSign = ydelta / ydeltaAbsolute;

        int const oldValue = this->verticalScrollBar()->value();
        int const newValue = oldValue - ydeltaSign * numberOfLinesToMove;
        this->verticalScrollBar()->setValue(newValue);
    }
}

void QHexView::keyPressEvent(QKeyEvent* e) {
    bool handled = false;

    if(this->hexCursor()) {
        handled = this->keyPressMove(e);
        if(!handled)
            handled = this->keyPressAction(e);
        if(!handled)
            handled = this->keyPressTextInput(e);
    }

    if(handled)
        e->accept();
    else
        QAbstractScrollArea::keyPressEvent(e);
}

QString QHexView::reduced(const QString& s, int maxlen) {
    if(s.length() <= maxlen)
        return s.leftJustified(maxlen);
    return s.mid(0, maxlen - 1) + "\u2026";
}

bool QHexView::isColorLight(QColor c) {
    return std::sqrt(0.299 * std::pow(c.red(), 2) +
                     0.587 * std::pow(c.green(), 2) +
                     0.114 * std::pow(c.blue(), 2)) > 127.5;
}

QColor QHexView::getReadableColor(QColor c) const {
    QPalette palette = this->palette();
    return QHexView::isColorLight(c)
               ? palette.color(QPalette::Normal, QPalette::WindowText)
               : palette.color(QPalette::Normal, QPalette::HighlightedText);
}

QByteArray QHexView::selectedBytes() const {
    return m_hexcursor->hasSelection()
               ? m_hexdocument->read(m_hexcursor->selectionStartOffset(),
                                     m_hexcursor->selectionLength())
               : QByteArray{};
}
QByteArray QHexView::getLine(qint64 line) const {
    return m_hexdocument ? m_hexdocument->read(line * m_options.linelength,
                                               m_options.linelength)
                         : QByteArray{};
}
