all repos — mgba @ cadceecdab62c255cbd321afd48cec91fc340668

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