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