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}