all repos — mgba @ abdf448f81da9f01a323ceef4cb00df9758d5002

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 =
320	    int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
321	if (event->button() == Qt::RightButton && isInSelection(address)) {
322		return;
323	}
324	m_selectionAnchor = address & ~(m_align - 1);
325	m_selection = qMakePair(m_selectionAnchor, m_selectionAnchor + m_align);
326	m_buffer = 0;
327	m_bufferedNybbles = 0;
328	emit selectionChanged(m_selection.first, m_selection.second);
329	viewport()->update();
330}
331
332void MemoryModel::mouseMoveEvent(QMouseEvent* event) {
333	if (event->x() < m_margins.left() || event->y() < m_margins.top() ||
334	    event->x() > size().width() - m_margins.right()) {
335		return;
336	}
337
338	QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
339	uint32_t address =
340	    int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
341	if ((address & ~(m_align - 1)) < m_selectionAnchor) {
342		m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align);
343	} else {
344		m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align);
345	}
346	m_buffer = 0;
347	m_bufferedNybbles = 0;
348	emit selectionChanged(m_selection.first, m_selection.second);
349	viewport()->update();
350}
351
352void MemoryModel::keyPressEvent(QKeyEvent* event) {
353	if (m_selection.first >= m_selection.second) {
354		return;
355	}
356	int key = event->key();
357	uint8_t nybble = 0;
358	switch (key) {
359	case Qt::Key_0:
360	case Qt::Key_1:
361	case Qt::Key_2:
362	case Qt::Key_3:
363	case Qt::Key_4:
364	case Qt::Key_5:
365	case Qt::Key_6:
366	case Qt::Key_7:
367	case Qt::Key_8:
368	case Qt::Key_9:
369		nybble = key - Qt::Key_0;
370		break;
371	case Qt::Key_A:
372	case Qt::Key_B:
373	case Qt::Key_C:
374	case Qt::Key_D:
375	case Qt::Key_E:
376	case Qt::Key_F:
377		nybble = key - Qt::Key_A + 10;
378		break;
379	case Qt::Key_Left:
380		adjustCursor(-m_align, event->modifiers() & Qt::ShiftModifier);
381		return;
382	case Qt::Key_Right:
383		adjustCursor(m_align, event->modifiers() & Qt::ShiftModifier);
384		return;
385	case Qt::Key_Up:
386		adjustCursor(-16, event->modifiers() & Qt::ShiftModifier);
387		return;
388	case Qt::Key_Down:
389		adjustCursor(16, event->modifiers() & Qt::ShiftModifier);
390		return;
391	default:
392		return;
393	}
394	m_buffer <<= 4;
395	m_buffer |= nybble;
396	++m_bufferedNybbles;
397	if (m_bufferedNybbles == m_align * 2) {
398		switch (m_align) {
399		case 1:
400			GBAPatch8(m_cpu, m_selection.first, m_buffer, nullptr);
401			break;
402		case 2:
403			GBAPatch16(m_cpu, m_selection.first, m_buffer, nullptr);
404			break;
405		case 4:
406			GBAPatch32(m_cpu, m_selection.first, m_buffer, nullptr);
407			break;
408		}
409		m_bufferedNybbles = 0;
410		m_buffer = 0;
411		m_selection.first += m_align;
412		if (m_selection.second <= m_selection.first) {
413			m_selection.second = m_selection.first + m_align;
414		}
415		emit selectionChanged(m_selection.first, m_selection.second);
416	}
417	viewport()->update();
418}
419
420void MemoryModel::boundsCheck() {
421	if (m_top < 0) {
422		m_top = 0;
423	} else if (m_top > (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight) {
424		m_top = (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight;
425	}
426}
427
428bool MemoryModel::isInSelection(uint32_t address) {
429	if (m_selection.first == m_selection.second) {
430		return false;
431	}
432	if (m_selection.second <= (address | (m_align - 1))) {
433		return false;
434	}
435	if (m_selection.first <= (address & ~(m_align - 1))) {
436		return true;
437	}
438	return false;
439}
440
441bool MemoryModel::isEditing(uint32_t address) {
442	return m_bufferedNybbles && m_selection.first == (address & ~(m_align - 1));
443}
444
445void MemoryModel::drawEditingText(QPainter& painter, const QPointF& origin) {
446	QPointF o(origin);
447	for (int nybbles = m_bufferedNybbles; nybbles > 0; nybbles -= 2) {
448		if (nybbles > 1) {
449			uint8_t b = m_buffer >> ((nybbles - 2) * 4);
450			painter.drawStaticText(o, m_staticNumbers[b]);
451		} else {
452			int b = m_buffer & 0xF;
453			if (b < 10) {
454				painter.drawStaticText(o, m_staticAscii[b + '0']);
455			} else {
456				painter.drawStaticText(o, m_staticAscii[b - 10 + 'A']);
457			}
458		}
459		o += QPointF(m_letterWidth * 2, 0);
460	}
461}
462
463void MemoryModel::adjustCursor(int adjust, bool shift) {
464	if (m_selection.first >= m_selection.second) {
465		return;
466	}
467	int cursorPosition = m_top;
468	if (shift) {
469		if (m_selectionAnchor == m_selection.first) {
470			if (adjust < 0 && m_base - adjust > m_selection.second) {
471				adjust = m_base - m_selection.second + m_align;
472			} else if (adjust > 0 && m_selection.second + adjust >= m_base + m_size) {
473				adjust = m_base + m_size - m_selection.second;
474			}
475			adjust += m_selection.second;
476			if (adjust <= m_selection.first) {
477				m_selection.second = m_selection.first + m_align;
478				m_selection.first = adjust - m_align;
479				cursorPosition = m_selection.first;
480			} else {
481				m_selection.second = adjust;
482				cursorPosition = m_selection.second - m_align;
483			}
484		} else {
485			if (adjust < 0 && m_base - adjust > m_selection.first) {
486				adjust = m_base - m_selection.first;
487			} else if (adjust > 0 && m_selection.first + adjust >= m_base + m_size) {
488				adjust = m_base + m_size - m_selection.first - m_align;
489			}
490			adjust += m_selection.first;
491			if (adjust >= m_selection.second) {
492				m_selection.first = m_selection.second - m_align;
493				m_selection.second = adjust + m_align;
494				cursorPosition = adjust;
495			} else {
496				m_selection.first = adjust;
497				cursorPosition = m_selection.first;
498			}
499		}
500		cursorPosition = (cursorPosition - m_base) / 16;
501	} else {
502		if (m_selectionAnchor == m_selection.first) {
503			m_selectionAnchor = m_selection.second - m_align;
504		} else {
505			m_selectionAnchor = m_selection.first;
506		}
507		if (adjust < 0 && m_base - adjust > m_selectionAnchor) {
508			m_selectionAnchor = m_base;
509		} else if (adjust > 0 && m_selectionAnchor + adjust >= m_base + m_size) {
510			m_selectionAnchor = m_base + m_size - m_align;
511		} else {
512			m_selectionAnchor += adjust;
513		}
514		m_selection.first = m_selectionAnchor;
515		m_selection.second = m_selection.first + m_align;
516		cursorPosition = (m_selectionAnchor - m_base) / 16;
517	}
518	if (cursorPosition < m_top) {
519		m_top = cursorPosition;
520	} else if (cursorPosition >= m_top + viewport()->size().height() / m_cellHeight - 1) {
521		m_top = cursorPosition - viewport()->size().height() / m_cellHeight + 2;
522	}
523	emit selectionChanged(m_selection.first, m_selection.second);
524	viewport()->update();
525}