|
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) |