all repos — mgba @ e9d8f1ca46a70ab63f1e5311942408610166d30b

mGBA Game Boy Advance Emulator

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}