From ded5d6f36b3c15bd533df71d18a978a5c6753378 Mon Sep 17 00:00:00 2001
From: mae <qt-info@nokia.com>
Date: Tue, 21 Jul 2009 16:46:24 +0200
Subject: [PATCH] support lazy data loading in the binary editor (find
 functionality still needs porting)

---
 src/plugins/bineditor/bineditor.cpp       | 253 ++++++++++++++++------
 src/plugins/bineditor/bineditor.h         |  26 +++
 src/plugins/bineditor/bineditorplugin.cpp |  39 +++-
 3 files changed, 253 insertions(+), 65 deletions(-)

diff --git a/src/plugins/bineditor/bineditor.cpp b/src/plugins/bineditor/bineditor.cpp
index adccb2f9d05..d43a003e0c5 100644
--- a/src/plugins/bineditor/bineditor.cpp
+++ b/src/plugins/bineditor/bineditor.cpp
@@ -65,8 +65,11 @@ BinEditor::BinEditor(QWidget *parent)
     : QAbstractScrollArea(parent)
 {
     m_ieditor = 0;
+    m_inLazyMode = false;
+    m_blockSize = 4096;
     init();
     m_unmodifiedState = 0;
+    m_readOnly = false;
     m_hexCursor = true;
     m_cursorPosition = 0;
     m_anchorPosition = 0;
@@ -89,7 +92,7 @@ void BinEditor::init()
     m_lineHeight = fm.lineSpacing();
     m_charWidth = fm.width(QChar(QLatin1Char('M')));
     m_columnWidth = 2 * m_charWidth + fm.width(QChar(QLatin1Char(' ')));
-    m_numLines = m_data.size() / 16 + 1;
+    m_numLines = m_size / 16 + 1;
     m_numVisibleLines = viewport()->height() / m_lineHeight;
     m_textWidth = 16 * m_charWidth + m_charWidth;
     int m_numberWidth = fm.width(QChar(QLatin1Char('9')));
@@ -114,6 +117,63 @@ void BinEditor::init()
 }
 
 
+void BinEditor::addLazyData(int block, const QByteArray &data)
+{
+    Q_ASSERT(m_inLazyMode);
+    Q_ASSERT(data.size() == m_blockSize);
+    m_lazyData.insert(block, data);
+    m_lazyRequests.remove(block);
+    viewport()->update();
+}
+
+bool BinEditor::requestDataAt(int pos) const
+{
+    if (!m_inLazyMode)
+        return true;
+
+    int block = pos / m_blockSize;
+    QMap<int, QByteArray>::const_iterator it = m_lazyData.find(block);
+    if (it == m_lazyData.end()) {
+        if (!m_lazyRequests.contains(block)) {
+            m_lazyRequests.insert(block);
+            emit const_cast<BinEditor*>(this)->lazyDataRequested(block);
+            if (!m_lazyRequests.contains(block))
+                return true; // synchronous data source
+        }
+        return false;
+    }
+
+    return true;
+}
+
+char BinEditor::dataAt(int pos) const
+{
+    if (!m_inLazyMode)
+        return m_data.at(pos);
+
+    int block = pos / m_blockSize;
+    return m_lazyData.value(block, m_emptyBlock).at(pos - (block*m_blockSize));
+
+}
+
+void BinEditor::changeDataAt(int pos, char c)
+{
+    if (!m_inLazyMode) {
+        m_data[pos] = c;
+        return;
+    }
+    int block = pos / m_blockSize;
+    if (m_lazyData.contains(block))
+        m_lazyData[block][pos - (block*m_blockSize)] = c;
+}
+
+QByteArray BinEditor::dataMid(int from, int length) const
+{
+    if (!m_inLazyMode)
+        return m_data.mid(from, length);
+    return QByteArray(length, '\0'); // ### TODO
+}
+
 void BinEditor::setFontSettings(const TextEditor::FontSettings &fs)
 {
     setFont(fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TEXT)).font());
@@ -188,15 +248,33 @@ bool BinEditor::isModified() const
     return (m_undoStack.size() != m_unmodifiedState);
 }
 
