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