all repos — mgba @ 298b7e7a8b0a37fca2f55b4b859652cfd7762613

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