all repos — mgba @ 67905d281bfecbb06f51f2ca5ac939df378734a5

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