src/platform/qt/MemoryModel.cpp (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "MemoryModel.h"
7
8#include "GameController.h"
9
10#include <QFontMetrics>
11#include <QPainter>
12#include <QScrollBar>
13#include <QSlider>
14#include <QWheelEvent>
15
16extern "C" {
17#include "gba/memory.h"
18}
19
20using namespace QGBA;
21
22MemoryModel::MemoryModel(QWidget* parent)
23 : QAbstractScrollArea(parent)
24 , m_cpu(nullptr)
25 , m_top(0)
26 , m_align(1)
27 , m_selection(0, 0)
28 , m_selectionAnchor(0)
29{
30 m_font.setFamily("Source Code Pro");
31 m_font.setStyleHint(QFont::Monospace);
32 m_font.setPointSize(12);
33 QFontMetrics metrics(m_font);
34 m_cellHeight = metrics.height();
35 m_letterWidth = metrics.averageCharWidth();
36
37 setFocusPolicy(Qt::StrongFocus);
38
39 for (int i = 0; i < 256; ++i) {
40 QStaticText str(QString("%0").arg(i, 2, 16, QChar('0')).toUpper());
41 str.prepare(QTransform(), m_font);
42 m_staticNumbers.append(str);
43 }
44
45 for (int i = 0; i < 128; ++i) {
46 QChar c(i);
47 if (!c.isPrint()) {
48 c = '.';
49 }
50 QStaticText str = QStaticText(QString(c));
51 str.prepare(QTransform(), m_font);
52 m_staticAscii.append(str);
53 }
54
55 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
56 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
57 m_margins = QMargins(metrics.width("0FFFFFF0 ") + 3, m_cellHeight + 1, metrics.width(" AAAAAAAAAAAAAAAA") + 3, 0);
58 m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight);
59
60 connect(verticalScrollBar(), &QSlider::sliderMoved, [this](int position) {
61 m_top = position;
62 update();
63 });
64
65 setRegion(0, 0x10000000, tr("All"));
66}
67
68void MemoryModel::setController(GameController* controller) {
69 m_cpu = controller->thread()->cpu;
70}
71
72void MemoryModel::setRegion(uint32_t base, uint32_t size, const QString& name) {
73 m_top = 0;
74 m_base = base;
75 m_size = size;
76 m_regionName = name;
77 m_regionName.prepare(QTransform(), m_font);
78 verticalScrollBar()->setRange(0, (size >> 4) + 1 - viewport()->size().height() / m_cellHeight);
79 verticalScrollBar()->setValue(0);
80 viewport()->update();
81}
82
83void MemoryModel::setAlignment(int width) {
84 if (width != 1 && width != 2 && width != 4) {
85 return;
86 }
87 m_align = width;
88 m_buffer = 0;
89 m_bufferedNybbles = 0;
90 viewport()->update();
91}
92
93void MemoryModel::jumpToAddress(const QString& hex) {
94 bool ok = false;
95 uint32_t i = hex.toInt(&ok, 16);
96 if (ok) {
97 jumpToAddress(i);
98 }
99}
100
101void MemoryModel::jumpToAddress(uint32_t address) {
102 if (address >= 0x10000000) {
103 return;
104 }
105 if (address < m_base || address >= m_base + m_size) {
106 setRegion(0, 0x10000000, tr("All"));
107 }
108 m_top = (address - m_base) / 16;
109 boundsCheck();
110 verticalScrollBar()->setValue(m_top);
111 m_buffer = 0;
112 m_bufferedNybbles = 0;
113}
114
115void MemoryModel::resizeEvent(QResizeEvent*) {
116 m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight);
117 verticalScrollBar()->setRange(0, (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight);
118 boundsCheck();
119}
120
121void MemoryModel::paintEvent(QPaintEvent* event) {
122 QPainter painter(viewport());
123 QPalette palette;
124 painter.setFont(m_font);
125 painter.setPen(palette.color(QPalette::WindowText));
126 QChar c0('0');
127 QSizeF letterSize = QSizeF(m_letterWidth, m_cellHeight);
128 painter.drawStaticText(QPointF((m_margins.left() - m_regionName.size().width() - 1) / 2.0, 0), m_regionName);
129 painter.drawText(QRect(QPoint(viewport()->size().width() - m_margins.right(), 0), QSize(m_margins.right(), m_margins.top())), Qt::AlignHCenter, tr("ASCII"));
130 for (int x = 0; x < 16; ++x) {
131 painter.drawText(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), 0), m_cellSize), Qt::AlignHCenter, QString::number(x, 16).toUpper());
132 }
133 int height = (viewport()->size().height() - m_cellHeight) / m_cellHeight;
134 for (int y = 0; y < height; ++y) {
135 int yp = m_cellHeight * y + m_margins.top();
136 QString data = QString("%0").arg((y + m_top) * 16 + m_base, 8, 16, c0).toUpper();
137 painter.drawText(QRectF(QPointF(0, yp), QSizeF(m_margins.left(), m_cellHeight)), Qt::AlignHCenter, data);
138 switch (m_align) {
139 case 2:
140 for (int x = 0; x < 16; x += 2) {
141 uint32_t address = (y + m_top) * 16 + x + m_base;
142 if (isInSelection(address)) {
143 painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), QSizeF(m_cellSize.width() * 2, m_cellSize.height())), palette.highlight());
144 painter.setPen(palette.color(QPalette::HighlightedText));
145 if (isEditing(address)) {
146 drawEditingText(painter, QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp));
147 continue;
148 }
149 } else {
150 painter.setPen(palette.color(QPalette::WindowText));
151 }
152 uint16_t b = m_cpu->memory.load16(m_cpu, address, nullptr);
153 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]);
154 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) + m_margins.left(), yp), m_staticNumbers[b & 0xFF]);
155 }
156 break;
157 case 4:
158 for (int x = 0; x < 16; x += 4) {
159 uint32_t address = (y + m_top) * 16 + x + m_base;
160 if (isInSelection(address)) {
161 painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), QSizeF(m_cellSize.width() * 4, m_cellSize.height())), palette.highlight());
162 painter.setPen(palette.color(QPalette::HighlightedText));
163 if (isEditing(address)) {
164 drawEditingText(painter, QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp));
165 continue;
166 }
167 } else {
168 painter.setPen(palette.color(QPalette::WindowText));
169 }
170 uint32_t b = m_cpu->memory.load32(m_cpu, address, nullptr);
171 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 24) & 0xFF]);
172 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 16) & 0xFF]);
173 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]);
174 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[b & 0xFF]);
175 }
176 break;
177 case 1:
178 default:
179 for (int x = 0; x < 16; ++x) {
180 uint32_t address = (y + m_top) * 16 + x + m_base;
181 if (isInSelection(address)) {
182 painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), m_cellSize), palette.highlight());
183 painter.setPen(palette.color(QPalette::HighlightedText));
184 if (isEditing(address)) {
185 drawEditingText(painter, QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp));
186 continue;
187 }
188 } else {
189 painter.setPen(palette.color(QPalette::WindowText));
190 }
191 uint8_t b = m_cpu->memory.load8(m_cpu, address, nullptr);
192 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp), m_staticNumbers[b]);
193 }
194 break;
195 }
196 painter.setPen(palette.color(QPalette::WindowText));
197 for (int x = 0; x < 16; ++x) {
198 uint8_t b = m_cpu->memory.load8(m_cpu, (y + m_top) * 16 + x + m_base, nullptr);
199 painter.drawStaticText(QPointF(viewport()->size().width() - (16 - x) * m_margins.right() / 17.0 - m_letterWidth * 0.5, yp), b < 0x80 ? m_staticAscii[b] : m_staticAscii[0]);
200 }
201 }
202 painter.drawLine(m_margins.left(), 0, m_margins.left(), viewport()->size().height());
203 painter.drawLine(viewport()->size().width() - m_margins.right(), 0, viewport()->size().width() - m_margins.right(), viewport()->size().height());
204 painter.drawLine(0, m_margins.top(), viewport()->size().width(), m_margins.top());
205}
206
207void MemoryModel::wheelEvent(QWheelEvent* event) {
208 m_top -= event->angleDelta().y() / 8;
209 boundsCheck();
210 event->accept();
211 verticalScrollBar()->setValue(m_top);
212 update();
213}
214
215void MemoryModel::mousePressEvent(QMouseEvent* event) {
216 if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) {
217 m_selection = qMakePair(0, 0);
218 return;
219 }
220
221 QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
222 uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
223 m_selectionAnchor = address & ~(m_align - 1);
224 m_selection = qMakePair(m_selectionAnchor, m_selectionAnchor + m_align);
225 m_buffer = 0;
226 m_bufferedNybbles = 0;
227 emit selectionChanged(m_selection.first, m_selection.second);
228 viewport()->update();
229}
230
231void MemoryModel::mouseMoveEvent(QMouseEvent* event) {
232 if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) {
233 return;
234 }
235
236 QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
237 uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
238 if ((address & ~(m_align - 1)) < m_selectionAnchor) {
239 m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align);
240 } else {
241 m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align);
242 }
243 m_buffer = 0;
244 m_bufferedNybbles = 0;
245 emit selectionChanged(m_selection.first, m_selection.second);
246 viewport()->update();
247}
248
249void MemoryModel::keyPressEvent(QKeyEvent* event) {
250 if (m_selection.first >= m_selection.second) {
251 return;
252 }
253 int key = event->key();
254 uint8_t nybble = 0;
255 switch (key) {
256 case Qt::Key_0:
257 case Qt::Key_1:
258 case Qt::Key_2:
259 case Qt::Key_3:
260 case Qt::Key_4:
261 case Qt::Key_5:
262 case Qt::Key_6:
263 case Qt::Key_7:
264 case Qt::Key_8:
265 case Qt::Key_9:
266 nybble = key - Qt::Key_0;
267 break;
268 case Qt::Key_A:
269 case Qt::Key_B:
270 case Qt::Key_C:
271 case Qt::Key_D:
272 case Qt::Key_E:
273 case Qt::Key_F:
274 nybble = key - Qt::Key_A + 10;
275 break;
276 default:
277 return;
278 }
279 m_buffer <<= 4;
280 m_buffer |= nybble;
281 ++m_bufferedNybbles;
282 if (m_bufferedNybbles == m_align * 2) {
283 switch (m_align) {
284 case 1:
285 GBAPatch8(m_cpu, m_selection.first, m_buffer, nullptr);
286 break;
287 case 2:
288 GBAPatch16(m_cpu, m_selection.first, m_buffer, nullptr);
289 break;
290 case 4:
291 GBAPatch32(m_cpu, m_selection.first, m_buffer, nullptr);
292 break;
293 }
294 m_bufferedNybbles = 0;
295 m_buffer = 0;
296 m_selection.first += m_align;
297 if (m_selection.second <= m_selection.first) {
298 m_selection.second = m_selection.first + m_align;
299 }
300 emit selectionChanged(m_selection.first, m_selection.second);
301 }
302 viewport()->update();
303}
304
305void MemoryModel::boundsCheck() {
306 if (m_top < 0) {
307 m_top = 0;
308 } else if (m_top > (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight) {
309 m_top = (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight;
310 }
311}
312
313bool MemoryModel::isInSelection(uint32_t address) {
314 if (m_selection.first == m_selection.second) {
315 return false;
316 }
317 if (m_selection.second <= (address | (m_align - 1))) {
318 return false;
319 }
320 if (m_selection.first <= (address & ~(m_align - 1))) {
321 return true;
322 }
323 return false;
324}
325
326bool MemoryModel::isEditing(uint32_t address) {
327 return m_bufferedNybbles && m_selection.first == (address & ~(m_align - 1));
328}
329
330void MemoryModel::drawEditingText(QPainter& painter, const QPointF& origin) {
331 QPointF o(origin);
332 for (int nybbles = m_bufferedNybbles; nybbles > 0; nybbles -= 2) {
333 if (nybbles > 1) {
334 uint8_t b = m_buffer >> ((nybbles - 2) * 4);
335 painter.drawStaticText(o, m_staticNumbers[b]);
336 } else {
337 int b = m_buffer & 0xF;
338 painter.drawStaticText(o, m_staticAscii[b + '0']);
339 }
340 o += QPointF(m_letterWidth * 2, 0);
341 }
342}