all repos — mgba @ a2b8c4ae807ae92103e14961fd34377b28cbe219

mGBA Game Boy Advance Emulator

src/platform/qt/LoadSaveState.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 "LoadSaveState.h"
  7
  8#include "GameController.h"
  9#include "GamepadAxisEvent.h"
 10#include "GamepadButtonEvent.h"
 11#include "VFileDevice.h"
 12
 13#include <QAction>
 14#include <QDateTime>
 15#include <QKeyEvent>
 16#include <QPainter>
 17
 18#include <mgba/core/serialize.h>
 19#include <mgba-util/memory.h>
 20#include <mgba-util/vfs.h>
 21
 22using namespace QGBA;
 23
 24LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
 25	: QWidget(parent)
 26	, m_controller(controller)
 27	, m_mode(LoadSave::LOAD)
 28	, m_currentFocus(controller->stateSlot() - 1)
 29{
 30	setAttribute(Qt::WA_TranslucentBackground);
 31	m_ui.setupUi(this);
 32
 33	m_slots[0] = m_ui.state1;
 34	m_slots[1] = m_ui.state2;
 35	m_slots[2] = m_ui.state3;
 36	m_slots[3] = m_ui.state4;
 37	m_slots[4] = m_ui.state5;
 38	m_slots[5] = m_ui.state6;
 39	m_slots[6] = m_ui.state7;
 40	m_slots[7] = m_ui.state8;
 41	m_slots[8] = m_ui.state9;
 42
 43	unsigned width, height;
 44	controller->thread()->core->desiredVideoDimensions(controller->thread()->core, &width, &height);
 45	int i;
 46	for (i = 0; i < NUM_SLOTS; ++i) {
 47		loadState(i + 1);
 48		m_slots[i]->installEventFilter(this);
 49		m_slots[i]->setMaximumSize(width + 2, height + 2);
 50		connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); });
 51	}
 52
 53	if (m_currentFocus >= 9) {
 54		m_currentFocus = 0;
 55	}
 56	if (m_currentFocus < 0) {
 57		m_currentFocus = 0;
 58	}
 59
 60	QAction* escape = new QAction(this);
 61	connect(escape, &QAction::triggered, this, &QWidget::close);
 62	escape->setShortcut(QKeySequence("Esc"));
 63	escape->setShortcutContext(Qt::WidgetWithChildrenShortcut);
 64	addAction(escape);
 65}
 66
 67void LoadSaveState::setMode(LoadSave mode) {
 68	m_mode = mode;
 69	QString text = mode == LoadSave::LOAD ? tr("Load State") : tr("Save State");
 70	setWindowTitle(text);
 71	m_ui.lsLabel->setText(text);
 72}
 73
 74bool LoadSaveState::eventFilter(QObject* object, QEvent* event) {
 75	if (event->type() == QEvent::KeyPress) {
 76		int column = m_currentFocus % 3;
 77		int row = m_currentFocus / 3;
 78		switch (static_cast<QKeyEvent*>(event)->key()) {
 79		case Qt::Key_Up:
 80			row += 2;
 81			break;
 82		case Qt::Key_Down:
 83			row += 1;
 84			break;
 85		case Qt::Key_Left:
 86			column += 2;
 87			break;
 88		case Qt::Key_Right:
 89			column += 1;
 90			break;
 91		case Qt::Key_1:
 92		case Qt::Key_2:
 93		case Qt::Key_3:
 94		case Qt::Key_4:
 95		case Qt::Key_5:
 96		case Qt::Key_6:
 97		case Qt::Key_7:
 98		case Qt::Key_8:
 99		case Qt::Key_9:
100			triggerState(static_cast<QKeyEvent*>(event)->key() - Qt::Key_1 + 1);
101			break;
102		case Qt::Key_Enter:
103		case Qt::Key_Return:
104			triggerState(m_currentFocus + 1);
105			break;
106		default:
107			return false;
108		}
109		column %= 3;
110		row %= 3;
111		m_currentFocus = column + row * 3;
112		m_slots[m_currentFocus]->setFocus();
113		return true;
114	}
115	if (event->type() == QEvent::Enter) {
116		int i;
117		for (i = 0; i < 9; ++i) {
118			if (m_slots[i] == object) {
119				m_currentFocus = i;
120				m_slots[m_currentFocus]->setFocus();
121				return true;
122			}
123		}
124	}
125	if (event->type() == GamepadButtonEvent::Down() || event->type() == GamepadAxisEvent::Type()) {
126		int column = m_currentFocus % 3;
127		int row = m_currentFocus - column;
128		GBAKey key = GBA_KEY_NONE;
129		if (event->type() == GamepadButtonEvent::Down()) {
130			key = static_cast<GamepadButtonEvent*>(event)->gbaKey();
131		} else if (event->type() == GamepadAxisEvent::Type()) {
132			GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
133			if (gae->isNew()) {
134				key = gae->gbaKey();
135			} else {
136				return false;
137			}
138		}
139		switch (key) {
140		case GBA_KEY_UP:
141			row += 6;
142			break;
143		case GBA_KEY_DOWN:
144			row += 3;
145			break;
146		case GBA_KEY_LEFT:
147			column += 2;
148			break;
149		case GBA_KEY_RIGHT:
150			column += 1;
151			break;
152		case GBA_KEY_B:
153			event->accept();
154			close();
155			return true;
156		case GBA_KEY_A:
157		case GBA_KEY_START:
158			event->accept();
159			triggerState(m_currentFocus + 1);
160			return true;
161		default:
162			return false;
163		}
164		column %= 3;
165		row %= 9;
166		m_currentFocus = column + row;
167		m_slots[m_currentFocus]->setFocus();
168		event->accept();
169		return true;
170	}
171	return false;
172}
173
174void LoadSaveState::loadState(int slot) {
175	mCoreThread* thread = m_controller->thread();
176	VFile* vf = mCoreGetState(thread->core, slot, 0);
177	if (!vf) {
178		m_slots[slot - 1]->setText(tr("Empty"));
179		return;
180	}
181
182	mStateExtdata extdata;
183	mStateExtdataInit(&extdata);
184	void* state = mCoreExtractState(thread->core, vf, &extdata);
185	vf->seek(vf, 0, SEEK_SET);
186	if (!state) {
187		m_slots[slot - 1]->setText(tr("Corrupted"));
188		mStateExtdataDeinit(&extdata);
189		return;
190	}
191
192	QDateTime creation;
193	QImage stateImage;
194
195	unsigned width, height;
196	thread->core->desiredVideoDimensions(thread->core, &width, &height);
197	mStateExtdataItem item;
198	if (mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item) && item.size >= width * height * 4) {
199		stateImage = QImage((uchar*) item.data, width, height, QImage::Format_ARGB32).rgbSwapped();
200	}
201
202	if (mStateExtdataGet(&extdata, EXTDATA_META_TIME, &item) && item.size == sizeof(uint64_t)) {
203		uint64_t creationUsec;
204		LOAD_64LE(creationUsec, 0, item.data);
205		creation = QDateTime::fromMSecsSinceEpoch(creationUsec / 1000LL);
206	}
207
208	if (!stateImage.isNull()) {
209		QPixmap statePixmap;
210		statePixmap.convertFromImage(stateImage);
211		m_slots[slot - 1]->setIcon(statePixmap);
212	}
213	if (creation.toMSecsSinceEpoch()) {
214		m_slots[slot - 1]->setText(creation.toString(Qt::DefaultLocaleShortDate));
215	} else if (stateImage.isNull()) {
216		m_slots[slot - 1]->setText(tr("Slot %1").arg(slot));
217	} else {
218		m_slots[slot - 1]->setText(QString());
219	}
220	vf->close(vf);
221	mappedMemoryFree(state, thread->core->stateSize(thread->core));
222}
223
224void LoadSaveState::triggerState(int slot) {
225	if (m_mode == LoadSave::SAVE) {
226		m_controller->saveState(slot);
227	} else {
228		m_controller->loadState(slot);
229	}
230	close();
231}
232
233void LoadSaveState::closeEvent(QCloseEvent* event) {
234	emit closed();
235	QWidget::closeEvent(event);
236}
237
238void LoadSaveState::showEvent(QShowEvent* event) {
239	m_slots[m_currentFocus]->setFocus();
240	QWidget::showEvent(event);
241}
242
243void LoadSaveState::paintEvent(QPaintEvent*) {
244	QPainter painter(this);
245	QRect full(QPoint(), size());
246	painter.fillRect(full, QColor(0, 0, 0, 128));
247}