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