1
0
Fork 0
forked from suyu/suyu
undo-deletions/externals/qhexedit/qhexedit_p.cpp
2014-08-19 21:34:00 +10:00

857 lines
24 KiB
C++

#include "qhexedit_p.h"
#include "commands.h"
const int HEXCHARS_IN_LINE = 47;
const int GAP_ADR_HEX = 10;
const int GAP_HEX_ASCII = 16;
const int BYTES_PER_LINE = 16;
QHexEditPrivate::QHexEditPrivate(QScrollArea *parent) : QWidget(parent)
{
_undoStack = new QUndoStack(this);
_scrollArea = parent;
setAddressWidth(4);
setAddressOffset(0);
setAddressArea(true);
setAsciiArea(true);
setHighlighting(true);
setOverwriteMode(true);
setReadOnly(false);
setAddressAreaColor(QColor(0xd4, 0xd4, 0xd4, 0xff));
setHighlightingColor(QColor(0xff, 0xff, 0x99, 0xff));
setSelectionColor(QColor(0x6d, 0x9e, 0xff, 0xff));
setFont(QFont("Courier", 10));
_size = 0;
resetSelection(0);
setFocusPolicy(Qt::StrongFocus);
connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor()));
_cursorTimer.setInterval(500);
_cursorTimer.start();
}
void QHexEditPrivate::setAddressOffset(int offset)
{
_xData.setAddressOffset(offset);
adjust();
}
int QHexEditPrivate::addressOffset()
{
return _xData.addressOffset();
}
void QHexEditPrivate::setData(const QByteArray &data)
{
_xData.setData(data);
_undoStack->clear();
adjust();
setCursorPos(0);
}
QByteArray QHexEditPrivate::data()
{
return _xData.data();
}
void QHexEditPrivate::setAddressAreaColor(const QColor &color)
{
_addressAreaColor = color;
update();
}
QColor QHexEditPrivate::addressAreaColor()
{
return _addressAreaColor;
}
void QHexEditPrivate::setHighlightingColor(const QColor &color)
{
_highlightingColor = color;
update();
}
QColor QHexEditPrivate::highlightingColor()
{
return _highlightingColor;
}
void QHexEditPrivate::setSelectionColor(const QColor &color)
{
_selectionColor = color;
update();
}
QColor QHexEditPrivate::selectionColor()
{
return _selectionColor;
}
void QHexEditPrivate::setReadOnly(bool readOnly)
{
_readOnly = readOnly;
}
bool QHexEditPrivate::isReadOnly()
{
return _readOnly;
}
XByteArray & QHexEditPrivate::xData()
{
return _xData;
}
int QHexEditPrivate::indexOf(const QByteArray & ba, int from)
{
if (from > (_xData.data().length() - 1))
from = _xData.data().length() - 1;
int idx = _xData.data().indexOf(ba, from);
if (idx > -1)
{
int curPos = idx*2;
setCursorPos(curPos + ba.length()*2);
resetSelection(curPos);
setSelection(curPos + ba.length()*2);
ensureVisible();
}
return idx;
}
void QHexEditPrivate::insert(int index, const QByteArray & ba)
{
if (ba.length() > 0)
{
if (_overwriteMode)
{
QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
_undoStack->push(arrayCommand);
emit dataChanged();
}
else
{
QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::insert, index, ba, ba.length());
_undoStack->push(arrayCommand);
emit dataChanged();
}
}
}
void QHexEditPrivate::insert(int index, char ch)
{
QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::insert, index, ch);
_undoStack->push(charCommand);
emit dataChanged();
}
int QHexEditPrivate::lastIndexOf(const QByteArray & ba, int from)
{
from -= ba.length();
if (from < 0)
from = 0;
int idx = _xData.data().lastIndexOf(ba, from);
if (idx > -1)
{
int curPos = idx*2;
setCursorPos(curPos);
resetSelection(curPos);
setSelection(curPos + ba.length()*2);
ensureVisible();
}
return idx;
}
void QHexEditPrivate::remove(int index, int len)
{
if (len > 0)
{
if (len == 1)
{
if (_overwriteMode)
{
QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, char(0));
_undoStack->push(charCommand);
emit dataChanged();
}
else
{
QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::remove, index, char(0));
_undoStack->push(charCommand);
emit dataChanged();
}
}
else
{
QByteArray ba = QByteArray(len, char(0));
if (_overwriteMode)
{
QUndoCommand *arrayCommand = new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
_undoStack->push(arrayCommand);
emit dataChanged();
}
else
{
QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::remove, index, ba, len);
_undoStack->push(arrayCommand);
emit dataChanged();
}
}
}
}
void QHexEditPrivate::replace(int index, char ch)
{
QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, ch);
_undoStack->push(charCommand);
resetSelection();
emit dataChanged();
}
void QHexEditPrivate::replace(int index, const QByteArray & ba)
{
QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
_undoStack->push(arrayCommand);
resetSelection();
emit dataChanged();
}
void QHexEditPrivate::replace(int pos, int len, const QByteArray &after)
{
QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, pos, after, len);
_undoStack->push(arrayCommand);
resetSelection();
emit dataChanged();
}
void QHexEditPrivate::setAddressArea(bool addressArea)
{
_addressArea = addressArea;
adjust();
setCursorPos(_cursorPosition);
}
void QHexEditPrivate::setAddressWidth(int addressWidth)
{
_xData.setAddressWidth(addressWidth);
setCursorPos(_cursorPosition);
}
void QHexEditPrivate::setAsciiArea(bool asciiArea)
{
_asciiArea = asciiArea;
adjust();
}
void QHexEditPrivate::setFont(const QFont &font)
{
QWidget::setFont(font);
adjust();
}
void QHexEditPrivate::setHighlighting(bool mode)
{
_highlighting = mode;
update();
}
void QHexEditPrivate::setOverwriteMode(bool overwriteMode)
{
_overwriteMode = overwriteMode;
}
bool QHexEditPrivate::overwriteMode()
{
return _overwriteMode;
}
void QHexEditPrivate::redo()
{
_undoStack->redo();
emit dataChanged();
setCursorPos(_cursorPosition);
update();
}
void QHexEditPrivate::undo()
{
_undoStack->undo();
emit dataChanged();
setCursorPos(_cursorPosition);
update();
}
QString QHexEditPrivate::toRedableString()
{
return _xData.toRedableString();
}
QString QHexEditPrivate::selectionToReadableString()
{
return _xData.toRedableString(getSelectionBegin(), getSelectionEnd());
}
void QHexEditPrivate::keyPressEvent(QKeyEvent *event)
{
int charX = (_cursorX - _xPosHex) / _charWidth;
int posX = (charX / 3) * 2 + (charX % 3);
int posBa = (_cursorY / _charHeight) * BYTES_PER_LINE + posX / 2;
/*****************************************************************************/
/* Cursor movements */
/*****************************************************************************/
if (event->matches(QKeySequence::MoveToNextChar))
{
setCursorPos(_cursorPosition + 1);
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToPreviousChar))
{
setCursorPos(_cursorPosition - 1);
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToEndOfLine))
{
setCursorPos(_cursorPosition | (2 * BYTES_PER_LINE -1));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToStartOfLine))
{
setCursorPos(_cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToPreviousLine))
{
setCursorPos(_cursorPosition - (2 * BYTES_PER_LINE));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToNextLine))
{
setCursorPos(_cursorPosition + (2 * BYTES_PER_LINE));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToNextPage))
{
setCursorPos(_cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToPreviousPage))
{
setCursorPos(_cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToEndOfDocument))
{
setCursorPos(_xData.size() * 2);
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToStartOfDocument))
{
setCursorPos(0);
resetSelection(_cursorPosition);
}
/*****************************************************************************/
/* Select commands */
/*****************************************************************************/
if (event->matches(QKeySequence::SelectAll))
{
resetSelection(0);
setSelection(2*_xData.size() + 1);
}
if (event->matches(QKeySequence::SelectNextChar))
{
int pos = _cursorPosition + 1;
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectPreviousChar))
{
int pos = _cursorPosition - 1;
setSelection(pos);
setCursorPos(pos);
}
if (event->matches(QKeySequence::SelectEndOfLine))
{
int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectStartOfLine))
{
int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE));
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectPreviousLine))
{
int pos = _cursorPosition - (2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectNextLine))
{
int pos = _cursorPosition + (2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectNextPage))
{
int pos = _cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectPreviousPage))
{
int pos = _cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectEndOfDocument))
{
int pos = _xData.size() * 2;
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectStartOfDocument))
{
int pos = 0;
setCursorPos(pos);
setSelection(pos);
}
/*****************************************************************************/
/* Edit Commands */
/*****************************************************************************/
if (!_readOnly)
{
/* Hex input */
int key = int(event->text()[0].toLatin1());
if ((key>='0' && key<='9') || (key>='a' && key <= 'f'))
{
if (getSelectionBegin() != getSelectionEnd())
{
posBa = getSelectionBegin();
remove(posBa, getSelectionEnd() - posBa);
setCursorPos(2*posBa);
resetSelection(2*posBa);
}
// If insert mode, then insert a byte
if (_overwriteMode == false)
if ((charX % 3) == 0)
{
insert(posBa, char(0));
}
// Change content
if (_xData.size() > 0)
{
QByteArray hexValue = _xData.data().mid(posBa, 1).toHex();
if ((charX % 3) == 0)
hexValue[0] = key;
else
hexValue[1] = key;
replace(posBa, QByteArray().fromHex(hexValue)[0]);
setCursorPos(_cursorPosition + 1);
resetSelection(_cursorPosition);
}
}
/* Cut & Paste */
if (event->matches(QKeySequence::Cut))
{
QString result = QString();
for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++)
{
result += _xData.data().mid(idx, 1).toHex() + " ";
if ((idx % 16) == 15)
result.append("\n");
}
remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin());
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(result);
setCursorPos(getSelectionBegin());
resetSelection(getSelectionBegin());
}
if (event->matches(QKeySequence::Paste))
{
QClipboard *clipboard = QApplication::clipboard();
QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1());
insert(_cursorPosition / 2, ba);
setCursorPos(_cursorPosition + 2 * ba.length());
resetSelection(getSelectionBegin());
}
/* Delete char */
if (event->matches(QKeySequence::Delete))
{
if (getSelectionBegin() != getSelectionEnd())
{
posBa = getSelectionBegin();
remove(posBa, getSelectionEnd() - posBa);
setCursorPos(2*posBa);
resetSelection(2*posBa);
}
else
{
if (_overwriteMode)
replace(posBa, char(0));
else
remove(posBa, 1);
}
}
/* Backspace */
if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier))
{
if (getSelectionBegin() != getSelectionEnd())
{
posBa = getSelectionBegin();
remove(posBa, getSelectionEnd() - posBa);
setCursorPos(2*posBa);
resetSelection(2*posBa);
}
else
{
if (posBa > 0)
{
if (_overwriteMode)
replace(posBa - 1, char(0));
else
remove(posBa - 1, 1);
setCursorPos(_cursorPosition - 2);
}
}
}
/* undo */
if (event->matches(QKeySequence::Undo))
{
undo();
}
/* redo */
if (event->matches(QKeySequence::Redo))
{
redo();
}
}
if (event->matches(QKeySequence::Copy))
{
QString result = QString();
for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++)
{
result += _xData.data().mid(idx, 1).toHex() + " ";
if ((idx % 16) == 15)
result.append('\n');
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(result);
}
// Switch between insert/overwrite mode
if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier))
{
_overwriteMode = !_overwriteMode;
setCursorPos(_cursorPosition);
overwriteModeChanged(_overwriteMode);
}
ensureVisible();
update();
}
void QHexEditPrivate::mouseMoveEvent(QMouseEvent * event)
{
_blink = false;
update();
int actPos = cursorPos(event->pos());
setCursorPos(actPos);
setSelection(actPos);
}
void QHexEditPrivate::mousePressEvent(QMouseEvent * event)
{
_blink = false;
update();
int cPos = cursorPos(event->pos());
resetSelection(cPos);
setCursorPos(cPos);
}
void QHexEditPrivate::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
// draw some patterns if needed
painter.fillRect(event->rect(), this->palette().color(QPalette::Base));
if (_addressArea)
painter.fillRect(QRect(_xPosAdr, event->rect().top(), _xPosHex - GAP_ADR_HEX + 2, height()), _addressAreaColor);
if (_asciiArea)
{
int linePos = _xPosAscii - (GAP_HEX_ASCII / 2);
painter.setPen(Qt::gray);
painter.drawLine(linePos, event->rect().top(), linePos, height());
}
painter.setPen(this->palette().color(QPalette::WindowText));
// calc position
int firstLineIdx = ((event->rect().top()/ _charHeight) - _charHeight) * BYTES_PER_LINE;
if (firstLineIdx < 0)
firstLineIdx = 0;
int lastLineIdx = ((event->rect().bottom() / _charHeight) + _charHeight) * BYTES_PER_LINE;
if (lastLineIdx > _xData.size())
lastLineIdx = _xData.size();
int yPosStart = ((firstLineIdx) / BYTES_PER_LINE) * _charHeight + _charHeight;
// paint address area
if (_addressArea)
{
for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
{
QString address = QString("%1")
.arg(lineIdx + _xData.addressOffset(), _xData.realAddressNumbers(), 16, QChar('0'));
painter.drawText(_xPosAdr, yPos, address);
}
}
// paint hex area
QByteArray hexBa(_xData.data().mid(firstLineIdx, lastLineIdx - firstLineIdx + 1).toHex());
QBrush highLighted = QBrush(_highlightingColor);
QPen colHighlighted = QPen(this->palette().color(QPalette::WindowText));
QBrush selected = QBrush(_selectionColor);
QPen colSelected = QPen(Qt::white);
QPen colStandard = QPen(this->palette().color(QPalette::WindowText));
painter.setBackgroundMode(Qt::TransparentMode);
for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
{
QByteArray hex;
int xPos = _xPosHex;
for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() && (colIdx < BYTES_PER_LINE)); colIdx++)
{
int posBa = lineIdx + colIdx;
if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa))
{
painter.setBackground(selected);
painter.setBackgroundMode(Qt::OpaqueMode);
painter.setPen(colSelected);
}
else
{
if (_highlighting)
{
// hilight diff bytes
painter.setBackground(highLighted);
if (_xData.dataChanged(posBa))
{
painter.setPen(colHighlighted);
painter.setBackgroundMode(Qt::OpaqueMode);
}
else
{
painter.setPen(colStandard);
painter.setBackgroundMode(Qt::TransparentMode);
}
}
}
// render hex value
if (colIdx == 0)
{
hex = hexBa.mid((lineIdx - firstLineIdx) * 2, 2);
painter.drawText(xPos, yPos, hex);
xPos += 2 * _charWidth;
} else {
hex = hexBa.mid((lineIdx + colIdx - firstLineIdx) * 2, 2).prepend(" ");
painter.drawText(xPos, yPos, hex);
xPos += 3 * _charWidth;
}
}
}
painter.setBackgroundMode(Qt::TransparentMode);
painter.setPen(this->palette().color(QPalette::WindowText));
// paint ascii area
if (_asciiArea)
{
for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
{
int xPosAscii = _xPosAscii;
for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() && (colIdx < BYTES_PER_LINE)); colIdx++)
{
painter.drawText(xPosAscii, yPos, _xData.asciiChar(lineIdx + colIdx));
xPosAscii += _charWidth;
}
}
}
// paint cursor
if (_blink && !_readOnly && hasFocus())
{
if (_overwriteMode)
painter.fillRect(_cursorX, _cursorY + _charHeight - 2, _charWidth, 2, this->palette().color(QPalette::WindowText));
else
painter.fillRect(_cursorX, _cursorY, 2, _charHeight, this->palette().color(QPalette::WindowText));
}
if (_size != _xData.size())
{
_size = _xData.size();
emit currentSizeChanged(_size);
}
}
void QHexEditPrivate::setCursorPos(int position)
{
// delete cursor
_blink = false;
update();
// cursor in range?
if (_overwriteMode)
{
if (position > (_xData.size() * 2 - 1))
position = _xData.size() * 2 - 1;
} else {
if (position > (_xData.size() * 2))
position = _xData.size() * 2;
}
if (position < 0)
position = 0;
// calc position
_cursorPosition = position;
_cursorY = (position / (2 * BYTES_PER_LINE)) * _charHeight + 4;
int x = (position % (2 * BYTES_PER_LINE));
_cursorX = (((x / 2) * 3) + (x % 2)) * _charWidth + _xPosHex;
// immiadately draw cursor
_blink = true;
update();
emit currentAddressChanged(_cursorPosition/2);
}
int QHexEditPrivate::cursorPos(QPoint pos)
{
int result = -1;
// find char under cursor
if ((pos.x() >= _xPosHex) && (pos.x() < (_xPosHex + HEXCHARS_IN_LINE * _charWidth)))
{
int x = (pos.x() - _xPosHex) / _charWidth;
if ((x % 3) == 0)
x = (x / 3) * 2;
else
x = ((x / 3) * 2) + 1;
int y = ((pos.y() - 3) / _charHeight) * 2 * BYTES_PER_LINE;
result = x + y;
}
return result;
}
int QHexEditPrivate::cursorPos()
{
return _cursorPosition;
}
void QHexEditPrivate::resetSelection()
{
_selectionBegin = _selectionInit;
_selectionEnd = _selectionInit;
}
void QHexEditPrivate::resetSelection(int pos)
{
if (pos < 0)
pos = 0;
pos = pos / 2;
_selectionInit = pos;
_selectionBegin = pos;
_selectionEnd = pos;
}
void QHexEditPrivate::setSelection(int pos)
{
if (pos < 0)
pos = 0;
pos = pos / 2;
if (pos >= _selectionInit)
{
_selectionEnd = pos;
_selectionBegin = _selectionInit;
}
else
{
_selectionBegin = pos;
_selectionEnd = _selectionInit;
}
}
int QHexEditPrivate::getSelectionBegin()
{
return _selectionBegin;
}
int QHexEditPrivate::getSelectionEnd()
{
return _selectionEnd;
}
void QHexEditPrivate::updateCursor()
{
if (_blink)
_blink = false;
else
_blink = true;
update(_cursorX, _cursorY, _charWidth, _charHeight);
}
void QHexEditPrivate::adjust()
{
_charWidth = fontMetrics().width(QLatin1Char('9'));
_charHeight = fontMetrics().height();
_xPosAdr = 0;
if (_addressArea)
_xPosHex = _xData.realAddressNumbers()*_charWidth + GAP_ADR_HEX;
else
_xPosHex = 0;
_xPosAscii = _xPosHex + HEXCHARS_IN_LINE * _charWidth + GAP_HEX_ASCII;
// tell QAbstractScollbar, how big we are
setMinimumHeight(((_xData.size()/16 + 1) * _charHeight) + 5);
if(_asciiArea)
setMinimumWidth(_xPosAscii + (BYTES_PER_LINE * _charWidth));
else
setMinimumWidth(_xPosHex + HEXCHARS_IN_LINE * _charWidth);
update();
}
void QHexEditPrivate::ensureVisible()
{
// scrolls to cursorx, cusory (which are set by setCursorPos)
// x-margin is 3 pixels, y-margin is half of charHeight
_scrollArea->ensureVisible(_cursorX, _cursorY + _charHeight/2, 3, _charHeight/2 + 2);
}