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 } else {
146 painter.setPen(palette.color(QPalette::WindowText));
147 }
148 uint16_t b = m_cpu->memory.load16(m_cpu, address, nullptr);
149 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]);
150 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) + m_margins.left(), yp), m_staticNumbers[b & 0xFF]);
151 }
152 break;
153 case 4:
154 for (int x = 0; x < 16; x += 4) {
155 uint32_t address = (y + m_top) * 16 + x + m_base;
156 if (isInSelection(address)) {
157 painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), QSizeF(m_cellSize.width() * 4, m_cellSize.height())), palette.highlight());
158 painter.setPen(palette.color(QPalette::HighlightedText));
159 } else {
160 painter.setPen(palette.color(QPalette::WindowText));
161 }
162 uint32_t b = m_cpu->memory.load32(m_cpu, address, nullptr);
163 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 24) & 0xFF]);
164 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 16) & 0xFF]);
165 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]);
166 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[b & 0xFF]);
167 }
168 break;
169 case 1:
170 default:
171 for (int x = 0; x < 16; ++x) {
172 uint32_t address = (y + m_top) * 16 + x + m_base;
173 if (isInSelection(address)) {
174 painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), m_cellSize), palette.highlight());
175 painter.setPen(palette.color(QPalette::HighlightedText));
176 } else {
177 painter.setPen(palette.color(QPalette::WindowText));
178 }
179 uint8_t b = m_cpu->memory.load8(m_cpu, address, nullptr);
180 painter.drawStaticText(QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp), m_staticNumbers[b]);
181 }
182 break;
183 }
184 painter.setPen(palette.color(QPalette::WindowText));
185 for (int x = 0; x < 16; ++x) {
186 uint8_t b = m_cpu->memory.load8(m_cpu, (y + m_top) * 16 + x + m_base, nullptr);
187 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]);
188 }
189 }
190 painter.drawLine(m_margins.left(), 0, m_margins.left(), viewport()->size().height());
191 painter.drawLine(viewport()->size().width() - m_margins.right(), 0, viewport()->size().width() - m_margins.right(), viewport()->size().height());
192 painter.drawLine(0, m_margins.top(), viewport()->size().width(), m_margins.top());
193}
194
195void MemoryModel::wheelEvent(QWheelEvent* event) {
196 m_top -= event->angleDelta().y() / 8;
197 boundsCheck();
198 event->accept();
199 verticalScrollBar()->setValue(m_top);
200 update();
201}
202
203void MemoryModel::mousePressEvent(QMouseEvent* event) {
204 if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) {
205 m_selection = qMakePair(0, 0);
206 return;
207 }
208
209 QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
210 uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
211 m_selectionAnchor = address & ~(m_align - 1);
212 m_selection = qMakePair(m_selectionAnchor, m_selectionAnchor + m_align);
213 m_buffer = 0;
214 m_bufferedNybbles = 0;
215 emit selectionChanged(m_selection.first, m_selection.second);
216 viewport()->update();
217}
218
219void MemoryModel::mouseMoveEvent(QMouseEvent* event) {
220 if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) {
221 return;
222 }
223
224 QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
225 uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
226 if ((address & ~(m_align - 1)) < m_selectionAnchor) {
227 m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align);
228 } else {
229 m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align);
230 }
231 m_buffer = 0;
232 m_bufferedNybbles = 0;
233 emit selectionChanged(m_selection.first, m_selection.second);
234 viewport()->update();
235}
236
237void MemoryModel::keyPressEvent(QKeyEvent* event) {
238 if (m_selection.first >= m_selection.second) {
239 return;
240 }
241 int key = event->key();
242 uint8_t nybble = 0;
243 switch (key) {
244 case Qt::Key_0:
245 case Qt::Key_1:
246 case Qt::Key_2:
247 case Qt::Key_3:
248 case Qt::Key_4:
249 case Qt::Key_5:
250 case Qt::Key_6:
251 case Qt::Key_7:
252 case Qt::Key_8:
253 case Qt::Key_9:
254 nybble = key - Qt::Key_0;
255 break;
256 case Qt::Key_A:
257 case Qt::Key_B:
258 case Qt::Key_C:
259 case Qt::Key_D:
260 case Qt::Key_E:
261 case Qt::Key_F:
262 nybble = key - Qt::Key_A + 10;
263 break;
264 default:
265 return;
266 }
267 m_buffer <<= 4;
268 m_buffer |= nybble;
269 ++m_bufferedNybbles;
270 if (m_bufferedNybbles == m_align * 2) {
271 switch (m_align) {
272 case 1:
273 GBAPatch8(m_cpu, m_selection.first, m_buffer, nullptr);
274 break;
275 case 2:
276 GBAPatch16(m_cpu, m_selection.first, m_buffer, nullptr);
277 break;
278 case 4:
279 GBAPatch32(m_cpu, m_selection.first, m_buffer, nullptr);
280 break;
281 }
282 m_bufferedNybbles = 0;
283 m_buffer = 0;
284 m_selection.first += m_align;
285 if (m_selection.second <= m_selection.first) {
286 m_selection.second = m_selection.first + m_align;
287 }
288 emit selectionChanged(m_selection.first, m_selection.second);
289 viewport()->update();
290 }
291}
292
293void MemoryModel::boundsCheck() {
294 if (m_top < 0) {
295 m_top = 0;
296 } else if (m_top > (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight) {
297 m_top = (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight;
298 }
299}
300
301bool MemoryModel::isInSelection(uint32_t address) {
302 if (m_selection.first == m_selection.second) {
303 return false;
304 }
305 if (m_selection.second <= (address | (m_align - 1))) {
306 return false;
307 }
308 if (m_selection.first <= (address & ~(m_align - 1))) {
309 return true;
310 }
311 return false;
312}