"Fossies" - the Fresh Open Source Software Archive

Member "eric6-20.9/eric/eric6/HexEdit/HexEditWidget.py" (4 Jul 2020, 59154 Bytes) of package /linux/misc/eric6-20.9.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "HexEditWidget.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 
    3 # Copyright (c) 2016 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
    4 #
    5 
    6 """
    7 Module implementing an editor for binary data.
    8 """
    9 
   10 import math
   11 
   12 from PyQt5.QtCore import (
   13     pyqtSignal, pyqtSlot, Qt, QByteArray, QTimer, QRect, QBuffer, QIODevice
   14 )
   15 from PyQt5.QtGui import (
   16     QBrush, QPen, QColor, QFont, QPalette, QKeySequence, QPainter
   17 )
   18 from PyQt5.QtWidgets import QAbstractScrollArea, QApplication
   19 
   20 from .HexEditChunks import HexEditChunks
   21 from .HexEditUndoStack import HexEditUndoStack
   22 
   23 import Globals
   24 
   25 
   26 class HexEditWidget(QAbstractScrollArea):
   27     """
   28     Class implementing an editor for binary data.
   29     
   30     @signal currentAddressChanged(address) emitted to indicate the new
   31         cursor position
   32     @signal currentSizeChanged(size) emitted to indicate the new size of
   33         the data
   34     @signal dataChanged(modified) emitted to indicate a change of the data
   35     @signal overwriteModeChanged(state) emitted to indicate a change of
   36         the overwrite mode
   37     @signal readOnlyChanged(state) emitted to indicate a change of the
   38         read only state
   39     @signal canRedoChanged(bool) emitted after the redo status has changed
   40     @signal canUndoChanged(bool) emitted after the undo status has changed
   41     @signal selectionAvailable(bool) emitted to signal a change of the
   42         selection
   43     """
   44     currentAddressChanged = pyqtSignal(int)
   45     currentSizeChanged = pyqtSignal(int)
   46     dataChanged = pyqtSignal(bool)
   47     overwriteModeChanged = pyqtSignal(bool)
   48     readOnlyChanged = pyqtSignal(bool)
   49     canRedoChanged = pyqtSignal(bool)
   50     canUndoChanged = pyqtSignal(bool)
   51     selectionAvailable = pyqtSignal(bool)
   52     
   53     HEXCHARS_PER_LINE = 47
   54     BYTES_PER_LINE = 16
   55     
   56     def __init__(self, parent=None):
   57         """
   58         Constructor
   59         
   60         @param parent refernce to the parent widget
   61         @type QWidget
   62         """
   63         super(HexEditWidget, self).__init__(parent)
   64         
   65         # Properties
   66         self.__addressArea = True
   67         # switch the address area on/off
   68         self.__addressAreaBrush = QBrush()
   69         self.__addressAreaPen = QPen()
   70         # background and pen of the address area
   71         self.__addressOffset = 0
   72         # offset into the shown address range
   73         self.__addressWidth = 4
   74         # address area width in characters
   75         self.__asciiArea = True
   76         # switch the ASCII area on/off
   77         self.__data = bytearray()
   78         # contents of the hex editor
   79         self.__highlighting = True
   80         # switch the highlighting feature on/off
   81         self.__highlightingBrush = QBrush()
   82         self.__highlightingPen = QPen()
   83         # background and pen of highlighted text
   84         self.__overwriteMode = True
   85         # set overwrite mode on/off
   86         self.__selectionBrush = QBrush()
   87         self.__selectionPen = QPen()
   88         # background and pen of selected text
   89         self.__readOnly = False
   90         # set read only mode on/off
   91         self.__cursorPosition = 0
   92         # absolute position of cursor, 1 Byte == 2 tics
   93         
   94         self.__addrDigits = 0
   95         self.__addrSeparators = 0
   96         self.__blink = True
   97         self.__bData = QBuffer()
   98         self.__cursorRect = QRect()
   99         self.__cursorRectAscii = QRect()
  100         self.__dataShown = bytearray()
  101         self.__hexDataShown = bytearray()
  102         self.__lastEventSize = 0
  103         self.__markedShown = bytearray()
  104         self.__modified = False
  105         self.__rowsShown = 0
  106         
  107         # pixel related attributes (starting with __px)
  108         self.__pxCharWidth = 0
  109         self.__pxCharHeight = 0
  110         self.__pxPosHexX = 0
  111         self.__pxPosAdrX = 0
  112         self.__pxPosAsciiX = 0
  113         self.__pxGapAdr = 0
  114         self.__pxGapAdrHex = 0
  115         self.__pxGapHexAscii = 0
  116         self.__pxSelectionSub = 0
  117         self.__pxCursorWidth = 0
  118         self.__pxCursorX = 0
  119         self.__pxCursorY = 0
  120         
  121         # absolute byte position related attributes (starting with __b)
  122         self.__bSelectionBegin = 0
  123         self.__bSelectionEnd = 0
  124         self.__bSelectionInit = 0
  125         self.__bPosFirst = 0
  126         self.__bPosLast = 0
  127         self.__bPosCurrent = 0
  128         
  129         self.__chunks = HexEditChunks()
  130         self.__undoStack = HexEditUndoStack(self.__chunks, self)
  131         if Globals.isWindowsPlatform():
  132             self.setFont(QFont("Courier", 10))
  133         else:
  134             self.setFont(QFont("Monospace", 10))
  135         
  136         self.setAddressAreaColors(
  137             self.palette().color(QPalette.WindowText),
  138             self.palette().alternateBase().color())
  139         self.setHighlightColors(
  140             self.palette().color(QPalette.WindowText),
  141             QColor(0xff, 0xff, 0x99, 0xff))
  142         self.setSelectionColors(
  143             self.palette().highlightedText().color(),
  144             self.palette().highlight().color())
  145         
  146         self.__cursorTimer = QTimer()
  147         self.__cursorTimer.timeout.connect(self.__updateCursor)
  148         
  149         self.verticalScrollBar().valueChanged.connect(self.__adjust)
  150         
  151         self.__undoStack.indexChanged.connect(self.__dataChangedPrivate)
  152         self.__undoStack.canRedoChanged.connect(self.__canRedoChanged)
  153         self.__undoStack.canUndoChanged.connect(self.__canUndoChanged)
  154         
  155         self.readOnlyChanged.connect(self.__canRedoChanged)
  156         self.readOnlyChanged.connect(self.__canUndoChanged)
  157         
  158         self.__cursorTimer.setInterval(500)
  159         self.__cursorTimer.start()
  160         
  161         self.setAddressWidth(4)
  162         self.setAddressArea(True)
  163         self.setAsciiArea(True)
  164         self.setOverwriteMode(True)
  165         self.setHighlighting(True)
  166         self.setReadOnly(False)
  167         
  168         self.__initialize()
  169     
  170     def undoStack(self):
  171         """
  172         Public method to get a reference to the undo stack.
  173         
  174         @return reference to the undo stack
  175         @rtype HexEditUndoStack
  176         """
  177         return self.__undoStack
  178     
  179     @pyqtSlot()
  180     def __canRedoChanged(self):
  181         """
  182         Private slot handling changes of the Redo state.
  183         """
  184         self.canRedoChanged.emit(
  185             self.__undoStack.canRedo() and not self.__readOnly)
  186     
  187     @pyqtSlot()
  188     def __canUndoChanged(self):
  189         """
  190         Private slot handling changes of the Undo state.
  191         """
  192         self.canUndoChanged.emit(
  193             self.__undoStack.canUndo() and not self.__readOnly)
  194     
  195     def addressArea(self):
  196         """
  197         Public method to get the address area visibility.
  198         
  199         @return flag indicating the address area visibility
  200         @rtype bool
  201         """
  202         return self.__addressArea
  203     
  204     def setAddressArea(self, on):
  205         """
  206         Public method to set the address area visibility.
  207         
  208         @param on flag indicating the address area visibility
  209         @type bool
  210         """
  211         self.__addressArea = on
  212         self.__adjust()
  213         self.setCursorPosition(self.__cursorPosition)
  214         self.viewport().update()
  215     
  216     def addressAreaColors(self):
  217         """
  218         Public method to get the address area colors.
  219         
  220         @return address area foreground and background colors
  221         @rtype tuple of 2 QColor
  222         """
  223         return self.__addressAreaPen.color(), self.__addressAreaBrush.color()
  224     
  225     def setAddressAreaColors(self, foreground, background):
  226         """
  227         Public method to set the address area colors.
  228         
  229         @param foreground address area foreground color
  230         @type QColor
  231         @param background address area background color
  232         @type QColor
  233         """
  234         self.__addressAreaPen = QPen(foreground)
  235         self.__addressAreaBrush = QBrush(background)
  236         self.viewport().update()
  237     
  238     def addressOffset(self):
  239         """
  240         Public method to get the address offset.
  241         
  242         @return address offset
  243         @rtype int
  244         """
  245         return self.__addressOffset
  246     
  247     def setAddressOffset(self, offset):
  248         """
  249         Public method to set the address offset.
  250         
  251         @param offset address offset
  252         @type int
  253         """
  254         self.__addressOffset = offset
  255         self.__adjust()
  256         self.setCursorPosition(self.__cursorPosition)
  257         self.viewport().update()
  258     
  259     def addressWidth(self):
  260         """
  261         Public method to get the width of the address area in
  262         characters.
  263         
  264         Note: The address area width is always a multiple of four.
  265         
  266         @return minimum width of the address area
  267         @rtype int
  268         """
  269         size = self.__chunks.size()
  270         n = 1
  271         if size > 0x100000000:
  272             n += 8
  273             size //= 0x100000000
  274         if size > 0x10000:
  275             n += 4
  276             size //= 0x10000
  277         if size > 0x100:
  278             n += 2
  279             size //= 0x100
  280         if size > 0x10:
  281             n += 1
  282             size //= 0x10
  283         n = int(math.ceil(n / 4)) * 4
  284         
  285         if n > self.__addressWidth:
  286             return n
  287         else:
  288             return self.__addressWidth
  289     
  290     def setAddressWidth(self, width):
  291         """
  292         Public method to set the width of the address area.
  293         
  294         Note: The address area width is always a multiple of four.
  295         The given value will be adjusted as required.
  296         
  297         @param width width of the address area in characters
  298         @type int
  299         """
  300         self.__addressWidth = int(math.ceil(width / 4)) * 4
  301         self.__adjust()
  302         self.setCursorPosition(self.__cursorPosition)
  303         self.viewport().update()
  304     
  305     def asciiArea(self):
  306         """
  307         Public method to get the visibility of the ASCII area.
  308         
  309         @return visibility of the ASCII area
  310         @rtype bool
  311         """
  312         return self.__asciiArea
  313     
  314     def setAsciiArea(self, on):
  315         """
  316         Public method to set the visibility of the ASCII area.
  317         
  318         @param on flag indicating the visibility of the ASCII area
  319         @type bool
  320         """
  321         self.__asciiArea = on
  322         self.viewport().update()
  323     
  324     def cursorPosition(self):
  325         """
  326         Public method to get the cursor position.
  327         
  328         @return cursor position
  329         @rtype int
  330         """
  331         return self.__cursorPosition
  332     
  333     def setCursorPosition(self, pos):
  334         """
  335         Public method to set the cursor position.
  336         
  337         @param pos cursor position
  338         @type int
  339         """
  340         # step 1: delete old cursor
  341         self.__blink = False
  342         self.viewport().update(self.__cursorRect)
  343         if self.__asciiArea:
  344             self.viewport().update(self.__cursorRectAscii)
  345         
  346         # step 2: check, if cursor is in range
  347         if self.__overwriteMode and pos > (self.__chunks.size() * 2 - 1):
  348             pos = self.__chunks.size() * 2 - 1
  349         if (not self.__overwriteMode) and pos > (self.__chunks.size() * 2):
  350             pos = self.__chunks.size() * 2
  351         if pos < 0:
  352             pos = 0
  353         
  354         # step 3: calculate new position of cursor
  355         self.__cursorPosition = pos
  356         self.__bPosCurrent = pos // 2
  357         self.__pxCursorY = (
  358             ((pos // 2 - self.__bPosFirst) // self.BYTES_PER_LINE + 1) *
  359             self.__pxCharHeight)
  360         x = (pos % (2 * self.BYTES_PER_LINE))
  361         self.__pxCursorX = (
  362             (((x // 2) * 3) + (x % 2)) * self.__pxCharWidth + self.__pxPosHexX)
  363         
  364         self.__setHexCursorRect()
  365         
  366         # step 4: calculate position of ASCII cursor
  367         x = self.__bPosCurrent % self.BYTES_PER_LINE
  368         self.__cursorRectAscii = QRect(
  369             self.__pxPosAsciiX + x * self.__pxCharWidth - 1,
  370             self.__pxCursorY - self.__pxCharHeight + 4,
  371             self.__pxCharWidth + 1, self.__pxCharHeight + 1)
  372         
  373         # step 5: draw new cursors
  374         self.__blink = True
  375         self.viewport().update(self.__cursorRect)
  376         if self.__asciiArea:
  377             self.viewport().update(self.__cursorRectAscii)
  378         self.currentAddressChanged.emit(self.__bPosCurrent)
  379     
  380     def __setHexCursorRect(self):
  381         """
  382         Private method to set the cursor.
  383         """
  384         if self.__overwriteMode:
  385             self.__cursorRect = QRect(
  386                 self.__pxCursorX, self.__pxCursorY + self.__pxCursorWidth,
  387                 self.__pxCharWidth, self.__pxCursorWidth)
  388         else:
  389             self.__cursorRect = QRect(
  390                 self.__pxCursorX, self.__pxCursorY - self.__pxCharHeight + 4,
  391                 self.__pxCursorWidth, self.__pxCharHeight)
  392     
  393     def cursorBytePosition(self):
  394         """
  395         Public method to get the cursor position in bytes.
  396         
  397         @return cursor position in bytes
  398         @rtype int
  399         """
  400         return self.__bPosCurrent
  401     
  402     def setCursorBytePosition(self, pos):
  403         """
  404         Public method to set the cursor position in bytes.
  405         
  406         @param pos cursor position in bytes
  407         @type int
  408         """
  409         self.setCursorPosition(pos * 2)
  410     
  411     def goto(self, offset, fromCursor=False, backwards=False,
  412              extendSelection=False):
  413         """
  414         Public method to move the cursor.
  415         
  416         @param offset offset to move to
  417         @type int
  418         @param fromCursor flag indicating a move relative to the current cursor
  419         @type bool
  420         @param backwards flag indicating a backwards move
  421         @type bool
  422         @param extendSelection flag indicating to extend the selection
  423         @type bool
  424         """
  425         if fromCursor:
  426             if backwards:
  427                 newPos = self.cursorBytePosition() - offset
  428             else:
  429                 newPos = self.cursorBytePosition() + offset
  430         else:
  431             if backwards:
  432                 newPos = self.__chunks.size() - offset
  433             else:
  434                 newPos = offset
  435         
  436         self.setCursorBytePosition(newPos)
  437         if extendSelection:
  438             self.__setSelection(self.__cursorPosition)
  439         else:
  440             self.__resetSelection(self.__cursorPosition)
  441         
  442         self.__refresh()
  443     
  444     def data(self):
  445         """
  446         Public method to get the binary data.
  447         
  448         @return binary data
  449         @rtype bytearray
  450         """
  451         return self.__chunks.data(0, -1)
  452     
  453     def setData(self, dataOrDevice):
  454         """
  455         Public method to set the data to show.
  456         
  457         @param dataOrDevice byte array or device containing the data
  458         @type bytearray, QByteArray or QIODevice
  459         @return flag indicating success
  460         @rtype bool
  461         @exception TypeError raised to indicate a wrong parameter type
  462         """
  463         if isinstance(dataOrDevice, (bytearray, QByteArray)):
  464             self.__data = bytearray(dataOrDevice)
  465             self.__bData.setData(self.__data)
  466             return self.__setData(self.__bData)
  467         elif isinstance(dataOrDevice, QIODevice):
  468             return self.__setData(dataOrDevice)
  469         else:
  470             raise TypeError(
  471                 "setData: parameter must be bytearray, "
  472                 "QByteArray or QIODevice")
  473     
  474     def __setData(self, ioDevice):
  475         """
  476         Private method to set the data to show.
  477         
  478         @param ioDevice device containing the data
  479         @type QIODevice
  480         @return flag indicating success
  481         @rtype bool
  482         """
  483         ok = self.__chunks.setIODevice(ioDevice)
  484         self.__initialize()
  485         self.__dataChangedPrivate()
  486         return ok
  487     
  488     def highlighting(self):
  489         """
  490         Public method to get the highlighting state.
  491         
  492         @return highlighting state
  493         @rtype bool
  494         """
  495         return self.__highlighting
  496     
  497     def setHighlighting(self, on):
  498         """
  499         Public method to set the highlighting state.
  500         
  501         @param on new highlighting state
  502         @type bool
  503         """
  504         self.__highlighting = on
  505         self.viewport().update()
  506     
  507     def highlightColors(self):
  508         """
  509         Public method to get the highlight colors.
  510         
  511         @return highlight foreground and background colors
  512         @rtype tuple of 2 QColor
  513         """
  514         return self.__highlightingPen.color(), self.__highlightingBrush.color()
  515     
  516     def setHighlightColors(self, foreground, background):
  517         """
  518         Public method to set the highlight colors.
  519         
  520         @param foreground highlight foreground color
  521         @type QColor
  522         @param background highlight background color
  523         @type QColor
  524         """
  525         self.__highlightingPen = QPen(foreground)
  526         self.__highlightingBrush = QBrush(background)
  527         self.viewport().update()
  528     
  529     def overwriteMode(self):
  530         """
  531         Public method to get the overwrite mode.
  532         
  533         @return overwrite mode
  534         @rtype bool
  535         """
  536         return self.__overwriteMode
  537     
  538     def setOverwriteMode(self, on):
  539         """
  540         Public method to set the overwrite mode.
  541         
  542         @param on flag indicating the new overwrite mode
  543         @type bool
  544         """
  545         self.__overwriteMode = on
  546         self.overwriteModeChanged.emit(self.__overwriteMode)
  547         
  548         # step 1: delete old cursor
  549         self.__blink = False
  550         self.viewport().update(self.__cursorRect)
  551         # step 2: change the cursor rectangle
  552         self.__setHexCursorRect()
  553         # step 3: draw new cursors
  554         self.__blink = True
  555         self.viewport().update(self.__cursorRect)
  556     
  557     def selectionColors(self):
  558         """
  559         Public method to get the selection colors.
  560         
  561         @return selection foreground and background colors
  562         @rtype tuple of 2 QColor
  563         """
  564         return self.__selectionPen.color(), self.__selectionBrush.color()
  565     
  566     def setSelectionColors(self, foreground, background):
  567         """
  568         Public method to set the selection colors.
  569         
  570         @param foreground selection foreground color
  571         @type QColor
  572         @param background selection background color
  573         @type QColor
  574         """
  575         self.__selectionPen = QPen(foreground)
  576         self.__selectionBrush = QBrush(background)
  577         self.viewport().update()
  578     
  579     def isReadOnly(self):
  580         """
  581         Public method to test the read only state.
  582         
  583         @return flag indicating the read only state
  584         @rtype bool
  585         """
  586         return self.__readOnly
  587     
  588     def setReadOnly(self, on):
  589         """
  590         Public method to set the read only state.
  591         
  592         @param on new read only state
  593         @type bool
  594         """
  595         self.__readOnly = on
  596         self.readOnlyChanged.emit(self.__readOnly)
  597     
  598     def font(self):
  599         """
  600         Public method to get the font used to show the data.
  601         
  602         @return font used to show the data
  603         @rtype QFont
  604         """
  605         return super(HexEditWidget, self).font()
  606     
  607     def setFont(self, font):
  608         """
  609         Public method to set the font used to show the data.
  610         
  611         @param font font used to show the data
  612         @type QFont
  613         """
  614         super(HexEditWidget, self).setFont(font)
  615         try:
  616             self.__pxCharWidth = self.fontMetrics().horizontalAdvance("2")
  617         except AttributeError:
  618             self.__pxCharWidth = self.fontMetrics().width("2")
  619         self.__pxCharHeight = self.fontMetrics().height()
  620         self.__pxGapAdr = self.__pxCharWidth // 2
  621         self.__pxGapAdrHex = self.__pxCharWidth
  622         self.__pxGapHexAscii = 2 * self.__pxCharWidth
  623         self.__pxCursorWidth = self.__pxCharHeight // 7
  624         self.__pxSelectionSub = self.fontMetrics().descent()
  625         self.__adjust()
  626         self.viewport().update()
  627     
  628     def dataAt(self, pos, count=-1):
  629         """
  630         Public method to get data from a given position.
  631         
  632         @param pos position to get data from
  633         @type int
  634         @param count amount of bytes to get
  635         @type int
  636         @return requested data
  637         @rtype bytearray
  638         """
  639         return bytearray(self.__chunks.data(pos, count))
  640     
  641     def write(self, device, pos=0, count=-1):
  642         """
  643         Public method to write data from a given position to a device.
  644         
  645         @param device device to write to
  646         @type QIODevice
  647         @param pos position to start the write at
  648         @type int
  649         @param count amount of bytes to write
  650         @type int
  651         @return flag indicating success
  652         @rtype bool
  653         """
  654         return self.__chunks.write(device, pos, count)
  655     
  656     def insert(self, pos, ch):
  657         """
  658         Public method to insert a byte.
  659         
  660         @param pos position to insert the byte at
  661         @type int
  662         @param ch byte to insert
  663         @type int in the range 0x00 to 0xff
  664         """
  665         if ch in range(0, 256):
  666             self.__undoStack.insert(pos, ch)
  667             self.__refresh()
  668     
  669     def remove(self, pos, length=1):
  670         """
  671         Public method to remove bytes.
  672         
  673         @param pos position to remove bytes from
  674         @type int
  675         @param length amount of bytes to remove
  676         @type int
  677         """
  678         self.__undoStack.removeAt(pos, length)
  679         self.__refresh()
  680     
  681     def replace(self, pos, ch):
  682         """
  683         Public method to replace a byte.
  684         
  685         @param pos position to replace the byte at
  686         @type int
  687         @param ch byte to replace with
  688         @type int in the range 0x00 to 0xff
  689         """
  690         if ch in range(0, 256):
  691             self.__undoStack.overwrite(pos, ch)
  692             self.__refresh()
  693     
  694     def insertByteArray(self, pos, byteArray):
  695         """
  696         Public method to insert bytes.
  697         
  698         @param pos position to insert the bytes at
  699         @type int
  700         @param byteArray bytes to be insert
  701         @type bytearray or QByteArray
  702         """
  703         self.__undoStack.insertByteArray(pos, bytearray(byteArray))
  704         self.__refresh()
  705     
  706     def replaceByteArray(self, pos, length, byteArray):
  707         """
  708         Public method to replace bytes.
  709         
  710         @param pos position to replace the bytes at
  711         @type int
  712         @param length amount of bytes to replace
  713         @type int
  714         @param byteArray bytes to replace with
  715         @type bytearray or QByteArray
  716         """
  717         self.__undoStack.overwriteByteArray(pos, length, bytearray(byteArray))
  718         self.__refresh()
  719     
  720     def cursorPositionFromPoint(self, point):
  721         """
  722         Public method to calculate a cursor position from a graphics position.
  723         
  724         @param point graphics position
  725         @type QPoint
  726         @return cursor position
  727         @rtype int
  728         """
  729         result = -1
  730         if (point.x() >= self.__pxPosHexX) and (
  731             point.x() < (self.__pxPosHexX + (1 + self.HEXCHARS_PER_LINE) *
  732                          self.__pxCharWidth)):
  733             x = (
  734                 (point.x() - self.__pxPosHexX - self.__pxCharWidth // 2) //
  735                 self.__pxCharWidth
  736             )
  737             x = (x // 3) * 2 + x % 3
  738             y = (
  739                 ((point.y() - 3) // self.__pxCharHeight) * 2 *
  740                 self.BYTES_PER_LINE
  741             )
  742             result = self.__bPosFirst * 2 + x + y
  743         return result
  744     
  745     def ensureVisible(self):
  746         """
  747         Public method to ensure, that the cursor is visible.
  748         """
  749         if self.__cursorPosition < 2 * self.__bPosFirst:
  750             self.verticalScrollBar().setValue(
  751                 self.__cursorPosition // 2 // self.BYTES_PER_LINE)
  752         if self.__cursorPosition > (
  753             (self.__bPosFirst + (self.__rowsShown - 1) *
  754              self.BYTES_PER_LINE) * 2):
  755             self.verticalScrollBar().setValue(
  756                 self.__cursorPosition // 2 // self.BYTES_PER_LINE -
  757                 self.__rowsShown + 1)
  758         self.viewport().update()
  759     
  760     def indexOf(self, byteArray, start):
  761         """
  762         Public method to find the first occurrence of a byte array in our data.
  763         
  764         @param byteArray data to search for
  765         @type bytearray or QByteArray
  766         @param start start position of the search
  767         @type int
  768         @return position of match (or -1 if not found)
  769         @rtype int
  770         """
  771         byteArray = bytearray(byteArray)
  772         pos = self.__chunks.indexOf(byteArray, start)
  773         if pos > -1:
  774             curPos = pos * 2
  775             self.setCursorPosition(curPos + len(byteArray) * 2)
  776             self.__resetSelection(curPos)
  777             self.__setSelection(curPos + len(byteArray) * 2)
  778             self.ensureVisible()
  779         return pos
  780     
  781     def lastIndexOf(self, byteArray, start):
  782         """
  783         Public method to find the last occurrence of a byte array in our data.
  784         
  785         @param byteArray data to search for
  786         @type bytearray or QByteArray
  787         @param start start position of the search
  788         @type int
  789         @return position of match (or -1 if not found)
  790         @rtype int
  791         """
  792         byteArray = bytearray(byteArray)
  793         pos = self.__chunks.lastIndexOf(byteArray, start)
  794         if pos > -1:
  795             curPos = pos * 2
  796             self.setCursorPosition(curPos - 1)
  797             self.__resetSelection(curPos)
  798             self.__setSelection(curPos + len(byteArray) * 2)
  799             self.ensureVisible()
  800         return pos
  801     
  802     def isModified(self):
  803         """
  804         Public method to check for any modification.
  805         
  806         @return flag indicating a modified state
  807         @rtype bool
  808         """
  809         return self.__modified
  810     
  811     def setModified(self, modified, setCleanState=False):
  812         """
  813         Public slot to set the modified flag.
  814         
  815         @param modified flag indicating the new modification status
  816         @type bool
  817         @param setCleanState flag indicating to set the undo stack to clean
  818         @type bool
  819         """
  820         self.__modified = modified
  821         self.dataChanged.emit(modified)
  822         
  823         if not modified and setCleanState:
  824             self.__undoStack.setClean()
  825     
  826     def selectionToHexString(self):
  827         """
  828         Public method to get a hexadecimal representation of the selection.
  829         
  830         @return hexadecimal representation of the selection
  831         @rtype str
  832         """
  833         byteArray = self.__chunks.data(self.getSelectionBegin(),
  834                                        self.getSelectionLength())
  835         return self.__toHex(byteArray).decode(encoding="ascii")
  836     
  837     def selectionToReadableString(self):
  838         """
  839         Public method to get a formatted representation of the selection.
  840         
  841         @return formatted representation of the selection
  842         @rtype str
  843         """
  844         byteArray = self.__chunks.data(self.getSelectionBegin(),
  845                                        self.getSelectionLength())
  846         return self.__toReadable(byteArray)
  847     
  848     def toReadableString(self):
  849         """
  850         Public method to get a formatted representation of our data.
  851         
  852         @return formatted representation of our data
  853         @rtype str
  854         """
  855         byteArray = self.__chunks.data()
  856         return self.__toReadable(byteArray)
  857     
  858     @pyqtSlot()
  859     def redo(self):
  860         """
  861         Public slot to redo the last operation.
  862         """
  863         self.__undoStack.redo()
  864         self.setCursorPosition(self.__chunks.pos() * 2)
  865         self.__refresh()
  866     
  867     @pyqtSlot()
  868     def undo(self):
  869         """
  870         Public slot to undo the last operation.
  871         """
  872         self.__undoStack.undo()
  873         self.setCursorPosition(self.__chunks.pos() * 2)
  874         self.__refresh()
  875     
  876     @pyqtSlot()
  877     def revertToUnmodified(self):
  878         """
  879         Public slot to revert all changes.
  880         """
  881         cleanIndex = self.__undoStack.cleanIndex()
  882         if cleanIndex >= 0:
  883             self.__undoStack.setIndex(cleanIndex)
  884         self.setCursorPosition(self.__chunks.pos() * 2)
  885         self.__refresh()
  886     
  887     ####################################################
  888     ## Cursor movement commands
  889     ####################################################
  890     
  891     def moveCursorToNextChar(self):
  892         """
  893         Public method to move the cursor to the next byte.
  894         """
  895         self.setCursorPosition(self.__cursorPosition + 1)
  896         self.__resetSelection(self.__cursorPosition)
  897     
  898     def moveCursorToPreviousChar(self):
  899         """
  900         Public method to move the cursor to the previous byte.
  901         """
  902         self.setCursorPosition(self.__cursorPosition - 1)
  903         self.__resetSelection(self.__cursorPosition)
  904     
  905     def moveCursorToEndOfLine(self):
  906         """
  907         Public method to move the cursor to the end of the current line.
  908         """
  909         self.setCursorPosition(self.__cursorPosition |
  910                                (2 * self.BYTES_PER_LINE - 1))
  911         self.__resetSelection(self.__cursorPosition)
  912     
  913     def moveCursorToStartOfLine(self):
  914         """
  915         Public method to move the cursor to the beginning of the current line.
  916         """
  917         self.setCursorPosition(
  918             self.__cursorPosition -
  919             (self.__cursorPosition % (2 * self.BYTES_PER_LINE)))
  920         self.__resetSelection(self.__cursorPosition)
  921     
  922     def moveCursorToPreviousLine(self):
  923         """
  924         Public method to move the cursor to the previous line.
  925         """
  926         self.setCursorPosition(self.__cursorPosition - 2 * self.BYTES_PER_LINE)
  927         self.__resetSelection(self.__cursorPosition)
  928     
  929     def moveCursorToNextLine(self):
  930         """
  931         Public method to move the cursor to the next line.
  932         """
  933         self.setCursorPosition(self.__cursorPosition + 2 * self.BYTES_PER_LINE)
  934         self.__resetSelection(self.__cursorPosition)
  935     
  936     def moveCursorToNextPage(self):
  937         """
  938         Public method to move the cursor to the next page.
  939         """
  940         self.setCursorPosition(
  941             self.__cursorPosition +
  942             (self.__rowsShown - 1) * 2 * self.BYTES_PER_LINE)
  943         self.__resetSelection(self.__cursorPosition)
  944     
  945     def moveCursorToPreviousPage(self):
  946         """
  947         Public method to move the cursor to the previous page.
  948         """
  949         self.setCursorPosition(
  950             self.__cursorPosition -
  951             (self.__rowsShown - 1) * 2 * self.BYTES_PER_LINE)
  952         self.__resetSelection(self.__cursorPosition)
  953     
  954     def moveCursorToEndOfDocument(self):
  955         """
  956         Public method to move the cursor to the end of the data.
  957         """
  958         self.setCursorPosition(self.__chunks.size() * 2)
  959         self.__resetSelection(self.__cursorPosition)
  960     
  961     def moveCursorToStartOfDocument(self):
  962         """
  963         Public method to move the cursor to the start of the data.
  964         """
  965         self.setCursorPosition(0)
  966         self.__resetSelection(self.__cursorPosition)
  967     
  968     ####################################################
  969     ## Selection commands
  970     ####################################################
  971     
  972     def deselectAll(self):
  973         """
  974         Public method to deselect all data.
  975         """
  976         self.__resetSelection(0)
  977         self.__refresh()
  978     
  979     def selectAll(self):
  980         """
  981         Public method to select all data.
  982         """
  983         self.__resetSelection(0)
  984         self.__setSelection(2 * self.__chunks.size() + 1)
  985         self.__refresh()
  986     
  987     def selectNextChar(self):
  988         """
  989         Public method to extend the selection by one byte right.
  990         """
  991         pos = self.__cursorPosition + 1
  992         self.setCursorPosition(pos)
  993         self.__setSelection(pos)
  994     
  995     def selectPreviousChar(self):
  996         """
  997         Public method to extend the selection by one byte left.
  998         """
  999         pos = self.__cursorPosition - 1
 1000         self.setCursorPosition(pos)
 1001         self.__setSelection(pos)
 1002     
 1003     def selectToEndOfLine(self):
 1004         """
 1005         Public method to extend the selection to the end of line.
 1006         """
 1007         pos = (
 1008             self.__cursorPosition -
 1009             (self.__cursorPosition % (2 * self.BYTES_PER_LINE)) +
 1010             2 * self.BYTES_PER_LINE
 1011         )
 1012         self.setCursorPosition(pos)
 1013         self.__setSelection(pos)
 1014     
 1015     def selectToStartOfLine(self):
 1016         """
 1017         Public method to extend the selection to the start of line.
 1018         """
 1019         pos = (
 1020             self.__cursorPosition -
 1021             (self.__cursorPosition % (2 * self.BYTES_PER_LINE))
 1022         )
 1023         self.setCursorPosition(pos)
 1024         self.__setSelection(pos)
 1025     
 1026     def selectPreviousLine(self):
 1027         """
 1028         Public method to extend the selection one line up.
 1029         """
 1030         pos = self.__cursorPosition - 2 * self.BYTES_PER_LINE
 1031         self.setCursorPosition(pos)
 1032         self.__setSelection(pos)
 1033     
 1034     def selectNextLine(self):
 1035         """
 1036         Public method to extend the selection one line down.
 1037         """
 1038         pos = self.__cursorPosition + 2 * self.BYTES_PER_LINE
 1039         self.setCursorPosition(pos)
 1040         self.__setSelection(pos)
 1041     
 1042     def selectNextPage(self):
 1043         """
 1044         Public method to extend the selection one page down.
 1045         """
 1046         pos = (
 1047             self.__cursorPosition +
 1048             ((self.viewport().height() // self.__pxCharHeight) - 1) *
 1049             2 * self.BYTES_PER_LINE
 1050         )
 1051         self.setCursorPosition(pos)
 1052         self.__setSelection(pos)
 1053     
 1054     def selectPreviousPage(self):
 1055         """
 1056         Public method to extend the selection one page up.
 1057         """
 1058         pos = (
 1059             self.__cursorPosition -
 1060             ((self.viewport().height() // self.__pxCharHeight) - 1) *
 1061             2 * self.BYTES_PER_LINE
 1062         )
 1063         self.setCursorPosition(pos)
 1064         self.__setSelection(pos)
 1065     
 1066     def selectEndOfDocument(self):
 1067         """
 1068         Public method to extend the selection to the end of the data.
 1069         """
 1070         pos = self.__chunks.size() * 2
 1071         self.setCursorPosition(pos)
 1072         self.__setSelection(pos)
 1073     
 1074     def selectStartOfDocument(self):
 1075         """
 1076         Public method to extend the selection to the start of the data.
 1077         """
 1078         pos = 0
 1079         self.setCursorPosition(pos)
 1080         self.__setSelection(pos)
 1081     
 1082     ####################################################
 1083     ## Edit commands
 1084     ####################################################
 1085     
 1086     def cut(self):
 1087         """
 1088         Public method to cut the selected bytes and move them to the clipboard.
 1089         """
 1090         if not self.__readOnly:
 1091             byteArray = self.__toHex(self.__chunks.data(
 1092                 self.getSelectionBegin(), self.getSelectionLength()))
 1093             idx = 32
 1094             while idx < len(byteArray):
 1095                 byteArray.insert(idx, "\n")
 1096                 idx += 33
 1097             cb = QApplication.clipboard()
 1098             cb.setText(byteArray.decode(encoding="latin1"))
 1099             if self.__overwriteMode:
 1100                 length = self.getSelectionLength()
 1101                 self.replaceByteArray(self.getSelectionBegin(), length,
 1102                                       bytearray(length))
 1103             else:
 1104                 self.remove(self.getSelectionBegin(),
 1105                             self.getSelectionLength())
 1106             self.setCursorPosition(2 * self.getSelectionBegin())
 1107             self.__resetSelection(2 * self.getSelectionBegin())
 1108     
 1109     def copy(self):
 1110         """
 1111         Public method to copy the selected bytes to the clipboard.
 1112         """
 1113         byteArray = self.__toHex(self.__chunks.data(
 1114             self.getSelectionBegin(), self.getSelectionLength()))
 1115         idx = 32
 1116         while idx < len(byteArray):
 1117             byteArray.insert(idx, "\n")
 1118             idx += 33
 1119         cb = QApplication.clipboard()
 1120         cb.setText(byteArray.decode(encoding="latin1"))
 1121     
 1122     def paste(self):
 1123         """
 1124         Public method to paste bytes from the clipboard.
 1125         """
 1126         if not self.__readOnly:
 1127             cb = QApplication.clipboard()
 1128             byteArray = self.__fromHex(cb.text().encode(encoding="latin1"))
 1129             if self.__overwriteMode:
 1130                 self.replaceByteArray(self.__bPosCurrent, len(byteArray),
 1131                                       byteArray)
 1132             else:
 1133                 self.insertByteArray(self.__bPosCurrent, byteArray)
 1134             self.setCursorPosition(
 1135                 self.__cursorPosition + 2 * len(byteArray))
 1136             self.__resetSelection(2 * self.getSelectionBegin())
 1137     
 1138     def deleteByte(self):
 1139         """
 1140         Public method to delete the current byte.
 1141         """
 1142         if not self.__readOnly:
 1143             if self.hasSelection():
 1144                 self.__bPosCurrent = self.getSelectionBegin()
 1145                 if self.__overwriteMode:
 1146                     byteArray = bytearray(self.getSelectionLength())
 1147                     self.replaceByteArray(self.__bPosCurrent, len(byteArray),
 1148                                           byteArray)
 1149                 else:
 1150                     self.remove(self.__bPosCurrent,
 1151                                 self.getSelectionLength())
 1152             else:
 1153                 if self.__overwriteMode:
 1154                     self.replace(self.__bPosCurrent, 0)
 1155                 else:
 1156                     self.remove(self.__bPosCurrent, 1)
 1157             self.setCursorPosition(2 * self.__bPosCurrent)
 1158             self.__resetSelection(2 * self.__bPosCurrent)
 1159     
 1160     def deleteByteBack(self):
 1161         """
 1162         Public method to delete the previous byte.
 1163         """
 1164         if not self.__readOnly:
 1165             if self.hasSelection():
 1166                 self.__bPosCurrent = self.getSelectionBegin()
 1167                 self.setCursorPosition(2 * self.__bPosCurrent)
 1168                 if self.__overwriteMode:
 1169                     byteArray = bytearray(self.getSelectionLength())
 1170                     self.replaceByteArray(self.__bPosCurrent, len(byteArray),
 1171                                           byteArray)
 1172                 else:
 1173                     self.remove(self.__bPosCurrent,
 1174                                 self.getSelectionLength())
 1175             else:
 1176                 self.__bPosCurrent -= 1
 1177                 if self.__overwriteMode:
 1178                     self.replace(self.__bPosCurrent, 0)
 1179                 else:
 1180                     self.remove(self.__bPosCurrent, 1)
 1181                 self.setCursorPosition(2 * self.__bPosCurrent)
 1182             self.__resetSelection(2 * self.__bPosCurrent)
 1183     
 1184     ####################################################
 1185     ## Event handling methods
 1186     ####################################################
 1187     
 1188     def keyPressEvent(self, evt):
 1189         """
 1190         Protected method to handle key press events.
 1191         
 1192         @param evt reference to the key event
 1193         @type QKeyEvent
 1194         """
 1195         # Cursor movements
 1196         if evt.matches(QKeySequence.MoveToNextChar):
 1197             self.moveCursorToNextChar()
 1198         elif evt.matches(QKeySequence.MoveToPreviousChar):
 1199             self.moveCursorToPreviousChar()
 1200         elif evt.matches(QKeySequence.MoveToEndOfLine):
 1201             self.moveCursorToEndOfLine()
 1202         elif evt.matches(QKeySequence.MoveToStartOfLine):
 1203             self.moveCursorToStartOfLine()
 1204         elif evt.matches(QKeySequence.MoveToPreviousLine):
 1205             self.moveCursorToPreviousLine()
 1206         elif evt.matches(QKeySequence.MoveToNextLine):
 1207             self.moveCursorToNextLine()
 1208         elif evt.matches(QKeySequence.MoveToNextPage):
 1209             self.moveCursorToNextPage()
 1210         elif evt.matches(QKeySequence.MoveToPreviousPage):
 1211             self.moveCursorToPreviousPage()
 1212         elif evt.matches(QKeySequence.MoveToEndOfDocument):
 1213             self.moveCursorToEndOfDocument()
 1214         elif evt.matches(QKeySequence.MoveToStartOfDocument):
 1215             self.moveCursorToStartOfDocument()
 1216         
 1217         # Selection commands
 1218         elif evt.matches(QKeySequence.SelectAll):
 1219             self.selectAll()
 1220         elif evt.matches(QKeySequence.SelectNextChar):
 1221             self.selectNextChar()
 1222         elif evt.matches(QKeySequence.SelectPreviousChar):
 1223             self.selectPreviousChar()
 1224         elif evt.matches(QKeySequence.SelectEndOfLine):
 1225             self.selectToEndOfLine()
 1226         elif evt.matches(QKeySequence.SelectStartOfLine):
 1227             self.selectToStartOfLine()
 1228         elif evt.matches(QKeySequence.SelectPreviousLine):
 1229             self.selectPreviousLine()
 1230         elif evt.matches(QKeySequence.SelectNextLine):
 1231             self.selectNextLine()
 1232         elif evt.matches(QKeySequence.SelectNextPage):
 1233             self.selectNextPage()
 1234         elif evt.matches(QKeySequence.SelectPreviousPage):
 1235             self.selectPreviousPage()
 1236         elif evt.matches(QKeySequence.SelectEndOfDocument):
 1237             self.selectEndOfDocument()
 1238         elif evt.matches(QKeySequence.SelectStartOfDocument):
 1239             self.selectStartOfDocument()
 1240         
 1241         # Edit commands
 1242         elif evt.matches(QKeySequence.Copy):
 1243             self.copy()
 1244         elif (
 1245             evt.key() == Qt.Key_Insert and
 1246             evt.modifiers() == Qt.NoModifier
 1247         ):
 1248             self.setOverwriteMode(not self.overwriteMode())
 1249             self.setCursorPosition(self.__cursorPosition)
 1250         
 1251         elif not self.__readOnly:
 1252             if evt.matches(QKeySequence.Cut):
 1253                 self.cut()
 1254             elif evt.matches(QKeySequence.Paste):
 1255                 self.paste()
 1256             elif evt.matches(QKeySequence.Delete):
 1257                 self.deleteByte()
 1258             elif (
 1259                 evt.key() == Qt.Key_Backspace and
 1260                 evt.modifiers() == Qt.NoModifier
 1261             ):
 1262                 self.deleteByteBack()
 1263             elif evt.matches(QKeySequence.Undo):
 1264                 self.undo()
 1265             elif evt.matches(QKeySequence.Redo):
 1266                 self.redo()
 1267             
 1268             elif QApplication.keyboardModifiers() in [
 1269                     Qt.NoModifier, Qt.KeypadModifier]:
 1270                 # some hex input
 1271                 key = evt.text()
 1272                 if key and key in "0123456789abcdef":
 1273                     if self.hasSelection():
 1274                         if self.__overwriteMode:
 1275                             length = self.getSelectionLength()
 1276                             self.replaceByteArray(
 1277                                 self.getSelectionBegin(), length,
 1278                                 bytearray(length))
 1279                         else:
 1280                             self.remove(self.getSelectionBegin(),
 1281                                         self.getSelectionLength())
 1282                             self.__bPosCurrent = self.getSelectionBegin()
 1283                         self.setCursorPosition(2 * self.__bPosCurrent)
 1284                         self.__resetSelection(2 * self.__bPosCurrent)
 1285                     
 1286                     # if in insert mode, insert a byte
 1287                     if not self.__overwriteMode:
 1288                         if (self.__cursorPosition % 2) == 0:
 1289                             self.insert(self.__bPosCurrent, 0)
 1290                     
 1291                     # change content
 1292                     if self.__chunks.size() > 0:
 1293                         hexValue = self.__toHex(
 1294                             self.__chunks.data(self.__bPosCurrent, 1))
 1295                         if (self.__cursorPosition % 2) == 0:
 1296                             hexValue[0] = ord(key)
 1297                         else:
 1298                             hexValue[1] = ord(key)
 1299                         self.replace(self.__bPosCurrent,
 1300                                      self.__fromHex(hexValue)[0])
 1301                         
 1302                         self.setCursorPosition(self.__cursorPosition + 1)
 1303                         self.__resetSelection(self.__cursorPosition)
 1304                     else:
 1305                         return
 1306                 else:
 1307                     return
 1308             else:
 1309                 return
 1310         else:
 1311             return
 1312         
 1313         self.__refresh()
 1314     
 1315     def mouseMoveEvent(self, evt):
 1316         """
 1317         Protected method to handle mouse moves.
 1318         
 1319         @param evt reference to the mouse event
 1320         @type QMouseEvent
 1321         """
 1322         self.__blink = False
 1323         self.viewport().update()
 1324         actPos = self.cursorPositionFromPoint(evt.pos())
 1325         if actPos >= 0:
 1326             self.setCursorPosition(actPos)
 1327             self.__setSelection(actPos)
 1328     
 1329     def mousePressEvent(self, evt):
 1330         """
 1331         Protected method to handle mouse button presses.
 1332         
 1333         @param evt reference to the mouse event
 1334         @type QMouseEvent
 1335         """
 1336         self.__blink = False
 1337         self.viewport().update()
 1338         cPos = self.cursorPositionFromPoint(evt.pos())
 1339         if cPos >= 0:
 1340             if evt.modifiers() == Qt.ShiftModifier:
 1341                 self.__setSelection(cPos)
 1342             else:
 1343                 self.__resetSelection(cPos)
 1344             self.setCursorPosition(cPos)
 1345     
 1346     def paintEvent(self, evt):
 1347         """
 1348         Protected method to handle paint events.
 1349         
 1350         @param evt reference to the paint event
 1351         @type QPaintEvent
 1352         """
 1353         painter = QPainter(self.viewport())
 1354         
 1355         if (
 1356             evt.rect() != self.__cursorRect and
 1357             evt.rect() != self.__cursorRectAscii
 1358         ):
 1359             pxOfsX = self.horizontalScrollBar().value()
 1360             pxPosStartY = self.__pxCharHeight
 1361             
 1362             # draw some patterns if needed
 1363             painter.fillRect(
 1364                 evt.rect(), self.viewport().palette().color(QPalette.Base))
 1365             if self.__addressArea:
 1366                 painter.fillRect(
 1367                     QRect(-pxOfsX, evt.rect().top(),
 1368                           self.__pxPosHexX - self.__pxGapAdrHex // 2 - pxOfsX,
 1369                           self.height()),
 1370                     self.__addressAreaBrush)
 1371             if self.__asciiArea:
 1372                 linePos = self.__pxPosAsciiX - (self.__pxGapHexAscii // 2)
 1373                 painter.setPen(Qt.gray)
 1374                 painter.drawLine(linePos - pxOfsX, evt.rect().top(),
 1375                                  linePos - pxOfsX, self.height())
 1376             
 1377             painter.setPen(
 1378                 self.viewport().palette().color(QPalette.WindowText))
 1379             
 1380             # paint the address area
 1381             if self.__addressArea:
 1382                 painter.setPen(self.__addressAreaPen)
 1383                 address = ""
 1384                 row = 0
 1385                 pxPosY = self.__pxCharHeight
 1386                 while row <= len(self.__dataShown) // self.BYTES_PER_LINE:
 1387                     address = "{0:0{1}x}".format(
 1388                         self.__bPosFirst + row * self.BYTES_PER_LINE,
 1389                         self.__addrDigits)
 1390                     address = Globals.strGroup(address, ":", 4)
 1391                     painter.drawText(self.__pxPosAdrX - pxOfsX, pxPosY,
 1392                                      address)
 1393                     # increment loop variables
 1394                     row += 1
 1395                     pxPosY += self.__pxCharHeight
 1396             
 1397             # paint hex and ascii area
 1398             colStandard = QPen(
 1399                 self.viewport().palette().color(QPalette.WindowText))
 1400             
 1401             painter.setBackgroundMode(Qt.TransparentMode)
 1402             
 1403             row = 0
 1404             pxPosY = pxPosStartY
 1405             while row <= self.__rowsShown:
 1406                 pxPosX = self.__pxPosHexX - pxOfsX
 1407                 pxPosAsciiX2 = self.__pxPosAsciiX - pxOfsX
 1408                 bPosLine = row * self.BYTES_PER_LINE
 1409                 
 1410                 colIdx = 0
 1411                 while (
 1412                     bPosLine + colIdx < len(self.__dataShown) and
 1413                     colIdx < self.BYTES_PER_LINE
 1414                 ):
 1415                     c = self.viewport().palette().color(QPalette.Base)
 1416                     painter.setPen(colStandard)
 1417                     
 1418                     posBa = self.__bPosFirst + bPosLine + colIdx
 1419                     if (
 1420                         self.getSelectionBegin() <= posBa and
 1421                         self.getSelectionEnd() > posBa
 1422                     ):
 1423                         c = self.__selectionBrush.color()
 1424                         painter.setPen(self.__selectionPen)
 1425                     elif self.__highlighting:
 1426                         if self.__markedShown and self.__markedShown[
 1427                                 posBa - self.__bPosFirst]:
 1428                             c = self.__highlightingBrush.color()
 1429                             painter.setPen(self.__highlightingPen)
 1430                     
 1431                     # render hex value
 1432                     r = QRect()
 1433                     if colIdx == 0:
 1434                         r.setRect(
 1435                             pxPosX,
 1436                             pxPosY - self.__pxCharHeight +
 1437                             self.__pxSelectionSub,
 1438                             2 * self.__pxCharWidth,
 1439                             self.__pxCharHeight)
 1440                     else:
 1441                         r.setRect(
 1442                             pxPosX - self.__pxCharWidth,
 1443                             pxPosY - self.__pxCharHeight +
 1444                             self.__pxSelectionSub,
 1445                             3 * self.__pxCharWidth,
 1446                             self.__pxCharHeight)
 1447                     painter.fillRect(r, c)
 1448                     hexStr = (
 1449                         chr(self.__hexDataShown[(bPosLine + colIdx) * 2]) +
 1450                         chr(self.__hexDataShown[(bPosLine + colIdx) * 2 + 1])
 1451                     )
 1452                     painter.drawText(pxPosX, pxPosY, hexStr)
 1453                     pxPosX += 3 * self.__pxCharWidth
 1454                     
 1455                     # render ascii value
 1456                     if self.__asciiArea:
 1457                         by = self.__dataShown[bPosLine + colIdx]
 1458                         if by < 0x20 or (by > 0x7e and by < 0xa0):
 1459                             ch = "."
 1460                         else:
 1461                             ch = chr(by)
 1462                         r.setRect(
 1463                             pxPosAsciiX2,
 1464                             pxPosY - self.__pxCharHeight +
 1465                             self.__pxSelectionSub,
 1466                             self.__pxCharWidth,
 1467                             self.__pxCharHeight)
 1468                         painter.fillRect(r, c)
 1469                         painter.drawText(pxPosAsciiX2, pxPosY, ch)
 1470                         pxPosAsciiX2 += self.__pxCharWidth
 1471                     
 1472                     # increment loop variable
 1473                     colIdx += 1
 1474                 
 1475                 # increment loop variables
 1476                 row += 1
 1477                 pxPosY += self.__pxCharHeight
 1478         
 1479         painter.setBackgroundMode(Qt.TransparentMode)
 1480         painter.setPen(
 1481             self.viewport().palette().color(QPalette.WindowText))
 1482             
 1483         # paint cursor
 1484         if self.__blink and not self.__readOnly and self.isActiveWindow():
 1485             painter.fillRect(
 1486                 self.__cursorRect, self.palette().color(QPalette.WindowText))
 1487         else:
 1488             if self.__hexDataShown:
 1489                 try:
 1490                     c = chr(self.__hexDataShown[
 1491                             self.__cursorPosition - self.__bPosFirst * 2])
 1492                 except IndexError:
 1493                     c = ""
 1494             else:
 1495                 c = ""
 1496             painter.drawText(self.__pxCursorX, self.__pxCursorY, c)
 1497         
 1498         if self.__asciiArea:
 1499             painter.drawRect(self.__cursorRectAscii)
 1500         
 1501         # emit event, if size has changed
 1502         if self.__lastEventSize != self.__chunks.size():
 1503             self.__lastEventSize = self.__chunks.size()
 1504             self.currentSizeChanged.emit(self.__lastEventSize)
 1505     
 1506     def resizeEvent(self, evt):
 1507         """
 1508         Protected method to handle resize events.
 1509         
 1510         @param evt reference to the resize event
 1511         @type QResizeEvent
 1512         """
 1513         self.__adjust()
 1514     
 1515     def __resetSelection(self, pos=None):
 1516         """
 1517         Private method to reset the selection.
 1518         
 1519         @param pos position to set selection start and end to
 1520             (if this is None, selection end is set to selection start)
 1521         @type int or None
 1522         """
 1523         if pos is None:
 1524             self.__bSelectionBegin = self.__bSelectionInit
 1525             self.__bSelectionEnd = self.__bSelectionInit
 1526         else:
 1527             if pos < 0:
 1528                 pos = 0
 1529             pos = pos // 2
 1530             self.__bSelectionInit = pos
 1531             self.__bSelectionBegin = pos
 1532             self.__bSelectionEnd = pos
 1533         
 1534         self.selectionAvailable.emit(False)
 1535     
 1536     def __setSelection(self, pos):
 1537         """
 1538         Private method to set the selection.
 1539         
 1540         @param pos position
 1541         @type int
 1542         """
 1543         if pos < 0:
 1544             pos = 0
 1545         pos = pos // 2
 1546         if pos >= self.__bSelectionInit:
 1547             self.__bSelectionEnd = pos
 1548             self.__bSelectionBegin = self.__bSelectionInit
 1549         else:
 1550             self.__bSelectionBegin = pos
 1551             self.__bSelectionEnd = self.__bSelectionInit
 1552         
 1553         self.selectionAvailable.emit(True)
 1554     
 1555     def getSelectionBegin(self):
 1556         """
 1557         Public method to get the start of the selection.
 1558         
 1559         @return selection start
 1560         @rtype int
 1561         """
 1562         return self.__bSelectionBegin
 1563     
 1564     def getSelectionEnd(self):
 1565         """
 1566         Public method to get the end of the selection.
 1567         
 1568         @return selection end
 1569         @rtype int
 1570         """
 1571         return self.__bSelectionEnd
 1572     
 1573     def getSelectionLength(self):
 1574         """
 1575         Public method to get the length of the selection.
 1576         
 1577         @return selection length
 1578         @rtype int
 1579         """
 1580         return self.__bSelectionEnd - self.__bSelectionBegin
 1581     
 1582     def hasSelection(self):
 1583         """
 1584         Public method to test for a selection.
 1585         
 1586         @return flag indicating the presence of a selection
 1587         @rtype bool
 1588         """
 1589         return self.__bSelectionBegin != self.__bSelectionEnd
 1590     
 1591     def __initialize(self):
 1592         """
 1593         Private method to do some initialization.
 1594         """
 1595         self.__undoStack.clear()
 1596         self.setAddressOffset(0)
 1597         self.__resetSelection(0)
 1598         self.setCursorPosition(0)
 1599         self.verticalScrollBar().setValue(0)
 1600         self.__modified = False
 1601     
 1602     def __readBuffers(self):
 1603         """
 1604         Private method to read the buffers.
 1605         """
 1606         self.__dataShown = self.__chunks.data(
 1607             self.__bPosFirst,
 1608             self.__bPosLast - self.__bPosFirst + self.BYTES_PER_LINE + 1,
 1609             self.__markedShown
 1610         )
 1611         self.__hexDataShown = self.__toHex(self.__dataShown)
 1612     
 1613     def __toHex(self, byteArray):
 1614         """
 1615         Private method to convert the data of a Python bytearray to hex.
 1616         
 1617         @param byteArray byte array to be converted
 1618         @type bytearray
 1619         @return converted data
 1620         @rtype bytearray
 1621         """
 1622         return bytearray(QByteArray(byteArray).toHex())
 1623     
 1624     def __fromHex(self, byteArray):
 1625         """
 1626         Private method to convert data of a Python bytearray from hex.
 1627         
 1628         @param byteArray byte array to be converted
 1629         @type bytearray
 1630         @return converted data
 1631         @rtype bytearray
 1632         """
 1633         return bytearray(QByteArray.fromHex(byteArray))
 1634         
 1635     def __toReadable(self, byteArray):
 1636         """
 1637         Private method to convert some data into a readable format.
 1638         
 1639         @param byteArray data to be converted
 1640         @type bytearray or QByteArray
 1641         @return readable data
 1642         @rtype str
 1643         """
 1644         byteArray = bytearray(byteArray)
 1645         result = ""
 1646         for i in range(0, len(byteArray), 16):
 1647             addrStr = "{0:0{1}x}".format(self.__addressOffset + i,
 1648                                          self.addressWidth())
 1649             hexStr = ""
 1650             ascStr = ""
 1651             for j in range(16):
 1652                 if (i + j) < len(byteArray):
 1653                     hexStr += " {0:02x}".format(byteArray[i + j])
 1654                     by = byteArray[i + j]
 1655                     if by < 0x20 or (by > 0x7e and by < 0xa0):
 1656                         ch = "."
 1657                     else:
 1658                         ch = chr(by)
 1659                     ascStr += ch
 1660             result += "{0} {1:<48} {2:<17}\n".format(addrStr, hexStr, ascStr)
 1661         return result
 1662     
 1663     @pyqtSlot()
 1664     def __adjust(self):
 1665         """
 1666         Private slot to recalculate pixel positions.
 1667         """
 1668         # recalculate graphics
 1669         if self.__addressArea:
 1670             self.__addrDigits = self.addressWidth()
 1671             self.__addrSeparators = self.__addrDigits // 4 - 1
 1672             self.__pxPosHexX = (
 1673                 self.__pxGapAdr +
 1674                 (self.__addrDigits + self.__addrSeparators) *
 1675                 self.__pxCharWidth + self.__pxGapAdrHex)
 1676         else:
 1677             self.__pxPosHexX = self.__pxGapAdrHex
 1678         self.__pxPosAdrX = self.__pxGapAdr
 1679         self.__pxPosAsciiX = (
 1680             self.__pxPosHexX +
 1681             self.HEXCHARS_PER_LINE * self.__pxCharWidth +
 1682             self.__pxGapHexAscii
 1683         )
 1684         
 1685         # set horizontal scrollbar
 1686         pxWidth = self.__pxPosAsciiX
 1687         if self.__asciiArea:
 1688             pxWidth += self.BYTES_PER_LINE * self.__pxCharWidth
 1689         self.horizontalScrollBar().setRange(
 1690             0, pxWidth - self.viewport().width())
 1691         self.horizontalScrollBar().setPageStep(self.viewport().width())
 1692         
 1693         # set vertical scrollbar
 1694         self.__rowsShown = (
 1695             (self.viewport().height() - 4) // self.__pxCharHeight
 1696         )
 1697         lineCount = (self.__chunks.size() // self.BYTES_PER_LINE) + 1
 1698         self.verticalScrollBar().setRange(0, lineCount - self.__rowsShown)
 1699         self.verticalScrollBar().setPageStep(self.__rowsShown)
 1700         
 1701         # do the rest
 1702         value = self.verticalScrollBar().value()
 1703         self.__bPosFirst = value * self.BYTES_PER_LINE
 1704         self.__bPosLast = (
 1705             self.__bPosFirst + self.__rowsShown * self.BYTES_PER_LINE - 1
 1706         )
 1707         if self.__bPosLast >= self.__chunks.size():
 1708             self.__bPosLast = self.__chunks.size() - 1
 1709         self.__readBuffers()
 1710         self.setCursorPosition(self.__cursorPosition)
 1711     
 1712     @pyqtSlot(int)
 1713     def __dataChangedPrivate(self, idx=0):
 1714         """
 1715         Private slot to handle data changes.
 1716         
 1717         @param idx index
 1718         @type int
 1719         """
 1720         self.__modified = (
 1721             self.__undoStack.cleanIndex() == -1 or
 1722             self.__undoStack.index() != self.__undoStack.cleanIndex())
 1723         self.__adjust()
 1724         self.dataChanged.emit(self.__modified)
 1725     
 1726     @pyqtSlot()
 1727     def __refresh(self):
 1728         """
 1729         Private slot to refresh the display.
 1730         """
 1731         self.ensureVisible()
 1732         self.__readBuffers()
 1733     
 1734     @pyqtSlot()
 1735     def __updateCursor(self):
 1736         """
 1737         Private slot to update the blinking cursor.
 1738         """
 1739         self.__blink = not self.__blink
 1740         self.viewport().update(self.__cursorRect)