Mercurial > lcfOS
comparison python/ide/hexedit.py @ 290:7b38782ed496
File moves
author | Windel Bouwman |
---|---|
date | Sun, 24 Nov 2013 11:24:15 +0100 |
parents | python/hexedit.py@1c7c1e619be8 |
children | dcae6574c974 |
comparison
equal
deleted
inserted
replaced
289:bd2593de3ff8 | 290:7b38782ed496 |
---|---|
1 #!/usr/bin/python | |
2 | |
3 import sys | |
4 import os | |
5 from PyQt4.QtCore import * | |
6 from PyQt4.QtGui import * | |
7 from PyQt4 import uic | |
8 | |
9 BYTES_PER_LINE, GAP = 8, 12 | |
10 | |
11 def clamp(minimum, x, maximum): | |
12 return max(minimum, min(x, maximum)) | |
13 | |
14 def asciiChar(v): | |
15 if v < 0x20 or v > 0x7e: | |
16 return '.' | |
17 else: | |
18 return chr(v) | |
19 | |
20 class BinViewer(QWidget): | |
21 """ The view has an address, hex byte and ascii column """ | |
22 def __init__(self, scrollArea): | |
23 super().__init__(scrollArea) | |
24 self.scrollArea = scrollArea | |
25 self.setFont(QFont('Courier', 16)) | |
26 self.setFocusPolicy(Qt.StrongFocus) | |
27 self.blinkcursor = False | |
28 self.cursorX = self.cursorY = 0 | |
29 self.scrollArea = scrollArea | |
30 self.Data = bytearray() | |
31 self.Offset = 0 | |
32 t = QTimer(self) | |
33 t.timeout.connect(self.updateCursor) | |
34 t.setInterval(500) | |
35 t.start() | |
36 def updateCursor(self): | |
37 self.blinkcursor = not self.blinkcursor | |
38 self.update(self.cursorX, self.cursorY, self.charWidth, self.charHeight) | |
39 def setCursorPosition(self, position): | |
40 position = clamp(0, int(position), len(self.Data) * 2 - 1) | |
41 self.cursorPosition = position | |
42 x = position % (2 * BYTES_PER_LINE) | |
43 x = x + int(x / 2) # Create a gap between hex values | |
44 self.cursorX = self.xposHex + x * self.charWidth | |
45 y = int(position / (2 * BYTES_PER_LINE)) | |
46 self.cursorY = y * self.charHeight + 2 | |
47 self.blinkcursor = True | |
48 self.update() | |
49 def getCursorPosition(self): | |
50 return self.cursorPosition | |
51 CursorPosition = property(getCursorPosition, setCursorPosition) | |
52 def setOffset(self, off): | |
53 self.offset = off | |
54 self.update() | |
55 Offset = property(lambda self: self.offset, setOffset) | |
56 def paintEvent(self, event): | |
57 # Helper variables: | |
58 er = event.rect() | |
59 chw, chh = self.charWidth, self.charHeight | |
60 painter = QPainter(self) | |
61 # Background: | |
62 painter.fillRect(er, self.palette().color(QPalette.Base)) | |
63 painter.fillRect(QRect(self.xposAddr, er.top(), 8 * chw, er.bottom() + 1), Qt.gray) | |
64 painter.setPen(Qt.gray) | |
65 x = self.xposAscii - (GAP / 2) | |
66 painter.drawLine(x, er.top(), x, er.bottom()) | |
67 x = self.xposEnd - (GAP / 2) | |
68 painter.drawLine(x, er.top(), x, er.bottom()) | |
69 # first and last index | |
70 firstIndex = max((int(er.top() / chh) - chh) * BYTES_PER_LINE, 0) | |
71 lastIndex = max((int(er.bottom() / chh) + chh) * BYTES_PER_LINE, 0) | |
72 yposStart = int(firstIndex / BYTES_PER_LINE) * chh + chh | |
73 # Draw contents: | |
74 painter.setPen(Qt.black) | |
75 ypos = yposStart | |
76 for index in range(firstIndex, lastIndex, BYTES_PER_LINE): | |
77 painter.setPen(Qt.black) | |
78 painter.drawText(self.xposAddr, ypos, '{0:08X}'.format(index + self.Offset)) | |
79 xpos = self.xposHex | |
80 xposAscii = self.xposAscii | |
81 for colIndex in range(BYTES_PER_LINE): | |
82 if index + colIndex < len(self.Data): | |
83 b = self.Data[index + colIndex] | |
84 bo = self.originalData[index + colIndex] | |
85 if b == bo: | |
86 painter.setPen(Qt.black) | |
87 else: | |
88 painter.setPen(Qt.red) | |
89 painter.drawText(xpos, ypos, '{0:02X}'.format(b)) | |
90 painter.drawText(xposAscii, ypos, asciiChar(b)) | |
91 xpos += 3 * chw | |
92 xposAscii += chw | |
93 ypos += chh | |
94 # cursor | |
95 if self.blinkcursor: | |
96 painter.fillRect(self.cursorX, self.cursorY + chh - 2, chw, 2, Qt.black) | |
97 def keyPressEvent(self, event): | |
98 if event.matches(QKeySequence.MoveToNextChar): | |
99 self.CursorPosition += 1 | |
100 if event.matches(QKeySequence.MoveToPreviousChar): | |
101 self.CursorPosition -= 1 | |
102 if event.matches(QKeySequence.MoveToNextLine): | |
103 self.CursorPosition += 2 * BYTES_PER_LINE | |
104 if event.matches(QKeySequence.MoveToPreviousLine): | |
105 self.CursorPosition -= 2 * BYTES_PER_LINE | |
106 if event.matches(QKeySequence.MoveToNextPage): | |
107 rows = int(self.scrollArea.viewport().height() / self.charHeight) | |
108 self.CursorPosition += (rows - 1) * 2 * BYTES_PER_LINE | |
109 if event.matches(QKeySequence.MoveToPreviousPage): | |
110 rows = int(self.scrollArea.viewport().height() / self.charHeight) | |
111 self.CursorPosition -= (rows - 1) * 2 * BYTES_PER_LINE | |
112 char = event.text().lower() | |
113 if char and char in '0123456789abcdef': | |
114 i = int(self.CursorPosition / 2) | |
115 hb = self.CursorPosition % 2 | |
116 v = int(char, 16) | |
117 if hb == 0: | |
118 # high half byte | |
119 self.data[i] = (self.data[i] & 0xF) | (v << 4) | |
120 else: | |
121 self.data[i] = (self.data[i] & 0xF0) | v | |
122 self.CursorPosition += 1 | |
123 self.scrollArea.ensureVisible(self.cursorX, self.cursorY + self.charHeight / 2, 4, self.charHeight / 2 + 4) | |
124 self.update() | |
125 def setCursorPositionAt(self, pos): | |
126 """ Calculate cursor position at a certain point """ | |
127 if pos.x() > self.xposHex and pos.x() < self.xposAscii: | |
128 x = round((2 * (pos.x() - self.xposHex)) / (self.charWidth * 3)) | |
129 y = int(pos.y() / self.charHeight) * 2 * BYTES_PER_LINE | |
130 self.setCursorPosition(x + y) | |
131 def mousePressEvent(self, event): | |
132 self.setCursorPositionAt(event.pos()) | |
133 def adjust(self): | |
134 self.charHeight = self.fontMetrics().height() | |
135 self.charWidth = self.fontMetrics().width('x') | |
136 self.xposAddr = GAP | |
137 self.xposHex = self.xposAddr + 8 * self.charWidth + GAP | |
138 self.xposAscii = self.xposHex + (BYTES_PER_LINE * 3 - 1) * self.charWidth + GAP | |
139 self.xposEnd = self.xposAscii + self.charWidth * BYTES_PER_LINE + GAP | |
140 self.setMinimumWidth(self.xposEnd) | |
141 if self.isVisible(): | |
142 sbw = self.scrollArea.verticalScrollBar().width() | |
143 self.scrollArea.setMinimumWidth(self.xposEnd + sbw + 5) | |
144 r = len(self.Data) % BYTES_PER_LINE | |
145 r = 1 if r > 0 else 0 | |
146 self.setMinimumHeight((int(len(self.Data) / BYTES_PER_LINE) + r) * self.charHeight + 4) | |
147 self.scrollArea.setMinimumHeight(self.charHeight * 8) | |
148 self.update() | |
149 def showEvent(self, e): | |
150 self.adjust() | |
151 super().showEvent(e) | |
152 def setData(self, d): | |
153 self.data = bytearray(d) | |
154 self.originalData = bytearray(d) | |
155 self.adjust() | |
156 self.setCursorPosition(0) | |
157 Data = property(lambda self: self.data, setData) | |
158 | |
159 class HexEdit(QScrollArea): | |
160 def __init__(self): | |
161 super().__init__() | |
162 self.bv = BinViewer(self) | |
163 self.setWidget(self.bv) | |
164 self.setWidgetResizable(True) | |
165 self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) | |
166 self.setFocusPolicy(Qt.NoFocus) | |
167 | |
168 class HexEditor(QMainWindow): | |
169 def __init__(self): | |
170 super().__init__() | |
171 basedir = os.path.dirname(__file__) | |
172 uic.loadUi(os.path.join(basedir, 'hexeditor.ui'), baseinstance=self) | |
173 self.he = HexEdit() | |
174 self.setCentralWidget(self.he) | |
175 self.actionOpen.triggered.connect(self.doOpen) | |
176 self.actionSave.triggered.connect(self.doSave) | |
177 self.actionSaveAs.triggered.connect(self.doSaveAs) | |
178 self.fileName = None | |
179 self.updateControls() | |
180 def updateControls(self): | |
181 s = True if self.fileName else False | |
182 self.actionSave.setEnabled(s) | |
183 self.actionSaveAs.setEnabled(s) | |
184 def doOpen(self): | |
185 filename = QFileDialog.getOpenFileName(self) | |
186 if filename: | |
187 with open(filename, 'rb') as f: | |
188 self.he.bv.Data = f.read() | |
189 self.fileName = filename | |
190 self.updateControls() | |
191 def doSave(self): | |
192 self.updateControls() | |
193 def doSaveAs(self): | |
194 filename = QFileDialog.getSaveFileName(self) | |
195 if filename: | |
196 with open(filename, 'wb') as f: | |
197 f.write(self.he.bv.Data) | |
198 self.fileName = filename | |
199 self.updateControls() | |
200 | |
201 if __name__ == '__main__': | |
202 app = QApplication(sys.argv) | |
203 he = HexEditor() | |
204 he.show() | |
205 #he.bv.Data = bytearray(range(100)) * 8 + b'x' | |
206 app.exec() | |
207 |