src/eric7/HexEdit/HexEditWidget.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9171
fd2218f08066
child 9221
bf71ee032bb4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/HexEdit/HexEditWidget.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,1682 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an editor for binary data.
+"""
+
+import math
+
+from PyQt6.QtCore import (
+    pyqtSignal, pyqtSlot, Qt, QByteArray, QTimer, QRect, QBuffer, QIODevice
+)
+from PyQt6.QtGui import QFont, QPalette, QKeySequence, QPainter
+from PyQt6.QtWidgets import QAbstractScrollArea, QApplication
+
+from EricWidgets.EricApplication import ericApp
+
+from .HexEditChunks import HexEditChunks
+from .HexEditUndoStack import HexEditUndoStack
+
+import Globals
+
+
+class HexEditWidget(QAbstractScrollArea):
+    """
+    Class implementing an editor for binary data.
+    
+    @signal currentAddressChanged(address) emitted to indicate the new
+        cursor position
+    @signal currentSizeChanged(size) emitted to indicate the new size of
+        the data
+    @signal dataChanged(modified) emitted to indicate a change of the data
+    @signal overwriteModeChanged(state) emitted to indicate a change of
+        the overwrite mode
+    @signal readOnlyChanged(state) emitted to indicate a change of the
+        read only state
+    @signal canRedoChanged(bool) emitted after the redo status has changed
+    @signal canUndoChanged(bool) emitted after the undo status has changed
+    @signal selectionAvailable(bool) emitted to signal a change of the
+        selection
+    """
+    currentAddressChanged = pyqtSignal(int)
+    currentSizeChanged = pyqtSignal(int)
+    dataChanged = pyqtSignal(bool)
+    overwriteModeChanged = pyqtSignal(bool)
+    readOnlyChanged = pyqtSignal(bool)
+    canRedoChanged = pyqtSignal(bool)
+    canUndoChanged = pyqtSignal(bool)
+    selectionAvailable = pyqtSignal(bool)
+    
+    HEXCHARS_PER_LINE = 47
+    BYTES_PER_LINE = 16
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent refernce to the parent widget
+        @type QWidget
+        """
+        super().__init__(parent)
+        
+        # Properties
+        self.__addressArea = True
+        # switch the address area on/off
+        self.__addressOffset = 0
+        # offset into the shown address range
+        self.__addressWidth = 4
+        # address area width in characters
+        self.__asciiArea = True
+        # switch the ASCII area on/off
+        self.__data = bytearray()
+        # contents of the hex editor
+        self.__highlighting = True
+        # switch the highlighting feature on/off
+        self.__overwriteMode = True
+        # set overwrite mode on/off
+        self.__readOnly = False
+        # set read only mode on/off
+        self.__cursorPosition = 0
+        # absolute position of cursor, 1 Byte == 2 tics
+        
+        self.__addrDigits = 0
+        self.__addrSeparators = 0
+        self.__blink = True
+        self.__bData = QBuffer()
+        self.__cursorRect = QRect()
+        self.__cursorRectAscii = QRect()
+        self.__dataShown = bytearray()
+        self.__hexDataShown = bytearray()
+        self.__lastEventSize = 0
+        self.__markedShown = bytearray()
+        self.__modified = False
+        self.__rowsShown = 0
+        
+        # pixel related attributes (starting with __px)
+        self.__pxCharWidth = 0
+        self.__pxCharHeight = 0
+        self.__pxPosHexX = 0
+        self.__pxPosAdrX = 0
+        self.__pxPosAsciiX = 0
+        self.__pxGapAdr = 0
+        self.__pxGapAdrHex = 0
+        self.__pxGapHexAscii = 0
+        self.__pxSelectionSub = 0
+        self.__pxCursorWidth = 0
+        self.__pxCursorX = 0
+        self.__pxCursorY = 0
+        
+        # absolute byte position related attributes (starting with __b)
+        self.__bSelectionBegin = 0
+        self.__bSelectionEnd = 0
+        self.__bSelectionInit = 0
+        self.__bPosFirst = 0
+        self.__bPosLast = 0
+        self.__bPosCurrent = 0
+        
+        self.__chunks = HexEditChunks()
+        self.__undoStack = HexEditUndoStack(self.__chunks, self)
+        if Globals.isWindowsPlatform():
+            self.setFont(QFont(["Courier"], 10))
+        else:
+            self.setFont(QFont(["Monospace"], 10))
+        
+        self.__cursorTimer = QTimer()
+        self.__cursorTimer.timeout.connect(self.__updateCursor)
+        
+        self.verticalScrollBar().valueChanged.connect(self.__adjust)
+        
+        self.__undoStack.indexChanged.connect(self.__dataChangedPrivate)
+        self.__undoStack.canRedoChanged.connect(self.__canRedoChanged)
+        self.__undoStack.canUndoChanged.connect(self.__canUndoChanged)
+        
+        self.readOnlyChanged.connect(self.__canRedoChanged)
+        self.readOnlyChanged.connect(self.__canUndoChanged)
+        
+        self.__cursorTimer.setInterval(500)
+        self.__cursorTimer.start()
+        
+        self.setAddressWidth(4)
+        self.setAddressArea(True)
+        self.setAsciiArea(True)
+        self.setOverwriteMode(True)
+        self.setHighlighting(True)
+        self.setReadOnly(False)
+        
+        self.__initialize()
+    
+    def undoStack(self):
+        """
+        Public method to get a reference to the undo stack.
+        
+        @return reference to the undo stack
+        @rtype HexEditUndoStack
+        """
+        return self.__undoStack
+    
+    @pyqtSlot()
+    def __canRedoChanged(self):
+        """
+        Private slot handling changes of the Redo state.
+        """
+        self.canRedoChanged.emit(
+            self.__undoStack.canRedo() and not self.__readOnly)
+    
+    @pyqtSlot()
+    def __canUndoChanged(self):
+        """
+        Private slot handling changes of the Undo state.
+        """
+        self.canUndoChanged.emit(
+            self.__undoStack.canUndo() and not self.__readOnly)
+    
+    def addressArea(self):
+        """
+        Public method to get the address area visibility.
+        
+        @return flag indicating the address area visibility
+        @rtype bool
+        """
+        return self.__addressArea
+    
+    def setAddressArea(self, on):
+        """
+        Public method to set the address area visibility.
+        
+        @param on flag indicating the address area visibility
+        @type bool
+        """
+        self.__addressArea = on
+        self.__adjust()
+        self.setCursorPosition(self.__cursorPosition)
+        self.viewport().update()
+    
+    def addressOffset(self):
+        """
+        Public method to get the address offset.
+        
+        @return address offset
+        @rtype int
+        """
+        return self.__addressOffset
+    
+    def setAddressOffset(self, offset):
+        """
+        Public method to set the address offset.
+        
+        @param offset address offset
+        @type int
+        """
+        self.__addressOffset = offset
+        self.__adjust()
+        self.setCursorPosition(self.__cursorPosition)
+        self.viewport().update()
+    
+    def addressWidth(self):
+        """
+        Public method to get the width of the address area in
+        characters.
+        
+        Note: The address area width is always a multiple of four.
+        
+        @return minimum width of the address area
+        @rtype int
+        """
+        size = self.__chunks.size()
+        n = 1
+        if size > 0x100000000:
+            n += 8
+            size //= 0x100000000
+        if size > 0x10000:
+            n += 4
+            size //= 0x10000
+        if size > 0x100:
+            n += 2
+            size //= 0x100
+        if size > 0x10:
+            n += 1
+            size //= 0x10
+        n = int(math.ceil(n / 4)) * 4
+        
+        if n > self.__addressWidth:
+            return n
+        else:
+            return self.__addressWidth
+    
+    def setAddressWidth(self, width):
+        """
+        Public method to set the width of the address area.
+        
+        Note: The address area width is always a multiple of four.
+        The given value will be adjusted as required.
+        
+        @param width width of the address area in characters
+        @type int
+        """
+        self.__addressWidth = int(math.ceil(width / 4)) * 4
+        self.__adjust()
+        self.setCursorPosition(self.__cursorPosition)
+        self.viewport().update()
+    
+    def asciiArea(self):
+        """
+        Public method to get the visibility of the ASCII area.
+        
+        @return visibility of the ASCII area
+        @rtype bool
+        """
+        return self.__asciiArea
+    
+    def setAsciiArea(self, on):
+        """
+        Public method to set the visibility of the ASCII area.
+        
+        @param on flag indicating the visibility of the ASCII area
+        @type bool
+        """
+        self.__asciiArea = on
+        self.viewport().update()
+    
+    def cursorPosition(self):
+        """
+        Public method to get the cursor position.
+        
+        @return cursor position
+        @rtype int
+        """
+        return self.__cursorPosition
+    
+    def setCursorPosition(self, pos):
+        """
+        Public method to set the cursor position.
+        
+        @param pos cursor position
+        @type int
+        """
+        # step 1: delete old cursor
+        self.__blink = False
+        self.viewport().update(self.__cursorRect)
+        if self.__asciiArea:
+            self.viewport().update(self.__cursorRectAscii)
+        
+        # step 2: check, if cursor is in range
+        if self.__overwriteMode and pos > (self.__chunks.size() * 2 - 1):
+            pos = self.__chunks.size() * 2 - 1
+        if (not self.__overwriteMode) and pos > (self.__chunks.size() * 2):
+            pos = self.__chunks.size() * 2
+        if pos < 0:
+            pos = 0
+        
+        # step 3: calculate new position of cursor
+        self.__cursorPosition = pos
+        self.__bPosCurrent = pos // 2
+        self.__pxCursorY = (
+            ((pos // 2 - self.__bPosFirst) // self.BYTES_PER_LINE + 1) *
+            self.__pxCharHeight)
+        x = (pos % (2 * self.BYTES_PER_LINE))
+        self.__pxCursorX = (
+            (((x // 2) * 3) + (x % 2)) * self.__pxCharWidth + self.__pxPosHexX)
+        
+        self.__setHexCursorRect()
+        
+        # step 4: calculate position of ASCII cursor
+        x = self.__bPosCurrent % self.BYTES_PER_LINE
+        self.__cursorRectAscii = QRect(
+            self.__pxPosAsciiX + x * self.__pxCharWidth - 1,
+            self.__pxCursorY - self.__pxCharHeight + 4,
+            self.__pxCharWidth + 1, self.__pxCharHeight + 1)
+        
+        # step 5: draw new cursors
+        self.__blink = True
+        self.viewport().update(self.__cursorRect)
+        if self.__asciiArea:
+            self.viewport().update(self.__cursorRectAscii)
+        self.currentAddressChanged.emit(self.__bPosCurrent)
+    
+    def __setHexCursorRect(self):
+        """
+        Private method to set the cursor.
+        """
+        if self.__overwriteMode:
+            self.__cursorRect = QRect(
+                self.__pxCursorX, self.__pxCursorY + self.__pxCursorWidth,
+                self.__pxCharWidth, self.__pxCursorWidth)
+        else:
+            self.__cursorRect = QRect(
+                self.__pxCursorX, self.__pxCursorY - self.__pxCharHeight + 4,
+                self.__pxCursorWidth, self.__pxCharHeight)
+    
+    def cursorBytePosition(self):
+        """
+        Public method to get the cursor position in bytes.
+        
+        @return cursor position in bytes
+        @rtype int
+        """
+        return self.__bPosCurrent
+    
+    def setCursorBytePosition(self, pos):
+        """
+        Public method to set the cursor position in bytes.
+        
+        @param pos cursor position in bytes
+        @type int
+        """
+        self.setCursorPosition(pos * 2)
+    
+    def goto(self, offset, fromCursor=False, backwards=False,
+             extendSelection=False):
+        """
+        Public method to move the cursor.
+        
+        @param offset offset to move to
+        @type int
+        @param fromCursor flag indicating a move relative to the current cursor
+        @type bool
+        @param backwards flag indicating a backwards move
+        @type bool
+        @param extendSelection flag indicating to extend the selection
+        @type bool
+        """
+        if fromCursor:
+            if backwards:
+                newPos = self.cursorBytePosition() - offset
+            else:
+                newPos = self.cursorBytePosition() + offset
+        else:
+            if backwards:
+                newPos = self.__chunks.size() - offset
+            else:
+                newPos = offset
+        
+        self.setCursorBytePosition(newPos)
+        if extendSelection:
+            self.__setSelection(self.__cursorPosition)
+        else:
+            self.__resetSelection(self.__cursorPosition)
+        
+        self.__refresh()
+    
+    def data(self):
+        """
+        Public method to get the binary data.
+        
+        @return binary data
+        @rtype bytearray
+        """
+        return self.__chunks.data(0, -1)
+    
+    def setData(self, dataOrDevice):
+        """
+        Public method to set the data to show.
+        
+        @param dataOrDevice byte array or device containing the data
+        @type bytes, bytearray, QByteArray or QIODevice
+        @return flag indicating success
+        @rtype bool
+        @exception TypeError raised to indicate a wrong parameter type
+        """
+        if not isinstance(dataOrDevice, (bytes, bytearray, QByteArray,
+                                         QIODevice)):
+            raise TypeError(
+                "setData: parameter must be bytes, bytearray, "
+                "QByteArray or QIODevice")
+        
+        if isinstance(dataOrDevice, (bytes, bytearray, QByteArray)):
+            self.__data = bytearray(dataOrDevice)
+            self.__bData.setData(self.__data)
+            return self.__setData(self.__bData)
+        else:
+            return self.__setData(dataOrDevice)
+    
+    def __setData(self, ioDevice):
+        """
+        Private method to set the data to show.
+        
+        @param ioDevice device containing the data
+        @type QIODevice
+        @return flag indicating success
+        @rtype bool
+        """
+        ok = self.__chunks.setIODevice(ioDevice)
+        self.__initialize()
+        self.__dataChangedPrivate()
+        return ok
+    
+    def highlighting(self):
+        """
+        Public method to get the highlighting state.
+        
+        @return highlighting state
+        @rtype bool
+        """
+        return self.__highlighting
+    
+    def setHighlighting(self, on):
+        """
+        Public method to set the highlighting state.
+        
+        @param on new highlighting state
+        @type bool
+        """
+        self.__highlighting = on
+        self.viewport().update()
+    
+    def overwriteMode(self):
+        """
+        Public method to get the overwrite mode.
+        
+        @return overwrite mode
+        @rtype bool
+        """
+        return self.__overwriteMode
+    
+    def setOverwriteMode(self, on):
+        """
+        Public method to set the overwrite mode.
+        
+        @param on flag indicating the new overwrite mode
+        @type bool
+        """
+        self.__overwriteMode = on
+        self.overwriteModeChanged.emit(self.__overwriteMode)
+        
+        # step 1: delete old cursor
+        self.__blink = False
+        self.viewport().update(self.__cursorRect)
+        # step 2: change the cursor rectangle
+        self.__setHexCursorRect()
+        # step 3: draw new cursors
+        self.__blink = True
+        self.viewport().update(self.__cursorRect)
+    
+    def isReadOnly(self):
+        """
+        Public method to test the read only state.
+        
+        @return flag indicating the read only state
+        @rtype bool
+        """
+        return self.__readOnly
+    
+    def setReadOnly(self, on):
+        """
+        Public method to set the read only state.
+        
+        @param on new read only state
+        @type bool
+        """
+        self.__readOnly = on
+        self.readOnlyChanged.emit(self.__readOnly)
+    
+    def font(self):
+        """
+        Public method to get the font used to show the data.
+        
+        @return font used to show the data
+        @rtype QFont
+        """
+        return super().font()
+    
+    def setFont(self, font):
+        """
+        Public method to set the font used to show the data.
+        
+        @param font font used to show the data
+        @type QFont
+        """
+        super().setFont(font)
+        try:
+            self.__pxCharWidth = self.fontMetrics().horizontalAdvance("2")
+        except AttributeError:
+            self.__pxCharWidth = self.fontMetrics().width("2")
+        self.__pxCharHeight = self.fontMetrics().height()
+        self.__pxGapAdr = self.__pxCharWidth // 2
+        self.__pxGapAdrHex = self.__pxCharWidth
+        self.__pxGapHexAscii = 2 * self.__pxCharWidth
+        self.__pxCursorWidth = self.__pxCharHeight // 7
+        self.__pxSelectionSub = self.fontMetrics().descent()
+        self.__adjust()
+        self.viewport().update()
+    
+    def dataAt(self, pos, count=-1):
+        """
+        Public method to get data from a given position.
+        
+        @param pos position to get data from
+        @type int
+        @param count amount of bytes to get
+        @type int
+        @return requested data
+        @rtype bytearray
+        """
+        return bytearray(self.__chunks.data(pos, count))
+    
+    def write(self, device, pos=0, count=-1):
+        """
+        Public method to write data from a given position to a device.
+        
+        @param device device to write to
+        @type QIODevice
+        @param pos position to start the write at
+        @type int
+        @param count amount of bytes to write
+        @type int
+        @return flag indicating success
+        @rtype bool
+        """
+        return self.__chunks.write(device, pos, count)
+    
+    def insert(self, pos, ch):
+        """
+        Public method to insert a byte.
+        
+        @param pos position to insert the byte at
+        @type int
+        @param ch byte to insert
+        @type int in the range 0x00 to 0xff
+        """
+        if ch in range(0, 256):
+            self.__undoStack.insert(pos, ch)
+            self.__refresh()
+    
+    def remove(self, pos, length=1):
+        """
+        Public method to remove bytes.
+        
+        @param pos position to remove bytes from
+        @type int
+        @param length amount of bytes to remove
+        @type int
+        """
+        self.__undoStack.removeAt(pos, length)
+        self.__refresh()
+    
+    def replace(self, pos, ch):
+        """
+        Public method to replace a byte.
+        
+        @param pos position to replace the byte at
+        @type int
+        @param ch byte to replace with
+        @type int in the range 0x00 to 0xff
+        """
+        if ch in range(0, 256):
+            self.__undoStack.overwrite(pos, ch)
+            self.__refresh()
+    
+    def insertByteArray(self, pos, byteArray):
+        """
+        Public method to insert bytes.
+        
+        @param pos position to insert the bytes at
+        @type int
+        @param byteArray bytes to be insert
+        @type bytearray or QByteArray
+        """
+        self.__undoStack.insertByteArray(pos, bytearray(byteArray))
+        self.__refresh()
+    
+    def replaceByteArray(self, pos, length, byteArray):
+        """
+        Public method to replace bytes.
+        
+        @param pos position to replace the bytes at
+        @type int
+        @param length amount of bytes to replace
+        @type int
+        @param byteArray bytes to replace with
+        @type bytearray or QByteArray
+        """
+        self.__undoStack.overwriteByteArray(pos, length, bytearray(byteArray))
+        self.__refresh()
+    
+    def cursorPositionFromPoint(self, point):
+        """
+        Public method to calculate a cursor position from a graphics position.
+        
+        @param point graphics position
+        @type QPoint
+        @return cursor position
+        @rtype int
+        """
+        result = -1
+        if (point.x() >= self.__pxPosHexX) and (
+            point.x() < (self.__pxPosHexX + (1 + self.HEXCHARS_PER_LINE) *
+                         self.__pxCharWidth)):
+            x = (
+                (point.x() - self.__pxPosHexX - self.__pxCharWidth // 2) //
+                self.__pxCharWidth
+            )
+            x = (x // 3) * 2 + x % 3
+            y = (
+                ((point.y() - 3) // self.__pxCharHeight) * 2 *
+                self.BYTES_PER_LINE
+            )
+            result = self.__bPosFirst * 2 + x + y
+        return result
+    
+    def ensureVisible(self):
+        """
+        Public method to ensure, that the cursor is visible.
+        """
+        if self.__cursorPosition < 2 * self.__bPosFirst:
+            self.verticalScrollBar().setValue(
+                self.__cursorPosition // 2 // self.BYTES_PER_LINE)
+        if self.__cursorPosition > (
+            (self.__bPosFirst + (self.__rowsShown - 1) *
+             self.BYTES_PER_LINE) * 2):
+            self.verticalScrollBar().setValue(
+                self.__cursorPosition // 2 // self.BYTES_PER_LINE -
+                self.__rowsShown + 1)
+        self.viewport().update()
+    
+    def indexOf(self, byteArray, start):
+        """
+        Public method to find the first occurrence of a byte array in our data.
+        
+        @param byteArray data to search for
+        @type bytearray or QByteArray
+        @param start start position of the search
+        @type int
+        @return position of match (or -1 if not found)
+        @rtype int
+        """
+        byteArray = bytearray(byteArray)
+        pos = self.__chunks.indexOf(byteArray, start)
+        if pos > -1:
+            curPos = pos * 2
+            self.setCursorPosition(curPos + len(byteArray) * 2)
+            self.__resetSelection(curPos)
+            self.__setSelection(curPos + len(byteArray) * 2)
+            self.ensureVisible()
+        return pos
+    
+    def lastIndexOf(self, byteArray, start):
+        """
+        Public method to find the last occurrence of a byte array in our data.
+        
+        @param byteArray data to search for
+        @type bytearray or QByteArray
+        @param start start position of the search
+        @type int
+        @return position of match (or -1 if not found)
+        @rtype int
+        """
+        byteArray = bytearray(byteArray)
+        pos = self.__chunks.lastIndexOf(byteArray, start)
+        if pos > -1:
+            curPos = pos * 2
+            self.setCursorPosition(curPos - 1)
+            self.__resetSelection(curPos)
+            self.__setSelection(curPos + len(byteArray) * 2)
+            self.ensureVisible()
+        return pos
+    
+    def isModified(self):
+        """
+        Public method to check for any modification.
+        
+        @return flag indicating a modified state
+        @rtype bool
+        """
+        return self.__modified
+    
+    def setModified(self, modified, setCleanState=False):
+        """
+        Public slot to set the modified flag.
+        
+        @param modified flag indicating the new modification status
+        @type bool
+        @param setCleanState flag indicating to set the undo stack to clean
+        @type bool
+        """
+        self.__modified = modified
+        self.dataChanged.emit(modified)
+        
+        if not modified and setCleanState:
+            self.__undoStack.setClean()
+    
+    def selectionToHexString(self):
+        """
+        Public method to get a hexadecimal representation of the selection.
+        
+        @return hexadecimal representation of the selection
+        @rtype str
+        """
+        byteArray = self.__chunks.data(self.getSelectionBegin(),
+                                       self.getSelectionLength())
+        return self.__toHex(byteArray).decode(encoding="ascii")
+    
+    def selectionToReadableString(self):
+        """
+        Public method to get a formatted representation of the selection.
+        
+        @return formatted representation of the selection
+        @rtype str
+        """
+        byteArray = self.__chunks.data(self.getSelectionBegin(),
+                                       self.getSelectionLength())
+        return self.__toReadable(byteArray)
+    
+    def toReadableString(self):
+        """
+        Public method to get a formatted representation of our data.
+        
+        @return formatted representation of our data
+        @rtype str
+        """
+        byteArray = self.__chunks.data()
+        return self.__toReadable(byteArray)
+    
+    @pyqtSlot()
+    def redo(self):
+        """
+        Public slot to redo the last operation.
+        """
+        self.__undoStack.redo()
+        self.setCursorPosition(self.__chunks.pos() * 2)
+        self.__refresh()
+    
+    @pyqtSlot()
+    def undo(self):
+        """
+        Public slot to undo the last operation.
+        """
+        self.__undoStack.undo()
+        self.setCursorPosition(self.__chunks.pos() * 2)
+        self.__refresh()
+    
+    @pyqtSlot()
+    def revertToUnmodified(self):
+        """
+        Public slot to revert all changes.
+        """
+        cleanIndex = self.__undoStack.cleanIndex()
+        if cleanIndex >= 0:
+            self.__undoStack.setIndex(cleanIndex)
+        self.setCursorPosition(self.__chunks.pos() * 2)
+        self.__refresh()
+    
+    ####################################################
+    ## Cursor movement commands
+    ####################################################
+    
+    def moveCursorToNextChar(self):
+        """
+        Public method to move the cursor to the next byte.
+        """
+        self.setCursorPosition(self.__cursorPosition + 1)
+        self.__resetSelection(self.__cursorPosition)
+    
+    def moveCursorToPreviousChar(self):
+        """
+        Public method to move the cursor to the previous byte.
+        """
+        self.setCursorPosition(self.__cursorPosition - 1)
+        self.__resetSelection(self.__cursorPosition)
+    
+    def moveCursorToEndOfLine(self):
+        """
+        Public method to move the cursor to the end of the current line.
+        """
+        self.setCursorPosition(self.__cursorPosition |
+                               (2 * self.BYTES_PER_LINE - 1))
+        self.__resetSelection(self.__cursorPosition)
+    
+    def moveCursorToStartOfLine(self):
+        """
+        Public method to move the cursor to the beginning of the current line.
+        """
+        self.setCursorPosition(
+            self.__cursorPosition -
+            (self.__cursorPosition % (2 * self.BYTES_PER_LINE)))
+        self.__resetSelection(self.__cursorPosition)
+    
+    def moveCursorToPreviousLine(self):
+        """
+        Public method to move the cursor to the previous line.
+        """
+        self.setCursorPosition(self.__cursorPosition - 2 * self.BYTES_PER_LINE)
+        self.__resetSelection(self.__cursorPosition)
+    
+    def moveCursorToNextLine(self):
+        """
+        Public method to move the cursor to the next line.
+        """
+        self.setCursorPosition(self.__cursorPosition + 2 * self.BYTES_PER_LINE)
+        self.__resetSelection(self.__cursorPosition)
+    
+    def moveCursorToNextPage(self):
+        """
+        Public method to move the cursor to the next page.
+        """
+        self.setCursorPosition(
+            self.__cursorPosition +
+            (self.__rowsShown - 1) * 2 * self.BYTES_PER_LINE)
+        self.__resetSelection(self.__cursorPosition)
+    
+    def moveCursorToPreviousPage(self):
+        """
+        Public method to move the cursor to the previous page.
+        """
+        self.setCursorPosition(
+            self.__cursorPosition -
+            (self.__rowsShown - 1) * 2 * self.BYTES_PER_LINE)
+        self.__resetSelection(self.__cursorPosition)
+    
+    def moveCursorToEndOfDocument(self):
+        """
+        Public method to move the cursor to the end of the data.
+        """
+        self.setCursorPosition(self.__chunks.size() * 2)
+        self.__resetSelection(self.__cursorPosition)
+    
+    def moveCursorToStartOfDocument(self):
+        """
+        Public method to move the cursor to the start of the data.
+        """
+        self.setCursorPosition(0)
+        self.__resetSelection(self.__cursorPosition)
+    
+    ####################################################
+    ## Selection commands
+    ####################################################
+    
+    def deselectAll(self):
+        """
+        Public method to deselect all data.
+        """
+        self.__resetSelection(0)
+        self.__refresh()
+    
+    def selectAll(self):
+        """
+        Public method to select all data.
+        """
+        self.__resetSelection(0)
+        self.__setSelection(2 * self.__chunks.size() + 1)
+        self.__refresh()
+    
+    def selectNextChar(self):
+        """
+        Public method to extend the selection by one byte right.
+        """
+        pos = self.__cursorPosition + 1
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    def selectPreviousChar(self):
+        """
+        Public method to extend the selection by one byte left.
+        """
+        pos = self.__cursorPosition - 1
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    def selectToEndOfLine(self):
+        """
+        Public method to extend the selection to the end of line.
+        """
+        pos = (
+            self.__cursorPosition -
+            (self.__cursorPosition % (2 * self.BYTES_PER_LINE)) +
+            2 * self.BYTES_PER_LINE
+        )
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    def selectToStartOfLine(self):
+        """
+        Public method to extend the selection to the start of line.
+        """
+        pos = (
+            self.__cursorPosition -
+            (self.__cursorPosition % (2 * self.BYTES_PER_LINE))
+        )
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    def selectPreviousLine(self):
+        """
+        Public method to extend the selection one line up.
+        """
+        pos = self.__cursorPosition - 2 * self.BYTES_PER_LINE
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    def selectNextLine(self):
+        """
+        Public method to extend the selection one line down.
+        """
+        pos = self.__cursorPosition + 2 * self.BYTES_PER_LINE
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    def selectNextPage(self):
+        """
+        Public method to extend the selection one page down.
+        """
+        pos = (
+            self.__cursorPosition +
+            ((self.viewport().height() // self.__pxCharHeight) - 1) *
+            2 * self.BYTES_PER_LINE
+        )
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    def selectPreviousPage(self):
+        """
+        Public method to extend the selection one page up.
+        """
+        pos = (
+            self.__cursorPosition -
+            ((self.viewport().height() // self.__pxCharHeight) - 1) *
+            2 * self.BYTES_PER_LINE
+        )
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    def selectEndOfDocument(self):
+        """
+        Public method to extend the selection to the end of the data.
+        """
+        pos = self.__chunks.size() * 2
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    def selectStartOfDocument(self):
+        """
+        Public method to extend the selection to the start of the data.
+        """
+        pos = 0
+        self.setCursorPosition(pos)
+        self.__setSelection(pos)
+    
+    ####################################################
+    ## Edit commands
+    ####################################################
+    
+    def cut(self):
+        """
+        Public method to cut the selected bytes and move them to the clipboard.
+        """
+        if not self.__readOnly:
+            byteArray = self.__toHex(self.__chunks.data(
+                self.getSelectionBegin(), self.getSelectionLength()))
+            idx = 32
+            while idx < len(byteArray):
+                byteArray.insert(idx, "\n")
+                idx += 33
+            cb = QApplication.clipboard()
+            cb.setText(byteArray.decode(encoding="latin1"))
+            if self.__overwriteMode:
+                length = self.getSelectionLength()
+                self.replaceByteArray(self.getSelectionBegin(), length,
+                                      bytearray(length))
+            else:
+                self.remove(self.getSelectionBegin(),
+                            self.getSelectionLength())
+            self.setCursorPosition(2 * self.getSelectionBegin())
+            self.__resetSelection(2 * self.getSelectionBegin())
+    
+    def copy(self):
+        """
+        Public method to copy the selected bytes to the clipboard.
+        """
+        byteArray = self.__toHex(self.__chunks.data(
+            self.getSelectionBegin(), self.getSelectionLength()))
+        idx = 32
+        while idx < len(byteArray):
+            byteArray.insert(idx, "\n")
+            idx += 33
+        cb = QApplication.clipboard()
+        cb.setText(byteArray.decode(encoding="latin1"))
+    
+    def paste(self):
+        """
+        Public method to paste bytes from the clipboard.
+        """
+        if not self.__readOnly:
+            cb = QApplication.clipboard()
+            byteArray = self.__fromHex(cb.text().encode(encoding="latin1"))
+            if self.__overwriteMode:
+                self.replaceByteArray(self.__bPosCurrent, len(byteArray),
+                                      byteArray)
+            else:
+                self.insertByteArray(self.__bPosCurrent, byteArray)
+            self.setCursorPosition(
+                self.__cursorPosition + 2 * len(byteArray))
+            self.__resetSelection(2 * self.getSelectionBegin())
+    
+    def deleteByte(self):
+        """
+        Public method to delete the current byte.
+        """
+        if not self.__readOnly:
+            if self.hasSelection():
+                self.__bPosCurrent = self.getSelectionBegin()
+                if self.__overwriteMode:
+                    byteArray = bytearray(self.getSelectionLength())
+                    self.replaceByteArray(self.__bPosCurrent, len(byteArray),
+                                          byteArray)
+                else:
+                    self.remove(self.__bPosCurrent,
+                                self.getSelectionLength())
+            else:
+                if self.__overwriteMode:
+                    self.replace(self.__bPosCurrent, 0)
+                else:
+                    self.remove(self.__bPosCurrent, 1)
+            self.setCursorPosition(2 * self.__bPosCurrent)
+            self.__resetSelection(2 * self.__bPosCurrent)
+    
+    def deleteByteBack(self):
+        """
+        Public method to delete the previous byte.
+        """
+        if not self.__readOnly:
+            if self.hasSelection():
+                self.__bPosCurrent = self.getSelectionBegin()
+                self.setCursorPosition(2 * self.__bPosCurrent)
+                if self.__overwriteMode:
+                    byteArray = bytearray(self.getSelectionLength())
+                    self.replaceByteArray(self.__bPosCurrent, len(byteArray),
+                                          byteArray)
+                else:
+                    self.remove(self.__bPosCurrent,
+                                self.getSelectionLength())
+            else:
+                self.__bPosCurrent -= 1
+                if self.__overwriteMode:
+                    self.replace(self.__bPosCurrent, 0)
+                else:
+                    self.remove(self.__bPosCurrent, 1)
+                self.setCursorPosition(2 * self.__bPosCurrent)
+            self.__resetSelection(2 * self.__bPosCurrent)
+    
+    ####################################################
+    ## Event handling methods
+    ####################################################
+    
+    def keyPressEvent(self, evt):
+        """
+        Protected method to handle key press events.
+        
+        @param evt reference to the key event
+        @type QKeyEvent
+        """
+        # Cursor movements
+        if evt.matches(QKeySequence.StandardKey.MoveToNextChar):
+            self.moveCursorToNextChar()
+        elif evt.matches(QKeySequence.StandardKey.MoveToPreviousChar):
+            self.moveCursorToPreviousChar()
+        elif evt.matches(QKeySequence.StandardKey.MoveToEndOfLine):
+            self.moveCursorToEndOfLine()
+        elif evt.matches(QKeySequence.StandardKey.MoveToStartOfLine):
+            self.moveCursorToStartOfLine()
+        elif evt.matches(QKeySequence.StandardKey.MoveToPreviousLine):
+            self.moveCursorToPreviousLine()
+        elif evt.matches(QKeySequence.StandardKey.MoveToNextLine):
+            self.moveCursorToNextLine()
+        elif evt.matches(QKeySequence.StandardKey.MoveToNextPage):
+            self.moveCursorToNextPage()
+        elif evt.matches(QKeySequence.StandardKey.MoveToPreviousPage):
+            self.moveCursorToPreviousPage()
+        elif evt.matches(QKeySequence.StandardKey.MoveToEndOfDocument):
+            self.moveCursorToEndOfDocument()
+        elif evt.matches(QKeySequence.StandardKey.MoveToStartOfDocument):
+            self.moveCursorToStartOfDocument()
+        
+        # Selection commands
+        elif evt.matches(QKeySequence.StandardKey.SelectAll):
+            self.selectAll()
+        elif evt.matches(QKeySequence.StandardKey.SelectNextChar):
+            self.selectNextChar()
+        elif evt.matches(QKeySequence.StandardKey.SelectPreviousChar):
+            self.selectPreviousChar()
+        elif evt.matches(QKeySequence.StandardKey.SelectEndOfLine):
+            self.selectToEndOfLine()
+        elif evt.matches(QKeySequence.StandardKey.SelectStartOfLine):
+            self.selectToStartOfLine()
+        elif evt.matches(QKeySequence.StandardKey.SelectPreviousLine):
+            self.selectPreviousLine()
+        elif evt.matches(QKeySequence.StandardKey.SelectNextLine):
+            self.selectNextLine()
+        elif evt.matches(QKeySequence.StandardKey.SelectNextPage):
+            self.selectNextPage()
+        elif evt.matches(QKeySequence.StandardKey.SelectPreviousPage):
+            self.selectPreviousPage()
+        elif evt.matches(QKeySequence.StandardKey.SelectEndOfDocument):
+            self.selectEndOfDocument()
+        elif evt.matches(QKeySequence.StandardKey.SelectStartOfDocument):
+            self.selectStartOfDocument()
+        
+        # Edit commands
+        elif evt.matches(QKeySequence.StandardKey.Copy):
+            self.copy()
+        elif (
+            evt.key() == Qt.Key.Key_Insert and
+            evt.modifiers() == Qt.KeyboardModifier.NoModifier
+        ):
+            self.setOverwriteMode(not self.overwriteMode())
+            self.setCursorPosition(self.__cursorPosition)
+        
+        elif not self.__readOnly:
+            if evt.matches(QKeySequence.StandardKey.Cut):
+                self.cut()
+            elif evt.matches(QKeySequence.StandardKey.Paste):
+                self.paste()
+            elif evt.matches(QKeySequence.StandardKey.Delete):
+                self.deleteByte()
+            elif (
+                evt.key() == Qt.Key.Key_Backspace and
+                evt.modifiers() == Qt.KeyboardModifier.NoModifier
+            ):
+                self.deleteByteBack()
+            elif evt.matches(QKeySequence.StandardKey.Undo):
+                self.undo()
+            elif evt.matches(QKeySequence.StandardKey.Redo):
+                self.redo()
+            
+            elif QApplication.keyboardModifiers() in [
+                Qt.KeyboardModifier.NoModifier,
+                Qt.KeyboardModifier.KeypadModifier
+            ]:
+                # some hex input
+                key = evt.text()
+                if key and key in "0123456789abcdef":
+                    if self.hasSelection():
+                        if self.__overwriteMode:
+                            length = self.getSelectionLength()
+                            self.replaceByteArray(
+                                self.getSelectionBegin(), length,
+                                bytearray(length))
+                        else:
+                            self.remove(self.getSelectionBegin(),
+                                        self.getSelectionLength())
+                            self.__bPosCurrent = self.getSelectionBegin()
+                        self.setCursorPosition(2 * self.__bPosCurrent)
+                        self.__resetSelection(2 * self.__bPosCurrent)
+                    
+                    # if in insert mode, insert a byte
+                    if (
+                        not self.__overwriteMode and
+                        (self.__cursorPosition % 2) == 0
+                    ):
+                        self.insert(self.__bPosCurrent, 0)
+                    
+                    # change content
+                    if self.__chunks.size() > 0:
+                        hexValue = self.__toHex(
+                            self.__chunks.data(self.__bPosCurrent, 1))
+                        if (self.__cursorPosition % 2) == 0:
+                            hexValue[0] = ord(key)
+                        else:
+                            hexValue[1] = ord(key)
+                        self.replace(self.__bPosCurrent,
+                                     self.__fromHex(hexValue)[0])
+                        
+                        self.setCursorPosition(self.__cursorPosition + 1)
+                        self.__resetSelection(self.__cursorPosition)
+                    else:
+                        return
+                else:
+                    return
+            else:
+                return
+        else:
+            return
+        
+        self.__refresh()
+    
+    def mouseMoveEvent(self, evt):
+        """
+        Protected method to handle mouse moves.
+        
+        @param evt reference to the mouse event
+        @type QMouseEvent
+        """
+        self.__blink = False
+        self.viewport().update()
+        actPos = self.cursorPositionFromPoint(evt.position().toPoint())
+        if actPos >= 0:
+            self.setCursorPosition(actPos)
+            self.__setSelection(actPos)
+    
+    def mousePressEvent(self, evt):
+        """
+        Protected method to handle mouse button presses.
+        
+        @param evt reference to the mouse event
+        @type QMouseEvent
+        """
+        self.__blink = False
+        self.viewport().update()
+        cPos = self.cursorPositionFromPoint(evt.position().toPoint())
+        if cPos >= 0:
+            if evt.modifiers() == Qt.KeyboardModifier.ShiftModifier:
+                self.__setSelection(cPos)
+            else:
+                self.__resetSelection(cPos)
+            self.setCursorPosition(cPos)
+    
+    def paintEvent(self, evt):
+        """
+        Protected method to handle paint events.
+        
+        @param evt reference to the paint event
+        @type QPaintEvent
+        """
+        painter = QPainter(self.viewport())
+        
+        # initialize colors
+        if ericApp().usesDarkPalette():
+            addressAreaForeground = self.palette().color(
+                QPalette.ColorRole.Text)
+            addressAreaBackground = self.palette().color(
+                QPalette.ColorRole.Base).lighter(200)
+            highlightingForeground = self.palette().color(
+                QPalette.ColorRole.HighlightedText).darker(200)
+            highlightingBackground = self.palette().color(
+                QPalette.ColorRole.Highlight).lighter()
+        else:
+            addressAreaForeground = self.palette().color(
+                QPalette.ColorRole.Text)
+            addressAreaBackground = self.palette().color(
+                QPalette.ColorRole.Base).darker()
+            highlightingForeground = self.palette().color(
+                QPalette.ColorRole.HighlightedText).lighter()
+            highlightingBackground = self.palette().color(
+                QPalette.ColorRole.Highlight).darker()
+        selectionForeground = self.palette().color(
+            QPalette.ColorRole.HighlightedText)
+        selectionBackground = self.palette().color(
+            QPalette.ColorRole.Highlight)
+        standardBackground = self.viewport().palette().color(
+            QPalette.ColorRole.Base)
+        standardForeground = self.viewport().palette().color(
+            QPalette.ColorRole.Text)
+        
+        if (
+            evt.rect() != self.__cursorRect and
+            evt.rect() != self.__cursorRectAscii
+        ):
+            pxOfsX = self.horizontalScrollBar().value()
+            pxPosStartY = self.__pxCharHeight
+            
+            # draw some patterns if needed
+            painter.fillRect(evt.rect(), standardBackground)
+            if self.__addressArea:
+                painter.fillRect(
+                    QRect(-pxOfsX, evt.rect().top(),
+                          self.__pxPosHexX - self.__pxGapAdrHex // 2 - pxOfsX,
+                          self.height()),
+                    addressAreaBackground)
+            if self.__asciiArea:
+                linePos = self.__pxPosAsciiX - (self.__pxGapHexAscii // 2)
+                painter.setPen(Qt.GlobalColor.gray)
+                painter.drawLine(linePos - pxOfsX, evt.rect().top(),
+                                 linePos - pxOfsX, self.height())
+            
+            # paint the address area
+            if self.__addressArea:
+                painter.setPen(addressAreaForeground)
+                address = ""
+                row = 0
+                pxPosY = self.__pxCharHeight
+                while row <= len(self.__dataShown) // self.BYTES_PER_LINE:
+                    address = "{0:0{1}x}".format(
+                        self.__bPosFirst + row * self.BYTES_PER_LINE,
+                        self.__addrDigits)
+                    address = Globals.strGroup(address, ":", 4)
+                    painter.drawText(self.__pxPosAdrX - pxOfsX, pxPosY,
+                                     address)
+                    # increment loop variables
+                    row += 1
+                    pxPosY += self.__pxCharHeight
+            
+            # paint hex and ascii area
+            painter.setBackgroundMode(Qt.BGMode.TransparentMode)
+            
+            row = 0
+            pxPosY = pxPosStartY
+            while row <= self.__rowsShown:
+                pxPosX = self.__pxPosHexX - pxOfsX
+                pxPosAsciiX2 = self.__pxPosAsciiX - pxOfsX
+                bPosLine = row * self.BYTES_PER_LINE
+                
+                colIdx = 0
+                while (
+                    bPosLine + colIdx < len(self.__dataShown) and
+                    colIdx < self.BYTES_PER_LINE
+                ):
+                    background = standardBackground
+                    painter.setPen(standardForeground)
+                    
+                    posBa = self.__bPosFirst + bPosLine + colIdx
+                    if (
+                        self.getSelectionBegin() <= posBa and
+                        self.getSelectionEnd() > posBa
+                    ):
+                        background = selectionBackground
+                        painter.setPen(selectionForeground)
+                    elif (
+                        self.__highlighting and
+                        self.__markedShown and
+                        self.__markedShown[posBa - self.__bPosFirst]
+                    ):
+                        background = highlightingBackground
+                        painter.setPen(highlightingForeground)
+                    
+                    # render hex value
+                    rect = QRect()
+                    if colIdx == 0:
+                        rect.setRect(
+                            pxPosX,
+                            pxPosY - self.__pxCharHeight +
+                            self.__pxSelectionSub,
+                            2 * self.__pxCharWidth,
+                            self.__pxCharHeight)
+                    else:
+                        rect.setRect(
+                            pxPosX - self.__pxCharWidth,
+                            pxPosY - self.__pxCharHeight +
+                            self.__pxSelectionSub,
+                            3 * self.__pxCharWidth,
+                            self.__pxCharHeight)
+                    painter.fillRect(rect, background)
+                    hexStr = (
+                        chr(self.__hexDataShown[(bPosLine + colIdx) * 2]) +
+                        chr(self.__hexDataShown[(bPosLine + colIdx) * 2 + 1])
+                    )
+                    painter.drawText(pxPosX, pxPosY, hexStr)
+                    pxPosX += 3 * self.__pxCharWidth
+                    
+                    # render ascii value
+                    if self.__asciiArea:
+                        by = self.__dataShown[bPosLine + colIdx]
+                        if by < 0x20 or (by > 0x7e and by < 0xa0):
+                            ch = "."
+                        else:
+                            ch = chr(by)
+                        rect.setRect(
+                            pxPosAsciiX2,
+                            pxPosY - self.__pxCharHeight +
+                            self.__pxSelectionSub,
+                            self.__pxCharWidth,
+                            self.__pxCharHeight)
+                        painter.fillRect(rect, background)
+                        painter.drawText(pxPosAsciiX2, pxPosY, ch)
+                        pxPosAsciiX2 += self.__pxCharWidth
+                    
+                    # increment loop variable
+                    colIdx += 1
+                
+                # increment loop variables
+                row += 1
+                pxPosY += self.__pxCharHeight
+        
+        painter.setBackgroundMode(Qt.BGMode.TransparentMode)
+        painter.setPen(standardForeground)
+        
+        # paint cursor
+        if self.__blink and not self.__readOnly and self.isActiveWindow():
+            painter.fillRect(self.__cursorRect, standardForeground)
+        else:
+            if self.__hexDataShown:
+                try:
+                    c = chr(self.__hexDataShown[
+                            self.__cursorPosition - self.__bPosFirst * 2])
+                except IndexError:
+                    c = ""
+            else:
+                c = ""
+            painter.drawText(self.__pxCursorX, self.__pxCursorY, c)
+        
+        if self.__asciiArea:
+            painter.drawRect(self.__cursorRectAscii)
+        
+        # emit event, if size has changed
+        if self.__lastEventSize != self.__chunks.size():
+            self.__lastEventSize = self.__chunks.size()
+            self.currentSizeChanged.emit(self.__lastEventSize)
+    
+    def resizeEvent(self, evt):
+        """
+        Protected method to handle resize events.
+        
+        @param evt reference to the resize event
+        @type QResizeEvent
+        """
+        self.__adjust()
+    
+    def __resetSelection(self, pos=None):
+        """
+        Private method to reset the selection.
+        
+        @param pos position to set selection start and end to
+            (if this is None, selection end is set to selection start)
+        @type int or None
+        """
+        if pos is None:
+            self.__bSelectionBegin = self.__bSelectionInit
+            self.__bSelectionEnd = self.__bSelectionInit
+        else:
+            if pos < 0:
+                pos = 0
+            pos //= 2
+            self.__bSelectionInit = pos
+            self.__bSelectionBegin = pos
+            self.__bSelectionEnd = pos
+        
+        self.selectionAvailable.emit(False)
+    
+    def __setSelection(self, pos):
+        """
+        Private method to set the selection.
+        
+        @param pos position
+        @type int
+        """
+        if pos < 0:
+            pos = 0
+        pos //= 2
+        if pos >= self.__bSelectionInit:
+            self.__bSelectionEnd = pos
+            self.__bSelectionBegin = self.__bSelectionInit
+        else:
+            self.__bSelectionBegin = pos
+            self.__bSelectionEnd = self.__bSelectionInit
+        
+        self.selectionAvailable.emit(True)
+    
+    def getSelectionBegin(self):
+        """
+        Public method to get the start of the selection.
+        
+        @return selection start
+        @rtype int
+        """
+        return self.__bSelectionBegin
+    
+    def getSelectionEnd(self):
+        """
+        Public method to get the end of the selection.
+        
+        @return selection end
+        @rtype int
+        """
+        return self.__bSelectionEnd
+    
+    def getSelectionLength(self):
+        """
+        Public method to get the length of the selection.
+        
+        @return selection length
+        @rtype int
+        """
+        return self.__bSelectionEnd - self.__bSelectionBegin
+    
+    def hasSelection(self):
+        """
+        Public method to test for a selection.
+        
+        @return flag indicating the presence of a selection
+        @rtype bool
+        """
+        return self.__bSelectionBegin != self.__bSelectionEnd
+    
+    def __initialize(self):
+        """
+        Private method to do some initialization.
+        """
+        self.__undoStack.clear()
+        self.setAddressOffset(0)
+        self.__resetSelection(0)
+        self.setCursorPosition(0)
+        self.verticalScrollBar().setValue(0)
+        self.__modified = False
+    
+    def __readBuffers(self):
+        """
+        Private method to read the buffers.
+        """
+        self.__dataShown = self.__chunks.data(
+            self.__bPosFirst,
+            self.__bPosLast - self.__bPosFirst + self.BYTES_PER_LINE + 1,
+            self.__markedShown
+        )
+        self.__hexDataShown = self.__toHex(self.__dataShown)
+    
+    def __toHex(self, byteArray):
+        """
+        Private method to convert the data of a Python bytearray to hex.
+        
+        @param byteArray byte array to be converted
+        @type bytearray
+        @return converted data
+        @rtype bytearray
+        """
+        return bytearray(QByteArray(byteArray).toHex())
+    
+    def __fromHex(self, byteArray):
+        """
+        Private method to convert data of a Python bytearray from hex.
+        
+        @param byteArray byte array to be converted
+        @type bytearray
+        @return converted data
+        @rtype bytearray
+        """
+        return bytearray(QByteArray.fromHex(byteArray))
+        
+    def __toReadable(self, byteArray):
+        """
+        Private method to convert some data into a readable format.
+        
+        @param byteArray data to be converted
+        @type bytearray or QByteArray
+        @return readable data
+        @rtype str
+        """
+        byteArray = bytearray(byteArray)
+        result = ""
+        for i in range(0, len(byteArray), 16):
+            addrStr = "{0:0{1}x}".format(self.__addressOffset + i,
+                                         self.addressWidth())
+            hexStr = ""
+            ascStr = ""
+            for j in range(16):
+                if (i + j) < len(byteArray):
+                    hexStr += " {0:02x}".format(byteArray[i + j])
+                    by = byteArray[i + j]
+                    if by < 0x20 or (by > 0x7e and by < 0xa0):
+                        ch = "."
+                    else:
+                        ch = chr(by)
+                    ascStr += ch
+            result += "{0} {1:<48} {2:<17}\n".format(addrStr, hexStr, ascStr)
+        return result
+    
+    @pyqtSlot()
+    def __adjust(self):
+        """
+        Private slot to recalculate pixel positions.
+        """
+        # recalculate graphics
+        if self.__addressArea:
+            self.__addrDigits = self.addressWidth()
+            self.__addrSeparators = self.__addrDigits // 4 - 1
+            self.__pxPosHexX = (
+                self.__pxGapAdr +
+                (self.__addrDigits + self.__addrSeparators) *
+                self.__pxCharWidth + self.__pxGapAdrHex)
+        else:
+            self.__pxPosHexX = self.__pxGapAdrHex
+        self.__pxPosAdrX = self.__pxGapAdr
+        self.__pxPosAsciiX = (
+            self.__pxPosHexX +
+            self.HEXCHARS_PER_LINE * self.__pxCharWidth +
+            self.__pxGapHexAscii
+        )
+        
+        # set horizontal scrollbar
+        pxWidth = self.__pxPosAsciiX
+        if self.__asciiArea:
+            pxWidth += self.BYTES_PER_LINE * self.__pxCharWidth
+        self.horizontalScrollBar().setRange(
+            0, pxWidth - self.viewport().width())
+        self.horizontalScrollBar().setPageStep(self.viewport().width())
+        
+        # set vertical scrollbar
+        self.__rowsShown = (
+            (self.viewport().height() - 4) // self.__pxCharHeight
+        )
+        lineCount = (self.__chunks.size() // self.BYTES_PER_LINE) + 1
+        self.verticalScrollBar().setRange(0, lineCount - self.__rowsShown)
+        self.verticalScrollBar().setPageStep(self.__rowsShown)
+        
+        # do the rest
+        value = self.verticalScrollBar().value()
+        self.__bPosFirst = value * self.BYTES_PER_LINE
+        self.__bPosLast = (
+            self.__bPosFirst + self.__rowsShown * self.BYTES_PER_LINE - 1
+        )
+        if self.__bPosLast >= self.__chunks.size():
+            self.__bPosLast = self.__chunks.size() - 1
+        self.__readBuffers()
+        self.setCursorPosition(self.__cursorPosition)
+    
+    @pyqtSlot(int)
+    def __dataChangedPrivate(self, idx=0):
+        """
+        Private slot to handle data changes.
+        
+        @param idx index
+        @type int
+        """
+        self.__modified = (
+            self.__undoStack.cleanIndex() == -1 or
+            self.__undoStack.index() != self.__undoStack.cleanIndex())
+        self.__adjust()
+        self.dataChanged.emit(self.__modified)
+    
+    @pyqtSlot()
+    def __refresh(self):
+        """
+        Private slot to refresh the display.
+        """
+        self.ensureVisible()
+        self.__readBuffers()
+    
+    @pyqtSlot()
+    def __updateCursor(self):
+        """
+        Private slot to update the blinking cursor.
+        """
+        self.__blink = not self.__blink
+        self.viewport().update(self.__cursorRect)

eric ide

mercurial