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