1
0
Fork 0
mirror of https://github.com/melonDS-emu/melonDS.git synced 2025-03-06 21:00:31 +01:00
melonDS/src/frontend/qt_sdl/Window.cpp

2334 lines
67 KiB
C++

/*
Copyright 2016-2024 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <optional>
#include <vector>
#include <string>
#include <algorithm>
#include <QProcess>
#include <QApplication>
#include <QMessageBox>
#include <QMenuBar>
#include <QMimeDatabase>
#include <QFileDialog>
#include <QInputDialog>
#include <QPaintEvent>
#include <QPainter>
#include <QKeyEvent>
#include <QMimeData>
#include <QVector>
#include <QCommandLineParser>
#include <QDesktopServices>
#ifndef _WIN32
#include <QGuiApplication>
#include <QSocketNotifier>
#include <unistd.h>
#include <sys/socket.h>
#include <signal.h>
#ifndef APPLE
#include <qpa/qplatformnativeinterface.h>
#endif
#endif
#include "main.h"
#include "CheatsDialog.h"
#include "DateTimeDialog.h"
#include "EmuSettingsDialog.h"
#include "InputConfig/InputConfigDialog.h"
#include "VideoSettingsDialog.h"
#include "CameraSettingsDialog.h"
#include "AudioSettingsDialog.h"
#include "FirmwareSettingsDialog.h"
#include "PathSettingsDialog.h"
#include "MPSettingsDialog.h"
#include "WifiSettingsDialog.h"
#include "InterfaceSettingsDialog.h"
#include "ROMInfoDialog.h"
#include "RAMInfoDialog.h"
#include "TitleManagerDialog.h"
#include "PowerManagement/PowerManagementDialog.h"
#include "Platform.h"
#include "Config.h"
#include "version.h"
#include "Savestate.h"
#include "MPInterface.h"
#include "LANDialog.h"
//#include "main_shaders.h"
#include "EmuInstance.h"
#include "ArchiveUtil.h"
#include "CameraManager.h"
#include "Window.h"
#include "AboutDialog.h"
#include "LuaMain.h"
using namespace melonDS;
extern CameraManager* camManager[2];
extern bool camStarted[2];
QString NdsRomMimeType = "application/x-nintendo-ds-rom";
QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" };
QString GbaRomMimeType = "application/x-gba-rom";
QStringList GbaRomExtensions { ".gba", ".agb" };
// This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09).
QStringList ArchiveMimeTypes
{
#ifdef ARCHIVE_SUPPORT_ENABLED
"application/zip",
"application/x-7z-compressed",
"application/vnd.rar", // *.rar
"application/x-tar",
"application/x-compressed-tar", // *.tar.gz
"application/x-xz-compressed-tar",
"application/x-bzip-compressed-tar",
"application/x-lz4-compressed-tar",
"application/x-zstd-compressed-tar",
"application/x-tarz", // *.tar.Z
"application/x-lzip-compressed-tar",
"application/x-lzma-compressed-tar",
"application/x-lrzip-compressed-tar",
"application/x-tzo", // *.tar.lzo
#endif
};
QStringList ArchiveExtensions
{
#ifdef ARCHIVE_SUPPORT_ENABLED
".zip", ".7z", ".rar", ".tar",
".tar.gz", ".tgz",
".tar.xz", ".txz",
".tar.bz2", ".tbz2",
".tar.lz4", ".tlz4",
".tar.zst", ".tzst",
".tar.Z", ".taz",
".tar.lz",
".tar.lzma", ".tlz",
".tar.lrz", ".tlrz",
".tar.lzo", ".tzo"
#endif
};
// AAAAAAA
static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive)
{
return std::any_of(extensions.cbegin(), extensions.cend(), [&](const auto& ext) {
return filename.endsWith(ext, cs);
});
}
static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames)
{
return std::any_of(superTypeNames.cbegin(), superTypeNames.cend(), [&](const auto& superTypeName) {
return mimetype.inherits(superTypeName);
});
}
static bool NdsRomByExtension(const QString& filename)
{
return FileExtensionInList(filename, NdsRomExtensions);
}
static bool GbaRomByExtension(const QString& filename)
{
return FileExtensionInList(filename, GbaRomExtensions);
}
static bool SupportedArchiveByExtension(const QString& filename)
{
return FileExtensionInList(filename, ArchiveExtensions);
}
static bool NdsRomByMimetype(const QMimeType& mimetype)
{
return mimetype.inherits(NdsRomMimeType);
}
static bool GbaRomByMimetype(const QMimeType& mimetype)
{
return mimetype.inherits(GbaRomMimeType);
}
static bool SupportedArchiveByMimetype(const QMimeType& mimetype)
{
return MimeTypeInList(mimetype, ArchiveMimeTypes);
}
static bool ZstdNdsRomByExtension(const QString& filename)
{
return filename.endsWith(".zst", Qt::CaseInsensitive) &&
NdsRomByExtension(filename.left(filename.size() - 4));
}
static bool ZstdGbaRomByExtension(const QString& filename)
{
return filename.endsWith(".zst", Qt::CaseInsensitive) &&
GbaRomByExtension(filename.left(filename.size() - 4));
}
static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive = false)
{
if (ZstdNdsRomByExtension(filename) || ZstdGbaRomByExtension(filename))
return true;
if (NdsRomByExtension(filename) || GbaRomByExtension(filename) || SupportedArchiveByExtension(filename))
return true;
const auto matchmode = insideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault;
const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchmode);
return NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype) || SupportedArchiveByMimetype(mimetype);
}
#ifndef _WIN32
static int signalFd[2];
QSocketNotifier *signalSn;
static void signalHandler(int)
{
char a = 1;
write(signalFd[0], &a, sizeof(a));
}
#endif
MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) :
QMainWindow(parent),
windowID(id),
emuInstance(inst),
globalCfg(inst->globalCfg),
localCfg(inst->localCfg),
windowCfg(localCfg.GetTable("Window"+std::to_string(id), "Window0")),
emuThread(inst->getEmuThread()),
enabledSaved(false),
focused(true)
{
#ifndef _WIN32
if (!parent)
{
if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd))
{
qFatal("Couldn't create socketpair");
}
signalSn = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this);
connect(signalSn, SIGNAL(activated(int)), this, SLOT(onQuit()));
struct sigaction sa;
sa.sa_handler = signalHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_flags |= SA_RESTART;
sigaction(SIGINT, &sa, 0);
}
#endif
showOSD = windowCfg.GetBool("ShowOSD");
setWindowTitle("melonDS " MELONDS_VERSION);
setAttribute(Qt::WA_DeleteOnClose);
setAcceptDrops(true);
setFocusPolicy(Qt::ClickFocus);
#if QT_VERSION_MAJOR == 6 && WIN32
// The "windows11" theme has pretty massive padding around menubar items, this makes Config and Help not fit in a window at 1x screen sizing
// So let's reduce the padding a bit.
if (QApplication::style()->name() == "windows11")
setStyleSheet("QMenuBar::item { padding: 4px 8px; }");
#endif
//hasMenu = (!parent);
hasMenu = true;
if (hasMenu)
{
QMenuBar * menubar = new QMenuBar();
{
QMenu * menu = menubar->addMenu("File");
actOpenROM = menu->addAction("Open ROM...");
connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile);
actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open));
/*actOpenROMArchive = menu->addAction("Open ROM inside archive...");
connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive);
actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/
recentMenu = menu->addMenu("Open recent");
loadRecentFilesMenu(true);
//actBootFirmware = menu->addAction("Launch DS menu");
actBootFirmware = menu->addAction("Boot firmware");
connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware);
menu->addSeparator();
actCurrentCart = menu->addAction("DS slot: " + emuInstance->cartLabel());
actCurrentCart->setEnabled(false);
actInsertCart = menu->addAction("Insert cart...");
connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart);
actEjectCart = menu->addAction("Eject cart");
connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart);
menu->addSeparator();
actCurrentGBACart = menu->addAction("GBA slot: " + emuInstance->gbaCartLabel());
actCurrentGBACart->setEnabled(false);
actInsertGBACart = menu->addAction("Insert ROM cart...");
connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart);
{
QMenu * submenu = menu->addMenu("Insert add-on cart");
QAction *act;
int addons[] = {GBAAddon_RAMExpansion, GBAAddon_RumblePak, -1};
for (int i = 0; addons[i] != -1; i++)
{
int addon = addons[i];
act = submenu->addAction(emuInstance->gbaAddonName(addon));
act->setData(QVariant(addon));
connect(act, &QAction::triggered, this, &MainWindow::onInsertGBAAddon);
actInsertGBAAddon.append(act);
}
}
actEjectGBACart = menu->addAction("Eject cart");
connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart);
menu->addSeparator();
actImportSavefile = menu->addAction("Import savefile");
connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile);
menu->addSeparator();
{
QMenu * submenu = menu->addMenu("Save state");
for (int i = 1; i < 9; i++)
{
actSaveState[i] = submenu->addAction(QString("%1").arg(i));
actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1 + i - 1)));
actSaveState[i]->setData(QVariant(i));
connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState);
}
actSaveState[0] = submenu->addAction("File...");
actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9));
actSaveState[0]->setData(QVariant(0));
connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState);
}
{
QMenu * submenu = menu->addMenu("Load state");
for (int i = 1; i < 9; i++)
{
actLoadState[i] = submenu->addAction(QString("%1").arg(i));
actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1 + i - 1));
actLoadState[i]->setData(QVariant(i));
connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState);
}
actLoadState[0] = submenu->addAction("File...");
actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9));
actLoadState[0]->setData(QVariant(0));
connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState);
}
actUndoStateLoad = menu->addAction("Undo state load");
actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12));
connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad);
menu->addSeparator();
actOpenConfig = menu->addAction("Open melonDS directory");
connect(actOpenConfig, &QAction::triggered, this, [&]()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(emuDirectory));
});
menu->addSeparator();
actQuit = menu->addAction("Quit");
connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit);
actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit));
}
{
QMenu * menu = menubar->addMenu("System");
actPause = menu->addAction("Pause");
actPause->setCheckable(true);
connect(actPause, &QAction::triggered, this, &MainWindow::onPause);
actReset = menu->addAction("Reset");
connect(actReset, &QAction::triggered, this, &MainWindow::onReset);
actStop = menu->addAction("Stop");
connect(actStop, &QAction::triggered, this, &MainWindow::onStop);
actFrameStep = menu->addAction("Frame step");
connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep);
menu->addSeparator();
actPowerManagement = menu->addAction("Power management");
connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement);
actDateTime = menu->addAction("Date and time");
connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime);
menu->addSeparator();
actLuaScript = menu->addAction("Lua Script");
connect(actLuaScript,&QAction::triggered,this,&MainWindow::onOpenLuaScript);
menu->addSeparator();
actEnableCheats = menu->addAction("Enable cheats");
actEnableCheats->setCheckable(true);
connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats);
//if (inst == 0)
{
actSetupCheats = menu->addAction("Setup cheat codes");
actSetupCheats->setMenuRole(QAction::NoRole);
connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats);
menu->addSeparator();
actROMInfo = menu->addAction("ROM info");
connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo);
actRAMInfo = menu->addAction("RAM search");
connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo);
actTitleManager = menu->addAction("Manage DSi titles");
connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager);
}
{
menu->addSeparator();
QMenu * submenu = menu->addMenu("Multiplayer");
actMPNewInstance = submenu->addAction("Launch new instance");
connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance);
submenu->addSeparator();
actLANStartHost = submenu->addAction("Host LAN game");
connect(actLANStartHost, &QAction::triggered, this, &MainWindow::onLANStartHost);
actLANStartClient = submenu->addAction("Join LAN game");
connect(actLANStartClient, &QAction::triggered, this, &MainWindow::onLANStartClient);
/*submenu->addSeparator();
actNPStartHost = submenu->addAction("NETPLAY HOST");
connect(actNPStartHost, &QAction::triggered, this, &MainWindow::onNPStartHost);
actNPStartClient = submenu->addAction("NETPLAY CLIENT");
connect(actNPStartClient, &QAction::triggered, this, &MainWindow::onNPStartClient);
actNPTest = submenu->addAction("NETPLAY GO");
connect(actNPTest, &QAction::triggered, this, &MainWindow::onNPTest);*/
}
}
{
QMenu * menu = menubar->addMenu("View");
{
QMenu * submenu = menu->addMenu("Screen size");
for (int i = 0; i < 4; i++)
{
int data = i + 1;
actScreenSize[i] = submenu->addAction(QString("%1x").arg(data));
actScreenSize[i]->setData(QVariant(data));
connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize);
}
}
{
QMenu * submenu = menu->addMenu("Screen rotation");
grpScreenRotation = new QActionGroup(submenu);
for (int i = 0; i < screenRot_MAX; i++)
{
int data = i * 90;
actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data));
actScreenRotation[i]->setActionGroup(grpScreenRotation);
actScreenRotation[i]->setData(QVariant(i));
actScreenRotation[i]->setCheckable(true);
}
connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation);
}
{
QMenu * submenu = menu->addMenu("Screen gap");
grpScreenGap = new QActionGroup(submenu);
const int screengap[] = {0, 1, 8, 64, 90, 128};
for (int i = 0; i < 6; i++)
{
int data = screengap[i];
actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data));
actScreenGap[i]->setActionGroup(grpScreenGap);
actScreenGap[i]->setData(QVariant(data));
actScreenGap[i]->setCheckable(true);
}
connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap);
}
{
QMenu * submenu = menu->addMenu("Screen layout");
grpScreenLayout = new QActionGroup(submenu);
const char *screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"};
for (int i = 0; i < screenLayout_MAX; i++)
{
actScreenLayout[i] = submenu->addAction(QString(screenlayout[i]));
actScreenLayout[i]->setActionGroup(grpScreenLayout);
actScreenLayout[i]->setData(QVariant(i));
actScreenLayout[i]->setCheckable(true);
}
connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout);
submenu->addSeparator();
actScreenSwap = submenu->addAction("Swap screens");
actScreenSwap->setCheckable(true);
connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap);
}
{
QMenu * submenu = menu->addMenu("Screen sizing");
grpScreenSizing = new QActionGroup(submenu);
const char *screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only",
"Bottom only"};
for (int i = 0; i < screenSizing_MAX; i++)
{
actScreenSizing[i] = submenu->addAction(QString(screensizing[i]));
actScreenSizing[i]->setActionGroup(grpScreenSizing);
actScreenSizing[i]->setData(QVariant(i));
actScreenSizing[i]->setCheckable(true);
}
connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing);
submenu->addSeparator();
actIntegerScaling = submenu->addAction("Force integer scaling");
actIntegerScaling->setCheckable(true);
connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling);
}
{
QMenu * submenu = menu->addMenu("Aspect ratio");
grpScreenAspectTop = new QActionGroup(submenu);
grpScreenAspectBot = new QActionGroup(submenu);
actScreenAspectTop = new QAction *[AspectRatiosNum];
actScreenAspectBot = new QAction *[AspectRatiosNum];
for (int i = 0; i < 2; i++)
{
QActionGroup * group = grpScreenAspectTop;
QAction **actions = actScreenAspectTop;
if (i == 1)
{
group = grpScreenAspectBot;
submenu->addSeparator();
actions = actScreenAspectBot;
}
for (int j = 0; j < AspectRatiosNum; j++)
{
auto ratio = aspectRatios[j];
QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label);
actions[j] = submenu->addAction(label);
actions[j]->setActionGroup(group);
actions[j]->setData(QVariant(ratio.id));
actions[j]->setCheckable(true);
}
connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect);
}
}
menu->addSeparator();
actNewWindow = menu->addAction("Open new window");
connect(actNewWindow, &QAction::triggered, this, &MainWindow::onOpenNewWindow);
menu->addSeparator();
actScreenFiltering = menu->addAction("Screen filtering");
actScreenFiltering->setCheckable(true);
connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering);
actShowOSD = menu->addAction("Show OSD");
actShowOSD->setCheckable(true);
connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD);
}
{
QMenu * menu = menubar->addMenu("Config");
actEmuSettings = menu->addAction("Emu settings");
connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings);
#ifdef __APPLE__
actPreferences = menu->addAction("Preferences...");
connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings);
actPreferences->setMenuRole(QAction::PreferencesRole);
#endif
actInputConfig = menu->addAction("Input and hotkeys");
connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig);
actVideoSettings = menu->addAction("Video settings");
connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings);
actCameraSettings = menu->addAction("Camera settings");
connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings);
actAudioSettings = menu->addAction("Audio settings");
connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings);
actMPSettings = menu->addAction("Multiplayer settings");
connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings);
actWifiSettings = menu->addAction("Wifi settings");
connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings);
actFirmwareSettings = menu->addAction("Firmware settings");
connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings);
actInterfaceSettings = menu->addAction("Interface settings");
connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings);
actPathSettings = menu->addAction("Path settings");
connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings);
{
QMenu * submenu = menu->addMenu("Savestate settings");
actSavestateSRAMReloc = submenu->addAction("Separate savefiles");
actSavestateSRAMReloc->setCheckable(true);
connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc);
}
menu->addSeparator();
actLimitFramerate = menu->addAction("Limit framerate");
actLimitFramerate->setCheckable(true);
connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate);
actAudioSync = menu->addAction("Audio sync");
actAudioSync->setCheckable(true);
connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync);
}
{
QMenu * menu = menubar->addMenu("Help");
actAbout = menu->addAction("About...");
connect(actAbout, &QAction::triggered, this, [&]
{
auto dialog = AboutDialog(this);
dialog.exec();
});
}
setMenuBar(menubar);
if (localCfg.GetString("Firmware.Username") == "Arisotura")
actMPNewInstance->setText("Fart");
}
#ifdef Q_OS_MAC
QPoint screenCenter = screen()->availableGeometry().center();
QRect frameGeo = frameGeometry();
frameGeo.moveCenter(screenCenter);
move(frameGeo.topLeft());
#endif
std::string geom = windowCfg.GetString("Geometry");
if (!geom.empty())
{
QByteArray raw = QByteArray::fromStdString(geom);
QByteArray dec = QByteArray::fromBase64(raw, QByteArray::Base64Encoding | QByteArray::AbortOnBase64DecodingErrors);
if (!dec.isEmpty())
restoreGeometry(dec);
// if the window was closed in fullscreen do not restore this
setWindowState(windowState() & ~Qt::WindowFullScreen);
}
show();
panel = nullptr;
createScreenPanel();
if (hasMenu)
{
actEjectCart->setEnabled(false);
actEjectGBACart->setEnabled(false);
if (globalCfg.GetInt("Emu.ConsoleType") == 1)
{
actInsertGBACart->setEnabled(false);
for (auto act: actInsertGBAAddon)
act->setEnabled(false);
}
for (int i = 0; i < 9; i++)
{
actSaveState[i]->setEnabled(false);
actLoadState[i]->setEnabled(false);
}
actUndoStateLoad->setEnabled(false);
actImportSavefile->setEnabled(false);
actPause->setEnabled(false);
actReset->setEnabled(false);
actStop->setEnabled(false);
actFrameStep->setEnabled(false);
actDateTime->setEnabled(true);
actPowerManagement->setEnabled(false);
actEnableCheats->setEnabled(false);
actSetupCheats->setEnabled(false);
actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty());
actEnableCheats->setChecked(localCfg.GetBool("EnableCheats"));
actROMInfo->setEnabled(false);
actRAMInfo->setEnabled(false);
actSavestateSRAMReloc->setChecked(globalCfg.GetBool("Savestate.RelocSRAM"));
actScreenRotation[windowCfg.GetInt("ScreenRotation")]->setChecked(true);
int screenGap = windowCfg.GetInt("ScreenGap");
for (int i = 0; i < 6; i++)
{
if (actScreenGap[i]->data().toInt() == screenGap)
{
actScreenGap[i]->setChecked(true);
break;
}
}
actScreenLayout[windowCfg.GetInt("ScreenLayout")]->setChecked(true);
actScreenSizing[windowCfg.GetInt("ScreenSizing")]->setChecked(true);
actIntegerScaling->setChecked(windowCfg.GetBool("IntegerScaling"));
actScreenSwap->setChecked(windowCfg.GetBool("ScreenSwap"));
int aspectTop = windowCfg.GetInt("ScreenAspectTop");
int aspectBot = windowCfg.GetInt("ScreenAspectBot");
for (int i = 0; i < AspectRatiosNum; i++)
{
if (aspectTop == aspectRatios[i].id)
actScreenAspectTop[i]->setChecked(true);
if (aspectBot == aspectRatios[i].id)
actScreenAspectBot[i]->setChecked(true);
}
actScreenFiltering->setChecked(windowCfg.GetBool("ScreenFilter"));
actShowOSD->setChecked(showOSD);
actLimitFramerate->setChecked(emuInstance->doLimitFPS);
actAudioSync->setChecked(emuInstance->doAudioSync);
if (emuInstance->instanceID > 0)
{
actEmuSettings->setEnabled(false);
actVideoSettings->setEnabled(false);
actMPSettings->setEnabled(false);
actWifiSettings->setEnabled(false);
actInterfaceSettings->setEnabled(false);
#ifdef __APPLE__
actPreferences->setEnabled(false);
#endif // __APPLE__
}
if (emuThread->emuIsActive())
onEmuStart();
}
QObject::connect(qApp, &QApplication::applicationStateChanged, this, &MainWindow::onAppStateChanged);
onUpdateInterfaceSettings();
updateMPInterface(MPInterface::GetType());
}
MainWindow::~MainWindow()
{
if (hasMenu)
{
delete[] actScreenAspectTop;
delete[] actScreenAspectBot;
}
}
void MainWindow::osdAddMessage(unsigned int color, const char* msg)
{
if (!showOSD) return;
panel->osdAddMessage(color, msg);
}
void MainWindow::saveEnabled(bool enabled)
{
if (enabledSaved) return;
windowCfg.SetBool("Enabled", enabled);
enabledSaved = true;
}
void MainWindow::closeEvent(QCloseEvent* event)
{
if (!emuInstance) return;
if (windowID == 0)
emuInstance->saveEnabledWindows();
else
saveEnabled(false);
QByteArray geom = saveGeometry();
QByteArray enc = geom.toBase64(QByteArray::Base64Encoding);
windowCfg.SetString("Geometry", enc.toStdString());
Config::Save();
emuInstance->deleteWindow(windowID, false);
// emuInstance may be deleted
// prevent use after free from us
emuInstance = nullptr;
QMainWindow::closeEvent(event);
}
void MainWindow::createScreenPanel()
{
if (panel) delete panel;
panel = nullptr;
hasOGL = globalCfg.GetBool("Screen.UseGL") ||
(globalCfg.GetInt("3D.Renderer") != renderer3D_Software);
if (hasOGL)
{
ScreenPanelGL* panelGL = new ScreenPanelGL(this);
panelGL->show();
panel = panelGL;
// Check that creating the context hasn't failed
if (panelGL->createContext() == false)
{
Log(Platform::LogLevel::Error, "Failed to create OpenGL context, falling back to Software Renderer.\n");
hasOGL = false;
globalCfg.SetBool("Screen.UseGL", false);
globalCfg.SetInt("3D.Renderer", renderer3D_Software);
}
}
if (!hasOGL)
{
ScreenPanelNative* panelNative = new ScreenPanelNative(this);
panel = panelNative;
panel->show();
}
setCentralWidget(panel);
if (hasMenu)
actScreenFiltering->setEnabled(hasOGL);
panel->osdSetEnabled(showOSD);
connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint()));
connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged()));
emit screenLayoutChange();
}
GL::Context* MainWindow::getOGLContext()
{
if (!hasOGL) return nullptr;
ScreenPanelGL* glpanel = static_cast<ScreenPanelGL*>(panel);
return glpanel->getContext();
}
void MainWindow::initOpenGL()
{
if (!hasOGL) return;
ScreenPanelGL* glpanel = static_cast<ScreenPanelGL*>(panel);
return glpanel->initOpenGL();
}
void MainWindow::deinitOpenGL()
{
if (!hasOGL) return;
ScreenPanelGL* glpanel = static_cast<ScreenPanelGL*>(panel);
return glpanel->deinitOpenGL();
}
void MainWindow::setGLSwapInterval(int intv)
{
if (!hasOGL) return;
ScreenPanelGL* glpanel = static_cast<ScreenPanelGL*>(panel);
return glpanel->setSwapInterval(intv);
}
void MainWindow::makeCurrentGL()
{
if (!hasOGL) return;
ScreenPanelGL* glpanel = static_cast<ScreenPanelGL*>(panel);
return glpanel->makeCurrentGL();
}
void MainWindow::drawScreenGL()
{
if (!hasOGL) return;
ScreenPanelGL* glpanel = static_cast<ScreenPanelGL*>(panel);
return glpanel->drawScreenGL();
}
void MainWindow::keyPressEvent(QKeyEvent* event)
{
if (event->isAutoRepeat()) return;
// TODO!! REMOVE ME IN RELEASE BUILDS!!
//if (event->key() == Qt::Key_F11) emuThread->NDS->debug(0);
emuInstance->onKeyPress(event);
}
void MainWindow::keyReleaseEvent(QKeyEvent* event)
{
if (event->isAutoRepeat()) return;
emuInstance->onKeyRelease(event);
}
void MainWindow::dragEnterEvent(QDragEnterEvent* event)
{
if (!event->mimeData()->hasUrls()) return;
QList<QUrl> urls = event->mimeData()->urls();
if (urls.count() > 1) return; // not handling more than one file at once
QString filename = urls.at(0).toLocalFile();
if (FileIsSupportedFiletype(filename))
event->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent* event)
{
if (!event->mimeData()->hasUrls()) return;
QList<QUrl> urls = event->mimeData()->urls();
if (urls.count() > 1) return; // not handling more than one file at once
if (!verifySetup())
return;
const QStringList file = splitArchivePath(urls.at(0).toLocalFile(), false);
if (file.isEmpty())
return;
const QString filename = file.last();
const bool romInsideArchive = file.size() > 1;
const auto matchMode = romInsideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault;
const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchMode);
bool isNdsRom = NdsRomByExtension(filename) || NdsRomByMimetype(mimetype);
bool isGbaRom = GbaRomByExtension(filename) || GbaRomByMimetype(mimetype);
isNdsRom |= ZstdNdsRomByExtension(filename);
isGbaRom |= ZstdGbaRomByExtension(filename);
QString errorstr;
if (isNdsRom)
{
if (!emuThread->bootROM(file, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return;
}
const QString barredFilename = file.join('|');
recentFileList.removeAll(barredFilename);
recentFileList.prepend(barredFilename);
updateRecentFilesMenu();
updateCartInserted(false);
}
else if (isGbaRom)
{
if (!emuThread->insertCart(file, true, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return;
}
updateCartInserted(true);
}
else
{
QMessageBox::critical(this, "melonDS", "The file could not be recognized as a DS or GBA ROM.");
return;
}
}
void MainWindow::focusInEvent(QFocusEvent* event)
{
onFocusIn();
}
void MainWindow::focusOutEvent(QFocusEvent* event)
{
onFocusOut();
}
void MainWindow::onFocusIn()
{
focused = true;
if (emuInstance)
emuInstance->audioMute();
}
void MainWindow::onFocusOut()
{
// focusOutEvent is called through the window close event handler
// prevent use after free
focused = false;
if (emuInstance)
emuInstance->audioMute();
}
void MainWindow::onAppStateChanged(Qt::ApplicationState state)
{
if (state == Qt::ApplicationInactive)
{
emuInstance->keyReleaseAll();
if (pauseOnLostFocus && emuThread->emuIsRunning())
emuThread->emuPause();
}
else if (state == Qt::ApplicationActive)
{
if (pauseOnLostFocus && !pausedManually)
emuThread->emuUnpause();
}
}
bool MainWindow::verifySetup()
{
QString res = emuInstance->verifySetup();
if (!res.isEmpty())
{
QMessageBox::critical(this, "melonDS", res);
return false;
}
return true;
}
bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot)
{
QString errorstr;
if (file.isEmpty() && gbafile.isEmpty())
return false;
if (!verifySetup())
{
return false;
}
bool gbaloaded = false;
if (!gbafile.isEmpty())
{
if (!emuThread->insertCart(gbafile, true, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return false;
}
gbaloaded = true;
}
bool ndsloaded = false;
if (!file.isEmpty())
{
if (boot)
{
if (!emuThread->bootROM(file, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return false;
}
}
else
{
if (!emuThread->insertCart(file, false, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return false;
}
}
recentFileList.removeAll(file.join("|"));
recentFileList.prepend(file.join("|"));
updateRecentFilesMenu();
ndsloaded = true;
}
else if (boot)
{
if (!emuThread->bootFirmware(errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return false;
}
}
updateCartInserted(false);
if (gbaloaded)
updateCartInserted(true);
return true;
}
QStringList MainWindow::splitArchivePath(const QString& filename, bool useMemberSyntax)
{
if (filename.isEmpty()) return {};
#ifdef ARCHIVE_SUPPORT_ENABLED
if (useMemberSyntax)
{
const QStringList filenameParts = filename.split('|');
if (filenameParts.size() > 2)
{
QMessageBox::warning(this, "melonDS", "This path contains too many '|'.");
return {};
}
if (filenameParts.size() == 2)
{
const QString archive = filenameParts.at(0);
if (!QFileInfo(archive).exists())
{
QMessageBox::warning(this, "melonDS", "This archive does not exist.");
return {};
}
const QString subfile = filenameParts.at(1);
if (!Archive::ListArchive(archive).contains(subfile))
{
QMessageBox::warning(this, "melonDS", "This archive does not contain the desired file.");
return {};
}
return filenameParts;
}
}
#endif
if (!QFileInfo(filename).exists())
{
QMessageBox::warning(this, "melonDS", "This ROM file does not exist.");
return {};
}
#ifdef ARCHIVE_SUPPORT_ENABLED
if (SupportedArchiveByExtension(filename)
|| SupportedArchiveByMimetype(QMimeDatabase().mimeTypeForFile(filename)))
{
const QString subfile = pickFileFromArchive(filename);
if (subfile.isEmpty())
return {};
return { filename, subfile };
}
#endif
return { filename };
}
QString MainWindow::pickFileFromArchive(QString archiveFileName)
{
QVector<QString> archiveROMList = Archive::ListArchive(archiveFileName);
if (archiveROMList.size() <= 1)
{
if (!archiveROMList.isEmpty() && archiveROMList.at(0) == "OK")
QMessageBox::warning(this, "melonDS", "This archive is empty.");
else
QMessageBox::critical(this, "melonDS", "This archive could not be read. It may be corrupt or you don't have the permissions.");
return QString();
}
archiveROMList.removeFirst();
const auto notSupportedRom = [&](const auto& filename){
if (NdsRomByExtension(filename) || GbaRomByExtension(filename))
return false;
const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension);
return !(NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype));
};
archiveROMList.erase(std::remove_if(archiveROMList.begin(), archiveROMList.end(), notSupportedRom),
archiveROMList.end());
if (archiveROMList.isEmpty())
{
QMessageBox::warning(this, "melonDS", "This archive does not contain any supported ROMs.");
return QString();
}
if (archiveROMList.size() == 1)
return archiveROMList.first();
bool ok;
const QString toLoad = QInputDialog::getItem(
this, "melonDS",
"This archive contains multiple files. Select which ROM you want to load.",
archiveROMList.toList(), 0, false, &ok
);
if (ok) return toLoad;
// User clicked on cancel
return QString();
}
QStringList MainWindow::pickROM(bool gba)
{
emuThread->emuPause();
const QString console = gba ? "GBA" : "DS";
const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions;
QString rawROMs = romexts.join(" *");
QString extraFilters = ";;" + console + " ROMs (*" + rawROMs;
QString allROMs = rawROMs;
QString zstdROMs = "*" + romexts.join(".zst *") + ".zst";
extraFilters += ");;Zstandard-compressed " + console + " ROMs (" + zstdROMs + ")";
allROMs += " " + zstdROMs;
#ifdef ARCHIVE_SUPPORT_ENABLED
QString archives = "*" + ArchiveExtensions.join(" *");
extraFilters += ";;Archives (" + archives + ")";
allROMs += " " + archives;
#endif
extraFilters += ";;All files (*.*)";
const QString filename = QFileDialog::getOpenFileName(
this, "Open " + console + " ROM",
globalCfg.GetQString("LastROMFolder"),
"All supported files (*" + allROMs + ")" + extraFilters
);
if (filename.isEmpty())
{
emuThread->emuUnpause();
return {};
}
globalCfg.SetQString("LastROMFolder", QFileInfo(filename).dir().path());
auto ret = splitArchivePath(filename, false);
emuThread->emuUnpause();
return ret;
}
void MainWindow::updateCartInserted(bool gba)
{
bool inserted;
QString label;
if (gba)
{
inserted = emuInstance->gbaCartInserted() && (emuInstance->getConsoleType() == 0);
label = "GBA slot: " + emuInstance->gbaCartLabel();
emuInstance->doOnAllWindows([=](MainWindow* win)
{
if (!win->hasMenu) return;
win->actCurrentGBACart->setText(label);
win->actEjectGBACart->setEnabled(inserted);
});
}
else
{
inserted = emuInstance->cartInserted();
label = "DS slot: " + emuInstance->cartLabel();
emuInstance->doOnAllWindows([=](MainWindow* win)
{
if (!win->hasMenu) return;
win->actCurrentCart->setText(label);
win->actEjectCart->setEnabled(inserted);
win->actImportSavefile->setEnabled(inserted);
win->actEnableCheats->setEnabled(inserted);
win->actSetupCheats->setEnabled(inserted);
win->actROMInfo->setEnabled(inserted);
win->actRAMInfo->setEnabled(inserted);
});
}
}
void MainWindow::onOpenFile()
{
if (!verifySetup())
return;
QStringList file = pickROM(false);
if (file.isEmpty())
return;
QString errorstr;
if (!emuThread->bootROM(file, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return;
}
QString filename = file.join('|');
recentFileList.removeAll(filename);
recentFileList.prepend(filename);
updateRecentFilesMenu();
updateCartInserted(false);
}
void MainWindow::onClearRecentFiles()
{
recentFileList.clear();
globalCfg.GetArray("RecentROM").Clear();
updateRecentFilesMenu();
}
void MainWindow::loadRecentFilesMenu(bool loadcfg)
{
if (loadcfg)
{
recentFileList.clear();
Config::Array recentROMs = globalCfg.GetArray("RecentROM");
int numrecent = std::min(kMaxRecentROMs, (int) recentROMs.Size());
for (int i = 0; i < numrecent; ++i)
{
std::string item = recentROMs.GetString(i);
if (!item.empty())
recentFileList.push_back(QString::fromStdString(item));
}
}
recentMenu->clear();
for (int i = 0; i < recentFileList.size(); ++i)
{
if (i >= kMaxRecentROMs) break;
QString item_full = recentFileList.at(i);
QString item_display = item_full;
int itemlen = item_full.length();
const int maxlen = 100;
if (itemlen > maxlen)
{
int cut_start = 0;
while (item_full[cut_start] != '/' && item_full[cut_start] != '\\' &&
cut_start < itemlen)
cut_start++;
int cut_end = itemlen-1;
while (((item_full[cut_end] != '/' && item_full[cut_end] != '\\') ||
(cut_start+4+(itemlen-cut_end) < maxlen)) &&
cut_end > 0)
cut_end--;
item_display.truncate(cut_start+1);
item_display += "...";
item_display += QString(item_full).remove(0, cut_end);
}
QAction *actRecentFile_i = recentMenu->addAction(QString("%1. %2").arg(i+1).arg(item_display));
actRecentFile_i->setData(item_full);
connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile);
}
while (recentFileList.size() > 10)
recentFileList.removeLast();
recentMenu->addSeparator();
QAction *actClearRecentList = recentMenu->addAction("Clear");
connect(actClearRecentList, &QAction::triggered, this, &MainWindow::onClearRecentFiles);
if (recentFileList.empty())
actClearRecentList->setEnabled(false);
}
void MainWindow::updateRecentFilesMenu()
{
Config::Array recentroms = globalCfg.GetArray("RecentROM");
recentroms.Clear();
for (int i = 0; i < recentFileList.size(); ++i)
{
if (i >= kMaxRecentROMs) break;
recentroms.SetQString(i, recentFileList.at(i));
}
Config::Save();
loadRecentFilesMenu(false);
emuInstance->broadcastCommand(InstCmd_UpdateRecentFiles);
}
void MainWindow::onClickRecentFile()
{
QAction *act = (QAction *)sender();
QString filename = act->data().toString();
if (!verifySetup())
return;
const QStringList file = splitArchivePath(filename, true);
if (file.isEmpty())
return;
QString errorstr;
if (!emuThread->bootROM(file, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return;
}
recentFileList.removeAll(filename);
recentFileList.prepend(filename);
updateRecentFilesMenu();
updateCartInserted(false);
}
void MainWindow::onBootFirmware()
{
if (!verifySetup())
return;
QString errorstr;
if (!emuThread->bootFirmware(errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return;
}
}
void MainWindow::onInsertCart()
{
QStringList file = pickROM(false);
if (file.isEmpty())
return;
QString errorstr;
if (!emuThread->insertCart(file, false, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return;
}
updateCartInserted(false);
}
void MainWindow::onEjectCart()
{
emuThread->ejectCart(false);
updateCartInserted(false);
}
void MainWindow::onInsertGBACart()
{
QStringList file = pickROM(true);
if (file.isEmpty())
return;
QString errorstr;
if (!emuThread->insertCart(file, true, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return;
}
updateCartInserted(true);
}
void MainWindow::onInsertGBAAddon()
{
QAction* act = (QAction*)sender();
int type = act->data().toInt();
QString errorstr;
if (!emuThread->insertGBAAddon(type, errorstr))
{
QMessageBox::critical(this, "melonDS", errorstr);
return;
}
updateCartInserted(true);
}
void MainWindow::onEjectGBACart()
{
emuThread->ejectCart(true);
updateCartInserted(true);
}
void MainWindow::onLuaSaveState(const QString& filename)
{
emuThread->emuPause();
emuInstance->saveState(filename.toStdString());
emuThread->emuUnpause();
}
void MainWindow::onSaveState()
{
int slot = ((QAction*)sender())->data().toInt();
QString filename;
if (slot > 0)
{
filename = QString::fromStdString(emuInstance->getSavestateName(slot));
}
else
{
// TODO: specific 'last directory' for savestate files?
emuThread->emuPause();
filename = QFileDialog::getSaveFileName(this,
"Save state",
globalCfg.GetQString("LastROMFolder"),
"melonDS savestates (*.mln);;Any file (*.*)");
emuThread->emuUnpause();
if (filename.isEmpty())
return;
}
if (emuThread->saveState(filename))
{
if (slot > 0) emuInstance->osdAddMessage(0, "State saved to slot %d", slot);
else emuInstance->osdAddMessage(0, "State saved to file");
actLoadState[slot]->setEnabled(true);
}
else
{
emuInstance->osdAddMessage(0xFFA0A0, "State save failed");
}
}
void MainWindow::onLuaLoadState(const QString& filename)
{
emuThread->emuPause();
emuInstance->loadState(filename.toStdString());
emuThread->emuUnpause();
}
void MainWindow::onLoadState()
{
int slot = ((QAction*)sender())->data().toInt();
QString filename;
if (slot > 0)
{
filename = QString::fromStdString(emuInstance->getSavestateName(slot));
}
else
{
// TODO: specific 'last directory' for savestate files?
emuThread->emuPause();
filename = QFileDialog::getOpenFileName(this,
"Load state",
globalCfg.GetQString("LastROMFolder"),
"melonDS savestates (*.ml*);;Any file (*.*)");
emuThread->emuUnpause();
if (filename.isEmpty())
return;
}
if (!Platform::FileExists(filename.toStdString()))
{
if (slot > 0) emuInstance->osdAddMessage(0xFFA0A0, "State slot %d is empty", slot);
else emuInstance->osdAddMessage(0xFFA0A0, "State file does not exist");
return;
}
if (emuThread->loadState(filename))
{
if (slot > 0) emuInstance->osdAddMessage(0, "State loaded from slot %d", slot);
else emuInstance->osdAddMessage(0, "State loaded from file");
actUndoStateLoad->setEnabled(true);
}
else
{
emuInstance->osdAddMessage(0xFFA0A0, "State load failed");
}
}
void MainWindow::onUndoStateLoad()
{
emuThread->undoStateLoad();
emuInstance->osdAddMessage(0, "State load undone");
}
void MainWindow::onImportSavefile()
{
QString path = QFileDialog::getOpenFileName(this,
"Select savefile",
globalCfg.GetQString("LastROMFolder"),
"Savefiles (*.sav *.bin *.dsv);;Any file (*.*)");
if (path.isEmpty())
return;
if (!Platform::FileExists(path.toStdString()))
{
QMessageBox::critical(this, "melonDS", "Could not open the given savefile.");
return;
}
if (emuThread->emuIsActive())
{
if (QMessageBox::warning(this,
"melonDS",
"The emulation will be reset and the current savefile overwritten.",
QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok)
{
return;
}
}
if (!emuThread->importSavefile(path))
{
QMessageBox::critical(this, "melonDS", "Could not import the given savefile.");
return;
}
}
void MainWindow::onQuit()
{
#ifndef _WIN32
if (!parentWidget())
signalSn->setEnabled(false);
#endif
close();
}
void MainWindow::onPause(bool checked)
{
if (!emuThread->emuIsActive()) return;
if (checked)
{
emuThread->emuPause();
pausedManually = true;
}
else
{
emuThread->emuUnpause();
pausedManually = false;
}
}
void MainWindow::onReset()
{
if (!emuThread->emuIsActive()) return;
emuThread->emuReset();
}
void MainWindow::onStop()
{
if (!emuThread->emuIsActive()) return;
emuThread->emuStop(true);
}
void MainWindow::onFrameStep()
{
if (!emuThread->emuIsActive()) return;
emuThread->emuFrameStep();
}
void MainWindow::onOpenDateTime()
{
DateTimeDialog* dlg = DateTimeDialog::openDlg(this);
}
void MainWindow::onOpenPowerManagement()
{
PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this);
}
void MainWindow::onOpenLuaScript()
{
if (luaDialog && luaDialog->flagClosed)
{
delete luaDialog;
luaDialog = nullptr;
}
if (luaDialog)
return;
luaDialog = new LuaConsoleDialog(this);
luaDialog->show();
connect(emuThread,&EmuThread::signalLuaUpdate,luaDialog,&LuaConsoleDialog::onLuaUpdate);
connect(luaDialog,&LuaConsoleDialog::signalLuaSaveState,this,&MainWindow::onLuaSaveState);
connect(luaDialog,&LuaConsoleDialog::signalLuaLoadState,this,&MainWindow::onLuaLoadState);
}
void MainWindow::onEnableCheats(bool checked)
{
localCfg.SetBool("EnableCheats", checked);
emuThread->enableCheats(checked);
emuInstance->doOnAllWindows([=](MainWindow* win)
{
win->actEnableCheats->setChecked(checked);
}, windowID);
}
void MainWindow::onSetupCheats()
{
emuThread->emuPause();
CheatsDialog* dlg = CheatsDialog::openDlg(this);
connect(dlg, &CheatsDialog::finished, this, &MainWindow::onCheatsDialogFinished);
}
void MainWindow::onCheatsDialogFinished(int res)
{
emuThread->emuUnpause();
}
void MainWindow::onROMInfo()
{
auto cart = emuInstance->nds->NDSCartSlot.GetCart();
if (cart)
ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this);
}
void MainWindow::onRAMInfo()
{
RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this);
}
void MainWindow::onOpenTitleManager()
{
TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this);
}
void MainWindow::onMPNewInstance()
{
createEmuInstance();
}
void MainWindow::onLANStartHost()
{
if (!lanWarning(true)) return;
LANStartHostDialog::openDlg(this);
}
void MainWindow::onLANStartClient()
{
if (!lanWarning(false)) return;
LANStartClientDialog::openDlg(this);
}
void MainWindow::onNPStartHost()
{
//Netplay::StartHost();
//NetplayStartHostDialog::openDlg(this);
}
void MainWindow::onNPStartClient()
{
//Netplay::StartClient();
//NetplayStartClientDialog::openDlg(this);
}
void MainWindow::onNPTest()
{
// HAX
//Netplay::StartGame();
}
void MainWindow::updateMPInterface(MPInterfaceType type)
{
if (!hasMenu) return;
// MP interface was changed, reflect it in the UI
bool enable = (type == MPInterface_Local);
actMPNewInstance->setEnabled(enable);
actLANStartHost->setEnabled(enable);
actLANStartClient->setEnabled(enable);
/*actNPStartHost->setEnabled(enable);
actNPStartClient->setEnabled(enable);
actNPTest->setEnabled(enable);*/
}
bool MainWindow::lanWarning(bool host)
{
if (numEmuInstances() < 2)
return true;
QString verb = host ? "host" : "join";
QString msg = "Multiple emulator instances are currently open.\n"
"If you "+verb+" a LAN game now, all secondary instances will be closed.\n\n"
"Do you wish to continue?";
auto res = QMessageBox::warning(this, "melonDS", msg, QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
if (res == QMessageBox::No)
return false;
deleteAllEmuInstances(1);
return true;
}
void MainWindow::onOpenEmuSettings()
{
emuThread->emuPause();
EmuSettingsDialog* dlg = EmuSettingsDialog::openDlg(this);
connect(dlg, &EmuSettingsDialog::finished, this, &MainWindow::onEmuSettingsDialogFinished);
}
void MainWindow::onEmuSettingsDialogFinished(int res)
{
if (globalCfg.GetInt("Emu.ConsoleType") == 1)
{
actInsertGBACart->setEnabled(false);
for (auto act : actInsertGBAAddon)
act->setEnabled(false);
actEjectGBACart->setEnabled(false);
}
else
{
actInsertGBACart->setEnabled(true);
for (auto act : actInsertGBAAddon)
act->setEnabled(true);
actEjectGBACart->setEnabled(emuInstance->gbaCartInserted());
}
if (EmuSettingsDialog::needsReset)
onReset();
actCurrentGBACart->setText("GBA slot: " + emuInstance->gbaCartLabel());
if (!emuThread->emuIsActive())
actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty());
emuThread->emuUnpause();
}
void MainWindow::onOpenInputConfig()
{
emuThread->emuPause();
InputConfigDialog* dlg = InputConfigDialog::openDlg(this);
connect(dlg, &InputConfigDialog::finished, this, &MainWindow::onInputConfigFinished);
}
void MainWindow::onInputConfigFinished(int res)
{
emuThread->emuUnpause();
}
void MainWindow::onOpenVideoSettings()
{
VideoSettingsDialog* dlg = VideoSettingsDialog::openDlg(this);
connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings);
}
void MainWindow::onOpenCameraSettings()
{
emuThread->emuPause();
camStarted[0] = camManager[0]->isStarted();
camStarted[1] = camManager[1]->isStarted();
if (camStarted[0]) camManager[0]->stop();
if (camStarted[1]) camManager[1]->stop();
CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this);
connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished);
}
void MainWindow::onCameraSettingsFinished(int res)
{
if (camStarted[0]) camManager[0]->start();
if (camStarted[1]) camManager[1]->start();
emuThread->emuUnpause();
}
void MainWindow::onOpenAudioSettings()
{
AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this);
connect(emuThread, &EmuThread::syncVolumeLevel, dlg, &AudioSettingsDialog::onSyncVolumeLevel);
connect(emuThread, &EmuThread::windowEmuStart, dlg, &AudioSettingsDialog::onConsoleReset);
connect(dlg, &AudioSettingsDialog::updateAudioVolume, this, &MainWindow::onUpdateAudioVolume);
connect(dlg, &AudioSettingsDialog::updateAudioSettings, this, &MainWindow::onUpdateAudioSettings);
connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished);
}
void MainWindow::onOpenFirmwareSettings()
{
emuThread->emuPause();
FirmwareSettingsDialog* dlg = FirmwareSettingsDialog::openDlg(this);
connect(dlg, &FirmwareSettingsDialog::finished, this, &MainWindow::onFirmwareSettingsFinished);
}
void MainWindow::onFirmwareSettingsFinished(int res)
{
if (FirmwareSettingsDialog::needsReset)
onReset();
emuThread->emuUnpause();
}
void MainWindow::onOpenPathSettings()
{
emuThread->emuPause();
PathSettingsDialog* dlg = PathSettingsDialog::openDlg(this);
connect(dlg, &PathSettingsDialog::finished, this, &MainWindow::onPathSettingsFinished);
}
void MainWindow::onPathSettingsFinished(int res)
{
if (PathSettingsDialog::needsReset)
onReset();
emuThread->emuUnpause();
}
void MainWindow::onUpdateAudioVolume(int vol, int dsisync)
{
emuInstance->audioVolume = vol;
emuInstance->audioDSiVolumeSync = dsisync;
}
void MainWindow::onUpdateAudioSettings()
{
if (!emuThread->emuIsActive()) return;
assert(emuInstance->nds != nullptr);
int interp = globalCfg.GetInt("Audio.Interpolation");
emuInstance->nds->SPU.SetInterpolation(static_cast<AudioInterpolation>(interp));
int bitdepth = globalCfg.GetInt("Audio.BitDepth");
if (bitdepth == 0)
emuInstance->nds->SPU.SetDegrade10Bit(emuInstance->nds->ConsoleType == 0);
else
emuInstance->nds->SPU.SetDegrade10Bit(bitdepth == 1);
}
void MainWindow::onAudioSettingsFinished(int res)
{
//AudioInOut::UpdateSettings(*emuThread->NDS);
}
void MainWindow::onOpenMPSettings()
{
emuThread->emuPause();
MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this);
connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished);
}
void MainWindow::onMPSettingsFinished(int res)
{
emuInstance->mpAudioMode = globalCfg.GetInt("MP.AudioMode");
emuInstance->audioMute();
MPInterface::Get().SetRecvTimeout(globalCfg.GetInt("MP.RecvTimeout"));
emuThread->emuUnpause();
}
void MainWindow::onOpenWifiSettings()
{
emuThread->emuPause();
WifiSettingsDialog* dlg = WifiSettingsDialog::openDlg(this);
connect(dlg, &WifiSettingsDialog::finished, this, &MainWindow::onWifiSettingsFinished);
}
void MainWindow::onWifiSettingsFinished(int res)
{
if (WifiSettingsDialog::needsReset)
onReset();
emuThread->emuUnpause();
}
void MainWindow::onOpenInterfaceSettings()
{
emuThread->emuPause();
InterfaceSettingsDialog* dlg = InterfaceSettingsDialog::openDlg(this);
connect(dlg, &InterfaceSettingsDialog::finished, this, &MainWindow::onInterfaceSettingsFinished);
connect(dlg, &InterfaceSettingsDialog::updateInterfaceSettings, this, &MainWindow::onUpdateInterfaceSettings);
}
void MainWindow::onUpdateInterfaceSettings()
{
pauseOnLostFocus = globalCfg.GetBool("PauseLostFocus");
emuInstance->targetFPS = globalCfg.GetDouble("TargetFPS");
emuInstance->fastForwardFPS = globalCfg.GetDouble("FastForwardFPS");
emuInstance->slowmoFPS = globalCfg.GetDouble("SlowmoFPS");
panel->setMouseHide(globalCfg.GetBool("Mouse.Hide"),
globalCfg.GetInt("Mouse.HideSeconds")*1000);
}
void MainWindow::onInterfaceSettingsFinished(int res)
{
emuThread->emuUnpause();
}
void MainWindow::onChangeSavestateSRAMReloc(bool checked)
{
globalCfg.SetBool("Savestate.RelocSRAM", checked);
}
void MainWindow::onChangeScreenSize()
{
int factor = ((QAction*)sender())->data().toInt();
QSize diff = size() - panel->size();
resize(panel->screenGetMinSize(factor) + diff);
}
void MainWindow::onChangeScreenRotation(QAction* act)
{
int rot = act->data().toInt();
windowCfg.SetInt("ScreenRotation", rot);
emit screenLayoutChange();
}
void MainWindow::onChangeScreenGap(QAction* act)
{
int gap = act->data().toInt();
windowCfg.SetInt("ScreenGap", gap);
emit screenLayoutChange();
}
void MainWindow::onChangeScreenLayout(QAction* act)
{
int layout = act->data().toInt();
windowCfg.SetInt("ScreenLayout", layout);
emit screenLayoutChange();
}
void MainWindow::onChangeScreenSwap(bool checked)
{
windowCfg.SetBool("ScreenSwap", checked);
// Swap between top and bottom screen when displaying one screen.
int sizing = windowCfg.GetInt("ScreenSizing");
if (sizing == screenSizing_TopOnly)
{
// Bottom Screen.
sizing = screenSizing_BotOnly;
actScreenSizing[screenSizing_TopOnly]->setChecked(false);
actScreenSizing[sizing]->setChecked(true);
}
else if (sizing == screenSizing_BotOnly)
{
// Top Screen.
sizing = screenSizing_TopOnly;
actScreenSizing[screenSizing_BotOnly]->setChecked(false);
actScreenSizing[sizing]->setChecked(true);
}
windowCfg.SetInt("ScreenSizing", sizing);
emit screenLayoutChange();
}
void MainWindow::onChangeScreenSizing(QAction* act)
{
int sizing = act->data().toInt();
windowCfg.SetInt("ScreenSizing", sizing);
emit screenLayoutChange();
}
void MainWindow::onChangeScreenAspect(QAction* act)
{
int aspect = act->data().toInt();
QActionGroup* group = act->actionGroup();
if (group == grpScreenAspectTop)
{
windowCfg.SetInt("ScreenAspectTop", aspect);
}
else
{
windowCfg.SetInt("ScreenAspectBot", aspect);
}
emit screenLayoutChange();
}
void MainWindow::onChangeIntegerScaling(bool checked)
{
windowCfg.SetBool("IntegerScaling", checked);
emit screenLayoutChange();
}
void MainWindow::onOpenNewWindow()
{
emuInstance->createWindow();
}
void MainWindow::onChangeScreenFiltering(bool checked)
{
windowCfg.SetBool("ScreenFilter", checked);
//emit screenLayoutChange();
panel->setFilter(checked);
}
void MainWindow::onChangeShowOSD(bool checked)
{
showOSD = checked;
panel->osdSetEnabled(showOSD);
windowCfg.SetBool("ShowOSD", showOSD);
}
void MainWindow::onChangeLimitFramerate(bool checked)
{
emuInstance->doLimitFPS = checked;
globalCfg.SetBool("LimitFPS", emuInstance->doLimitFPS);
}
void MainWindow::onChangeAudioSync(bool checked)
{
emuInstance->doAudioSync = checked;
globalCfg.SetBool("AudioSync", emuInstance->doAudioSync);
}
void MainWindow::onTitleUpdate(QString title)
{
setWindowTitle(title);
}
void MainWindow::toggleFullscreen()
{
if (!isFullScreen())
{
showFullScreen();
if (hasMenu)
menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working
}
else
{
showNormal();
if (hasMenu)
{
int menuBarHeight = menuBar()->sizeHint().height();
menuBar()->setFixedHeight(menuBarHeight);
}
}
}
void MainWindow::onFullscreenToggled()
{
toggleFullscreen();
}
void MainWindow::onScreenEmphasisToggled()
{
int currentSizing = windowCfg.GetInt("ScreenSizing");
if (currentSizing == screenSizing_EmphTop)
{
currentSizing = screenSizing_EmphBot;
}
else if (currentSizing == screenSizing_EmphBot)
{
currentSizing = screenSizing_EmphTop;
}
windowCfg.SetInt("ScreenSizing", currentSizing);
emit screenLayoutChange();
}
void MainWindow::onEmuStart()
{
if (!hasMenu) return;
for (int i = 1; i < 9; i++)
{
actSaveState[i]->setEnabled(true);
actLoadState[i]->setEnabled(emuInstance->savestateExists(i));
}
actSaveState[0]->setEnabled(true);
actLoadState[0]->setEnabled(true);
actUndoStateLoad->setEnabled(false);
actPause->setEnabled(true);
actPause->setChecked(false);
actReset->setEnabled(true);
actStop->setEnabled(true);
actFrameStep->setEnabled(true);
actDateTime->setEnabled(false);
actPowerManagement->setEnabled(true);
actTitleManager->setEnabled(false);
}
void MainWindow::onEmuStop()
{
if (!hasMenu) return;
for (int i = 0; i < 9; i++)
{
actSaveState[i]->setEnabled(false);
actLoadState[i]->setEnabled(false);
}
actUndoStateLoad->setEnabled(false);
actPause->setEnabled(false);
actReset->setEnabled(false);
actStop->setEnabled(false);
actFrameStep->setEnabled(false);
actDateTime->setEnabled(true);
actPowerManagement->setEnabled(false);
actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty());
}
void MainWindow::onEmuPause(bool pause)
{
if (!hasMenu) return;
actPause->setChecked(pause);
}
void MainWindow::onEmuReset()
{
if (!hasMenu) return;
actUndoStateLoad->setEnabled(false);
}
void MainWindow::onUpdateVideoSettings(bool glchange)
{
if (!emuInstance) return;
// if we have a parent window: pass the message over to the parent
// the topmost parent takes care of updating all the windows
MainWindow* parentwin = (MainWindow*)parentWidget();
if (parentwin)
return parentwin->onUpdateVideoSettings(glchange);
bool hadOGL = hasOGL;
if (glchange)
{
emuThread->emuPause();
if (hadOGL) emuThread->deinitContext(windowID);
createScreenPanel();
}
emuThread->updateVideoSettings();
if (glchange)
{
if (hasOGL) emuThread->initContext(windowID);
}
// update any child windows we have
auto childwins = findChildren<MainWindow *>(nullptr, Qt::FindDirectChildrenOnly);
for (auto child: childwins)
{
// child windows may belong to a different instance
// in that case we need to signal their thread appropriately
auto thread = child->getEmuInstance()->getEmuThread();
if (glchange)
{
if (hadOGL) thread->deinitContext(child->windowID);
child->createScreenPanel();
}
if (child->getWindowID() == 0)
thread->updateVideoSettings();
if (glchange)
{
if (hasOGL) thread->initContext(child->windowID);
}
}
if (glchange)
{
emuThread->emuUnpause();
}
}