all repos — mgba @ 4d49aa095b000f2f869d53d7e1c0f527d6858e88

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