src/eric7/HexEdit/HexEditWidget.py

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

eric ide

mercurial