all repos — mgba @ 9bee2f4cd3db42062731756c7080aeb4c3891300

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 "GBAApp.h"
  9#include "GameController.h"
 10
 11#include <QAction>
 12#include <QApplication>
 13#include <QClipboard>
 14#include <QFontMetrics>
 15#include <QPainter>
 16#include <QScrollBar>
 17#include <QSlider>
 18#include <QWheelEvent>
 19
 20extern "C" {
 21#include "gba/memory.h"
 22}
 23
 24using namespace QGBA;
 25
 26MemoryModel::MemoryModel(QWidget* parent)
 27	: QAbstractScrollArea(parent)
 28	, m_cpu(nullptr)
 29	, m_top(0)
 30	, m_align(1)
 31	, m_selection(0, 0)
 32	, m_selectionAnchor(0)
 33{
 34	m_font.setFamily("Source Code Pro");
 35	m_font.setStyleHint(QFont::Monospace);
 36	m_font.setPointSize(12);
 37	QFontMetrics metrics(m_font);
 38	m_cellHeight = metrics.height();
 39	m_letterWidth = metrics.averageCharWidth();
 40
 41	setFocusPolicy(Qt::StrongFocus);
 42	setContextMenuPolicy(Qt::ActionsContextMenu);
 43
 44	QAction* copy = new QAction(tr("Copy selection"), this);
 45	copy->setShortcut(QKeySequence::Copy);
 46	connect(copy, SIGNAL(triggered()), this, SLOT(copy()));
 47	addAction(copy);
 48
 49	QAction* save = new QAction(tr("Save selection"), this);
 50	save->setShortcut(QKeySequence::Save);
 51	connect(save, SIGNAL(triggered()), this, SLOT(save()));
 52	addAction(save);
 53
 54	static QString arg("%0");
 55	for (int i = 0; i < 256; ++i) {
 56		QStaticText str(arg.arg(i, 2, 16, QChar('0')).toUpper());
 57		str.prepare(QTransform(), m_font);
 58		m_staticNumbers.append(str);
 59	}
 60
 61	for (int i = 0; i < 128; ++i) {
 62		QChar c(i);
 63		if (!c.isPrint()) {
 64			c = '.';
 65		}
 66		QStaticText str = QStaticText(QString(c));
 67		str.prepare(QTransform(), m_font);
 68		m_staticAscii.append(str);
 69	}
 70
 71	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
 72	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
 73	m_margins = QMargins(metrics.width("0FFFFFF0 ") + 3, m_cellHeight + 1, metrics.width(" AAAAAAAAAAAAAAAA") + 3, 0);
 74	m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight);
 75
 76	connect(verticalScrollBar(), &QSlider::sliderMoved, [this](int position) {
 77		m_top = position;
 78		update();
 79	});
 80
 81	setRegion(0, 0x10000000, tr("All"));
 82}
 83
 84void MemoryModel::setController(GameController* controller) {
 85	m_cpu = controller->thread()->cpu;
 86}
 87
 88void MemoryModel::setRegion(uint32_t base, uint32_t size, const QString& name) {
 89	m_top = 0;
 90	m_base = base;
 91	m_size = size;
 92	m_regionName = name;
 93	m_regionName.prepare(QTransform(), m_font);
 94	verticalScrollBar()->setRange(0, (size >> 4) + 1 - viewport()->size().height() / m_cellHeight);
 95	verticalScrollBar()->setValue(0);
 96	viewport()->update();
 97}
 98
 99void MemoryModel::setAlignment(int width) {
100	if (width != 1 && width != 2 && width != 4) {
101		return;
102	}
103	m_align = width;
104	m_buffer = 0;
105	m_bufferedNybbles = 0;
106	viewport()->update();
107}
108
109void MemoryModel::jumpToAddress(const QString& hex) {
110	bool ok = false;
111	uint32_t i = hex.toInt(&ok, 16);
112	if (ok) {
113		jumpToAddress(i);
114	}
115}
116
117void MemoryModel::jumpToAddress(uint32_t address) {
118	if (address >= 0x10000000) {
119		return;
120	}
121	if (address < m_base || address >= m_base + m_size) {
122		setRegion(0, 0x10000000, tr("All"));
123	}
124	m_top = (address - m_base) / 16;
125	boundsCheck();
126	verticalScrollBar()->setValue(m_top);
127	m_buffer = 0;
128	m_bufferedNybbles = 0;
129}
130
131void MemoryModel::copy() {
132	QClipboard* clipboard = QApplication::clipboard();
133	if (!clipboard) {
134		return;
135	}
136	QByteArray bytestring;
137	QDataStream stream(&bytestring, QIODevice::WriteOnly);
138	serialize(&stream);
139	QString string;
140	string.reserve(bytestring.size() * 2);
141	static QString arg("%0");
142	static QChar c0('0');
143	for (uchar c : bytestring) {
144		string.append(arg.arg(c, 2, 16, c0).toUpper());
145	}
146	clipboard->setText(string);
147}
148
149void MemoryModel::save() {
150	QString filename = GBAApp::app()->getSaveFileName(this, tr("Save selected memory"));
151	if (filename.isNull()) {
152		return;
153	}
154	QFile outfile(filename);
155	if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
156		// TODO: Log
157		return;
158	}
159	QDataStream stream(&outfile);
160	serialize(&stream);
161}
162
163void MemoryModel::serialize(QDataStream* stream) {
164	switch (m_align) {
165	case 1:
166		for (uint32_t i = m_selection.first; i < m_selection.second; i += m_align) {
167			*stream << (quint8) m_cpu->memory.load8(m_cpu, i, nullptr);
168		}
169		break;
170	case 2:
171		for (uint32_t i = m_selection.first; i < m_selection.second; i += m_align) {
172			*stream << (quint16) m_cpu->memory.load16(m_cpu, i, nullptr);
173		}
174		break;
175	case 4:
176		for (uint32_t i = m_selection.first; i < m_selection.second; i += m_align) {
177			*stream << (quint32) m_cpu->memory.load32(m_cpu, i, nullptr);
178		}
179		break;
180	}
181}
182
183void MemoryModel::resizeEvent(QResizeEvent*) {
184	m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight);
185	verticalScrollBar()->setRange(0, (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight);
186	boundsCheck();
187}
188
189void MemoryModel::paintEvent(QPaintEvent* event) {
190	QPainter painter(viewport());
191	QPalette palette;
192	painter.setFont(m_font);
193	painter.setPen(palette.color(QPalette::WindowText));
194	static QChar c0('0');
195	static QString arg("%0");
196	QSizeF letterSize = QSizeF(m_letterWidth, m_cellHeight);
197	painter.drawStaticText(QPointF((m_margins.left() - m_regionName.size().width() - 1) / 2.0, 0), m_regionName);
198	painter.drawText(QRect(QPoint(viewport()->size().width() - m_margins.right(), 0), QSize(m_margins.right(), m_margins.top())), Qt::AlignHCenter, tr("ASCII"));
199	for (int x = 0; x < 16; ++x) {
200		painter.drawText(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), 0), m_cellSize), Qt::AlignHCenter, QString::number(x, 16).toUpper());
201	}
202	int height = (viewport()->size().height() - m_cellHeight) / m_cellHeight;
203	for (int y = 0; y < height; ++y) {
204		int yp = m_cellHeight * y + m_margins.top();
205		QString data = arg.arg((y + m_top) * 16 + m_base, 8, 16, c0).toUpper();
206		painter.drawText(QRectF(QPointF(0, yp), QSizeF(m_margins.left(), m_cellHeight)), Qt::AlignHCenter, data);
207		switch (m_align) {
208		case 2:
209			for (int x = 0; x < 16; x += 2) {
210				uint32_t address = (y + m_top) * 16 + x + m_base;
211				if (isInSelection(address)) {
212					painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), QSizeF(m_cellSize.width() * 2, m_cellSize.height())), palette.highlight());
213					painter.setPen(palette.color(QPalette::HighlightedText));
214					if (isEditing(address)) {
215						drawEditingText(painter, QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp));
216						continue;
217					}
218				} else {
219					painter.setPen(palette.color(QPalette::WindowText));
220				}
221				uint16_t b = m_cpu->memory.load16(m_cpu, address, nullptr);
222				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]);
223				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) + m_margins.left(), yp), m_staticNumbers[b & 0xFF]);
224			}
225			break;
226		case 4:
227			for (int x = 0; x < 16; x += 4) {
228				uint32_t address = (y + m_top) * 16 + x + m_base;
229				if (isInSelection(address)) {
230					painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), QSizeF(m_cellSize.width() * 4, m_cellSize.height())), palette.highlight());
231					painter.setPen(palette.color(QPalette::HighlightedText));
232					if (isEditing(address)) {
233						drawEditingText(painter, QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp));
234						continue;
235					}
236				} else {
237					painter.setPen(palette.color(QPalette::WindowText));
238				}
239				uint32_t b = m_cpu->memory.load32(m_cpu, address, nullptr);
240				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 24) & 0xFF]);
241				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 16) & 0xFF]);
242				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]);
243				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[b & 0xFF]);
244			}
245			break;
246		case 1:
247		default:
248			for (int x = 0; x < 16; ++x) {
249				uint32_t address = (y + m_top) * 16 + x + m_base;
250				if (isInSelection(address)) {
251					painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), m_cellSize), palette.highlight());
252					painter.setPen(palette.color(QPalette::HighlightedText));
253					if (isEditing(address)) {
254						drawEditingText(painter, QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp));
255						continue;
256					}
257				} else {
258					painter.setPen(palette.color(QPalette::WindowText));
259				}
260				uint8_t b = m_cpu->memory.load8(m_cpu, address, nullptr);
261				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp), m_staticNumbers[b]);
262			}
263			break;
264		}
265		painter.setPen(palette.color(QPalette::WindowText));
266		for (int x = 0; x < 16; ++x) {
267			uint8_t b = m_cpu->memory.load8(m_cpu, (y + m_top) * 16 + x + m_base, nullptr);
268			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]);
269		}
270	}
271	painter.drawLine(m_margins.left(), 0, m_margins.left(), viewport()->size().height());
272	painter.drawLine(viewport()->size().width() - m_margins.right(), 0, viewport()->size().width() - m_margins.right(), viewport()->size().height());
273	painter.drawLine(0, m_margins.top(), viewport()->size().width(), m_margins.top());
274}
275
276void MemoryModel::wheelEvent(QWheelEvent* event) {
277	m_top -= event->angleDelta().y() / 8;
278	boundsCheck();
279	event->accept();
280	verticalScrollBar()->setValue(m_top);
281	update();
282}
283
284void MemoryModel::mousePressEvent(QMouseEvent* event) {
285	if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) {
286		m_selection = qMakePair(0, 0);
287		return;
288	}
289
290	QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
291	uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
292	if (event->button() == Qt::RightButton && isInSelection(address)) {
293		return;
294	}
295	m_selectionAnchor = address & ~(m_align - 1);
296	m_selection = qMakePair(m_selectionAnchor, m_selectionAnchor + m_align);
297	m_buffer = 0;
298	m_bufferedNybbles = 0;
299	emit selectionChanged(m_selection.first, m_selection.second);
300	viewport()->update();
301}
302
303void MemoryModel::mouseMoveEvent(QMouseEvent* event) {
304	if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) {
305		return;
306	}
307
308	QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
309	uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
310	if ((address & ~(m_align - 1)) < m_selectionAnchor) {
311		m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align);
312	} else {
313		m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align);
314	}
315	m_buffer = 0;
316	m_bufferedNybbles = 0;
317	emit selectionChanged(m_selection.first, m_selection.second);
318	viewport()->update();
319}
320
321void MemoryModel::keyPressEvent(QKeyEvent* event) {
322	if (m_selection.first >= m_selection.second) {
323		return;
324	}
325	int key = event->key();
326	uint8_t nybble = 0;
327	switch (key) {
328	case Qt::Key_0:
329	case Qt::Key_1:
330	case Qt::Key_2:
331	case Qt::Key_3:
332	case Qt::Key_4:
333	case Qt::Key_5:
334	case Qt::Key_6:
335	case Qt::Key_7:
336	case Qt::Key_8:
337	case Qt::Key_9:
338		nybble = key - Qt::Key_0;
339		break;
340	case Qt::Key_A:
341	case Qt::Key_B:
342	case Qt::Key_C:
343	case Qt::Key_D:
344	case Qt::Key_E:
345	case Qt::Key_F:
346		nybble = key - Qt::Key_A + 10;
347		break;
348	case Qt::Key_Left:
349		adjustCursor(-m_align, event->modifiers() & Qt::ShiftModifier);
350		return;
351	case Qt::Key_Right:
352		adjustCursor(m_align, event->modifiers() & Qt::ShiftModifier);
353		return;
354	case Qt::Key_Up:
355		adjustCursor(-16, event->modifiers() & Qt::ShiftModifier);
356		return;
357	case Qt::Key_Down:
358		adjustCursor(16, event->modifiers() & Qt::ShiftModifier);
359		return;
360	default:
361		return;
362	}
363	m_buffer <<= 4;
364	m_buffer |= nybble;
365	++m_bufferedNybbles;
366	if (m_bufferedNybbles == m_align * 2) {
367		switch (m_align) {
368		case 1:
369			GBAPatch8(m_cpu, m_selection.first, m_buffer, nullptr);
370			break;
371		case 2:
372			GBAPatch16(m_cpu, m_selection.first, m_buffer, nullptr);
373			break;
374		case 4:
375			GBAPatch32(m_cpu, m_selection.first, m_buffer, nullptr);
376			break;
377		}
378		m_bufferedNybbles = 0;
379		m_buffer = 0;
380		m_selection.first += m_align;
381		if (m_selection.second <= m_selection.first) {
382			m_selection.second = m_selection.first + m_align;
383		}
384		emit selectionChanged(m_selection.first, m_selection.second);
385	}
386	viewport()->update();
387}
388
389void MemoryModel::boundsCheck() {
390	if (m_top < 0) {
391		m_top = 0;
392	} else if (m_top > (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight) {
393		m_top = (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight;
394	}
395}
396
397bool MemoryModel::isInSelection(uint32_t address) {
398	if (m_selection.first == m_selection.second) {
399		return false;
400	}
401	if (m_selection.second <= (address | (m_align - 1))) {
402		return false;
403	}
404	if (m_selection.first <= (address & ~(m_align - 1))) {
405		return true;
406	}
407	return false;
408}
409
410bool MemoryModel::isEditing(uint32_t address) {
411	return m_bufferedNybbles && m_selection.first == (address & ~(m_align - 1));
412}
413
414void MemoryModel::drawEditingText(QPainter& painter, const QPointF& origin) {
415	QPointF o(origin);
416	for (int nybbles = m_bufferedNybbles; nybbles > 0; nybbles -= 2) {
417		if (nybbles > 1) {
418			uint8_t b = m_buffer >> ((nybbles - 2) * 4);
419			painter.drawStaticText(o, m_staticNumbers[b]);
420		} else {
421			int b = m_buffer & 0xF;
422			painter.drawStaticText(o, m_staticAscii[b + '0']);
423		}
424		o += QPointF(m_letterWidth * 2, 0);
425	}
426}
427
428void MemoryModel::adjustCursor(int adjust, bool shift) {
429	if (m_selection.first >= m_selection.second) {
430		return;
431	}
432	int cursorPosition = m_top;
433	if (shift) {
434		if (m_selectionAnchor == m_selection.first) {
435			if (adjust < 0 && m_base - adjust > m_selection.second) {
436				adjust = m_base - m_selection.second + m_align;
437			} else if (adjust > 0 && m_selection.second + adjust >= m_base + m_size) {
438				adjust = m_base + m_size - m_selection.second;
439			}
440			adjust += m_selection.second;
441			if (adjust <= m_selection.first) {
442				m_selection.second = m_selection.first + m_align;
443				m_selection.first = adjust - m_align;
444				cursorPosition = m_selection.first;
445			} else {
446				m_selection.second = adjust;
447				cursorPosition = m_selection.second - m_align;
448			}
449		} else {
450			if (adjust < 0 && m_base - adjust > m_selection.first) {
451				adjust = m_base - m_selection.first;
452			} else if (adjust > 0 && m_selection.first + adjust >= m_base + m_size) {
453				adjust = m_base + m_size - m_selection.first - m_align;
454			}
455			adjust += m_selection.first;
456			if (adjust >= m_selection.second) {
457				m_selection.first = m_selection.second - m_align;
458				m_selection.second = adjust + m_align;
459				cursorPosition = adjust;
460			} else {
461				m_selection.first = adjust;
462				cursorPosition = m_selection.first;
463			}
464		}
465		cursorPosition = (cursorPosition - m_base) / 16;
466	} else {
467		if (m_selectionAnchor == m_selection.first) {
468			m_selectionAnchor = m_selection.second - m_align;
469		} else {
470			m_selectionAnchor = m_selection.first;
471		}
472		if (adjust < 0 && m_base - adjust > m_selectionAnchor) {
473			m_selectionAnchor = m_base;
474		} else if (adjust > 0 && m_selectionAnchor + adjust >= m_base + m_size) {
475			m_selectionAnchor = m_base + m_size - m_align;
476		} else {
477			m_selectionAnchor += adjust;
478		}
479		m_selection.first = m_selectionAnchor;
480		m_selection.second = m_selection.first + m_align;
481		cursorPosition = (m_selectionAnchor - m_base) / 16;
482	}
483	if (cursorPosition < m_top) {
484		m_top = cursorPosition;
485	} else if (cursorPosition >= m_top + viewport()->size().height() / m_cellHeight - 1) {
486		m_top = cursorPosition - viewport()->size().height() / m_cellHeight + 2;
487	}
488	emit selectionChanged(m_selection.first, m_selection.second);
489	viewport()->update();
490}