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 <QDateTime>
14#include <QKeyEvent>
15#include <QPainter>
16
17#include <mgba/core/serialize.h>
18#include <mgba-util/memory.h>
19#include <mgba-util/vfs.h>
20
21using namespace QGBA;
22
23LoadSaveState::LoadSaveState(std::shared_ptr<CoreController> controller, QWidget* parent)
24 : QWidget(parent)
25 , m_controller(controller)
26 , m_mode(LoadSave::LOAD)
27 , m_currentFocus(controller->stateSlot() - 1)
28{
29 setAttribute(Qt::WA_TranslucentBackground);
30 m_ui.setupUi(this);
31
32 m_slots[0] = m_ui.state1;
33 m_slots[1] = m_ui.state2;
34 m_slots[2] = m_ui.state3;
35 m_slots[3] = m_ui.state4;
36 m_slots[4] = m_ui.state5;
37 m_slots[5] = m_ui.state6;
38 m_slots[6] = m_ui.state7;
39 m_slots[7] = m_ui.state8;
40 m_slots[8] = m_ui.state9;
41
42 unsigned width, height;
43 controller->thread()->core->desiredVideoDimensions(controller->thread()->core, &width, &height);
44 int i;
45 for (i = 0; i < NUM_SLOTS; ++i) {
46 loadState(i + 1);
47 m_slots[i]->installEventFilter(this);
48 m_slots[i]->setMaximumSize(width + 2, height + 2);
49 connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); });
50 }
51
52 if (m_currentFocus >= 9) {
53 m_currentFocus = 0;
54 }
55 if (m_currentFocus < 0) {
56 m_currentFocus = 0;
57 }
58
59 QAction* escape = new QAction(this);
60 connect(escape, &QAction::triggered, this, &QWidget::close);
61 escape->setShortcut(QKeySequence("Esc"));
62 escape->setShortcutContext(Qt::WidgetWithChildrenShortcut);
63 addAction(escape);
64
65 connect(m_controller.get(), &CoreController::stopping, this, &QWidget::close);
66}
67
68void LoadSaveState::setMode(LoadSave mode) {
69 m_mode = mode;
70 QString text = mode == LoadSave::LOAD ? tr("Load State") : tr("Save State");
71 setWindowTitle(text);
72 m_ui.lsLabel->setText(text);
73}
74
75bool LoadSaveState::eventFilter(QObject* object, QEvent* event) {
76 if (event->type() == QEvent::KeyPress) {
77 int column = m_currentFocus % 3;
78 int row = m_currentFocus / 3;
79 switch (static_cast<QKeyEvent*>(event)->key()) {
80 case Qt::Key_Up:
81 row += 2;
82 break;
83 case Qt::Key_Down:
84 row += 1;
85 break;
86 case Qt::Key_Left:
87 column += 2;
88 break;
89 case Qt::Key_Right:
90 column += 1;
91 break;
92 case Qt::Key_1:
93 case Qt::Key_2:
94 case Qt::Key_3:
95 case Qt::Key_4:
96 case Qt::Key_5:
97 case Qt::Key_6:
98 case Qt::Key_7:
99 case Qt::Key_8:
100 case Qt::Key_9:
101 triggerState(static_cast<QKeyEvent*>(event)->key() - Qt::Key_1 + 1);
102 break;
103 case Qt::Key_Enter:
104 case Qt::Key_Return:
105 triggerState(m_currentFocus + 1);
106 break;
107 default:
108 return false;
109 }
110 column %= 3;
111 row %= 3;
112 m_currentFocus = column + row * 3;
113 m_slots[m_currentFocus]->setFocus();
114 return true;
115 }
116 if (event->type() == QEvent::Enter) {
117 int i;
118 for (i = 0; i < 9; ++i) {
119 if (m_slots[i] == object) {
120 m_currentFocus = i;
121 m_slots[m_currentFocus]->setFocus();
122 return true;
123 }
124 }
125 }
126 if (event->type() == GamepadButtonEvent::Down() || event->type() == GamepadAxisEvent::Type()) {
127 int column = m_currentFocus % 3;
128 int row = m_currentFocus - column;
129 GBAKey key = GBA_KEY_NONE;
130 if (event->type() == GamepadButtonEvent::Down()) {
131 key = static_cast<GamepadButtonEvent*>(event)->gbaKey();
132 } else if (event->type() == GamepadAxisEvent::Type()) {
133 GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);
134 if (gae->isNew()) {
135 key = gae->gbaKey();
136 } else {
137 return false;
138 }
139 }
140 switch (key) {
141 case GBA_KEY_UP:
142 row += 6;
143 break;
144 case GBA_KEY_DOWN:
145 row += 3;
146 break;
147 case GBA_KEY_LEFT:
148 column += 2;
149 break;
150 case GBA_KEY_RIGHT:
151 column += 1;
152 break;
153 case GBA_KEY_B:
154 event->accept();
155 close();
156 return true;
157 case GBA_KEY_A:
158 case GBA_KEY_START:
159 event->accept();
160 triggerState(m_currentFocus + 1);
161 return true;
162 default:
163 return false;
164 }
165 column %= 3;
166 row %= 9;
167 m_currentFocus = column + row;
168 m_slots[m_currentFocus]->setFocus();
169 event->accept();
170 return true;
171 }
172 return false;
173}
174
175void LoadSaveState::loadState(int slot) {
176 mCoreThread* thread = m_controller->thread();
177 VFile* vf = mCoreGetState(thread->core, slot, 0);
178 if (!vf) {
179 m_slots[slot - 1]->setText(tr("Empty"));
180 return;
181 }
182
183 mStateExtdata extdata;
184 mStateExtdataInit(&extdata);
185 void* state = mCoreExtractState(thread->core, vf, &extdata);
186 vf->seek(vf, 0, SEEK_SET);
187 if (!state) {
188 m_slots[slot - 1]->setText(tr("Corrupted"));
189 mStateExtdataDeinit(&extdata);
190 return;
191 }
192
193 QDateTime creation;
194 QImage stateImage;
195
196 unsigned width, height;
197 thread->core->desiredVideoDimensions(thread->core, &width, &height);
198 mStateExtdataItem item;
199 if (mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item) && item.size >= width * height * 4) {
200 stateImage = QImage((uchar*) item.data, width, height, QImage::Format_ARGB32).rgbSwapped();
201 }
202
203 if (mStateExtdataGet(&extdata, EXTDATA_META_TIME, &item) && item.size == sizeof(uint64_t)) {
204 uint64_t creationUsec;
205 LOAD_64LE(creationUsec, 0, item.data);
206 creation = QDateTime::fromMSecsSinceEpoch(creationUsec / 1000LL);
207 }
208
209 if (!stateImage.isNull()) {
210 QPixmap statePixmap;
211 statePixmap.convertFromImage(stateImage);
212 m_slots[slot - 1]->setIcon(statePixmap);
213 }
214 if (creation.toMSecsSinceEpoch()) {
215 m_slots[slot - 1]->setText(creation.toString(Qt::DefaultLocaleShortDate));
216 } else if (stateImage.isNull()) {
217 m_slots[slot - 1]->setText(tr("Slot %1").arg(slot));
218 } else {
219 m_slots[slot - 1]->setText(QString());
220 }
221 vf->close(vf);
222 mappedMemoryFree(state, thread->core->stateSize(thread->core));
223}
224
225void LoadSaveState::triggerState(int slot) {
226 if (m_mode == LoadSave::SAVE) {
227 m_controller->saveState(slot);
228 } else {
229 m_controller->loadState(slot);
230 }
231 close();
232}
233
234void LoadSaveState::closeEvent(QCloseEvent* event) {
235 emit closed();
236 QWidget::closeEvent(event);
237}
238
239void LoadSaveState::showEvent(QShowEvent* event) {
240 m_slots[m_currentFocus]->setFocus();
241 QWidget::showEvent(event);
242}
243
244void LoadSaveState::paintEvent(QPaintEvent*) {
245 QPainter painter(this);
246 QRect full(QPoint(), size());
247 painter.fillRect(full, QColor(0, 0, 0, 128));
248}