src/platform/qt/BattleChipView.cpp (view raw)
1/* Copyright (c) 2013-2019 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 "BattleChipView.h"
7
8#include "ConfigController.h"
9#include "CoreController.h"
10#include "GBAApp.h"
11#include "ShortcutController.h"
12#include "Window.h"
13
14#include <QtAlgorithms>
15#include <QFile>
16#include <QFontMetrics>
17#include <QMessageBox>
18#include <QMultiMap>
19#include <QResource>
20#include <QSettings>
21#include <QStringList>
22
23using namespace QGBA;
24
25BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Window* window, QWidget* parent)
26 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
27 , m_controller(controller)
28 , m_window(window)
29{
30 QResource::registerResource(GBAApp::dataDir() + "/chips.rcc", "/exe");
31 QResource::registerResource(ConfigController::configDir() + "/chips.rcc", "/exe");
32
33 m_ui.setupUi(this);
34
35 char title[9];
36 CoreController::Interrupter interrupter(m_controller);
37 mCore* core = m_controller->thread()->core;
38 title[8] = '\0';
39 core->getGameCode(core, title);
40 QString qtitle(title);
41
42#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
43 int size = QFontMetrics(QFont()).height() / ((int) ceil(devicePixelRatioF()) * 12);
44#else
45 int size = QFontMetrics(QFont()).height() / (devicePixelRatio() * 12);
46#endif
47 m_ui.chipList->setIconSize(m_ui.chipList->iconSize() * size);
48 m_ui.chipList->setGridSize(m_ui.chipList->gridSize() * size);
49
50 connect(m_ui.chipId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), m_ui.inserted, [this]() {
51 m_ui.inserted->setChecked(Qt::Unchecked);
52 });
53 connect(m_ui.chipName, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), m_ui.chipId, [this](int id) {
54 m_ui.chipId->setValue(m_chipIndexToId[id]);
55 });
56
57 connect(m_ui.inserted, &QAbstractButton::toggled, this, &BattleChipView::insertChip);
58 connect(m_ui.insert, &QAbstractButton::clicked, this, &BattleChipView::reinsert);
59 connect(m_ui.add, &QAbstractButton::clicked, this, &BattleChipView::addChip);
60 connect(m_ui.remove, &QAbstractButton::clicked, this, &BattleChipView::removeChip);
61 connect(controller.get(), &CoreController::stopping, this, &QWidget::close);
62 connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck);
63 connect(m_ui.load, &QAbstractButton::clicked, this, &BattleChipView::loadDeck);
64 connect(m_ui.buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, m_ui.chipList, &QListWidget::clear);
65
66 connect(m_ui.gateBattleChip, &QAbstractButton::toggled, this, [this](bool on) {
67 if (on) {
68 setFlavor(GBA_FLAVOR_BATTLECHIP_GATE);
69 }
70 });
71 connect(m_ui.gateProgress, &QAbstractButton::toggled, this, [this](bool on) {
72 if (on) {
73 setFlavor(GBA_FLAVOR_PROGRESS_GATE);
74 }
75 });
76 connect(m_ui.gateBeastLink, &QAbstractButton::toggled, this, [this, qtitle](bool on) {
77 if (on) {
78 if (qtitle.endsWith('E') || qtitle.endsWith('P')) {
79 setFlavor(GBA_FLAVOR_BEAST_LINK_GATE_US);
80 } else {
81 setFlavor(GBA_FLAVOR_BEAST_LINK_GATE);
82 }
83 }
84 });
85
86 connect(m_controller.get(), &CoreController::frameAvailable, this, &BattleChipView::advanceFrameCounter);
87
88 connect(m_ui.chipList, &QListWidget::itemClicked, this, [this](QListWidgetItem* item) {
89 QVariant chip = item->data(Qt::UserRole);
90 bool blocked = m_ui.chipId->blockSignals(true);
91 m_ui.chipId->setValue(chip.toInt());
92 m_ui.chipId->blockSignals(blocked);
93 reinsert();
94 });
95
96 m_controller->attachBattleChipGate();
97 setFlavor(4);
98 if (qtitle.startsWith("AGB-B4B") || qtitle.startsWith("AGB-B4W") || qtitle.startsWith("AGB-BR4") || qtitle.startsWith("AGB-BZ3")) {
99 m_ui.gateBattleChip->setChecked(Qt::Checked);
100 } else if (qtitle.startsWith("AGB-BRB") || qtitle.startsWith("AGB-BRK")) {
101 m_ui.gateProgress->setChecked(Qt::Checked);
102 } else if (qtitle.startsWith("AGB-BR5") || qtitle.startsWith("AGB-BR6")) {
103 m_ui.gateBeastLink->setChecked(Qt::Checked);
104 }
105}
106
107BattleChipView::~BattleChipView() {
108 m_controller->detachBattleChipGate();
109}
110
111void BattleChipView::setFlavor(int flavor) {
112 m_controller->setBattleChipFlavor(flavor);
113 loadChipNames(flavor);
114}
115
116void BattleChipView::insertChip(bool inserted) {
117 bool blocked = m_ui.inserted->blockSignals(true);
118 m_ui.inserted->setChecked(inserted);
119 m_ui.inserted->blockSignals(blocked);
120 if (inserted) {
121 m_controller->setBattleChipId(m_ui.chipId->value());
122 } else {
123 m_controller->setBattleChipId(0);
124 }
125}
126
127void BattleChipView::reinsert() {
128 if (m_ui.inserted->isChecked()) {
129 insertChip(false);
130 m_next = true;
131 m_frameCounter = UNINSERTED_TIME;
132 } else {
133 insertChip(true);
134 }
135 m_window->setWindowState(m_window->windowState() & ~Qt::WindowActive);
136 m_window->setWindowState(m_window->windowState() | Qt::WindowActive);
137}
138
139void BattleChipView::addChip() {
140 int insertedChip = m_ui.chipId->value();
141 if (insertedChip < 1) {
142 return;
143 }
144 addChipId(insertedChip);
145}
146
147void BattleChipView::addChipId(int id) {
148 QListWidgetItem* add = new QListWidgetItem(m_chipIdToName[id]);
149 add->setData(Qt::UserRole, id);
150 QString path = QString(":/exe/exe%1/%2.png").arg(m_flavor).arg(id, 3, 10, QLatin1Char('0'));
151 if (!QFile(path).exists()) {
152 path = QString(":/exe/exe%1/placeholder.png").arg(m_flavor);
153 }
154 add->setIcon(QPixmap(path).scaled(m_ui.chipList->iconSize()));
155 m_ui.chipList->addItem(add);
156}
157
158void BattleChipView::removeChip() {
159 qDeleteAll(m_ui.chipList->selectedItems());
160}
161
162void BattleChipView::loadChipNames(int flavor) {
163 QStringList chipNames;
164 chipNames.append(tr("(None)"));
165
166 m_chipIndexToId.clear();
167 m_chipIdToName.clear();
168 if (flavor == GBA_FLAVOR_BEAST_LINK_GATE_US) {
169 flavor = GBA_FLAVOR_BEAST_LINK_GATE;
170 }
171 m_flavor = flavor;
172
173 QFile file(QString(":/exe/exe%1/chip-names.txt").arg(flavor));
174 file.open(QIODevice::ReadOnly | QIODevice::Text);
175 int id = 0;
176 while (true) {
177 QByteArray line = file.readLine();
178 if (line.isEmpty()) {
179 break;
180 }
181 ++id;
182 if (line.trimmed().isEmpty()) {
183 continue;
184 }
185 QString name = QString::fromUtf8(line).trimmed();
186 m_chipIndexToId[chipNames.length()] = id;
187 m_chipIdToName[id] = name;
188 chipNames.append(name);
189 }
190
191 m_ui.chipName->clear();
192 m_ui.chipName->addItems(chipNames);
193}
194
195void BattleChipView::advanceFrameCounter() {
196 if (m_frameCounter == 0) {
197 insertChip(m_next);
198 }
199 if (m_frameCounter >= 0) {
200 --m_frameCounter;
201 }
202}
203
204void BattleChipView::saveDeck() {
205 QString filename = GBAApp::app()->getSaveFileName(this, tr("Select deck file"), tr(("BattleChip deck file (*.deck)")));
206 if (filename.isEmpty()) {
207 return;
208 }
209
210 QStringList deck;
211 for (int i = 0; i < m_ui.chipList->count(); ++i) {
212 deck.append(m_ui.chipList->item(i)->data(Qt::UserRole).toString());
213 }
214
215 QSettings ini(filename, QSettings::IniFormat);
216 ini.clear();
217 ini.beginGroup("BattleChipDeck");
218 ini.setValue("version", m_flavor);
219 ini.setValue("deck", deck.join(','));
220 ini.sync();
221}
222
223void BattleChipView::loadDeck() {
224 QString filename = GBAApp::app()->getOpenFileName(this, tr("Select deck file"), tr(("BattleChip deck file (*.deck)")));
225 if (filename.isEmpty()) {
226 return;
227 }
228
229 QSettings ini(filename, QSettings::IniFormat);
230 ini.beginGroup("BattleChipDeck");
231 int flavor = ini.value("version").toInt();
232 if (flavor != m_flavor) {
233 QMessageBox* error = new QMessageBox(this);
234 error->setIcon(QMessageBox::Warning);
235 error->setStandardButtons(QMessageBox::Ok);
236 error->setWindowTitle(tr("Incompatible deck"));
237 error->setText(tr("The selected deck is not compatible with this Chip Gate"));
238 error->setAttribute(Qt::WA_DeleteOnClose);
239 error->show();
240 return;
241 }
242
243 m_ui.chipList->clear();
244 QStringList deck = ini.value("deck").toString().split(',');
245 for (const auto& item : deck) {
246 bool ok;
247 int id = item.toInt(&ok);
248 if (ok) {
249 addChipId(id);
250 }
251 }
252}