all repos — mgba @ 217d1b238b5d7c194a20d75cf3e48bc98d1411dd

mGBA Game Boy Advance Emulator

src/platform/qt/GBAApp.cpp (view raw)

  1/* Copyright (c) 2013-2014 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 "GBAApp.h"
  7
  8#include "AudioProcessor.h"
  9#include "CoreController.h"
 10#include "CoreManager.h"
 11#include "ConfigController.h"
 12#include "Display.h"
 13#include "LogController.h"
 14#include "VFileDevice.h"
 15#include "Window.h"
 16
 17#include <QFileInfo>
 18#include <QFileOpenEvent>
 19#include <QFontDatabase>
 20#include <QIcon>
 21
 22#include <mgba-util/socket.h>
 23#include <mgba-util/vfs.h>
 24
 25#ifdef USE_SQLITE3
 26#include "feature/sqlite3/no-intro.h"
 27#endif
 28
 29#ifdef USE_DISCORD_RPC
 30#include "DiscordCoordinator.h"
 31#endif
 32
 33using namespace QGBA;
 34
 35static GBAApp* g_app = nullptr;
 36
 37mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt");
 38
 39GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
 40	: QApplication(argc, argv)
 41	, m_configController(config)
 42	, m_monospace(QFontDatabase::systemFont(QFontDatabase::FixedFont))
 43{
 44	g_app = this;
 45
 46#ifdef BUILD_SDL
 47	SDL_Init(SDL_INIT_NOPARACHUTE);
 48#endif
 49
 50	SocketSubsystemInit();
 51	qRegisterMetaType<const uint32_t*>("const uint32_t*");
 52	qRegisterMetaType<mCoreThread*>("mCoreThread*");
 53
 54	if (!m_configController->getQtOption("displayDriver").isNull()) {
 55		Display::setDriver(static_cast<Display::Driver>(m_configController->getQtOption("displayDriver").toInt()));
 56	}
 57
 58	reloadGameDB();
 59
 60	m_manager.setConfig(m_configController->config());
 61	m_manager.setMultiplayerController(&m_multiplayer);
 62
 63	if (!m_configController->getQtOption("audioDriver").isNull()) {
 64		AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController->getQtOption("audioDriver").toInt()));
 65	}
 66
 67	LogController::global()->load(m_configController);
 68
 69#ifdef USE_DISCORD_RPC
 70	ConfigOption* useDiscordPresence = m_configController->addOption("useDiscordPresence");
 71	useDiscordPresence->addBoolean(tr("Enable Discord Rich Presence"));
 72	useDiscordPresence->connect([](const QVariant& value) {
 73		if (value.toBool()) {
 74			DiscordCoordinator::init();
 75		} else {
 76			DiscordCoordinator::deinit();
 77		}
 78	}, this);
 79	m_configController->updateOption("useDiscordPresence");
 80#endif
 81
 82	connect(this, &GBAApp::aboutToQuit, this, &GBAApp::cleanup);
 83}
 84
 85void GBAApp::cleanup() {
 86	m_workerThreads.waitForDone();
 87
 88	while (!m_workerJobs.isEmpty()) {
 89		finishJob(m_workerJobs.firstKey());
 90	}
 91
 92#ifdef USE_SQLITE3
 93	if (m_db) {
 94		NoIntroDBDestroy(m_db);
 95	}
 96#endif
 97
 98#ifdef USE_DISCORD_RPC
 99	DiscordCoordinator::deinit();
100#endif
101}
102
103bool GBAApp::event(QEvent* event) {
104	if (event->type() == QEvent::FileOpen) {
105		CoreController* core = m_manager.loadGame(static_cast<QFileOpenEvent*>(event)->file());
106		m_windows[0]->setController(core, static_cast<QFileOpenEvent*>(event)->file());
107		return true;
108	}
109	return QApplication::event(event);
110}
111
112Window* GBAApp::newWindow() {
113	if (m_windows.count() >= MAX_GBAS) {
114		return nullptr;
115	}
116	Window* w = new Window(&m_manager, m_configController, m_multiplayer.attached());
117	connect(w, &Window::destroyed, [this, w]() {
118		m_windows.removeAll(w);
119		for (Window* w : m_windows) {
120			w->updateMultiplayerStatus(m_windows.count() < MAX_GBAS);
121		}
122	});
123	m_windows.append(w);
124	w->setAttribute(Qt::WA_DeleteOnClose);
125	w->loadConfig();
126	w->show();
127	w->multiplayerChanged();
128	for (Window* w : m_windows) {
129		w->updateMultiplayerStatus(m_windows.count() < MAX_GBAS);
130	}
131	return w;
132}
133
134GBAApp* GBAApp::app() {
135	return g_app;
136}
137
138void GBAApp::pauseAll(QList<Window*>* paused) {
139	for (auto& window : m_windows) {
140		if (!window->controller() || window->controller()->isPaused()) {
141			continue;
142		}
143		window->controller()->setPaused(true);
144		paused->append(window);
145	}
146}
147
148void GBAApp::continueAll(const QList<Window*>& paused) {
149	for (auto& window : paused) {
150		if (window->controller()) {
151			window->controller()->setPaused(false);
152		}
153	}
154}
155
156QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) {
157	QList<Window*> paused;
158	pauseAll(&paused);
159	QString filename = QFileDialog::getOpenFileName(owner, title, m_configController->getOption("lastDirectory"), filter);
160	continueAll(paused);
161	if (!filename.isEmpty()) {
162		m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
163	}
164	return filename;
165}
166
167QStringList GBAApp::getOpenFileNames(QWidget* owner, const QString& title, const QString& filter) {
168	QList<Window*> paused;
169	pauseAll(&paused);
170	QStringList filenames = QFileDialog::getOpenFileNames(owner, title, m_configController->getOption("lastDirectory"), filter);
171	continueAll(paused);
172	if (!filenames.isEmpty()) {
173		m_configController->setOption("lastDirectory", QFileInfo(filenames.at(0)).dir().canonicalPath());
174	}
175	return filenames;
176}
177
178QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter) {
179	QList<Window*> paused;
180	pauseAll(&paused);
181	QString filename = QFileDialog::getSaveFileName(owner, title, m_configController->getOption("lastDirectory"), filter);
182	continueAll(paused);
183	if (!filename.isEmpty()) {
184		m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
185	}
186	return filename;
187}
188
189QString GBAApp::getOpenDirectoryName(QWidget* owner, const QString& title, const QString& path) {
190	QList<Window*> paused;
191	pauseAll(&paused);
192	QString filename = QFileDialog::getExistingDirectory(owner, title, !path.isNull() ? path : m_configController->getOption("lastDirectory"));
193	continueAll(paused);
194	if (path.isNull() && !filename.isEmpty()) {
195		m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
196	}
197	return filename;
198}
199
200QString GBAApp::dataDir() {
201#ifdef DATADIR
202	QString path = QString::fromUtf8(DATADIR);
203#else
204	QString path = QCoreApplication::applicationDirPath();
205#ifdef Q_OS_MAC
206	path += QLatin1String("/../Resources");
207#endif
208#endif
209	return path;
210}
211
212#ifdef USE_SQLITE3
213bool GBAApp::reloadGameDB() {
214	NoIntroDB* db = nullptr;
215	db = NoIntroDBLoad((ConfigController::configDir() + "/nointro.sqlite3").toUtf8().constData());
216	if (db && m_db) {
217		NoIntroDBDestroy(m_db);
218	}
219	if (db) {
220		std::shared_ptr<GameDBParser> parser = std::make_shared<GameDBParser>(db);
221		submitWorkerJob(std::bind(&GameDBParser::parseNoIntroDB, parser));
222		m_db = db;
223		return true;
224	}
225	return false;
226}
227#else
228bool GBAApp::reloadGameDB() {
229	return false;
230}
231#endif
232
233qint64 GBAApp::submitWorkerJob(std::function<void ()> job, std::function<void ()> callback) {
234	return submitWorkerJob(job, nullptr, callback);
235}
236
237qint64 GBAApp::submitWorkerJob(std::function<void ()> job, QObject* context, std::function<void ()> callback) {
238	qint64 jobId = m_nextJob;
239	++m_nextJob;
240	WorkerJob* jobRunnable = new WorkerJob(jobId, job, this);
241	m_workerJobs.insert(jobId, jobRunnable);
242	if (callback) {
243		waitOnJob(jobId, context, callback);
244	}
245	m_workerThreads.start(jobRunnable);
246	return jobId;
247}
248
249bool GBAApp::removeWorkerJob(qint64 jobId) {
250	for (auto& job : m_workerJobCallbacks.values(jobId)) {
251		disconnect(job);
252	}
253	m_workerJobCallbacks.remove(jobId);
254	if (!m_workerJobs.contains(jobId)) {
255		return true;
256	}
257	bool success = false;
258#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
259	success = m_workerThreads.tryTake(m_workerJobs[jobId]);
260#endif
261	if (success) {
262		m_workerJobs.remove(jobId);
263	}
264	return success;
265}
266
267
268bool GBAApp::waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback) {
269	if (!m_workerJobs.contains(jobId)) {
270		return false;
271	}
272	if (!context) {
273		context = this;
274	}
275	QMetaObject::Connection connection = connect(this, &GBAApp::jobFinished, context, [jobId, callback](qint64 testedJobId) {
276		if (jobId != testedJobId) {
277			return;
278		}
279		callback();
280	});
281	m_workerJobCallbacks.insert(m_nextJob, connection);
282	return true;
283}
284
285void GBAApp::finishJob(qint64 jobId) {
286	m_workerJobs.remove(jobId);
287	emit jobFinished(jobId);
288	m_workerJobCallbacks.remove(jobId);
289}
290
291GBAApp::WorkerJob::WorkerJob(qint64 id, std::function<void ()> job, GBAApp* owner)
292	: m_id(id)
293	, m_job(job)
294	, m_owner(owner)
295{
296	setAutoDelete(true);
297}
298
299void GBAApp::WorkerJob::run() {
300	m_job();
301	QMetaObject::invokeMethod(m_owner, "finishJob", Q_ARG(qint64, m_id));
302}
303
304#ifdef USE_SQLITE3
305GameDBParser::GameDBParser(NoIntroDB* db, QObject* parent)
306	: QObject(parent)
307	, m_db(db)
308{
309	// Nothing to do
310}
311
312void GameDBParser::parseNoIntroDB() {
313	VFile* vf = VFileDevice::open(GBAApp::dataDir() + "/nointro.dat", O_RDONLY);
314	if (vf) {
315		NoIntroDBLoadClrMamePro(m_db, vf);
316		vf->close(vf);
317	}
318}
319
320#endif