all repos — mgba @ 99944093f562f2fd39a330b2d61d14ddaa18f53e

mGBA Game Boy Advance Emulator

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	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
126BattleChipView::~BattleChipView() {
127	m_controller->detachBattleChipGate();
128}
129
130void 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
137void 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
148void 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
160void BattleChipView::addChip() {
161	int insertedChip = m_ui.chipId->value();
162	if (insertedChip < 1) {
163		return;
164	}
165	m_model.addChip(insertedChip);
166}
167
168void BattleChipView::removeChip() {
169	for (const auto& index : m_ui.chipList->selectionModel()->selectedIndexes()) {
170		m_model.removeChip(index);
171	}
172}
173
174void BattleChipView::advanceFrameCounter() {
175	if (m_frameCounter == 0) {
176		insertChip(m_next);
177	}
178	if (m_frameCounter >= 0) {
179		--m_frameCounter;
180	}
181}
182
183void 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
202void 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
234void 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
250void 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}