all repos — mgba @ f836a67863e66499c812c8d963d37ff56ad10638

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