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