all repos — mgba @ 6f7573dda4eea59cd2ea8291890476861281ddaa

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 <QIcon>
 20
 21#include <mgba/core/version.h>
 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
 29using namespace QGBA;
 30
 31static GBAApp* g_app = nullptr;
 32
 33mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt");
 34
 35GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
 36	: QApplication(argc, argv)
 37	, m_configController(config)
 38{
 39	g_app = this;
 40
 41#ifdef BUILD_SDL
 42	SDL_Init(SDL_INIT_NOPARACHUTE);
 43#endif
 44
 45#ifndef Q_OS_MAC
 46	setWindowIcon(QIcon(":/res/mgba-512.png"));
 47#endif
 48
 49	SocketSubsystemInit();
 50	qRegisterMetaType<const uint32_t*>("const uint32_t*");
 51	qRegisterMetaType<mCoreThread*>("mCoreThread*");
 52
 53	QApplication::setApplicationName(projectName);
 54	QApplication::setApplicationVersion(projectVersion);
 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	connect(this, &GBAApp::aboutToQuit, this, &GBAApp::cleanup);
 72}
 73
 74void GBAApp::cleanup() {
 75	m_workerThreads.waitForDone();
 76
 77	while (!m_workerJobs.isEmpty()) {
 78		finishJob(m_workerJobs.firstKey());
 79	}
 80
 81#ifdef USE_SQLITE3
 82	if (m_db) {
 83		NoIntroDBDestroy(m_db);
 84	}
 85#endif
 86}
 87
 88bool GBAApp::event(QEvent* event) {
 89	if (event->type() == QEvent::FileOpen) {
 90		CoreController* core = m_manager.loadGame(static_cast<QFileOpenEvent*>(event)->file());
 91		m_windows[0]->setController(core, static_cast<QFileOpenEvent*>(event)->file());
 92		return true;
 93	}
 94	return QApplication::event(event);
 95}
 96
 97Window* GBAApp::newWindow() {
 98	if (m_windows.count() >= MAX_GBAS) {
 99		return nullptr;
100	}
101	Window* w = new Window(&m_manager, m_configController, m_multiplayer.attached());
102	int windowId = m_multiplayer.attached();
103	connect(w, &Window::destroyed, [this, w]() {
104		m_windows.removeAll(w);
105		for (Window* w : m_windows) {
106			w->updateMultiplayerStatus(m_windows.count() < MAX_GBAS);
107		}
108	});
109	m_windows.append(w);
110	w->setAttribute(Qt::WA_DeleteOnClose);
111	w->loadConfig();
112	w->show();
113	w->multiplayerChanged();
114	for (Window* w : m_windows) {
115		w->updateMultiplayerStatus(m_windows.count() < MAX_GBAS);
116	}
117	return w;
118}
119
120GBAApp* GBAApp::app() {
121	return g_app;
122}
123
124void GBAApp::pauseAll(QList<Window*>* paused) {
125	for (auto& window : m_windows) {
126		if (!window->controller() || window->controller()->isPaused()) {
127			continue;
128		}
129		window->controller()->setPaused(true);
130		paused->append(window);
131	}
132}
133
134void GBAApp::continueAll(const QList<Window*>& paused) {
135	for (auto& window : paused) {
136		if (window->controller()) {
137			window->controller()->setPaused(false);
138		}
139	}
140}
141
142QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) {
143	QList<Window*> paused;
144	pauseAll(&paused);
145	QString filename = QFileDialog::getOpenFileName(owner, title, m_configController->getOption("lastDirectory"), filter);
146	continueAll(paused);
147	if (!filename.isEmpty()) {
148		m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
149	}
150	return filename;
151}
152
153QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter) {
154	QList<Window*> paused;
155	pauseAll(&paused);
156	QString filename = QFileDialog::getSaveFileName(owner, title, m_configController->getOption("lastDirectory"), filter);
157	continueAll(paused);
158	if (!filename.isEmpty()) {
159		m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
160	}
161	return filename;
162}
163
164QString GBAApp::getOpenDirectoryName(QWidget* owner, const QString& title) {
165	QList<Window*> paused;
166	pauseAll(&paused);
167	QString filename = QFileDialog::getExistingDirectory(owner, title, m_configController->getOption("lastDirectory"));
168	continueAll(paused);
169	if (!filename.isEmpty()) {
170		m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
171	}
172	return filename;
173}
174
175QString GBAApp::dataDir() {
176#ifdef DATADIR
177	QString path = QString::fromUtf8(DATADIR);
178#else
179	QString path = QCoreApplication::applicationDirPath();
180#ifdef Q_OS_MAC
181	path += QLatin1String("/../Resources");
182#endif
183#endif
184	return path;
185}
186
187#ifdef USE_SQLITE3
188bool GBAApp::reloadGameDB() {
189	NoIntroDB* db = nullptr;
190	db = NoIntroDBLoad((ConfigController::configDir() + "/nointro.sqlite3").toUtf8().constData());
191	if (db && m_db) {
192		NoIntroDBDestroy(m_db);
193	}
194	if (db) {
195		std::shared_ptr<GameDBParser> parser = std::make_shared<GameDBParser>(db);
196		submitWorkerJob(std::bind(&GameDBParser::parseNoIntroDB, parser));
197		m_db = db;
198		return true;
199	}
200	return false;
201}
202#else
203bool GBAApp::reloadGameDB() {
204	return false;
205}
206#endif
207
208qint64 GBAApp::submitWorkerJob(std::function<void ()> job, std::function<void ()> callback) {
209	return submitWorkerJob(job, nullptr, callback);
210}
211
212qint64 GBAApp::submitWorkerJob(std::function<void ()> job, QObject* context, std::function<void ()> callback) {
213	qint64 jobId = m_nextJob;
214	++m_nextJob;
215	WorkerJob* jobRunnable = new WorkerJob(jobId, job, this);
216	m_workerJobs.insert(jobId, jobRunnable);
217	if (callback) {
218		waitOnJob(jobId, context, callback);
219	}
220	m_workerThreads.start(jobRunnable);
221	return jobId;
222}
223
224bool GBAApp::removeWorkerJob(qint64 jobId) {
225	for (auto& job : m_workerJobCallbacks.values(jobId)) {
226		disconnect(job);
227	}
228	m_workerJobCallbacks.remove(jobId);
229	if (!m_workerJobs.contains(jobId)) {
230		return true;
231	}
232	bool success = false;
233#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
234	success = m_workerThreads.tryTake(m_workerJobs[jobId]);
235#endif
236	if (success) {
237		m_workerJobs.remove(jobId);
238	}
239	return success;
240}
241
242
243bool GBAApp::waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback) {
244	if (!m_workerJobs.contains(jobId)) {
245		return false;
246	}
247	if (!context) {
248		context = this;
249	}
250	QMetaObject::Connection connection = connect(this, &GBAApp::jobFinished, context, [jobId, callback](qint64 testedJobId) {
251		if (jobId != testedJobId) {
252			return;
253		}
254		callback();
255	});
256	m_workerJobCallbacks.insert(m_nextJob, connection);
257	return true;
258}
259
260void GBAApp::finishJob(qint64 jobId) {
261	m_workerJobs.remove(jobId);
262	emit jobFinished(jobId);
263	m_workerJobCallbacks.remove(jobId);
264}
265
266GBAApp::WorkerJob::WorkerJob(qint64 id, std::function<void ()> job, GBAApp* owner)
267	: m_id(id)
268	, m_job(job)
269	, m_owner(owner)
270{
271	setAutoDelete(true);
272}
273
274void GBAApp::WorkerJob::run() {
275	m_job();
276	QMetaObject::invokeMethod(m_owner, "finishJob", Q_ARG(qint64, m_id));
277}
278
279#ifdef USE_SQLITE3
280GameDBParser::GameDBParser(NoIntroDB* db, QObject* parent)
281	: QObject(parent)
282	, m_db(db)
283{
284	// Nothing to do
285}
286
287void GameDBParser::parseNoIntroDB() {
288	VFile* vf = VFileDevice::open(GBAApp::dataDir() + "/nointro.dat", O_RDONLY);
289	if (vf) {
290		NoIntroDBLoadClrMamePro(m_db, vf);
291		vf->close(vf);
292	}
293}
294
295#endif