all repos — mgba @ 60577e83948647d36a2e6a8b4ec8f8556df3f72f

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