all repos — mgba @ f6755a6e1b7b0cf2b944cd8ca842746f11d6bf82

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	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}