all repos — mgba @ f6755a6e1b7b0cf2b944cd8ca842746f11d6bf82

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