eric6/HexEdit/HexEditWidget.py

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

eric ide

mercurial