bineditor.cpp 55 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30 31 32 33
#include "bineditor.h"

#include <texteditor/fontsettings.h>
#include <texteditor/texteditorconstants.h>
hjk's avatar
hjk committed
34
#include <texteditor/texteditorsettings.h>
35
#include <coreplugin/editormanager/ieditor.h>
36
#include <utils/fileutils.h>
37
#include <utils/qtcassert.h>
con's avatar
con committed
38

39 40 41 42 43
#include <QByteArrayMatcher>
#include <QDebug>
#include <QFile>
#include <QTemporaryFile>
#include <QVariant>
ck's avatar
ck committed
44

45 46 47 48 49 50 51 52
#include <QApplication>
#include <QAction>
#include <QClipboard>
#include <QFontMetrics>
#include <QHelpEvent>
#include <QMenu>
#include <QMessageBox>
#include <QPainter>
53
#include <QPointer>
54 55 56
#include <QScrollBar>
#include <QToolTip>
#include <QWheelEvent>
con's avatar
con committed
57

58 59 60 61 62 63 64 65 66 67 68 69
// QByteArray::toLower() is broken, it stops at the first \0
static void lower(QByteArray &ba)
{
    char *data = ba.data();
    char *end = data + ba.size();
    while (data != end) {
        if (*data >= 0x41 && *data <= 0x5A)
            *data += 0x20;
        ++data;
    }
}

con's avatar
con committed
70 71 72 73 74 75 76 77
static QByteArray calculateHexPattern(const QByteArray &pattern)
{
    QByteArray result;
    if (pattern.size() % 2 == 0) {
        bool ok = true;
        int i = 0;
        while (i < pattern.size()) {
            ushort s = pattern.mid(i, 2).toUShort(&ok, 16);
78
            if (!ok)
con's avatar
con committed
79 80 81 82 83 84 85 86
                return QByteArray();
            result.append(s);
            i += 2;
        }
    }
    return result;
}

