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 "BattleChipUpdater.h"
9 #include "ConfigController.h"
10 #include "CoreController.h"
11 #include "GBAApp.h"
12 #include "ShortcutController.h"
13 #include "Window.h"
14
15 #include <QtAlgorithms>
16 #include <QFileInfo>
17 #include <QFontMetrics>
18 #include <QMessageBox>
19 #include <QMultiMap>
20 #include <QSettings>
21 #include <QStringList>
22
23 using namespace QGBA;
24
BattleChipView(std::shared_ptr<CoreController> controller,Window * window,QWidget * parent)25 BattleChipView::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 m_ui.setupUi(this);
31 m_ui.chipList->setModel(&m_model);
32
33 char title[9];
34 CoreController::Interrupter interrupter(m_controller);
35 mCore* core = m_controller->thread()->core;
36 title[8] = '\0';
37 core->getGameCode(core, title);
38 QString qtitle(title);
39
40 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
41 int size = QFontMetrics(QFont()).height() / ((int) ceil(devicePixelRatioF()) * 12);
42 #else
43 int size = QFontMetrics(QFont()).height() / (devicePixelRatio() * 12);
44 #endif
45 if (!size) {
46 size = 1;
47 }
48 m_ui.chipList->setIconSize(m_ui.chipList->iconSize() * size);
49 m_ui.chipList->setGridSize(m_ui.chipList->gridSize() * size);
50 m_model.setScale(size);
51
52 connect(m_ui.chipId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), m_ui.inserted, [this]() {
53 m_ui.inserted->setChecked(Qt::Unchecked);
54 });
55 connect(m_ui.chipName, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), m_ui.chipId, [this](int id) {
56 if (id < 0) {
57 return;
58 }
59 m_ui.chipId->setValue(m_model.chipNames().keys()[id]);
60 });
61
62 connect(m_ui.inserted, &QAbstractButton::toggled, this, &BattleChipView::insertChip);
63 connect(m_ui.insert, &QAbstractButton::clicked, this, &BattleChipView::reinsert);
64 connect(m_ui.add, &QAbstractButton::clicked, this, &BattleChipView::addChip);
65 connect(m_ui.remove, &QAbstractButton::clicked, this, &BattleChipView::removeChip);
66 connect(controller.get(), &CoreController::stopping, this, &QWidget::close);
67 connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck);
68 connect(m_ui.load, &QAbstractButton::clicked, this, &BattleChipView::loadDeck);
69 connect(m_ui.updateData, &QAbstractButton::clicked, this, &BattleChipView::updateData);
70 connect(m_ui.buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, &m_model, &BattleChipModel::clear);
71
72 connect(m_ui.gateBattleChip, &QAbstractButton::toggled, this, [this](bool on) {
73 if (on) {
74 setFlavor(GBA_FLAVOR_BATTLECHIP_GATE);
75 }
76 });
77 connect(m_ui.gateProgress, &QAbstractButton::toggled, this, [this](bool on) {
78 if (on) {
79 setFlavor(GBA_FLAVOR_PROGRESS_GATE);
80 }
81 });
82 connect(m_ui.gateBeastLink, &QAbstractButton::toggled, this, [this, qtitle](bool on) {
83 if (on) {
84 if (qtitle.endsWith('E') || qtitle.endsWith('P')) {
85 setFlavor(GBA_FLAVOR_BEAST_LINK_GATE_US);
86 } else {
87 setFlavor(GBA_FLAVOR_BEAST_LINK_GATE);
88 }
89 }
90 });
91
92 connect(m_controller.get(), &CoreController::frameAvailable, this, &BattleChipView::advanceFrameCounter);
93
94 connect(m_ui.chipList, &QAbstractItemView::clicked, this, [this](const QModelIndex& index) {
95 QVariant chip = m_model.data(index, Qt::UserRole);
96 bool blocked = m_ui.chipId->blockSignals(true);
97 m_ui.chipId->setValue(chip.toInt());
98 m_ui.chipId->blockSignals(blocked);
99 reinsert();
100 });
101 connect(m_ui.chipList, &QListView::indexesMoved, this, &BattleChipView::resort);
102
103 m_controller->attachBattleChipGate();
104 setFlavor(4);
105 if (qtitle.startsWith("AGB-B4B") || qtitle.startsWith("AGB-B4W") || qtitle.startsWith("AGB-BR4") || qtitle.startsWith("AGB-BZ3")) {
106 m_ui.gateBattleChip->setChecked(Qt::Checked);
107 } else if (qtitle.startsWith("AGB-BRB") || qtitle.startsWith("AGB-BRK")) {
108 m_ui.gateProgress->setChecked(Qt::Checked);
109 } else if (qtitle.startsWith("AGB-BR5") || qtitle.startsWith("AGB-BR6")) {
110 m_ui.gateBeastLink->setChecked(Qt::Checked);
111 }
112
113 if (!QFileInfo(GBAApp::dataDir() + "/chips.rcc").exists() && !QFileInfo(ConfigController::configDir() + "/chips.rcc").exists()) {
114 QMessageBox* download = new QMessageBox(this);
115 download->setIcon(QMessageBox::Information);
116 download->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
117 download->setWindowTitle(tr("BattleChip data missing"));
118 download->setText(tr("BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now?"));
119 download->setAttribute(Qt::WA_DeleteOnClose);
120 download->setWindowModality(Qt::NonModal);
121 connect(download, &QDialog::accepted, this, &BattleChipView::updateData);
122 download->show();
123 }
124 }
125
~BattleChipView()126 BattleChipView::~BattleChipView() {
127 m_controller->detachBattleChipGate();
128 }
129
setFlavor(int flavor)130 void BattleChipView::setFlavor(int flavor) {
131 m_controller->setBattleChipFlavor(flavor);
132 m_model.setFlavor(flavor);
133 m_ui.chipName->clear();
134 m_ui.chipName->addItems(m_model.chipNames().values());
135 }
136
insertChip(bool inserted)137 void BattleChipView::insertChip(bool inserted) {
138 bool blocked = m_ui.inserted->blockSignals(true);
139 m_ui.inserted->setChecked(inserted);
140 m_ui.inserted->blockSignals(blocked);
141 if (inserted) {
142 m_controller->setBattleChipId(m_ui.chipId->value());
143 } else {
144 m_controller->setBattleChipId(0);
145 }
146 }
147
reinsert()148 void BattleChipView::reinsert() {
149 if (m_ui.inserted->isChecked()) {
150 insertChip(false);
151 m_next = true;
152 m_frameCounter = UNINSERTED_TIME;
153 } else {
154 insertChip(true);
155 }
156 m_window->setWindowState(m_window->windowState() & ~Qt::WindowActive);
157 m_window->setWindowState(m_window->windowState() | Qt::WindowActive);
158 }
159
addChip()160 void BattleChipView::addChip() {
161 int insertedChip = m_ui.chipId->value();
162 if (insertedChip < 1) {
163 return;
164 }
165 m_model.addChip(insertedChip);
166 }
167
removeChip()168 void BattleChipView::removeChip() {
169 for (const auto& index : m_ui.chipList->selectionModel()->selectedIndexes()) {
170 m_model.removeChip(index);
171 }
172 }
173
advanceFrameCounter()174 void BattleChipView::advanceFrameCounter() {
175 if (m_frameCounter == 0) {
176 insertChip(m_next);
177 }
178 if (m_frameCounter >= 0) {
179 --m_frameCounter;
180 }
181 }
182
saveDeck()183 void BattleChipView::saveDeck() {
184 QString filename = GBAApp::app()->getSaveFileName(this, tr("Select deck file"), tr(("BattleChip deck file (*.deck)")));
185 if (filename.isEmpty()) {
186 return;
187 }
188
189 QStringList deck;
190 for (int i = 0; i < m_model.rowCount(); ++i) {
191 deck.append(m_model.data(m_model.index(i, 0), Qt::UserRole).toString());
192 }
193
194 QSettings ini(filename, QSettings::IniFormat);
195 ini.clear();
196 ini.beginGroup("BattleChipDeck");
197 ini.setValue("version", m_model.flavor());
198 ini.setValue("deck", deck.join(','));
199 ini.sync();
200 }
201
loadDeck()202 void BattleChipView::loadDeck() {
203 QString filename = GBAApp::app()->getOpenFileName(this, tr("Select deck file"), tr(("BattleChip deck file (*.deck)")));
204 if (filename.isEmpty()) {
205 return;
206 }
207
208 QSettings ini(filename, QSettings::IniFormat);
209 ini.beginGroup("BattleChipDeck");
210 int flavor = ini.value("version").toInt();
211 if (flavor != m_model.flavor()) {
212 QMessageBox* error = new QMessageBox(this);
213 error->setIcon(QMessageBox::Warning);
214 error->setStandardButtons(QMessageBox::Ok);
215 error->setWindowTitle(tr("Incompatible deck"));
216 error->setText(tr("The selected deck is not compatible with this Chip Gate"));
217 error->setAttribute(Qt::WA_DeleteOnClose);
218 error->show();
219 return;
220 }
221
222 QList<int> newDeck;
223 QStringList deck = ini.value("deck").toString().split(',');
224 for (const auto& item : deck) {
225 bool ok;
226 int id = item.toInt(&ok);
227 if (ok) {
228 newDeck.append(id);
229 }
230 }
231 m_model.setChips(newDeck);
232 }
233
resort()234 void BattleChipView::resort() {
235 QMap<int, int> chips;
236 for (int i = 0; i < m_model.rowCount(); ++i) {
237 QModelIndex index = m_model.index(i, 0);
238 QRect visualRect = m_ui.chipList->visualRect(index);
239 QSize gridSize = m_ui.chipList->gridSize();
240 int x = visualRect.y() / gridSize.height();
241 x *= m_ui.chipList->viewport()->width();
242 x += visualRect.x();
243 x *= m_model.rowCount();
244 x += index.row();
245 chips[x] = m_model.data(index, Qt::UserRole).toInt();
246 }
247 m_model.setChips(chips.values());
248 }
249
updateData()250 void BattleChipView::updateData() {
251 if (m_updater) {
252 return;
253 }
254 m_updater = new BattleChipUpdater(this);
255 connect(m_updater, &BattleChipUpdater::updateDone, this, [this](bool success) {
256 if (success) {
257 m_model.reloadAssets();
258 m_ui.chipName->clear();
259 m_ui.chipName->addItems(m_model.chipNames().values());
260 }
261 delete m_updater;
262 m_updater = nullptr;
263 });
264 m_updater->downloadUpdate();
265 }
266