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 
22 using namespace QGBA;
23 
LoadSaveState(std::shared_ptr<CoreController> controller,QWidget * parent)24 LoadSaveState::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 
setMode(LoadSave mode)72 void 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 
eventFilter(QObject * object,QEvent * event)79 bool 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 
loadState(int slot)179 void 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 >= static_cast<int32_t>(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 
triggerState(int slot)229 void 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 
closeEvent(QCloseEvent * event)238 void LoadSaveState::closeEvent(QCloseEvent* event) {
239 	emit closed();
240 	QWidget::closeEvent(event);
241 }
242 
showEvent(QShowEvent * event)243 void LoadSaveState::showEvent(QShowEvent* event) {
244 	m_slots[m_currentFocus]->setFocus();
245 	QWidget::showEvent(event);
246 }
247 
focusInEvent(QFocusEvent *)248 void LoadSaveState::focusInEvent(QFocusEvent*) {
249 	m_slots[m_currentFocus]->setFocus();
250 }
251 
paintEvent(QPaintEvent *)252 void LoadSaveState::paintEvent(QPaintEvent*) {
253 	QPainter painter(this);
254 	QRect full(QPoint(), size());
255 	painter.fillRect(full, QColor(0, 0, 0, 128));
256 }
257