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}