1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "bineditor.h"
36 #include <texteditor/fontsettings.h>
37 #include <texteditor/texteditorconstants.h>
38 #include <coreplugin/editormanager/ieditor.h>
40 #include <QtCore/QByteArrayMatcher>
41 #include <QtCore/QDebug>
42 #include <QtCore/QFile>
43 #include <QtCore/QTemporaryFile>
44 #include <QtCore/QVariant>
46 #include <QtGui/QApplication>
47 #include <QtGui/QAction>
48 #include <QtGui/QClipboard>
49 #include <QtGui/QFontMetrics>
50 #include <QtGui/QHelpEvent>
51 #include <QtGui/QMenu>
52 #include <QtGui/QMessageBox>
53 #include <QtGui/QPainter>
54 #include <QtGui/QScrollBar>
55 #include <QtGui/QToolTip>
56 #include <QtGui/QWheelEvent>
58 // QByteArray::toLower() is broken, it stops at the first \0
59 static void lower(QByteArray &ba)
61 char *data = ba.data();
62 char *end = data + ba.size();
64 if (*data >= 0x41 && *data <= 0x5A)
70 static QByteArray calculateHexPattern(const QByteArray &pattern)
73 if (pattern.size() % 2 == 0) {
76 while (i < pattern.size()) {
77 ushort s = pattern.mid(i, 2).toUShort(&ok, 16);
90 BinEditor::BinEditor(QWidget *parent)
91 : QAbstractScrollArea(parent)
99 m_unmodifiedState = 0;
102 m_cursorPosition = 0;
103 m_anchorPosition = 0;
105 m_cursorVisible = false;
106 m_caseSensitiveSearch = false;
107 m_canRequestNewWindow = false;
108 setFocusPolicy(Qt::WheelFocus);
111 BinEditor::~BinEditor()
115 void BinEditor::init()
117 const int addressStringWidth =
118 2*m_addressBytes + (m_addressBytes - 1) / 2;
119 m_addressString = QString(addressStringWidth, QLatin1Char(':'));
120 QFontMetrics fm(fontMetrics());
122 m_descent = fm.descent();
123 m_ascent = fm.ascent();
124 m_lineHeight = fm.lineSpacing();
125 m_charWidth = fm.width(QChar(QLatin1Char('M')));
126 m_columnWidth = 2 * m_charWidth + fm.width(QChar(QLatin1Char(' ')));
127 m_numLines = m_size / 16 + 1;
128 m_numVisibleLines = viewport()->height() / m_lineHeight;
129 m_textWidth = 16 * m_charWidth + m_charWidth;
130 int m_numberWidth = fm.width(QChar(QLatin1Char('9')));
132 2*m_addressBytes * m_numberWidth + (m_addressBytes - 1)/2 * m_charWidth;
134 int expectedCharWidth = m_columnWidth / 3;
135 const char *hex = "0123456789abcdef";
136 m_isMonospacedFont = true;
138 if (fm.width(QLatin1Char(*hex)) != expectedCharWidth) {
139 m_isMonospacedFont = false;
145 if (m_isMonospacedFont && fm.width("M M ") != m_charWidth * 4) {
146 // On Qt/Mac, monospace font widths may have a fractional component
147 // This breaks the assumption that width("MMM") == width('M') * 3
149 m_isMonospacedFont = false;
150 m_columnWidth = fm.width("MMM");
151 m_labelWidth = m_addressBytes == 4
152 ? fm.width("MMMM:MMMM")
153 : fm.width("MMMM:MMMM:MMMM:MMMM");
156 horizontalScrollBar()->setRange(0, 2 * m_margin + 16 * m_columnWidth
157 + m_labelWidth + m_textWidth - viewport()->width());
158 horizontalScrollBar()->setPageStep(viewport()->width());
159 verticalScrollBar()->setRange(0, m_numLines - m_numVisibleLines);
160 verticalScrollBar()->setPageStep(m_numVisibleLines);
161 ensureCursorVisible();
165 void BinEditor::addData(quint64 block, const QByteArray &data)
167 Q_ASSERT(data.size() == m_blockSize);
168 const quint64 addr = block * m_blockSize;
169 if (addr >= m_baseAddr && addr <= m_baseAddr + m_size - 1) {
170 if (m_data.size() * m_blockSize >= 64 * 1024 * 1024)
172 const int translatedBlock = (addr - m_baseAddr) / m_blockSize;
173 m_data.insert(translatedBlock, data);
174 m_requests.remove(translatedBlock);
175 viewport()->update();
179 bool BinEditor::requestDataAt(int pos) const
181 int block = pos / m_blockSize;
182 BlockMap::const_iterator it = m_modifiedData.find(block);
183 if (it != m_modifiedData.constEnd())
185 it = m_data.find(block);
186 if (it != m_data.end())
188 if (!m_requests.contains(block)) {
189 m_requests.insert(block);
190 emit const_cast<BinEditor*>(this)->
191 dataRequested(editor(), m_baseAddr / m_blockSize + block);
197 bool BinEditor::requestOldDataAt(int pos) const
199 int block = pos / m_blockSize;
200 BlockMap::const_iterator it = m_oldData.find(block);
201 return it != m_oldData.end();
204 char BinEditor::dataAt(int pos, bool old) const
206 int block = pos / m_blockSize;
207 return blockData(block, old).at(pos - block*m_blockSize);
210 void BinEditor::changeDataAt(int pos, char c)
212 int block = pos / m_blockSize;
213 BlockMap::iterator it = m_modifiedData.find(block);
214 if (it != m_modifiedData.end()) {
215 it.value()[pos - (block*m_blockSize)] = c;
217 it = m_data.find(block);
218 if (it != m_data.end()) {
219 QByteArray data = it.value();
220 data[pos - (block*m_blockSize)] = c;
221 m_modifiedData.insert(block, data);
225 emit dataChanged(editor(), m_baseAddr + pos, QByteArray(1, c));
228 QByteArray BinEditor::dataMid(int from, int length, bool old) const
230 int end = from + length;
231 int block = from / m_blockSize;
234 data.reserve(length);
236 data += blockData(block++, old);
237 } while (block * m_blockSize < end);
239 return data.mid(from - ((from / m_blockSize) * m_blockSize), length);
242 QByteArray BinEditor::blockData(int block, bool old) const
245 BlockMap::const_iterator it = m_modifiedData.find(block);
246 return it != m_modifiedData.constEnd()
247 ? it.value() : m_oldData.value(block, m_emptyBlock);
249 BlockMap::const_iterator it = m_modifiedData.find(block);
250 return it != m_modifiedData.constEnd()
251 ? it.value() : m_data.value(block, m_emptyBlock);
254 void BinEditor::setFontSettings(const TextEditor::FontSettings &fs)
256 setFont(fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TEXT)).font());
259 void BinEditor::setBlinkingCursorEnabled(bool enable)
261 if (enable && QApplication::cursorFlashTime() > 0)
262 m_cursorBlinkTimer.start(QApplication::cursorFlashTime() / 2, this);
264 m_cursorBlinkTimer.stop();
265 m_cursorVisible = enable;
269 void BinEditor::focusInEvent(QFocusEvent *)
271 setBlinkingCursorEnabled(true);
274 void BinEditor::focusOutEvent(QFocusEvent *)
276 setBlinkingCursorEnabled(false);
279 void BinEditor::timerEvent(QTimerEvent *e)
281 if (e->timerId() == m_autoScrollTimer.timerId()) {
282 QRect visible = viewport()->rect();
284 const QPoint globalPos = QCursor::pos();
285 pos = viewport()->mapFromGlobal(globalPos);
286 QMouseEvent ev(QEvent::MouseMove, pos, globalPos,
287 Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
289 int deltaY = qMax(pos.y() - visible.top(),
290 visible.bottom() - pos.y()) - visible.height();
291 int deltaX = qMax(pos.x() - visible.left(),
292 visible.right() - pos.x()) - visible.width();
293 int delta = qMax(deltaX, deltaY);
297 int timeout = 4900 / (delta * delta);
298 m_autoScrollTimer.start(timeout, this);
301 verticalScrollBar()->triggerAction(pos.y() < visible.center().y() ?
302 QAbstractSlider::SliderSingleStepSub
303 : QAbstractSlider::SliderSingleStepAdd);
305 horizontalScrollBar()->triggerAction(pos.x() < visible.center().x() ?
306 QAbstractSlider::SliderSingleStepSub
307 : QAbstractSlider::SliderSingleStepAdd);
309 } else if (e->timerId() == m_cursorBlinkTimer.timerId()) {
310 m_cursorVisible = !m_cursorVisible;
313 QAbstractScrollArea::timerEvent(e);
317 void BinEditor::setModified(bool modified)
319 int unmodifiedState = modified ? -1 : m_undoStack.size();
320 if (unmodifiedState == m_unmodifiedState)
322 m_unmodifiedState = unmodifiedState;
323 emit modificationChanged(m_undoStack.size() != m_unmodifiedState);
326 bool BinEditor::isModified() const
328 return (m_undoStack.size() != m_unmodifiedState);
331 void BinEditor::setReadOnly(bool readOnly)
333 m_readOnly = readOnly;
336 bool BinEditor::isReadOnly() const
341 bool BinEditor::save(const QString &oldFileName, const QString &newFileName)
343 if (oldFileName != newFileName) {
346 QTemporaryFile tmp(newFileName + QLatin1String("_XXXXXX.new"));
349 tmpName = tmp.fileName();
351 if (!QFile::copy(oldFileName, tmpName))
353 if (QFile::exists(newFileName) && !QFile::remove(newFileName))
355 if (!QFile::rename(tmpName, newFileName))
358 QFile output(newFileName);
359 if (!output.open(QIODevice::ReadWrite)) // QtBug: WriteOnly truncates.
361 const qint64 size = output.size();
362 for (BlockMap::const_iterator it = m_modifiedData.constBegin();
363 it != m_modifiedData.constEnd(); ++it) {
364 if (!output.seek(it.key() * m_blockSize))
366 if (output.write(it.value()) < m_blockSize)
370 // We may have padded the displayed data, so we have to make sure
371 // changes to that area are not actually written back to disk.
372 if (!output.resize(size))
379 void BinEditor::setSizes(quint64 startAddr, int range, int blockSize)
381 m_blockSize = blockSize;
382 Q_ASSERT((blockSize/16) * 16 == blockSize);
383 m_emptyBlock = QByteArray(blockSize, '\0');
384 m_modifiedData.clear();
387 // Users can edit data in the range
388 // [startAddr - range/2, startAddr + range/2].
389 m_baseAddr = quint64(range/2) > startAddr ? 0 : startAddr - range/2;
390 m_baseAddr = (m_baseAddr / blockSize) * blockSize;
392 const quint64 maxRange = Q_UINT64_C(0xffffffffffffffff) - m_baseAddr + 1;
393 m_size = m_baseAddr != 0 && quint64(range) >= maxRange
395 m_addressBytes = (m_baseAddr + m_size < quint64(1) << 32
396 && m_baseAddr + m_size >= m_baseAddr) ? 4 : 8;
398 m_unmodifiedState = 0;
404 setCursorPosition(startAddr - m_baseAddr);
405 viewport()->update();
408 void BinEditor::resizeEvent(QResizeEvent *)
413 void BinEditor::scrollContentsBy(int dx, int dy)
415 viewport()->scroll(isRightToLeft() ? -dx : dx, dy * m_lineHeight);
416 const QScrollBar * const scrollBar = verticalScrollBar();
417 const int scrollPos = scrollBar->value();
418 if (dy <= 0 && scrollPos == scrollBar->maximum())
419 emit newRangeRequested(editor(), baseAddress() + m_size);
420 else if (dy >= 0 && scrollPos == scrollBar->minimum())
421 emit newRangeRequested(editor(), baseAddress());
424 void BinEditor::changeEvent(QEvent *e)
426 QAbstractScrollArea::changeEvent(e);
427 if (e->type() == QEvent::ActivationChange) {
428 if (!isActiveWindow())
429 m_autoScrollTimer.stop();
432 viewport()->update();
436 void BinEditor::wheelEvent(QWheelEvent *e)
438 if (e->modifiers() & Qt::ControlModifier) {
439 const int delta = e->delta();
446 QAbstractScrollArea::wheelEvent(e);
451 QRect BinEditor::cursorRect() const
453 int topLine = verticalScrollBar()->value();
454 int line = m_cursorPosition / 16;
455 int y = (line - topLine) * m_lineHeight;
456 int xoffset = horizontalScrollBar()->value();
457 int column = m_cursorPosition % 16;
459 ? (-xoffset + m_margin + m_labelWidth + column * m_columnWidth)
460 : (-xoffset + m_margin + m_labelWidth + 16 * m_columnWidth
461 + m_charWidth + column * m_charWidth);
462 int w = m_hexCursor ? m_columnWidth : m_charWidth;
463 return QRect(x, y, w, m_lineHeight);
466 int BinEditor::posAt(const QPoint &pos) const
468 int xoffset = horizontalScrollBar()->value();
469 int x = xoffset + pos.x() - m_margin - m_labelWidth;
470 int column = qMin(15, qMax(0,x) / m_columnWidth);
471 int topLine = verticalScrollBar()->value();
472 int line = pos.y() / m_lineHeight;
475 if (x > 16 * m_columnWidth + m_charWidth/2) {
476 x -= 16 * m_columnWidth + m_charWidth;
477 for (column = 0; column < 15; ++column) {
478 int dataPos = (topLine + line) * 16 + column;
479 if (dataPos < 0 || dataPos >= m_size)
481 QChar qc(QLatin1Char(dataAt(dataPos)));
484 x -= fontMetrics().width(qc);
490 return (qMin(m_size, qMin(m_numLines, topLine + line) * 16) + column);
493 bool BinEditor::inTextArea(const QPoint &pos) const
495 int xoffset = horizontalScrollBar()->value();
496 int x = xoffset + pos.x() - m_margin - m_labelWidth;
497 return (x > 16 * m_columnWidth + m_charWidth/2);
500 void BinEditor::updateLines()
502 updateLines(m_cursorPosition, m_cursorPosition);
505 void BinEditor::updateLines(int fromPosition, int toPosition)
507 int topLine = verticalScrollBar()->value();
508 int firstLine = qMin(fromPosition, toPosition) / 16;
509 int lastLine = qMax(fromPosition, toPosition) / 16;
510 int y = (firstLine - topLine) * m_lineHeight;
511 int h = (lastLine - firstLine + 1 ) * m_lineHeight;
513 viewport()->update(0, y, viewport()->width(), h);
516 int BinEditor::dataIndexOf(const QByteArray &pattern, int from, bool caseSensitive) const
518 int trailing = pattern.size();
519 if (trailing > m_blockSize)
523 buffer.resize(m_blockSize + trailing);
524 char *b = buffer.data();
525 QByteArrayMatcher matcher(pattern);
527 int block = from / m_blockSize;
529 qMin<qint64>(static_cast<qint64>(from) + SearchStride, m_size);
531 if (!requestDataAt(block * m_blockSize))
533 QByteArray data = blockData(block);
534 ::memcpy(b, b + m_blockSize, trailing);
535 ::memcpy(b + trailing, data.constData(), m_blockSize);
540 int pos = matcher.indexIn(buffer, from - (block * m_blockSize) + trailing);
542 return pos + block * m_blockSize - trailing;
544 from = block * m_blockSize - trailing;
546 return end == m_size ? -1 : -2;
549 int BinEditor::dataLastIndexOf(const QByteArray &pattern, int from, bool caseSensitive) const
551 int trailing = pattern.size();
552 if (trailing > m_blockSize)
556 buffer.resize(m_blockSize + trailing);
557 char *b = buffer.data();
559 int block = from / m_blockSize;
560 const int lowerBound = qMax(0, from - SearchStride);
561 while (from > lowerBound) {
562 if (!requestDataAt(block * m_blockSize))
564 QByteArray data = blockData(block);
565 ::memcpy(b + m_blockSize, b, trailing);
566 ::memcpy(b, data.constData(), m_blockSize);
571 int pos = buffer.lastIndexOf(pattern, from - (block * m_blockSize));
573 return pos + block * m_blockSize;
575 from = block * m_blockSize + (m_blockSize-1) + trailing;
577 return lowerBound == 0 ? -1 : -2;
581 int BinEditor::find(const QByteArray &pattern_arg, int from,
582 QTextDocument::FindFlags findFlags)
584 if (pattern_arg.isEmpty())
587 QByteArray pattern = pattern_arg;
589 bool caseSensitiveSearch = (findFlags & QTextDocument::FindCaseSensitively);
591 if (!caseSensitiveSearch)
594 bool backwards = (findFlags & QTextDocument::FindBackward);
595 int found = backwards ? dataLastIndexOf(pattern, from, caseSensitiveSearch)
596 : dataIndexOf(pattern, from, caseSensitiveSearch);
599 QByteArray hexPattern = calculateHexPattern(pattern_arg);
600 if (!hexPattern.isEmpty()) {
601 foundHex = backwards ? dataLastIndexOf(hexPattern, from)
602 : dataIndexOf(hexPattern, from);
605 int pos = foundHex == -1 || (found >= 0 && (foundHex == -2 || found < foundHex))
612 setCursorPosition(pos);
613 setCursorPosition(pos + (found == pos ? pattern.size() : hexPattern.size()), KeepAnchor);
618 int BinEditor::findPattern(const QByteArray &data, const QByteArray &dataHex,
619 int from, int offset, int *match)
621 if (m_searchPattern.isEmpty())
623 int normal = m_searchPattern.isEmpty()
624 ? -1 : data.indexOf(m_searchPattern, from - offset);
625 int hex = m_searchPatternHex.isEmpty()
626 ? -1 : dataHex.indexOf(m_searchPatternHex, from - offset);
628 if (normal >= 0 && (hex < 0 || normal < hex)) {
630 *match = m_searchPattern.length();
631 return normal + offset;
635 *match = m_searchPatternHex.length();
643 void BinEditor::drawItems(QPainter *painter, int x, int y, const QString &itemString)
645 if (m_isMonospacedFont) {
646 painter->drawText(x, y, itemString);
648 for (int i = 0; i < 16; ++i)
649 painter->drawText(x + i*m_columnWidth, y, itemString.mid(i*3, 2));
653 void BinEditor::drawChanges(QPainter *painter, int x, int y, const char *changes)
655 const QBrush red(QColor(250, 150, 150));
656 for (int i = 0; i < 16; ++i) {
658 painter->fillRect(x + i*m_columnWidth, y - m_ascent,
659 2*m_charWidth, m_lineHeight, red);
664 QString BinEditor::addressString(quint64 address)
666 QChar *addressStringData = m_addressString.data();
667 const char *hex = "0123456789abcdef";
669 // Take colons into account.
670 const int indices[16] = {
671 0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 15, 16, 17, 18
674 for (int b = 0; b < m_addressBytes; ++b) {
675 addressStringData[indices[2*m_addressBytes - 1 - b*2]] =
676 hex[(address >> (8*b)) & 0xf];
677 addressStringData[indices[2*m_addressBytes - 2 - b*2]] =
678 hex[(address >> (8*b + 4)) & 0xf];
680 return m_addressString;
683 void BinEditor::paintEvent(QPaintEvent *e)
685 QPainter painter(viewport());
686 int topLine = verticalScrollBar()->value();
687 int xoffset = horizontalScrollBar()->value();
688 painter.drawLine(-xoffset + m_margin + m_labelWidth - m_charWidth/2, 0,
689 -xoffset + m_margin + m_labelWidth - m_charWidth/2, viewport()->height());
690 painter.drawLine(-xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth/2, 0,
691 -xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth/2, viewport()->height());
694 int viewport_height = viewport()->height();
695 QBrush alternate_base = palette().alternateBase();
696 for (int i = 0; i < 8; ++i) {
697 int bg_x = -xoffset + m_margin + (2 * i + 1) * m_columnWidth + m_labelWidth;
698 QRect r(bg_x - m_charWidth/2, 0, m_columnWidth, viewport_height);
699 painter.fillRect(e->rect() & r, palette().alternateBase());
704 QByteArray patternData, patternDataHex;
705 int patternOffset = qMax(0, topLine*16 - m_searchPattern.size());
706 if (!m_searchPattern.isEmpty()) {
707 patternData = dataMid(patternOffset, m_numVisibleLines * 16 + (topLine*16 - patternOffset));
708 patternDataHex = patternData;
709 if (!m_caseSensitiveSearch)
710 ::lower(patternData);
714 int foundPatternAt = findPattern(patternData, patternDataHex, patternOffset, patternOffset, &matchLength);
716 int selStart, selEnd;
717 if (m_cursorPosition >= m_anchorPosition) {
718 selStart = m_anchorPosition;
719 selEnd = m_cursorPosition;
721 selStart = m_cursorPosition;
722 selEnd = m_anchorPosition + 1;
725 QString itemString(16*3, QLatin1Char(' '));
726 QChar *itemStringData = itemString.data();
727 char changedString[16] = { false };
728 const char *hex = "0123456789abcdef";
730 painter.setPen(palette().text().color());
731 const QFontMetrics &fm = painter.fontMetrics();
732 for (int i = 0; i <= m_numVisibleLines; ++i) {
733 int line = topLine + i;
734 if (line >= m_numLines)
737 int y = i * m_lineHeight + m_ascent;
738 if (y - m_ascent > e->rect().bottom())
740 if (y + m_descent < e->rect().top())
744 painter.drawText(-xoffset, i * m_lineHeight + m_ascent,
745 addressString(m_baseAddr + uint(line) * 16));
748 if (line * 16 <= m_cursorPosition && m_cursorPosition < line * 16 + 16)
749 cursor = m_cursorPosition - line * 16;
751 bool hasData = requestDataAt(line * 16);
752 bool hasOldData = requestOldDataAt(line * 16);
753 bool isOld = hasOldData && !hasData;
757 if (hasData || hasOldData) {
758 for (int c = 0; c < 16; ++c) {
759 int pos = line * 16 + c;
762 QChar qc(QLatin1Char(dataAt(pos, isOld)));
763 if (qc.unicode() >= 127 || !qc.isPrint())
768 printable = QString(16, QLatin1Char(' '));
772 QRect printableSelectionRect;
774 bool isFullySelected = (selStart < selEnd && selStart <= line*16 && (line+1)*16 <= selEnd);
775 bool somethingChanged = false;
777 if (hasData || hasOldData) {
778 for (int c = 0; c < 16; ++c) {
779 int pos = line * 16 + c;
782 itemStringData[c*3] = itemStringData[c*3+1] = ' ';
788 if (foundPatternAt >= 0 && pos >= foundPatternAt + matchLength)
789 foundPatternAt = findPattern(patternData, patternDataHex, foundPatternAt + matchLength, patternOffset, &matchLength);
792 const uchar value = uchar(dataAt(pos, isOld));
793 itemStringData[c*3] = hex[value >> 4];
794 itemStringData[c*3+1] = hex[value & 0xf];
795 if (hasOldData && !isOld && value != uchar(dataAt(pos, true))) {
796 changedString[c] = true;
797 somethingChanged = true;
800 int item_x = -xoffset + m_margin + c * m_columnWidth + m_labelWidth;
802 if (foundPatternAt >= 0 && pos >= foundPatternAt && pos < foundPatternAt + matchLength) {
803 painter.fillRect(item_x, y-m_ascent, m_columnWidth, m_lineHeight, QColor(0xffef0b));
804 int printable_item_x = -xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth
805 + fm.width(printable.left(c));
806 painter.fillRect(printable_item_x, y-m_ascent,
807 fm.width(printable.at(c)),
808 m_lineHeight, QColor(0xffef0b));
811 if (selStart < selEnd && !isFullySelected && pos >= selStart && pos < selEnd) {
812 selectionRect |= QRect(item_x, y-m_ascent, m_columnWidth, m_lineHeight);
813 int printable_item_x = -xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth
814 + fm.width(printable.left(c));
815 printableSelectionRect |= QRect(printable_item_x, y-m_ascent,
816 fm.width(printable.at(c)),
822 int x = -xoffset + m_margin + m_labelWidth;
823 bool cursorWanted = m_cursorPosition == m_anchorPosition;
825 if (isFullySelected) {
827 painter.fillRect(x, y-m_ascent, 16*m_columnWidth, m_lineHeight, palette().highlight());
828 painter.setPen(palette().highlightedText().color());
829 drawItems(&painter, x, y, itemString);
832 if (somethingChanged)
833 drawChanges(&painter, x, y, changedString);
834 drawItems(&painter, x, y, itemString);
835 if (!selectionRect.isEmpty()) {
837 painter.fillRect(selectionRect, palette().highlight());
838 painter.setPen(palette().highlightedText().color());
839 painter.setClipRect(selectionRect);
840 drawItems(&painter, x, y, itemString);
846 if (cursor >= 0 && cursorWanted) {
847 int w = fm.boundingRect(itemString.mid(cursor*3, 2)).width();
848 QRect cursorRect(x + cursor * m_columnWidth, y - m_ascent, w + 1, m_lineHeight);
850 painter.setPen(Qt::red);
851 painter.drawRect(cursorRect.adjusted(0, 0, 0, -1));
853 if (m_hexCursor && m_cursorVisible) {
855 cursorRect.adjust(fm.width(itemString.left(1)), 0, 0, 0);
856 painter.fillRect(cursorRect, Qt::red);
858 painter.setClipRect(cursorRect);
859 painter.setPen(Qt::white);
860 drawItems(&painter, x, y, itemString);
865 int text_x = -xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth;
867 if (isFullySelected) {
869 painter.fillRect(text_x, y-m_ascent, fm.width(printable), m_lineHeight,
870 palette().highlight());
871 painter.setPen(palette().highlightedText().color());
872 painter.drawText(text_x, y, printable);
875 painter.drawText(text_x, y, printable);
876 if (!printableSelectionRect.isEmpty()) {
878 painter.fillRect(printableSelectionRect, palette().highlight());
879 painter.setPen(palette().highlightedText().color());
880 painter.setClipRect(printableSelectionRect);
881 painter.drawText(text_x, y, printable);
886 if (cursor >= 0 && !printable.isEmpty() && cursorWanted) {
887 QRect cursorRect(text_x + fm.width(printable.left(cursor)),
889 fm.width(printable.at(cursor)),
892 if (m_hexCursor || !m_cursorVisible) {
893 painter.setPen(Qt::red);
894 painter.drawRect(cursorRect.adjusted(0, 0, 0, -1));
896 painter.setClipRect(cursorRect);
897 painter.fillRect(cursorRect, Qt::red);
898 painter.setPen(Qt::white);
899 painter.drawText(text_x, y, printable);
907 int BinEditor::cursorPosition() const
909 return m_cursorPosition;
912 void BinEditor::setCursorPosition(int pos, MoveMode moveMode)
914 pos = qMin(m_size-1, qMax(0, pos));
915 int oldCursorPosition = m_cursorPosition;
917 bool hasSelection = m_anchorPosition != m_cursorPosition;
921 m_cursorPosition = pos;
922 if (moveMode == MoveAnchor) {
924 updateLines(m_anchorPosition, oldCursorPosition);
925 m_anchorPosition = m_cursorPosition;
928 hasSelection = m_anchorPosition != m_cursorPosition;
929 updateLines(hasSelection ? oldCursorPosition : m_cursorPosition, m_cursorPosition);
930 ensureCursorVisible();
931 if (hasSelection != (m_anchorPosition != m_anchorPosition))
932 emit copyAvailable(m_anchorPosition != m_cursorPosition);
933 emit cursorPositionChanged(m_cursorPosition);
937 void BinEditor::ensureCursorVisible()
939 QRect cr = cursorRect();
940 QRect vr = viewport()->rect();
941 if (!vr.contains(cr)) {
942 if (cr.top() < vr.top())
943 verticalScrollBar()->setValue(m_cursorPosition / 16);
944 else if (cr.bottom() > vr.bottom())
945 verticalScrollBar()->setValue(m_cursorPosition / 16 - m_numVisibleLines + 1);
949 void BinEditor::mousePressEvent(QMouseEvent *e)
951 if (e->button() != Qt::LeftButton)
953 setCursorPosition(posAt(e->pos()));
954 setBlinkingCursorEnabled(true);
955 if (m_hexCursor == inTextArea(e->pos())) {
956 m_hexCursor = !m_hexCursor;
961 void BinEditor::mouseMoveEvent(QMouseEvent *e)
963 if (!(e->buttons() & Qt::LeftButton))
965 setCursorPosition(posAt(e->pos()), KeepAnchor);
966 if (m_hexCursor == inTextArea(e->pos())) {
967 m_hexCursor = !m_hexCursor;
970 QRect visible = viewport()->rect();
971 if (visible.contains(e->pos()))
972 m_autoScrollTimer.stop();
973 else if (!m_autoScrollTimer.isActive())
974 m_autoScrollTimer.start(100, this);
977 void BinEditor::mouseReleaseEvent(QMouseEvent *)
979 if (m_autoScrollTimer.isActive()) {
980 m_autoScrollTimer.stop();
981 ensureCursorVisible();
985 void BinEditor::selectAll()
987 setCursorPosition(0);
988 setCursorPosition(m_size-1, KeepAnchor);
991 void BinEditor::clear()
996 m_modifiedData.clear();
1001 m_unmodifiedState = 0;
1002 m_undoStack.clear();
1003 m_redoStack.clear();
1006 m_cursorPosition = 0;
1007 verticalScrollBar()->setValue(0);
1009 emit cursorPositionChanged(m_cursorPosition);
1010 viewport()->update();
1013 bool BinEditor::event(QEvent *e)
1015 if (e->type() == QEvent::KeyPress) {
1016 switch (static_cast<QKeyEvent*>(e)->key()) {
1018 case Qt::Key_Backtab:
1019 m_hexCursor = !m_hexCursor;
1020 setBlinkingCursorEnabled(true);
1021 ensureCursorVisible();
1024 case Qt::Key_Down: {
1025 const QScrollBar * const scrollBar = verticalScrollBar();
1026 if (scrollBar->value() >= scrollBar->maximum() - 1) {
1027 emit newRangeRequested(editor(), baseAddress() + m_size);
1034 } else if (e->type() == QEvent::ToolTip) {
1035 const QHelpEvent * const helpEvent = static_cast<QHelpEvent *>(e);
1037 int selStart = selectionStart();
1038 int selEnd = selectionEnd();
1039 int byteCount = selEnd - selStart;
1040 if (byteCount <= 0) {
1041 selStart = m_cursorPosition;
1042 selStart = posAt(helpEvent->pos());
1043 selEnd = selStart + 1;
1046 if (m_hexCursor && byteCount <= 8) {
1047 const QPoint &startPoint = offsetToPos(selStart);
1048 const QPoint &endPoint = offsetToPos(selEnd);
1049 const QPoint expandedEndPoint
1050 = QPoint(endPoint.x(), endPoint.y() + m_lineHeight);
1051 const QRect selRect(startPoint, expandedEndPoint);
1052 const QPoint &mousePos = helpEvent->pos();
1053 if (selRect.contains(mousePos)) {
1054 quint64 beValue, leValue;
1055 quint64 beValueOld, leValueOld;
1056 asIntegers(selStart, byteCount, beValue, leValue);
1057 asIntegers(selStart, byteCount, beValueOld, leValueOld, true);
1060 QString leSignedOld;
1061 QString beSignedOld;
1062 switch (byteCount) {
1063 case 8: case 7: case 6: case 5:
1064 leSigned = QString::number(static_cast<qint64>(leValue));
1065 beSigned = QString::number(static_cast<qint64>(beValue));
1066 leSignedOld = QString::number(static_cast<qint64>(leValueOld));
1067 beSignedOld = QString::number(static_cast<qint64>(beValueOld));
1070 leSigned = QString::number(static_cast<qint32>(leValue));
1071 beSigned = QString::number(static_cast<qint32>(beValue));
1072 leSignedOld = QString::number(static_cast<qint32>(leValueOld));
1073 beSignedOld = QString::number(static_cast<qint32>(beValueOld));
1076 leSigned = QString::number(static_cast<qint16>(leValue));
1077 beSigned = QString::number(static_cast<qint16>(beValue));
1078 leSignedOld = QString::number(static_cast<qint16>(leValueOld));
1079 beSignedOld = QString::number(static_cast<qint16>(beValueOld));
1082 leSigned = QString::number(static_cast<qint8>(leValue));
1083 beSigned = QString::number(static_cast<qint8>(beValue));
1084 leSignedOld = QString::number(static_cast<qint8>(leValueOld));
1085 beSignedOld = QString::number(static_cast<qint8>(beValueOld));
1089 //int pos = posAt(mousePos);
1090 //uchar old = dataAt(pos, true);
1091 //uchar current = dataAt(pos, false);
1094 tr("Decimal unsigned value (little endian): %1\n"
1095 "Decimal unsigned value (big endian): %2\n"
1096 "Decimal signed value (little endian): %3\n"
1097 "Decimal signed value (big endian): %4")
1098 .arg(QString::number(leValue))
1099 .arg(QString::number(beValue))
1102 if (beValue != beValueOld) {
1103 msg += QLatin1Char('\n');
1104 msg += tr("Previous decimal unsigned value (little endian): %1\n"
1105 "Previous decimal unsigned value (big endian): %2\n"
1106 "Previous decimal signed value (little endian): %3\n"
1107 "Previous decimal signed value (big endian): %4")
1108 .arg(QString::number(leValueOld))
1109 .arg(QString::number(beValueOld))
1113 QToolTip::showText(helpEvent->globalPos(), msg, this);
1117 QToolTip::hideText();
1122 return QAbstractScrollArea::event(e);
1125 void BinEditor::keyPressEvent(QKeyEvent *e)
1128 if (e == QKeySequence::SelectAll) {
1132 } else if (e == QKeySequence::Copy) {
1136 } else if (e == QKeySequence::Undo) {
1140 } else if (e == QKeySequence::Redo) {
1147 MoveMode moveMode = e->modifiers() & Qt::ShiftModifier ? KeepAnchor : MoveAnchor;
1150 setCursorPosition(m_cursorPosition - 16, moveMode);
1153 setCursorPosition(m_cursorPosition + 16, moveMode);
1156 setCursorPosition(m_cursorPosition + 1, moveMode);
1159 setCursorPosition(m_cursorPosition - 1, moveMode);
1161 case Qt::Key_PageUp:
1162 case Qt::Key_PageDown: {
1163 int line = qMax(0, m_cursorPosition / 16 - verticalScrollBar()->value());
1164 verticalScrollBar()->triggerAction(e->key() == Qt::Key_PageUp ?
1165 QScrollBar::SliderPageStepSub : QScrollBar::SliderPageStepAdd);
1166 setCursorPosition((verticalScrollBar()->value() + line) * 16 + m_cursorPosition % 16, moveMode);
1170 if (e->modifiers() & Qt::ControlModifier) {
1171 emit startOfFileRequested(editor());
1173 setCursorPosition(m_cursorPosition/16 * 16, moveMode);
1177 if (e->modifiers() & Qt::ControlModifier) {
1178 emit endOfFileRequested(editor());
1180 setCursorPosition(m_cursorPosition/16 * 16 + 15, moveMode);
1187 QString text = e->text();
1188 for (int i = 0; i < text.length(); ++i) {
1189 QChar c = text.at(i);
1193 if (c.unicode() >= 'a' && c.unicode() <= 'f')
1194 nibble = c.unicode() - 'a' + 10;
1195 else if (c.unicode() >= '0' && c.unicode() <= '9')
1196 nibble = c.unicode() - '0';
1200 changeData(m_cursorPosition, nibble + (dataAt(m_cursorPosition) & 0xf0));
1201 m_lowNibble = false;
1202 setCursorPosition(m_cursorPosition + 1);
1204 changeData(m_cursorPosition, (nibble << 4) + (dataAt(m_cursorPosition) & 0x0f), true);
1209 if (c.unicode() >= 128 || !c.isPrint())
1211 changeData(m_cursorPosition, c.unicode(), m_cursorPosition + 1);
1212 setCursorPosition(m_cursorPosition + 1);
1214 setBlinkingCursorEnabled(true);
1222 void BinEditor::zoomIn(int range)
1225 const int newSize = f.pointSize() + range;
1228 f.setPointSize(newSize);
1232 void BinEditor::zoomOut(int range)
1237 void BinEditor::copy(bool raw)
1239 int selStart = selectionStart();
1240 int selEnd = selectionEnd();
1241 if (selStart >= selEnd)
1242 qSwap(selStart, selEnd);
1244 const int selectionLength = selEnd - selStart;
1245 if (selectionLength >> 22) {
1246 QMessageBox::warning(this, tr("Copying Failed"),
1247 tr("You cannot copy more than 4 MB of binary data."));
1250 const QByteArray &data = dataMid(selStart, selectionLength);
1252 QApplication::clipboard()->setText(data);
1256 const char * const hex = "0123456789abcdef";
1257 hexString.reserve(3 * data.size());
1258 for (int i = 0; i < data.size(); ++i) {
1259 const uchar val = static_cast<uchar>(data[i]);
1260 hexString.append(hex[val >> 4]).append(hex[val & 0xf]).append(' ');
1263 QApplication::clipboard()->setText(hexString);
1266 void BinEditor::highlightSearchResults(const QByteArray &pattern, QTextDocument::FindFlags findFlags)
1268 if (m_searchPattern == pattern)
1270 m_searchPattern = pattern;
1271 m_caseSensitiveSearch = (findFlags & QTextDocument::FindCaseSensitively);
1272 if (!m_caseSensitiveSearch)
1273 ::lower(m_searchPattern);
1274 m_searchPatternHex = calculateHexPattern(pattern);
1275 viewport()->update();
1279 void BinEditor::changeData(int position, uchar character, bool highNibble)
1281 if (!requestDataAt(position))
1283 m_redoStack.clear();
1284 if (m_unmodifiedState > m_undoStack.size())
1285 m_unmodifiedState = -1;
1286 BinEditorEditCommand cmd;
1287 cmd.position = position;
1288 cmd.character = (uchar) dataAt(position);
1289 cmd.highNibble = highNibble;
1292 && !m_undoStack.isEmpty()
1293 && m_undoStack.top().position == position
1294 && m_undoStack.top().highNibble) {
1296 cmd.character = m_undoStack.top().character;
1300 changeDataAt(position, (char) character);
1301 bool emitModificationChanged = (m_undoStack.size() == m_unmodifiedState);
1302 m_undoStack.push(cmd);
1303 if (emitModificationChanged) {
1304 emit modificationChanged(m_undoStack.size() != m_unmodifiedState);
1307 if (m_undoStack.size() == 1)
1308 emit undoAvailable(true);
1312 void BinEditor::undo()
1314 if (m_undoStack.isEmpty())
1316 bool emitModificationChanged = (m_undoStack.size() == m_unmodifiedState);
1317 BinEditorEditCommand cmd = m_undoStack.pop();
1318 emitModificationChanged |= (m_undoStack.size() == m_unmodifiedState);
1319 uchar c = dataAt(cmd.position);
1320 changeDataAt(cmd.position, (char)cmd.character);
1322 m_redoStack.push(cmd);
1323 setCursorPosition(cmd.position);
1324 if (emitModificationChanged)
1325 emit modificationChanged(m_undoStack.size() != m_unmodifiedState);
1326 if (!m_undoStack.size())
1327 emit undoAvailable(false);
1328 if (m_redoStack.size() == 1)
1329 emit redoAvailable(true);
1332 void BinEditor::redo()
1334 if (m_redoStack.isEmpty())
1336 BinEditorEditCommand cmd = m_redoStack.pop();
1337 uchar c = dataAt(cmd.position);
1338 changeDataAt(cmd.position, (char)cmd.character);
1340 bool emitModificationChanged = (m_undoStack.size() == m_unmodifiedState);
1341 m_undoStack.push(cmd);
1342 setCursorPosition(cmd.position + 1);
1343 if (emitModificationChanged)
1344 emit modificationChanged(m_undoStack.size() != m_unmodifiedState);
1345 if (m_undoStack.size() == 1)
1346 emit undoAvailable(true);
1347 if (!m_redoStack.size())
1348 emit redoAvailable(false);
1351 void BinEditor::contextMenuEvent(QContextMenuEvent *event)
1353 const int selStart = selectionStart();
1354 const int byteCount = selectionEnd() - selStart;
1359 QAction copyAsciiAction(tr("Copy Selection as ASCII Characters"), this);
1360 QAction copyHexAction(tr("Copy Selection as Hex Values"), this);
1361 QAction jumpToBeAddressHere(this);
1362 QAction jumpToBeAddressNewWindow(this);
1363 QAction jumpToLeAddressHere(this);
1364 QAction jumpToLeAddressNewWindow(this);
1365 contextMenu.addAction(©AsciiAction);
1366 contextMenu.addAction(©HexAction);
1368 quint64 beAddress = 0;
1369 quint64 leAddress = 0;
1370 if (byteCount <= 8) {
1371 asIntegers(selStart, byteCount, beAddress, leAddress);
1372 setupJumpToMenuAction(&contextMenu, &jumpToBeAddressHere,
1373 &jumpToBeAddressNewWindow, beAddress);
1375 // If the menu entries would be identical, show only one of them.
1376 if (beAddress != leAddress) {
1377 setupJumpToMenuAction(&contextMenu, &jumpToLeAddressHere,
1378 &jumpToLeAddressNewWindow, leAddress);
1381 jumpToBeAddressHere.setText(tr("Jump to Address in This Window"));
1382 jumpToBeAddressNewWindow.setText(tr("Jump to Address in New Window"));
1383 jumpToBeAddressHere.setEnabled(false);
1384 jumpToBeAddressNewWindow.setEnabled(false);
1385 contextMenu.addAction(&jumpToBeAddressHere);
1386 contextMenu.addAction(&jumpToBeAddressNewWindow);
1389 QAction *action = contextMenu.exec(event->globalPos());
1390 if (action == ©AsciiAction)
1392 else if (action == ©HexAction)
1394 else if (action == &jumpToBeAddressHere)
1395 jumpToAddress(beAddress);
1396 else if (action == &jumpToLeAddressHere)
1397 jumpToAddress(leAddress);
1398 else if (action == &jumpToBeAddressNewWindow)
1399 emit newWindowRequested(beAddress);
1400 else if (action == &jumpToLeAddressNewWindow)
1401 emit newWindowRequested(leAddress);
1404 void BinEditor::setupJumpToMenuAction(QMenu *menu, QAction *actionHere,
1405 QAction *actionNew, quint64 addr)
1407 actionHere->setText(tr("Jump to Address 0x%1 in This Window")
1408 .arg(QString::number(addr, 16)));
1409 actionNew->setText(tr("Jump to Address 0x%1 in New Window")
1410 .arg(QString::number(addr, 16)));
1411 menu->addAction(actionHere);
1412 menu->addAction(actionNew);
1413 if (!m_canRequestNewWindow)
1414 actionNew->setEnabled(false);
1417 void BinEditor::jumpToAddress(quint64 address)
1419 if (address >= m_baseAddr && address < m_baseAddr + m_size)
1420 setCursorPosition(address - m_baseAddr);
1422 emit newRangeRequested(editor(), address);
1425 void BinEditor::setNewWindowRequestAllowed()
1427 m_canRequestNewWindow = true;
1430 void BinEditor::updateContents()
1434 setSizes(baseAddress() + cursorPosition(), m_size, m_blockSize);
1437 QPoint BinEditor::offsetToPos(int offset)
1439 const int x = m_labelWidth + (offset % 16) * m_columnWidth;
1440 const int y = (offset / 16 - verticalScrollBar()->value()) * m_lineHeight;
1441 return QPoint(x, y);
1444 void BinEditor::asIntegers(int offset, int count, quint64 &beValue,
1445 quint64 &leValue, bool old)
1447 beValue = leValue = 0;
1448 const QByteArray &data = dataMid(offset, count, old);
1449 for (int pos = 0; pos < data.size(); ++pos) {
1450 const quint64 val = static_cast<quint64>(data.at(pos)) & 0xff;
1451 beValue += val << (pos * 8);
1452 leValue += val << ((count - pos - 1) * 8);
1456 bool BinEditor::isMemoryView() const
1458 return editor()->property("MemoryView").toBool();
1461 } // namespace BINEditor