all repos — mgba @ ebca878c3177f1f97c9d78df6deb0aa7ee40e17f

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(
199	    QRect(QPoint(viewport()->size().width() - m_margins.right(), 0), QSize(m_margins.right(), m_margins.top())),
200	    Qt::AlignHCenter, tr("ASCII"));
201	for (int x = 0; x < 16; ++x) {
202		painter.drawText(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), 0), m_cellSize), Qt::AlignHCenter,
203		                 QString::number(x, 16).toUpper());
204	}
205	int height = (viewport()->size().height() - m_cellHeight) / m_cellHeight;
206	for (int y = 0; y < height; ++y) {
207		int yp = m_cellHeight * y + m_margins.top();
208		QString data = arg.arg((y + m_top) * 16 + m_base, 8, 16, c0).toUpper();
209		painter.drawText(QRectF(QPointF(0, yp), QSizeF(m_margins.left(), m_cellHeight)), Qt::AlignHCenter, data);
210		switch (m_align) {
211		case 2:
212			for (int x = 0; x < 16; x += 2) {
213				uint32_t address = (y + m_top) * 16 + x + m_base;
214				if (isInSelection(address)) {
215					painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp),
216					                        QSizeF(m_cellSize.width() * 2, m_cellSize.height())),
217					                 palette.highlight());
218					painter.setPen(palette.color(QPalette::HighlightedText));
219					if (isEditing(address)) {
220						drawEditingText(
221						    painter,
222						    QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp));
223						continue;
224					}
225				} else {
226					painter.setPen(palette.color(QPalette::WindowText));
227				}
228				uint16_t b = m_cpu->memory.load16(m_cpu, address, nullptr);
229				painter.drawStaticText(
230				    QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp),
231				    m_staticNumbers[(b >> 8) & 0xFF]);
232				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) + m_margins.left(), yp),
233				                       m_staticNumbers[b & 0xFF]);
234			}
235			break;
236		case 4:
237			for (int x = 0; x < 16; x += 4) {
238				uint32_t address = (y + m_top) * 16 + x + m_base;
239				if (isInSelection(address)) {
240					painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp),
241					                        QSizeF(m_cellSize.width() * 4, m_cellSize.height())),
242					                 palette.highlight());
243					painter.setPen(palette.color(QPalette::HighlightedText));
244					if (isEditing(address)) {
245						drawEditingText(
246						    painter,
247						    QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp));
248						continue;
249					}
250				} else {
251					painter.setPen(palette.color(QPalette::WindowText));
252				}
253				uint32_t b = m_cpu->memory.load32(m_cpu, address, nullptr);
254				painter.drawStaticText(
255				    QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp),
256				    m_staticNumbers[(b >> 24) & 0xFF]);
257				painter.drawStaticText(
258				    QPointF(m_cellSize.width() * (x + 2.0) - 2 * m_letterWidth + m_margins.left(), yp),
259				    m_staticNumbers[(b >> 16) & 0xFF]);
260				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + m_margins.left(), yp),
261				                       m_staticNumbers[(b >> 8) & 0xFF]);
262				painter.drawStaticText(
263				    QPointF(m_cellSize.width() * (x + 2.0) + 2 * m_letterWidth + m_margins.left(), yp),
264				    m_staticNumbers[b & 0xFF]);
265			}
266			break;
267		case 1:
268		default:
269			for (int x = 0; x < 16; ++x) {
270				uint32_t address = (y + m_top) * 16 + x + m_base;
271				if (isInSelection(address)) {
272					painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), m_cellSize),
273					                 palette.highlight());
274					painter.setPen(palette.color(QPalette::HighlightedText));
275					if (isEditing(address)) {
276						drawEditingText(painter,
277						                QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp));
278						continue;
279					}
280				} else {
281					painter.setPen(palette.color(QPalette::WindowText));
282				}
283				uint8_t b = m_cpu->memory.load8(m_cpu, address, nullptr);
284				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp),
285				                       m_staticNumbers[b]);
286			}
287			break;
288		}
289		painter.setPen(palette.color(QPalette::WindowText));
290		for (int x = 0; x < 16; ++x) {
291			uint8_t b = m_cpu->memory.load8(m_cpu, (y + m_top) * 16 + x + m_base, nullptr);
292			painter.drawStaticText(
293			    QPointF(viewport()->size().width() - (16 - x) * m_margins.right() / 17.0 - m_letterWidth * 0.5, yp),
294			    b < 0x80 ? m_staticAscii[b] : m_staticAscii[0]);
295		}
296	}
297	painter.drawLine(m_margins.left(), 0, m_margins.left(), viewport()->size().height());
298	painter.drawLine(viewport()->size().width() - m_margins.right(), 0, viewport()->size().width() - m_margins.right(),
299	                 viewport()->size().height());
300	painter.drawLine(0, m_margins.top(), viewport()->size().width(), m_margins.top());
301}
302
303void MemoryModel::wheelEvent(QWheelEvent* event) {
304	m_top -= event->angleDelta().y() / 8;
305	boundsCheck();
306	event->accept();
307	verticalScrollBar()->setValue(m_top);
308	update();
309}
310
311void MemoryModel::mousePressEvent(QMouseEvent* event) {
312	if (event->x() < m_margins.left() || event->y() < m_margins.top() ||
313	    event->x() > size().width() - m_margins.right()) {
314		m_selection = qMakePair(0, 0);
315		return;
316	}
317
318	QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
319	uint32_t address = int(position.x() / m_cellSize.width()) +
320	                   (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
321	if (event->button() == Qt::RightButton && isInSelection(address)) {
322		return;
323	}
324	if (event->modifiers() & Qt::ShiftModifier) {
325		if ((address & ~(m_align - 1)) < m_selectionAnchor) {
326			m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align);
327		} else {
328			m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align);
329		}
330	} else {
331		m_selectionAnchor = address & ~(m_align - 1);
332		m_selection = qMakePair(m_selectionAnchor, m_selectionAnchor + m_align);
333	}
334	m_buffer = 0;
335	m_bufferedNybbles = 0;
336	emit selectionChanged(m_selection.first, m_selection.second);
337	viewport()->update();
338}
339
340void MemoryModel::mouseMoveEvent(QMouseEvent* event) {
341	if (event->x() < m_margins.left() || event->y() < m_margins.top() ||
342	    event->x() > size().width() - m_margins.right()) {
343		return;
344	}
345
346	QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
347	uint32_t address = int(position.x() / m_cellSize.width()) +
348	                   (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
349	if ((address & ~(m_align - 1)) < m_selectionAnchor) {
350		m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align);
351	} else {
352		m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align);
353	}
354	m_buffer = 0;
355	m_bufferedNybbles = 0;
356	emit selectionChanged(m_selection.first, m_selection.second);
357	viewport()->update();
358}
359
360void MemoryModel::keyPressEvent(QKeyEvent* event) {
361	if (m_selection.first >= m_selection.second) {
362		return;
363	}
364	int key = event->key();
365	uint8_t nybble = 0;
366	switch (key) {
367	case Qt::Key_0:
368	case Qt::Key_1:
369	case Qt::Key_2:
370	case Qt::Key_3:
371	case Qt::Key_4:
372	case Qt::Key_5:
373	case Qt::Key_6:
374	case Qt::Key_7:
375	case Qt::Key_8:
376	case Qt::Key_9:
377		nybble = key - Qt::Key_0;
378		break;
379	case Qt::Key_A:
380	case Qt::Key_B:
381	case Qt::Key_C:
382	case Qt::Key_D:
383	case Qt::Key_E:
384	case Qt::Key_F:
385		nybble = key - Qt::Key_A + 10;
386		break;
387	case Qt::Key_Left:
388		adjustCursor(-m_align, event->modifiers() & Qt::ShiftModifier);
389		return;
390	case Qt::Key_Right:
391		adjustCursor(m_align, event->modifiers() & Qt::ShiftModifier);
392		return;
393	case Qt::Key_Up:
394		adjustCursor(-16, event->modifiers() & Qt::ShiftModifier);
395		return;
396	case Qt::Key_Down:
397		adjustCursor(16, event->modifiers() & Qt::ShiftModifier);
398		return;
399	default:
400		return;
401	}
402	m_buffer <<= 4;
403	m_buffer |= nybble;
404	++m_bufferedNybbles;
405	if (m_bufferedNybbles == m_align * 2) {
406		switch (m_align) {
407		case 1:
408			GBAPatch8(m_cpu, m_selection.first, m_buffer, nullptr);
409			break;
410		case 2:
411			GBAPatch16(m_cpu, m_selection.first, m_buffer, nullptr);
412			break;
413		case 4:
414			GBAPatch32(m_cpu, m_selection.first, m_buffer, nullptr);
415			break;
416		}
417		m_bufferedNybbles = 0;
418		m_buffer = 0;
419		m_selection.first += m_align;
420		if (m_selection.second <= m_selection.first) {
421			m_selection.second = m_selection.first + m_align;
422		}
423		emit selectionChanged(m_selection.first, m_selection.second);
424	}
425	viewport()->update();
426}
427
428void MemoryModel::boundsCheck() {
429	if (m_top < 0) {
430		m_top = 0;
431	} else if (m_top > (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight) {
432		m_top = (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight;
433	}
434}
435
436bool MemoryModel::isInSelection(uint32_t address) {
437	if (m_selection.first == m_selection.second) {
438		return false;
439	}
440	if (m_selection.second <= (address | (m_align - 1))) {
441		return false;
442	}
443	if (m_selection.first <= (address & ~(m_align - 1))) {
444		return true;
445	}
446	return false;
447}
448
449bool MemoryModel::isEditing(uint32_t address) {
450	return m_bufferedNybbles && m_selection.first == (address & ~(m_align - 1));
451}
452
453void MemoryModel::drawEditingText(QPainter& painter, const QPointF& origin) {
454	QPointF o(origin);
455	for (int nybbles = m_bufferedNybbles; nybbles > 0; nybbles -= 2) {
456		if (nybbles > 1) {
457			uint8_t b = m_buffer >> ((nybbles - 2) * 4);
458			painter.drawStaticText(o, m_staticNumbers[b]);
459		} else {
460			int b = m_buffer & 0xF;
461			if (b < 10) {
462				painter.drawStaticText(o, m_staticAscii[b + '0']);
463			} else {
464				painter.drawStaticText(o, m_staticAscii[b - 10 + 'A']);
465			}
466		}
467		o += QPointF(m_letterWidth * 2, 0);
468	}
469}
470
471void MemoryModel::adjustCursor(int adjust, bool shift) {
472	if (m_selection.first >= m_selection.second) {
473		return;
474	}
475	int cursorPosition = m_top;
476	if (shift) {
477		if (m_selectionAnchor == m_selection.first) {
478			if (adjust < 0 && m_base - adjust > m_selection.second) {
479				adjust = m_base - m_selection.second + m_align;
480			} else if (adjust > 0 && m_selection.second + adjust >= m_base + m_size) {
481				adjust = m_base + m_size - m_selection.second;
482			}
483			adjust += m_selection.second;
484			if (adjust <= m_selection.first) {
485				m_selection.second = m_selection.first + m_align;
486				m_selection.first = adjust - m_align;
487				cursorPosition = m_selection.first;
488			} else {
489				m_selection.second = adjust;
490				cursorPosition = m_selection.second - m_align;
491			}
492		} else {
493			if (adjust < 0 && m_base - adjust > m_selection.first) {
494				adjust = m_base - m_selection.first;
495			} else if (adjust > 0 && m_selection.first + adjust >= m_base + m_size) {
496				adjust = m_base + m_size - m_selection.first - m_align;
497			}
498			adjust += m_selection.first;
499			if (adjust >= m_selection.second) {
500				m_selection.first = m_selection.second - m_align;
501				m_selection.second = adjust + m_align;
502				cursorPosition = adjust;
503			} else {
504				m_selection.first = adjust;
505				cursorPosition = m_selection.first;
506			}
507		}
508		cursorPosition = (cursorPosition - m_base) / 16;
509	} else {
510		if (m_selectionAnchor == m_selection.first) {
511			m_selectionAnchor = m_selection.second - m_align;
512		} else {
513			m_selectionAnchor = m_selection.first;
514		}
515		if (adjust < 0 && m_base - adjust > m_selectionAnchor) {
516			m_selectionAnchor = m_base;
517		} else if (adjust > 0 && m_selectionAnchor + adjust >= m_base + m_size) {
518			m_selectionAnchor = m_base + m_size - m_align;
519		} else {
520			m_selectionAnchor += adjust;
521		}
522		m_selection.first = m_selectionAnchor;
523		m_selection.second = m_selection.first + m_align;
524		cursorPosition = (m_selectionAnchor - m_base) / 16;
525	}
526	if (cursorPosition < m_top) {
527		m_top = cursorPosition;
528	} else if (cursorPosition >= m_top + viewport()->size().height() / m_cellHeight - 1) {
529		m_top = cursorPosition - viewport()->size().height() / m_cellHeight + 2;
530	}
531	emit selectionChanged(m_selection.first, m_selection.second);
532	viewport()->update();
533}