+void BinEditor::setReadOnly(bool readOnly)
+{
+    m_readOnly = readOnly;
+}
+
+bool BinEditor::isReadOnly() const
+{
+    return m_readOnly;
+}
+
 void BinEditor::setData(const QByteArray &data)
 {
+    m_inLazyMode = false;
+    m_lazyData.clear();
+    m_lazyRequests.clear();
     m_data = data;
+    m_size = data.size();
+
     m_unmodifiedState = 0;
     m_undoStack.clear();
     m_redoStack.clear();
+
     init();
-    emit cursorPositionChanged(m_cursorPosition);
+    m_cursorPosition = 0;
+    verticalScrollBar()->setValue(0);
 
+    emit cursorPositionChanged(m_cursorPosition);
     viewport()->update();
 }
 
@@ -205,6 +283,43 @@ QByteArray BinEditor::data() const
     return m_data;
 }
 
+bool BinEditor::applyModifications(QByteArray &data) const
+{
+    if (!m_inLazyMode) {
+        data = m_data;
+        return true;
+    }
+    if (data.size() != m_size)
+        return false;
+    for (QMap<int,QByteArray>::const_iterator it = m_lazyData.begin(); it != m_lazyData.end(); ++it) {
+        ::memcpy(data.data() + it.key() * m_blockSize, it->constData(), m_blockSize);
+    }
+    return true;
+}
+
+void BinEditor::setLazyData(int cursorPosition, int size, int blockSize)
+{
+    m_inLazyMode = true;
+    m_blockSize = blockSize;
+    m_emptyBlock = QByteArray(blockSize, '\0');
+    m_data.clear();
+    m_lazyData.clear();
+    m_lazyRequests.clear();
+    m_size = size;
+
+    m_unmodifiedState = 0;
+    m_undoStack.clear();
+    m_redoStack.clear();
+
+    init();
+
+    m_cursorPosition = cursorPosition;
+    verticalScrollBar()->setValue(m_cursorPosition / 16);
+
+    emit cursorPositionChanged(m_cursorPosition);
+    viewport()->update();
+}
+
 void BinEditor::resizeEvent(QResizeEvent *)
 {
     init();
@@ -269,9 +384,9 @@ int BinEditor::posAt(const QPoint &pos) const
         x -= 16 * m_columnWidth + m_charWidth;
         for (column = 0; column < 15; ++column) {
             int pos = (topLine + line) * 16 + column;
-            if (pos < 0 || pos >= m_data.size())
+            if (pos < 0 || pos >= m_size)
                 break;
-            QChar qc(QLatin1Char(m_data.at(pos)));
+            QChar qc(QLatin1Char(dataAt(pos)));
             if (!qc.isPrint())
                 qc = 0xB7;
             x -= fontMetrics().width(qc);
@@ -280,7 +395,7 @@ int BinEditor::posAt(const QPoint &pos) const
         }
     }
 
-    return (qMin(m_data.size(), qMin(m_numLines, topLine + line) * 16) + column);
+    return (qMin(m_size, qMin(m_numLines, topLine + line) * 16) + column);
 }
 
 bool BinEditor::inTextArea(const QPoint &pos) const
@@ -401,14 +516,14 @@ void BinEditor::paintEvent(QPaintEvent *e)
     QByteArray patternData;
     int patternOffset = qMax(0, topLine*16 - m_searchPattern.size());
     if (!m_searchPattern.isEmpty())
-        patternData = m_data.mid(patternOffset, m_numVisibleLines * 16);
+        patternData = dataMid(patternOffset, m_numVisibleLines * 16 + (topLine*16 - patternOffset));
 
     int foundPatternAt = findPattern(patternData, patternOffset, patternOffset, &matchLength);
 
     int selStart = qMin(m_cursorPosition, m_anchorPosition);
     int selEnd = qMax(m_cursorPosition, m_anchorPosition);
 
-    QString itemString(QLatin1String("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"));
+    QString itemString(16*3, QLatin1Char(' '));
     QChar *itemStringData = itemString.data();
     const char *hex = "0123456789abcdef";
 
