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