all repos — mgba @ 4c38f769565e8ddd7d3a8eef1a41975206c129a0

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