@@ -426,16 +541,27 @@ void BinEditor::paintEvent(QPaintEvent *e)
 
 
         painter.drawText(-xoffset, i * m_lineHeight + m_ascent, addressString(((uint) line) * 16));
-        QString printable;
+
         int cursor = -1;
-        for (int c = 0; c < 16; ++c) {
-            int pos = line * 16 + c;
-            if (pos >= m_data.size())
-                break;
-            QChar qc(QLatin1Char(m_data.at(pos)));
-            if (qc.unicode() >= 127 || !qc.isPrint())
-                qc = 0xB7;
-            printable += qc;
+        if (line * 16 <= m_cursorPosition && m_cursorPosition < line * 16 + 16)
+            cursor = m_cursorPosition - line * 16;
+
+        bool hasData = requestDataAt(line * 16);
+
+        QString printable;
+
+        if (hasData) {
+            for (int c = 0; c < 16; ++c) {
+                int pos = line * 16 + c;
+                if (pos >= m_size)
+                    break;
+                QChar qc(QLatin1Char(dataAt(pos)));
+                if (qc.unicode() >= 127 || !qc.isPrint())
+                    qc = 0xB7;
+                printable += qc;
+            }
+        } else {
+            printable = QString(16, QLatin1Char(' '));
         }
 
         QRect selectionRect;
@@ -443,47 +569,45 @@ void BinEditor::paintEvent(QPaintEvent *e)
 
         bool isFullySelected = (selStart < selEnd && selStart <= line*16 && (line+1)*16 <= selEnd);
 
-        for (int c = 0; c < 16; ++c) {
-            int pos = line * 16 + c;
-            if (pos >= m_data.size()) {
-                while (c < 16) {
-                    itemStringData[c*3] = itemStringData[c*3+1] = ' ';
-                    ++c;
+        if (hasData) {
+            for (int c = 0; c < 16; ++c) {
+                int pos = line * 16 + c;
+                if (pos >= m_size) {
+                    while (c < 16) {
+                        itemStringData[c*3] = itemStringData[c*3+1] = ' ';
+                        ++c;
+                    }
+                    break;
                 }
-                break;
-            }
 
-            if (foundPatternAt >= 0 && pos >= foundPatternAt + matchLength)
-                foundPatternAt = findPattern(patternData, foundPatternAt + matchLength, patternOffset, &matchLength);
+                if (foundPatternAt >= 0 && pos >= foundPatternAt + matchLength)
+                    foundPatternAt = findPattern(patternData, foundPatternAt + matchLength, patternOffset, &matchLength);
 
 
-            uchar value = (uchar)m_data.at(pos);
-            itemStringData[c*3] = hex[value >> 4];
-            itemStringData[c*3+1] = hex[value & 0xf];
+                uchar value = (uchar)dataAt(pos);
+                itemStringData[c*3] = hex[value >> 4];
+                itemStringData[c*3+1] = hex[value & 0xf];
 
-            int item_x = -xoffset +  m_margin + c * m_columnWidth + m_labelWidth;
+                int item_x = -xoffset +  m_margin + c * m_columnWidth + m_labelWidth;
 
-            if (foundPatternAt >= 0 && pos >= foundPatternAt && pos < foundPatternAt + matchLength) {
-                painter.fillRect(item_x, y-m_ascent, m_columnWidth, m_lineHeight, QColor(0xffef0b));
-                int printable_item_x = -xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth
-                                       + painter.fontMetrics().width( printable.left(c));
-                painter.fillRect(printable_item_x, y-m_ascent,
-                                 painter.fontMetrics().width(printable.at(c)),
-                                 m_lineHeight, QColor(0xffef0b));
-            }
+                if (foundPatternAt >= 0 && pos >= foundPatternAt && pos < foundPatternAt + matchLength) {
+                    painter.fillRect(item_x, y-m_ascent, m_columnWidth, m_lineHeight, QColor(0xffef0b));
+                    int printable_item_x = -xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth
+                                           + painter.fontMetrics().width( printable.left(c));
+                    painter.fillRect(printable_item_x, y-m_ascent,
+                                     painter.fontMetrics().width(printable.at(c)),
+                                     m_lineHeight, QColor(0xffef0b));
+                }
 
-            if (selStart < selEnd && !isFullySelected && pos >= selStart && pos < selEnd) {
-                selectionRect |= QRect(item_x, y-m_ascent, m_columnWidth, m_lineHeight);
-                int printable_item_x = -xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth
-                                       + painter.fontMetrics().width( printable.left(c));
-                printableSelectionRect |= QRect(printable_item_x, y-m_ascent,
-                                                painter.fontMetrics().width(printable.at(c)),
-                                                m_lineHeight);
+                if (selStart < selEnd && !isFullySelected && pos >= selStart && pos < selEnd) {
+                    selectionRect |= QRect(item_x, y-m_ascent, m_columnWidth, m_lineHeight);
+                    int printable_item_x = -xoffset + m_margin + m_labelWidth + 16 * m_columnWidth + m_charWidth
+                                           + painter.fontMetrics().width( printable.left(c));
+                    printableSelectionRect |= QRect(printable_item_x, y-m_ascent,
+                                                    painter.fontMetrics().width(printable.at(c)),
+                                                    m_lineHeight);
+                }
             }
-
-            if (pos == m_cursorPosition)
-                cursor = c;
-
         }
 
         int x = -xoffset +  m_margin + m_labelWidth;
@@ -575,7 +699,7 @@ int BinEditor::cursorPosition() const
 
 void BinEditor::setCursorPosition(int pos, MoveMode moveMode)
 {
-    pos = qMin(m_data.size()-1, qMax(0, pos));
+    pos = qMin(m_size-1, qMax(0, pos));
     if (pos == m_cursorPosition
         && (m_anchorPosition == m_cursorPosition || moveMode == KeepAnchor)
         && !m_lowNibble)
@@ -654,7 +778,7 @@ void BinEditor::mouseReleaseEvent(QMouseEvent *)
 void BinEditor::selectAll()
 {
     setCursorPosition(0);
-    setCursorPosition(m_data.size()-1, KeepAnchor);
+    setCursorPosition(m_size-1, KeepAnchor);
 }
 
 void BinEditor::clear()
@@ -728,10 +852,13 @@ void BinEditor::keyPressEvent(QKeyEvent *e)
         break;
     case Qt::Key_End:
         setCursorPosition((e->modifiers() & Qt::ControlModifier) ?
-                          (m_data.size()-1) : (m_cursorPosition/16 * 16 + 15), moveMode);
+                          (m_size-1) : (m_cursorPosition/16 * 16 + 15), moveMode);
         break;
 
-    default: {
+    default:
+        if (m_readOnly)
+            break;
+        {
         QString text = e->text();
         for (int i = 0; i < text.length(); ++i) {
             QChar c = text.at(i);
@@ -745,11 +872,11 @@ void BinEditor::keyPressEvent(QKeyEvent *e)
                 if (nibble < 0)
                     continue;
                 if (m_lowNibble) {
-                    changeData(m_cursorPosition, nibble + (m_data[m_cursorPosition] & 0xf0));
+                    changeData(m_cursorPosition, nibble + (dataAt(m_cursorPosition) & 0xf0));
                     m_lowNibble = false;
                     setCursorPosition(m_cursorPosition + 1);
                 } else {
-                    changeData(m_cursorPosition, (nibble << 4) + (m_data[m_cursorPosition] & 0x0f), true);
+                    changeData(m_cursorPosition, (nibble << 4) + (dataAt(m_cursorPosition) & 0x0f), true);
                     m_lowNibble = true;
                     updateLines();
                 }
@@ -787,7 +914,7 @@ void BinEditor::copy()
     int selStart = qMin(m_cursorPosition, m_anchorPosition);
     int selEnd = qMax(m_cursorPosition, m_anchorPosition);
     if (selStart < selEnd)
-        QApplication::clipboard()->setText(QString::fromLatin1(m_data.mid(selStart, selEnd - selStart)));
+        QApplication::clipboard()->setText(QString::fromLatin1(dataMid(selStart, selEnd - selStart)));
 }
 
 void BinEditor::highlightSearchResults(const QByteArray &pattern, QTextDocument::FindFlags /*findFlags*/)
@@ -802,12 +929,14 @@ void BinEditor::highlightSearchResults(const QByteArray &pattern, QTextDocument:
 
 void BinEditor::changeData(int position, uchar character, bool highNibble)
 {
+    if (!requestDataAt(position))
+        return;
     m_redoStack.clear();
     if (m_unmodifiedState > m_undoStack.size())
         m_unmodifiedState = -1;
     BinEditorEditCommand cmd;
     cmd.position = position;
-    cmd.character = (uchar) m_data[position];
+    cmd.character = (uchar) dataAt(position);
     cmd.highNibble = highNibble;
 
     if (!highNibble && !m_undoStack.isEmpty() && m_undoStack.top().position == position && m_undoStack.top().highNibble) {
@@ -816,7 +945,7 @@ void BinEditor::changeData(int position, uchar character, bool highNibble)
         m_undoStack.pop();
     }
 
-    m_data[position] = (char) character;
+    changeDataAt(position, (char) character);
     bool emitModificationChanged = (m_undoStack.size() == m_unmodifiedState);
     m_undoStack.push(cmd);
     if (emitModificationChanged) {
@@ -835,8 +964,8 @@ void BinEditor::undo()
     bool emitModificationChanged = (m_undoStack.size() == m_unmodifiedState);
     BinEditorEditCommand cmd = m_undoStack.pop();
     emitModificationChanged |= (m_undoStack.size() == m_unmodifiedState);
-    uchar c = m_data[cmd.position];
-    m_data[cmd.position] = (char)cmd.character;
+    uchar c = dataAt(cmd.position);
+    changeDataAt(cmd.position, (char)cmd.character);
     cmd.character = c;
     m_redoStack.push(cmd);
     setCursorPosition(cmd.position);
@@ -853,8 +982,8 @@ void BinEditor::redo()
     if (m_redoStack.isEmpty())
         return;
     BinEditorEditCommand cmd = m_redoStack.pop();
-    uchar c = m_data[cmd.position];
-    m_data[cmd.position] = (char)cmd.character;
+    uchar c = dataAt(cmd.position);
+    changeDataAt(cmd.position, (char)cmd.character);
     cmd.character = c;
     bool emitModificationChanged = (m_undoStack.size() == m_unmodifiedState);
     m_undoStack.push(cmd);
diff --git a/src/plugins/bineditor/bineditor.h b/src/plugins/bineditor/bineditor.h
index 64955804137..5f95514330d 100644
--- a/src/plugins/bineditor/bineditor.h
+++ b/src/plugins/bineditor/bineditor.h
@@ -33,6 +33,7 @@
 #include <QtGui/qabstractscrollarea.h>
 #include <QtCore/qbasictimer.h>
 #include <QtCore/qstack.h>
+#include <QtCore/qset.h>
 #include <QtGui/qtextdocument.h>
 #include <QtGui/qtextformat.h>
 
@@ -50,6 +51,7 @@ class BinEditor : public QAbstractScrollArea
 {
     Q_OBJECT
     Q_PROPERTY(bool modified READ isModified WRITE setModified DESIGNABLE false)
+    Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE false)
 public:
 
     BinEditor(QWidget *parent = 0);
@@ -58,6 +60,12 @@ public:
     void setData(const QByteArray &data);
     QByteArray data() const;
 
+    inline bool inLazyMode() const { return m_inLazyMode; }
+    void setLazyData(int cursorPosition, int size, int blockSize = 4096);
+    inline int lazyDataBlockSize() const { return m_blockSize; }
+    void addLazyData(int block, const QByteArray &data);
+    bool applyModifications(QByteArray &data) const;
+
     void zoomIn(int range = 1);
     void zoomOut(int range = 1);
 
@@ -72,6 +80,9 @@ public:
     void setModified(bool);
     bool isModified() const;
 
+    void setReadOnly(bool);
+    bool isReadOnly() const;
+
     int find(const QByteArray &pattern, int from = 0, QTextDocument::FindFlags findFlags = 0);
 
     void selectAll();
@@ -107,6 +118,8 @@ Q_SIGNALS:
     void copyAvailable(bool);
     void cursorPositionChanged(int position);
 
+    void lazyDataRequested(int block);
+
 protected:
     void scrollContentsBy(int dx, int dy);
     void paintEvent(QPaintEvent *e);
@@ -122,8 +135,21 @@ protected:
     void timerEvent(QTimerEvent *);
 
 private:
+    bool m_inLazyMode;
     QByteArray m_data;
+    QMap <int, QByteArray> m_lazyData;
+    int m_blockSize;
+    mutable QSet<int> m_lazyRequests;
+    QByteArray m_emptyBlock;
+    int m_size;
+
+    bool requestDataAt(int pos) const;
+    char dataAt(int pos) const;
+    void changeDataAt(int pos, char c);
+    QByteArray dataMid(int from, int length) const;
+
     int m_unmodifiedState;
+    int m_readOnly;
     int m_margin;
     int m_descent;
     int m_ascent;
diff --git a/src/plugins/bineditor/bineditorplugin.cpp b/src/plugins/bineditor/bineditorplugin.cpp
index 4f854b71381..9e873d3f7ac 100644
--- a/src/plugins/bineditor/bineditorplugin.cpp
+++ b/src/plugins/bineditor/bineditorplugin.cpp
@@ -33,6 +33,7 @@
 
 #include <QtCore/QFile>
 #include <QtCore/QFileInfo>
+#include <QtCore/QDebug>
 #include <QtGui/QMenu>
 #include <QtGui/QAction>
 #include <QtGui/QMainWindow>
@@ -136,6 +137,7 @@ public:
         m_mimeType(QLatin1String(BINEditor::Constants::C_BINEDITOR_MIMETYPE))
     {
         m_editor = parent;
+        connect(m_editor, SIGNAL(lazyDataRequested(int)), this, SLOT(provideData(int)));
     }
     ~BinEditorFile() {}
 
@@ -143,8 +145,21 @@ public:
 
     bool save(const QString &fileName = QString()) {
         QFile file(fileName);
+
+        QByteArray data;
+        if (m_editor->inLazyMode()) {
+            QFile read(m_fileName);
+            if (!read.open(QIODevice::ReadOnly))
+                return false;
+            data = read.readAll();
+            read.close();
+            if (!m_editor->applyModifications(data))
+                return false;
+        } else {
+            data = m_editor->data();
+        }
         if (file.open(QIODevice::WriteOnly)) {
-            file.write(m_editor->data());
+            file.write(data);
             file.close();
             m_editor->setModified(false);
             m_editor->editorInterface()->setDisplayName(QFileInfo(fileName).fileName());
@@ -159,14 +174,32 @@ public:
         QFile file(fileName);
         if (file.open(QIODevice::ReadOnly)) {
             m_fileName = fileName;
-            m_editor->setData(file.readAll());
-            m_editor->editorInterface()->setDisplayName(QFileInfo(fileName).fileName());
+            if (file.isSequential()) {
+                m_editor->setData(file.readAll());
+            } else {
+                m_editor->setLazyData(0, file.size());
+                m_editor->editorInterface()->setDisplayName(QFileInfo(fileName).fileName());
+            }
             file.close();
             return true;
         }
         return false;
     }
 
+private slots:
+    void provideData(int block) {
+        QFile file(m_fileName);
+        if (file.open(QIODevice::ReadOnly)) {
+            int blockSize = m_editor->lazyDataBlockSize();
+            file.seek(block * blockSize);
+            QByteArray data = file.read(blockSize);
+            if (data.size() != blockSize)
+                data.resize(blockSize);
+            m_editor->addLazyData(block, data);
+            file.close();
+        }
+    }
+public:
 
     void setFilename(const QString &filename) {
         m_fileName = filename;
-- 
GitLab