src/platform/qt/Window.cpp (view raw)
1/* Copyright (c) 2013-2017 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 "Window.h"
7
8#include <QDesktopWidget>
9#include <QKeyEvent>
10#include <QKeySequence>
11#include <QMenuBar>
12#include <QMessageBox>
13#include <QMimeData>
14#include <QPainter>
15#include <QStackedLayout>
16
17#ifdef USE_SQLITE3
18#include "ArchiveInspector.h"
19#include "library/LibraryController.h"
20#endif
21
22#include "AboutScreen.h"
23#include "AudioProcessor.h"
24#include "CheatsView.h"
25#include "ConfigController.h"
26#include "CoreController.h"
27#include "DebuggerConsole.h"
28#include "DebuggerConsoleController.h"
29#include "Display.h"
30#include "CoreController.h"
31#include "GBAApp.h"
32#include "GDBController.h"
33#include "GDBWindow.h"
34#include "GIFView.h"
35#include "InputModel.h"
36#include "IOViewer.h"
37#include "LoadSaveState.h"
38#include "LogView.h"
39#include "MapView.h"
40#include "MemorySearch.h"
41#include "MemoryView.h"
42#include "MultiplayerController.h"
43#include "OverrideView.h"
44#include "ObjView.h"
45#include "PaletteView.h"
46#include "PlacementControl.h"
47#include "PrinterView.h"
48#include "ROMInfo.h"
49#include "SensorView.h"
50#include "SettingsView.h"
51#include "ShaderSelector.h"
52#include "TileView.h"
53#include "VideoView.h"
54
55#include <mgba/core/version.h>
56#include <mgba/core/cheats.h>
57#ifdef M_CORE_GB
58#include <mgba/internal/gb/gb.h>
59#include <mgba/internal/gb/input.h>
60#include <mgba/internal/gb/video.h>
61#endif
62#ifdef M_CORE_GBA
63#include <mgba/internal/gba/gba.h>
64#include <mgba/internal/gba/input.h>
65#include <mgba/internal/gba/video.h>
66#endif
67#ifdef M_CORE_DS
68#include <mgba/internal/ds/input.h>
69#endif
70#include <mgba/feature/commandline.h>
71#include "feature/sqlite3/no-intro.h"
72#include <mgba-util/vfs.h>
73
74#ifdef M_CORE_GB
75#define SUPPORT_GB (1 << PLATFORM_GB)
76#else
77#define SUPPORT_GB 0
78#endif
79
80#ifdef M_CORE_GBA
81#define SUPPORT_GBA (1 << PLATFORM_GBA)
82#else
83#define SUPPORT_GBA 0
84#endif
85
86#ifdef M_CORE_DS
87#define SUPPORT_DS (1 << PLATFORM_DS)
88#else
89#define SUPPORT_DS 0
90#endif
91
92using namespace QGBA;
93
94Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWidget* parent)
95 : QMainWindow(parent)
96 , m_manager(manager)
97 , m_logView(new LogView(&m_log))
98 , m_screenWidget(new WindowBackground())
99 , m_config(config)
100 , m_inputController(playerId, this)
101{
102 setFocusPolicy(Qt::StrongFocus);
103 setAcceptDrops(true);
104 setAttribute(Qt::WA_DeleteOnClose);
105 updateTitle();
106 reloadDisplayDriver();
107
108 m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio());
109 m_logo = m_logo; // Free memory left over in old pixmap
110
111#if defined(M_CORE_GBA)
112 float i = 2;
113#elif defined(M_CORE_GB)
114 float i = 3;
115#endif
116 QVariant multiplier = m_config->getOption("scaleMultiplier");
117 if (!multiplier.isNull()) {
118 m_savedScale = multiplier.toInt();
119 i = m_savedScale;
120 }
121#ifdef USE_SQLITE3
122 m_libraryView = new LibraryController(nullptr, ConfigController::configDir() + "/library.sqlite3", m_config);
123 ConfigOption* showLibrary = m_config->addOption("showLibrary");
124 showLibrary->connect([this](const QVariant& value) {
125 if (value.toBool()) {
126 if (m_controller) {
127 m_screenWidget->layout()->addWidget(m_libraryView);
128 } else {
129 attachWidget(m_libraryView);
130 }
131 } else {
132 detachWidget(m_libraryView);
133 }
134 }, this);
135 m_config->updateOption("showLibrary");
136 ConfigOption* libraryStyle = m_config->addOption("libraryStyle");
137 libraryStyle->connect([this](const QVariant& value) {
138 m_libraryView->setViewStyle(static_cast<LibraryStyle>(value.toInt()));
139 }, this);
140 m_config->updateOption("libraryStyle");
141
142 connect(m_libraryView, &LibraryController::startGame, [this]() {
143 VFile* output = m_libraryView->selectedVFile();
144 if (output) {
145 QPair<QString, QString> path = m_libraryView->selectedPath();
146 setController(m_manager->loadGame(output, path.second, path.first), path.first + "/" + path.second);
147 }
148 });
149#endif
150#if defined(M_CORE_GBA)
151 resizeFrame(QSize(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i));
152#elif defined(M_CORE_GB)
153 resizeFrame(QSize(GB_VIDEO_HORIZONTAL_PIXELS * i, GB_VIDEO_VERTICAL_PIXELS * i));
154#endif
155 m_screenWidget->setPixmap(m_logo);
156 m_screenWidget->setCenteredAspectRatio(m_logo.width(), m_logo.height());
157 m_screenWidget->setDimensions(m_logo.width(), m_logo.height());
158 m_screenWidget->setLockIntegerScaling(false);
159 setCentralWidget(m_screenWidget);
160
161 connect(this, &Window::shutdown, m_logView, &QWidget::hide);
162 connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS);
163 connect(&m_frameTimer, &QTimer::timeout, this, &Window::delimitFrames);
164 connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck);
165
166 m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL);
167 m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);
168 m_frameTimer.setInterval(FRAME_LIST_INTERVAL);
169 m_focusCheck.setInterval(200);
170
171 setupMenu(menuBar());
172
173#ifdef M_CORE_GBA
174 m_inputController.addPlatform(PLATFORM_GBA, &GBAInputInfo);
175#endif
176#ifdef M_CORE_GB
177 m_inputController.addPlatform(PLATFORM_GB, &GBInputInfo);
178#endif
179#ifdef M_CORE_DS
180 m_inputController.addPlatform(PLATFORM_DS, &DSInputInfo);
181#endif
182}
183
184Window::~Window() {
185 delete m_logView;
186
187#ifdef USE_FFMPEG
188 delete m_videoView;
189#endif
190
191#ifdef USE_MAGICK
192 delete m_gifView;
193#endif
194
195#ifdef USE_SQLITE3
196 delete m_libraryView;
197#endif
198}
199
200void Window::argumentsPassed(mArguments* args) {
201 loadConfig();
202
203 if (args->fname) {
204 setController(m_manager->loadGame(args->fname), args->fname);
205 }
206
207#ifdef USE_GDB_STUB
208 if (args->debuggerType == DEBUGGER_GDB) {
209 if (!m_gdbController) {
210 m_gdbController = new GDBController(this);
211 if (m_controller) {
212 m_gdbController->setController(m_controller);
213 }
214 m_gdbController->listen();
215 }
216 }
217#endif
218}
219
220void Window::resizeFrame(const QSize& size) {
221 QSize newSize(size);
222 m_screenWidget->setSizeHint(newSize);
223 newSize -= m_screenWidget->size();
224 newSize += this->size();
225 if (!isFullScreen()) {
226 resize(newSize);
227 }
228}
229
230void Window::setConfig(ConfigController* config) {
231 m_config = config;
232}
233
234void Window::loadConfig() {
235 const mCoreOptions* opts = m_config->options();
236 reloadConfig();
237
238 if (opts->width && opts->height) {
239 resizeFrame(QSize(opts->width, opts->height));
240 }
241
242 if (opts->fullscreen) {
243 enterFullScreen();
244 }
245
246 m_mruFiles = m_config->getMRU();
247 updateMRU();
248
249 m_inputController.setConfiguration(m_config);
250}
251
252void Window::reloadConfig() {
253 const mCoreOptions* opts = m_config->options();
254
255 m_log.setLevels(opts->logLevel);
256
257 if (m_controller) {
258 m_controller->loadConfig(m_config);
259 if (m_audioProcessor) {
260 m_audioProcessor->setBufferSamples(opts->audioBuffers);
261 m_audioProcessor->requestSampleRate(opts->sampleRate);
262 }
263 }
264 m_display->lockAspectRatio(opts->lockAspectRatio);
265 m_display->filter(opts->resampleVideo);
266
267 m_inputController.setScreensaverSuspendable(opts->suspendScreensaver);
268}
269
270void Window::saveConfig() {
271 m_inputController.saveConfiguration();
272 m_config->write();
273}
274
275QString Window::getFilters() const {
276 QStringList filters;
277 QStringList formats;
278
279#ifdef M_CORE_GBA
280 QStringList gbaFormats{
281 "*.gba",
282#if defined(USE_LIBZIP) || defined(USE_ZLIB)
283 "*.zip",
284#endif
285#ifdef USE_LZMA
286 "*.7z",
287#endif
288#ifdef USE_ELF
289 "*.elf",
290#endif
291 "*.agb",
292 "*.mb",
293 "*.rom",
294 "*.bin"};
295 formats.append(gbaFormats);
296 filters.append(tr("Game Boy Advance ROMs (%1)").arg(gbaFormats.join(QChar(' '))));
297#endif
298
299#ifdef M_CORE_DS
300 QStringList dsFormats{
301 "*.nds",
302 "*.srl",
303#if defined(USE_LIBZIP) || defined(USE_ZLIB)
304 "*.zip",
305#endif
306#ifdef USE_LZMA
307 "*.7z",
308#endif
309 "*.rom",
310 "*.bin"};
311 formats.append(dsFormats);
312 filters.append(tr("DS ROMs (%1)").arg(dsFormats.join(QChar(' '))));
313#endif
314
315#ifdef M_CORE_GB
316 QStringList gbFormats{
317 "*.gb",
318 "*.gbc",
319 "*.sgb",
320#if defined(USE_LIBZIP) || defined(USE_ZLIB)
321 "*.zip",
322#endif
323#ifdef USE_LZMA
324 "*.7z",
325#endif
326 "*.rom",
327 "*.bin"};
328 formats.append(gbFormats);
329 filters.append(tr("Game Boy ROMs (%1)").arg(gbFormats.join(QChar(' '))));
330#endif
331
332 formats.removeDuplicates();
333 filters.prepend(tr("All ROMs (%1)").arg(formats.join(QChar(' '))));
334 filters.append(tr("%1 Video Logs (*.mvl)").arg(projectName));
335 return filters.join(";;");
336}
337
338QString Window::getFiltersArchive() const {
339 QStringList filters;
340
341 QStringList formats{
342#if defined(USE_LIBZIP) || defined(USE_ZLIB)
343 "*.zip",
344#endif
345#ifdef USE_LZMA
346 "*.7z",
347#endif
348 };
349 filters.append(tr("Archives (%1)").arg(formats.join(QChar(' '))));
350 return filters.join(";;");
351}
352
353void Window::selectROM() {
354 QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters());
355 if (!filename.isEmpty()) {
356 setController(m_manager->loadGame(filename), filename);
357 }
358}
359
360#ifdef USE_SQLITE3
361void Window::selectROMInArchive() {
362 QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFiltersArchive());
363 if (filename.isEmpty()) {
364 return;
365 }
366 ArchiveInspector* archiveInspector = new ArchiveInspector(filename);
367 connect(archiveInspector, &QDialog::accepted, [this, archiveInspector]() {
368 VFile* output = archiveInspector->selectedVFile();
369 QPair<QString, QString> path = archiveInspector->selectedPath();
370 if (output) {
371 setController(m_manager->loadGame(output, path.second, path.first), path.first + "/" + path.second);
372 }
373 archiveInspector->close();
374 });
375 archiveInspector->setAttribute(Qt::WA_DeleteOnClose);
376 archiveInspector->show();
377}
378
379void Window::addDirToLibrary() {
380 QString filename = GBAApp::app()->getOpenDirectoryName(this, tr("Select folder"));
381 if (filename.isEmpty()) {
382 return;
383 }
384 m_libraryView->addDirectory(filename);
385}
386#endif
387
388void Window::replaceROM() {
389 QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters());
390 if (!filename.isEmpty()) {
391 m_controller->replaceGame(filename);
392 }
393}
394
395void Window::selectSave(bool temporary) {
396 QStringList formats{"*.sav"};
397 QString filter = tr("Game Boy Advance save files (%1)").arg(formats.join(QChar(' ')));
398 QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save"), filter);
399 if (!filename.isEmpty()) {
400 m_controller->loadSave(filename, temporary);
401 }
402}
403
404void Window::multiplayerChanged() {
405 if (!m_controller) {
406 return;
407 }
408 int attached = 1;
409 MultiplayerController* multiplayer = m_controller->multiplayerController();
410 if (multiplayer) {
411 attached = multiplayer->attached();
412 }
413 for (QAction* action : m_nonMpActions) {
414 action->setDisabled(attached > 1);
415 }
416}
417
418void Window::selectPatch() {
419 QString filename = GBAApp::app()->getOpenFileName(this, tr("Select patch"), tr("Patches (*.ips *.ups *.bps)"));
420 if (!filename.isEmpty()) {
421 if (m_controller) {
422 m_controller->loadPatch(filename);
423 } else {
424 m_pendingPatch = filename;
425 }
426 }
427}
428
429void Window::openView(QWidget* widget) {
430 connect(this, &Window::shutdown, widget, &QWidget::close);
431 widget->setAttribute(Qt::WA_DeleteOnClose);
432 widget->show();
433}
434
435void Window::loadCamImage() {
436 QString filename = GBAApp::app()->getOpenFileName(this, tr("Select image"), tr("Image file (*.png *.gif *.jpg *.jpeg);;All files (*)"));
437 if (!filename.isEmpty()) {
438 m_inputController.loadCamImage(filename);
439 }
440}
441
442void Window::importSharkport() {
443 QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save"), tr("GameShark saves (*.sps *.xps)"));
444 if (!filename.isEmpty()) {
445 m_controller->importSharkport(filename);
446 }
447}
448
449void Window::exportSharkport() {
450 QString filename = GBAApp::app()->getSaveFileName(this, tr("Select save"), tr("GameShark saves (*.sps *.xps)"));
451 if (!filename.isEmpty()) {
452 m_controller->exportSharkport(filename);
453 }
454}
455
456void Window::openSettingsWindow() {
457 SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController);
458#if defined(BUILD_GL) || defined(BUILD_GLES)
459 if (m_display->supportsShaders()) {
460 settingsWindow->setShaderSelector(m_shaderView.get());
461 }
462#endif
463 connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::reloadDisplayDriver);
464 connect(settingsWindow, &SettingsView::audioDriverChanged, this, &Window::reloadAudioDriver);
465 connect(settingsWindow, &SettingsView::cameraDriverChanged, this, &Window::mustRestart);
466 connect(settingsWindow, &SettingsView::languageChanged, this, &Window::mustRestart);
467 connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig);
468#ifdef USE_SQLITE3
469 connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear);
470#endif
471 openView(settingsWindow);
472}
473
474void Window::startVideoLog() {
475 QString filename = GBAApp::app()->getSaveFileName(this, tr("Select video log"), tr("Video logs (*.mvl)"));
476 if (!filename.isEmpty()) {
477 m_controller->startVideoLog(filename);
478 }
479}
480
481template <typename T, typename... A>
482std::function<void()> Window::openTView(A... arg) {
483 return [=]() {
484 T* view = new T(arg...);
485 openView(view);
486 };
487}
488
489
490template <typename T, typename... A>
491std::function<void()> Window::openControllerTView(A... arg) {
492 return [=]() {
493 T* view = new T(m_controller, arg...);
494 openView(view);
495 };
496}
497
498#ifdef USE_FFMPEG
499void Window::openVideoWindow() {
500 if (!m_videoView) {
501 m_videoView = new VideoView();
502 if (m_controller) {
503 m_videoView->setController(m_controller);
504 }
505 connect(this, &Window::shutdown, m_videoView, &QWidget::close);
506 }
507 m_videoView->show();
508}
509#endif
510
511#ifdef USE_MAGICK
512void Window::openGIFWindow() {
513 if (!m_gifView) {
514 m_gifView = new GIFView();
515 if (m_controller) {
516 m_gifView->setController(m_controller);
517 }
518 connect(this, &Window::shutdown, m_gifView, &QWidget::close);
519 }
520 m_gifView->show();
521}
522#endif
523
524#ifdef USE_GDB_STUB
525void Window::gdbOpen() {
526 if (!m_gdbController) {
527 m_gdbController = new GDBController(this);
528 }
529 GDBWindow* window = new GDBWindow(m_gdbController);
530 m_gdbController->setController(m_controller);
531 connect(m_controller.get(), &CoreController::stopping, window, &QWidget::close);
532 openView(window);
533}
534#endif
535
536#ifdef USE_DEBUGGERS
537void Window::consoleOpen() {
538 if (!m_console) {
539 m_console = new DebuggerConsoleController(this);
540 }
541 DebuggerConsole* window = new DebuggerConsole(m_console);
542 if (m_controller) {
543 m_console->setController(m_controller);
544 }
545 openView(window);
546}
547#endif
548
549void Window::resizeEvent(QResizeEvent* event) {
550 if (!isFullScreen()) {
551 m_config->setOption("height", m_screenWidget->height());
552 m_config->setOption("width", m_screenWidget->width());
553 }
554
555 int factor = 0;
556 QSize size(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
557 if (m_controller) {
558 size = m_controller->screenDimensions();
559 }
560 if (m_screenWidget->width() % size.width() == 0 && m_screenWidget->height() % size.height() == 0 &&
561 m_screenWidget->width() / size.width() == m_screenWidget->height() / size.height()) {
562 factor = m_screenWidget->width() / size.width();
563 } else {
564 m_savedScale = 0;
565 }
566 for (QMap<int, QAction*>::iterator iter = m_frameSizes.begin(); iter != m_frameSizes.end(); ++iter) {
567 bool enableSignals = iter.value()->blockSignals(true);
568 iter.value()->setChecked(iter.key() == factor);
569 iter.value()->blockSignals(enableSignals);
570 }
571
572 m_config->setOption("fullscreen", isFullScreen());
573}
574
575void Window::showEvent(QShowEvent* event) {
576 if (m_wasOpened) {
577 return;
578 }
579 m_wasOpened = true;
580 resizeFrame(m_screenWidget->sizeHint());
581 QVariant windowPos = m_config->getQtOption("windowPos");
582 QRect geom = QApplication::desktop()->availableGeometry(this);
583 if (!windowPos.isNull() && geom.contains(windowPos.toPoint())) {
584 move(windowPos.toPoint());
585 } else {
586 QRect rect = frameGeometry();
587 rect.moveCenter(geom.center());
588 move(rect.topLeft());
589 }
590 if (m_fullscreenOnStart) {
591 enterFullScreen();
592 m_fullscreenOnStart = false;
593 }
594 if (m_display) {
595 reloadDisplayDriver();
596 }
597}
598
599void Window::closeEvent(QCloseEvent* event) {
600 emit shutdown();
601 m_config->setQtOption("windowPos", pos());
602
603 if (m_savedScale > 0) {
604 m_config->setOption("height", VIDEO_VERTICAL_PIXELS * m_savedScale);
605 m_config->setOption("width", VIDEO_HORIZONTAL_PIXELS * m_savedScale);
606 }
607 saveConfig();
608 QMainWindow::closeEvent(event);
609}
610
611void Window::focusInEvent(QFocusEvent*) {
612 m_display->forceDraw();
613}
614
615void Window::focusOutEvent(QFocusEvent*) {
616}
617
618void Window::dragEnterEvent(QDragEnterEvent* event) {
619 if (event->mimeData()->hasFormat("text/uri-list")) {
620 event->acceptProposedAction();
621 }
622}
623
624void Window::dropEvent(QDropEvent* event) {
625 QString uris = event->mimeData()->data("text/uri-list");
626 uris = uris.trimmed();
627 if (uris.contains("\n")) {
628 // Only one file please
629 return;
630 }
631 QUrl url(uris);
632 if (!url.isLocalFile()) {
633 // No remote loading
634 return;
635 }
636 event->accept();
637 setController(m_manager->loadGame(url.toLocalFile()), url.toLocalFile());
638}
639
640void Window::mouseMoveEvent(QMouseEvent* event) {
641 if (!m_controller) {
642 return;
643 }
644 QPoint pos = event->pos();
645 pos = m_screenWidget->mapFrom(this, pos);
646 QSize dimensions = m_controller->screenDimensions();
647 QSize viewportDimensions = m_display->viewportSize();
648 QSize screenDimensions = m_screenWidget->size();
649 int x = dimensions.width() * (pos.x() - (screenDimensions.width() - viewportDimensions.width()) / 2) / viewportDimensions.width();
650 int y = dimensions.height() * (pos.y() - (screenDimensions.height() - viewportDimensions.height()) / 2) / viewportDimensions.height();
651 mCore* core = m_controller->thread()->core;
652 core->setCursorLocation(core, x, y);
653 event->accept();
654}
655
656void Window::mousePressEvent(QMouseEvent* event) {
657 if (event->button() != Qt::LeftButton) {
658 return;
659 }
660 if (!m_controller) {
661 return;
662 }
663 mouseMoveEvent(event);
664 mCore* core = m_controller->thread()->core;
665 core->setCursorDown(core, true);
666}
667
668void Window::mouseReleaseEvent(QMouseEvent* event) {
669 if (event->button() != Qt::LeftButton) {
670 return;
671 }
672 if (!m_controller) {
673 return;
674 }
675 mouseMoveEvent(event);
676 mCore* core = m_controller->thread()->core;
677 core->setCursorDown(core, false);
678}
679
680void Window::enterFullScreen() {
681 if (!isVisible()) {
682 m_fullscreenOnStart = true;
683 return;
684 }
685 if (isFullScreen()) {
686 return;
687 }
688 showFullScreen();
689#ifndef Q_OS_MAC
690 if (m_controller && !m_controller->isPaused()) {
691 menuBar()->hide();
692 }
693#endif
694}
695
696void Window::exitFullScreen() {
697 if (!isFullScreen()) {
698 return;
699 }
700 m_screenWidget->unsetCursor();
701 menuBar()->show();
702 showNormal();
703}
704
705void Window::toggleFullScreen() {
706 if (isFullScreen()) {
707 exitFullScreen();
708 } else {
709 enterFullScreen();
710 }
711}
712
713void Window::gameStarted() {
714 for (QAction* action : m_gameActions) {
715 action->setDisabled(false);
716 }
717 int platform = 1 << m_controller->platform();
718 for (QPair<QAction*, int> action : m_platformActions) {
719 action.first->setEnabled(action.second & platform);
720 }
721#ifdef M_CORE_DS
722 if ((platform & SUPPORT_DS) && (!m_config->getOption("useBios").toInt() || m_config->getOption("ds.bios7").isNull() || m_config->getOption("ds.bios9").isNull() || m_config->getOption("ds.firmware").isNull())) {
723 QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("BIOS required"),
724 tr("DS support requires dumps of the BIOS and firmware."),
725 QMessageBox::Ok, this, Qt::Sheet);
726 fail->setAttribute(Qt::WA_DeleteOnClose);
727 fail->show();
728 m_controller->stop();
729 return;
730 }
731#endif
732 QSize size = m_controller->screenDimensions();
733 m_screenWidget->setDimensions(size.width(), size.height());
734 m_config->updateOption("lockIntegerScaling");
735 m_config->updateOption("lockAspectRatio");
736 if (m_savedScale > 0) {
737 resizeFrame(size * m_savedScale);
738 }
739 attachWidget(m_display.get());
740 setMouseTracking(true);
741 m_display->setMinimumSize(size);
742
743#ifndef Q_OS_MAC
744 if (isFullScreen()) {
745 menuBar()->hide();
746 }
747#endif
748 m_display->startDrawing(m_controller);
749
750 reloadAudioDriver();
751 multiplayerChanged();
752 updateTitle();
753
754 m_hitUnimplementedBiosCall = false;
755 if (m_config->getOption("showFps", "1").toInt()) {
756 m_fpsTimer.start();
757 m_frameTimer.start();
758 }
759 m_focusCheck.start();
760 if (m_display->underMouse()) {
761 m_screenWidget->setCursor(Qt::BlankCursor);
762 }
763
764 CoreController::Interrupter interrupter(m_controller, true);
765 mCore* core = m_controller->thread()->core;
766 m_videoLayers->clear();
767 m_audioChannels->clear();
768 const mCoreChannelInfo* videoLayers;
769 const mCoreChannelInfo* audioChannels;
770 size_t nVideo = core->listVideoLayers(core, &videoLayers);
771 size_t nAudio = core->listAudioChannels(core, &audioChannels);
772
773 if (nVideo) {
774 for (size_t i = 0; i < nVideo; ++i) {
775 QAction* action = new QAction(videoLayers[i].visibleName, m_videoLayers);
776 action->setCheckable(true);
777 action->setChecked(true);
778 connect(action, &QAction::triggered, [this, videoLayers, i](bool enable) {
779 m_controller->thread()->core->enableVideoLayer(m_controller->thread()->core, videoLayers[i].id, enable);
780 });
781 m_videoLayers->addAction(action);
782 }
783 }
784 if (nAudio) {
785 for (size_t i = 0; i < nAudio; ++i) {
786 QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels);
787 action->setCheckable(true);
788 action->setChecked(true);
789 connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) {
790 m_controller->thread()->core->enableAudioChannel(m_controller->thread()->core, audioChannels[i].id, enable);
791 });
792 m_audioChannels->addAction(action);
793 }
794 }
795}
796
797void Window::gameStopped() {
798 m_controller.reset();
799 for (QPair<QAction*, int> action : m_platformActions) {
800 action.first->setDisabled(false);
801 }
802 for (QAction* action : m_gameActions) {
803 action->setDisabled(true);
804 }
805 setWindowFilePath(QString());
806 updateTitle();
807 detachWidget(m_display.get());
808 m_screenWidget->setCenteredAspectRatio(m_logo.width(), m_logo.height());
809 m_screenWidget->setDimensions(m_logo.width(), m_logo.height());
810 m_screenWidget->setLockIntegerScaling(false);
811 m_screenWidget->setPixmap(m_logo);
812 m_screenWidget->unsetCursor();
813#ifdef M_CORE_GB
814 m_display->setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
815#elif defined(M_CORE_GBA)
816 m_display->setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
817#endif
818
819 setMouseTracking(false);
820 m_videoLayers->clear();
821 m_audioChannels->clear();
822
823 m_fpsTimer.stop();
824 m_frameTimer.stop();
825 m_focusCheck.stop();
826
827 emit paused(false);
828}
829
830void Window::gameCrashed(const QString& errorMessage) {
831 QMessageBox* crash = new QMessageBox(QMessageBox::Critical, tr("Crash"),
832 tr("The game has crashed with the following error:\n\n%1").arg(errorMessage),
833 QMessageBox::Ok, this, Qt::Sheet);
834 crash->setAttribute(Qt::WA_DeleteOnClose);
835 crash->show();
836 m_controller->stop();
837}
838
839void Window::gameFailed() {
840 QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("Couldn't Load"),
841 tr("Could not load game. Are you sure it's in the correct format?"),
842 QMessageBox::Ok, this, Qt::Sheet);
843 fail->setAttribute(Qt::WA_DeleteOnClose);
844 fail->show();
845}
846
847void Window::unimplementedBiosCall(int call) {
848 if (m_hitUnimplementedBiosCall) {
849 return;
850 }
851 m_hitUnimplementedBiosCall = true;
852
853 QMessageBox* fail = new QMessageBox(
854 QMessageBox::Warning, tr("Unimplemented BIOS call"),
855 tr("This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience."),
856 QMessageBox::Ok, this, Qt::Sheet);
857 fail->setAttribute(Qt::WA_DeleteOnClose);
858 fail->show();
859}
860
861void Window::reloadDisplayDriver() {
862 if (m_controller) {
863 m_display->stopDrawing();
864 detachWidget(m_display.get());
865 }
866 m_display = std::move(std::unique_ptr<Display>(Display::create(this)));
867#if defined(BUILD_GL) || defined(BUILD_GLES2)
868 m_shaderView.reset();
869 m_shaderView = std::make_unique<ShaderSelector>(m_display.get(), m_config);
870#endif
871
872 connect(this, &Window::shutdown, m_display.get(), &Display::stopDrawing);
873 connect(m_display.get(), &Display::hideCursor, [this]() {
874 if (static_cast<QStackedLayout*>(m_screenWidget->layout())->currentWidget() == m_display.get()) {
875 m_screenWidget->setCursor(Qt::BlankCursor);
876 }
877 });
878 connect(m_display.get(), &Display::showCursor, [this]() {
879 m_screenWidget->unsetCursor();
880 });
881
882 const mCoreOptions* opts = m_config->options();
883 m_display->lockAspectRatio(opts->lockAspectRatio);
884 m_display->filter(opts->resampleVideo);
885#if defined(BUILD_GL) || defined(BUILD_GLES2)
886 if (opts->shader) {
887 struct VDir* shader = VDirOpen(opts->shader);
888 if (shader && m_display->supportsShaders()) {
889 m_display->setShaders(shader);
890 m_shaderView->refreshShaders();
891 shader->close(shader);
892 }
893 }
894#endif
895
896 if (m_controller) {
897 m_display->setMinimumSize(m_controller->screenDimensions());
898 connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing);
899 connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw);
900 connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw);
901 connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing);
902 connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing);
903 connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted);
904 connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
905
906 attachWidget(m_display.get());
907 m_display->startDrawing(m_controller);
908 } else {
909#ifdef M_CORE_GB
910 m_display->setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
911#elif defined(M_CORE_GBA)
912 m_display->setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
913#endif
914 }
915}
916
917void Window::reloadAudioDriver() {
918 if (!m_controller) {
919 return;
920 }
921 if (m_audioProcessor) {
922 m_audioProcessor->stop();
923 m_audioProcessor.reset();
924 }
925
926 const mCoreOptions* opts = m_config->options();
927 m_audioProcessor = std::move(std::unique_ptr<AudioProcessor>(AudioProcessor::create()));
928 m_audioProcessor->setInput(m_controller);
929 m_audioProcessor->setBufferSamples(opts->audioBuffers);
930 m_audioProcessor->requestSampleRate(opts->sampleRate);
931 m_audioProcessor->start();
932 connect(m_controller.get(), &CoreController::stopping, m_audioProcessor.get(), &AudioProcessor::stop);
933}
934
935void Window::tryMakePortable() {
936 QMessageBox* confirm = new QMessageBox(QMessageBox::Question, tr("Really make portable?"),
937 tr("This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?"),
938 QMessageBox::Yes | QMessageBox::Cancel, this, Qt::Sheet);
939 confirm->setAttribute(Qt::WA_DeleteOnClose);
940 connect(confirm->button(QMessageBox::Yes), &QAbstractButton::clicked, m_config, &ConfigController::makePortable);
941 confirm->show();
942}
943
944void Window::mustRestart() {
945 QMessageBox* dialog = new QMessageBox(QMessageBox::Warning, tr("Restart needed"),
946 tr("Some changes will not take effect until the emulator is restarted."),
947 QMessageBox::Ok, this, Qt::Sheet);
948 dialog->setAttribute(Qt::WA_DeleteOnClose);
949 dialog->show();
950}
951
952void Window::recordFrame() {
953 if (m_frameList.isEmpty()) {
954 m_frameList.append(1);
955 } else {
956 ++m_frameList.back();
957 }
958}
959
960void Window::delimitFrames() {
961 if (m_frameList.size() >= FRAME_LIST_SIZE) {
962 m_frameCounter -= m_frameList.takeAt(0);
963 }
964 m_frameCounter += m_frameList.back();
965 m_frameList.append(0);
966}
967
968void Window::showFPS() {
969 if (m_frameList.isEmpty()) {
970 updateTitle();
971 return;
972 }
973 float fps = m_frameCounter * 10000.f / (FRAME_LIST_INTERVAL * (m_frameList.size() - 1));
974 fps = round(fps) / 10.f;
975 updateTitle(fps);
976}
977
978void Window::updateTitle(float fps) {
979 QString title;
980
981 if (m_controller) {
982 CoreController::Interrupter interrupter(m_controller);
983 const NoIntroDB* db = GBAApp::app()->gameDB();
984 NoIntroGame game{};
985 uint32_t crc32 = 0;
986 m_controller->thread()->core->checksum(m_controller->thread()->core, &crc32, CHECKSUM_CRC32);
987
988 char gameTitle[17] = { '\0' };
989 mCore* core = m_controller->thread()->core;
990 core->getGameTitle(core, gameTitle);
991 title = gameTitle;
992
993#ifdef USE_SQLITE3
994 if (db && crc32 && NoIntroDBLookupGameByCRC(db, crc32, &game)) {
995 title = QLatin1String(game.name);
996 }
997#endif
998 MultiplayerController* multiplayer = m_controller->multiplayerController();
999 if (multiplayer && multiplayer->attached() > 1) {
1000 title += tr(" - Player %1 of %2").arg(multiplayer->playerId(m_controller.get()) + 1).arg(multiplayer->attached());
1001 for (QAction* action : m_nonMpActions) {
1002 action->setDisabled(true);
1003 }
1004 } else {
1005 for (QAction* action : m_nonMpActions) {
1006 action->setDisabled(false);
1007 }
1008 }
1009 }
1010 if (title.isNull()) {
1011 setWindowTitle(tr("%1 - %2").arg(projectName).arg(projectVersion));
1012 } else if (fps < 0) {
1013 setWindowTitle(tr("%1 - %2 - %3").arg(projectName).arg(title).arg(projectVersion));
1014 } else {
1015 setWindowTitle(tr("%1 - %2 (%3 fps) - %4").arg(projectName).arg(title).arg(fps).arg(projectVersion));
1016 }
1017}
1018
1019void Window::openStateWindow(LoadSave ls) {
1020 if (m_stateWindow) {
1021 return;
1022 }
1023 MultiplayerController* multiplayer = m_controller->multiplayerController();
1024 if (multiplayer && multiplayer->attached() > 1) {
1025 return;
1026 }
1027 bool wasPaused = m_controller->isPaused();
1028 m_stateWindow = new LoadSaveState(m_controller);
1029 connect(this, &Window::shutdown, m_stateWindow, &QWidget::close);
1030 connect(m_stateWindow, &LoadSaveState::closed, [this]() {
1031 detachWidget(m_stateWindow);
1032 m_stateWindow = nullptr;
1033 QMetaObject::invokeMethod(this, "setFocus", Qt::QueuedConnection);
1034 });
1035 if (!wasPaused) {
1036 m_controller->setPaused(true);
1037 connect(m_stateWindow, &LoadSaveState::closed, [this]() {
1038 if (m_controller) {
1039 m_controller->setPaused(false);
1040 }
1041 });
1042 }
1043 m_stateWindow->setAttribute(Qt::WA_DeleteOnClose);
1044 m_stateWindow->setMode(ls);
1045 updateFrame();
1046 attachWidget(m_stateWindow);
1047}
1048
1049void Window::setupMenu(QMenuBar* menubar) {
1050 menubar->clear();
1051 installEventFilter(&m_inputController);
1052
1053 QMenu* fileMenu = menubar->addMenu(tr("&File"));
1054 addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open),
1055 "loadROM");
1056#ifdef USE_SQLITE3
1057 addControlledAction(fileMenu, fileMenu->addAction(tr("Load ROM in archive..."), this, SLOT(selectROMInArchive())),
1058 "loadROMInArchive");
1059 addControlledAction(fileMenu, fileMenu->addAction(tr("Add folder to library..."), this, SLOT(addDirToLibrary())),
1060 "addDirToLibrary");
1061#endif
1062
1063 QAction* loadAlternateSave = new QAction(tr("Load alternate save..."), fileMenu);
1064 connect(loadAlternateSave, &QAction::triggered, [this]() { this->selectSave(false); });
1065 m_gameActions.append(loadAlternateSave);
1066 addControlledAction(fileMenu, loadAlternateSave, "loadAlternateSave");
1067
1068 QAction* loadTemporarySave = new QAction(tr("Load temporary save..."), fileMenu);
1069 connect(loadTemporarySave, &QAction::triggered, [this]() { this->selectSave(true); });
1070 m_gameActions.append(loadTemporarySave);
1071 addControlledAction(fileMenu, loadTemporarySave, "loadTemporarySave");
1072
1073 addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch");
1074
1075#ifdef M_CORE_GBA
1076 QAction* bootBIOS = new QAction(tr("Boot BIOS"), fileMenu);
1077 connect(bootBIOS, &QAction::triggered, [this]() {
1078 setController(m_manager->loadBIOS(PLATFORM_GBA, m_config->getOption("gba.bios")), QString());
1079 });
1080 addControlledAction(fileMenu, bootBIOS, "bootBIOS");
1081#endif
1082
1083 addControlledAction(fileMenu, fileMenu->addAction(tr("Replace ROM..."), this, SLOT(replaceROM())), "replaceROM");
1084
1085 QAction* romInfo = new QAction(tr("ROM &info..."), fileMenu);
1086 connect(romInfo, &QAction::triggered, openControllerTView<ROMInfo>());
1087 m_gameActions.append(romInfo);
1088 addControlledAction(fileMenu, romInfo, "romInfo");
1089
1090 m_mruMenu = fileMenu->addMenu(tr("Recent"));
1091
1092 fileMenu->addSeparator();
1093
1094 addControlledAction(fileMenu, fileMenu->addAction(tr("Make portable"), this, SLOT(tryMakePortable())), "makePortable");
1095
1096 fileMenu->addSeparator();
1097
1098 QAction* loadState = new QAction(tr("&Load state"), fileMenu);
1099 loadState->setShortcut(tr("F10"));
1100 connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); });
1101 m_gameActions.append(loadState);
1102 m_nonMpActions.append(loadState);
1103 m_platformActions.append(qMakePair(loadState, SUPPORT_GB | SUPPORT_GBA));
1104 addControlledAction(fileMenu, loadState, "loadState");
1105
1106 QAction* saveState = new QAction(tr("&Save state"), fileMenu);
1107 saveState->setShortcut(tr("Shift+F10"));
1108 connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); });
1109 m_gameActions.append(saveState);
1110 m_nonMpActions.append(saveState);
1111 m_platformActions.append(qMakePair(saveState, SUPPORT_GB | SUPPORT_GBA));
1112 addControlledAction(fileMenu, saveState, "saveState");
1113
1114 QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load"));
1115 QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save"));
1116
1117 QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu);
1118 connect(quickLoad, &QAction::triggered, [this] {
1119 m_controller->loadState();
1120 });
1121 m_gameActions.append(quickLoad);
1122 m_nonMpActions.append(quickLoad);
1123 m_platformActions.append(qMakePair(quickLoad, SUPPORT_GB | SUPPORT_GBA));
1124 addControlledAction(quickLoadMenu, quickLoad, "quickLoad");
1125
1126 QAction* quickSave = new QAction(tr("Save recent"), quickSaveMenu);
1127 connect(quickLoad, &QAction::triggered, [this] {
1128 m_controller->saveState();
1129 });
1130 m_gameActions.append(quickSave);
1131 m_nonMpActions.append(quickSave);
1132 addControlledAction(quickSaveMenu, quickSave, "quickSave");
1133 m_platformActions.append(qMakePair(quickSave, SUPPORT_GB | SUPPORT_GBA));
1134
1135 quickLoadMenu->addSeparator();
1136 quickSaveMenu->addSeparator();
1137
1138 QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu);
1139 undoLoadState->setShortcut(tr("F11"));
1140 connect(undoLoadState, &QAction::triggered, [this]() {
1141 m_controller->loadBackupState();
1142 });
1143 m_gameActions.append(undoLoadState);
1144 m_nonMpActions.append(undoLoadState);
1145 m_platformActions.append(qMakePair(undoLoadState, SUPPORT_GB | SUPPORT_GBA));
1146 addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState");
1147
1148 QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu);
1149 undoSaveState->setShortcut(tr("Shift+F11"));
1150 connect(undoSaveState, &QAction::triggered, [this]() {
1151 m_controller->saveBackupState();
1152 });
1153 m_gameActions.append(undoSaveState);
1154 m_nonMpActions.append(undoSaveState);
1155 m_platformActions.append(qMakePair(undoSaveState, SUPPORT_GB | SUPPORT_GBA));
1156 addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState");
1157
1158 quickLoadMenu->addSeparator();
1159 quickSaveMenu->addSeparator();
1160
1161 int i;
1162 for (i = 1; i < 10; ++i) {
1163 quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu);
1164 quickLoad->setShortcut(tr("F%1").arg(i));
1165 connect(quickLoad, &QAction::triggered, [this, i]() {
1166 m_controller->loadState(i);
1167 });
1168 m_gameActions.append(quickLoad);
1169 m_nonMpActions.append(quickLoad);
1170 m_platformActions.append(qMakePair(quickLoad, SUPPORT_GB | SUPPORT_GBA));
1171 addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i));
1172
1173 quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu);
1174 quickSave->setShortcut(tr("Shift+F%1").arg(i));
1175 connect(quickSave, &QAction::triggered, [this, i]() {
1176 m_controller->saveState(i);
1177 });
1178 m_gameActions.append(quickSave);
1179 m_nonMpActions.append(quickSave);
1180 m_platformActions.append(qMakePair(quickSave, SUPPORT_GB | SUPPORT_GBA));
1181 addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i));
1182 }
1183
1184 fileMenu->addSeparator();
1185 QAction* camImage = new QAction(tr("Load camera image..."), fileMenu);
1186 connect(camImage, &QAction::triggered, this, &Window::loadCamImage);
1187 addControlledAction(fileMenu, camImage, "loadCamImage");
1188
1189#ifdef M_CORE_GBA
1190 fileMenu->addSeparator();
1191 QAction* importShark = new QAction(tr("Import GameShark Save"), fileMenu);
1192 connect(importShark, &QAction::triggered, this, &Window::importSharkport);
1193 m_gameActions.append(importShark);
1194 m_platformActions.append(qMakePair(importShark, SUPPORT_GBA));
1195 addControlledAction(fileMenu, importShark, "importShark");
1196
1197 QAction* exportShark = new QAction(tr("Export GameShark Save"), fileMenu);
1198 connect(exportShark, &QAction::triggered, this, &Window::exportSharkport);
1199 m_gameActions.append(exportShark);
1200 m_platformActions.append(qMakePair(exportShark, SUPPORT_GBA));
1201 addControlledAction(fileMenu, exportShark, "exportShark");
1202#endif
1203
1204 fileMenu->addSeparator();
1205 m_multiWindow = new QAction(tr("New multiplayer window"), fileMenu);
1206 connect(m_multiWindow, &QAction::triggered, [this]() {
1207 GBAApp::app()->newWindow();
1208 });
1209 addControlledAction(fileMenu, m_multiWindow, "multiWindow");
1210
1211#ifndef Q_OS_MAC
1212 fileMenu->addSeparator();
1213#endif
1214
1215 QAction* about = new QAction(tr("About"), fileMenu);
1216 connect(about, &QAction::triggered, openTView<AboutScreen>());
1217 fileMenu->addAction(about);
1218
1219#ifndef Q_OS_MAC
1220 addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit");
1221#endif
1222
1223 QMenu* emulationMenu = menubar->addMenu(tr("&Emulation"));
1224 QAction* reset = new QAction(tr("&Reset"), emulationMenu);
1225 reset->setShortcut(tr("Ctrl+R"));
1226 connect(reset, &QAction::triggered, [this]() {
1227 m_controller->reset();
1228 });
1229 m_gameActions.append(reset);
1230 addControlledAction(emulationMenu, reset, "reset");
1231
1232 QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu);
1233 connect(shutdown, &QAction::triggered, [this]() {
1234 m_controller->stop();
1235 });
1236 m_gameActions.append(shutdown);
1237 addControlledAction(emulationMenu, shutdown, "shutdown");
1238
1239#ifdef M_CORE_GBA
1240 QAction* yank = new QAction(tr("Yank game pak"), emulationMenu);
1241 connect(yank, &QAction::triggered, [this]() {
1242 m_controller->yankPak();
1243 });
1244 m_gameActions.append(yank);
1245 m_platformActions.append(qMakePair(yank, SUPPORT_GBA));
1246 addControlledAction(emulationMenu, yank, "yank");
1247#endif
1248 emulationMenu->addSeparator();
1249
1250 QAction* pause = new QAction(tr("&Pause"), emulationMenu);
1251 pause->setChecked(false);
1252 pause->setCheckable(true);
1253 pause->setShortcut(tr("Ctrl+P"));
1254 connect(pause, &QAction::triggered, [this](bool paused) {
1255 m_controller->setPaused(paused);
1256 });
1257 connect(this, &Window::paused, [pause](bool paused) {
1258 pause->setChecked(paused);
1259 });
1260 m_gameActions.append(pause);
1261 addControlledAction(emulationMenu, pause, "pause");
1262
1263 QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu);
1264 frameAdvance->setShortcut(tr("Ctrl+N"));
1265 connect(frameAdvance, &QAction::triggered, [this]() {
1266 m_controller->frameAdvance();
1267 });
1268 m_gameActions.append(frameAdvance);
1269 addControlledAction(emulationMenu, frameAdvance, "frameAdvance");
1270
1271 emulationMenu->addSeparator();
1272
1273 m_inputController.inputIndex()->addItem(qMakePair([this]() {
1274 if (m_controller) {
1275 m_controller->setFastForward(true);
1276 }
1277 }, [this]() {
1278 if (m_controller) {
1279 m_controller->setFastForward(false);
1280 }
1281 }), tr("Fast forward (held)"), "holdFastForward", emulationMenu)->setShortcut(QKeySequence(Qt::Key_Tab)[0]);
1282
1283 QAction* turbo = new QAction(tr("&Fast forward"), emulationMenu);
1284 turbo->setCheckable(true);
1285 turbo->setChecked(false);
1286 turbo->setShortcut(tr("Shift+Tab"));
1287 connect(turbo, &QAction::triggered, [this](bool value) {
1288 m_controller->forceFastForward(value);
1289 });
1290 addControlledAction(emulationMenu, turbo, "fastForward");
1291 m_gameActions.append(turbo);
1292
1293 QMenu* ffspeedMenu = emulationMenu->addMenu(tr("Fast forward speed"));
1294 ConfigOption* ffspeed = m_config->addOption("fastForwardRatio");
1295 ffspeed->connect([this](const QVariant& value) {
1296 reloadConfig();
1297 }, this);
1298 ffspeed->addValue(tr("Unbounded"), -1.0f, ffspeedMenu);
1299 ffspeed->setValue(QVariant(-1.0f));
1300 ffspeedMenu->addSeparator();
1301 for (i = 2; i < 11; ++i) {
1302 ffspeed->addValue(tr("%0x").arg(i), i, ffspeedMenu);
1303 }
1304 m_config->updateOption("fastForwardRatio");
1305
1306 m_inputController.inputIndex()->addItem(qMakePair([this]() {
1307 if (m_controller) {
1308 m_controller->setRewinding(true);
1309 }
1310 }, [this]() {
1311 if (m_controller) {
1312 m_controller->setRewinding(false);
1313 }
1314 }), tr("Rewind (held)"), "holdRewind", emulationMenu)->setShortcut(QKeySequence("`")[0]);
1315
1316 QAction* rewind = new QAction(tr("Re&wind"), emulationMenu);
1317 rewind->setShortcut(tr("~"));
1318 connect(rewind, &QAction::triggered, [this]() {
1319 m_controller->rewind();
1320 });
1321 m_gameActions.append(rewind);
1322 m_nonMpActions.append(rewind);
1323 m_platformActions.append(qMakePair(rewind, SUPPORT_GB | SUPPORT_GBA));
1324 addControlledAction(emulationMenu, rewind, "rewind");
1325
1326 QAction* frameRewind = new QAction(tr("Step backwards"), emulationMenu);
1327 frameRewind->setShortcut(tr("Ctrl+B"));
1328 connect(frameRewind, &QAction::triggered, [this] () {
1329 m_controller->rewind(1);
1330 });
1331 m_gameActions.append(frameRewind);
1332 m_nonMpActions.append(frameRewind);
1333 m_platformActions.append(qMakePair(frameRewind, SUPPORT_GB | SUPPORT_GBA));
1334 addControlledAction(emulationMenu, frameRewind, "frameRewind");
1335
1336 ConfigOption* videoSync = m_config->addOption("videoSync");
1337 videoSync->addBoolean(tr("Sync to &video"), emulationMenu);
1338 videoSync->connect([this](const QVariant& value) {
1339 reloadConfig();
1340 }, this);
1341 m_config->updateOption("videoSync");
1342
1343 ConfigOption* audioSync = m_config->addOption("audioSync");
1344 audioSync->addBoolean(tr("Sync to &audio"), emulationMenu);
1345 audioSync->connect([this](const QVariant& value) {
1346 reloadConfig();
1347 }, this);
1348 m_config->updateOption("audioSync");
1349
1350 emulationMenu->addSeparator();
1351
1352 QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor"));
1353 QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu);
1354 connect(solarIncrease, &QAction::triggered, &m_inputController, &InputController::increaseLuminanceLevel);
1355 addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel");
1356
1357 QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu);
1358 connect(solarDecrease, &QAction::triggered, &m_inputController, &InputController::decreaseLuminanceLevel);
1359 addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel");
1360
1361 QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu);
1362 connect(maxSolar, &QAction::triggered, [this]() { m_inputController.setLuminanceLevel(10); });
1363 addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel");
1364
1365 QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu);
1366 connect(minSolar, &QAction::triggered, [this]() { m_inputController.setLuminanceLevel(0); });
1367 addControlledAction(solarMenu, minSolar, "minLuminanceLevel");
1368
1369 solarMenu->addSeparator();
1370 for (int i = 0; i <= 10; ++i) {
1371 QAction* setSolar = new QAction(tr("Brightness %1").arg(QString::number(i)), solarMenu);
1372 connect(setSolar, &QAction::triggered, [this, i]() {
1373 m_inputController.setLuminanceLevel(i);
1374 });
1375 addControlledAction(solarMenu, setSolar, QString("luminanceLevel.%1").arg(QString::number(i)));
1376 }
1377
1378 QMenu* avMenu = menubar->addMenu(tr("Audio/&Video"));
1379 QMenu* frameMenu = avMenu->addMenu(tr("Frame size"));
1380 for (int i = 1; i <= 6; ++i) {
1381 QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu);
1382 setSize->setCheckable(true);
1383 if (m_savedScale == i) {
1384 setSize->setChecked(true);
1385 }
1386 connect(setSize, &QAction::triggered, [this, i, setSize]() {
1387 showNormal();
1388 QSize size(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
1389 if (m_controller) {
1390 size = m_controller->screenDimensions();
1391 }
1392 size *= i;
1393 m_savedScale = i;
1394 m_config->setOption("scaleMultiplier", i); // TODO: Port to other
1395 resizeFrame(size);
1396 bool enableSignals = setSize->blockSignals(true);
1397 setSize->setChecked(true);
1398 setSize->blockSignals(enableSignals);
1399 });
1400 m_frameSizes[i] = setSize;
1401 addControlledAction(frameMenu, setSize, QString("frame%1x").arg(QString::number(i)));
1402 }
1403 QKeySequence fullscreenKeys;
1404#ifdef Q_OS_WIN
1405 fullscreenKeys = QKeySequence("Alt+Return");
1406#else
1407 fullscreenKeys = QKeySequence("Ctrl+F");
1408#endif
1409 addControlledAction(frameMenu, frameMenu->addAction(tr("Toggle fullscreen"), this, SLOT(toggleFullScreen()), fullscreenKeys), "fullscreen");
1410
1411 ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio");
1412 lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu);
1413 lockAspectRatio->connect([this](const QVariant& value) {
1414 m_display->lockAspectRatio(value.toBool());
1415 if (m_controller) {
1416 m_screenWidget->setLockAspectRatio(value.toBool());
1417 }
1418 }, this);
1419 m_config->updateOption("lockAspectRatio");
1420
1421 ConfigOption* lockIntegerScaling = m_config->addOption("lockIntegerScaling");
1422 lockIntegerScaling->addBoolean(tr("Force integer scaling"), avMenu);
1423 lockIntegerScaling->connect([this](const QVariant& value) {
1424 m_display->lockIntegerScaling(value.toBool());
1425 if (m_controller) {
1426 m_screenWidget->setLockIntegerScaling(value.toBool());
1427 }
1428 }, this);
1429 m_config->updateOption("lockIntegerScaling");
1430
1431 ConfigOption* resampleVideo = m_config->addOption("resampleVideo");
1432 resampleVideo->addBoolean(tr("Bilinear filtering"), avMenu);
1433 resampleVideo->connect([this](const QVariant& value) {
1434 m_display->filter(value.toBool());
1435 }, this);
1436 m_config->updateOption("resampleVideo");
1437
1438 QMenu* skipMenu = avMenu->addMenu(tr("Frame&skip"));
1439 ConfigOption* skip = m_config->addOption("frameskip");
1440 skip->connect([this](const QVariant& value) {
1441 reloadConfig();
1442 }, this);
1443 for (int i = 0; i <= 10; ++i) {
1444 skip->addValue(QString::number(i), i, skipMenu);
1445 }
1446 m_config->updateOption("frameskip");
1447
1448 avMenu->addSeparator();
1449
1450 ConfigOption* mute = m_config->addOption("mute");
1451 QAction* muteAction = mute->addBoolean(tr("Mute"), avMenu);
1452 mute->connect([this](const QVariant& value) {
1453 reloadConfig();
1454 }, this);
1455 m_config->updateOption("mute");
1456 addControlledAction(avMenu, muteAction, "mute");
1457
1458 QMenu* target = avMenu->addMenu(tr("FPS target"));
1459 ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget");
1460 fpsTargetOption->connect([this](const QVariant& value) {
1461 reloadConfig();
1462 }, this);
1463 fpsTargetOption->addValue(tr("15"), 15, target);
1464 fpsTargetOption->addValue(tr("30"), 30, target);
1465 fpsTargetOption->addValue(tr("45"), 45, target);
1466 fpsTargetOption->addValue(tr("Native (59.7)"), float(GBA_ARM7TDMI_FREQUENCY) / float(VIDEO_TOTAL_LENGTH), target);
1467 fpsTargetOption->addValue(tr("60"), 60, target);
1468 fpsTargetOption->addValue(tr("90"), 90, target);
1469 fpsTargetOption->addValue(tr("120"), 120, target);
1470 fpsTargetOption->addValue(tr("240"), 240, target);
1471 m_config->updateOption("fpsTarget");
1472
1473 avMenu->addSeparator();
1474
1475#ifdef USE_PNG
1476 QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu);
1477 screenshot->setShortcut(tr("F12"));
1478 connect(screenshot, &QAction::triggered, [this]() {
1479 m_controller->screenshot();
1480 });
1481 m_gameActions.append(screenshot);
1482 addControlledAction(avMenu, screenshot, "screenshot");
1483#endif
1484
1485#ifdef USE_FFMPEG
1486 QAction* recordOutput = new QAction(tr("Record output..."), avMenu);
1487 connect(recordOutput, &QAction::triggered, this, &Window::openVideoWindow);
1488 addControlledAction(avMenu, recordOutput, "recordOutput");
1489 m_gameActions.append(recordOutput);
1490#endif
1491
1492#ifdef USE_MAGICK
1493 QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu);
1494 connect(recordGIF, &QAction::triggered, this, &Window::openGIFWindow);
1495 addControlledAction(avMenu, recordGIF, "recordGIF");
1496#endif
1497
1498 QAction* recordVL = new QAction(tr("Record video log..."), avMenu);
1499 connect(recordVL, &QAction::triggered, this, &Window::startVideoLog);
1500 addControlledAction(avMenu, recordVL, "recordVL");
1501 m_gameActions.append(recordVL);
1502
1503 QAction* stopVL = new QAction(tr("Stop video log"), avMenu);
1504 connect(stopVL, &QAction::triggered, [this]() {
1505 m_controller->endVideoLog();
1506 });
1507 addControlledAction(avMenu, stopVL, "stopVL");
1508 m_gameActions.append(stopVL);
1509
1510#ifdef M_CORE_GB
1511 QAction* gbPrint = new QAction(tr("Game Boy Printer..."), avMenu);
1512 connect(gbPrint, &QAction::triggered, [this]() {
1513 PrinterView* view = new PrinterView(m_controller);
1514 openView(view);
1515 m_controller->attachPrinter();
1516
1517 });
1518 addControlledAction(avMenu, gbPrint, "gbPrint");
1519 m_gameActions.append(gbPrint);
1520#endif
1521
1522 avMenu->addSeparator();
1523 m_videoLayers = avMenu->addMenu(tr("Video layers"));
1524 m_audioChannels = avMenu->addMenu(tr("Audio channels"));
1525
1526 QAction* placementControl = new QAction(tr("Adjust layer placement..."), avMenu);
1527 connect(placementControl, &QAction::triggered, openControllerTView<PlacementControl>());
1528 m_gameActions.append(placementControl);
1529 addControlledAction(avMenu, placementControl, "placementControl");
1530
1531 QMenu* toolsMenu = menubar->addMenu(tr("&Tools"));
1532 QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu);
1533 connect(viewLogs, &QAction::triggered, m_logView, &QWidget::show);
1534 addControlledAction(toolsMenu, viewLogs, "viewLogs");
1535
1536 QAction* overrides = new QAction(tr("Game &overrides..."), toolsMenu);
1537 connect(overrides, &QAction::triggered, [this]() {
1538 if (!m_overrideView) {
1539 m_overrideView = std::move(std::make_unique<OverrideView>(m_config));
1540 if (m_controller) {
1541 m_overrideView->setController(m_controller);
1542 }
1543 connect(this, &Window::shutdown, m_overrideView.get(), &QWidget::close);
1544 }
1545 m_overrideView->show();
1546 m_overrideView->recheck();
1547 });
1548 addControlledAction(toolsMenu, overrides, "overrideWindow");
1549
1550 QAction* sensors = new QAction(tr("Game &Pak sensors..."), toolsMenu);
1551 connect(sensors, &QAction::triggered, [this]() {
1552 if (!m_sensorView) {
1553 m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController));
1554 if (m_controller) {
1555 m_sensorView->setController(m_controller);
1556 }
1557 connect(this, &Window::shutdown, m_sensorView.get(), &QWidget::close);
1558 }
1559 m_sensorView->show();
1560 });
1561 addControlledAction(toolsMenu, sensors, "sensorWindow");
1562
1563 QAction* cheats = new QAction(tr("&Cheats..."), toolsMenu);
1564 connect(cheats, &QAction::triggered, openControllerTView<CheatsView>());
1565 m_gameActions.append(cheats);
1566 m_platformActions.append(qMakePair(cheats, SUPPORT_GB | SUPPORT_GBA));
1567 addControlledAction(toolsMenu, cheats, "cheatsWindow");
1568
1569 toolsMenu->addSeparator();
1570 addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())),
1571 "settings");
1572
1573 toolsMenu->addSeparator();
1574
1575#ifdef USE_DEBUGGERS
1576 QAction* consoleWindow = new QAction(tr("Open debugger console..."), toolsMenu);
1577 connect(consoleWindow, &QAction::triggered, this, &Window::consoleOpen);
1578 addControlledAction(toolsMenu, consoleWindow, "debuggerWindow");
1579#endif
1580
1581#ifdef USE_GDB_STUB
1582 QAction* gdbWindow = new QAction(tr("Start &GDB server..."), toolsMenu);
1583 connect(gdbWindow, &QAction::triggered, this, &Window::gdbOpen);
1584 m_platformActions.append(qMakePair(gdbWindow, SUPPORT_GBA | SUPPORT_DS));
1585 m_gameActions.append(gdbWindow);
1586 addControlledAction(toolsMenu, gdbWindow, "gdbWindow");
1587#endif
1588 toolsMenu->addSeparator();
1589
1590 QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu);
1591 connect(paletteView, &QAction::triggered, openControllerTView<PaletteView>());
1592 m_gameActions.append(paletteView);
1593 m_platformActions.append(qMakePair(paletteView, SUPPORT_GB | SUPPORT_GBA));
1594 addControlledAction(toolsMenu, paletteView, "paletteWindow");
1595
1596 QAction* objView = new QAction(tr("View &sprites..."), toolsMenu);
1597 connect(objView, &QAction::triggered, openControllerTView<ObjView>());
1598 m_gameActions.append(objView);
1599 m_platformActions.append(qMakePair(objView, SUPPORT_GB | SUPPORT_GBA));
1600 addControlledAction(toolsMenu, objView, "spriteWindow");
1601
1602 QAction* tileView = new QAction(tr("View &tiles..."), toolsMenu);
1603 connect(tileView, &QAction::triggered, openControllerTView<TileView>());
1604 m_gameActions.append(tileView);
1605 m_platformActions.append(qMakePair(tileView, SUPPORT_GB | SUPPORT_GBA));
1606 addControlledAction(toolsMenu, tileView, "tileWindow");
1607
1608 QAction* mapView = new QAction(tr("View &map..."), toolsMenu);
1609 connect(mapView, &QAction::triggered, openControllerTView<MapView>());
1610 m_gameActions.append(mapView);
1611 addControlledAction(toolsMenu, mapView, "mapWindow");
1612
1613 QAction* memoryView = new QAction(tr("View memory..."), toolsMenu);
1614 connect(memoryView, &QAction::triggered, openControllerTView<MemoryView>());
1615 m_gameActions.append(memoryView);
1616 addControlledAction(toolsMenu, memoryView, "memoryView");
1617
1618 QAction* memorySearch = new QAction(tr("Search memory..."), toolsMenu);
1619 connect(memorySearch, &QAction::triggered, openControllerTView<MemorySearch>());
1620 m_gameActions.append(memorySearch);
1621 addControlledAction(toolsMenu, memorySearch, "memorySearch");
1622
1623#ifdef M_CORE_GBA
1624 QAction* ioViewer = new QAction(tr("View &I/O registers..."), toolsMenu);
1625 connect(ioViewer, &QAction::triggered, openControllerTView<IOViewer>());
1626 m_gameActions.append(ioViewer);
1627 m_platformActions.append(qMakePair(ioViewer, SUPPORT_GBA));
1628 addControlledAction(toolsMenu, ioViewer, "ioViewer");
1629#endif
1630
1631 ConfigOption* skipBios = m_config->addOption("skipBios");
1632 skipBios->connect([this](const QVariant& value) {
1633 reloadConfig();
1634 }, this);
1635
1636 ConfigOption* useBios = m_config->addOption("useBios");
1637 useBios->connect([this](const QVariant& value) {
1638 reloadConfig();
1639 }, this);
1640
1641 ConfigOption* buffers = m_config->addOption("audioBuffers");
1642 buffers->connect([this](const QVariant& value) {
1643 reloadConfig();
1644 }, this);
1645
1646 ConfigOption* sampleRate = m_config->addOption("sampleRate");
1647 sampleRate->connect([this](const QVariant& value) {
1648 reloadConfig();
1649 }, this);
1650
1651 ConfigOption* volume = m_config->addOption("volume");
1652 volume->connect([this](const QVariant& value) {
1653 reloadConfig();
1654 }, this);
1655
1656 ConfigOption* rewindEnable = m_config->addOption("rewindEnable");
1657 rewindEnable->connect([this](const QVariant& value) {
1658 reloadConfig();
1659 }, this);
1660
1661 ConfigOption* rewindBufferCapacity = m_config->addOption("rewindBufferCapacity");
1662 rewindBufferCapacity->connect([this](const QVariant& value) {
1663 reloadConfig();
1664 }, this);
1665
1666 ConfigOption* rewindSave = m_config->addOption("rewindSave");
1667 rewindBufferCapacity->connect([this](const QVariant& value) {
1668 reloadConfig();
1669 }, this);
1670
1671 ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections");
1672 allowOpposingDirections->connect([this](const QVariant& value) {
1673 reloadConfig();
1674 }, this);
1675
1676 ConfigOption* saveStateExtdata = m_config->addOption("saveStateExtdata");
1677 saveStateExtdata->connect([this](const QVariant& value) {
1678 reloadConfig();
1679 }, this);
1680
1681 ConfigOption* loadStateExtdata = m_config->addOption("loadStateExtdata");
1682 loadStateExtdata->connect([this](const QVariant& value) {
1683 reloadConfig();
1684 }, this);
1685
1686 ConfigOption* preload = m_config->addOption("preload");
1687 preload->connect([this](const QVariant& value) {
1688 m_manager->setPreload(value.toBool());
1689 }, this);
1690 m_config->updateOption("preload");
1691
1692 ConfigOption* showFps = m_config->addOption("showFps");
1693 showFps->connect([this](const QVariant& value) {
1694 if (!value.toInt()) {
1695 m_fpsTimer.stop();
1696 m_frameTimer.stop();
1697 updateTitle();
1698 } else if (m_controller) {
1699 m_fpsTimer.start();
1700 m_frameTimer.start();
1701 }
1702 }, this);
1703
1704 QAction* exitFullScreen = new QAction(tr("Exit fullscreen"), frameMenu);
1705 connect(exitFullScreen, &QAction::triggered, this, &Window::exitFullScreen);
1706 exitFullScreen->setShortcut(QKeySequence("Esc"));
1707 addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen");
1708
1709 m_inputController.inputIndex()->addItem(qMakePair([this]() {
1710 if (m_controller) {
1711 mCheatPressButton(m_controller->cheatDevice(), true);
1712 }
1713 }, [this]() {
1714 if (m_controller) {
1715 mCheatPressButton(m_controller->cheatDevice(), false);
1716 }
1717 }), tr("GameShark Button (held)"), "holdGSButton", toolsMenu)->setShortcut(QKeySequence(Qt::Key_Apostrophe)[0]);
1718
1719 for (QAction* action : m_gameActions) {
1720 action->setDisabled(true);
1721 }
1722
1723 m_inputController.rebuildIndex();
1724}
1725
1726void Window::attachWidget(QWidget* widget) {
1727 m_screenWidget->layout()->addWidget(widget);
1728 m_screenWidget->unsetCursor();
1729 static_cast<QStackedLayout*>(m_screenWidget->layout())->setCurrentWidget(widget);
1730}
1731
1732void Window::detachWidget(QWidget* widget) {
1733 m_screenWidget->layout()->removeWidget(widget);
1734}
1735
1736void Window::appendMRU(const QString& fname) {
1737 int index = m_mruFiles.indexOf(fname);
1738 if (index >= 0) {
1739 m_mruFiles.removeAt(index);
1740 }
1741 m_mruFiles.prepend(fname);
1742 while (m_mruFiles.size() > ConfigController::MRU_LIST_SIZE) {
1743 m_mruFiles.removeLast();
1744 }
1745 updateMRU();
1746}
1747
1748void Window::updateMRU() {
1749 if (!m_mruMenu) {
1750 return;
1751 }
1752 for (QAction* action : m_mruMenu->actions()) {
1753 delete action;
1754 }
1755 m_mruMenu->clear();
1756 int i = 0;
1757 for (const QString& file : m_mruFiles) {
1758 QAction* item = new QAction(QDir::toNativeSeparators(file).replace("&", "&&"), m_mruMenu);
1759 item->setShortcut(QString("Ctrl+%1").arg(i));
1760 connect(item, &QAction::triggered, [this, file]() {
1761 setController(m_manager->loadGame(file), file);
1762 });
1763 m_mruMenu->addAction(item);
1764 ++i;
1765 }
1766 m_config->setMRU(m_mruFiles);
1767 m_config->write();
1768 m_mruMenu->setEnabled(i > 0);
1769}
1770
1771QAction* Window::addControlledAction(QMenu* menu, QAction* action, const QString& name) {
1772 addHiddenAction(menu, action, name);
1773 menu->addAction(action);
1774 return action;
1775}
1776
1777QAction* Window::addHiddenAction(QMenu* menu, QAction* action, const QString& name) {
1778 m_inputController.inputIndex()->addItem(action, name, menu);
1779 action->setShortcutContext(Qt::WidgetShortcut);
1780 addAction(action);
1781 return action;
1782}
1783
1784void Window::focusCheck() {
1785 if (!m_config->getOption("pauseOnFocusLost").toInt() || !m_controller) {
1786 return;
1787 }
1788 if (QGuiApplication::focusWindow() && m_autoresume) {
1789 m_controller->setPaused(false);
1790 m_autoresume = false;
1791 } else if (!QGuiApplication::focusWindow() && !m_controller->isPaused()) {
1792 m_autoresume = true;
1793 m_controller->setPaused(true);
1794 }
1795}
1796
1797void Window::updateFrame() {
1798 QSize size = m_controller->screenDimensions();
1799 QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), size.width(), size.height(),
1800 size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888);
1801 QPixmap pixmap;
1802 pixmap.convertFromImage(currentImage);
1803 m_screenWidget->setPixmap(pixmap);
1804 emit paused(true);
1805}
1806
1807void Window::setController(CoreController* controller, const QString& fname) {
1808 if (!controller) {
1809 return;
1810 }
1811
1812 if (m_controller) {
1813 m_controller->stop();
1814 QTimer::singleShot(0, this, [this, controller, fname]() {
1815 setController(controller, fname);
1816 });
1817 return;
1818 }
1819 if (!fname.isEmpty()) {
1820 setWindowFilePath(fname);
1821 appendMRU(fname);
1822 }
1823
1824 m_controller = std::shared_ptr<CoreController>(controller);
1825 m_inputController.recalibrateAxes();
1826 m_controller->setInputController(&m_inputController);
1827 m_controller->setLogger(&m_log);
1828
1829 connect(this, &Window::shutdown, [this]() {
1830 if (!m_controller) {
1831 return;
1832 }
1833 m_controller->stop();
1834 });
1835
1836 connect(m_controller.get(), &CoreController::started, this, &Window::gameStarted);
1837 connect(m_controller.get(), &CoreController::started, &m_inputController, &InputController::suspendScreensaver);
1838 connect(m_controller.get(), &CoreController::stopping, this, &Window::gameStopped);
1839 {
1840 connect(m_controller.get(), &CoreController::stopping, [this]() {
1841 m_controller.reset();
1842 });
1843 }
1844 connect(m_controller.get(), &CoreController::stopping, &m_inputController, &InputController::resumeScreensaver);
1845 connect(m_controller.get(), &CoreController::paused, this, &Window::updateFrame);
1846
1847#ifndef Q_OS_MAC
1848 connect(m_controller.get(), &CoreController::paused, menuBar(), &QWidget::show);
1849 connect(m_controller.get(), &CoreController::unpaused, [this]() {
1850 if(isFullScreen()) {
1851 menuBar()->hide();
1852 }
1853 });
1854#endif
1855
1856 connect(m_controller.get(), &CoreController::paused, &m_inputController, &InputController::resumeScreensaver);
1857 connect(m_controller.get(), &CoreController::unpaused, [this]() {
1858 emit paused(false);
1859 });
1860
1861 connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing);
1862 connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw);
1863 connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw);
1864 connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing);
1865 connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing);
1866 connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted);
1867 connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage);
1868
1869 connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver);
1870 connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame);
1871 connect(m_controller.get(), &CoreController::crashed, this, &Window::gameCrashed);
1872 connect(m_controller.get(), &CoreController::failed, this, &Window::gameFailed);
1873 connect(m_controller.get(), &CoreController::unimplementedBiosCall, this, &Window::unimplementedBiosCall);
1874
1875#ifdef USE_GDB_STUB
1876 if (m_gdbController) {
1877 m_gdbController->setController(m_controller);
1878 }
1879#endif
1880
1881#ifdef USE_DEBUGGERS
1882 if (m_console) {
1883 m_console->setController(m_controller);
1884 }
1885#endif
1886
1887#ifdef USE_MAGICK
1888 if (m_gifView) {
1889 m_gifView->setController(m_controller);
1890 }
1891#endif
1892
1893#ifdef USE_FFMPEG
1894 if (m_videoView) {
1895 m_videoView->setController(m_controller);
1896 }
1897#endif
1898
1899 if (m_sensorView) {
1900 m_sensorView->setController(m_controller);
1901 }
1902
1903 if (m_overrideView) {
1904 m_overrideView->setController(m_controller);
1905 }
1906
1907 if (!m_pendingPatch.isEmpty()) {
1908 m_controller->loadPatch(m_pendingPatch);
1909 m_pendingPatch = QString();
1910 }
1911
1912 m_controller->loadConfig(m_config);
1913 m_controller->start();
1914}
1915
1916WindowBackground::WindowBackground(QWidget* parent)
1917 : QWidget(parent)
1918{
1919 setLayout(new QStackedLayout());
1920 layout()->setContentsMargins(0, 0, 0, 0);
1921}
1922
1923void WindowBackground::setPixmap(const QPixmap& pmap) {
1924 m_pixmap = pmap;
1925 update();
1926}
1927
1928void WindowBackground::setSizeHint(const QSize& hint) {
1929 m_sizeHint = hint;
1930}
1931
1932QSize WindowBackground::sizeHint() const {
1933 return m_sizeHint;
1934}
1935
1936void WindowBackground::setCenteredAspectRatio(int width, int height) {
1937 m_centered = true;
1938 m_lockAspectRatio = true;
1939 setDimensions(width, height);
1940}
1941
1942void WindowBackground::setDimensions(int width, int height) {
1943 m_aspectWidth = width;
1944 m_aspectHeight = height;
1945}
1946
1947void WindowBackground::setLockIntegerScaling(bool lock) {
1948 m_lockIntegerScaling = lock;
1949}
1950
1951void WindowBackground::setLockAspectRatio(bool lock) {
1952 m_centered = false;
1953 m_lockAspectRatio = lock;
1954}
1955
1956void WindowBackground::paintEvent(QPaintEvent* event) {
1957 QWidget::paintEvent(event);
1958 const QPixmap& logo = pixmap();
1959 QPainter painter(this);
1960 painter.setRenderHint(QPainter::SmoothPixmapTransform);
1961 painter.fillRect(QRect(QPoint(), size()), Qt::black);
1962 QSize s = size();
1963 QSize ds = s;
1964 if (m_centered) {
1965 if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) {
1966 ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight);
1967 } else if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) {
1968 ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth);
1969 }
1970 } else if (m_lockAspectRatio) {
1971 if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) {
1972 ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight);
1973 } else if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) {
1974 ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth);
1975 }
1976 }
1977 if (m_lockIntegerScaling) {
1978 ds.setWidth(ds.width() - ds.width() % m_aspectWidth);
1979 ds.setHeight(ds.height() - ds.height() % m_aspectHeight);
1980 }
1981 QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
1982 QRect full(origin, ds);
1983 painter.drawPixmap(full, logo);
1984}