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