hjk's avatar
hjk committed
87
namespace BinEditor {
hjk's avatar
hjk committed
88

89
BinEditorWidget::BinEditorWidget(QWidget *parent)
con's avatar
con committed
90 91
    : QAbstractScrollArea(parent)
{
hjk's avatar
hjk committed
92
    m_bytesPerLine = 16;
con's avatar
con committed
93
    m_ieditor = 0;
ck's avatar
ck committed
94
    m_baseAddr = 0;
95
    m_blockSize = 4096;
con's avatar
con committed
96
    m_size = 0;
97
    m_addressBytes = 4;
con's avatar
con committed
98 99
    init();
    m_unmodifiedState = 0;
100
    m_readOnly = false;
con's avatar
con committed
101 102 103 104 105
    m_hexCursor = true;
    m_cursorPosition = 0;
    m_anchorPosition = 0;
    m_lowNibble = false;
    m_cursorVisible = false;
106
    m_caseSensitiveSearch = false;
107
    m_canRequestNewWindow = false;
con's avatar
con committed
108
    setFocusPolicy(Qt::WheelFocus);
hjk's avatar
hjk committed
109
    setFrameStyle(QFrame::Plain);
hjk's avatar
hjk committed
110 111

    // Font settings
112 113 114
    setFontSettings(TextEditor::TextEditorSettings::fontSettings());
    connect(TextEditor::TextEditorSettings::instance(),
            SIGNAL(fontSettingsChanged(TextEditor::FontSettings)),
hjk's avatar
hjk committed
115 116
            this, SLOT(setFontSettings(TextEditor::FontSettings)));

con's avatar
con committed
117 118
}

119
BinEditorWidget::~BinEditorWidget()
con's avatar
con committed
120 121 122
{
}

123
void BinEditorWidget::init()
con's avatar
con committed
124
{
125 126 127
    const int addressStringWidth =
        2*m_addressBytes + (m_addressBytes - 1) / 2;
    m_addressString = QString(addressStringWidth, QLatin1Char(':'));
con's avatar
con committed
128 129 130 131 132
    QFontMetrics fm(fontMetrics());
    m_descent = fm.descent();
    m_ascent = fm.ascent();
    m_lineHeight = fm.lineSpacing();
    m_charWidth = fm.width(QChar(QLatin1Char('M')));
133
    m_margin = m_charWidth;
con's avatar
con committed
134
    m_columnWidth = 2 * m_charWidth + fm.width(QChar(QLatin1Char(' ')));
hjk's avatar
hjk committed
135
    m_numLines = m_size / m_bytesPerLine + 1;
con's avatar
con committed
136
    m_numVisibleLines = viewport()->height() / m_lineHeight;
hjk's avatar
hjk committed
137
    m_textWidth = m_bytesPerLine * m_charWidth + m_charWidth;
con's avatar
con committed
138
    int m_numberWidth = fm.width(QChar(QLatin1Char('9')));
139 140
    m_labelWidth =
        2*m_addressBytes * m_numberWidth + (m_addressBytes - 1)/2 * m_charWidth;
con's avatar
con committed
141 142 143 144 145 146 147 148 149 150 151 152

    int expectedCharWidth = m_columnWidth / 3;
    const char *hex = "0123456789abcdef";
    m_isMonospacedFont = true;
    while (*hex) {
        if (fm.width(QLatin1Char(*hex)) != expectedCharWidth) {
            m_isMonospacedFont = false;
            break;
        }
        ++hex;
    }

153
    if (m_isMonospacedFont && fm.width(QLatin1String("M M ")) != m_charWidth * 4) {
154 155 156 157
        // On Qt/Mac, monospace font widths may have a fractional component
        // This breaks the assumption that width("MMM") == width('M') * 3

        m_isMonospacedFont = false;
158
        m_columnWidth = fm.width(QLatin1String("MMM"));
159
        m_labelWidth = m_addressBytes == 4
160 161
            ? fm.width(QLatin1String("MMMM:MMMM"))
            : fm.width(QLatin1String("MMMM:MMMM:MMMM:MMMM"));
162 163
    }

hjk's avatar
hjk committed
164
    horizontalScrollBar()->setRange(0, 2 * m_margin + m_bytesPerLine * m_columnWidth
con's avatar
con committed
165 166 167 168
                                    + m_labelWidth + m_textWidth - viewport()->width());
    horizontalScrollBar()->setPageStep(viewport()->width());
    verticalScrollBar()->setRange(0, m_numLines - m_numVisibleLines);
    verticalScrollBar()->setPageStep(m_numVisibleLines);
169
    ensureCursorVisible();
con's avatar
con committed
170 171 172
}


173
void BinEditorWidget::addData(quint64 block, const QByteArray &data)
174
{
hjk's avatar
hjk committed
175
    QTC_ASSERT(data.size() == m_blockSize, return);
ck's avatar
ck committed
176 177
    const quint64 addr = block * m_blockSize;
    if (addr >= m_baseAddr && addr <= m_baseAddr + m_size - 1) {
hjk's avatar
hjk committed
178 179
        if (m_data.size() * m_blockSize >= 64 * 1024 * 1024)
            m_data.clear();
ck's avatar
ck committed
180
        const int translatedBlock = (addr - m_baseAddr) / m_blockSize;
hjk's avatar
hjk committed
181 182
        m_data.insert(translatedBlock, data);
        m_requests.remove(translatedBlock);
ck's avatar
ck committed
183 184
        viewport()->update();
    }
185 186
}

187
bool BinEditorWidget::requestDataAt(int pos) const
188 189
{
    int block = pos / m_blockSize;
hjk's avatar
hjk committed
190
    BlockMap::const_iterator it = m_modifiedData.find(block);
ck's avatar
ck committed
191 192
    if (it != m_modifiedData.constEnd())
        return true;
hjk's avatar
hjk committed
193 194
    it = m_data.find(block);
    if (it != m_data.end())
195
        return true;
hjk's avatar
hjk committed
196 197
    if (!m_requests.contains(block)) {
        m_requests.insert(block);
198
        emit const_cast<BinEditorWidget*>(this)->
199
            dataRequested(m_baseAddr / m_blockSize + block);
hjk's avatar
hjk committed
200
        return true;
201
    }
202 203
    return false;
}
204

205
bool BinEditorWidget::requestOldDataAt(int pos) const
206 207
{
    int block = pos / m_blockSize;
hjk's avatar
hjk committed
208 209
    BlockMap::const_iterator it = m_oldData.find(block);
    return it != m_oldData.end();
210 211
}

212
char BinEditorWidget::dataAt(int pos, bool old) const
213 214
{
    int block = pos / m_blockSize;
hjk's avatar
hjk committed
215
    return blockData(block, old).at(pos - block*m_blockSize);
216 217
}

218
void BinEditorWidget::changeDataAt(int pos, char c)
219 220
{
    int block = pos / m_blockSize;
hjk's avatar
hjk committed
221
    BlockMap::iterator it = m_modifiedData.find(block);
ck's avatar
ck committed
222 223 224
    if (it != m_modifiedData.end()) {
        it.value()[pos - (block*m_blockSize)] = c;
    } else {
hjk's avatar
hjk committed
225 226
        it = m_data.find(block);
        if (it != m_data.end()) {
ck's avatar
ck committed
227 228 229 230 231
            QByteArray data = it.value();
            data[pos - (block*m_blockSize)] = c;
            m_modifiedData.insert(block, data);
        }
    }
hjk's avatar
hjk committed
232

233
    emit dataChanged(m_baseAddr + pos, QByteArray(1, c));
234 235
}

236
QByteArray BinEditorWidget::dataMid(int from, int length, bool old) const
237
{
238 239 240 241
    int end = from + length;
    int block = from / m_blockSize;

    QByteArray data;
242
    data.reserve(length);
243
    do {
244
        data += blockData(block++, old);
245 246 247
    } while (block * m_blockSize < end);

    return data.mid(from - ((from / m_blockSize) * m_blockSize), length);
248 249
}

250
QByteArray BinEditorWidget::blockData(int block, bool old) const
251
{
252
    if (old) {
hjk's avatar
hjk committed
253
        BlockMap::const_iterator it = m_modifiedData.find(block);
254
        return it != m_modifiedData.constEnd()
hjk's avatar
hjk committed
255
                ? it.value() : m_oldData.value(block, m_emptyBlock);
256
    }
hjk's avatar
hjk committed
257
    BlockMap::const_iterator it = m_modifiedData.find(block);
ck's avatar
ck committed
258
    return it != m_modifiedData.constEnd()
hjk's avatar
hjk committed
259
            ? it.value() : m_data.value(block, m_emptyBlock);
260 261
}

262
void BinEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
con's avatar
con committed
263
{
264
    setFont(fs.toTextCharFormat(TextEditor::C_TEXT).font());
con's avatar
con committed
265 266
}

267
void BinEditorWidget::setBlinkingCursorEnabled(bool enable)
con's avatar
con committed
268 269 270 271 272 273 274 275 276
{
    if (enable && QApplication::cursorFlashTime() > 0)
        m_cursorBlinkTimer.start(QApplication::cursorFlashTime() / 2, this);
    else
        m_cursorBlinkTimer.stop();
    m_cursorVisible = enable;
    updateLines();
}

277
void BinEditorWidget::focusInEvent(QFocusEvent *)
con's avatar
con committed
278 279 280 281
{
    setBlinkingCursorEnabled(true);
}

282
void BinEditorWidget::focusOutEvent(QFocusEvent *)
con's avatar
con committed
283 284 285 286
{
    setBlinkingCursorEnabled(false);
}

287
void BinEditorWidget::timerEvent(QTimerEvent *e)
con's avatar
con committed
288 289 290 291 292 293
{
    if (e->timerId() == m_autoScrollTimer.timerId()) {
        QRect visible = viewport()->rect();
        QPoint pos;
        const QPoint globalPos = QCursor::pos();
        pos = viewport()->mapFromGlobal(globalPos);
hjk's avatar
hjk committed
294 295
        QMouseEvent ev(QEvent::MouseMove, pos, globalPos,
            Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
con's avatar
con committed
296
        mouseMoveEvent(&ev);
hjk's avatar
hjk committed
297 298 299 300
        int deltaY = qMax(pos.y() - visible.top(),
                          visible.bottom() - pos.y()) - visible.height();
        int deltaX = qMax(pos.x() - visible.left(),
                          visible.right() - pos.x()) - visible.width();
con's avatar
con committed
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
        int delta = qMax(deltaX, deltaY);
        if (delta >= 0) {
            if (delta < 7)
                delta = 7;
            int timeout = 4900 / (delta * delta);
            m_autoScrollTimer.start(timeout, this);

            if (deltaY > 0)
                verticalScrollBar()->triggerAction(pos.y() < visible.center().y() ?
                                       QAbstractSlider::SliderSingleStepSub
                                       : QAbstractSlider::SliderSingleStepAdd);
            if (deltaX > 0)
                horizontalScrollBar()->triggerAction(pos.x() < visible.center().x() ?
                                       QAbstractSlider::SliderSingleStepSub
                                       : QAbstractSlider::SliderSingleStepAdd);
        }
    } else if (e->timerId() == m_cursorBlinkTimer.timerId()) {
        m_cursorVisible = !m_cursorVisible;
        updateLines();
    }
    QAbstractScrollArea::timerEvent(e);
}


325
void BinEditorWidget::setModified(bool modified)
con's avatar
con committed
326 327 328 329 330 331 332 333
{
    int unmodifiedState = modified ? -1 : m_undoStack.size();
    if (unmodifiedState == m_unmodifiedState)
        return;
    m_unmodifiedState = unmodifiedState;
    emit modificationChanged(m_undoStack.size() != m_unmodifiedState);
}

334
bool BinEditorWidget::isModified() const
con's avatar
con committed
335 336 337 338
{
    return (m_undoStack.size() != m_unmodifiedState);
}

339
void BinEditorWidget::setReadOnly(bool readOnly)
340 341 342 343
{
    m_readOnly = readOnly;
}

344
bool BinEditorWidget::isReadOnly() const
345 346 347 348
{
    return m_readOnly;
}

349
bool BinEditorWidget::save(QString *errorString, const QString &oldFileName, const QString &newFileName)
350
{
hjk's avatar
hjk committed
351 352 353
    if (oldFileName != newFileName) {
        QString tmpName;
        {
354
            QTemporaryFile tmp(newFileName + QLatin1String("_XXXXXX.new"));
hjk's avatar
hjk committed
355
            if (!tmp.open())
ck's avatar
ck committed
356
                return false;
hjk's avatar
hjk committed
357
            tmpName = tmp.fileName();
ck's avatar
ck committed
358
        }
hjk's avatar
hjk committed
359
        if (!QFile::copy(oldFileName, tmpName))
ck's avatar
ck committed
360
            return false;
hjk's avatar
hjk committed
361
        if (QFile::exists(newFileName) && !QFile::remove(newFileName))
ck's avatar
ck committed
362
            return false;
hjk's avatar
hjk committed
363
        if (!QFile::rename(tmpName, newFileName))
ck's avatar
ck committed
364
            return false;
hjk's avatar
hjk committed
365
    }
366 367 368 369 370 371 372 373 374 375 376 377 378
    Utils::FileSaver saver(newFileName, QIODevice::ReadWrite); // QtBug: WriteOnly truncates.
    if (!saver.hasError()) {
        QFile *output = saver.file();
        const qint64 size = output->size();
        for (BlockMap::const_iterator it = m_modifiedData.constBegin();
            it != m_modifiedData.constEnd(); ++it) {
            if (!saver.setResult(output->seek(it.key() * m_blockSize)))
                break;
            if (!saver.write(it.value()))
                break;
            if (!saver.setResult(output->flush()))
                break;
        }
hjk's avatar
hjk committed
379

380 381 382 383 384 385
        // We may have padded the displayed data, so we have to make sure
        // changes to that area are not actually written back to disk.
        if (!saver.hasError())
            saver.setResult(output->resize(size));
    }
    if (!saver.finalize(errorString))
hjk's avatar
hjk committed
386 387
        return false;

ck's avatar
ck committed
388
    setModified(false);
389 390 391
    return true;
}

392
void BinEditorWidget::setSizes(quint64 startAddr, int range, int blockSize)
393
{
Daniel Teske's avatar
Daniel Teske committed
394
    int newBlockSize = blockSize;
hjk's avatar
hjk committed
395 396
    QTC_ASSERT((blockSize/m_bytesPerLine) * m_bytesPerLine == blockSize,
               blockSize = (blockSize/m_bytesPerLine + 1) * m_bytesPerLine);
hjk's avatar
hjk committed
397
    // Users can edit data in the range
ck's avatar
ck committed
398
    // [startAddr - range/2, startAddr + range/2].
Daniel Teske's avatar
Daniel Teske committed
399 400
    quint64 newBaseAddr = quint64(range/2) > startAddr ? 0 : startAddr - range/2;
    newBaseAddr = (newBaseAddr / blockSize) * blockSize;
hjk's avatar
hjk committed
401

Daniel Teske's avatar
Daniel Teske committed
402 403
    const quint64 maxRange = Q_UINT64_C(0xffffffffffffffff) - newBaseAddr + 1;
    int newSize = newBaseAddr != 0 && quint64(range) >= maxRange
hjk's avatar
hjk committed
404
              ? maxRange : range;
Daniel Teske's avatar
Daniel Teske committed
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
    int newAddressBytes = (newBaseAddr + newSize < quint64(1) << 32
                   && newBaseAddr + newSize >= newBaseAddr) ? 4 : 8;



    if (newBlockSize == m_blockSize
            && newBaseAddr == m_baseAddr
            && newSize == m_size
            && newAddressBytes == m_addressBytes)
        return;

    m_blockSize = blockSize;
    m_emptyBlock = QByteArray(blockSize, '\0');
    m_modifiedData.clear();
    m_requests.clear();

    m_baseAddr = newBaseAddr;
    m_size = newSize;
    m_addressBytes = newAddressBytes;
424 425 426 427 428 429

    m_unmodifiedState = 0;
    m_undoStack.clear();
    m_redoStack.clear();
    init();

430
    setCursorPosition(startAddr - m_baseAddr);
431
    viewport()->update();
432 433
}

434
void BinEditorWidget::resizeEvent(QResizeEvent *)
con's avatar
con committed
435 436 437 438
{
    init();
}

439
void BinEditorWidget::scrollContentsBy(int dx, int dy)
con's avatar
con committed
440 441
{
    viewport()->scroll(isRightToLeft() ? -dx : dx, dy * m_lineHeight);
hjk's avatar
hjk committed
442 443 444
    const QScrollBar * const scrollBar = verticalScrollBar();
    const int scrollPos = scrollBar->value();
    if (dy <= 0 && scrollPos == scrollBar->maximum())
445
        emit newRangeRequested(baseAddress() + m_size);
hjk's avatar
hjk committed
446
    else if (dy >= 0 && scrollPos == scrollBar->minimum())
447
        emit newRangeRequested(baseAddress());
con's avatar
con committed
448 449
}

450
void BinEditorWidget::changeEvent(QEvent *e)
con's avatar
con committed
451 452
{
    QAbstractScrollArea::changeEvent(e);
hjk's avatar
hjk committed
453
    if (e->type() == QEvent::ActivationChange) {
con's avatar
con committed
454 455 456 457 458 459 460 461
        if (!isActiveWindow())
            m_autoScrollTimer.stop();
    }
    init();
    viewport()->update();
}


462
void BinEditorWidget::wheelEvent(QWheelEvent *e)
con's avatar
con committed
463 464 465 466 467 468 469 470 471 472 473 474 475 476
{
    if (e->modifiers() & Qt::ControlModifier) {
        const int delta = e->delta();
        if (delta < 0)
            zoomOut();
        else if (delta > 0)
            zoomIn();
        return;
    }
    QAbstractScrollArea::wheelEvent(e);
}



477
QRect BinEditorWidget::cursorRect() const
con's avatar
con committed
478 479
{
    int topLine = verticalScrollBar()->value();
hjk's avatar
hjk committed
480
    int line = m_cursorPosition / m_bytesPerLine;
con's avatar
con committed
481 482
    int y = (line - topLine) * m_lineHeight;
    int xoffset = horizontalScrollBar()->value();
hjk's avatar
hjk committed
483
    int column = m_cursorPosition % m_bytesPerLine;
hjk's avatar
hjk committed
484 485
    int x = m_hexCursor
            ? (-xoffset + m_margin + m_labelWidth + column * m_columnWidth)
hjk's avatar
hjk committed
486
            : (-xoffset + m_margin + m_labelWidth + m_bytesPerLine * m_columnWidth
hjk's avatar
hjk committed
487
               + m_charWidth + column * m_charWidth);
con's avatar
con committed
488 489 490 491
    int w = m_hexCursor ? m_columnWidth : m_charWidth;
    return QRect(x, y, w, m_lineHeight);
}

492
int BinEditorWidget::posAt(const QPoint &pos) const
con's avatar
con committed
493 494 495 496 497 498 499 500
{
    int xoffset = horizontalScrollBar()->value();
    int x = xoffset + pos.x() - m_margin - m_labelWidth;
    int column = qMin(15, qMax(0,x) / m_columnWidth);
    int topLine = verticalScrollBar()->value();
    int line = pos.y() / m_lineHeight;


hjk's avatar
hjk committed
501 502
    if (x > m_bytesPerLine * m_columnWidth + m_charWidth/2) {
        x -= m_bytesPerLine * m_columnWidth + m_charWidth;
con's avatar
con committed
503
        for (column = 0; column < 15; ++column) {
hjk's avatar
hjk committed
504
            int dataPos = (topLine + line) * m_bytesPerLine + column;
505
            if (dataPos < 0 || dataPos >= m_size)
con's avatar
con committed
506
                break;
507
            QChar qc(QLatin1Char(dataAt(dataPos)));
con's avatar
con committed
508 509 510 511 512 513 514 515
            if (!qc.isPrint())
                qc = 0xB7;
            x -= fontMetrics().width(qc);
            if (x <= 0)
                break;
        }
    }

hjk's avatar
hjk committed
516
    return qMin(m_size, qMin(m_numLines, topLine + line) * m_bytesPerLine) + column;
con's avatar
con committed
517 518
}

519
bool BinEditorWidget::inTextArea(const QPoint &pos) const
con's avatar
con committed
520 521 522
{
    int xoffset = horizontalScrollBar()->value();
    int x = xoffset + pos.x() - m_margin - m_labelWidth;
hjk's avatar
hjk committed
523
    return (x > m_bytesPerLine * m_columnWidth + m_charWidth/2);
con's avatar
con committed
524 525
}

526
void BinEditorWidget::updateLines()
hjk's avatar
hjk committed
527 528 529
{
    updateLines(m_cursorPosition, m_cursorPosition);
}
con's avatar
con committed
530

531
void BinEditorWidget::updateLines(int fromPosition, int toPosition)
con's avatar
con committed
532 533
{
    int topLine = verticalScrollBar()->value();
hjk's avatar
hjk committed
534 535
    int firstLine = qMin(fromPosition, toPosition) / m_bytesPerLine;
    int lastLine = qMax(fromPosition, toPosition) / m_bytesPerLine;
con's avatar
con committed
536 537 538 539 540 541
    int y = (firstLine - topLine) * m_lineHeight;
    int h = (lastLine - firstLine + 1 ) * m_lineHeight;

    viewport()->update(0, y, viewport()->width(), h);
}

542
int BinEditorWidget::dataIndexOf(const QByteArray &pattern, int from, bool caseSensitive) const
con's avatar
con committed
543
{
544 545 546 547
    int trailing = pattern.size();
    if (trailing > m_blockSize)
        return -1;

con's avatar
con committed
548 549
    QByteArray buffer;
    buffer.resize(m_blockSize + trailing);
550 551 552 553
    char *b = buffer.data();
    QByteArrayMatcher matcher(pattern);

    int block = from / m_blockSize;
ck's avatar
ck committed
554 555 556
    const int end =
        qMin<qint64>(static_cast<qint64>(from) + SearchStride, m_size);
    while (from < end) {
hjk's avatar
hjk committed
557
        if (!requestDataAt(block * m_blockSize))
558 559 560 561 562 563 564 565 566 567 568 569 570 571
            return -1;
        QByteArray data = blockData(block);
        ::memcpy(b, b + m_blockSize, trailing);
        ::memcpy(b + trailing, data.constData(), m_blockSize);

        if (!caseSensitive)
            ::lower(buffer);

        int pos = matcher.indexIn(buffer, from - (block * m_blockSize) + trailing);
        if (pos >= 0)
            return pos + block * m_blockSize - trailing;
        ++block;
        from = block * m_blockSize - trailing;
    }
ck's avatar
ck committed
572
    return end == m_size ? -1 : -2;
573 574
}

575
int BinEditorWidget::dataLastIndexOf(const QByteArray &pattern, int from, bool caseSensitive) const
576 577 578 579 580
{
    int trailing = pattern.size();
    if (trailing > m_blockSize)
        return -1;

mae's avatar
mae committed
581 582
    QByteArray buffer;
    buffer.resize(m_blockSize + trailing);
583 584
    char *b = buffer.data();

585 586
    if (from == -1)
        from = m_size;
587
    int block = from / m_blockSize;
ck's avatar
ck committed
588 589
    const int lowerBound = qMax(0, from - SearchStride);
    while (from > lowerBound) {
hjk's avatar
hjk committed
590
        if (!requestDataAt(block * m_blockSize))
591 592 593 594 595 596 597 598 599 600 601 602 603 604
            return -1;
        QByteArray data = blockData(block);
        ::memcpy(b + m_blockSize, b, trailing);
        ::memcpy(b, data.constData(), m_blockSize);

        if (!caseSensitive)
            ::lower(buffer);

        int pos = buffer.lastIndexOf(pattern, from - (block * m_blockSize));
        if (pos >= 0)
            return pos + block * m_blockSize;
        --block;
        from = block * m_blockSize + (m_blockSize-1) + trailing;
    }
ck's avatar
ck committed
605
    return lowerBound == 0 ? -1 : -2;
606 607 608
}


609
int BinEditorWidget::find(const QByteArray &pattern_arg, int from,
ck's avatar
ck committed
610
                    QTextDocument::FindFlags findFlags)
611 612 613 614 615 616 617 618 619 620 621
{
    if (pattern_arg.isEmpty())
        return 0;

    QByteArray pattern = pattern_arg;

    bool caseSensitiveSearch = (findFlags & QTextDocument::FindCaseSensitively);

    if (!caseSensitiveSearch)
        ::lower(pattern);

con's avatar
con committed
622
    bool backwards = (findFlags & QTextDocument::FindBackward);
623 624
    int found = backwards ? dataLastIndexOf(pattern, from, caseSensitiveSearch)
                : dataIndexOf(pattern, from, caseSensitiveSearch);
ck's avatar
ck committed
625

con's avatar
con committed
626
    int foundHex = -1;
627
    QByteArray hexPattern = calculateHexPattern(pattern_arg);
con's avatar
con committed
628
    if (!hexPattern.isEmpty()) {
629 630
        foundHex = backwards ? dataLastIndexOf(hexPattern, from)
                   : dataIndexOf(hexPattern, from);
con's avatar
con committed
631 632
    }

ck's avatar
ck committed
633 634
    int pos = foundHex == -1 || (found >= 0 && (foundHex == -2 || found < foundHex))
              ? found : foundHex;
635 636 637 638

    if (pos >= m_size)
        pos = -1;

con's avatar
con committed
639 640
    if (pos >= 0) {
        setCursorPosition(pos);
641
        setCursorPosition(pos + (found == pos ? pattern.size() : hexPattern.size()) - 1, KeepAnchor);
con's avatar
con committed
642 643 644 645
    }
    return pos;
}

646
int BinEditorWidget::findPattern(const QByteArray &data, const QByteArray &dataHex,
hjk's avatar
hjk committed
647
    int from, int offset, int *match)
con's avatar
con committed
648 649 650
{
    if (m_searchPattern.isEmpty())
        return -1;
hjk's avatar
hjk committed
651 652 653 654
    int normal = m_searchPattern.isEmpty()
        ? -1 : data.indexOf(m_searchPattern, from - offset);
    int hex = m_searchPatternHex.isEmpty()
        ? -1 : dataHex.indexOf(m_searchPatternHex, from - offset);
con's avatar
con committed
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670

    if (normal >= 0 && (hex < 0 || normal < hex)) {
        if (match)
            *match = m_searchPattern.length();
        return normal + offset;
    }
    if (hex >= 0) {
        if (match)
            *match = m_searchPatternHex.length();
        return hex + offset;
    }

    return -1;
}


671
void BinEditorWidget::drawItems(QPainter *painter, int x, int y, const QString &itemString)
con's avatar
con committed
672 673 674 675
{
    if (m_isMonospacedFont) {
        painter->drawText(x, y, itemString);
    } else {
hjk's avatar
hjk committed
676
        for (int i = 0; i < m_bytesPerLine; ++i)
con's avatar
con committed
677 678 679 680
            painter->drawText(x + i*m_columnWidth, y, itemString.mid(i*3, 2));
    }
}

681
void BinEditorWidget::drawChanges(QPainter *painter, int x, int y, const char *changes)
682 683
{
    const QBrush red(QColor(250, 150, 150));
hjk's avatar
hjk committed
684
    for (int i = 0; i < m_bytesPerLine; ++i) {
685 686 687 688 689 690 691
        if (changes[i]) {
            painter->fillRect(x + i*m_columnWidth, y - m_ascent,
                2*m_charWidth, m_lineHeight, red);
        }
    }
}

692
QString BinEditorWidget::addressString(quint64 address)
con's avatar
con committed
693 694 695
{
    QChar *addressStringData = m_addressString.data();
    const char *hex = "0123456789abcdef";
ck's avatar
ck committed
696 697 698 699 700 701

    // Take colons into account.
    const int indices[16] = {
        0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 15, 16, 17, 18
    };

702 703
    for (int b = 0; b < m_addressBytes; ++b) {
        addressStringData[indices[2*m_addressBytes - 1 - b*2]] =
704
            QLatin1Char(hex[(address >> (8*b)) & 0xf]);
705
        addressStringData[indices[2*m_addressBytes - 2 - b*2]] =
706
            QLatin1Char(hex[(address >> (8*b + 4)) & 0xf]);
con's avatar
con committed
707 708 709 710
    }
    return m_addressString;
}

711
void BinEditorWidget::paintEvent(QPaintEvent *e)
con's avatar
con committed
712 713
{
    QPainter painter(viewport());
hjk's avatar
hjk committed
714 715
    const int topLine = verticalScrollBar()->value();
    const int xoffset = horizontalScrollBar()->value();
716
    const int x1 = -xoffset + m_margin + m_labelWidth - m_charWidth/2 - 1;
hjk's avatar
hjk committed
717 718 719
    const int x2 = -xoffset + m_margin + m_labelWidth + m_bytesPerLine * m_columnWidth + m_charWidth/2;
    painter.drawLine(x1, 0, x1, viewport()->height());
    painter.drawLine(x2, 0, x2, viewport()->height());
con's avatar
con committed
720 721 722 723 724 725 726 727 728 729

    int viewport_height = viewport()->height();
    for (int i = 0; i < 8; ++i) {
        int bg_x = -xoffset +  m_margin + (2 * i + 1) * m_columnWidth + m_labelWidth;
        QRect r(bg_x - m_charWidth/2, 0, m_columnWidth, viewport_height);
        painter.fillRect(e->rect() & r, palette().alternateBase());
    }

    int matchLength = 0;

730
    QByteArray patternData, patternDataHex;
hjk's avatar
hjk committed
731
    int patternOffset = qMax(0, topLine*m_bytesPerLine - m_searchPattern.size());
732
    if (!m_searchPattern.isEmpty()) {
hjk's avatar
hjk committed
733
        patternData = dataMid(patternOffset, m_numVisibleLines * m_bytesPerLine + (topLine*m_bytesPerLine - patternOffset));
734 735 736 737 738 739
        patternDataHex = patternData;
        if (!m_caseSensitiveSearch)
            ::lower(patternData);
    }

    int foundPatternAt = findPattern(patternData, patternDataHex, patternOffset, patternOffset, &matchLength);
con's avatar
con committed
740

741 742 743 744 745 746
    int selStart, selEnd;
    if (m_cursorPosition >= m_anchorPosition) {
        selStart = m_anchorPosition;
        selEnd = m_cursorPosition;
    } else {
        selStart = m_cursorPosition;
Orgad Shaneh's avatar
Orgad Shaneh committed
747
        selEnd = m_anchorPosition;
748
    }
con's avatar
con committed
749

hjk's avatar
hjk committed
750
    QString itemString(m_bytesPerLine*3, QLatin1Char(' '));
con's avatar
con committed
751
    QChar *itemStringData = itemString.data();
hjk's avatar
hjk committed
752
    char changedString[160] = { false };
753
    QTC_ASSERT((size_t)m_bytesPerLine < sizeof(changedString), return);
con's avatar
con committed
754 755 756
    const char *hex = "0123456789abcdef";

    painter.setPen(palette().text().color());
hjk's avatar
hjk committed
757
    const QFontMetrics &fm = painter.fontMetrics();
con's avatar
con committed
758 759 760 761 762
    for (int i = 0; i <= m_numVisibleLines; ++i) {
        int line = topLine + i;
        if (line >= m_numLines)
            break;

hjk's avatar
hjk committed
763
        const quint64 lineAddress = m_baseAddr + uint(line) * m_bytesPerLine;
con's avatar
con committed
764 765 766 767 768 769
        int y = i * m_lineHeight + m_ascent;
        if (y - m_ascent > e->rect().bottom())
            break;
        if (y + m_descent < e->rect().top())
            continue;

ck's avatar
ck committed
770
        painter.drawText(-xoffset, i * m_lineHeight + m_ascent,
771
                         addressString(lineAddress));
772

con's avatar
con committed
773
        int cursor = -1;
hjk's avatar
hjk committed
774 775 776
        if (line * m_bytesPerLine <= m_cursorPosition
                && m_cursorPosition < line * m_bytesPerLine + m_bytesPerLine)
            cursor = m_cursorPosition - line * m_bytesPerLine;
777

hjk's avatar
hjk committed
778 779
        bool hasData = requestDataAt(line * m_bytesPerLine);
        bool hasOldData = requestOldDataAt(line * m_bytesPerLine);
780
        bool isOld = hasOldData && !hasData;
781 782 783

        QString printable;

784
        if (hasData || hasOldData) {
hjk's avatar
hjk committed
785 786
            for (int c = 0; c < m_bytesPerLine; ++c) {
                int pos = line * m_bytesPerLine + c;
787 788
                if (pos >= m_size)
                    break;
789
                QChar qc(QLatin1Char(dataAt(pos, isOld)));
790 791 792 793 794
                if (qc.unicode() >= 127 || !qc.isPrint())
                    qc = 0xB7;
                printable += qc;
            }
        } else {
hjk's avatar
hjk committed
795
            printable = QString(m_bytesPerLine, QLatin1Char(' '));
con's avatar
con committed
796 797 798 799 800
        }

        QRect selectionRect;
        QRect printableSelectionRect;

hjk's avatar
hjk committed
801
        bool isFullySelected = (selStart < selEnd && selStart <= line*m_bytesPerLine && (line+1)*m_bytesPerLine <= selEnd);
802
        bool somethingChanged = false;
con's avatar
con committed
803

804
        if (hasData || hasOldData) {
hjk's avatar
hjk committed
805 806
            for (int c = 0; c < m_bytesPerLine; ++c) {
                int pos = line * m_bytesPerLine + c;
807
                if (pos >= m_size) {
hjk's avatar
hjk committed
808
                    while (c < m_bytesPerLine) {
809
                        itemStringData[c*3] = itemStringData[c*3+1] = QLatin1Char(' ');
810 811 812
                        ++c;
                    }
                    break;
con's avatar
con committed
813
                }
814
                if (foundPatternAt >= 0 && pos >= foundPatternAt + matchLength)
815
                    foundPatternAt = findPattern(patternData, patternDataHex, foundPatternAt + matchLength, patternOffset, &matchLength);
con's avatar
con committed
816 817


818
                const uchar value = uchar(dataAt(pos, isOld));
819 820
                itemStringData[c*3] = QLatin1Char(hex[value >> 4]);
                itemStringData[c*3+1] = QLatin1Char(hex[value & 0xf]);
821 822 823 824
                if (hasOldData && !isOld && value != uchar(dataAt(pos, true))) {
                    changedString[c] = true;
                    somethingChanged = true;
                }
con's avatar
con committed
825

826
                int item_x = -xoffset +  m_margin + c * m_columnWidth + m_labelWidth;
con's avatar
con committed
827

828 829 830 831 832 833 834 835 836 837 838
                QColor color;
                foreach (const Markup &m, m_markup) {
                    if (m.covers(lineAddress + c)) {
                        color = m.color;
                        break;
                    }
                }
                if (foundPatternAt >= 0 && pos >= foundPatternAt && pos < foundPatternAt + matchLength)
                    color = QColor(0xffef0b);

                if (color.isValid()) {
839
                    painter.fillRect(item_x - m_charWidth/2, y-m_ascent, m_columnWidth, m_lineHeight, color);
hjk's avatar
hjk committed
840
                    int printable_item_x = -xoffset + m_margin + m_labelWidth + m_bytesPerLine * m_columnWidth + m_charWidth
hjk's avatar
hjk committed
841
                                           + fm.width(printable.left(c));
842
                    painter.fillRect(printable_item_x, y-m_ascent,
hjk's avatar
hjk committed
843
                                     fm.width(printable.at(c)),
844
                                     m_lineHeight, color);
845
                }
con's avatar
con committed
846

847 848
                if (!isFullySelected && pos >= selStart && pos <= selEnd) {
                    selectionRect |= QRect(item_x - m_charWidth/2, y-m_ascent, m_columnWidth, m_lineHeight);
hjk's avatar
hjk committed
849
                    int printable_item_x = -xoffset + m_margin + m_labelWidth + m_bytesPerLine * m_columnWidth + m_charWidth
hjk's avatar
hjk committed
850
                                           + fm.width(printable.left(c));
851
                    printableSelectionRect |= QRect(printable_item_x, y-m_ascent,
hjk's avatar
hjk committed
852
                                                    fm.width(printable.at(c)),
853 854
                                                    m_lineHeight);
                }
con's avatar
con committed
855 856 857 858 859 860 861
            }
        }

        int x = -xoffset +  m_margin + m_labelWidth;

        if (isFullySelected) {
            painter.save();
862
            painter.fillRect(x - m_charWidth/2, y-m_ascent, m_bytesPerLine*m_columnWidth, m_lineHeight, palette().highlight());
con's avatar
con committed
863 864 865 866
            painter.setPen(palette().highlightedText().color());
            drawItems(&painter, x, y, itemString);
            painter.restore();
        } else {
867 868
            if (somethingChanged)
                drawChanges(&painter, x, y, changedString);
con's avatar
con committed
869 870 871 872 873 874 875 876 877 878 879
            drawItems(&painter, x, y, itemString);
            if (!selectionRect.isEmpty()) {
                painter.save();
                painter.fillRect(selectionRect, palette().highlight());
                painter.setPen(palette().highlightedText().color());
                painter.setClipRect(selectionRect);
                drawItems(&painter, x, y, itemString);
                painter.restore();
            }
        }

880
        if (cursor >= 0) {
hjk's avatar
hjk committed
881
            int w = fm.boundingRect(itemString.mid(cursor*3, 2)).width();
con's avatar
con committed
882 883 884 885 886 887 888
            QRect cursorRect(x + cursor * m_columnWidth, y - m_ascent, w + 1, m_lineHeight);
            painter.save();
            painter.setPen(Qt::red);
            painter.drawRect(cursorRect.adjusted(0, 0, 0, -1));
            painter.restore();
            if (m_hexCursor && m_cursorVisible) {
                if (m_lowNibble)
hjk's avatar
hjk committed
889
                    cursorRect.adjust(fm.width(itemString.left(1)), 0, 0, 0);
con's avatar
con committed
890 891 892 893 894 895 896 897 898
                painter.fillRect(cursorRect, Qt::red);
                painter.save();
                painter.setClipRect(cursorRect);
                painter.setPen(Qt::white);
                drawItems(&painter, x, y, itemString);
                painter.restore();
            }
        }

hjk's avatar
hjk committed
899
        int text_x = -xoffset + m_margin + m_labelWidth + m_bytesPerLine * m_columnWidth + m_charWidth;
con's avatar
con committed
900 901