OSDN Git Service

772fad0adb71ef2df36f8ba0916bde4830b56c3c
[qt-creator-jp/qt-creator-jp.git] / src / plugins / bineditor / bineditor.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "bineditor.h"
35
36 #include <texteditor/fontsettings.h>
37 #include <texteditor/texteditorconstants.h>
38 #include <coreplugin/editormanager/ieditor.h>
39
40 #include <QtCore/QByteArrayMatcher>
41 #include <QtCore/QDebug>
42 #include <QtCore/QFile>
43 #include <QtCore/QTemporaryFile>
44 #include <QtCore/QVariant>
45
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>
57
58 // QByteArray::toLower() is broken, it stops at the first \0
59 static void lower(QByteArray &ba)
60 {
61     char *data = ba.data();
62     char *end = data + ba.size();
63     while (data != end) {
64         if (*data >= 0x41 && *data <= 0x5A)
65             *data += 0x20;
66         ++data;
67     }
68 }
69
70 static QByteArray calculateHexPattern(const QByteArray &pattern)
71 {
72     QByteArray result;
73     if (pattern.size() % 2 == 0) {
74         bool ok = true;
75         int i = 0;
76         while (i < pattern.size()) {
77             ushort s = pattern.mid(i, 2).toUShort(&ok, 16);
78             if (!ok) {
79                 return QByteArray();
80             }
81             result.append(s);
82             i += 2;
83         }
84     }
85     return result;
86 }
87
88 namespace BINEditor {
89
90 BinEditor::BinEditor(QWidget *parent)
91     : QAbstractScrollArea(parent)
92 {
93     m_ieditor = 0;
94     m_baseAddr = 0;
95     m_blockSize = 4096;
96     m_size = 0;
97     m_addressBytes = 4;
98     init();
99     m_unmodifiedState = 0;
100     m_readOnly = false;
101     m_hexCursor = true;
102     m_cursorPosition = 0;
103     m_anchorPosition = 0;
104     m_lowNibble = false;
105     m_cursorVisible = false;
106     m_caseSensitiveSearch = false;
107     m_canRequestNewWindow = false;
108     setFocusPolicy(Qt::WheelFocus);
109 }
110
111 BinEditor::~BinEditor()
112 {
113 }
114
115 void BinEditor::init()
116 {
117     const int addressStringWidth =
118         2*m_addressBytes + (m_addressBytes - 1) / 2;
119     m_addressString = QString(addressStringWidth, QLatin1Char(':'));
120     QFontMetrics fm(fontMetrics());
121     m_margin = 4;
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')));
131     m_labelWidth =
132         2*m_addressBytes * m_numberWidth + (m_addressBytes - 1)/2 * m_charWidth;
133
134     int expectedCharWidth = m_columnWidth / 3;
135     const char *hex = "0123456789abcdef";
136     m_isMonospacedFont = true;
137     while (*hex) {
138         if (fm.width(QLatin1Char(*hex)) != expectedCharWidth) {
139             m_isMonospacedFont = false;
140             break;
141         }
142         ++hex;
143     }
144
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
148
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");
154     }
155
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();
162 }
163
164
165 void BinEditor::addData(quint64 block, const QByteArray &data)
166 {
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)
171             m_data.clear();
172         const int translatedBlock = (addr - m_baseAddr) / m_blockSize;
173         m_data.insert(translatedBlock, data);
174         m_requests.remove(translatedBlock);
175         viewport()->update();
176     }
177 }
178
179 bool BinEditor::requestDataAt(int pos) const
180 {
181     int block = pos / m_blockSize;
182     BlockMap::const_iterator it = m_modifiedData.find(block);
183     if (it != m_modifiedData.constEnd())
184         return true;
185     it = m_data.find(block);
186     if (it != m_data.end())
187         return true;
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);
192         return true;
193     }
194     return false;
195 }
196
197 bool BinEditor::requestOldDataAt(int pos) const
198 {
199     int block = pos / m_blockSize;
200     BlockMap::const_iterator it = m_oldData.find(block);
201     return it != m_oldData.end();
202 }
203
204 char BinEditor::dataAt(int pos, bool old) const
205 {
206     int block = pos / m_blockSize;
207     return blockData(block, old).at(pos - block*m_blockSize);
208 }
209
210 void BinEditor::changeDataAt(int pos, char c)
211 {
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;
216     } else {
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);
222         }
223     }
224
225     emit dataChanged(editor(), m_baseAddr + pos, QByteArray(1, c));
226 }
227
228 QByteArray BinEditor::dataMid(int from, int length, bool old) const
229 {
230     int end = from + length;
231     int block = from / m_blockSize;
232
233     QByteArray data;
234     data.reserve(length);
235     do {
236         data += blockData(block++, old);
237     } while (block * m_blockSize < end);
238
239     return data.mid(from - ((from / m_blockSize) * m_blockSize), length);
240 }
241
242 QByteArray BinEditor::blockData(int block, bool old) const
243 {
244     if (old) {
245         BlockMap::const_iterator it = m_modifiedData.find(block);
246         return it != m_modifiedData.constEnd()
247                 ? it.value() : m_oldData.value(block, m_emptyBlock);
248     }
249     BlockMap::const_iterator it = m_modifiedData.find(block);
250     return it != m_modifiedData.constEnd()
251             ? it.value() : m_data.value(block, m_emptyBlock);
252 }
253
254 void BinEditor::setFontSettings(const TextEditor::FontSettings &fs)
255 {
256     setFont(fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TEXT)).font());
257 }
258
259 void BinEditor::setBlinkingCursorEnabled(bool enable)
260 {
261     if (enable && QApplication::cursorFlashTime() > 0)
262         m_cursorBlinkTimer.start(QApplication::cursorFlashTime() / 2, this);
263     else
264         m_cursorBlinkTimer.stop();
265     m_cursorVisible = enable;
266     updateLines();
267 }
268
269 void BinEditor::focusInEvent(QFocusEvent *)
270 {
271     setBlinkingCursorEnabled(true);
272 }
273
274 void BinEditor::focusOutEvent(QFocusEvent *)
275 {
276     setBlinkingCursorEnabled(false);
277 }
278
279 void BinEditor::timerEvent(QTimerEvent *e)
280 {
281     if (e->timerId() == m_autoScrollTimer.timerId()) {
282         QRect visible = viewport()->rect();
283         QPoint pos;
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);
288         mouseMoveEvent(&ev);
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);
294         if (delta >= 0) {
295             if (delta < 7)
296                 delta = 7;
297             int timeout = 4900 / (delta * delta);
298             m_autoScrollTimer.start(timeout, this);
299
300             if (deltaY > 0)
301                 verticalScrollBar()->triggerAction(pos.y() < visible.center().y() ?
302                                        QAbstractSlider::SliderSingleStepSub
303                                        : QAbstractSlider::SliderSingleStepAdd);
304             if (deltaX > 0)
305                 horizontalScrollBar()->triggerAction(pos.x() < visible.center().x() ?
306                                        QAbstractSlider::SliderSingleStepSub
307                                        : QAbstractSlider::SliderSingleStepAdd);
308         }
309     } else if (e->timerId() == m_cursorBlinkTimer.timerId()) {
310         m_cursorVisible = !m_cursorVisible;
311         updateLines();
312     }
313     QAbstractScrollArea::timerEvent(e);
314 }
315
316
317 void BinEditor::setModified(bool modified)
318 {
319     int unmodifiedState = modified ? -1 : m_undoStack.size();
320     if (unmodifiedState == m_unmodifiedState)
321         return;
322     m_unmodifiedState = unmodifiedState;
323     emit modificationChanged(m_undoStack.size() != m_unmodifiedState);
324 }
325
326 bool BinEditor::isModified() const
327 {
328     return (m_undoStack.size() != m_unmodifiedState);
329 }
330
331 void BinEditor::setReadOnly(bool readOnly)
332 {
333     m_readOnly = readOnly;
334 }
335
336 bool BinEditor::isReadOnly() const
337 {
338     return m_readOnly;
339 }
340
341 bool BinEditor::save(const QString &oldFileName, const QString &newFileName)
342 {
343     if (oldFileName != newFileName) {
344         QString tmpName;
345         {
346             QTemporaryFile tmp(newFileName + QLatin1String("_XXXXXX.new"));
347             if (!tmp.open())
348                 return false;
349             tmpName = tmp.fileName();
350         }
351         if (!QFile::copy(oldFileName, tmpName))
352             return false;
353         if (QFile::exists(newFileName) && !QFile::remove(newFileName))
354             return false;
355         if (!QFile::rename(tmpName, newFileName))
356             return false;
357     }
358     QFile output(newFileName);
359     if (!output.open(QIODevice::ReadWrite)) // QtBug: WriteOnly truncates.
360         return false;
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))
365             return false;
366         if (output.write(it.value()) < m_blockSize)
367             return false;
368     }
369
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))
373         return false;
374
375     setModified(false);
376     return true;
377 }
378
379 void BinEditor::setSizes(quint64 startAddr, int range, int blockSize)
380 {
381     m_blockSize = blockSize;
382     Q_ASSERT((blockSize/16) * 16 == blockSize);
383     m_emptyBlock = QByteArray(blockSize, '\0');
384     m_modifiedData.clear();
385     m_requests.clear();
386
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;
391
392     const quint64 maxRange = Q_UINT64_C(0xffffffffffffffff) - m_baseAddr + 1;
393     m_size = m_baseAddr != 0 && quint64(range) >= maxRange
394               ? maxRange : range;
395     m_addressBytes = (m_baseAddr + m_size < quint64(1) << 32
396                    && m_baseAddr + m_size >= m_baseAddr) ? 4 : 8;
397
398     m_unmodifiedState = 0;
399     m_undoStack.clear();
400     m_redoStack.clear();
401
402     init();
403
404     setCursorPosition(startAddr - m_baseAddr);
405     viewport()->update();
406 }
407
408 void BinEditor::resizeEvent(QResizeEvent *)
409 {
410     init();
411 }
412
413 void BinEditor::scrollContentsBy(int dx, int dy)
414 {
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());
422 }
423
424 void BinEditor::changeEvent(QEvent *e)
425 {
426     QAbstractScrollArea::changeEvent(e);
427     if (e->type() == QEvent::ActivationChange) {
428         if (!isActiveWindow())
429             m_autoScrollTimer.stop();
430     }
431     init();
432     viewport()->update();
433 }
434
435
436 void BinEditor::wheelEvent(QWheelEvent *e)
437 {
438     if (e->modifiers() & Qt::ControlModifier) {
439         const int delta = e->delta();
440         if (delta < 0)
441             zoomOut();
442         else if (delta > 0)
443             zoomIn();
444         return;
445     }
446     QAbstractScrollArea::wheelEvent(e);
447 }
448
449
450
451 QRect BinEditor::cursorRect() const
452 {
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;
458     int x = m_hexCursor
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);
464 }
465
466 int BinEditor::posAt(const QPoint &pos) const
467 {
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;
473
474
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)
480                 break;
481             QChar qc(QLatin1Char(dataAt(dataPos)));
482             if (!qc.isPrint())
483                 qc = 0xB7;
484             x -= fontMetrics().width(qc);
485             if (x <= 0)
486                 break;
487         }
488     }
489
490     return (qMin(m_size, qMin(m_numLines, topLine + line) * 16) + column);
491 }
492
493 bool BinEditor::inTextArea(const QPoint &pos) const
494 {
495     int xoffset = horizontalScrollBar()->value();
496     int x = xoffset + pos.x() - m_margin - m_labelWidth;
497     return (x > 16 * m_columnWidth + m_charWidth/2);
498 }
499
500 void BinEditor::updateLines()
501 {
502     updateLines(m_cursorPosition, m_cursorPosition);
503 }
504
505 void BinEditor::updateLines(int fromPosition, int toPosition)
506 {
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;
512
513     viewport()->update(0, y, viewport()->width(), h);
514 }
515
516 int BinEditor::dataIndexOf(const QByteArray &pattern, int from, bool caseSensitive) const
517 {
518     int trailing = pattern.size();
519     if (trailing > m_blockSize)
520         return -1;
521
522     QByteArray buffer;
523     buffer.resize(m_blockSize + trailing);
524     char *b = buffer.data();
525     QByteArrayMatcher matcher(pattern);
526
527     int block = from / m_blockSize;
528     const int end =
529         qMin<qint64>(static_cast<qint64>(from) + SearchStride, m_size);
530     while (from < end) {
531         if (!requestDataAt(block * m_blockSize))
532             return -1;
533         QByteArray data = blockData(block);
534         ::memcpy(b, b + m_blockSize, trailing);
535         ::memcpy(b + trailing, data.constData(), m_blockSize);
536
537         if (!caseSensitive)
538             ::lower(buffer);
539
540         int pos = matcher.indexIn(buffer, from - (block * m_blockSize) + trailing);
541         if (pos >= 0)
542             return pos + block * m_blockSize - trailing;
543         ++block;
544         from = block * m_blockSize - trailing;
545     }
546     return end == m_size ? -1 : -2;
547 }
548
549 int BinEditor::dataLastIndexOf(const QByteArray &pattern, int from, bool caseSensitive) const
550 {
551     int trailing = pattern.size();
552     if (trailing > m_blockSize)
553         return -1;
554
555     QByteArray buffer;
556     buffer.resize(m_blockSize + trailing);
557     char *b = buffer.data();
558
559     int block = from / m_blockSize;
560     const int lowerBound = qMax(0, from - SearchStride);
561     while (from > lowerBound) {
562         if (!requestDataAt(block * m_blockSize))
563             return -1;
564         QByteArray data = blockData(block);
565         ::memcpy(b + m_blockSize, b, trailing);
566         ::memcpy(b, data.constData(), m_blockSize);
567
568         if (!caseSensitive)
569             ::lower(buffer);
570
571         int pos = buffer.lastIndexOf(pattern, from - (block * m_blockSize));
572         if (pos >= 0)
573             return pos + block * m_blockSize;
574         --block;
575         from = block * m_blockSize + (m_blockSize-1) + trailing;
576     }
577     return lowerBound == 0 ? -1 : -2;
578 }
579
580
581 int BinEditor::find(const QByteArray &pattern_arg, int from,
582                     QTextDocument::FindFlags findFlags)
583 {
584     if (pattern_arg.isEmpty())
585         return 0;
586
587     QByteArray pattern = pattern_arg;
588
589     bool caseSensitiveSearch = (findFlags & QTextDocument::FindCaseSensitively);
590
591     if (!caseSensitiveSearch)
592         ::lower(pattern);
593
594     bool backwards = (findFlags & QTextDocument::FindBackward);
595     int found = backwards ? dataLastIndexOf(pattern, from, caseSensitiveSearch)
596                 : dataIndexOf(pattern, from, caseSensitiveSearch);
597
598     int foundHex = -1;
599     QByteArray hexPattern = calculateHexPattern(pattern_arg);
600     if (!hexPattern.isEmpty()) {
601         foundHex = backwards ? dataLastIndexOf(hexPattern, from)
602                    : dataIndexOf(hexPattern, from);
603     }
604
605     int pos = foundHex == -1 || (found >= 0 && (foundHex == -2 || found < foundHex))
606               ? found : foundHex;
607
608     if (pos >= m_size)
609         pos = -1;
610
611     if (pos >= 0) {
612         setCursorPosition(pos);
613         setCursorPosition(pos + (found == pos ? pattern.size() : hexPattern.size()), KeepAnchor);
614     }
615     return pos;
616 }
617
618 int BinEditor::findPattern(const QByteArray &data, const QByteArray &dataHex,
619     int from, int offset, int *match)
620 {
621     if (m_searchPattern.isEmpty())
622         return -1;
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);
627
628     if (normal >= 0 && (hex < 0 || normal < hex)) {
629         if (match)
630             *match = m_searchPattern.length();
631         return normal + offset;
632     }
633     if (hex >= 0) {
634         if (match)
635             *match = m_searchPatternHex.length();
636         return hex + offset;
637     }
638
639     return -1;
640 }
641
642
643 void BinEditor::drawItems(QPainter *painter, int x, int y, const QString &itemString)
644 {
645     if (m_isMonospacedFont) {
646         painter->drawText(x, y, itemString);
647     } else {
648         for (int i = 0; i < 16; ++i)
649             painter->drawText(x + i*m_columnWidth, y, itemString.mid(i*3, 2));
650     }
651 }
652
653 void BinEditor::drawChanges(QPainter *painter, int x, int y, const char *changes)
654 {
655     const QBrush red(QColor(250, 150, 150));
656     for (int i = 0; i < 16; ++i) {
657         if (changes[i]) {
658             painter->fillRect(x + i*m_columnWidth, y - m_ascent,
659                 2*m_charWidth, m_lineHeight, red);
660         }
661     }
662 }
663
664 QString BinEditor::addressString(quint64 address)
665 {
666     QChar *addressStringData = m_addressString.data();
667     const char *hex = "0123456789abcdef";
668
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
672     };
673
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];
679     }
680     return m_addressString;
681 }
682
683 void BinEditor::paintEvent(QPaintEvent *e)
684 {
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());
692
693
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());
700     }
701
702     int matchLength = 0;
703
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);
711     }
712
713
714     int foundPatternAt = findPattern(patternData, patternDataHex, patternOffset, patternOffset, &matchLength);
715
716     int selStart, selEnd;
717     if (m_cursorPosition >= m_anchorPosition) {
718         selStart = m_anchorPosition;
719         selEnd = m_cursorPosition;
720     } else {
721         selStart = m_cursorPosition;
722         selEnd = m_anchorPosition + 1;
723     }
724
725     QString itemString(16*3, QLatin1Char(' '));
726     QChar *itemStringData = itemString.data();
727     char changedString[16] = { false };
728     const char *hex = "0123456789abcdef";
729
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)
735             break;
736
737         int y = i * m_lineHeight + m_ascent;
738         if (y - m_ascent > e->rect().bottom())
739             break;
740         if (y + m_descent < e->rect().top())
741             continue;
742
743
744         painter.drawText(-xoffset, i * m_lineHeight + m_ascent,
745                          addressString(m_baseAddr + uint(line) * 16));
746
747         int cursor = -1;
748         if (line * 16 <= m_cursorPosition && m_cursorPosition < line * 16 + 16)
749             cursor = m_cursorPosition - line * 16;
750
751         bool hasData = requestDataAt(line * 16);
752         bool hasOldData = requestOldDataAt(line * 16);
753         bool isOld = hasOldData && !hasData;
754
755         QString printable;
756
757         if (hasData || hasOldData) {
758             for (int c = 0; c < 16; ++c) {
759                 int pos = line * 16 + c;
760                 if (pos >= m_size)
761                     break;
762                 QChar qc(QLatin1Char(dataAt(pos, isOld)));
763                 if (qc.unicode() >= 127 || !qc.isPrint())
764                     qc = 0xB7;
765                 printable += qc;
766             }
767         } else {
768             printable = QString(16, QLatin1Char(' '));
769         }
770
771         QRect selectionRect;
772         QRect printableSelectionRect;
773
774         bool isFullySelected = (selStart < selEnd && selStart <= line*16 && (line+1)*16 <= selEnd);
775         bool somethingChanged = false;
776
777         if (hasData || hasOldData) {
778             for (int c = 0; c < 16; ++c) {
779                 int pos = line * 16 + c;
780                 if (pos >= m_size) {
781                     while (c < 16) {
782                         itemStringData[c*3] = itemStringData[c*3+1] = ' ';
783                         ++c;
784                     }
785                     break;
786                 }
787
788                 if (foundPatternAt >= 0 && pos >= foundPatternAt + matchLength)
789                     foundPatternAt = findPattern(patternData, patternDataHex, foundPatternAt + matchLength, patternOffset, &matchLength);
790
791
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;
798                 }
799
800                 int item_x = -xoffset +  m_margin + c * m_columnWidth + m_labelWidth;
801
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));
809                 }
810
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)),
817                                                     m_lineHeight);
818                 }
819             }
820         }
821
822         int x = -xoffset +  m_margin + m_labelWidth;
823         bool cursorWanted = m_cursorPosition == m_anchorPosition;
824
825         if (isFullySelected) {
826             painter.save();
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);
830             painter.restore();
831         } else {
832             if (somethingChanged)
833                 drawChanges(&painter, x, y, changedString);
834             drawItems(&painter, x, y, itemString);
835             if (!selectionRect.isEmpty()) {
836                 painter.save();
837                 painter.fillRect(selectionRect, palette().highlight());
838                 painter.setPen(palette().highlightedText().color());
839                 painter.setClipRect(selectionRect);
840                 drawItems(&painter, x, y, itemString);
841                 painter.restore();
842             }
843         }
844
845
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);
849             painter.save();
850             painter.setPen(Qt::red);
851             painter.drawRect(cursorRect.adjusted(0, 0, 0, -1));
852             painter.restore();
853             if (m_hexCursor && m_cursorVisible) {
854                 if (m_lowNibble)
855                     cursorRect.adjust(fm.width(itemString.left(1)), 0, 0, 0);
856                 painter.fillRect(cursorRect, Qt::red);
857                 painter.save();
858                 painter.setClipRect(cursorRect);
859                 painter.setPen(Qt::white);
860                 drawItems(&painter, x, y, itemString);
861                 painter.restore();
862             }
863         }
864
865         int text_x = -xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth;
866
867         if (isFullySelected) {
868                 painter.save();
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);
873                 painter.restore();
874         } else {
875             painter.drawText(text_x, y, printable);
876             if (!printableSelectionRect.isEmpty()) {
877                 painter.save();
878                 painter.fillRect(printableSelectionRect, palette().highlight());
879                 painter.setPen(palette().highlightedText().color());
880                 painter.setClipRect(printableSelectionRect);
881                 painter.drawText(text_x, y, printable);
882                 painter.restore();
883             }
884         }
885
886         if (cursor >= 0 && !printable.isEmpty() && cursorWanted) {
887             QRect cursorRect(text_x + fm.width(printable.left(cursor)),
888                              y-m_ascent,
889                              fm.width(printable.at(cursor)),
890                              m_lineHeight);
891             painter.save();
892             if (m_hexCursor || !m_cursorVisible) {
893                 painter.setPen(Qt::red);
894                 painter.drawRect(cursorRect.adjusted(0, 0, 0, -1));
895             } else {
896                 painter.setClipRect(cursorRect);
897                 painter.fillRect(cursorRect, Qt::red);
898                 painter.setPen(Qt::white);
899                 painter.drawText(text_x, y, printable);
900             }
901             painter.restore();
902         }
903     }
904 }
905
906
907 int BinEditor::cursorPosition() const
908 {
909     return m_cursorPosition;
910 }
911
912 void BinEditor::setCursorPosition(int pos, MoveMode moveMode)
913 {
914     pos = qMin(m_size-1, qMax(0, pos));
915     int oldCursorPosition = m_cursorPosition;
916
917     bool hasSelection = m_anchorPosition != m_cursorPosition;
918     m_lowNibble = false;
919     if (!hasSelection)
920         updateLines();
921     m_cursorPosition = pos;
922     if (moveMode == MoveAnchor) {
923         if (hasSelection)
924             updateLines(m_anchorPosition, oldCursorPosition);
925         m_anchorPosition = m_cursorPosition;
926     }
927
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);
934 }
935
936
937 void BinEditor::ensureCursorVisible()
938 {
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);
946     }
947 }
948
949 void BinEditor::mousePressEvent(QMouseEvent *e)
950 {
951     if (e->button() != Qt::LeftButton)
952         return;
953     setCursorPosition(posAt(e->pos()));
954     setBlinkingCursorEnabled(true);
955     if (m_hexCursor == inTextArea(e->pos())) {
956         m_hexCursor = !m_hexCursor;
957         updateLines();
958     }
959 }
960
961 void BinEditor::mouseMoveEvent(QMouseEvent *e)
962 {
963     if (!(e->buttons() & Qt::LeftButton))
964         return;
965     setCursorPosition(posAt(e->pos()), KeepAnchor);
966     if (m_hexCursor == inTextArea(e->pos())) {
967         m_hexCursor = !m_hexCursor;
968         updateLines();
969     }
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);
975 }
976
977 void BinEditor::mouseReleaseEvent(QMouseEvent *)
978 {
979     if (m_autoScrollTimer.isActive()) {
980         m_autoScrollTimer.stop();
981         ensureCursorVisible();
982     }
983 }
984
985 void BinEditor::selectAll()
986 {
987     setCursorPosition(0);
988     setCursorPosition(m_size-1, KeepAnchor);
989 }
990
991 void BinEditor::clear()
992 {
993     m_baseAddr = 0;
994     m_data.clear();
995     m_oldData.clear();
996     m_modifiedData.clear();
997     m_requests.clear();
998     m_size = 0;
999     m_addressBytes = 4;
1000
1001     m_unmodifiedState = 0;
1002     m_undoStack.clear();
1003     m_redoStack.clear();
1004
1005     init();
1006     m_cursorPosition = 0;
1007     verticalScrollBar()->setValue(0);
1008
1009     emit cursorPositionChanged(m_cursorPosition);
1010     viewport()->update();
1011 }
1012
1013 bool BinEditor::event(QEvent *e)
1014 {
1015     if (e->type() == QEvent::KeyPress) {
1016         switch (static_cast<QKeyEvent*>(e)->key()) {
1017         case Qt::Key_Tab:
1018         case Qt::Key_Backtab:
1019             m_hexCursor = !m_hexCursor;
1020             setBlinkingCursorEnabled(true);
1021             ensureCursorVisible();
1022             e->accept();
1023             return true;
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);
1028                 return true;
1029             }
1030             break;
1031         }
1032         default:;
1033         }
1034     } else if (e->type() == QEvent::ToolTip) {
1035         const QHelpEvent * const helpEvent = static_cast<QHelpEvent *>(e);
1036         bool hide = true;
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;
1044             byteCount = 1;
1045         }
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);
1058                 QString leSigned;
1059                 QString beSigned;
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));
1068                     break;
1069                 case 4: case 3:
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));
1074                     break;
1075                 case 2:
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));
1080                     break;
1081                 case 1:
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));
1086                     break;
1087                 }
1088                 hide = false;
1089                 //int pos = posAt(mousePos);
1090                 //uchar old = dataAt(pos, true);
1091                 //uchar current = dataAt(pos, false);
1092
1093                 QString msg = 
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))
1100                        .arg(leSigned)
1101                        .arg(beSigned);
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))
1110                        .arg(leSignedOld)
1111                        .arg(beSignedOld);
1112                 }
1113                 QToolTip::showText(helpEvent->globalPos(), msg, this);
1114             }
1115         }
1116         if (hide)
1117             QToolTip::hideText();
1118         e->accept();
1119         return true;
1120     }
1121
1122     return QAbstractScrollArea::event(e);
1123 }
1124
1125 void BinEditor::keyPressEvent(QKeyEvent *e)
1126 {
1127
1128     if (e == QKeySequence::SelectAll) {
1129             e->accept();
1130             selectAll();
1131             return;
1132     } else if (e == QKeySequence::Copy) {
1133         e->accept();
1134         copy();
1135         return;
1136     } else if (e == QKeySequence::Undo) {
1137         e->accept();
1138         undo();
1139         return;
1140     } else if (e == QKeySequence::Redo) {
1141         e->accept();
1142         redo();
1143         return;
1144     }
1145
1146
1147     MoveMode moveMode = e->modifiers() & Qt::ShiftModifier ? KeepAnchor : MoveAnchor;
1148     switch (e->key()) {
1149     case Qt::Key_Up:
1150         setCursorPosition(m_cursorPosition - 16, moveMode);
1151         break;
1152     case Qt::Key_Down:
1153         setCursorPosition(m_cursorPosition + 16, moveMode);
1154         break;
1155     case Qt::Key_Right:
1156         setCursorPosition(m_cursorPosition + 1, moveMode);
1157         break;
1158     case Qt::Key_Left:
1159         setCursorPosition(m_cursorPosition - 1, moveMode);
1160         break;
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);
1167     } break;
1168
1169     case Qt::Key_Home:
1170         if (e->modifiers() & Qt::ControlModifier) {
1171             emit startOfFileRequested(editor());
1172         } else {
1173             setCursorPosition(m_cursorPosition/16 * 16, moveMode);
1174         }
1175         break;
1176     case Qt::Key_End:
1177         if (e->modifiers() & Qt::ControlModifier) {
1178             emit endOfFileRequested(editor());
1179         } else {
1180             setCursorPosition(m_cursorPosition/16 * 16 + 15, moveMode);
1181         }
1182         break;
1183     default:
1184         if (m_readOnly)
1185             break;
1186         {
1187         QString text = e->text();
1188         for (int i = 0; i < text.length(); ++i) {
1189             QChar c = text.at(i);
1190             if (m_hexCursor) {
1191                 c = c.toLower();
1192                 int nibble = -1;
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';
1197                 if (nibble < 0)
1198                     continue;
1199                 if (m_lowNibble) {
1200                     changeData(m_cursorPosition, nibble + (dataAt(m_cursorPosition) & 0xf0));
1201                     m_lowNibble = false;
1202                     setCursorPosition(m_cursorPosition + 1);
1203                 } else {
1204                     changeData(m_cursorPosition, (nibble << 4) + (dataAt(m_cursorPosition) & 0x0f), true);
1205                     m_lowNibble = true;
1206                     updateLines();
1207                 }
1208             } else {
1209                 if (c.unicode() >= 128 || !c.isPrint())
1210                     continue;
1211                 changeData(m_cursorPosition, c.unicode(), m_cursorPosition + 1);
1212                 setCursorPosition(m_cursorPosition + 1);
1213             }
1214             setBlinkingCursorEnabled(true);
1215         }
1216     }
1217     }
1218
1219     e->accept();
1220 }
1221
1222 void BinEditor::zoomIn(int range)
1223 {
1224     QFont f = font();
1225     const int newSize = f.pointSize() + range;
1226     if (newSize <= 0)
1227         return;
1228     f.setPointSize(newSize);
1229     setFont(f);
1230 }
1231
1232 void BinEditor::zoomOut(int range)
1233 {
1234     zoomIn(-range);
1235 }
1236
1237 void BinEditor::copy(bool raw)
1238 {
1239     int selStart = selectionStart();
1240     int selEnd = selectionEnd();
1241     if (selStart >= selEnd)
1242         qSwap(selStart, selEnd);
1243
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."));
1248         return;
1249     }
1250     const QByteArray &data = dataMid(selStart, selectionLength);
1251     if (raw) {
1252         QApplication::clipboard()->setText(data);
1253         return;
1254     }
1255     QString hexString;
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(' ');
1261     }
1262     hexString.chop(1);
1263     QApplication::clipboard()->setText(hexString);
1264 }
1265
1266 void BinEditor::highlightSearchResults(const QByteArray &pattern, QTextDocument::FindFlags findFlags)
1267 {
1268     if (m_searchPattern == pattern)
1269         return;
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();
1276 }
1277
1278
1279 void BinEditor::changeData(int position, uchar character, bool highNibble)
1280 {
1281     if (!requestDataAt(position))
1282         return;
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;
1290
1291     if (!highNibble
1292             && !m_undoStack.isEmpty()
1293             && m_undoStack.top().position == position
1294             && m_undoStack.top().highNibble) {
1295         // compress
1296         cmd.character = m_undoStack.top().character;
1297         m_undoStack.pop();
1298     }
1299
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);
1305     }
1306
1307     if (m_undoStack.size() == 1)
1308         emit undoAvailable(true);
1309 }
1310
1311
1312 void BinEditor::undo()
1313 {
1314     if (m_undoStack.isEmpty())
1315         return;
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);
1321     cmd.character = c;
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);
1330 }
1331
1332 void BinEditor::redo()
1333 {
1334     if (m_redoStack.isEmpty())
1335         return;
1336     BinEditorEditCommand cmd = m_redoStack.pop();
1337     uchar c = dataAt(cmd.position);
1338     changeDataAt(cmd.position, (char)cmd.character);
1339     cmd.character = c;
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);
1349 }
1350
1351 void BinEditor::contextMenuEvent(QContextMenuEvent *event)
1352 {
1353     const int selStart = selectionStart();
1354     const int byteCount = selectionEnd() - selStart;
1355     if (byteCount == 0)
1356         return;
1357
1358     QMenu contextMenu;
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(&copyAsciiAction);
1366     contextMenu.addAction(&copyHexAction);
1367
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);
1374
1375         // If the menu entries would be identical, show only one of them.
1376         if (beAddress != leAddress) {
1377             setupJumpToMenuAction(&contextMenu, &jumpToLeAddressHere,
1378                               &jumpToLeAddressNewWindow, leAddress);
1379         }
1380     } else {
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);
1387     }
1388
1389     QAction *action = contextMenu.exec(event->globalPos());
1390     if (action == &copyAsciiAction)
1391         copy(true);
1392     else if (action == &copyHexAction)
1393         copy(false);
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);
1402 }
1403
1404 void BinEditor::setupJumpToMenuAction(QMenu *menu, QAction *actionHere,
1405                                       QAction *actionNew, quint64 addr)
1406 {
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);
1415 }
1416
1417 void BinEditor::jumpToAddress(quint64 address)
1418 {
1419     if (address >= m_baseAddr && address < m_baseAddr + m_size)
1420         setCursorPosition(address - m_baseAddr);
1421     else
1422         emit newRangeRequested(editor(), address);
1423 }
1424
1425 void BinEditor::setNewWindowRequestAllowed()
1426 {
1427     m_canRequestNewWindow = true;
1428 }
1429
1430 void BinEditor::updateContents()
1431 {
1432     m_oldData = m_data;
1433     m_data.clear();
1434     setSizes(baseAddress() + cursorPosition(), m_size, m_blockSize);
1435 }
1436
1437 QPoint BinEditor::offsetToPos(int offset)
1438 {
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);
1442 }
1443
1444 void BinEditor::asIntegers(int offset, int count, quint64 &beValue,
1445     quint64 &leValue, bool old)
1446 {
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);
1453     }
1454 }
1455
1456 bool BinEditor::isMemoryView() const
1457 {
1458     return editor()->property("MemoryView").toBool();
1459 }
1460
1461 } // namespace BINEditor