Files
RMG/Source/RMG/UserInterface/MainWindow.cpp
T
2026-01-07 02:52:47 +01:00

2869 lines
91 KiB
C++

/*
* Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG
* Copyright (C) 2020-2025 Rosalie Wanders <rosalie@mailbox.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "MainWindow.hpp"
#include "UserInterface/Dialog/AboutDialog.hpp"
#include "Dialog/Cheats/CheatsDialog.hpp"
#include "Dialog/SettingsDialog.hpp"
#include "Dialog/RomInfoDialog.hpp"
#ifdef UPDATER
#include "UserInterface/Dialog/Update/DownloadUpdateDialog.hpp"
#include "UserInterface/Dialog/Update/InstallUpdateDialog.hpp"
#include "UserInterface/Dialog/Update/UpdateDialog.hpp"
#endif // UPDATER
#ifdef NETPLAY
#include "Dialog/Netplay/NetplaySessionBrowserDialog.hpp"
#include "Dialog/Netplay/CreateNetplaySessionDialog.hpp"
#include "Dialog/Netplay/NetplaySessionDialog.hpp"
#endif // NETPLAY
#include "UserInterface/EventFilter.hpp"
#include "Utilities/QtKeyToSdl3Key.hpp"
#include "Utilities/QtMessageBox.hpp"
#include "OnScreenDisplay.hpp"
#include "Callbacks.hpp"
#include "VidExt.hpp"
#ifdef UPDATER
#include <QNetworkAccessManager>
#include <QJsonDocument>
#include <QJsonObject>
#endif // UPDATER
#ifdef NETPLAY
#include <QWebSocket>
#endif // NETPLAY
#include <QCoreApplication>
#include <QDesktopServices>
#include <QGuiApplication>
#include <QStyleFactory>
#include <QActionGroup>
#include <QFileDialog>
#include <QMessageBox>
#include <QMimeData>
#include <QSettings>
#include <QStatusBar>
#include <QMenuBar>
#include <QString>
#include <QTimer>
#include <QDir>
#include <QUrl>
#ifdef KCA_DRAG_DROP
#include <KUrlMimeData>
#endif
#include <cstdlib>
#include <cmath>
#include <RMG-Core/CachedRomHeaderAndSettings.hpp>
#include <RMG-Core/SpeedLimiter.hpp>
#include <RMG-Core/Directories.hpp>
#include <RMG-Core/SpeedFactor.hpp>
#include <RMG-Core/Screenshot.hpp>
#include <RMG-Core/Emulation.hpp>
#include <RMG-Core/SaveState.hpp>
#include <RMG-Core/Settings.hpp>
#include <RMG-Core/Netplay.hpp>
#include <RMG-Core/Version.hpp>
#include <RMG-Core/Cheats.hpp>
#include <RMG-Core/Volume.hpp>
#include <RMG-Core/Error.hpp>
#include <RMG-Core/Video.hpp>
#include <RMG-Core/Core.hpp>
#include <RMG-Core/Key.hpp>
using namespace UserInterface;
using namespace Utilities;
MainWindow::MainWindow() : QMainWindow(nullptr)
{
}
MainWindow::~MainWindow()
{
}
bool MainWindow::Init(QApplication* app, bool showUI, bool launchROM)
{
if (!CoreInit())
{
this->showErrorMessage("CoreInit() Failed", QString::fromStdString(CoreGetError()));
return false;
}
if (!CoreApplyPluginSettings())
{
this->showErrorMessage("CoreApplyPluginSettings() Failed", QString::fromStdString(CoreGetError()));
}
this->configureTheme(app);
this->initializeUI(launchROM);
this->initializeActions();
this->configureUI(app, showUI);
this->connectActionSignals();
this->configureActions();
this->updateActions(false, false);
#ifdef UPDATER
this->checkForUpdates(true, false);
#else
this->action_Help_Update->setVisible(false);
#endif // UPDATER
#ifndef NETPLAY
this->menuNetplay->menuAction()->setVisible(false);
#endif // NETPLAY
this->initializeEmulationThread();
this->connectEmulationThreadSignals();
if (!SetupVidExt(this->emulationThread, this, &this->ui_Widget_OpenGL, &this->ui_Widget_Vulkan))
{
this->showErrorMessage("SetupVidExt() Failed", QString::fromStdString(CoreGetError()));
return false;
}
this->coreCallBacks = new CoreCallbacks(this);
// connect signals early due to pending debug callbacks
connect(coreCallBacks, &CoreCallbacks::OnCoreDebugCallback, this, &MainWindow::on_Core_DebugCallback);
connect(coreCallBacks, &CoreCallbacks::OnCoreStateCallback, this, &MainWindow::on_Core_StateCallback);
connect(app, &QGuiApplication::applicationStateChanged, this, &MainWindow::on_QGuiApplication_applicationStateChanged);
if (!this->coreCallBacks->Init())
{
this->showErrorMessage("CoreCallbacks::Init() Failed", QString::fromStdString(CoreGetError()));
return false;
}
// add actions when there's no UI
if (!showUI)
{
this->addActions();
}
return true;
}
void MainWindow::OpenROM(QString file, QString disk, bool fullscreen, bool quitAfterEmulation, int stateSlot)
{
this->ui_LaunchInFullscreen = fullscreen;
this->ui_QuitAfterEmulation = quitAfterEmulation;
// ensure we don't switch to the ROM browser
// because it can cause a slight flicker,
// if we just ensure the UI is in an emulation
// state, then the transition will be smoother
this->updateUI(true, false);
this->launchEmulationThread(file, disk, true, stateSlot);
}
void MainWindow::closeEvent(QCloseEvent *event)
{
bool inEmulation = this->emulationThread->isRunning();
if (this->ui_ShowUI &&
!this->ui_ForceClose &&
inEmulation &&
CoreSettingsGetBoolValue(SettingsID::GUI_ConfirmExitWhileInGame))
{
bool skipExitConfirmation = false;
bool ret = QtMessageBox::Question(this, "Are you sure you want to exit RMG?", "Don't ask for confirmation again", skipExitConfirmation);
if (!ret)
{
event->ignore();
return;
}
// only save setting when user accepted
CoreSettingsSetValue(SettingsID::GUI_ConfirmExitWhileInGame, !skipExitConfirmation);
}
// we have to make sure we save the geomtry
// for the ROM browser when emulation
// isn't running (or hasn't run at all)
if (!this->ui_QuitAfterEmulation &&
!inEmulation)
{
this->storeGeometry();
}
// store toolbar location to settings
Qt::ToolBarArea toolbarArea = this->toolBarArea(this->toolBar);
CoreSettingsSetValue(SettingsID::GUI_ToolbarArea, this->getToolbarSettingAreaFromArea(toolbarArea));
// attempt to shutdown emulation
this->ui_NoSwitchToRomBrowser = true;
this->on_Action_System_Shutdown();
// wait until emulation has shut down
while (this->emulationThread->isRunning())
{
QCoreApplication::processEvents();
}
this->ui_Widget_RomBrowser->StopRefreshRomList();
this->coreCallBacks->Stop();
#ifdef NETPLAY
if (this->netplaySessionDialog != nullptr)
{
this->netplaySessionDialog->close();
}
#endif // NETPLAY
this->logDialog.close();
CoreSettingsSave();
CoreShutdown();
QMainWindow::closeEvent(event);
}
void MainWindow::initializeUI(bool launchROM)
{
this->setupUi(this);
this->ui_Widgets = new QStackedWidget(this);
this->ui_Widget_RomBrowser = new Widget::RomBrowserWidget(this);
this->ui_Widget_Dummy = new Widget::DummyWidget(this);
this->ui_EventFilter = new EventFilter(this);
this->ui_StatusBar_Label = new QLabel(this);
this->ui_StatusBar_RenderModeLabel = new QLabel(this);
// only start refreshing the ROM browser
// when RMG isn't launched with a ROM
// specified on the commandline
if (!launchROM)
{
this->ui_Widget_RomBrowser->RefreshRomList();
}
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::PlayGame, this,
&MainWindow::on_RomBrowser_PlayGame);
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::PlayGameWith, this,
&MainWindow::on_RomBrowser_PlayGameWith);
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::PlayGameWithDisk, this,
&MainWindow::on_RomBrowser_PlayGameWithDisk);
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::PlayGameWithSlot, this,
&MainWindow::on_RomBrowser_PlayGameWithSlot);
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::EditGameSettings, this,
&MainWindow::on_RomBrowser_EditGameSettings);
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::EditGameInputSettings, this,
&MainWindow::on_RomBrowser_EditGameInputSettings);
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::Cheats, this,
&MainWindow::on_RomBrowser_Cheats);
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::ChangeRomDirectory, this,
&MainWindow::on_RomBrowser_ChangeRomDirectory);
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::RomInformation, this,
&MainWindow::on_RomBrowser_RomInformation);
connect(this->ui_Widget_RomBrowser, &Widget::RomBrowserWidget::FileDropped, this,
&MainWindow::on_EventFilter_FileDropped);
connect(this->ui_EventFilter, &EventFilter::on_EventFilter_KeyPressed, this,
&MainWindow::on_EventFilter_KeyPressed);
connect(this->ui_EventFilter, &EventFilter::on_EventFilter_KeyReleased, this,
&MainWindow::on_EventFilter_KeyReleased);
connect(this->ui_EventFilter, &EventFilter::on_EventFilter_FileDropped, this,
&MainWindow::on_EventFilter_FileDropped);
}
void MainWindow::configureUI(QApplication* app, bool showUI)
{
this->setCentralWidget(this->ui_Widgets);
/*QString geometry = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::RomBrowser_Geometry));
bool maximized = CoreSettingsGetBoolValue(SettingsID::RomBrowser_Maximized);
if (maximized)
{
this->showMaximized();
}
else if (!geometry.isEmpty())
{
this->restoreGeometry(QByteArray::fromBase64(geometry.toLocal8Bit()));
}*/
this->ui_ShowUI = showUI;
if (this->ui_ShowUI)
{
this->ui_ShowMenubar = true;
this->ui_ShowToolbar = CoreSettingsGetBoolValue(SettingsID::GUI_Toolbar);
this->ui_ShowStatusbar = CoreSettingsGetBoolValue(SettingsID::GUI_StatusBar);
}
else
{
this->ui_ShowMenubar = false;
this->ui_ShowToolbar = false;
this->ui_ShowStatusbar = false;
}
this->menuBar()->setVisible(this->ui_ShowMenubar);
this->toolBar->setVisible(this->ui_ShowToolbar);
this->statusBar()->setVisible(this->ui_ShowStatusbar);
this->statusBar()->addPermanentWidget(this->ui_StatusBar_Label, 99);
this->statusBar()->addPermanentWidget(this->ui_StatusBar_RenderModeLabel, 1);
// set toolbar position according to setting
int toolbarAreaSetting = CoreSettingsGetIntValue(SettingsID::GUI_ToolbarArea);
Qt::ToolBarArea toolbarArea = this->getToolbarAreaFromSettingArea(toolbarAreaSetting);
if (this->toolBarArea(this->toolBar) != toolbarArea)
{
this->removeToolBar(this->toolBar);
this->addToolBar(toolbarArea, this->toolBar);
}
this->ui_StatusBarTimerTimeout = CoreSettingsGetIntValue(SettingsID::GUI_StatusbarMessageDuration);
this->ui_Widgets->addWidget(this->ui_Widget_RomBrowser);
this->ui_Widgets->addWidget(this->ui_Widget_Dummy);
this->ui_Widgets->setCurrentWidget(this->ui_Widget_RomBrowser);
this->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
this->installEventFilter(this->ui_EventFilter);
this->ui_Widget_Dummy->installEventFilter(this->ui_EventFilter);
this->ui_WindowTitle = this->getWindowTitle();
this->setWindowTitle(this->ui_WindowTitle);
}
void MainWindow::configureTheme(QApplication* app)
{
// we have to retrieve the fallback icon theme
// before applying the app theme
QString fallbackThemeName = QIcon::themeName();
// set theme style
QString fallbackStyleSheet = "QTableView { border: none; color: #0096d3; selection-color: #FFFFFF; selection-background-color: #0096d3; }";
this->setStyleSheet(fallbackStyleSheet);
// set application theme
QString theme = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::GUI_Theme));
if (theme == "Native")
{
// do nothing
}
#ifdef _WIN32
else if (theme == "Windows Vista")
{
app->setStyle(QStyleFactory::create("WindowsVista"));
}
#endif
else if (theme == "Fusion")
{
app->setPalette(QApplication::style()->standardPalette());
app->setStyleSheet(QString());
app->setStyle(QStyleFactory::create("Fusion"));
}
else if (theme == "Fusion Dark")
{
// adapted from https://gist.github.com/QuantumCD/6245215
app->setStyle(QStyleFactory::create("Fusion"));
const QColor lighterGray(75, 75, 75);
const QColor darkGray(53, 53, 53);
const QColor gray(128, 128, 128);
const QColor black(25, 25, 25);
const QColor blue(198, 238, 255);
QPalette darkPalette;
darkPalette.setColor(QPalette::Window, darkGray);
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Base, black);
darkPalette.setColor(QPalette::AlternateBase, darkGray);
darkPalette.setColor(QPalette::ToolTipBase, darkGray);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Button, darkGray);
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Link, blue);
darkPalette.setColor(QPalette::Highlight, lighterGray);
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker());
darkPalette.setColor(QPalette::Active, QPalette::Button, darkGray);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray);
app->setPalette(darkPalette);
app->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme.endsWith(".qss"))
{
QString themePath;
themePath = QString::fromStdString(CoreGetSharedDataDirectory().string());
themePath += CORE_DIR_SEPERATOR_STR;
themePath += "Styles";
themePath += CORE_DIR_SEPERATOR_STR;
themePath += theme;
// use Fusion as a base for the stylesheet
app->setPalette(QApplication::style()->standardPalette());
app->setStyleSheet(QString());
app->setStyle(QStyleFactory::create("Fusion"));
// set the stylesheet theme,
// if the file exists and can be opened
QFile themeFile(themePath);
if (themeFile.exists() &&
themeFile.open(QIODevice::ReadOnly))
{
app->setStyleSheet(themeFile.readAll());
}
}
// set application icon theme
QString iconTheme = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::GUI_IconTheme));
if (iconTheme == "White" || iconTheme == "Black")
{
QIcon::setThemeName(iconTheme.toLower());
}
else
{ // fallback to automatic
QPalette palette = app->palette();
bool dark = palette.windowText().color().value() > palette.window().color().value();
QIcon::setThemeName(dark ? "white" : "black");
}
// fallback for icons we don't provide (i.e standard system icons)
QIcon::setFallbackThemeName(fallbackThemeName);
}
QString MainWindow::getWindowTitle(void)
{
const QDate currentDate = QDateTime::currentDateTime().date();
const QStringList firstWordList = {
{
"lesbian",
"gay",
"bisexual",
"transgender",
"queer",
}};
const QStringList secondWordList = {
{
" rights!!!",
"s rise up!!!"
}};
QString windowTitle = QCoreApplication::applicationName();
// initialize random seed
srand(time(nullptr));
bool showCustomWindowTitle = (rand() % 10) < 3;
if (showCustomWindowTitle && currentDate.month() == 3 && currentDate.day() == 31)
{
QString secondWord = secondWordList.at(rand() % secondWordList.count());
windowTitle += " (transgender" + secondWord + ")";
}
else if (showCustomWindowTitle && currentDate.month() == 6)
{
QString firstWsord = firstWordList.at(rand() % firstWordList.count());
QString secondWord = secondWordList.at(rand() % secondWordList.count());
windowTitle += " (" + firstWsord + secondWord + ")";
}
else
{
windowTitle += " (";
windowTitle += QString::fromStdString(CoreGetVersion());
windowTitle += ")";
}
return windowTitle;
}
void MainWindow::showErrorMessage(QString text, QString details, bool force)
{
// fallback to helper when forced
if (force)
{
QtMessageBox::Error(this, text, details);
return;
}
// update the message box list and ensure
// that we don't already have one open with
// the error that we want to display
for (const QMessageBox* messageBox : this->ui_MessageBoxList)
{
if (!messageBox->isVisible())
{
this->ui_MessageBoxList.removeOne(messageBox);
continue;
}
if (messageBox->text() == text &&
messageBox->detailedText() == details)
{
return;
}
}
// ensure we only display 10 errors at
// the same time, to prevent message dialog spam
if (this->ui_MessageBoxList.size() >= 10)
{
return;
}
QMessageBox* msgBox = new QMessageBox(this);
msgBox->setIcon(QMessageBox::Icon::Critical);
msgBox->setWindowTitle("Error");
msgBox->setText(text);
msgBox->setDetailedText(details);
msgBox->addButton(QMessageBox::Ok);
// expand details by default
if (!details.isEmpty())
{
for (const auto& button : msgBox->buttons())
{
if (msgBox->buttonRole(button) == QMessageBox::ActionRole)
{
button->click();
break;
}
}
}
msgBox->show();
this->ui_MessageBoxList.append(msgBox);
}
void MainWindow::updateUI(bool inEmulation, bool isPaused)
{
if (!this->ui_NoSwitchToRomBrowser)
{
this->updateActions(inEmulation, isPaused);
}
if (inEmulation)
{
CoreRomSettings settings;
CoreGetCurrentRomSettings(settings);
if (!settings.GoodName.empty())
{
QString goodName = QString::fromStdString(settings.GoodName);
if (goodName.endsWith("(unknown rom)") ||
goodName.endsWith("(unknown disk)"))
{
std::filesystem::path romPath;
if (CoreGetRomPath(romPath))
{
goodName = QString::fromStdString(romPath.filename().string());
}
}
this->setWindowTitle(goodName + QString(" - ") + this->ui_WindowTitle);
}
if (this->ui_VidExtRenderMode == VidExtRenderMode::OpenGL)
{
if (QSurfaceFormat::defaultFormat().renderableType() == QSurfaceFormat::OpenGLES)
{
this->ui_StatusBar_RenderModeLabel->setText("OpenGL ES");
}
else
{
this->ui_StatusBar_RenderModeLabel->setText("OpenGL");
}
this->ui_Widgets->setCurrentWidget(this->ui_Widget_OpenGL->GetWidget());
}
else if (this->ui_VidExtRenderMode == VidExtRenderMode::Vulkan)
{
this->ui_StatusBar_RenderModeLabel->setText("Vulkan");
this->ui_Widgets->setCurrentWidget(this->ui_Widget_Vulkan->GetWidget());
}
else
{
// when the video extension hasn't been initialized correctly
// yet, we'll show a dummy widget with a black color pallete
// to minimize the flicker that would occur when switching
// from the ROM browser to the render widget when you i.e
// launch RMG with a ROM on the commandline or drag & drop
this->ui_Widgets->setCurrentWidget(this->ui_Widget_Dummy);
}
this->storeGeometry();
}
else if (!this->ui_NoSwitchToRomBrowser)
{
this->setWindowTitle(this->ui_WindowTitle);
this->ui_Widgets->setCurrentWidget(this->ui_Widget_RomBrowser);
this->ui_StatusBar_RenderModeLabel->clear();
this->loadGeometry();
}
else
{
this->ui_NoSwitchToRomBrowser = false;
}
// update timer timeout
this->ui_StatusBarTimerTimeout = CoreSettingsGetIntValue(SettingsID::GUI_StatusbarMessageDuration);
}
void MainWindow::storeGeometry(void)
{
if (this->ui_Geometry_Saved)
{
return;
}
this->ui_Geometry = this->saveGeometry();
this->ui_Geometry_Maximized = this->isMaximized();
this->ui_Geometry_Saved = true;
std::string geometryStr = this->ui_Geometry.toBase64().toStdString();
CoreSettingsSetValue(SettingsID::RomBrowser_Geometry, geometryStr);
CoreSettingsSetValue(SettingsID::RomBrowser_Maximized, this->ui_Geometry_Maximized);
}
void MainWindow::loadGeometry(void)
{
if (!this->ui_Geometry_Saved)
{
return;
}
if (this->ui_Geometry_Maximized)
{
this->showMaximized();
}
else
{
this->restoreGeometry(this->ui_Geometry);
}
if (this->isFullScreen())
{
this->showNormal();
}
if (this->ui_ShowMenubar && this->menuBar()->isHidden())
{
this->menuBar()->show();
}
else if (!this->ui_ShowMenubar && !this->menuBar()->isHidden())
{
this->menuBar()->hide();
}
if (this->ui_ShowToolbar && this->toolBar->isHidden())
{
this->toolBar->show();
}
else if (!this->ui_ShowToolbar && !this->toolBar->isHidden())
{
this->toolBar->hide();
}
if (this->ui_ShowStatusbar && this->statusBar()->isHidden())
{
this->statusBar()->show();
}
else if (!this->ui_ShowStatusbar && !this->statusBar()->isHidden())
{
this->statusBar()->hide();
}
this->ui_Geometry_Saved = false;
}
void MainWindow::initializeEmulationThread(void)
{
this->emulationThread = new Thread::EmulationThread(this);
}
void MainWindow::connectEmulationThreadSignals(void)
{
connect(this->emulationThread, &Thread::EmulationThread::on_Emulation_Finished, this,
&MainWindow::on_Emulation_Finished);
connect(this->emulationThread, &Thread::EmulationThread::on_Emulation_Started, this,
&MainWindow::on_Emulation_Started);
connect(this->emulationThread, &Thread::EmulationThread::on_VidExt_Init, this, &MainWindow::on_VidExt_Init,
Qt::BlockingQueuedConnection);
connect(this->emulationThread, &Thread::EmulationThread::on_VidExt_SetupOGL, this, &MainWindow::on_VidExt_SetupOGL,
Qt::BlockingQueuedConnection);
connect(this->emulationThread, &Thread::EmulationThread::on_VidExt_SetWindowedMode, this,
&MainWindow::on_VidExt_SetWindowedMode, Qt::BlockingQueuedConnection);
connect(this->emulationThread, &Thread::EmulationThread::on_VidExt_SetFullscreenMode, this,
&MainWindow::on_VidExt_SetFullscreenMode, Qt::BlockingQueuedConnection);
connect(this->emulationThread, &Thread::EmulationThread::on_VidExt_ResizeWindow, this,
&MainWindow::on_VidExt_ResizeWindow, Qt::BlockingQueuedConnection);
connect(this->emulationThread, &Thread::EmulationThread::on_VidExt_ToggleFS, this, &MainWindow::on_VidExt_ToggleFS,
Qt::BlockingQueuedConnection);
connect(this->emulationThread, &Thread::EmulationThread::on_VidExt_Quit, this, &MainWindow::on_VidExt_Quit,
Qt::BlockingQueuedConnection);
}
void MainWindow::launchEmulationThread(QString cartRom, QString address, int port, int player)
{
CoreSettingsSave();
if (this->emulationThread->isRunning())
{
this->on_Action_System_Shutdown();
while (this->emulationThread->isRunning())
{
QCoreApplication::processEvents();
}
}
this->emulationThread->SetNetplay(address, port, player);
this->launchEmulationThread(cartRom, "", false, -1, true);
}
void MainWindow::launchEmulationThread(QString cartRom, QString diskRom, bool refreshRomListAfterEmulation, int slot, bool netplay, bool dragdrop)
{
#ifdef NETPLAY
if (this->netplaySessionDialog != nullptr && !netplay)
{
this->showErrorMessage("EmulationThread::run Failed", "Cannot start emulation when netplay session is active");
return;
}
#endif // NETPLAY
if (!dragdrop && this->emulationThread->isRunning())
{
this->showErrorMessage("EmulationThread::run Failed", "Cannot start emulation when emulation is already running or being started");
return;
}
CoreSettingsSave();
if (this->emulationThread->isRunning())
{
this->on_Action_System_Shutdown();
while (this->emulationThread->isRunning())
{
QCoreApplication::processEvents();
}
}
this->ui_RefreshRomListAfterEmulation = refreshRomListAfterEmulation || this->ui_Widget_RomBrowser->IsRefreshingRomList();
if (this->ui_RefreshRomListAfterEmulation)
{
this->ui_Widget_RomBrowser->StopRefreshRomList();
}
if (!CoreArePluginsReady())
{
// always go back to ROM Browser
this->ui_NoSwitchToRomBrowser = false;
this->updateUI(false, false);
this->showErrorMessage("CoreArePluginsReady() Failed", QString::fromStdString(CoreGetError()));
return;
}
if (this->ui_LaunchInFullscreen || CoreSettingsGetBoolValue(SettingsID::GUI_AutomaticFullscreen))
{
this->ui_FullscreenTimerId = this->startTimer(100);
this->ui_LaunchInFullscreen = false;
}
this->ui_LoadSaveStateSlotCounter = 0;
this->ui_LoadSaveStateSlot = slot;
if (slot != -1)
{
this->ui_LoadSaveStateSlotTimerId = this->startTimer(100);
}
this->ui_CheckVideoSizeTimerId = this->startTimer(2000);
this->ui_HideCursorInEmulation = CoreSettingsGetBoolValue(SettingsID::GUI_HideCursorInEmulation);
this->ui_HideCursorInFullscreenEmulation = CoreSettingsGetBoolValue(SettingsID::GUI_HideCursorInFullscreenEmulation);
if (this->ui_ShowUI)
{
this->ui_ShowToolbar = CoreSettingsGetBoolValue(SettingsID::GUI_Toolbar);
this->ui_ShowStatusbar = CoreSettingsGetBoolValue(SettingsID::GUI_StatusBar);
}
this->emulationThread->SetRomFile(cartRom);
this->emulationThread->SetDiskFile(diskRom);
this->emulationThread->start();
}
void MainWindow::updateActions(bool inEmulation, bool isPaused)
{
QString keyBinding;
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_StartROM));
this->action_System_StartRom->setShortcut(QKeySequence(keyBinding));
this->action_System_StartRom->setEnabled(!inEmulation);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_StartCombo));
this->action_System_OpenCombo->setShortcut(QKeySequence(keyBinding));
this->action_System_OpenCombo->setEnabled(!inEmulation);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Shutdown));
this->action_System_Shutdown->setShortcut(QKeySequence(keyBinding));
this->action_System_Shutdown->setEnabled(inEmulation);
this->menuReset->setEnabled(inEmulation && !CoreHasInitNetplay());
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_SoftReset));
this->action_System_SoftReset->setEnabled(inEmulation && !CoreHasInitNetplay());
this->action_System_SoftReset->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_HardReset));
this->action_System_HardReset->setEnabled(inEmulation && !CoreHasInitNetplay());
this->action_System_HardReset->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Resume));
this->action_System_Pause->setChecked(isPaused);
this->action_System_Pause->setEnabled(inEmulation && !CoreHasInitNetplay());
this->action_System_Pause->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Screenshot));
this->action_System_Screenshot->setEnabled(inEmulation);
this->action_System_Screenshot->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_LimitFPS));
this->action_System_LimitFPS->setEnabled(inEmulation && !CoreHasInitNetplay());
this->action_System_LimitFPS->setShortcut(QKeySequence(keyBinding));
this->action_System_LimitFPS->setChecked(CoreIsSpeedLimiterEnabled());
this->menuSpeedFactor->setEnabled(inEmulation && !CoreHasInitNetplay());
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_SaveState));
this->action_System_SaveState->setEnabled(inEmulation);
this->action_System_SaveState->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_SaveAs));
this->action_System_SaveAs->setEnabled(inEmulation);
this->action_System_SaveAs->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_LoadState));
this->action_System_LoadState->setEnabled(inEmulation && !CoreHasInitNetplay());
this->action_System_LoadState->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Load));
this->action_System_Load->setEnabled(inEmulation && !CoreHasInitNetplay());
this->action_System_Load->setShortcut(QKeySequence(keyBinding));
this->menuCurrent_Save_State->setEnabled(inEmulation);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Cheats));
this->action_System_Cheats->setEnabled(inEmulation && !CoreHasInitNetplay());
this->action_System_Cheats->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_GSButton));
this->action_System_GSButton->setEnabled(inEmulation && !CoreHasInitNetplay());
this->action_System_GSButton->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Exit));
this->action_System_Exit->setShortcut(QKeySequence(keyBinding));
// configure keybindings for speed factor
QAction* speedActions[] =
{
this->actionSpeed25, this->actionSpeed50, this->actionSpeed75,
this->actionSpeed100, this->actionSpeed125, this->actionSpeed150,
this->actionSpeed175, this->actionSpeed200, this->actionSpeed225,
this->actionSpeed250, this->actionSpeed275, this->actionSpeed300
};
SettingsID speedKeybindSettingsId[] =
{
SettingsID::KeyBinding_SpeedFactor25, SettingsID::KeyBinding_SpeedFactor50,
SettingsID::KeyBinding_SpeedFactor75, SettingsID::KeyBinding_SpeedFactor100,
SettingsID::KeyBinding_SpeedFactor125, SettingsID::KeyBinding_SpeedFactor150,
SettingsID::KeyBinding_SpeedFactor175, SettingsID::KeyBinding_SpeedFactor200,
SettingsID::KeyBinding_SpeedFactor225, SettingsID::KeyBinding_SpeedFactor250,
SettingsID::KeyBinding_SpeedFactor275, SettingsID::KeyBinding_SpeedFactor300
};
for (int i = 0; i < 12; i++)
{
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(speedKeybindSettingsId[i]));
speedActions[i]->setShortcut(QKeySequence(keyBinding));
}
// configure keybindings for save slots
SettingsID slotKeybindSettingsId[] =
{
SettingsID::KeyBinding_SaveStateSlot0, SettingsID::KeyBinding_SaveStateSlot1,
SettingsID::KeyBinding_SaveStateSlot2, SettingsID::KeyBinding_SaveStateSlot3,
SettingsID::KeyBinding_SaveStateSlot4, SettingsID::KeyBinding_SaveStateSlot5,
SettingsID::KeyBinding_SaveStateSlot6, SettingsID::KeyBinding_SaveStateSlot7,
SettingsID::KeyBinding_SaveStateSlot8, SettingsID::KeyBinding_SaveStateSlot9
};
int currentSlot = CoreGetSaveStateSlot();
for (int i = 0; i < 10; i++)
{
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(slotKeybindSettingsId[i]));
this->ui_SlotActions[i]->setShortcut(QKeySequence(keyBinding));
this->ui_SlotActions[i]->setChecked(i == currentSlot);
}
// configure text and filename data for save slots
this->updateSaveStateSlotActions(inEmulation, isPaused);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_GraphicsSettings));
this->action_Settings_Graphics->setEnabled(CorePluginsHasConfig(CorePluginType::Gfx));
this->action_Settings_Graphics->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_AudioSettings));
this->action_Settings_Audio->setEnabled(CorePluginsHasConfig(CorePluginType::Audio));
this->action_Settings_Audio->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_RspSettings));
this->action_Settings_Rsp->setEnabled(CorePluginsHasConfig(CorePluginType::Rsp));
this->action_Settings_Rsp->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_InputSettings));
this->action_Settings_Input->setEnabled(CorePluginsHasConfig(CorePluginType::Input));
this->action_Settings_Input->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Settings));
this->action_Settings_Settings->setShortcut(QKeySequence(keyBinding));
this->action_View_GameList->setEnabled(!inEmulation);
this->action_View_GameGrid->setEnabled(!inEmulation);
this->action_View_UniformSize->setEnabled(!inEmulation);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Fullscreen));
this->action_View_Fullscreen->setEnabled(inEmulation);
this->action_View_Fullscreen->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_RefreshROMList));
this->action_View_RefreshRoms->setEnabled(!inEmulation);
this->action_View_RefreshRoms->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_ViewLog));
this->action_View_Log->setShortcut(QKeySequence(keyBinding));
this->action_View_ClearRomCache->setEnabled(!inEmulation);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_ViewSearch));
this->action_View_Search->setShortcut(QKeySequence(keyBinding));
this->action_View_Search->setEnabled(!inEmulation);
#ifdef NETPLAY
this->action_Netplay_CreateSession->setEnabled(!inEmulation && this->netplaySessionDialog == nullptr);
this->action_Netplay_BrowseSessions->setEnabled(!inEmulation && this->netplaySessionDialog == nullptr);
this->action_Netplay_ViewSession->setEnabled(inEmulation && this->netplaySessionDialog != nullptr);
#endif // NETPLAY
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_IncreaseVolume));
this->action_Audio_IncreaseVolume->setShortcut(QKeySequence(keyBinding));
this->action_Audio_IncreaseVolume->setEnabled(inEmulation);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_DecreaseVolume));
this->action_Audio_DecreaseVolume->setShortcut(QKeySequence(keyBinding));
this->action_Audio_DecreaseVolume->setEnabled(inEmulation);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_ToggleMuteVolume));
this->action_Audio_ToggleVolumeMute->setShortcut(QKeySequence(keyBinding));
this->action_Audio_ToggleVolumeMute->setEnabled(inEmulation);
}
void MainWindow::updateSaveStateSlotActions(bool inEmulation, bool isPaused)
{
std::filesystem::path saveStatePath;
// do nothing when paused
if (isPaused)
{
return;
}
for (int i = 0; i < 10; i++)
{
if (inEmulation &&
CoreGetSaveStatePath(i, saveStatePath))
{
this->ui_SlotActions[i]->setData(QString::fromStdString(saveStatePath.string()));
this->ui_SlotActions[i]->setText(this->getSaveStateSlotText(this->ui_SlotActions[i], i));
}
else
{
this->ui_SlotActions[i]->setData("");
this->ui_SlotActions[i]->setText(this->getSaveStateSlotText(this->ui_SlotActions[i], i));
}
}
}
void MainWindow::addActions(void)
{
if (this->ui_AddedActions)
{
return;
}
for (QAction* action : this->ui_Actions)
{
this->addAction(action);
}
this->ui_AddedActions = true;
}
void MainWindow::removeActions(void)
{
if (!this->ui_AddedActions)
{
return;
}
for (QAction* action : this->ui_Actions)
{
this->removeAction(action);
}
this->ui_AddedActions = false;
}
QString MainWindow::getSaveStateSlotDateTimeText(QAction* action)
{
QString saveStateSlotText;
QString filePath;
// retrieve file name
filePath = action->data().toString();
// check if file exists, if it does,
// return a string with the datetime
QFileInfo saveStateFileInfo(filePath);
if (!filePath.isEmpty() && saveStateFileInfo.exists())
{
saveStateSlotText = saveStateFileInfo.lastModified().toString("dd/MM/yyyy hh:mm");
}
return saveStateSlotText;
}
QString MainWindow::getSaveStateSlotText(QAction* action, int slot)
{
QString saveStateSlotText;
QString dateTimeText;
QString filePath;
// base text
saveStateSlotText = "Slot " + QString::number(slot);
// add date and time text when it isnt empty
dateTimeText = this->getSaveStateSlotDateTimeText(action);
if (!dateTimeText.isEmpty())
{
saveStateSlotText += " - ";
saveStateSlotText += dateTimeText;
}
return saveStateSlotText;
}
int MainWindow::getToolbarSettingAreaFromArea(Qt::ToolBarArea area)
{
switch (area)
{
case Qt::ToolBarArea::TopToolBarArea:
return 0;
case Qt::ToolBarArea::BottomToolBarArea:
return 1;
case Qt::ToolBarArea::LeftToolBarArea:
return 2;
case Qt::ToolBarArea::RightToolBarArea:
return 3;
default:
return 0;
}
}
Qt::ToolBarArea MainWindow::getToolbarAreaFromSettingArea(int value)
{
switch (value)
{
default:
case 0:
return Qt::ToolBarArea::TopToolBarArea;
case 1:
return Qt::ToolBarArea::BottomToolBarArea;
case 2:
return Qt::ToolBarArea::LeftToolBarArea;
case 3:
return Qt::ToolBarArea::RightToolBarArea;
}
}
void MainWindow::initializeActions(void)
{
// Audio actions
this->action_Audio_IncreaseVolume = new QAction(this);
this->action_Audio_DecreaseVolume = new QAction(this);
this->action_Audio_ToggleVolumeMute = new QAction(this);
this->action_Audio_IncreaseVolume->setEnabled(false);
this->action_Audio_DecreaseVolume->setEnabled(false);
this->action_Audio_ToggleVolumeMute->setEnabled(false);
// because these are hotkey exclusive actions,
// we only have to add them once
this->addAction(this->action_Audio_IncreaseVolume);
this->addAction(this->action_Audio_DecreaseVolume);
this->addAction(this->action_Audio_ToggleVolumeMute);
}
void MainWindow::configureActions(void)
{
// configure actions list
this->ui_Actions.append(
{
// System actions
this->action_System_StartRom, this->action_System_OpenCombo,
this->action_System_Shutdown, this->action_System_SoftReset,
this->action_System_HardReset, this->action_System_Pause,
this->action_System_Screenshot, this->action_System_LimitFPS,
this->actionSpeed25, this->actionSpeed50, this->actionSpeed75,
this->actionSpeed100, this->actionSpeed125, this->actionSpeed150,
this->actionSpeed175, this->actionSpeed200, this->actionSpeed225,
this->actionSpeed250, this->actionSpeed275, this->actionSpeed300,
this->action_System_SaveState, this->action_System_SaveAs,
this->action_System_LoadState, this->action_System_Load,
this->actionSlot_0, this->actionSlot_1, this->actionSlot_2,
this->actionSlot_3, this->actionSlot_4, this->actionSlot_5,
this->actionSlot_6, this->actionSlot_7, this->actionSlot_8,
this->actionSlot_9, this->action_System_Cheats,
this->action_System_GSButton, this->action_System_Exit,
// Settings actions
this->action_Settings_Graphics, this->action_Settings_Audio,
this->action_Settings_Rsp, this->action_Settings_Input,
this->action_Settings_Settings,
// View actions
this->action_View_Fullscreen, this->action_View_RefreshRoms,
this->action_View_Log,
// Help actions
this->action_Help_Github, this->action_Help_About,
});
// configure save slot actions
this->ui_SlotActions.append(
{
this->actionSlot_0, this->actionSlot_1, this->actionSlot_2,
this->actionSlot_3, this->actionSlot_4, this->actionSlot_5,
this->actionSlot_6, this->actionSlot_7, this->actionSlot_8,
this->actionSlot_9
});
// configure emulation speed actions
QActionGroup* speedActionGroup = new QActionGroup(this);
QAction* speedActions[] =
{
this->actionSpeed25, this->actionSpeed50, this->actionSpeed75,
this->actionSpeed100, this->actionSpeed125, this->actionSpeed150,
this->actionSpeed175, this->actionSpeed200, this->actionSpeed225,
this->actionSpeed250, this->actionSpeed275, this->actionSpeed300
};
int speedActionNumbers[] =
{
25, 50, 75, 100,
125, 150, 175, 200,
225, 250, 275, 300
};
int currentSpeedFactor = CoreGetSpeedFactor();
for (int i = 0; i < 12; i++)
{
QAction* speedAction = speedActions[i];
int speedActionNumber = speedActionNumbers[i];
speedAction->setCheckable(true);
speedAction->setChecked(currentSpeedFactor == speedActionNumber);
speedAction->setActionGroup(speedActionGroup);
// connect emulation speed action here because we need to do
// something special for them
connect(speedAction, &QAction::triggered, [this, speedActionNumber](bool checked)
{
if (checked)
{
this->on_Action_System_SpeedFactor(speedActionNumber);
}
});
}
// configure save slot actions
QActionGroup* slotActionGroup = new QActionGroup(this);
for (int i = 0; i < 10; i++)
{
QAction* slotAction = this->ui_SlotActions[i];
slotAction->setCheckable(true);
slotAction->setChecked(i == 0);
slotAction->setActionGroup(slotActionGroup);
// connect slot action here because we need to do
// something special for them
connect(slotAction, &QAction::triggered, [this, i](bool checked)
{
if (checked)
{
this->on_Action_System_CurrentSaveState(i);
}
});
}
// configure toolbar & statusbar actions
this->action_View_Toolbar->setChecked(CoreSettingsGetBoolValue(SettingsID::GUI_Toolbar));
this->action_View_StatusBar->setChecked(CoreSettingsGetBoolValue(SettingsID::GUI_StatusBar));
// configure ROM browser view actions
QActionGroup* romBrowserViewActionGroup = new QActionGroup(this);
int currentView = CoreSettingsGetIntValue(SettingsID::RomBrowser_ViewMode);
QAction* romBrowserViewActions[] =
{
this->action_View_GameList,
this->action_View_GameGrid
};
for (int i = 0; i < 2; i++)
{
QAction* action = romBrowserViewActions[i];
action->setCheckable(true);
action->setChecked(i == currentView);
action->setActionGroup(romBrowserViewActionGroup);
}
// configure grid view options actions
this->action_View_UniformSize->setChecked(CoreSettingsGetBoolValue(SettingsID::RomBrowser_GridViewUniformItemSizes));
}
void MainWindow::connectActionSignals(void)
{
connect(this->action_System_StartRom, &QAction::triggered, this, &MainWindow::on_Action_System_OpenRom);
connect(this->action_System_OpenCombo, &QAction::triggered, this, &MainWindow::on_Action_System_OpenCombo);
connect(this->action_System_Exit, &QAction::triggered, this, &MainWindow::on_Action_System_Exit);
connect(this->action_System_Shutdown, &QAction::triggered, this, &MainWindow::on_Action_System_Shutdown);
connect(this->action_System_SoftReset, &QAction::triggered, this, &MainWindow::on_Action_System_SoftReset);
connect(this->action_System_HardReset, &QAction::triggered, this, &MainWindow::on_Action_System_HardReset);
connect(this->action_System_Pause, &QAction::triggered, this, &MainWindow::on_Action_System_Pause);
connect(this->action_System_Screenshot, &QAction::triggered, this,
&MainWindow::on_Action_System_Screenshot);
connect(this->action_System_LimitFPS, &QAction::triggered, this, &MainWindow::on_Action_System_LimitFPS);
connect(this->action_System_SaveState, &QAction::triggered, this, &MainWindow::on_Action_System_SaveState);
connect(this->action_System_SaveAs, &QAction::triggered, this, &MainWindow::on_Action_System_SaveAs);
connect(this->action_System_LoadState, &QAction::triggered, this, &MainWindow::on_Action_System_LoadState);
connect(this->action_System_Load, &QAction::triggered, this, &MainWindow::on_Action_System_Load);
connect(this->action_System_Cheats, &QAction::triggered, this, &MainWindow::on_Action_System_Cheats);
connect(this->action_System_GSButton, &QAction::triggered, this, &MainWindow::on_Action_System_GSButton);
connect(this->action_Settings_Graphics, &QAction::triggered, this, &MainWindow::on_Action_Settings_Graphics);
connect(this->action_Settings_Audio, &QAction::triggered, this, &MainWindow::on_Action_Settings_Audio);
connect(this->action_Settings_Rsp, &QAction::triggered, this, &MainWindow::on_Action_Settings_Rsp);
connect(this->action_Settings_Input, &QAction::triggered, this,
&MainWindow::on_Action_Settings_Input);
connect(this->action_Settings_Settings, &QAction::triggered, this, &MainWindow::on_Action_Settings_Settings);
connect(this->action_View_Toolbar, &QAction::toggled, this, &MainWindow::on_Action_View_Toolbar);
connect(this->action_View_StatusBar, &QAction::toggled, this, &MainWindow::on_Action_View_StatusBar);
connect(this->action_View_GameList, &QAction::toggled, this, &MainWindow::on_Action_View_GameList);
connect(this->action_View_GameGrid, &QAction::toggled, this, &MainWindow::on_Action_View_GameGrid);
connect(this->action_View_UniformSize, &QAction::toggled, this, &MainWindow::on_Action_View_UniformSize);
connect(this->action_View_Fullscreen, &QAction::triggered, this, &MainWindow::on_Action_View_Fullscreen);
connect(this->action_View_RefreshRoms, &QAction::triggered, this, &MainWindow::on_Action_View_RefreshRoms);
connect(this->action_View_ClearRomCache, &QAction::triggered, this, &MainWindow::on_Action_View_ClearRomCache);
connect(this->action_View_Log, &QAction::triggered, this, &MainWindow::on_Action_View_Log);
connect(this->action_View_Search, &QAction::triggered, this, &MainWindow::on_Action_View_Search);
connect(this->action_Netplay_CreateSession, &QAction::triggered, this, &MainWindow::on_Action_Netplay_CreateSession);
connect(this->action_Netplay_BrowseSessions, &QAction::triggered, this, &MainWindow::on_Action_Netplay_BrowseSessions);
connect(this->action_Netplay_ViewSession, &QAction::triggered, this, &MainWindow::on_Action_Netplay_ViewSession);
connect(this->action_Help_Github, &QAction::triggered, this, &MainWindow::on_Action_Help_Github);
connect(this->action_Help_About, &QAction::triggered, this, &MainWindow::on_Action_Help_About);
connect(this->action_Help_Update, &QAction::triggered, this, &MainWindow::on_Action_Help_Update);
connect(this->action_Audio_IncreaseVolume, &QAction::triggered, this, &MainWindow::on_Action_Audio_IncreaseVolume);
connect(this->action_Audio_DecreaseVolume, &QAction::triggered, this, &MainWindow::on_Action_Audio_DecreaseVolume);
connect(this->action_Audio_ToggleVolumeMute, &QAction::triggered, this, &MainWindow::on_Action_Audio_ToggleVolumeMute);
}
#ifdef UPDATER
void MainWindow::checkForUpdates(bool silent, bool force)
{
if (!force && !CoreSettingsGetBoolValue(SettingsID::GUI_CheckForUpdates))
{
return;
}
// only check for updates on the stable versions unless forced
QString currentVersion = QString::fromStdString(CoreGetVersion());
if (!force && currentVersion.size() != 6)
{
return;
}
QString dateTimeFormat = "dd-MM-yyyy_hh:mm";
QString lastUpdateCheckDateTimeString = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::GUI_LastUpdateCheck));
QDateTime lastUpdateCheckDateTime = QDateTime::fromString(lastUpdateCheckDateTimeString, dateTimeFormat);
QDateTime currentDateTime = QDateTime::currentDateTime();
// only check for updates once every hour unless forced
if (!force &&
lastUpdateCheckDateTime.isValid() &&
lastUpdateCheckDateTime.addSecs(3600) > currentDateTime)
{
return;
}
// update last update check date & time
CoreSettingsSetValue(SettingsID::GUI_LastUpdateCheck, currentDateTime.toString(dateTimeFormat).toStdString());
// whether or not the update check is silent
this->ui_SilentUpdateCheck = silent;
// execute update check
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &MainWindow::on_networkAccessManager_Finished);
networkAccessManager->setTransferTimeout(15000);
networkAccessManager->get(QNetworkRequest(QUrl("https://api.github.com/repos/Rosalie241/RMG/releases/latest")));
}
#endif // UPDATER
#ifdef NETPLAY
void MainWindow::showNetplaySessionDialog(QWebSocket* webSocket, QJsonObject json, QString sessionFile)
{
if (this->netplaySessionDialog != nullptr)
{
this->netplaySessionDialog->deleteLater();
this->netplaySessionDialog = nullptr;
}
this->netplaySessionDialog = new Dialog::NetplaySessionDialog(nullptr, webSocket, json, sessionFile);
connect(this->netplaySessionDialog, &Dialog::NetplaySessionDialog::OnPlayGame, this, &MainWindow::on_Netplay_PlayGame);
connect(this->netplaySessionDialog, &Dialog::NetplaySessionDialog::rejected, this, &MainWindow::on_NetplaySessionDialog_rejected);
this->netplaySessionDialog->show();
// force refresh of actions
this->updateActions(false, false);
}
#endif // NETPLAY
void MainWindow::timerEvent(QTimerEvent *event)
{
int timerId = event->timerId();
if (timerId == this->ui_ResetStatusBarTimerId)
{
this->ui_StatusBar_Label->clear();
}
else if (timerId == this->ui_FullscreenTimerId)
{
// only try to go to fullscreen
// when emulation is running
if (!CoreIsEmulationRunning())
{
return;
}
// we're finished when we're in fullscreen already,
// or when switching to fullscreen succeeds
if (this->isFullScreen() || CoreToggleFullscreen())
{
this->killTimer(timerId);
this->ui_FullscreenTimerId = 0;
}
}
else if (timerId == this->ui_GamesharkButtonTimerId)
{
if (!CoreIsEmulationRunning())
{
return;
}
if (CorePressGamesharkButton(false))
{
this->killTimer(timerId);
this->ui_GamesharkButtonTimerId = 0;
}
}
else if (timerId == this->ui_UpdateSaveStateSlotTimerId)
{
this->updateSaveStateSlotActions(CoreIsEmulationRunning(), false);
this->killTimer(timerId);
this->ui_UpdateSaveStateSlotTimerId = 0;
}
else if (timerId == this->ui_CheckVideoSizeTimerId)
{
if (!CoreIsEmulationRunning())
{
return;
}
int width = 0;
int height = 0;
if (!CoreGetVideoSize(width, height))
{
return;
}
int expectedWidth = 0;
int expectedHeight = 0;
if (this->ui_VidExtRenderMode == VidExtRenderMode::OpenGL)
{
expectedWidth = this->ui_Widget_OpenGL->GetWidget()->width() * this->devicePixelRatio();
expectedHeight = this->ui_Widget_OpenGL->GetWidget()->height() * this->devicePixelRatio();
}
else if (this->ui_VidExtRenderMode == VidExtRenderMode::Vulkan)
{
expectedWidth = this->ui_Widget_Vulkan->GetWidget()->width() * this->devicePixelRatio();
expectedHeight = this->ui_Widget_Vulkan->GetWidget()->height() * this->devicePixelRatio();
}
if (width != expectedWidth ||
height != expectedHeight)
{
CoreSetVideoSize(expectedWidth, expectedHeight);
}
}
else if (timerId == this->ui_LoadSaveStateSlotTimerId)
{
if (!CoreIsEmulationRunning())
{
return;
}
if (!CoreSetSaveStateSlot(this->ui_LoadSaveStateSlot) ||
!CoreLoadSaveState())
{
this->ui_LoadSaveStateSlotCounter++;
if (this->ui_LoadSaveStateSlotCounter >= 5)
{ // give up after 5 attempts
this->killTimer(this->ui_LoadSaveStateSlotTimerId);
this->ui_LoadSaveStateSlotCounter = 0;
this->ui_LoadSaveStateSlotTimerId = -1;
this->ui_LoadSaveStateSlot = -1;
}
return;
}
this->killTimer(this->ui_LoadSaveStateSlotTimerId);
}
}
void MainWindow::on_EventFilter_KeyPressed(QKeyEvent *event)
{
if (!CoreIsEmulationRunning())
{
QMainWindow::keyPressEvent(event);
return;
}
int key = Utilities::QtKeyToSdl3Key(event->key());
int mod = Utilities::QtModKeyToSdl3ModKey(event->modifiers());
CoreSetKeyDown(key, mod);
}
void MainWindow::on_EventFilter_KeyReleased(QKeyEvent *event)
{
if (!CoreIsEmulationRunning())
{
QMainWindow::keyReleaseEvent(event);
return;
}
int key = Utilities::QtKeyToSdl3Key(event->key());
int mod = Utilities::QtModKeyToSdl3ModKey(event->modifiers());
CoreSetKeyUp(key, mod);
}
void MainWindow::on_EventFilter_FileDropped(QDropEvent *event)
{
const QMimeData *mimeData = event->mimeData();
QList<QUrl> urls = mimeData->urls();
#ifdef KCA_DRAG_DROP
urls = KUrlMimeData::urlsFromMimeData(mimeData, KUrlMimeData::PreferLocalUrls);
#endif
if (!mimeData->hasUrls() || urls.empty() ||
!urls.first().isLocalFile())
{
return;
}
bool inEmulation = (this->ui_Widgets->currentIndex() != 0);
bool confirmDragDrop = CoreSettingsGetBoolValue(SettingsID::GUI_ConfirmDragDrop);
bool refreshRomList = false;
QString file;
// when we're still opening the ROM while emulation is running,
// ignore the event
if (this->emulationThread->isRunning() && !CoreHasRomOpen())
{
return;
}
if (inEmulation && confirmDragDrop)
{
confirmDragDrop = false;
bool ret = QtMessageBox::Question(this, "Are you sure you want to launch the drag & dropped ROM?",
"Don't ask for confirmation again", confirmDragDrop);
if (!ret)
{
return;
}
CoreSettingsSetValue(SettingsID::GUI_ConfirmDragDrop, !confirmDragDrop);
}
file = urls.first().toLocalFile();
if (inEmulation)
{
this->ui_NoSwitchToRomBrowser = true;
// we have to keep the state of this->ui_RefreshRomListAfterEmulation,
// because when that's reset on every launch, and because when
// RMG is launched with a ROM specified, the ROM browser
// hasn't loaded yet, we should pass on the state to ensure
// it does refresh when you do the following:
// launch RMG with a ROM -> drag & drop -> return to ROM browser
refreshRomList = this->ui_RefreshRomListAfterEmulation;
}
this->launchEmulationThread(file, "", refreshRomList, -1, false, true);
}
void MainWindow::on_QGuiApplication_applicationStateChanged(Qt::ApplicationState state)
{
bool isRunning = CoreIsEmulationRunning();
bool isPaused = CoreIsEmulationPaused();
bool pauseOnFocusLoss = CoreSettingsGetBoolValue(SettingsID::GUI_PauseEmulationOnFocusLoss);
bool resumeOnFocus = CoreSettingsGetBoolValue(SettingsID::GUI_ResumeEmulationOnFocus);
switch (state)
{
default:
break;
case Qt::ApplicationState::ApplicationInactive:
{
if (pauseOnFocusLoss && isRunning && !isPaused)
{
this->on_Action_System_Pause();
this->ui_ManuallyPaused = false;
}
} break;
case Qt::ApplicationState::ApplicationActive:
{
if (resumeOnFocus && isPaused && !this->ui_ManuallyPaused)
{
this->on_Action_System_Pause();
}
} break;
}
}
#ifdef UPDATER
void MainWindow::on_networkAccessManager_Finished(QNetworkReply* reply)
{
if (reply->error())
{
if (!this->ui_SilentUpdateCheck)
{
this->showErrorMessage("Failed to check for updates", reply->errorString());
}
reply->deleteLater();
return;
}
QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll());
QJsonObject jsonObject = jsonDocument.object();
QString currentVersion = QString::fromStdString(CoreGetVersion());
QString latestVersion = jsonObject.value("tag_name").toString();
reply->deleteLater();
// do nothing when versions match
if (currentVersion == latestVersion)
{
if (!this->ui_SilentUpdateCheck)
{
Utilities::QtMessageBox::Info(this, "You're already on the latest version");
}
return;
}
int ret = 0;
Dialog::UpdateDialog updateDialog(this, jsonObject, !this->ui_SilentUpdateCheck);
ret = updateDialog.exec();
if (ret != QDialog::Accepted)
{
return;
}
Dialog::DownloadUpdateDialog downloadUpdateDialog(this, updateDialog.GetUrl(), updateDialog.GetFileName());
ret = downloadUpdateDialog.exec();
if (ret != QDialog::Accepted)
{
return;
}
#ifdef APPIMAGE_UPDATER
this->ui_ForceClose = true;
this->close();
#else // normal updater
Dialog::InstallUpdateDialog installUpdateDialog(this, QCoreApplication::applicationDirPath(), downloadUpdateDialog.GetTempDirectory(), downloadUpdateDialog.GetFileName());
ret = installUpdateDialog.exec();
if (ret != QDialog::Accepted)
{
return;
}
#endif // APPIMAGE_UPDATER
}
#endif // UPDATER
void MainWindow::on_Action_System_OpenRom(void)
{
bool isRunning = CoreIsEmulationRunning();
bool isPaused = CoreIsEmulationPaused();
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
QString romFile = QFileDialog::getOpenFileName(this, tr("Open N64 ROM or 64DD Disk"), "", "N64 ROMs & Disks (*.n64 *.z64 *.v64 *.ndd *.d64 *.zip *.7z)");
if (romFile.isEmpty())
{
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
return;
}
if (this->ui_Widgets->currentIndex() != 0)
{
this->ui_NoSwitchToRomBrowser = true;
}
this->launchEmulationThread(romFile);
}
void MainWindow::on_Action_System_OpenCombo(void)
{
bool isRunning = CoreIsEmulationRunning();
bool isPaused = CoreIsEmulationPaused();
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
QString cartRom = QFileDialog::getOpenFileName(this, tr("Open N64 ROM"), "", "N64 ROMs (*.n64 *.z64 *.v64 *.zip *.7z)");
if (cartRom.isEmpty())
{
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
return;
}
QString diskRom = QFileDialog::getOpenFileName(this, tr("Open 64DD Disk"), "", "N64DD Disk Image (*.ndd *.d64 *.zip *.7z)");
if (diskRom.isEmpty())
{
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
return;
}
if (this->ui_Widgets->currentIndex() != 0)
{
this->ui_NoSwitchToRomBrowser = true;
}
this->launchEmulationThread(cartRom, diskRom);
}
void MainWindow::on_Action_System_Shutdown(void)
{
if (CoreIsEmulationPaused())
{
this->on_Action_System_Pause();
}
if (!CoreIsEmulationRunning())
{
return;
}
if (!CoreStopEmulation())
{
this->showErrorMessage("CoreStopEmulation() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Action_System_Exit(void)
{
this->close();
}
void MainWindow::on_Action_System_SoftReset(void)
{
if (!CoreResetEmulation(false))
{
this->showErrorMessage("CoreResetEmulation() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Action_System_HardReset(void)
{
if (!CoreResetEmulation(true))
{
this->showErrorMessage("CoreResetEmulation() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Action_System_Pause(void)
{
if (!this->action_System_Pause->isEnabled())
{
return;
}
bool isPaused = CoreIsEmulationPaused();
bool ret;
QString error;
if (!isPaused)
{
ret = CorePauseEmulation();
error = "CorePauseEmulation() Failed";
}
else
{
ret = CoreResumeEmulation();
error = "CoreResumeEmulation() Failed";
}
if (!ret)
{
this->showErrorMessage(error, QString::fromStdString(CoreGetError()));
}
this->updateUI(true, (!isPaused && ret));
this->ui_ManuallyPaused = true;
}
void MainWindow::on_Action_System_Screenshot(void)
{
if (!CoreTakeScreenshot())
{
this->showErrorMessage("CoreTakeScreenshot() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Action_System_LimitFPS(void)
{
bool enabled = this->action_System_LimitFPS->isChecked();
if (!CoreSetSpeedLimiterState(enabled))
{
this->showErrorMessage("CoreSetSpeedLimiterState() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Action_System_SpeedFactor(int factor)
{
if (!CoreSetSpeedFactor(factor))
{
this->showErrorMessage("CoreSetSpeedFactor() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Action_System_SaveState(void)
{
this->ui_ManuallySavedState = true;
if (!CoreSaveState())
{
this->ui_ManuallySavedState = false;
this->showErrorMessage("CoreSaveState() Failed", QString::fromStdString(CoreGetError()));
}
else
{
OnScreenDisplaySetMessage("Saved state to slot: " + std::to_string(CoreGetSaveStateSlot()));
}
}
void MainWindow::on_Action_System_SaveAs(void)
{
bool isRunning = CoreIsEmulationRunning();
bool isPaused = CoreIsEmulationPaused();
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
QString fileName = QFileDialog::getSaveFileName(this, tr("Save State"), "", tr("Save State (*.state);;Project64 Save State (*.pj);;All Files (*)"));
if (!fileName.isEmpty())
{
this->ui_ManuallySavedState = true;
CoreSaveStateType type = fileName.endsWith(".pj") ?
CoreSaveStateType::Project64 :
CoreSaveStateType::Mupen64Plus;
if (!CoreSaveState(fileName.toStdU32String(), type))
{
this->ui_ManuallySavedState = false;
this->showErrorMessage("CoreSaveState() Failed", QString::fromStdString(CoreGetError()));
}
else
{
OnScreenDisplaySetMessage("Saved state to: " + QDir::toNativeSeparators(fileName).toStdString());
}
}
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
}
void MainWindow::on_Action_System_LoadState(void)
{
this->ui_ManuallyLoadedState = true;
if (!CoreLoadSaveState())
{
this->ui_ManuallyLoadedState = false;
this->showErrorMessage("CoreLoadSaveState() Failed", QString::fromStdString(CoreGetError()));
}
else
{
OnScreenDisplaySetMessage("State loaded from slot: " + std::to_string(CoreGetSaveStateSlot()));
}
}
void MainWindow::on_Action_System_Load(void)
{
bool isRunning = CoreIsEmulationRunning();
bool isPaused = CoreIsEmulationPaused();
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
QString fileName =
QFileDialog::getOpenFileName(this, tr("Open Save State"), "", tr("Save State (*.dat *.state *.st* *.pj*);;All Files (*)"));
if (!fileName.isEmpty())
{
this->ui_ManuallyLoadedState = true;
if (!CoreLoadSaveState(fileName.toStdU32String()))
{
this->ui_ManuallyLoadedState = false;
this->showErrorMessage("CoreLoadSaveState() Failed", QString::fromStdString(CoreGetError()));
}
else
{
OnScreenDisplaySetMessage("State loaded from: " + QDir::toNativeSeparators(fileName).toStdString());
}
}
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
}
void MainWindow::on_Action_System_CurrentSaveState(int slot)
{
if (!CoreSetSaveStateSlot(slot))
{
this->showErrorMessage("CoreSetSaveStateSlot() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Action_System_Cheats(void)
{
bool isRunning = CoreIsEmulationRunning();
bool isPaused = CoreIsEmulationPaused();
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
Dialog::CheatsDialog dialog(this);
if (!dialog.HasFailed())
{
dialog.exec();
}
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
}
void MainWindow::on_Action_System_GSButton(void)
{
if (!CorePressGamesharkButton(true))
{
this->showErrorMessage("CorePressGamesharkButton() Failed", QString::fromStdString(CoreGetError()));
}
else
{
// only hold the gameshark button for 1 second
this->ui_GamesharkButtonTimerId = this->startTimer(1000);
}
}
void MainWindow::on_Action_Settings_Graphics(void)
{
CorePluginsOpenConfig(CorePluginType::Gfx, this);
}
void MainWindow::on_Action_Settings_Audio(void)
{
CorePluginsOpenConfig(CorePluginType::Audio, this);
}
void MainWindow::on_Action_Settings_Rsp(void)
{
CorePluginsOpenConfig(CorePluginType::Rsp, this);
}
void MainWindow::on_Action_Settings_Input(void)
{
CorePluginsOpenConfig(CorePluginType::Input, this);
}
void MainWindow::on_Action_Settings_Settings(void)
{
bool isRunning = CoreIsEmulationRunning();
bool isPaused = CoreIsEmulationPaused();
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
Dialog::SettingsDialog dialog(this);
dialog.exec();
// reload UI,
// because we need to keep Settings -> {type}
// up-to-date
this->updateActions(emulationThread->isRunning(), isPaused);
// update core callbacks settings
this->coreCallBacks->LoadSettings();
if (isRunning && !isPaused)
{
this->on_Action_System_Pause();
}
}
void MainWindow::on_Action_View_Toolbar(bool checked)
{
if (!this->ui_ShowUI)
{
return;
}
CoreSettingsSetValue(SettingsID::GUI_Toolbar, checked);
this->toolBar->setVisible(checked);
this->ui_ShowToolbar = checked;
}
void MainWindow::on_Action_View_StatusBar(bool checked)
{
if (!this->ui_ShowUI)
{
return;
}
CoreSettingsSetValue(SettingsID::GUI_StatusBar, checked);
this->statusBar()->setVisible(checked);
this->ui_ShowStatusbar = checked;
}
void MainWindow::on_Action_View_GameList(bool checked)
{
if (checked)
{
this->ui_Widget_RomBrowser->ShowList();
CoreSettingsSetValue(SettingsID::RomBrowser_ViewMode, 0);
}
}
void MainWindow::on_Action_View_GameGrid(bool checked)
{
if (checked)
{
this->ui_Widget_RomBrowser->ShowGrid();
CoreSettingsSetValue(SettingsID::RomBrowser_ViewMode, 1);
}
}
void MainWindow::on_Action_View_UniformSize(bool checked)
{
this->ui_Widget_RomBrowser->SetGridViewUniformSizes(checked);
CoreSettingsSetValue(SettingsID::RomBrowser_GridViewUniformItemSizes, checked);
}
void MainWindow::on_Action_View_Fullscreen(void)
{
if (!CoreToggleFullscreen())
{
this->showErrorMessage("CoreToggleFullscreen() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Action_View_RefreshRoms(void)
{
if (!this->ui_Widget_RomBrowser->IsRefreshingRomList())
{
this->ui_Widget_RomBrowser->RefreshRomList();
}
}
void MainWindow::on_Action_View_ClearRomCache(void)
{
if (!CoreClearRomHeaderAndSettingsCache())
{
this->showErrorMessage("CoreClearRomHeaderAndSettingsCache() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Action_View_Log(void)
{
this->logDialog.show();
}
void MainWindow::on_Action_View_Search(void)
{
this->ui_Widget_RomBrowser->SetToggleSearch();
}
void MainWindow::on_Action_Netplay_CreateSession(void)
{
#ifdef NETPLAY
static QWebSocket webSocket;
Dialog::CreateNetplaySessionDialog dialog(this, &webSocket, this->ui_Widget_RomBrowser->GetModelData());
int ret = dialog.exec();
if (ret == QDialog::Accepted)
{
this->showNetplaySessionDialog(&webSocket, dialog.GetSessionJson(), dialog.GetSessionFile());
}
#endif // NETPLAY
}
void MainWindow::on_Action_Netplay_BrowseSessions(void)
{
#ifdef NETPLAY
static QWebSocket webSocket;
Dialog::NetplaySessionBrowserDialog dialog(this, &webSocket, this->ui_Widget_RomBrowser->GetModelData());
int ret = dialog.exec();
if (ret == QDialog::Accepted)
{
this->showNetplaySessionDialog(&webSocket, dialog.GetSessionJson(), dialog.GetSessionFile());
}
#endif // NETPLAY
}
void MainWindow::on_Action_Netplay_ViewSession(void)
{
#ifdef NETPLAY
if (this->netplaySessionDialog != nullptr &&
this->netplaySessionDialog->isHidden())
{
this->netplaySessionDialog->show();
}
#endif
}
void MainWindow::on_Action_Help_Github(void)
{
QDesktopServices::openUrl(QUrl("https://github.com/Rosalie241/RMG"));
}
void MainWindow::on_Action_Help_About(void)
{
Dialog::AboutDialog dialog(this);
dialog.exec();
}
void MainWindow::on_Action_Help_Update(void)
{
#ifdef UPDATER
this->checkForUpdates(false, true);
#endif // UPDATER
}
void MainWindow::on_Action_Audio_IncreaseVolume(void)
{
CoreIncreaseVolume();
}
void MainWindow::on_Action_Audio_DecreaseVolume(void)
{
CoreDecreaseVolume();
}
void MainWindow::on_Action_Audio_ToggleVolumeMute(void)
{
if (!CoreToggleMuteVolume())
{
this->showErrorMessage("CoreToggleMuteVolume() Failed", QString::fromStdString(CoreGetError()));
}
}
void MainWindow::on_Emulation_Started(void)
{
// only clear log dialog when we've gone over the limit
if (this->logDialog.GetLineCount() >= 500000)
{
this->logDialog.Clear();
}
this->ui_MessageBoxList.clear();
this->ui_DebugCallbackErrors.clear();
}
void MainWindow::on_Emulation_Finished(bool ret, QString error)
{
if (!ret)
{
// whatever we do on failure,
// always return to the rombrowser
this->ui_NoSwitchToRomBrowser = false;
}
#ifdef NETPLAY
if (this->netplaySessionDialog != nullptr)
{
this->netplaySessionDialog->deleteLater();
this->netplaySessionDialog = nullptr;
}
#endif // NETPLAY
if (!this->ui_QuitAfterEmulation &&
!this->ui_NoSwitchToRomBrowser &&
this->ui_RefreshRomListAfterEmulation)
{
this->ui_Widget_RomBrowser->RefreshRomList();
this->ui_RefreshRomListAfterEmulation = false;
}
if (this->ui_FullscreenTimerId != 0)
{
this->killTimer(this->ui_FullscreenTimerId);
this->ui_FullscreenTimerId = 0;
}
if (this->ui_CheckVideoSizeTimerId != 0)
{
this->killTimer(this->ui_CheckVideoSizeTimerId);
this->ui_CheckVideoSizeTimerId = 0;
}
if (this->ui_QuitAfterEmulation)
{
// show error message when
// the user has requested we quit
// after emulation
if (!ret)
{
this->showErrorMessage("EmulationThread::run Failed", error);
}
this->ui_ForceClose = true;
this->close();
return;
}
// always refresh UI
this->updateUI(false, false);
// show error message to the user
// after switching back to the ROM browser
if (!ret)
{
this->showErrorMessage("EmulationThread::run Failed", error);
}
}
void MainWindow::on_RomBrowser_PlayGame(QString file)
{
this->launchEmulationThread(file);
}
void MainWindow::on_RomBrowser_PlayGameWith(CoreRomType type, QString file)
{
QString mainRom;
QString otherRom;
if (type == CoreRomType::Cartridge)
{ // cartridge
mainRom = file;
otherRom = QFileDialog::getOpenFileName(this, tr("Open 64DD Disk"), "", "N64DD Disk Image (*.ndd *.d64 *.zip *.7z)");
}
else
{ // disk
mainRom = QFileDialog::getOpenFileName(this, tr("Open N64 ROM"), "", "N64 ROMs (*.n64 *.z64 *.v64 *.zip *.7z)");
otherRom = file;
}
if (mainRom.isEmpty() || otherRom.isEmpty())
{
return;
}
this->launchEmulationThread(mainRom, otherRom);
}
void MainWindow::on_RomBrowser_PlayGameWithDisk(QString cartridge, QString disk)
{
this->launchEmulationThread(cartridge, disk);
}
void MainWindow::on_RomBrowser_PlayGameWithSlot(QString file, int slot)
{
this->launchEmulationThread(file, "", false, slot);
}
void MainWindow::on_RomBrowser_ChangeRomDirectory(void)
{
QString currentDir = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::RomBrowser_Directory));
if (!QDir(currentDir).exists())
{
currentDir = "";
}
QString dir = QFileDialog::getExistingDirectory(this, tr("Select ROM Directory"), currentDir);
if (!dir.isEmpty() && currentDir != dir)
{
CoreSettingsSetValue(SettingsID::RomBrowser_Directory, dir.toStdString());
this->ui_Widget_RomBrowser->RefreshRomList();
}
}
void MainWindow::on_RomBrowser_RomInformation(QString file)
{
bool isRefreshingRomList = this->ui_Widget_RomBrowser->IsRefreshingRomList();
if (isRefreshingRomList)
{
this->ui_Widget_RomBrowser->StopRefreshRomList();
}
CoreRomType romType;
CoreRomHeader romHeader;
CoreRomSettings romSettings;
if (!CoreGetCachedRomHeaderAndSettings(file.toStdU32String(), &romType, &romHeader, nullptr, &romSettings))
{
this->showErrorMessage("CoreGetCachedRomHeaderAndSettings() Failed", QString::fromStdString(CoreGetError()));
return;
}
Dialog::RomInfoDialog dialog(this, file, romType, romHeader, romSettings);
dialog.exec();
if (isRefreshingRomList)
{
this->ui_Widget_RomBrowser->RefreshRomList();
}
}
void MainWindow::on_RomBrowser_EditGameSettings(QString file)
{
bool isRefreshingRomList = this->ui_Widget_RomBrowser->IsRefreshingRomList();
if (isRefreshingRomList)
{
this->ui_Widget_RomBrowser->StopRefreshRomList();
}
Dialog::SettingsDialog dialog(this, file);
dialog.ShowGameTab();
dialog.exec();
this->updateActions(false, false);
this->coreCallBacks->LoadSettings();
if (isRefreshingRomList)
{
this->ui_Widget_RomBrowser->RefreshRomList();
}
}
void MainWindow::on_RomBrowser_EditGameInputSettings(QString file)
{
bool isRefreshingRomList = this->ui_Widget_RomBrowser->IsRefreshingRomList();
if (isRefreshingRomList)
{
this->ui_Widget_RomBrowser->StopRefreshRomList();
}
if (!CorePluginsOpenROMConfig(CorePluginType::Input, this, file.toStdU32String()))
{
this->showErrorMessage("CorePluginsOpenROMConfig() Failed", QString::fromStdString(CoreGetError()));
return;
}
if (isRefreshingRomList)
{
this->ui_Widget_RomBrowser->RefreshRomList();
}
}
void MainWindow::on_RomBrowser_Cheats(QString file)
{
bool isRefreshingRomList = this->ui_Widget_RomBrowser->IsRefreshingRomList();
if (isRefreshingRomList)
{
this->ui_Widget_RomBrowser->StopRefreshRomList();
}
Dialog::CheatsDialog dialog(this, file);
if (!dialog.HasFailed())
{
dialog.exec();
}
if (isRefreshingRomList)
{
this->ui_Widget_RomBrowser->RefreshRomList();
}
}
void MainWindow::on_Netplay_PlayGame(QString file, QString address, int port, int player)
{
this->launchEmulationThread(file, address, port, player);
}
void MainWindow::on_NetplaySessionDialog_rejected()
{
#ifdef NETPLAY
bool isRunning = CoreIsEmulationRunning();
bool isPaused = CoreIsEmulationPaused();
if (this->netplaySessionDialog != nullptr)
{
this->netplaySessionDialog->deleteLater();
this->netplaySessionDialog = nullptr;
}
// force refresh of actions
this->updateActions(isRunning, isPaused);
#endif // NETPLAY
}
void MainWindow::on_VidExt_Init(VidExtRenderMode renderMode)
{
this->ui_VidExtRenderMode = renderMode;
this->ui_VidExtForceSetMode = true;
if (CoreSettingsGetBoolValue(SettingsID::GUI_OpenGLES))
{
QSurfaceFormat format = QSurfaceFormat::defaultFormat();
format.setSwapInterval(0);
format.setMajorVersion(2);
format.setMinorVersion(0);
format.setRenderableType(QSurfaceFormat::OpenGLES);
QSurfaceFormat::setDefaultFormat(format);
}
else
{
QSurfaceFormat format = QSurfaceFormat::defaultFormat();
format.setSwapInterval(0);
format.setMajorVersion(3);
format.setMinorVersion(3);
format.setRenderableType(QSurfaceFormat::OpenGL);
QSurfaceFormat::setDefaultFormat(format);
}
if (renderMode == VidExtRenderMode::OpenGL)
{
this->ui_Widget_OpenGL = new Widget::OGLWidget(this);
this->ui_Widget_OpenGL->installEventFilter(this->ui_EventFilter);
this->ui_Widget_OpenGL->SetHideCursor(this->ui_HideCursorInEmulation);
this->ui_Widgets->addWidget(this->ui_Widget_OpenGL->GetWidget());
}
else if (renderMode == VidExtRenderMode::Vulkan)
{
this->ui_Widget_Vulkan = new Widget::VKWidget(this);
this->ui_Widget_Vulkan->installEventFilter(this->ui_EventFilter);
this->ui_Widget_Vulkan->SetHideCursor(this->ui_HideCursorInEmulation);
this->ui_Widgets->addWidget(this->ui_Widget_Vulkan->GetWidget());
}
this->updateUI(true, false);
}
void MainWindow::on_VidExt_SetupOGL(QSurfaceFormat format, QThread* thread)
{
this->ui_Widget_OpenGL->MoveContextToThread(thread);
// on wayland setting the surface format
// fails for some reason, and if we set it anyways
// ->makeCurrent() will fail in VidExt.cpp,
// so to resolve that I've set OpenGL 3.3 as
// default surface format in main.cpp and we
// skip it here only on when on wayland
if (QGuiApplication::platformName() != "wayland")
{
this->ui_Widget_OpenGL->setFormat(format);
}
}
void MainWindow::on_VidExt_SetWindowedMode(int width, int height, int bps, int flags)
{
bool returnedFromFullscreen = false;
if (this->isFullScreen())
{
returnedFromFullscreen = true;
this->showNormal();
}
if (this->ui_ShowMenubar && this->menuBar()->isHidden())
{
this->menuBar()->show();
}
if (this->ui_ShowToolbar && this->toolBar->isHidden())
{
this->toolBar->show();
}
if (this->ui_ShowStatusbar && this->statusBar()->isHidden())
{
this->statusBar()->show();
}
if (!this->ui_HideCursorInEmulation && this->ui_HideCursorInFullscreenEmulation)
{
if (this->ui_VidExtRenderMode == VidExtRenderMode::OpenGL)
{
this->ui_Widget_OpenGL->SetHideCursor(false);
}
else if (this->ui_VidExtRenderMode == VidExtRenderMode::Vulkan)
{
this->ui_Widget_Vulkan->SetHideCursor(false);
}
}
if (this->ui_ShowUI)
{
this->removeActions();
}
// only resize window when we're
// not returning from fullscreen
if (!returnedFromFullscreen)
{
this->on_VidExt_ResizeWindow(width, height);
}
}
void MainWindow::on_VidExt_SetFullscreenMode(int width, int height, int bps, int flags)
{
if (!this->isFullScreen())
{
this->showFullScreen();
}
if (!this->menuBar()->isHidden())
{
this->menuBar()->hide();
}
if (this->ui_ShowToolbar && !this->toolBar->isHidden())
{
this->toolBar->hide();
}
if (this->ui_ShowStatusbar && !this->statusBar()->isHidden())
{
this->statusBar()->hide();
}
if (!this->ui_HideCursorInEmulation && this->ui_HideCursorInFullscreenEmulation)
{
if (this->ui_VidExtRenderMode == VidExtRenderMode::OpenGL)
{
this->ui_Widget_OpenGL->SetHideCursor(true);
}
else if (this->ui_VidExtRenderMode == VidExtRenderMode::Vulkan)
{
this->ui_Widget_Vulkan->SetHideCursor(true);
}
}
if (this->ui_ShowUI)
{
this->addActions();
}
}
void MainWindow::on_VidExt_ResizeWindow(int width, int height)
{
// account for HiDPI scaling
if (this->devicePixelRatio() != 1)
{
width = static_cast<int>(std::ceil(static_cast<double>(static_cast<double>(width) / this->devicePixelRatio())));
height = static_cast<int>(std::ceil(static_cast<double>(static_cast<double>(height) / this->devicePixelRatio())));
}
if (!this->menuBar()->isHidden())
{
height += this->menuBar()->height();
}
if (!this->toolBar->isHidden())
{
Qt::ToolBarArea area = this->toolBarArea(this->toolBar);
// dont resize when toolbar is floating
if (this->toolBar->isFloating())
{
return;
}
if (area == Qt::ToolBarArea::TopToolBarArea ||
area == Qt::ToolBarArea::BottomToolBarArea)
{
height += this->toolBar->height();
}
else if (area == Qt::ToolBarArea::LeftToolBarArea ||
area == Qt::ToolBarArea::RightToolBarArea)
{
width += this->toolBar->width();
}
}
if (!this->statusBar()->isHidden())
{
height += this->statusBar()->height();
}
if (!this->ui_VidExtForceSetMode)
{
if (this->size() == QSize(width, height))
{
return;
}
}
if (this->isMaximized() || this->isMinimized())
{
this->showNormal();
}
this->resize(width, height);
// we've force set the size once,
// we can safely disable it now
this->ui_VidExtForceSetMode = false;
}
void MainWindow::on_VidExt_ToggleFS(bool fullscreen)
{
if (fullscreen)
{
if (!this->isFullScreen())
{
this->showFullScreen();
}
if (this->ui_ShowMenubar && !this->menuBar()->isHidden())
{
this->menuBar()->hide();
}
if (this->ui_ShowToolbar && !this->toolBar->isHidden())
{
this->toolBar->hide();
}
if (this->ui_ShowStatusbar && !this->statusBar()->isHidden())
{
this->statusBar()->hide();
}
if (!this->ui_HideCursorInEmulation && this->ui_HideCursorInFullscreenEmulation)
{
if (this->ui_VidExtRenderMode == VidExtRenderMode::OpenGL)
{
this->ui_Widget_OpenGL->SetHideCursor(true);
}
else if (this->ui_VidExtRenderMode == VidExtRenderMode::Vulkan)
{
this->ui_Widget_Vulkan->SetHideCursor(true);
}
}
if (this->ui_ShowUI)
{
this->addActions();
}
}
else
{
if (this->isFullScreen())
{
this->showNormal();
}
if (this->ui_ShowMenubar && this->menuBar()->isHidden())
{
this->menuBar()->show();
}
if (this->ui_ShowToolbar && this->toolBar->isHidden())
{
this->toolBar->show();
}
if (this->ui_ShowStatusbar && this->statusBar()->isHidden())
{
this->statusBar()->show();
}
if (!this->ui_HideCursorInEmulation && this->ui_HideCursorInFullscreenEmulation)
{
if (this->ui_VidExtRenderMode == VidExtRenderMode::OpenGL)
{
this->ui_Widget_OpenGL->SetHideCursor(false);
}
else if (this->ui_VidExtRenderMode == VidExtRenderMode::Vulkan)
{
this->ui_Widget_Vulkan->SetHideCursor(false);
}
}
if (this->ui_ShowUI)
{
this->removeActions();
}
}
}
void MainWindow::on_Core_DebugCallback(QList<CoreCallbackMessage> messages)
{
// pass callback messages to the log window
this->logDialog.AddMessages(messages);
// only display in statusbar when emulation is running
if (!this->emulationThread->isRunning())
{
return;
}
// attempt to find last core message
CoreCallbackMessage statusbarMessage = {};
qsizetype i = messages.size() - 1;
for (; i >= 0; i--)
{
if (messages[i].Context.startsWith("[CORE]") &&
messages[i].Type != CoreDebugMessageType::Verbose &&
!messages[i].Message.startsWith("IS64:"))
{
statusbarMessage = messages[i];
break;
}
}
if (i < 0)
{ // no wanted core message found
return;
}
if (statusbarMessage.Type == CoreDebugMessageType::Error)
{
// when we've reached 50 of the same error in the same
// emulation run, we'll stop displaying it
if (this->ui_DebugCallbackErrors.count(statusbarMessage.Message) < 50)
{
this->showErrorMessage("Core Error", statusbarMessage.Message, false);
}
this->ui_DebugCallbackErrors.append(statusbarMessage.Message);
return;
}
if (!this->ui_ShowStatusbar)
{
return;
}
this->ui_StatusBar_Label->setText(statusbarMessage.Message);
// reset label deletion timer
if (this->ui_ResetStatusBarTimerId != 0)
{
this->killTimer(this->ui_ResetStatusBarTimerId);
}
this->ui_ResetStatusBarTimerId = this->startTimer(this->ui_StatusBarTimerTimeout * 1000);
}
void MainWindow::on_Core_StateCallback(CoreStateCallbackType type, int value)
{
switch (type)
{
default:
break;
case CoreStateCallbackType::EmulationState:
{
// update Pause button
this->action_System_Pause->setChecked(value == static_cast<int>(CoreEmulationState::Paused));
// update OSD state
if (value == (int)CoreEmulationState::Paused)
{
OnScreenDisplayPause();
}
else if (value == (int)CoreEmulationState::Running)
{
OnScreenDisplayResume();
}
} break;
case CoreStateCallbackType::SaveStateSlot:
{
QAction* slotAction = this->ui_SlotActions[value];
QString dateTimeText = this->getSaveStateSlotDateTimeText(slotAction);
std::string message = "Selected save slot: " + std::to_string(value);
if (this->ui_LoadSaveStateSlotTimerId != -1)
{
return;
}
// add date and time when available
if (!dateTimeText.isEmpty())
{
message += " [";
message += dateTimeText.toStdString();
message += "]";
}
else
{
message += " [N/A]";
}
// display message
OnScreenDisplaySetMessage(message);
// update UI
this->ui_SlotActions[value]->setChecked(true);
} break;
case CoreStateCallbackType::SpeedFactor:
{
OnScreenDisplaySetMessage("Playback speed: " + std::to_string(value) + "%");
} break;
case CoreStateCallbackType::AudioVolume:
{
OnScreenDisplaySetMessage("Volume: " + std::to_string(value) + "%");
} break;
case CoreStateCallbackType::AudioMute:
{
if (value == 0)
{
OnScreenDisplaySetMessage("Volume unmuted");
}
else
{
OnScreenDisplaySetMessage("Volume muted");
}
} break;
case CoreStateCallbackType::SaveStateLoaded:
{
if (this->ui_LoadSaveStateSlotTimerId != -1 && value == 0)
{
this->ui_LoadSaveStateSlotCounter++;
if (this->ui_LoadSaveStateSlotCounter >= 5)
{ // give up after 5 attempts
this->showErrorMessage("Failed to load save state");
this->ui_LoadSaveStateSlotCounter = 0;
this->ui_LoadSaveStateSlotTimerId = -1;
this->ui_LoadSaveStateSlot = -1;
}
else
{
this->ui_LoadSaveStateSlotTimerId = this->startTimer(500);
}
}
else if (this->ui_LoadSaveStateSlotTimerId != -1 && value != 0)
{
this->ui_LoadSaveStateSlotCounter = 0;
this->ui_LoadSaveStateSlotTimerId = -1;
this->ui_LoadSaveStateSlot = -1;
}
else if (value == 0)
{
OnScreenDisplaySetMessage("Failed to load save state.");
}
else if (!this->ui_ManuallyLoadedState)
{
OnScreenDisplaySetMessage("Loaded save state.");
}
this->ui_ManuallyLoadedState = false;
} break;
case CoreStateCallbackType::SaveStateSaved:
{
if (value == 0)
{
OnScreenDisplaySetMessage("Failed to save state.");
}
else if (!this->ui_ManuallySavedState)
{
OnScreenDisplaySetMessage("Saved state.");
}
// refresh savestate slot times in 1 second,
// kill any existing timers
if (this->ui_UpdateSaveStateSlotTimerId != 0)
{
this->killTimer(this->ui_UpdateSaveStateSlotTimerId);
this->ui_UpdateSaveStateSlotTimerId = 0;
}
this->ui_UpdateSaveStateSlotTimerId = this->startTimer(1000);
this->ui_ManuallySavedState = false;
} break;
case CoreStateCallbackType::ScreenshotCaptured:
{
if (value == 0)
{
OnScreenDisplaySetMessage("Failed to capture screenshot.");
}
else
{
OnScreenDisplaySetMessage("Captured screenshot.");
}
} break;
}
}
void MainWindow::on_VidExt_Quit(void)
{
if (this->ui_VidExtRenderMode == VidExtRenderMode::OpenGL)
{
this->ui_Widgets->removeWidget(this->ui_Widget_OpenGL->GetWidget());
this->ui_Widget_OpenGL->GetWidget()->deleteLater();
this->ui_Widget_OpenGL = nullptr;
}
else if (this->ui_VidExtRenderMode == VidExtRenderMode::Vulkan)
{
this->ui_Widgets->removeWidget(this->ui_Widget_Vulkan->GetWidget());
this->ui_Widget_Vulkan->GetWidget()->deleteLater();
this->ui_Widget_Vulkan = nullptr;
}
this->ui_VidExtRenderMode = VidExtRenderMode::Invalid;
}