eric7/HexEdit/HexEditWidget.py

branch
eric7
changeset 8312
800c432b34c8
parent 8222
5994b80b8760
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2016 - 2021 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().__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.ColorRole.WindowText),
138 self.palette().alternateBase().color())
139 self.setHighlightColors(
140 self.palette().color(QPalette.ColorRole.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 not isinstance(dataOrDevice, (bytearray, QByteArray, QIODevice)):
464 raise TypeError(
465 "setData: parameter must be bytearray, "
466 "QByteArray or QIODevice")
467
468 if isinstance(dataOrDevice, (bytearray, QByteArray)):
469 self.__data = bytearray(dataOrDevice)
470 self.__bData.setData(self.__data)
471 return self.__setData(self.__bData)
472 else:
473 return self.__setData(dataOrDevice)
474
475 def __setData(self, ioDevice):
476 """
477 Private method to set the data to show.
478
479 @param ioDevice device containing the data
480 @type QIODevice
481 @return flag indicating success
482 @rtype bool
483 """
484 ok = self.__chunks.setIODevice(ioDevice)
485 self.__initialize()
486 self.__dataChangedPrivate()
487 return ok
488
489 def highlighting(self):
490 """
491 Public method to get the highlighting state.
492
493 @return highlighting state
494 @rtype bool
495 """
496 return self.__highlighting
497
498 def setHighlighting(self, on):
499 """
500 Public method to set the highlighting state.
501
502 @param on new highlighting state
503 @type bool
504 """
505 self.__highlighting = on
506 self.viewport().update()
507
508 def highlightColors(self):
509 """
510 Public method to get the highlight colors.
511
512 @return highlight foreground and background colors
513 @rtype tuple of 2 QColor
514 """
515 return self.__highlightingPen.color(), self.__highlightingBrush.color()
516
517 def setHighlightColors(self, foreground, background):
518 """
519 Public method to set the highlight colors.
520
521 @param foreground highlight foreground color
522 @type QColor
523 @param background highlight background color
524 @type QColor
525 """
526 self.__highlightingPen = QPen(foreground)
527 self.__highlightingBrush = QBrush(background)
528 self.viewport().update()
529
530 def overwriteMode(self):
531 """
532 Public method to get the overwrite mode.
533
534 @return overwrite mode
535 @rtype bool
536 """
537 return self.__overwriteMode
538
539 def setOverwriteMode(self, on):
540 """
541 Public method to set the overwrite mode.
542
543 @param on flag indicating the new overwrite mode
544 @type bool
545 """
546 self.__overwriteMode = on
547 self.overwriteModeChanged.emit(self.__overwriteMode)
548
549 # step 1: delete old cursor
550 self.__blink = False
551 self.viewport().update(self.__cursorRect)
552 # step 2: change the cursor rectangle
553 self.__setHexCursorRect()
554 # step 3: draw new cursors
555 self.__blink = True
556 self.viewport().update(self.__cursorRect)
557
558 def selectionColors(self):
559 """
560 Public method to get the selection colors.
561
562 @return selection foreground and background colors
563 @rtype tuple of 2 QColor
564 """
565 return self.__selectionPen.color(), self.__selectionBrush.color()
566
567 def setSelectionColors(self, foreground, background):
568 """
569 Public method to set the selection colors.
570
571 @param foreground selection foreground color
572 @type QColor
573 @param background selection background color
574 @type QColor
575 """
576 self.__selectionPen = QPen(foreground)
577 self.__selectionBrush = QBrush(background)
578 self.viewport().update()
579
580 def isReadOnly(self):
581 """
582 Public method to test the read only state.
583
584 @return flag indicating the read only state
585 @rtype bool
586 """
587 return self.__readOnly
588
589 def setReadOnly(self, on):
590 """
591 Public method to set the read only state.
592
593 @param on new read only state
594 @type bool
595 """
596 self.__readOnly = on
597 self.readOnlyChanged.emit(self.__readOnly)
598
599 def font(self):
600 """
601 Public method to get the font used to show the data.
602
603 @return font used to show the data
604 @rtype QFont
605 """
606 return super().font()
607
608 def setFont(self, font):
609 """
610 Public method to set the font used to show the data.
611
612 @param font font used to show the data
613 @type QFont
614 """
615 super().setFont(font)
616 try:
617 self.__pxCharWidth = self.fontMetrics().horizontalAdvance("2")
618 except AttributeError:
619 self.__pxCharWidth = self.fontMetrics().width("2")
620 self.__pxCharHeight = self.fontMetrics().height()
621 self.__pxGapAdr = self.__pxCharWidth // 2
622 self.__pxGapAdrHex = self.__pxCharWidth
623 self.__pxGapHexAscii = 2 * self.__pxCharWidth
624 self.__pxCursorWidth = self.__pxCharHeight // 7
625 self.__pxSelectionSub = self.fontMetrics().descent()
626 self.__adjust()
627 self.viewport().update()
628
629 def dataAt(self, pos, count=-1):
630 """
631 Public method to get data from a given position.
632
633 @param pos position to get data from
634 @type int
635 @param count amount of bytes to get
636 @type int
637 @return requested data
638 @rtype bytearray
639 """
640 return bytearray(self.__chunks.data(pos, count))
641
642 def write(self, device, pos=0, count=-1):
643 """
644 Public method to write data from a given position to a device.
645
646 @param device device to write to
647 @type QIODevice
648 @param pos position to start the write at
649 @type int
650 @param count amount of bytes to write
651 @type int
652 @return flag indicating success
653 @rtype bool
654 """
655 return self.__chunks.write(device, pos, count)
656
657 def insert(self, pos, ch):
658 """
659 Public method to insert a byte.
660
661 @param pos position to insert the byte at
662 @type int
663 @param ch byte to insert
664 @type int in the range 0x00 to 0xff
665 """
666 if ch in range(0, 256):
667 self.__undoStack.insert(pos, ch)
668 self.__refresh()
669
670 def remove(self, pos, length=1):
671 """
672 Public method to remove bytes.
673
674 @param pos position to remove bytes from
675 @type int
676 @param length amount of bytes to remove
677 @type int
678 """
679 self.__undoStack.removeAt(pos, length)
680 self.__refresh()
681
682 def replace(self, pos, ch):
683 """
684 Public method to replace a byte.
685
686 @param pos position to replace the byte at
687 @type int
688 @param ch byte to replace with
689 @type int in the range 0x00 to 0xff
690 """
691 if ch in range(0, 256):
692 self.__undoStack.overwrite(pos, ch)
693 self.__refresh()
694
695 def insertByteArray(self, pos, byteArray):
696 """
697 Public method to insert bytes.
698
699 @param pos position to insert the bytes at
700 @type int
701 @param byteArray bytes to be insert
702 @type bytearray or QByteArray
703 """
704 self.__undoStack.insertByteArray(pos, bytearray(byteArray))
705 self.__refresh()
706
707 def replaceByteArray(self, pos, length, byteArray):
708 """
709 Public method to replace bytes.
710
711 @param pos position to replace the bytes at
712 @type int
713 @param length amount of bytes to replace
714 @type int
715 @param byteArray bytes to replace with
716 @type bytearray or QByteArray
717 """
718 self.__undoStack.overwriteByteArray(pos, length, bytearray(byteArray))
719 self.__refresh()
720
721 def cursorPositionFromPoint(self, point):
722 """
723 Public method to calculate a cursor position from a graphics position.
724
725 @param point graphics position
726 @type QPoint
727 @return cursor position
728 @rtype int
729 """
730 result = -1
731 if (point.x() >= self.__pxPosHexX) and (
732 point.x() < (self.__pxPosHexX + (1 + self.HEXCHARS_PER_LINE) *
733 self.__pxCharWidth)):
734 x = (
735 (point.x() - self.__pxPosHexX - self.__pxCharWidth // 2) //
736 self.__pxCharWidth
737 )
738 x = (x // 3) * 2 + x % 3
739 y = (
740 ((point.y() - 3) // self.__pxCharHeight) * 2 *
741 self.BYTES_PER_LINE
742 )
743 result = self.__bPosFirst * 2 + x + y
744 return result
745
746 def ensureVisible(self):
747 """
748 Public method to ensure, that the cursor is visible.
749 """
750 if self.__cursorPosition < 2 * self.__bPosFirst:
751 self.verticalScrollBar().setValue(
752 self.__cursorPosition // 2 // self.BYTES_PER_LINE)
753 if self.__cursorPosition > (
754 (self.__bPosFirst + (self.__rowsShown - 1) *
755 self.BYTES_PER_LINE) * 2):
756 self.verticalScrollBar().setValue(
757 self.__cursorPosition // 2 // self.BYTES_PER_LINE -
758 self.__rowsShown + 1)
759 self.viewport().update()
760
761 def indexOf(self, byteArray, start):
762 """
763 Public method to find the first occurrence of a byte array in our data.
764
765 @param byteArray data to search for
766 @type bytearray or QByteArray
767 @param start start position of the search
768 @type int
769 @return position of match (or -1 if not found)
770 @rtype int
771 """
772 byteArray = bytearray(byteArray)
773 pos = self.__chunks.indexOf(byteArray, start)
774 if pos > -1:
775 curPos = pos * 2
776 self.setCursorPosition(curPos + len(byteArray) * 2)
777 self.__resetSelection(curPos)
778 self.__setSelection(curPos + len(byteArray) * 2)
779 self.ensureVisible()
780 return pos
781
782 def lastIndexOf(self, byteArray, start):
783 """
784 Public method to find the last occurrence of a byte array in our data.
785
786 @param byteArray data to search for
787 @type bytearray or QByteArray
788 @param start start position of the search
789 @type int
790 @return position of match (or -1 if not found)
791 @rtype int
792 """
793 byteArray = bytearray(byteArray)
794 pos = self.__chunks.lastIndexOf(byteArray, start)
795 if pos > -1:
796 curPos = pos * 2
797 self.setCursorPosition(curPos - 1)
798 self.__resetSelection(curPos)
799 self.__setSelection(curPos + len(byteArray) * 2)
800 self.ensureVisible()
801 return pos
802
803 def isModified(self):
804 """
805 Public method to check for any modification.
806
807 @return flag indicating a modified state
808 @rtype bool
809 """
810 return self.__modified
811
812 def setModified(self, modified, setCleanState=False):
813 """
814 Public slot to set the modified flag.
815
816 @param modified flag indicating the new modification status
817 @type bool
818 @param setCleanState flag indicating to set the undo stack to clean
819 @type bool
820 """
821 self.__modified = modified
822 self.dataChanged.emit(modified)
823
824 if not modified and setCleanState:
825 self.__undoStack.setClean()
826
827 def selectionToHexString(self):
828 """
829 Public method to get a hexadecimal representation of the selection.
830
831 @return hexadecimal representation of the selection
832 @rtype str
833 """
834 byteArray = self.__chunks.data(self.getSelectionBegin(),
835 self.getSelectionLength())
836 return self.__toHex(byteArray).decode(encoding="ascii")
837
838 def selectionToReadableString(self):
839 """
840 Public method to get a formatted representation of the selection.
841
842 @return formatted representation of the selection
843 @rtype str
844 """
845 byteArray = self.__chunks.data(self.getSelectionBegin(),
846 self.getSelectionLength())
847 return self.__toReadable(byteArray)
848
849 def toReadableString(self):
850 """
851 Public method to get a formatted representation of our data.
852
853 @return formatted representation of our data
854 @rtype str
855 """
856 byteArray = self.__chunks.data()
857 return self.__toReadable(byteArray)
858
859 @pyqtSlot()
860 def redo(self):
861 """
862 Public slot to redo the last operation.
863 """
864 self.__undoStack.redo()
865 self.setCursorPosition(self.__chunks.pos() * 2)
866 self.__refresh()
867
868 @pyqtSlot()
869 def undo(self):
870 """
871 Public slot to undo the last operation.
872 """
873 self.__undoStack.undo()
874 self.setCursorPosition(self.__chunks.pos() * 2)
875 self.__refresh()
876
877 @pyqtSlot()
878 def revertToUnmodified(self):
879 """
880 Public slot to revert all changes.
881 """
882 cleanIndex = self.__undoStack.cleanIndex()
883 if cleanIndex >= 0:
884 self.__undoStack.setIndex(cleanIndex)
885 self.setCursorPosition(self.__chunks.pos() * 2)
886 self.__refresh()
887
888 ####################################################
889 ## Cursor movement commands
890 ####################################################
891
892 def moveCursorToNextChar(self):
893 """
894 Public method to move the cursor to the next byte.
895 """
896 self.setCursorPosition(self.__cursorPosition + 1)
897 self.__resetSelection(self.__cursorPosition)
898
899 def moveCursorToPreviousChar(self):
900 """
901 Public method to move the cursor to the previous byte.
902 """
903 self.setCursorPosition(self.__cursorPosition - 1)
904 self.__resetSelection(self.__cursorPosition)
905
906 def moveCursorToEndOfLine(self):
907 """
908 Public method to move the cursor to the end of the current line.
909 """
910 self.setCursorPosition(self.__cursorPosition |
911 (2 * self.BYTES_PER_LINE - 1))
912 self.__resetSelection(self.__cursorPosition)
913
914 def moveCursorToStartOfLine(self):
915 """
916 Public method to move the cursor to the beginning of the current line.
917 """
918 self.setCursorPosition(
919 self.__cursorPosition -
920 (self.__cursorPosition % (2 * self.BYTES_PER_LINE)))
921 self.__resetSelection(self.__cursorPosition)
922
923 def moveCursorToPreviousLine(self):
924 """
925 Public method to move the cursor to the previous line.
926 """
927 self.setCursorPosition(self.__cursorPosition - 2 * self.BYTES_PER_LINE)
928 self.__resetSelection(self.__cursorPosition)
929
930 def moveCursorToNextLine(self):
931 """
932 Public method to move the cursor to the next line.
933 """
934 self.setCursorPosition(self.__cursorPosition + 2 * self.BYTES_PER_LINE)
935 self.__resetSelection(self.__cursorPosition)
936
937 def moveCursorToNextPage(self):
938 """
939 Public method to move the cursor to the next page.
940 """
941 self.setCursorPosition(
942 self.__cursorPosition +
943 (self.__rowsShown - 1) * 2 * self.BYTES_PER_LINE)
944 self.__resetSelection(self.__cursorPosition)
945
946 def moveCursorToPreviousPage(self):
947 """
948 Public method to move the cursor to the previous page.
949 """
950 self.setCursorPosition(
951 self.__cursorPosition -
952 (self.__rowsShown - 1) * 2 * self.BYTES_PER_LINE)
953 self.__resetSelection(self.__cursorPosition)
954
955 def moveCursorToEndOfDocument(self):
956 """
957 Public method to move the cursor to the end of the data.
958 """
959 self.setCursorPosition(self.__chunks.size() * 2)
960 self.__resetSelection(self.__cursorPosition)
961
962 def moveCursorToStartOfDocument(self):
963 """
964 Public method to move the cursor to the start of the data.
965 """
966 self.setCursorPosition(0)
967 self.__resetSelection(self.__cursorPosition)
968
969 ####################################################
970 ## Selection commands
971 ####################################################
972
973 def deselectAll(self):
974 """
975 Public method to deselect all data.
976 """
977 self.__resetSelection(0)
978 self.__refresh()
979
980 def selectAll(self):
981 """
982 Public method to select all data.
983 """
984 self.__resetSelection(0)
985 self.__setSelection(2 * self.__chunks.size() + 1)
986 self.__refresh()
987
988 def selectNextChar(self):
989 """
990 Public method to extend the selection by one byte right.
991 """
992 pos = self.__cursorPosition + 1
993 self.setCursorPosition(pos)
994 self.__setSelection(pos)
995
996 def selectPreviousChar(self):
997 """
998 Public method to extend the selection by one byte left.
999 """
1000 pos = self.__cursorPosition - 1
1001 self.setCursorPosition(pos)
1002 self.__setSelection(pos)
1003
1004 def selectToEndOfLine(self):
1005 """
1006 Public method to extend the selection to the end of line.
1007 """
1008 pos = (
1009 self.__cursorPosition -
1010 (self.__cursorPosition % (2 * self.BYTES_PER_LINE)) +
1011 2 * self.BYTES_PER_LINE
1012 )
1013 self.setCursorPosition(pos)
1014 self.__setSelection(pos)
1015
1016 def selectToStartOfLine(self):
1017 """
1018 Public method to extend the selection to the start of line.
1019 """
1020 pos = (
1021 self.__cursorPosition -
1022 (self.__cursorPosition % (2 * self.BYTES_PER_LINE))
1023 )
1024 self.setCursorPosition(pos)
1025 self.__setSelection(pos)
1026
1027 def selectPreviousLine(self):
1028 """
1029 Public method to extend the selection one line up.
1030 """
1031 pos = self.__cursorPosition - 2 * self.BYTES_PER_LINE
1032 self.setCursorPosition(pos)
1033 self.__setSelection(pos)
1034
1035 def selectNextLine(self):
1036 """
1037 Public method to extend the selection one line down.
1038 """
1039 pos = self.__cursorPosition + 2 * self.BYTES_PER_LINE
1040 self.setCursorPosition(pos)
1041 self.__setSelection(pos)
1042
1043 def selectNextPage(self):
1044 """
1045 Public method to extend the selection one page down.
1046 """
1047 pos = (
1048 self.__cursorPosition +
1049 ((self.viewport().height() // self.__pxCharHeight) - 1) *
1050 2 * self.BYTES_PER_LINE
1051 )
1052 self.setCursorPosition(pos)
1053 self.__setSelection(pos)
1054
1055 def selectPreviousPage(self):
1056 """
1057 Public method to extend the selection one page up.
1058 """
1059 pos = (
1060 self.__cursorPosition -
1061 ((self.viewport().height() // self.__pxCharHeight) - 1) *
1062 2 * self.BYTES_PER_LINE
1063 )
1064 self.setCursorPosition(pos)
1065 self.__setSelection(pos)
1066
1067 def selectEndOfDocument(self):
1068 """
1069 Public method to extend the selection to the end of the data.
1070 """
1071 pos = self.__chunks.size() * 2
1072 self.setCursorPosition(pos)
1073 self.__setSelection(pos)
1074
1075 def selectStartOfDocument(self):
1076 """
1077 Public method to extend the selection to the start of the data.
1078 """
1079 pos = 0
1080 self.setCursorPosition(pos)
1081 self.__setSelection(pos)
1082
1083 ####################################################
1084 ## Edit commands
1085 ####################################################
1086
1087 def cut(self):
1088 """
1089 Public method to cut the selected bytes and move them to the clipboard.
1090 """
1091 if not self.__readOnly:
1092 byteArray = self.__toHex(self.__chunks.data(
1093 self.getSelectionBegin(), self.getSelectionLength()))
1094 idx = 32
1095 while idx < len(byteArray):
1096 byteArray.insert(idx, "\n")
1097 idx += 33
1098 cb = QApplication.clipboard()
1099 cb.setText(byteArray.decode(encoding="latin1"))
1100 if self.__overwriteMode:
1101 length = self.getSelectionLength()
1102 self.replaceByteArray(self.getSelectionBegin(), length,
1103 bytearray(length))
1104 else:
1105 self.remove(self.getSelectionBegin(),
1106 self.getSelectionLength())
1107 self.setCursorPosition(2 * self.getSelectionBegin())
1108 self.__resetSelection(2 * self.getSelectionBegin())
1109
1110 def copy(self):
1111 """
1112 Public method to copy the selected bytes to the clipboard.
1113 """
1114 byteArray = self.__toHex(self.__chunks.data(
1115 self.getSelectionBegin(), self.getSelectionLength()))
1116 idx = 32
1117 while idx < len(byteArray):
1118 byteArray.insert(idx, "\n")
1119 idx += 33
1120 cb = QApplication.clipboard()
1121 cb.setText(byteArray.decode(encoding="latin1"))
1122
1123 def paste(self):
1124 """
1125 Public method to paste bytes from the clipboard.
1126 """
1127 if not self.__readOnly:
1128 cb = QApplication.clipboard()
1129 byteArray = self.__fromHex(cb.text().encode(encoding="latin1"))
1130 if self.__overwriteMode:
1131 self.replaceByteArray(self.__bPosCurrent, len(byteArray),
1132 byteArray)
1133 else:
1134 self.insertByteArray(self.__bPosCurrent, byteArray)
1135 self.setCursorPosition(
1136 self.__cursorPosition + 2 * len(byteArray))
1137 self.__resetSelection(2 * self.getSelectionBegin())
1138
1139 def deleteByte(self):
1140 """
1141 Public method to delete the current byte.
1142 """
1143 if not self.__readOnly:
1144 if self.hasSelection():
1145 self.__bPosCurrent = self.getSelectionBegin()
1146 if self.__overwriteMode:
1147 byteArray = bytearray(self.getSelectionLength())
1148 self.replaceByteArray(self.__bPosCurrent, len(byteArray),
1149 byteArray)
1150 else:
1151 self.remove(self.__bPosCurrent,
1152 self.getSelectionLength())
1153 else:
1154 if self.__overwriteMode:
1155 self.replace(self.__bPosCurrent, 0)
1156 else:
1157 self.remove(self.__bPosCurrent, 1)
1158 self.setCursorPosition(2 * self.__bPosCurrent)
1159 self.__resetSelection(2 * self.__bPosCurrent)
1160
1161 def deleteByteBack(self):
1162 """
1163 Public method to delete the previous byte.
1164 """
1165 if not self.__readOnly:
1166 if self.hasSelection():
1167 self.__bPosCurrent = self.getSelectionBegin()
1168 self.setCursorPosition(2 * self.__bPosCurrent)
1169 if self.__overwriteMode:
1170 byteArray = bytearray(self.getSelectionLength())
1171 self.replaceByteArray(self.__bPosCurrent, len(byteArray),
1172 byteArray)
1173 else:
1174 self.remove(self.__bPosCurrent,
1175 self.getSelectionLength())
1176 else:
1177 self.__bPosCurrent -= 1
1178 if self.__overwriteMode:
1179 self.replace(self.__bPosCurrent, 0)
1180 else:
1181 self.remove(self.__bPosCurrent, 1)
1182 self.setCursorPosition(2 * self.__bPosCurrent)
1183 self.__resetSelection(2 * self.__bPosCurrent)
1184
1185 ####################################################
1186 ## Event handling methods
1187 ####################################################
1188
1189 def keyPressEvent(self, evt):
1190 """
1191 Protected method to handle key press events.
1192
1193 @param evt reference to the key event
1194 @type QKeyEvent
1195 """
1196 # Cursor movements
1197 if evt.matches(QKeySequence.StandardKey.MoveToNextChar):
1198 self.moveCursorToNextChar()
1199 elif evt.matches(QKeySequence.StandardKey.MoveToPreviousChar):
1200 self.moveCursorToPreviousChar()
1201 elif evt.matches(QKeySequence.StandardKey.MoveToEndOfLine):
1202 self.moveCursorToEndOfLine()
1203 elif evt.matches(QKeySequence.StandardKey.MoveToStartOfLine):
1204 self.moveCursorToStartOfLine()
1205 elif evt.matches(QKeySequence.StandardKey.MoveToPreviousLine):
1206 self.moveCursorToPreviousLine()
1207 elif evt.matches(QKeySequence.StandardKey.MoveToNextLine):
1208 self.moveCursorToNextLine()
1209 elif evt.matches(QKeySequence.StandardKey.MoveToNextPage):
1210 self.moveCursorToNextPage()
1211 elif evt.matches(QKeySequence.StandardKey.MoveToPreviousPage):
1212 self.moveCursorToPreviousPage()
1213 elif evt.matches(QKeySequence.StandardKey.MoveToEndOfDocument):
1214 self.moveCursorToEndOfDocument()
1215 elif evt.matches(QKeySequence.StandardKey.MoveToStartOfDocument):
1216 self.moveCursorToStartOfDocument()
1217
1218 # Selection commands
1219 elif evt.matches(QKeySequence.StandardKey.SelectAll):
1220 self.selectAll()
1221 elif evt.matches(QKeySequence.StandardKey.SelectNextChar):
1222 self.selectNextChar()
1223 elif evt.matches(QKeySequence.StandardKey.SelectPreviousChar):
1224 self.selectPreviousChar()
1225 elif evt.matches(QKeySequence.StandardKey.SelectEndOfLine):
1226 self.selectToEndOfLine()
1227 elif evt.matches(QKeySequence.StandardKey.SelectStartOfLine):
1228 self.selectToStartOfLine()
1229 elif evt.matches(QKeySequence.StandardKey.SelectPreviousLine):
1230 self.selectPreviousLine()
1231 elif evt.matches(QKeySequence.StandardKey.SelectNextLine):
1232 self.selectNextLine()
1233 elif evt.matches(QKeySequence.StandardKey.SelectNextPage):
1234 self.selectNextPage()
1235 elif evt.matches(QKeySequence.StandardKey.SelectPreviousPage):
1236 self.selectPreviousPage()
1237 elif evt.matches(QKeySequence.StandardKey.SelectEndOfDocument):
1238 self.selectEndOfDocument()
1239 elif evt.matches(QKeySequence.StandardKey.SelectStartOfDocument):
1240 self.selectStartOfDocument()
1241
1242 # Edit commands
1243 elif evt.matches(QKeySequence.StandardKey.Copy):
1244 self.copy()
1245 elif (
1246 evt.key() == Qt.Key.Key_Insert and
1247 evt.modifiers() == Qt.KeyboardModifier.NoModifier
1248 ):
1249 self.setOverwriteMode(not self.overwriteMode())
1250 self.setCursorPosition(self.__cursorPosition)
1251
1252 elif not self.__readOnly:
1253 if evt.matches(QKeySequence.StandardKey.Cut):
1254 self.cut()
1255 elif evt.matches(QKeySequence.StandardKey.Paste):
1256 self.paste()
1257 elif evt.matches(QKeySequence.StandardKey.Delete):
1258 self.deleteByte()
1259 elif (
1260 evt.key() == Qt.Key.Key_Backspace and
1261 evt.modifiers() == Qt.KeyboardModifier.NoModifier
1262 ):
1263 self.deleteByteBack()
1264 elif evt.matches(QKeySequence.StandardKey.Undo):
1265 self.undo()
1266 elif evt.matches(QKeySequence.StandardKey.Redo):
1267 self.redo()
1268
1269 elif QApplication.keyboardModifiers() in [
1270 Qt.KeyboardModifier.NoModifier,
1271 Qt.KeyboardModifier.KeypadModifier
1272 ]:
1273 # some hex input
1274 key = evt.text()
1275 if key and key in "0123456789abcdef":
1276 if self.hasSelection():
1277 if self.__overwriteMode:
1278 length = self.getSelectionLength()
1279 self.replaceByteArray(
1280 self.getSelectionBegin(), length,
1281 bytearray(length))
1282 else:
1283 self.remove(self.getSelectionBegin(),
1284 self.getSelectionLength())
1285 self.__bPosCurrent = self.getSelectionBegin()
1286 self.setCursorPosition(2 * self.__bPosCurrent)
1287 self.__resetSelection(2 * self.__bPosCurrent)
1288
1289 # if in insert mode, insert a byte
1290 if (
1291 not self.__overwriteMode and
1292 (self.__cursorPosition % 2) == 0
1293 ):
1294 self.insert(self.__bPosCurrent, 0)
1295
1296 # change content
1297 if self.__chunks.size() > 0:
1298 hexValue = self.__toHex(
1299 self.__chunks.data(self.__bPosCurrent, 1))
1300 if (self.__cursorPosition % 2) == 0:
1301 hexValue[0] = ord(key)
1302 else:
1303 hexValue[1] = ord(key)
1304 self.replace(self.__bPosCurrent,
1305 self.__fromHex(hexValue)[0])
1306
1307 self.setCursorPosition(self.__cursorPosition + 1)
1308 self.__resetSelection(self.__cursorPosition)
1309 else:
1310 return
1311 else:
1312 return
1313 else:
1314 return
1315 else:
1316 return
1317
1318 self.__refresh()
1319
1320 def mouseMoveEvent(self, evt):
1321 """
1322 Protected method to handle mouse moves.
1323
1324 @param evt reference to the mouse event
1325 @type QMouseEvent
1326 """
1327 self.__blink = False
1328 self.viewport().update()
1329 actPos = self.cursorPositionFromPoint(evt.pos())
1330 if actPos >= 0:
1331 self.setCursorPosition(actPos)
1332 self.__setSelection(actPos)
1333
1334 def mousePressEvent(self, evt):
1335 """
1336 Protected method to handle mouse button presses.
1337
1338 @param evt reference to the mouse event
1339 @type QMouseEvent
1340 """
1341 self.__blink = False
1342 self.viewport().update()
1343 cPos = self.cursorPositionFromPoint(evt.pos())
1344 if cPos >= 0:
1345 if evt.modifiers() == Qt.KeyboardModifier.ShiftModifier:
1346 self.__setSelection(cPos)
1347 else:
1348 self.__resetSelection(cPos)
1349 self.setCursorPosition(cPos)
1350
1351 def paintEvent(self, evt):
1352 """
1353 Protected method to handle paint events.
1354
1355 @param evt reference to the paint event
1356 @type QPaintEvent
1357 """
1358 painter = QPainter(self.viewport())
1359
1360 if (
1361 evt.rect() != self.__cursorRect and
1362 evt.rect() != self.__cursorRectAscii
1363 ):
1364 pxOfsX = self.horizontalScrollBar().value()
1365 pxPosStartY = self.__pxCharHeight
1366
1367 # draw some patterns if needed
1368 painter.fillRect(
1369 evt.rect(),
1370 self.viewport().palette().color(QPalette.ColorRole.Base))
1371 if self.__addressArea:
1372 painter.fillRect(
1373 QRect(-pxOfsX, evt.rect().top(),
1374 self.__pxPosHexX - self.__pxGapAdrHex // 2 - pxOfsX,
1375 self.height()),
1376 self.__addressAreaBrush)
1377 if self.__asciiArea:
1378 linePos = self.__pxPosAsciiX - (self.__pxGapHexAscii // 2)
1379 painter.setPen(Qt.GlobalColor.gray)
1380 painter.drawLine(linePos - pxOfsX, evt.rect().top(),
1381 linePos - pxOfsX, self.height())
1382
1383 painter.setPen(
1384 self.viewport().palette().color(QPalette.ColorRole.WindowText))
1385
1386 # paint the address area
1387 if self.__addressArea:
1388 painter.setPen(self.__addressAreaPen)
1389 address = ""
1390 row = 0
1391 pxPosY = self.__pxCharHeight
1392 while row <= len(self.__dataShown) // self.BYTES_PER_LINE:
1393 address = "{0:0{1}x}".format(
1394 self.__bPosFirst + row * self.BYTES_PER_LINE,
1395 self.__addrDigits)
1396 address = Globals.strGroup(address, ":", 4)
1397 painter.drawText(self.__pxPosAdrX - pxOfsX, pxPosY,
1398 address)
1399 # increment loop variables
1400 row += 1
1401 pxPosY += self.__pxCharHeight
1402
1403 # paint hex and ascii area
1404 colStandard = QPen(
1405 self.viewport().palette().color(QPalette.ColorRole.WindowText))
1406
1407 painter.setBackgroundMode(Qt.BGMode.TransparentMode)
1408
1409 row = 0
1410 pxPosY = pxPosStartY
1411 while row <= self.__rowsShown:
1412 pxPosX = self.__pxPosHexX - pxOfsX
1413 pxPosAsciiX2 = self.__pxPosAsciiX - pxOfsX
1414 bPosLine = row * self.BYTES_PER_LINE
1415
1416 colIdx = 0
1417 while (
1418 bPosLine + colIdx < len(self.__dataShown) and
1419 colIdx < self.BYTES_PER_LINE
1420 ):
1421 c = self.viewport().palette().color(
1422 QPalette.ColorRole.Base)
1423 painter.setPen(colStandard)
1424
1425 posBa = self.__bPosFirst + bPosLine + colIdx
1426 if (
1427 self.getSelectionBegin() <= posBa and
1428 self.getSelectionEnd() > posBa
1429 ):
1430 c = self.__selectionBrush.color()
1431 painter.setPen(self.__selectionPen)
1432 elif (
1433 self.__highlighting and
1434 self.__markedShown and
1435 self.__markedShown[posBa - self.__bPosFirst]
1436 ):
1437 c = self.__highlightingBrush.color()
1438 painter.setPen(self.__highlightingPen)
1439
1440 # render hex value
1441 r = QRect()
1442 if colIdx == 0:
1443 r.setRect(
1444 pxPosX,
1445 pxPosY - self.__pxCharHeight +
1446 self.__pxSelectionSub,
1447 2 * self.__pxCharWidth,
1448 self.__pxCharHeight)
1449 else:
1450 r.setRect(
1451 pxPosX - self.__pxCharWidth,
1452 pxPosY - self.__pxCharHeight +
1453 self.__pxSelectionSub,
1454 3 * self.__pxCharWidth,
1455 self.__pxCharHeight)
1456 painter.fillRect(r, c)
1457 hexStr = (
1458 chr(self.__hexDataShown[(bPosLine + colIdx) * 2]) +
1459 chr(self.__hexDataShown[(bPosLine + colIdx) * 2 + 1])
1460 )
1461 painter.drawText(pxPosX, pxPosY, hexStr)
1462 pxPosX += 3 * self.__pxCharWidth
1463
1464 # render ascii value
1465 if self.__asciiArea:
1466 by = self.__dataShown[bPosLine + colIdx]
1467 if by < 0x20 or (by > 0x7e and by < 0xa0):
1468 ch = "."
1469 else:
1470 ch = chr(by)
1471 r.setRect(
1472 pxPosAsciiX2,
1473 pxPosY - self.__pxCharHeight +
1474 self.__pxSelectionSub,
1475 self.__pxCharWidth,
1476 self.__pxCharHeight)
1477 painter.fillRect(r, c)
1478 painter.drawText(pxPosAsciiX2, pxPosY, ch)
1479 pxPosAsciiX2 += self.__pxCharWidth
1480
1481 # increment loop variable
1482 colIdx += 1
1483
1484 # increment loop variables
1485 row += 1
1486 pxPosY += self.__pxCharHeight
1487
1488 painter.setBackgroundMode(Qt.BGMode.TransparentMode)
1489 painter.setPen(
1490 self.viewport().palette().color(QPalette.ColorRole.WindowText))
1491
1492 # paint cursor
1493 if self.__blink and not self.__readOnly and self.isActiveWindow():
1494 painter.fillRect(
1495 self.__cursorRect,
1496 self.palette().color(QPalette.ColorRole.WindowText))
1497 else:
1498 if self.__hexDataShown:
1499 try:
1500 c = chr(self.__hexDataShown[
1501 self.__cursorPosition - self.__bPosFirst * 2])
1502 except IndexError:
1503 c = ""
1504 else:
1505 c = ""
1506 painter.drawText(self.__pxCursorX, self.__pxCursorY, c)
1507
1508 if self.__asciiArea:
1509 painter.drawRect(self.__cursorRectAscii)
1510
1511 # emit event, if size has changed
1512 if self.__lastEventSize != self.__chunks.size():
1513 self.__lastEventSize = self.__chunks.size()
1514 self.currentSizeChanged.emit(self.__lastEventSize)
1515
1516 def resizeEvent(self, evt):
1517 """
1518 Protected method to handle resize events.
1519
1520 @param evt reference to the resize event
1521 @type QResizeEvent
1522 """
1523 self.__adjust()
1524
1525 def __resetSelection(self, pos=None):
1526 """
1527 Private method to reset the selection.
1528
1529 @param pos position to set selection start and end to
1530 (if this is None, selection end is set to selection start)
1531 @type int or None
1532 """
1533 if pos is None:
1534 self.__bSelectionBegin = self.__bSelectionInit
1535 self.__bSelectionEnd = self.__bSelectionInit
1536 else:
1537 if pos < 0:
1538 pos = 0
1539 pos //= 2
1540 self.__bSelectionInit = pos
1541 self.__bSelectionBegin = pos
1542 self.__bSelectionEnd = pos
1543
1544 self.selectionAvailable.emit(False)
1545
1546 def __setSelection(self, pos):
1547 """
1548 Private method to set the selection.
1549
1550 @param pos position
1551 @type int
1552 """
1553 if pos < 0:
1554 pos = 0
1555 pos //= 2
1556 if pos >= self.__bSelectionInit:
1557 self.__bSelectionEnd = pos
1558 self.__bSelectionBegin = self.__bSelectionInit
1559 else:
1560 self.__bSelectionBegin = pos
1561 self.__bSelectionEnd = self.__bSelectionInit
1562
1563 self.selectionAvailable.emit(True)
1564
1565 def getSelectionBegin(self):
1566 """
1567 Public method to get the start of the selection.
1568
1569 @return selection start
1570 @rtype int
1571 """
1572 return self.__bSelectionBegin
1573
1574 def getSelectionEnd(self):
1575 """
1576 Public method to get the end of the selection.
1577
1578 @return selection end
1579 @rtype int
1580 """
1581 return self.__bSelectionEnd
1582
1583 def getSelectionLength(self):
1584 """
1585 Public method to get the length of the selection.
1586
1587 @return selection length
1588 @rtype int
1589 """
1590 return self.__bSelectionEnd - self.__bSelectionBegin
1591
1592 def hasSelection(self):
1593 """
1594 Public method to test for a selection.
1595
1596 @return flag indicating the presence of a selection
1597 @rtype bool
1598 """
1599 return self.__bSelectionBegin != self.__bSelectionEnd
1600
1601 def __initialize(self):
1602 """
1603 Private method to do some initialization.
1604 """
1605 self.__undoStack.clear()
1606 self.setAddressOffset(0)
1607 self.__resetSelection(0)
1608 self.setCursorPosition(0)
1609 self.verticalScrollBar().setValue(0)
1610 self.__modified = False
1611
1612 def __readBuffers(self):
1613 """
1614 Private method to read the buffers.
1615 """
1616 self.__dataShown = self.__chunks.data(
1617 self.__bPosFirst,
1618 self.__bPosLast - self.__bPosFirst + self.BYTES_PER_LINE + 1,
1619 self.__markedShown
1620 )
1621 self.__hexDataShown = self.__toHex(self.__dataShown)
1622
1623 def __toHex(self, byteArray):
1624 """
1625 Private method to convert the data of a Python bytearray to hex.
1626
1627 @param byteArray byte array to be converted
1628 @type bytearray
1629 @return converted data
1630 @rtype bytearray
1631 """
1632 return bytearray(QByteArray(byteArray).toHex())
1633
1634 def __fromHex(self, byteArray):
1635 """
1636 Private method to convert data of a Python bytearray from hex.
1637
1638 @param byteArray byte array to be converted
1639 @type bytearray
1640 @return converted data
1641 @rtype bytearray
1642 """
1643 return bytearray(QByteArray.fromHex(byteArray))
1644
1645 def __toReadable(self, byteArray):
1646 """
1647 Private method to convert some data into a readable format.
1648
1649 @param byteArray data to be converted
1650 @type bytearray or QByteArray
1651 @return readable data
1652 @rtype str
1653 """
1654 byteArray = bytearray(byteArray)
1655 result = ""
1656 for i in range(0, len(byteArray), 16):
1657 addrStr = "{0:0{1}x}".format(self.__addressOffset + i,
1658 self.addressWidth())
1659 hexStr = ""
1660 ascStr = ""
1661 for j in range(16):
1662 if (i + j) < len(byteArray):
1663 hexStr += " {0:02x}".format(byteArray[i + j])
1664 by = byteArray[i + j]
1665 if by < 0x20 or (by > 0x7e and by < 0xa0):
1666 ch = "."
1667 else:
1668 ch = chr(by)
1669 ascStr += ch
1670 result += "{0} {1:<48} {2:<17}\n".format(addrStr, hexStr, ascStr)
1671 return result
1672
1673 @pyqtSlot()
1674 def __adjust(self):
1675 """
1676 Private slot to recalculate pixel positions.
1677 """
1678 # recalculate graphics
1679 if self.__addressArea:
1680 self.__addrDigits = self.addressWidth()
1681 self.__addrSeparators = self.__addrDigits // 4 - 1
1682 self.__pxPosHexX = (
1683 self.__pxGapAdr +
1684 (self.__addrDigits + self.__addrSeparators) *
1685 self.__pxCharWidth + self.__pxGapAdrHex)
1686 else:
1687 self.__pxPosHexX = self.__pxGapAdrHex
1688 self.__pxPosAdrX = self.__pxGapAdr
1689 self.__pxPosAsciiX = (
1690 self.__pxPosHexX +
1691 self.HEXCHARS_PER_LINE * self.__pxCharWidth +
1692 self.__pxGapHexAscii
1693 )
1694
1695 # set horizontal scrollbar
1696 pxWidth = self.__pxPosAsciiX
1697 if self.__asciiArea:
1698 pxWidth += self.BYTES_PER_LINE * self.__pxCharWidth
1699 self.horizontalScrollBar().setRange(
1700 0, pxWidth - self.viewport().width())
1701 self.horizontalScrollBar().setPageStep(self.viewport().width())
1702
1703 # set vertical scrollbar
1704 self.__rowsShown = (
1705 (self.viewport().height() - 4) // self.__pxCharHeight
1706 )
1707 lineCount = (self.__chunks.size() // self.BYTES_PER_LINE) + 1
1708 self.verticalScrollBar().setRange(0, lineCount - self.__rowsShown)
1709 self.verticalScrollBar().setPageStep(self.__rowsShown)
1710
1711 # do the rest
1712 value = self.verticalScrollBar().value()
1713 self.__bPosFirst = value * self.BYTES_PER_LINE
1714 self.__bPosLast = (
1715 self.__bPosFirst + self.__rowsShown * self.BYTES_PER_LINE - 1
1716 )
1717 if self.__bPosLast >= self.__chunks.size():
1718 self.__bPosLast = self.__chunks.size() - 1
1719 self.__readBuffers()
1720 self.setCursorPosition(self.__cursorPosition)
1721
1722 @pyqtSlot(int)
1723 def __dataChangedPrivate(self, idx=0):
1724 """
1725 Private slot to handle data changes.
1726
1727 @param idx index
1728 @type int
1729 """
1730 self.__modified = (
1731 self.__undoStack.cleanIndex() == -1 or
1732 self.__undoStack.index() != self.__undoStack.cleanIndex())
1733 self.__adjust()
1734 self.dataChanged.emit(self.__modified)
1735
1736 @pyqtSlot()
1737 def __refresh(self):
1738 """
1739 Private slot to refresh the display.
1740 """
1741 self.ensureVisible()
1742 self.__readBuffers()
1743
1744 @pyqtSlot()
1745 def __updateCursor(self):
1746 """
1747 Private slot to update the blinking cursor.
1748 """
1749 self.__blink = not self.__blink
1750 self.viewport().update(self.__cursorRect)

eric ide

mercurial