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