all repos — mgba @ b9d4101937d166a24018b5bb5423ead87c22f6bc

mGBA Game Boy Advance Emulator

src/platform/qt/Window.cpp (view raw)

  1#include "Window.h"
  2
  3#include <QFileDialog>
  4#include <QKeyEvent>
  5#include <QKeySequence>
  6#include <QMenuBar>
  7#include <QStackedLayout>
  8
  9#include "GameController.h"
 10#include "GDBWindow.h"
 11#include "GDBController.h"
 12#include "LoadSaveState.h"
 13#include "LogView.h"
 14
 15using namespace QGBA;
 16
 17Window::Window(QWidget* parent)
 18	: QMainWindow(parent)
 19	, m_logView(new LogView())
 20	, m_stateWindow(nullptr)
 21	, m_screenWidget(new QWidget())
 22#ifdef USE_GDB_STUB
 23	, m_gdbController(nullptr)
 24#endif
 25{
 26	m_controller = new GameController(this);
 27
 28	QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer));
 29	format.setSwapInterval(1);
 30	m_screenWidget->setLayout(new QStackedLayout());
 31	m_screenWidget->layout()->setContentsMargins(0, 0, 0, 0);
 32	setCentralWidget(m_screenWidget);
 33	m_display = new Display(format);
 34	attachWidget(m_display);
 35	connect(m_controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*)));
 36	connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_display, SLOT(stopDrawing()));
 37	connect(m_controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(gameStopped()));
 38	connect(m_controller, SIGNAL(stateLoaded(GBAThread*)), m_display, SLOT(forceDraw()));
 39	connect(m_controller, SIGNAL(postLog(int, const QString&)), m_logView, SLOT(postLog(int, const QString&)));
 40	connect(this, SIGNAL(startDrawing(const uint32_t*, GBAThread*)), m_display, SLOT(startDrawing(const uint32_t*, GBAThread*)), Qt::QueuedConnection);
 41	connect(this, SIGNAL(shutdown()), m_display, SLOT(stopDrawing()));
 42	connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame()));
 43	connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide()));
 44	connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));
 45	connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float)));
 46
 47	setupMenu(menuBar());
 48}
 49
 50Window::~Window() {
 51	delete m_logView;
 52}
 53
 54GBAKey Window::mapKey(int qtKey) {
 55	switch (qtKey) {
 56	case Qt::Key_Z:
 57		return GBA_KEY_A;
 58		break;
 59	case Qt::Key_X:
 60		return GBA_KEY_B;
 61		break;
 62	case Qt::Key_A:
 63		return GBA_KEY_L;
 64		break;
 65	case Qt::Key_S:
 66		return GBA_KEY_R;
 67		break;
 68	case Qt::Key_Return:
 69		return GBA_KEY_START;
 70		break;
 71	case Qt::Key_Backspace:
 72		return GBA_KEY_SELECT;
 73		break;
 74	case Qt::Key_Up:
 75		return GBA_KEY_UP;
 76		break;
 77	case Qt::Key_Down:
 78		return GBA_KEY_DOWN;
 79		break;
 80	case Qt::Key_Left:
 81		return GBA_KEY_LEFT;
 82		break;
 83	case Qt::Key_Right:
 84		return GBA_KEY_RIGHT;
 85		break;
 86	default:
 87		return GBA_KEY_NONE;
 88	}
 89}
 90
 91void Window::selectROM() {
 92	QString filename = QFileDialog::getOpenFileName(this, tr("Select ROM"));
 93	if (!filename.isEmpty()) {
 94		m_controller->loadGame(filename);
 95	}
 96}
 97
 98#ifdef USE_GDB_STUB
 99void Window::gdbOpen() {
100	if (!m_gdbController) {
101		m_gdbController = new GDBController(m_controller, this);
102	}
103	GDBWindow* window = new GDBWindow(m_gdbController);
104	window->show();
105}
106#endif
107
108void Window::keyPressEvent(QKeyEvent* event) {
109	if (event->isAutoRepeat()) {
110		QWidget::keyPressEvent(event);
111		return;
112	}
113	GBAKey key = mapKey(event->key());
114	if (key == GBA_KEY_NONE) {
115		QWidget::keyPressEvent(event);
116		return;
117	}
118	m_controller->keyPressed(key);
119	event->accept();
120}
121
122void Window::keyReleaseEvent(QKeyEvent* event) {
123	if (event->isAutoRepeat()) {
124		QWidget::keyReleaseEvent(event);
125		return;
126	}
127	GBAKey key = mapKey(event->key());
128	if (key == GBA_KEY_NONE) {
129		QWidget::keyPressEvent(event);
130		return;
131	}
132	m_controller->keyReleased(key);
133	event->accept();
134}
135
136void Window::closeEvent(QCloseEvent* event) {
137	emit shutdown();
138	QMainWindow::closeEvent(event);
139}
140
141void Window::toggleFullScreen() {
142	if (isFullScreen()) {
143		showNormal();
144		setCursor(Qt::ArrowCursor);
145	} else {
146		showFullScreen();
147		setCursor(Qt::BlankCursor);
148	}
149}
150
151void Window::gameStarted(GBAThread* context) {
152	emit startDrawing(m_controller->drawContext(), context);
153	foreach (QAction* action, m_gameActions) {
154		action->setDisabled(false);
155	}
156}
157
158void Window::gameStopped() {
159	foreach (QAction* action, m_gameActions) {
160		action->setDisabled(true);
161	}
162}
163
164void Window::openStateWindow(LoadSave ls) {
165	if (m_stateWindow) {
166		return;
167	}
168	bool wasPaused = m_controller->isPaused();
169	m_stateWindow = new LoadSaveState(m_controller);
170	connect(this, SIGNAL(shutdown()), m_stateWindow, SLOT(hide()));
171	connect(m_stateWindow, &LoadSaveState::closed, [this]() {
172		m_screenWidget->layout()->removeWidget(m_stateWindow);
173		m_stateWindow = nullptr;
174		setFocus();
175	});
176	if (!wasPaused) {
177		m_controller->setPaused(true);
178		connect(m_stateWindow, &LoadSaveState::closed, [this]() { m_controller->setPaused(false); });
179	}
180	m_stateWindow->setAttribute(Qt::WA_DeleteOnClose);
181	m_stateWindow->setMode(ls);
182	if (isFullScreen()) {
183		attachWidget(m_stateWindow);
184	} else {
185		m_stateWindow->show();
186	}
187}
188
189void Window::setupMenu(QMenuBar* menubar) {
190	menubar->clear();
191	QMenu* fileMenu = menubar->addMenu(tr("&File"));
192	fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open);
193
194	QMenu* emulationMenu = menubar->addMenu(tr("&Emulation"));
195	QAction* reset = new QAction(tr("&Reset"), emulationMenu);
196	reset->setShortcut(tr("Ctrl+R"));
197	connect(reset, SIGNAL(triggered()), m_controller, SLOT(reset()));
198	m_gameActions.append(reset);
199	emulationMenu->addAction(reset);
200
201	QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu);
202	connect(shutdown, SIGNAL(triggered()), m_controller, SLOT(closeGame()));
203	m_gameActions.append(shutdown);
204	emulationMenu->addAction(shutdown);
205	emulationMenu->addSeparator();
206
207	QAction* loadState = new QAction(tr("&Load state"), emulationMenu);
208	loadState->setShortcut(tr("Ctrl+L"));
209	connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); });
210	m_gameActions.append(loadState);
211	emulationMenu->addAction(loadState);
212
213	QAction* saveState = new QAction(tr("&Save state"), emulationMenu);
214	saveState->setShortcut(tr("Ctrl+S"));
215	connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); });
216	m_gameActions.append(saveState);
217	emulationMenu->addAction(saveState);
218
219	QMenu* quickLoadMenu = emulationMenu->addMenu(tr("Quick load"));
220	QMenu* quickSaveMenu = emulationMenu->addMenu(tr("Quick save"));
221	int i;
222	for (i = 1; i < 10; ++i) {
223		QAction* quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu);
224		quickLoad->setShortcut(tr("F%1").arg(i));
225		connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); });
226		m_gameActions.append(quickLoad);
227		quickLoadMenu->addAction(quickLoad);
228
229		QAction* quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu);
230		quickSave->setShortcut(tr("Shift+F%1").arg(i));
231		connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); });
232		m_gameActions.append(quickSave);
233		quickSaveMenu->addAction(quickSave);
234	}
235
236	emulationMenu->addSeparator();
237
238	QAction* pause = new QAction(tr("&Pause"), emulationMenu);
239	pause->setChecked(false);
240	pause->setCheckable(true);
241	pause->setShortcut(tr("Ctrl+P"));
242	connect(pause, SIGNAL(triggered(bool)), m_controller, SLOT(setPaused(bool)));
243	connect(m_controller, &GameController::gamePaused, [pause]() { pause->setChecked(true); });
244	connect(m_controller, &GameController::gameUnpaused, [pause]() { pause->setChecked(false); });
245	m_gameActions.append(pause);
246	emulationMenu->addAction(pause);
247
248	QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu);
249	frameAdvance->setShortcut(tr("Ctrl+N"));
250	connect(frameAdvance, SIGNAL(triggered()), m_controller, SLOT(frameAdvance()));
251	m_gameActions.append(frameAdvance);
252	emulationMenu->addAction(frameAdvance);
253
254	QMenu* target = emulationMenu->addMenu("FPS target");
255	QAction* setTarget = new QAction(tr("15"), emulationMenu);
256	connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(15); });
257	target->addAction(setTarget);
258	setTarget = new QAction(tr("30"), emulationMenu);
259	connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(30); });
260	target->addAction(setTarget);
261	setTarget = new QAction(tr("45"), emulationMenu);
262	connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(45); });
263	target->addAction(setTarget);
264	setTarget = new QAction(tr("60"), emulationMenu);
265	connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(60); });
266	target->addAction(setTarget);
267	setTarget = new QAction(tr("90"), emulationMenu);
268	connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(90); });
269	target->addAction(setTarget);
270	setTarget = new QAction(tr("120"), emulationMenu);
271	connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(120); });
272	target->addAction(setTarget);
273	setTarget = new QAction(tr("240"), emulationMenu);
274	connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(240); });
275	target->addAction(setTarget);
276
277	QMenu* videoMenu = menubar->addMenu(tr("&Video"));
278	QMenu* frameMenu = videoMenu->addMenu(tr("Frame &size"));
279	QAction* setSize = new QAction(tr("1x"), videoMenu);
280	connect(setSize, &QAction::triggered, [this]() {
281		showNormal();
282		resize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
283	});
284	frameMenu->addAction(setSize);
285	setSize = new QAction(tr("2x"), videoMenu);
286	connect(setSize, &QAction::triggered, [this]() {
287		showNormal();
288		resize(VIDEO_HORIZONTAL_PIXELS * 2, VIDEO_VERTICAL_PIXELS * 2);
289	});
290	frameMenu->addAction(setSize);
291	setSize = new QAction(tr("3x"), videoMenu);
292	connect(setSize, &QAction::triggered, [this]() {
293		showNormal();
294		resize(VIDEO_HORIZONTAL_PIXELS * 3, VIDEO_VERTICAL_PIXELS * 3);
295	});
296	frameMenu->addAction(setSize);
297	setSize = new QAction(tr("4x"), videoMenu);
298	connect(setSize, &QAction::triggered, [this]() {
299		showNormal();
300		resize(VIDEO_HORIZONTAL_PIXELS * 4, VIDEO_VERTICAL_PIXELS * 4);
301	});
302	frameMenu->addAction(setSize);
303	frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F"));
304
305	QMenu* soundMenu = menubar->addMenu(tr("&Sound"));
306	QMenu* buffersMenu = soundMenu->addMenu(tr("Buffer &size"));
307	QAction* setBuffer = new QAction(tr("512"), buffersMenu);
308	connect(setBuffer, &QAction::triggered, [this]() { emit audioBufferSamplesChanged(512); });
309	buffersMenu->addAction(setBuffer);
310	setBuffer = new QAction(tr("1024"), buffersMenu);
311	connect(setBuffer, &QAction::triggered, [this]() { emit audioBufferSamplesChanged(1024); });
312	buffersMenu->addAction(setBuffer);
313	setBuffer = new QAction(tr("2048"), buffersMenu);
314	connect(setBuffer, &QAction::triggered, [this]() { emit audioBufferSamplesChanged(2048); });
315	buffersMenu->addAction(setBuffer);
316
317	QMenu* debuggingMenu = menubar->addMenu(tr("&Debugging"));
318	QAction* viewLogs = new QAction(tr("View &logs..."), debuggingMenu);
319	connect(viewLogs, SIGNAL(triggered()), m_logView, SLOT(show()));
320	debuggingMenu->addAction(viewLogs);
321#ifdef USE_GDB_STUB
322	QAction* gdbWindow = new QAction(tr("Start &GDB server..."), debuggingMenu);
323	connect(gdbWindow, SIGNAL(triggered()), this, SLOT(gdbOpen()));
324	debuggingMenu->addAction(gdbWindow);
325#endif
326
327	foreach (QAction* action, m_gameActions) {
328		action->setDisabled(true);
329	}
330}
331
332void Window::attachWidget(QWidget* widget) {
333	m_screenWidget->layout()->addWidget(widget);
334	static_cast<QStackedLayout*>(m_screenWidget->layout())->setCurrentWidget(widget);
335}