pcsx2/pcsx2-qt/MainWindow.cpp

2754 lines
90 KiB
C++
Raw Normal View History

2021-12-13 12:12:54 +00:00
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 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 PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include <QtCore/QDateTime>
#include <QtGui/QCloseEvent>
2021-12-13 12:12:54 +00:00
#include <QtWidgets/QFileDialog>
2022-06-28 13:34:45 +00:00
#include <QtWidgets/QInputDialog>
2021-12-13 12:12:54 +00:00
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QProgressBar>
#include <QtWidgets/QStyle>
#include <QtWidgets/QStyleFactory>
#include "common/Assertions.h"
2022-05-25 22:32:10 +00:00
#include "common/CocoaTools.h"
2021-12-13 12:12:54 +00:00
#include "common/FileSystem.h"
2022-07-18 22:13:02 +00:00
#include "pcsx2/CDVD/CDVDcommon.h"
2022-06-28 13:34:45 +00:00
#include "pcsx2/CDVD/CDVDdiscReader.h"
2021-12-13 12:12:54 +00:00
#include "pcsx2/Frontend/GameList.h"
#include "pcsx2/Frontend/LogSink.h"
2022-03-25 10:14:23 +00:00
#include "pcsx2/GSDumpReplayer.h"
2021-12-13 12:12:54 +00:00
#include "pcsx2/HostDisplay.h"
#include "pcsx2/HostSettings.h"
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/Recording/InputRecording.h"
2021-12-13 12:12:54 +00:00
#include "AboutDialog.h"
2022-04-04 11:27:23 +00:00
#include "AutoUpdaterDialog.h"
2022-09-03 11:29:02 +00:00
#include "CoverDownloadDialog.h"
2021-12-13 12:12:54 +00:00
#include "DisplayWidget.h"
#include "GameList/GameListRefreshThread.h"
#include "GameList/GameListWidget.h"
#include "MainWindow.h"
#include "QtHost.h"
#include "QtUtils.h"
#include "Settings/ControllerSettingsDialog.h"
#include "Settings/GameListSettingsWidget.h"
#include "Settings/InterfaceSettingsWidget.h"
2022-03-04 09:46:47 +00:00
#include "SettingWidgetBinder.h"
#include "svnrev.h"
#include "Tools/InputRecording/NewInputRecordingDlg.h"
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#include <Dbt.h>
#endif
#ifdef ENABLE_RAINTEGRATION
#include "pcsx2/Frontend/Achievements.h"
#endif
2021-12-13 12:12:54 +00:00
2022-06-28 13:34:45 +00:00
static constexpr char OPEN_FILE_FILTER[] =
2022-10-05 22:58:17 +00:00
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.gz *.elf *.irx *.gs *.gs.xz *.gs.zst *.dump);;"
"Single-Track Raw Images (*.bin *.iso);;"
"Cue Sheets (*.cue);;"
"MAME CHD Images (*.chd);;"
"CSO Images (*.cso);;"
"GZ Images (*.gz);;"
"ELF Executables (*.elf);;"
"IRX Executables (*.irx);;"
"GS Dumps (*.gs *.gs.xz *.gs.zst);;"
"Block Dumps (*.dump)");
2021-12-13 12:12:54 +00:00
2022-06-28 13:34:45 +00:00
static constexpr char DISC_IMAGE_FILTER[] =
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.gz *.dump);;"
"Single-Track Raw Images (*.bin *.iso);;"
"Cue Sheets (*.cue);;"
"MAME CHD Images (*.chd);;"
"CSO Images (*.cso);;"
"GZ Images (*.gz);;"
"Block Dumps (*.dump)");
#ifdef __APPLE__
const char* MainWindow::DEFAULT_THEME_NAME = "";
#else
2021-12-13 12:12:54 +00:00
const char* MainWindow::DEFAULT_THEME_NAME = "darkfusion";
#endif
2021-12-13 12:12:54 +00:00
MainWindow* g_main_window = nullptr;
static QString s_unthemed_style_name;
static QPalette s_unthemed_palette;
static bool s_unthemed_style_name_set;
2021-12-13 12:12:54 +00:00
2022-07-09 06:59:13 +00:00
#if defined(_WIN32) || defined(__APPLE__)
static const bool s_use_central_widget = false;
#else
// Qt Wayland is broken. Any sort of stacked widget usage fails to update,
// leading to broken window resizes, no display rendering, etc. So, we mess
// with the central widget instead. Which we can't do on xorg, because it
// breaks window resizing there...
static bool s_use_central_widget = false;
#endif
// UI thread VM validity.
static bool s_vm_valid = false;
static bool s_vm_paused = false;
MainWindow::MainWindow()
2021-12-13 12:12:54 +00:00
{
pxAssert(!g_main_window);
g_main_window = this;
2022-07-09 06:59:13 +00:00
#if !defined(_WIN32) && !defined(__APPLE__)
s_use_central_widget = DisplayContainer::isRunningOnWayland();
#endif
2021-12-13 12:12:54 +00:00
}
MainWindow::~MainWindow()
{
// we compare here, since recreate destroys the window later
if (g_main_window == this)
g_main_window = nullptr;
#ifdef _WIN32
unregisterForDeviceNotifications();
#endif
2022-05-25 22:32:10 +00:00
#ifdef __APPLE__
CocoaTools::RemoveThemeChangeHandler(this);
#endif
2021-12-13 12:12:54 +00:00
}
void MainWindow::initialize()
{
2022-05-25 22:32:10 +00:00
#ifdef __APPLE__
2022-06-28 13:34:45 +00:00
CocoaTools::AddThemeChangeHandler(this, [](void* ctx) {
2022-05-25 22:32:10 +00:00
// This handler is called *before* the style change has propagated far enough for Qt to see it
// Use RunOnUIThread to delay until it has
QtHost::RunOnUIThread([ctx = static_cast<MainWindow*>(ctx)] {
ctx->updateTheme(); // Qt won't notice the style change without us touching the palette in some way
2022-05-25 22:32:10 +00:00
});
});
#endif
2021-12-13 12:12:54 +00:00
m_ui.setupUi(this);
setupAdditionalUi();
connectSignals();
2022-05-24 08:04:26 +00:00
connectVMThreadSignals(g_emu_thread);
2021-12-13 12:12:54 +00:00
restoreStateFromConfig();
switchToGameListView();
updateWindowTitle();
updateSaveStateMenus(QString(), QString(), 0);
#ifdef _WIN32
registerForDeviceNotifications();
#endif
2021-12-13 12:12:54 +00:00
}
// TODO: Figure out how to set this in the .ui file
/// Marks the icons for all actions in the given menu as mask icons
/// This means macOS's menubar renderer will ignore color values and use only the alpha in the image.
/// The color value will instead be taken from the system theme.
/// Since the menubar follows the OS's dark/light mode and not our current theme's, this prevents problems where a theme mismatch puts white icons in light mode or dark icons in dark mode.
static void makeIconsMasks(QWidget* menu)
{
for (QAction* action : menu->actions())
{
if (!action->icon().isNull())
{
QIcon icon = action->icon();
icon.setIsMask(true);
action->setIcon(icon);
}
if (action->menu())
makeIconsMasks(action->menu());
}
}
2022-07-09 06:59:13 +00:00
QWidget* MainWindow::getContentParent()
{
return s_use_central_widget ? static_cast<QWidget*>(this) : static_cast<QWidget*>(m_ui.mainContainer);
}
2021-12-13 12:12:54 +00:00
void MainWindow::setupAdditionalUi()
{
const bool show_advanced_settings = QtHost::ShouldShowAdvancedSettings();
2022-06-04 05:53:31 +00:00
setWindowIcon(QIcon(QStringLiteral("%1/icons/AppIconLarge.png").arg(QtHost::GetResourcesBasePath())));
makeIconsMasks(menuBar());
m_ui.menuDebug->menuAction()->setVisible(show_advanced_settings);
const bool toolbar_visible = Host::GetBaseBoolSettingValue("UI", "ShowToolbar", false);
2021-12-13 12:12:54 +00:00
m_ui.actionViewToolbar->setChecked(toolbar_visible);
m_ui.toolBar->setVisible(toolbar_visible);
const bool toolbars_locked = Host::GetBaseBoolSettingValue("UI", "LockToolbar", false);
2021-12-13 12:12:54 +00:00
m_ui.actionViewLockToolbar->setChecked(toolbars_locked);
m_ui.toolBar->setMovable(!toolbars_locked);
m_ui.toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
const bool status_bar_visible = Host::GetBaseBoolSettingValue("UI", "ShowStatusBar", true);
2021-12-13 12:12:54 +00:00
m_ui.actionViewStatusBar->setChecked(status_bar_visible);
m_ui.statusBar->setVisible(status_bar_visible);
2022-07-09 06:59:13 +00:00
m_game_list_widget = new GameListWidget(getContentParent());
2021-12-13 12:12:54 +00:00
m_game_list_widget->initialize();
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
2022-07-09 06:59:13 +00:00
if (s_use_central_widget)
{
m_ui.mainContainer = nullptr; // setCentralWidget() will delete this
setCentralWidget(m_game_list_widget);
}
else
{
m_ui.mainContainer->addWidget(m_game_list_widget);
}
2021-12-13 12:12:54 +00:00
m_status_progress_widget = new QProgressBar(m_ui.statusBar);
m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
m_status_progress_widget->setFixedSize(140, 16);
m_status_progress_widget->setMinimum(0);
m_status_progress_widget->setMaximum(100);
2021-12-13 12:12:54 +00:00
m_status_progress_widget->hide();
2022-07-03 13:30:03 +00:00
m_status_verbose_widget = new QLabel(m_ui.statusBar);
m_status_verbose_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_status_verbose_widget->setFixedHeight(16);
m_status_verbose_widget->hide();
m_status_renderer_widget = new QLabel(m_ui.statusBar);
m_status_renderer_widget->setFixedHeight(16);
m_status_renderer_widget->setFixedSize(65, 16);
m_status_renderer_widget->hide();
m_status_resolution_widget = new QLabel(m_ui.statusBar);
m_status_resolution_widget->setFixedHeight(16);
m_status_resolution_widget->setFixedSize(70, 16);
m_status_resolution_widget->hide();
m_status_fps_widget = new QLabel(m_ui.statusBar);
2022-07-03 13:30:03 +00:00
m_status_fps_widget->setFixedSize(85, 16);
m_status_fps_widget->hide();
2022-07-03 13:30:03 +00:00
m_status_vps_widget = new QLabel(m_ui.statusBar);
m_status_vps_widget->setFixedSize(125, 16);
m_status_vps_widget->hide();
2021-12-13 12:12:54 +00:00
for (u32 scale = 0; scale <= 10; scale++)
{
2022-02-15 14:29:18 +00:00
QAction* action = m_ui.menuWindowSize->addAction((scale == 0) ? tr("Internal Resolution") : tr("%1x Scale").arg(scale));
2021-12-13 12:12:54 +00:00
connect(action, &QAction::triggered, [scale]() { g_emu_thread->requestDisplaySize(static_cast<float>(scale)); });
}
updateEmulationActions(false, false);
updateDisplayRelatedActions(false, false, false);
#ifdef ENABLE_RAINTEGRATION
if (Achievements::IsUsingRAIntegration())
{
QMenu* raMenu = new QMenu(QStringLiteral("RAIntegration"), m_ui.menu_Tools);
connect(raMenu, &QMenu::aboutToShow, this, [this, raMenu]() {
raMenu->clear();
const auto items = Achievements::RAIntegration::GetMenuItems();
for (const auto& [id, title, checked] : items)
{
if (id == 0)
{
raMenu->addSeparator();
continue;
}
QAction* raAction = raMenu->addAction(QString::fromUtf8(title));
if (checked)
{
raAction->setCheckable(true);
raAction->setChecked(checked);
}
connect(raAction, &QAction::triggered, this, [id = id]() {
Host::RunOnCPUThread([id]() { Achievements::RAIntegration::ActivateMenuItem(id); }, false);
});
}
});
m_ui.menu_Tools->insertMenu(m_ui.menuInput_Recording->menuAction(), raMenu);
}
#endif
2021-12-13 12:12:54 +00:00
}
void MainWindow::connectSignals()
{
connect(m_ui.actionStartFile, &QAction::triggered, this, &MainWindow::onStartFileActionTriggered);
2022-06-28 13:34:45 +00:00
connect(m_ui.actionStartDisc, &QAction::triggered, this, &MainWindow::onStartDiscActionTriggered);
2021-12-13 12:12:54 +00:00
connect(m_ui.actionStartBios, &QAction::triggered, this, &MainWindow::onStartBIOSActionTriggered);
connect(m_ui.actionChangeDisc, &QAction::triggered, [this] { m_ui.menuChangeDisc->exec(QCursor::pos()); });
connect(m_ui.actionChangeDiscFromFile, &QAction::triggered, this, &MainWindow::onChangeDiscFromFileActionTriggered);
2022-02-15 14:29:18 +00:00
connect(m_ui.actionChangeDiscFromDevice, &QAction::triggered, this, &MainWindow::onChangeDiscFromDeviceActionTriggered);
connect(m_ui.actionChangeDiscFromGameList, &QAction::triggered, this, &MainWindow::onChangeDiscFromGameListActionTriggered);
2022-06-28 14:00:51 +00:00
connect(m_ui.actionRemoveDisc, &QAction::triggered, this, &MainWindow::onRemoveDiscActionTriggered);
2021-12-13 12:12:54 +00:00
connect(m_ui.menuChangeDisc, &QMenu::aboutToShow, this, &MainWindow::onChangeDiscMenuAboutToShow);
connect(m_ui.menuChangeDisc, &QMenu::aboutToHide, this, &MainWindow::onChangeDiscMenuAboutToHide);
connect(m_ui.actionPowerOff, &QAction::triggered, this, [this]() { requestShutdown(true, true, EmuConfig.SaveStateOnShutdown); });
connect(m_ui.actionPowerOffWithoutSaving, &QAction::triggered, this, [this]() { requestShutdown(false, false, false); });
2021-12-13 12:12:54 +00:00
connect(m_ui.actionLoadState, &QAction::triggered, this, [this]() { m_ui.menuLoadState->exec(QCursor::pos()); });
connect(m_ui.actionSaveState, &QAction::triggered, this, [this]() { m_ui.menuSaveState->exec(QCursor::pos()); });
connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::close);
connect(m_ui.actionScreenshot, &QAction::triggered, this, &MainWindow::onScreenshotActionTriggered);
2021-12-13 12:12:54 +00:00
connect(m_ui.menuLoadState, &QMenu::aboutToShow, this, &MainWindow::onLoadStateMenuAboutToShow);
connect(m_ui.menuSaveState, &QMenu::aboutToShow, this, &MainWindow::onSaveStateMenuAboutToShow);
2022-02-15 14:29:18 +00:00
connect(m_ui.actionSettings, &QAction::triggered, [this]() { doSettings(); });
connect(m_ui.actionInterfaceSettings, &QAction::triggered, [this]() { doSettings("Interface"); });
connect(m_ui.actionGameListSettings, &QAction::triggered, [this]() { doSettings("Game List"); });
connect(m_ui.actionEmulationSettings, &QAction::triggered, [this]() { doSettings("Emulation"); });
connect(m_ui.actionBIOSSettings, &QAction::triggered, [this]() { doSettings("BIOS"); });
connect(m_ui.actionGraphicsSettings, &QAction::triggered, [this]() { doSettings("Graphics"); });
connect(m_ui.actionAudioSettings, &QAction::triggered, [this]() { doSettings("Audio"); });
2022-03-25 09:24:41 +00:00
connect(m_ui.actionMemoryCardSettings, &QAction::triggered, [this]() { doSettings("Memory Cards"); });
2022-03-13 17:58:39 +00:00
connect(m_ui.actionDEV9Settings, &QAction::triggered, [this]() { doSettings("Network & HDD"); });
2022-06-18 10:20:24 +00:00
connect(m_ui.actionFolderSettings, &QAction::triggered, [this]() { doSettings("Folders"); });
connect(m_ui.actionAchievementSettings, &QAction::triggered, [this]() { doSettings("Achievements"); });
2022-02-15 14:29:18 +00:00
connect(
m_ui.actionControllerSettings, &QAction::triggered, [this]() { doControllerSettings(ControllerSettingsDialog::Category::GlobalSettings); });
connect(m_ui.actionHotkeySettings, &QAction::triggered, [this]() { doControllerSettings(ControllerSettingsDialog::Category::HotkeySettings); });
connect(
m_ui.actionAddGameDirectory, &QAction::triggered, [this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); });
2021-12-13 12:12:54 +00:00
connect(m_ui.actionScanForNewGames, &QAction::triggered, [this]() { refreshGameList(false); });
connect(m_ui.actionRescanAllGames, &QAction::triggered, [this]() { refreshGameList(true); });
connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled);
connect(m_ui.actionViewLockToolbar, &QAction::toggled, this, &MainWindow::onViewLockToolbarActionToggled);
connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled);
connect(m_ui.actionViewGameList, &QAction::triggered, this, &MainWindow::onViewGameListActionTriggered);
connect(m_ui.actionViewGameGrid, &QAction::triggered, this, &MainWindow::onViewGameGridActionTriggered);
connect(m_ui.actionViewSystemDisplay, &QAction::triggered, this, &MainWindow::onViewSystemDisplayTriggered);
connect(m_ui.actionViewGameProperties, &QAction::triggered, this, &MainWindow::onViewGamePropertiesActionTriggered);
connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered);
connect(m_ui.actionSupportForums, &QAction::triggered, this, &MainWindow::onSupportForumsActionTriggered);
connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered);
connect(m_ui.actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt);
connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
2022-09-03 11:29:02 +00:00
connect(m_ui.actionCoverDownloader, &QAction::triggered, this, &MainWindow::onToolsCoverDownloaderTriggered);
2021-12-13 12:12:54 +00:00
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
if (isShowingGameList())
m_game_list_widget->gridZoomIn();
});
connect(m_ui.actionGridViewZoomOut, &QAction::triggered, m_game_list_widget, [this]() {
if (isShowingGameList())
m_game_list_widget->gridZoomOut();
});
2022-02-15 14:29:18 +00:00
connect(m_ui.actionGridViewRefreshCovers, &QAction::triggered, m_game_list_widget, &GameListWidget::refreshGridCovers);
2022-07-09 08:52:33 +00:00
connect(m_game_list_widget, &GameListWidget::layoutChange, this, [this]() {
QSignalBlocker sb(m_ui.actionGridViewShowTitles);
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
});
2021-12-13 12:12:54 +00:00
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionViewStatusBarVerbose, "UI", "VerboseStatusBar", false);
2022-03-04 09:46:47 +00:00
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableSystemConsole, "Logging", "EnableSystemConsole", false);
#ifndef PCSX2_DEVBUILD
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableVerboseLogging, "Logging", "EnableVerbose", false);
#else
// Dev builds always have verbose logging.
m_ui.actionEnableVerboseLogging->setChecked(true);
m_ui.actionEnableVerboseLogging->setEnabled(false);
#endif
2022-03-19 14:05:46 +00:00
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableEEConsoleLogging, "Logging", "EnableEEConsole", true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableIOPConsoleLogging, "Logging", "EnableIOPConsole", true);
2022-05-24 12:02:05 +00:00
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableFileLogging, "Logging", "EnableFileLogging", false);
2022-05-24 14:15:41 +00:00
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableLogTimestamps, "Logging", "EnableTimestamps", true);
2022-05-24 12:02:05 +00:00
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableCDVDVerboseReads, "EmuCore", "CdvdVerboseReads", false);
2022-05-24 12:22:15 +00:00
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionSaveBlockDump, "EmuCore", "CdvdDumpBlocks", false);
m_ui.actionShowAdvancedSettings->setChecked(QtHost::ShouldShowAdvancedSettings());
2022-05-24 12:22:15 +00:00
connect(m_ui.actionSaveBlockDump, &QAction::toggled, this, &MainWindow::onBlockDumpActionToggled);
connect(m_ui.actionShowAdvancedSettings, &QAction::toggled, this, &MainWindow::onShowAdvancedSettingsToggled);
connect(m_ui.actionSaveGSDump, &QAction::triggered, this, &MainWindow::onSaveGSDumpActionTriggered);
// Input Recording
connect(m_ui.actionInputRecNew, &QAction::triggered, this, &MainWindow::onInputRecNewActionTriggered);
connect(m_ui.actionInputRecPlay, &QAction::triggered, this, &MainWindow::onInputRecPlayActionTriggered);
connect(m_ui.actionInputRecStop, &QAction::triggered, this, &MainWindow::onInputRecStopActionTriggered);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionInputRecConsoleLogs, "Logging", "EnableInputRecordingLogs", false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionInputRecControllerLogs, "Logging", "EnableControllerLogs", false);
2021-12-13 12:12:54 +00:00
// These need to be queued connections to stop crashing due to menus opening/closing and switching focus.
connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress);
connect(m_game_list_widget, &GameListWidget::refreshComplete, this, &MainWindow::onGameListRefreshComplete);
2022-02-15 14:29:18 +00:00
connect(m_game_list_widget, &GameListWidget::selectionChanged, this, &MainWindow::onGameListSelectionChanged, Qt::QueuedConnection);
connect(m_game_list_widget, &GameListWidget::entryActivated, this, &MainWindow::onGameListEntryActivated, Qt::QueuedConnection);
connect(
m_game_list_widget, &GameListWidget::entryContextMenuRequested, this, &MainWindow::onGameListEntryContextMenuRequested, Qt::QueuedConnection);
connect(m_game_list_widget, &GameListWidget::addGameDirectoryRequested, this,
[this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); });
2021-12-13 12:12:54 +00:00
}
void MainWindow::connectVMThreadSignals(EmuThread* thread)
{
connect(m_ui.actionStartFullscreenUI, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
connect(m_ui.actionStartFullscreenUI2, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
connect(thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection);
2021-12-13 12:12:54 +00:00
connect(thread, &EmuThread::onCreateDisplayRequested, this, &MainWindow::createDisplay, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplay, Qt::BlockingQueuedConnection);
2022-02-15 14:29:18 +00:00
connect(thread, &EmuThread::onDestroyDisplayRequested, this, &MainWindow::destroyDisplay, Qt::BlockingQueuedConnection);
2021-12-13 12:12:54 +00:00
connect(thread, &EmuThread::onResizeDisplayRequested, this, &MainWindow::displayResizeRequested);
connect(thread, &EmuThread::onVMStarting, this, &MainWindow::onVMStarting);
connect(thread, &EmuThread::onVMStarted, this, &MainWindow::onVMStarted);
connect(thread, &EmuThread::onVMPaused, this, &MainWindow::onVMPaused);
connect(thread, &EmuThread::onVMResumed, this, &MainWindow::onVMResumed);
connect(thread, &EmuThread::onVMStopped, this, &MainWindow::onVMStopped);
connect(thread, &EmuThread::onGameChanged, this, &MainWindow::onGameChanged);
connect(m_ui.actionReset, &QAction::triggered, thread, &EmuThread::resetVM);
connect(m_ui.actionPause, &QAction::toggled, thread, &EmuThread::setVMPaused);
connect(m_ui.actionFullscreen, &QAction::triggered, thread, &EmuThread::toggleFullscreen);
connect(m_ui.actionToggleSoftwareRendering, &QAction::triggered, thread, &EmuThread::toggleSoftwareRendering);
connect(m_ui.actionReloadPatches, &QAction::triggered, thread, &EmuThread::reloadPatches);
static constexpr GSRendererType renderers[] = {
#ifdef _WIN32
2022-03-19 12:19:16 +00:00
GSRendererType::DX11, GSRendererType::DX12,
2021-12-13 12:12:54 +00:00
#endif
GSRendererType::OGL, GSRendererType::VK, GSRendererType::SW, GSRendererType::Null};
for (GSRendererType renderer : renderers)
{
2022-02-15 14:29:18 +00:00
connect(m_ui.menuDebugSwitchRenderer->addAction(QString::fromUtf8(Pcsx2Config::GSOptions::GetRendererName(renderer))), &QAction::triggered,
[renderer] { g_emu_thread->switchRenderer(renderer); });
2021-12-13 12:12:54 +00:00
}
}
void MainWindow::recreate()
{
if (s_vm_valid)
requestShutdown(false, true, EmuConfig.SaveStateOnShutdown);
2021-12-13 12:12:54 +00:00
// We need to close input sources, because e.g. DInput uses our window handle.
g_emu_thread->closeInputSources();
2021-12-13 12:12:54 +00:00
close();
g_main_window = nullptr;
MainWindow* new_main_window = new MainWindow();
2021-12-13 12:12:54 +00:00
new_main_window->initialize();
new_main_window->refreshGameList(false);
new_main_window->show();
deleteLater();
// Reload the sources we just closed.
g_emu_thread->reloadInputSources();
2021-12-13 12:12:54 +00:00
}
2022-09-07 07:44:10 +00:00
void MainWindow::recreateSettings()
{
QString current_category;
if (m_settings_dialog)
{
const bool was_visible = m_settings_dialog->isVisible();
2022-09-07 07:44:10 +00:00
current_category = m_settings_dialog->getCategory();
m_settings_dialog->hide();
m_settings_dialog->deleteLater();
m_settings_dialog = nullptr;
if (!was_visible)
return;
2022-09-07 07:44:10 +00:00
}
doSettings(current_category.toUtf8().constData());
}
void MainWindow::resetSettings(bool ui)
{
Host::RequestResetSettings(false, true, false, false, ui);
if (ui)
{
// UI reset includes theme (and eventually language).
// Just updating the theme here, when there's no change, causes Qt to get very confused..
// So, we'll just tear down everything and recreate. We'll need to do that for language
// resets eventaully anyway.
recreate();
}
// g_main_window here for recreate() case above.
g_main_window->recreateSettings();
}
void MainWindow::updateApplicationTheme()
{
if (!s_unthemed_style_name_set)
{
s_unthemed_style_name_set = true;
s_unthemed_style_name = QApplication::style()->objectName();
s_unthemed_palette = QApplication::style()->standardPalette();
}
setStyleFromSettings();
setIconThemeFromStyle();
}
2021-12-13 12:12:54 +00:00
void MainWindow::setStyleFromSettings()
{
const std::string theme(Host::GetBaseStringSettingValue("UI", "Theme", DEFAULT_THEME_NAME));
2021-12-13 12:12:54 +00:00
if (theme == "fusion")
{
qApp->setPalette(s_unthemed_palette);
2021-12-13 12:12:54 +00:00
qApp->setStyleSheet(QString());
qApp->setStyle(QStyleFactory::create("Fusion"));
}
else if (theme == "UntouchedLagoon")
{
// Custom pallete by RedDevilus, Tame (Light/Washed out) Green as main color and Grayish Blue as complimentary.
// Alternative white theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor black(25, 25, 25);
const QColor teal(0, 128, 128);
const QColor tameTeal(160, 190, 185);
const QColor grayBlue(160, 180, 190);
QPalette standardPalette;
standardPalette.setColor(QPalette::Window, tameTeal);
standardPalette.setColor(QPalette::WindowText, black);
standardPalette.setColor(QPalette::Base, grayBlue);
standardPalette.setColor(QPalette::AlternateBase, tameTeal);
standardPalette.setColor(QPalette::ToolTipBase, tameTeal);
standardPalette.setColor(QPalette::ToolTipText, grayBlue);
standardPalette.setColor(QPalette::Text, black);
standardPalette.setColor(QPalette::Button, tameTeal);
standardPalette.setColor(QPalette::ButtonText, Qt::white);
standardPalette.setColor(QPalette::Link, black);
standardPalette.setColor(QPalette::Highlight, teal);
standardPalette.setColor(QPalette::HighlightedText, Qt::white);
standardPalette.setColor(QPalette::Active, QPalette::Button, tameTeal.darker());
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, Qt::white);
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, Qt::white);
standardPalette.setColor(QPalette::Disabled, QPalette::Text, Qt::white);
standardPalette.setColor(QPalette::Disabled, QPalette::Light, tameTeal);
qApp->setPalette(standardPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "BabyPastel")
{
// Custom pallete by RedDevilus, Blue as main color and blue as complimentary.
// Alternative light theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor gray(150, 150, 150);
const QColor black(25, 25, 25);
const QColor pink(255, 174, 201);
const QColor brightPink(255, 230, 255);
const QColor congoPink(255, 127, 121);
const QColor blue(221, 225, 239);
QPalette standardPalette;
standardPalette.setColor(QPalette::Window, pink);
standardPalette.setColor(QPalette::WindowText, black);
standardPalette.setColor(QPalette::Base, brightPink);
standardPalette.setColor(QPalette::AlternateBase, blue);
standardPalette.setColor(QPalette::ToolTipBase, pink);
standardPalette.setColor(QPalette::ToolTipText, brightPink);
standardPalette.setColor(QPalette::Text, black);
standardPalette.setColor(QPalette::Button, pink);
standardPalette.setColor(QPalette::ButtonText, black);
standardPalette.setColor(QPalette::Link, black);
standardPalette.setColor(QPalette::Highlight, congoPink);
standardPalette.setColor(QPalette::HighlightedText, black);
standardPalette.setColor(QPalette::Active, QPalette::Button, pink);
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, congoPink);
standardPalette.setColor(QPalette::Disabled, QPalette::Text, blue);
standardPalette.setColor(QPalette::Disabled, QPalette::Light, gray);
qApp->setPalette(standardPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "PCSX2Blue")
{
// Custom pallete by RedDevilus, White as main color and Blue as complimentary.
// Alternative light theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor black(25, 25, 25);
const QColor darkBlue(73, 97, 177);
const QColor blue(106, 156, 255);
const QColor lightBlue(130, 155, 241);
QPalette standardPalette;
standardPalette.setColor(QPalette::Window, lightBlue);
standardPalette.setColor(QPalette::WindowText, black);
standardPalette.setColor(QPalette::Base, darkBlue);
standardPalette.setColor(QPalette::AlternateBase, lightBlue);
standardPalette.setColor(QPalette::ToolTipBase, lightBlue);
standardPalette.setColor(QPalette::ToolTipText, Qt::white);
standardPalette.setColor(QPalette::Text, Qt::white);
standardPalette.setColor(QPalette::Button, blue);
standardPalette.setColor(QPalette::ButtonText, Qt::white);
standardPalette.setColor(QPalette::Link, darkBlue);
standardPalette.setColor(QPalette::Highlight, Qt::white);
standardPalette.setColor(QPalette::HighlightedText, black);
standardPalette.setColor(QPalette::Active, QPalette::Button, blue.darker());
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, darkBlue);
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, darkBlue);
standardPalette.setColor(QPalette::Disabled, QPalette::Text, black);
standardPalette.setColor(QPalette::Disabled, QPalette::Light, darkBlue);
qApp->setPalette(standardPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
2021-12-13 12:12:54 +00:00
else if (theme == "darkfusion")
{
// adapted from https://gist.github.com/QuantumCD/6245215
qApp->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);
2022-07-09 08:52:33 +00:00
darkPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker());
2021-12-13 12:12:54 +00:00
darkPalette.setColor(QPalette::Active, QPalette::Button, gray.darker());
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);
qApp->setPalette(darkPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "darkfusionblue")
{
// adapted from https://gist.github.com/QuantumCD/6245215
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor darkGray(53, 53, 53);
const QColor gray(128, 128, 128);
const QColor black(25, 25, 25);
const QColor blue(198, 238, 255);
const QColor blue2(0, 88, 208);
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, blue2);
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, blue2);
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
2022-07-09 08:52:33 +00:00
darkPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker());
2021-12-13 12:12:54 +00:00
darkPalette.setColor(QPalette::Active, QPalette::Button, gray.darker());
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);
qApp->setPalette(darkPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "ScarletDevilRed")
{
// Custom pallete by RedDevilus, Red as main color and Purple as complimentary.
// Alternative dark theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor darkRed(80, 45, 69);
const QColor purplishRed(120, 45, 69);
const QColor brightRed(200, 45, 69);
QPalette darkPalette;
darkPalette.setColor(QPalette::Window, darkRed);
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Base, purplishRed);
darkPalette.setColor(QPalette::AlternateBase, darkRed);
darkPalette.setColor(QPalette::ToolTipBase, darkRed);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Button, darkRed);
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Link, brightRed);
darkPalette.setColor(QPalette::Highlight, brightRed);
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Active, QPalette::Button, purplishRed.darker());
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, brightRed);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, brightRed);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, brightRed);
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkRed);
qApp->setPalette(darkPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "Ruby")
{
// Custom pallete by Daisouji, Black as main color andd Red as complimentary.
// Alternative dark theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor gray(128, 128, 128);
const QColor slate(18, 18, 18);
const QColor rubyish(172, 21, 31);
QPalette darkPalette;
darkPalette.setColor(QPalette::Window, slate);
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Base, slate.lighter());
darkPalette.setColor(QPalette::AlternateBase, slate.lighter());
darkPalette.setColor(QPalette::ToolTipBase, slate);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Button, slate);
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Link, Qt::white);
darkPalette.setColor(QPalette::Highlight, rubyish);
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Active, QPalette::Button, slate.lighter());
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, slate.lighter());
qApp->setPalette(darkPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
}
else if (theme == "Custom")
{
//Additional Theme option than loads .qss from main PCSX2 Directory
qApp->setStyle(QStyleFactory::create("Fusion"));
QString sheet_content;
QFile sheets(QString::fromStdString(Path::Combine(EmuFolders::DataRoot, "custom.qss")));
if (sheets.open(QFile::ReadOnly))
{
QString sheet_content = QString::fromUtf8(sheets.readAll().data());
qApp->setStyleSheet(sheet_content);
}
else
{
qApp->setStyle(QStyleFactory::create("Fusion"));
}
}
else
2021-12-13 12:12:54 +00:00
{
qApp->setPalette(s_unthemed_palette);
2021-12-13 12:12:54 +00:00
qApp->setStyleSheet(QString());
qApp->setStyle(s_unthemed_style_name);
2021-12-13 12:12:54 +00:00
}
}
void MainWindow::setIconThemeFromStyle()
2021-12-13 12:12:54 +00:00
{
QPalette palette = qApp->palette();
bool dark = palette.windowText().color().value() > palette.window().color().value();
QIcon::setThemeName(dark ? QStringLiteral("white") : QStringLiteral("black"));
2021-12-13 12:12:54 +00:00
}
void MainWindow::onScreenshotActionTriggered()
{
g_emu_thread->queueSnapshot(0);
}
void MainWindow::onSaveGSDumpActionTriggered()
{
g_emu_thread->queueSnapshot(1);
}
2022-05-24 12:22:15 +00:00
void MainWindow::onBlockDumpActionToggled(bool checked)
{
if (!checked)
return;
std::string old_directory(Host::GetBaseStringSettingValue("EmuCore", "BlockDumpSaveDirectory", ""));
if (old_directory.empty())
old_directory = FileSystem::GetWorkingDirectory();
// prompt for a location to save
const QString new_dir(
QFileDialog::getExistingDirectory(this, tr("Select location to save block dump:"),
QString::fromStdString(old_directory)));
if (new_dir.isEmpty())
{
// disable it again
m_ui.actionSaveBlockDump->setChecked(false);
return;
}
2022-09-07 07:44:10 +00:00
Host::SetBaseStringSettingValue("EmuCore", "BlockDumpSaveDirectory", new_dir.toUtf8().constData());
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
2022-05-24 12:22:15 +00:00
}
void MainWindow::onShowAdvancedSettingsToggled(bool checked)
{
if (checked && !Host::GetBaseBoolSettingValue("UI", "AdvancedSettingsWarningShown", false))
{
QCheckBox* cb = new QCheckBox(tr("Do not show again"));
QMessageBox mb(this);
mb.setWindowTitle(tr("Show Advanced Settings"));
mb.setText(
tr("Changing advanced settings can have unpredictable effects on games, including graphical glitches, lock-ups, and even corrupted save files. "
"We do not recommend changing advanced settings unless you know what you are doing, and the implications of changing each setting.\n\n"
"The PCSX2 team will not provide any support for configurations that modify these settings, you are on your own.\n\n"
"Are you sure you want to continue?"));
mb.setIcon(QMessageBox::Warning);
mb.addButton(QMessageBox::Yes);
mb.addButton(QMessageBox::No);
mb.setDefaultButton(QMessageBox::No);
mb.setCheckBox(cb);
if (mb.exec() == QMessageBox::No)
{
QSignalBlocker sb(m_ui.actionShowAdvancedSettings);
m_ui.actionShowAdvancedSettings->setChecked(false);
return;
}
if (cb->isChecked())
{
Host::SetBaseBoolSettingValue("UI", "AdvancedSettingsWarningShown", true);
Host::CommitBaseSettingChanges();
}
}
Host::SetBaseBoolSettingValue("UI", "ShowAdvancedSettings", checked);
Host::CommitBaseSettingChanges();
m_ui.menuDebug->menuAction()->setVisible(checked);
// just recreate the entire settings window, it's easier.
if (m_settings_dialog)
recreateSettings();
}
2021-12-13 12:12:54 +00:00
void MainWindow::saveStateToConfig()
{
2022-06-29 02:44:10 +00:00
if (!isVisible())
return;
2021-12-13 12:12:54 +00:00
{
const QByteArray geometry = saveGeometry();
const QByteArray geometry_b64 = geometry.toBase64();
const std::string old_geometry_b64 = Host::GetBaseStringSettingValue("UI", "MainWindowGeometry");
2021-12-13 12:12:54 +00:00
if (old_geometry_b64 != geometry_b64.constData())
2022-09-07 07:44:10 +00:00
{
Host::SetBaseStringSettingValue("UI", "MainWindowGeometry", geometry_b64.constData());
Host::CommitBaseSettingChanges();
}
2021-12-13 12:12:54 +00:00
}
{
const QByteArray state = saveState();
const QByteArray state_b64 = state.toBase64();
const std::string old_state_b64 = Host::GetBaseStringSettingValue("UI", "MainWindowState");
2021-12-13 12:12:54 +00:00
if (old_state_b64 != state_b64.constData())
2022-09-07 07:44:10 +00:00
{
Host::SetBaseStringSettingValue("UI", "MainWindowState", state_b64.constData());
Host::CommitBaseSettingChanges();
}
2021-12-13 12:12:54 +00:00
}
}
void MainWindow::restoreStateFromConfig()
{
{
const std::string geometry_b64 = Host::GetBaseStringSettingValue("UI", "MainWindowGeometry");
2021-12-13 12:12:54 +00:00
const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64));
if (!geometry.isEmpty())
restoreGeometry(geometry);
}
{
const std::string state_b64 = Host::GetBaseStringSettingValue("UI", "MainWindowState");
2021-12-13 12:12:54 +00:00
const QByteArray state = QByteArray::fromBase64(QByteArray::fromStdString(state_b64));
if (!state.isEmpty())
restoreState(state);
{
QSignalBlocker sb(m_ui.actionViewToolbar);
m_ui.actionViewToolbar->setChecked(!m_ui.toolBar->isHidden());
}
{
QSignalBlocker sb(m_ui.actionViewStatusBar);
m_ui.actionViewStatusBar->setChecked(!m_ui.statusBar->isHidden());
}
}
}
void MainWindow::updateEmulationActions(bool starting, bool running)
{
const bool starting_or_running = starting || running;
m_ui.actionStartFile->setDisabled(starting_or_running);
m_ui.actionStartDisc->setDisabled(starting_or_running);
m_ui.actionStartBios->setDisabled(starting_or_running);
m_ui.actionPowerOff->setEnabled(running);
2022-05-07 12:56:44 +00:00
m_ui.actionPowerOffWithoutSaving->setEnabled(running);
2021-12-13 12:12:54 +00:00
m_ui.actionReset->setEnabled(running);
m_ui.actionPause->setEnabled(running);
m_ui.actionChangeDisc->setEnabled(running);
m_ui.actionScreenshot->setEnabled(running);
m_ui.menuChangeDisc->setEnabled(running);
m_ui.actionSaveState->setEnabled(running);
m_ui.menuSaveState->setEnabled(running);
m_ui.actionViewGameProperties->setEnabled(running);
m_game_list_widget->setDisabled(starting && !running);
2022-05-06 11:03:16 +00:00
if (!starting && !running)
m_ui.actionPause->setChecked(false);
// scanning needs to be disabled while running
m_ui.actionScanForNewGames->setDisabled(starting_or_running);
m_ui.actionRescanAllGames->setDisabled(starting_or_running);
2021-12-13 12:12:54 +00:00
}
void MainWindow::updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen)
{
// rendering to main, or switched to gamelist/grid
m_ui.actionViewSystemDisplay->setEnabled((has_surface && render_to_main) || (!has_surface && g_host_display));
m_ui.menuWindowSize->setEnabled(has_surface && !fullscreen);
m_ui.actionFullscreen->setEnabled(has_surface);
{
QSignalBlocker blocker(m_ui.actionFullscreen);
m_ui.actionFullscreen->setChecked(fullscreen);
}
}
void MainWindow::updateStatusBarWidgetVisibility()
{
2022-06-28 13:34:45 +00:00
auto Update = [this](QWidget* widget, bool visible, int stretch) {
if (widget->isVisible())
{
m_ui.statusBar->removeWidget(widget);
widget->hide();
}
if (visible)
{
m_ui.statusBar->addPermanentWidget(widget, stretch);
widget->show();
}
};
2022-07-03 13:30:03 +00:00
Update(m_status_verbose_widget, s_vm_valid, 1);
Update(m_status_renderer_widget, s_vm_valid, 0);
Update(m_status_resolution_widget, s_vm_valid, 0);
Update(m_status_fps_widget, s_vm_valid, 0);
2022-07-03 13:30:03 +00:00
Update(m_status_vps_widget, s_vm_valid, 0);
}
2021-12-13 12:12:54 +00:00
void MainWindow::updateWindowTitle()
{
QString suffix(QtHost::GetAppConfigSuffix());
QString main_title(QtHost::GetAppNameAndVersion() + suffix);
QString display_title(m_current_game_name + suffix);
2022-05-06 11:03:16 +00:00
if (!s_vm_valid || m_current_game_name.isEmpty())
2022-05-06 11:03:16 +00:00
display_title = main_title;
else if (isRenderingToMain())
main_title = display_title;
if (windowTitle() != main_title)
setWindowTitle(main_title);
if (m_display_widget && !isRenderingToMain())
2021-12-13 12:12:54 +00:00
{
2022-05-06 11:03:16 +00:00
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
if (container->windowTitle() != display_title)
container->setWindowTitle(display_title);
2021-12-13 12:12:54 +00:00
}
}
2022-06-28 12:16:45 +00:00
void MainWindow::updateWindowState(bool force_visible)
{
2022-06-29 02:44:10 +00:00
// Skip all of this when we're closing, since we don't want to make ourselves visible and cancel it.
if (m_is_closing)
return;
const bool hide_window = !isRenderingToMain() && shouldHideMainWindow();
const bool disable_resize = Host::GetBoolSettingValue("UI", "DisableWindowResize", false);
const bool has_window = s_vm_valid || m_display_widget;
2022-06-28 12:16:45 +00:00
// Need to test both valid and display widget because of startup (vm invalid while window is created).
const bool visible = force_visible || !hide_window || !has_window;
if (isVisible() != visible)
setVisible(visible);
2022-06-28 12:16:45 +00:00
// No point changing realizability if we're not visible.
const bool resizeable = force_visible || !disable_resize || !has_window;
if (visible)
QtUtils::SetWindowResizeable(this, resizeable);
// Update the display widget too if rendering separately.
if (m_display_widget && !isRenderingToMain())
QtUtils::SetWindowResizeable(getDisplayContainer(), resizeable);
}
2021-12-13 12:12:54 +00:00
void MainWindow::setProgressBar(int current, int total)
{
const int value = (total != 0) ? ((current * 100) / total) : 0;
if (m_status_progress_widget->value() != value)
m_status_progress_widget->setValue(value);
2021-12-13 12:12:54 +00:00
if (m_status_progress_widget->isVisible())
return;
m_status_progress_widget->show();
m_ui.statusBar->addPermanentWidget(m_status_progress_widget);
}
void MainWindow::clearProgressBar()
{
if (!m_status_progress_widget->isVisible())
return;
m_status_progress_widget->hide();
m_ui.statusBar->removeWidget(m_status_progress_widget);
}
bool MainWindow::isShowingGameList() const
{
2022-07-09 06:59:13 +00:00
if (s_use_central_widget)
return (centralWidget() == m_game_list_widget);
else
return (m_ui.mainContainer->currentIndex() == 0);
2022-05-06 11:03:16 +00:00
}
bool MainWindow::isRenderingFullscreen() const
{
if (!g_host_display || !m_display_widget)
2022-05-06 11:03:16 +00:00
return false;
return getDisplayContainer()->isFullScreen() || g_host_display->IsFullscreen();
2022-05-06 11:03:16 +00:00
}
bool MainWindow::isRenderingToMain() const
{
2022-07-09 06:59:13 +00:00
if (s_use_central_widget)
return (m_display_widget && centralWidget() == m_display_widget);
else
return (m_display_widget && m_ui.mainContainer->indexOf(m_display_widget) == 1);
}
2021-12-13 12:12:54 +00:00
bool MainWindow::shouldHideMouseCursor() const
{
return isRenderingFullscreen() && Host::GetBoolSettingValue("UI", "HideMouseCursor", false);
}
2022-07-09 09:14:48 +00:00
bool MainWindow::shouldHideMainWindow() const
{
// NOTE: We can't use isRenderingToMain() here, because this happens post-fullscreen-switch.
return Host::GetBoolSettingValue("UI", "HideMainWindowWhenRunning", false) ||
(g_emu_thread->shouldRenderToMain() && isRenderingFullscreen()) ||
QtHost::InNoGUIMode();
2022-07-09 09:14:48 +00:00
}
2021-12-13 12:12:54 +00:00
void MainWindow::switchToGameListView()
{
2022-07-09 06:59:13 +00:00
if (isShowingGameList())
2022-05-06 11:03:16 +00:00
{
m_game_list_widget->setFocus();
2021-12-13 12:12:54 +00:00
return;
2022-05-06 11:03:16 +00:00
}
2021-12-13 12:12:54 +00:00
if (m_display_created)
2021-12-13 12:12:54 +00:00
{
m_was_paused_on_surface_loss = s_vm_paused;
if (!s_vm_paused)
2021-12-13 12:12:54 +00:00
g_emu_thread->setVMPaused(true);
2022-05-06 11:03:16 +00:00
// switch to surfaceless. we have to wait until the display widget is gone before we swap over.
g_emu_thread->setSurfaceless(true);
while (m_display_widget)
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
2021-12-13 12:12:54 +00:00
}
}
void MainWindow::switchToEmulationView()
{
if (!m_display_created || !isShowingGameList())
2021-12-13 12:12:54 +00:00
return;
2022-05-06 11:03:16 +00:00
// we're no longer surfaceless! this will call back to UpdateDisplay(), which will swap the widget out.
g_emu_thread->setSurfaceless(false);
2021-12-13 12:12:54 +00:00
2022-05-06 11:03:16 +00:00
// resume if we weren't paused at switch time
if (s_vm_paused && !m_was_paused_on_surface_loss)
2022-05-06 11:03:16 +00:00
g_emu_thread->setVMPaused(false);
if (m_display_widget)
m_display_widget->setFocus();
2021-12-13 12:12:54 +00:00
}
void MainWindow::refreshGameList(bool invalidate_cache)
{
// can't do this while the VM is running because of CDVD
if (s_vm_valid)
return;
m_game_list_widget->refresh(invalidate_cache);
}
2021-12-13 12:12:54 +00:00
void MainWindow::cancelGameListRefresh()
{
m_game_list_widget->cancelRefresh();
}
void MainWindow::invalidateSaveStateCache()
{
m_save_states_invalidated = true;
}
2021-12-13 12:12:54 +00:00
void MainWindow::reportError(const QString& title, const QString& message)
{
QMessageBox::critical(this, title, message);
}
2021-12-13 12:12:54 +00:00
bool MainWindow::confirmMessage(const QString& title, const QString& message)
{
VMLock lock(pauseAndLockVM());
return (QMessageBox::question(this, title, message) == QMessageBox::Yes);
}
2022-04-06 10:49:00 +00:00
void MainWindow::runOnUIThread(const std::function<void()>& func)
{
func();
}
bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_save_to_state /* = true */, bool default_save_to_state /* = true */, bool block_until_done /* = false */)
{
if (!s_vm_valid)
return true;
// If we don't have a crc, we can't save state.
2022-05-07 12:56:44 +00:00
allow_save_to_state &= (m_current_game_crc != 0);
bool save_state = allow_save_to_state && default_save_to_state;
2022-05-07 12:56:44 +00:00
// Only confirm on UI thread because we need to display a msgbox.
if (!m_is_closing && allow_confirm && !GSDumpReplayer::IsReplayingDump() && Host::GetBoolSettingValue("UI", "ConfirmShutdown", true))
2022-03-25 10:14:23 +00:00
{
VMLock lock(pauseAndLockVM());
2022-05-07 12:56:44 +00:00
QMessageBox msgbox(lock.getDialogParent());
msgbox.setIcon(QMessageBox::Question);
msgbox.setWindowTitle(tr("Confirm Shutdown"));
msgbox.setText(tr("Are you sure you want to shut down the virtual machine?"));
2022-05-07 12:56:44 +00:00
QCheckBox* save_cb = new QCheckBox(tr("Save State For Resume"), &msgbox);
save_cb->setChecked(save_state);
save_cb->setEnabled(allow_save_to_state);
msgbox.setCheckBox(save_cb);
msgbox.addButton(QMessageBox::Yes);
msgbox.addButton(QMessageBox::No);
msgbox.setDefaultButton(QMessageBox::Yes);
if (msgbox.exec() != QMessageBox::Yes)
2022-03-25 10:14:23 +00:00
return false;
2022-05-07 12:56:44 +00:00
save_state = save_cb->isChecked();
// Don't switch back to fullscreen when we're shutting down anyway.
lock.cancelResume();
2022-03-25 10:14:23 +00:00
}
// This is a little bit annoying. Qt will close everything down if we don't have at least one window visible,
// but we might not be visible because the user is using render-to-separate and hide. We don't want to always
// reshow the main window during display updates, because otherwise fullscreen transitions and renderer switches
// would briefly show and then hide the main window. So instead, we do it on shutdown, here. Except if we're in
// batch mode, when we're going to exit anyway.
if (!isRenderingToMain() && isHidden() && !QtHost::InBatchMode() && !g_emu_thread->isRunningFullscreenUI())
2022-06-28 12:16:45 +00:00
updateWindowState(true);
// Now we can actually shut down the VM.
2022-05-07 12:56:44 +00:00
g_emu_thread->shutdownVM(save_state);
2022-03-25 10:14:23 +00:00
2022-06-29 02:44:10 +00:00
if (block_until_done || m_is_closing || QtHost::InBatchMode())
2022-03-25 10:14:23 +00:00
{
// We need to yield here, since the display gets destroyed.
while (VMManager::GetState() != VMState::Shutdown)
2022-03-25 10:14:23 +00:00
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
}
2022-06-29 02:44:10 +00:00
if (!m_is_closing && QtHost::InBatchMode())
{
2022-07-09 09:14:48 +00:00
// If we don't set the closing flag here, the VM shutdown may not complete by the time closeEvent() is called,
// leading to a confirm.
2022-06-29 02:44:10 +00:00
m_is_closing = true;
2022-07-09 09:14:48 +00:00
QGuiApplication::quit();
}
2022-03-25 10:14:23 +00:00
return true;
}
void MainWindow::requestExit()
{
2022-03-25 10:14:23 +00:00
// this is block, because otherwise closeEvent() will also prompt
if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown, true))
return;
2022-07-09 09:14:48 +00:00
// We could use close here, but if we're not visible (e.g. quitting from fullscreen), closing the window
// doesn't quit the application.
QGuiApplication::quit();
}
void MainWindow::checkForSettingChanges()
{
if (m_display_widget)
m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused);
2022-06-28 12:16:45 +00:00
updateWindowState();
}
2022-10-15 09:03:52 +00:00
std::optional<WindowInfo> MainWindow::getWindowInfo()
{
return QtUtils::GetWindowInfoForWidget(this);
}
void Host::InvalidateSaveStateCache()
{
QMetaObject::invokeMethod(g_main_window, &MainWindow::invalidateSaveStateCache, Qt::QueuedConnection);
}
2021-12-13 12:12:54 +00:00
void MainWindow::onGameListRefreshProgress(const QString& status, int current, int total)
{
m_ui.statusBar->showMessage(status);
setProgressBar(current, total);
}
void MainWindow::onGameListRefreshComplete()
{
clearProgressBar();
}
2021-12-13 12:12:54 +00:00
void MainWindow::onGameListSelectionChanged()
{
auto lock = GameList::GetLock();
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
if (!entry)
return;
m_ui.statusBar->showMessage(QString::fromStdString(entry->path));
}
void MainWindow::onGameListEntryActivated()
{
auto lock = GameList::GetLock();
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
if (!entry)
return;
if (s_vm_valid)
2021-12-13 12:12:54 +00:00
{
// change disc on double click
2022-06-28 13:34:45 +00:00
if (!entry->IsDisc())
{
QMessageBox::critical(this, tr("Error"), tr("You must select a disc to change discs."));
return;
}
doDiscChange(CDVD_SourceType::Iso, QString::fromStdString(entry->path));
2021-12-13 12:12:54 +00:00
return;
}
// we might still be saving a resume state...
VMManager::WaitForSaveStateFlush();
2022-05-07 12:56:44 +00:00
const std::optional<bool> resume = promptForResumeState(
QString::fromStdString(VMManager::GetSaveStateFileName(entry->serial.c_str(), entry->crc, -1)));
if (!resume.has_value())
{
// cancelled
return;
}
2021-12-13 12:12:54 +00:00
// only resume if the option is enabled, and we have one for this game
2022-05-07 12:56:44 +00:00
startGameListEntry(entry, resume.value() ? std::optional<s32>(-1) : std::optional<s32>(), std::nullopt);
2021-12-13 12:12:54 +00:00
}
void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
{
auto lock = GameList::GetLock();
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
QMenu menu;
if (entry)
{
QAction* action = menu.addAction(tr("Properties..."));
2022-02-15 14:29:18 +00:00
action->setEnabled(!entry->serial.empty());
if (action->isEnabled())
connect(action, &QAction::triggered, [entry]() { SettingsDialog::openGamePropertiesDialog(entry, entry->serial, entry->crc); });
2021-12-13 12:12:54 +00:00
action = menu.addAction(tr("Open Containing Directory..."));
connect(action, &QAction::triggered, [this, entry]() {
const QFileInfo fi(QString::fromStdString(entry->path));
QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absolutePath()));
});
action = menu.addAction(tr("Set Cover Image..."));
connect(action, &QAction::triggered, [this, entry]() { setGameListEntryCoverImage(entry); });
connect(menu.addAction(tr("Exclude From List")), &QAction::triggered,
[this, entry]() { getSettingsDialog()->getGameListSettingsWidget()->addExcludedPath(entry->path); });
connect(menu.addAction(tr("Reset Play Time")), &QAction::triggered,
[this, entry]() { clearGameListEntryPlayTime(entry); });
2021-12-13 12:12:54 +00:00
menu.addSeparator();
if (!s_vm_valid)
2021-12-13 12:12:54 +00:00
{
action = menu.addAction(tr("Default Boot"));
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry); });
// Make bold to indicate it's the default choice when double-clicking
2022-05-07 12:56:44 +00:00
if (!VMManager::HasSaveStateInSlot(entry->serial.c_str(), entry->crc, -1))
2021-12-13 12:12:54 +00:00
QtUtils::MarkActionAsDefault(action);
action = menu.addAction(tr("Fast Boot"));
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry, std::nullopt, true); });
action = menu.addAction(tr("Full Boot"));
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry, std::nullopt, false); });
if (m_ui.menuDebug->menuAction()->isVisible())
{
// TODO: Hook this up once it's implemented.
action = menu.addAction(tr("Boot and Debug"));
}
menu.addSeparator();
2022-02-15 14:29:18 +00:00
populateLoadStateMenu(&menu, QString::fromStdString(entry->path), QString::fromStdString(entry->serial), entry->crc);
2021-12-13 12:12:54 +00:00
}
2022-06-28 13:34:45 +00:00
else if (entry->IsDisc())
2021-12-13 12:12:54 +00:00
{
action = menu.addAction(tr("Change Disc"));
connect(action, &QAction::triggered, [this, entry]() {
2022-06-28 13:34:45 +00:00
g_emu_thread->changeDisc(CDVD_SourceType::Iso, QString::fromStdString(entry->path));
2021-12-13 12:12:54 +00:00
switchToEmulationView();
});
QtUtils::MarkActionAsDefault(action);
}
menu.addSeparator();
}
connect(menu.addAction(tr("Add Search Directory...")), &QAction::triggered,
[this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); });
menu.exec(point);
}
void MainWindow::onStartFileActionTriggered()
{
2022-06-28 13:34:45 +00:00
const QString path(QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Start File"), QString(), tr(OPEN_FILE_FILTER), nullptr)));
if (path.isEmpty())
return;
doStartFile(std::nullopt, path);
}
void MainWindow::onStartDiscActionTriggered()
{
QString path(getDiscDevicePath(tr("Start Disc")));
2022-05-26 07:49:40 +00:00
if (path.isEmpty())
2021-12-13 12:12:54 +00:00
return;
2022-06-28 13:34:45 +00:00
doStartFile(CDVD_SourceType::Disc, path);
2021-12-13 12:12:54 +00:00
}
void MainWindow::onStartBIOSActionTriggered()
{
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
g_emu_thread->startVM(std::move(params));
}
void MainWindow::onChangeDiscFromFileActionTriggered()
{
VMLock lock(pauseAndLockVM());
QString filename = QFileDialog::getOpenFileName(lock.getDialogParent(), tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr);
2021-12-13 12:12:54 +00:00
if (filename.isEmpty())
return;
2022-06-28 13:34:45 +00:00
g_emu_thread->changeDisc(CDVD_SourceType::Iso, filename);
2021-12-13 12:12:54 +00:00
}
void MainWindow::onChangeDiscFromGameListActionTriggered()
{
m_was_disc_change_request = true;
switchToGameListView();
}
2021-12-13 12:12:54 +00:00
void MainWindow::onChangeDiscFromDeviceActionTriggered()
{
2022-06-28 13:34:45 +00:00
QString path(getDiscDevicePath(tr("Change Disc")));
if (path.isEmpty())
return;
g_emu_thread->changeDisc(CDVD_SourceType::Disc, path);
2021-12-13 12:12:54 +00:00
}
2022-06-28 14:00:51 +00:00
void MainWindow::onRemoveDiscActionTriggered()
{
g_emu_thread->changeDisc(CDVD_SourceType::NoDisc, QString());
}
2021-12-13 12:12:54 +00:00
void MainWindow::onChangeDiscMenuAboutToShow()
{
// TODO: This is where we would populate the playlist if there is one.
}
2022-02-15 14:29:18 +00:00
void MainWindow::onChangeDiscMenuAboutToHide() {}
2021-12-13 12:12:54 +00:00
void MainWindow::onLoadStateMenuAboutToShow()
{
if (m_save_states_invalidated)
updateSaveStateMenus(m_current_disc_path, m_current_game_serial, m_current_game_crc);
}
void MainWindow::onSaveStateMenuAboutToShow()
{
if (m_save_states_invalidated)
updateSaveStateMenus(m_current_disc_path, m_current_game_serial, m_current_game_crc);
}
void MainWindow::onViewToolbarActionToggled(bool checked)
{
2022-09-07 07:44:10 +00:00
Host::SetBaseBoolSettingValue("UI", "ShowToolbar", checked);
Host::CommitBaseSettingChanges();
2021-12-13 12:12:54 +00:00
m_ui.toolBar->setVisible(checked);
}
void MainWindow::onViewLockToolbarActionToggled(bool checked)
{
2022-09-07 07:44:10 +00:00
Host::SetBaseBoolSettingValue("UI", "LockToolbar", checked);
Host::CommitBaseSettingChanges();
2021-12-13 12:12:54 +00:00
m_ui.toolBar->setMovable(!checked);
}
void MainWindow::onViewStatusBarActionToggled(bool checked)
{
2022-09-07 07:44:10 +00:00
Host::SetBaseBoolSettingValue("UI", "ShowStatusBar", checked);
Host::CommitBaseSettingChanges();
2021-12-13 12:12:54 +00:00
m_ui.statusBar->setVisible(checked);
}
void MainWindow::onViewGameListActionTriggered()
{
switchToGameListView();
m_game_list_widget->showGameList();
}
void MainWindow::onViewGameGridActionTriggered()
{
switchToGameListView();
m_game_list_widget->showGameGrid();
}
void MainWindow::onViewSystemDisplayTriggered()
{
if (m_display_created)
2021-12-13 12:12:54 +00:00
switchToEmulationView();
}
void MainWindow::onViewGamePropertiesActionTriggered()
{
if (!s_vm_valid)
2021-12-13 12:12:54 +00:00
return;
2022-02-15 14:29:18 +00:00
// prefer to use a game list entry, if we have one, that way the summary is populated
if (!m_current_disc_path.isEmpty())
{
auto lock = GameList::GetLock();
const GameList::Entry* entry = GameList::GetEntryForPath(m_current_disc_path.toUtf8().constData());
if (entry)
{
SettingsDialog::openGamePropertiesDialog(entry, entry->serial, entry->crc);
2022-02-15 14:29:18 +00:00
return;
}
}
2021-12-13 12:12:54 +00:00
2022-02-15 14:29:18 +00:00
// open properties for the current running file (isn't in the game list)
if (m_current_game_crc != 0)
SettingsDialog::openGamePropertiesDialog(nullptr, m_current_game_serial.toStdString(), m_current_game_crc);
2021-12-13 12:12:54 +00:00
}
void MainWindow::onGitHubRepositoryActionTriggered()
{
QtUtils::OpenURL(this, AboutDialog::getGitHubRepositoryUrl());
}
2022-02-15 14:29:18 +00:00
void MainWindow::onSupportForumsActionTriggered()
{
QtUtils::OpenURL(this, AboutDialog::getSupportForumsUrl());
}
2022-02-15 14:29:18 +00:00
void MainWindow::onDiscordServerActionTriggered()
{
QtUtils::OpenURL(this, AboutDialog::getDiscordServerUrl());
}
2021-12-13 12:12:54 +00:00
void MainWindow::onAboutActionTriggered()
{
AboutDialog about(this);
about.exec();
}
2022-04-04 11:27:23 +00:00
void MainWindow::onCheckForUpdatesActionTriggered()
{
// Wipe out the last version, that way it displays the update if we've previously skipped it.
2022-09-07 07:44:10 +00:00
Host::RemoveBaseSettingValue("AutoUpdater", "LastVersion");
Host::CommitBaseSettingChanges();
2022-04-04 11:27:23 +00:00
checkForUpdates(true);
}
void MainWindow::checkForUpdates(bool display_message)
{
if (!AutoUpdaterDialog::isSupported())
{
if (display_message)
{
QMessageBox mbox(this);
mbox.setWindowTitle(tr("Updater Error"));
mbox.setTextFormat(Qt::RichText);
QString message;
#ifdef _WIN32
message =
tr("<p>Sorry, you are trying to update a PCSX2 version which is not an official GitHub release. To "
"prevent incompatibilities, the auto-updater is only enabled on official builds.</p>"
"<p>To obtain an official build, please download from the link below:</p>"
"<p><a href=\"https://pcsx2.net/downloads/\">https://pcsx2.net/downloads/</a></p>");
#else
message = tr("Automatic updating is not supported on the current platform.");
#endif
mbox.setText(message);
mbox.setIcon(QMessageBox::Critical);
mbox.exec();
}
return;
}
if (m_auto_updater_dialog)
return;
m_auto_updater_dialog = new AutoUpdaterDialog(this);
connect(m_auto_updater_dialog, &AutoUpdaterDialog::updateCheckCompleted, this, &MainWindow::onUpdateCheckComplete);
m_auto_updater_dialog->queueUpdateCheck(display_message);
}
void MainWindow::onUpdateCheckComplete()
{
if (!m_auto_updater_dialog)
return;
m_auto_updater_dialog->deleteLater();
m_auto_updater_dialog = nullptr;
}
void MainWindow::startupUpdateCheck()
{
if (!Host::GetBaseBoolSettingValue("AutoUpdater", "CheckAtStartup", true))
2022-04-04 11:27:23 +00:00
return;
checkForUpdates(false);
}
2021-12-13 12:12:54 +00:00
void MainWindow::onToolsOpenDataDirectoryTriggered()
{
const QString path(QString::fromStdString(EmuFolders::DataRoot));
2021-12-13 12:12:54 +00:00
QtUtils::OpenURL(this, QUrl::fromLocalFile(path));
}
2022-09-03 11:29:02 +00:00
void MainWindow::onToolsCoverDownloaderTriggered()
{
CoverDownloadDialog dlg(this);
connect(&dlg, &CoverDownloadDialog::coverRefreshRequested, m_game_list_widget, &GameListWidget::refreshGridCovers);
dlg.exec();
}
void MainWindow::updateTheme()
2021-12-13 12:12:54 +00:00
{
updateApplicationTheme();
m_game_list_widget->refreshImages();
2021-12-13 12:12:54 +00:00
}
void MainWindow::onInputRecNewActionTriggered()
{
const bool wasPaused = s_vm_paused;
const bool wasRunning = s_vm_valid;
if (wasRunning && !wasPaused)
{
VMManager::SetPaused(true);
}
NewInputRecordingDlg dlg(this);
const auto result = dlg.exec();
if (result == QDialog::Accepted)
{
if (g_InputRecording.Create(
dlg.getFilePath(),
dlg.getInputRecType() == InputRecording::Type::FROM_SAVESTATE,
dlg.getAuthorName()))
{
return;
}
}
if (wasRunning && !wasPaused)
{
VMManager::SetPaused(false);
}
}
#include "pcsx2/Recording/InputRecordingControls.h"
void MainWindow::onInputRecPlayActionTriggered()
{
const bool wasPaused = s_vm_paused;
if (!wasPaused)
g_InputRecordingControls.PauseImmediately();
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setWindowTitle("Select a File");
dialog.setNameFilter(tr("Input Recording Files (*.p2m2)"));
QStringList fileNames;
if (dialog.exec())
{
fileNames = dialog.selectedFiles();
}
if (fileNames.length() > 0)
{
if (g_InputRecording.IsActive())
{
g_InputRecording.Stop();
}
if (g_InputRecording.Play(fileNames.first().toStdString()))
{
return;
}
}
if (!wasPaused)
{
g_InputRecordingControls.Resume();
}
}
void MainWindow::onInputRecStopActionTriggered()
{
if (g_InputRecording.IsActive())
{
g_InputRecording.Stop();
}
}
void MainWindow::onInputRecOpenSettingsTriggered()
{
// TODO - Vaser - Implement
}
2021-12-13 12:12:54 +00:00
void MainWindow::onVMStarting()
{
s_vm_valid = true;
2021-12-13 12:12:54 +00:00
updateEmulationActions(true, false);
updateWindowTitle();
// prevent loading state until we're fully initialized
updateSaveStateMenus(QString(), QString(), 0);
}
void MainWindow::onVMStarted()
{
s_vm_valid = true;
m_was_disc_change_request = false;
2021-12-13 12:12:54 +00:00
updateEmulationActions(true, true);
updateWindowTitle();
updateStatusBarWidgetVisibility();
2021-12-13 12:12:54 +00:00
}
void MainWindow::onVMPaused()
{
// update UI
{
QSignalBlocker sb(m_ui.actionPause);
m_ui.actionPause->setChecked(true);
}
s_vm_paused = true;
2021-12-13 12:12:54 +00:00
updateWindowTitle();
updateStatusBarWidgetVisibility();
2022-07-03 13:30:03 +00:00
m_last_fps_status = m_status_verbose_widget->text();
m_status_verbose_widget->setText(tr("Paused"));
if (m_display_widget)
{
m_display_widget->updateRelativeMode(false);
m_display_widget->updateCursor(false);
}
2021-12-13 12:12:54 +00:00
}
void MainWindow::onVMResumed()
{
// update UI
{
QSignalBlocker sb(m_ui.actionPause);
m_ui.actionPause->setChecked(false);
}
s_vm_paused = false;
m_was_disc_change_request = false;
2021-12-13 12:12:54 +00:00
updateWindowTitle();
updateStatusBarWidgetVisibility();
2022-07-03 13:30:03 +00:00
m_status_verbose_widget->setText(m_last_fps_status);
m_last_fps_status = QString();
if (m_display_widget)
{
m_display_widget->updateRelativeMode(true);
m_display_widget->updateCursor(true);
m_display_widget->setFocus();
}
2021-12-13 12:12:54 +00:00
}
void MainWindow::onVMStopped()
{
s_vm_valid = false;
s_vm_paused = false;
m_last_fps_status = QString();
2021-12-13 12:12:54 +00:00
updateEmulationActions(false, false);
updateWindowTitle();
2022-06-28 12:16:45 +00:00
updateWindowState();
updateStatusBarWidgetVisibility();
if (m_display_widget)
{
m_display_widget->updateRelativeMode(false);
m_display_widget->updateCursor(false);
}
else
{
switchToGameListView();
}
// reload played time
if (m_game_list_widget->isShowingGameList())
m_game_list_widget->refresh(false);
2021-12-13 12:12:54 +00:00
}
void MainWindow::onGameChanged(const QString& path, const QString& serial, const QString& name, quint32 crc)
{
m_current_disc_path = path;
m_current_game_serial = serial;
m_current_game_name = name;
m_current_game_crc = crc;
updateWindowTitle();
updateSaveStateMenus(path, serial, crc);
}
void MainWindow::showEvent(QShowEvent* event)
{
QMainWindow::showEvent(event);
// This is a bit silly, but for some reason resizing *before* the window is shown
// gives the incorrect sizes for columns, if you set the style before setting up
// the rest of the window... so, instead, let's just force it to be resized on show.
if (isShowingGameList())
m_game_list_widget->resizeTableViewColumnsToFit();
#ifdef ENABLE_RAINTEGRATION
if (Achievements::IsUsingRAIntegration())
Achievements::RAIntegration::MainWindowChanged((void*)winId());
#endif
}
2021-12-13 12:12:54 +00:00
void MainWindow::closeEvent(QCloseEvent* event)
{
if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown, true))
{
event->ignore();
return;
}
2021-12-13 12:12:54 +00:00
saveStateToConfig();
2022-06-29 02:44:10 +00:00
m_is_closing = true;
2021-12-13 12:12:54 +00:00
QMainWindow::closeEvent(event);
}
2022-05-26 07:49:40 +00:00
static QString getFilenameFromMimeData(const QMimeData* md)
{
QString filename;
if (md->hasUrls())
{
// only one url accepted
const QList<QUrl> urls(md->urls());
if (urls.size() == 1)
filename = urls.front().toLocalFile();
}
return filename;
}
void MainWindow::dragEnterEvent(QDragEnterEvent* event)
{
const std::string filename(getFilenameFromMimeData(event->mimeData()).toStdString());
// allow save states being dragged in
if (!VMManager::IsLoadableFileName(filename) && !VMManager::IsSaveStateFileName(filename))
return;
event->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent* event)
{
const QString filename(getFilenameFromMimeData(event->mimeData()));
const std::string filename_str(filename.toStdString());
if (VMManager::IsSaveStateFileName(filename_str))
{
2022-06-28 13:34:45 +00:00
// can't load a save state without a current VM
if (s_vm_valid)
2022-05-26 07:49:40 +00:00
{
event->acceptProposedAction();
g_emu_thread->loadState(filename);
}
else
{
QMessageBox::critical(this, tr("Load State Failed"), tr("Cannot load a save state without a running VM."));
}
}
else if (VMManager::IsLoadableFileName(filename_str))
{
// if we're already running, do a disc change, otherwise start
event->acceptProposedAction();
if (s_vm_valid)
2022-06-28 13:34:45 +00:00
doDiscChange(CDVD_SourceType::Iso, filename);
2022-05-26 07:49:40 +00:00
else
2022-06-29 09:31:19 +00:00
doStartFile(std::nullopt, filename);
2022-06-28 13:34:45 +00:00
}
2022-05-26 07:49:40 +00:00
}
void MainWindow::registerForDeviceNotifications()
{
#ifdef _WIN32
// We use these notifications to detect when a controller is connected or disconnected.
DEV_BROADCAST_DEVICEINTERFACE_W filter = {sizeof(DEV_BROADCAST_DEVICEINTERFACE_W), DBT_DEVTYP_DEVICEINTERFACE};
m_device_notification_handle = RegisterDeviceNotificationW((HANDLE)winId(), &filter,
DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
#endif
}
void MainWindow::unregisterForDeviceNotifications()
{
#ifdef _WIN32
if (!m_device_notification_handle)
return;
UnregisterDeviceNotification(static_cast<HDEVNOTIFY>(m_device_notification_handle));
m_device_notification_handle = nullptr;
#endif
}
#ifdef _WIN32
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result)
{
static constexpr const char win_type[] = "windows_generic_MSG";
if (eventType == QByteArray(win_type, sizeof(win_type) - 1))
{
const MSG* msg = static_cast<const MSG*>(message);
if (msg->message == WM_DEVICECHANGE && msg->wParam == DBT_DEVNODES_CHANGED)
{
g_emu_thread->reloadInputDevices();
*result = 1;
return true;
}
}
return QMainWindow::nativeEvent(eventType, message, result);
}
#endif
2021-12-13 12:12:54 +00:00
DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
{
2022-05-06 11:03:16 +00:00
DevCon.WriteLn("createDisplay(%u, %u)", static_cast<u32>(fullscreen), static_cast<u32>(render_to_main));
if (!g_host_display)
2021-12-13 12:12:54 +00:00
return nullptr;
const std::string fullscreen_mode(Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", ""));
const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && g_host_display->SupportsFullscreen());
2021-12-13 12:12:54 +00:00
2022-07-09 06:59:13 +00:00
createDisplayWidget(fullscreen, render_to_main, is_exclusive_fullscreen);
2021-12-13 12:12:54 +00:00
// we need the surface visible.. this might be able to be replaced with something else
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
if (!wi.has_value())
{
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
2022-07-09 06:59:13 +00:00
destroyDisplayWidget(true);
2021-12-13 12:12:54 +00:00
return nullptr;
}
2022-04-15 08:45:41 +00:00
g_emu_thread->connectDisplaySignals(m_display_widget);
if (!g_host_display->CreateDevice(wi.value()))
2021-12-13 12:12:54 +00:00
{
QMessageBox::critical(this, tr("Error"), tr("Failed to create host display device context."));
2022-07-09 06:59:13 +00:00
destroyDisplayWidget(true);
2021-12-13 12:12:54 +00:00
return nullptr;
}
m_display_created = true;
2021-12-13 12:12:54 +00:00
if (is_exclusive_fullscreen)
setDisplayFullscreen(fullscreen_mode);
2022-05-06 11:03:16 +00:00
updateWindowTitle();
2022-06-28 12:16:45 +00:00
updateWindowState();
2022-05-06 11:03:16 +00:00
m_ui.actionStartFullscreenUI->setEnabled(false);
m_ui.actionStartFullscreenUI2->setEnabled(false);
2022-07-09 06:59:13 +00:00
m_display_widget->setShouldHideCursor(shouldHideMouseCursor());
m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused);
m_display_widget->updateCursor(s_vm_valid && !s_vm_paused);
2022-07-09 06:59:13 +00:00
m_display_widget->setFocus();
g_host_display->DoneCurrent();
2021-12-13 12:12:54 +00:00
return m_display_widget;
}
2022-05-06 11:03:16 +00:00
DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless)
2021-12-13 12:12:54 +00:00
{
DevCon.WriteLn("updateDisplay() fullscreen=%s render_to_main=%s surfaceless=%s",
fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false");
2022-05-06 11:03:16 +00:00
2022-04-15 08:45:41 +00:00
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
const bool is_fullscreen = isRenderingFullscreen();
const bool is_rendering_to_main = isRenderingToMain();
const std::string fullscreen_mode(Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", ""));
const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && g_host_display->SupportsFullscreen());
2022-05-06 11:03:16 +00:00
const bool changing_surfaceless = (!m_display_widget != surfaceless);
if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless)
2021-12-13 12:12:54 +00:00
return m_display_widget;
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
2022-04-15 08:45:41 +00:00
// .. except on Wayland, where everything tends to break if you don't recreate.
2021-12-13 12:12:54 +00:00
const bool has_container = (m_display_container != nullptr);
2022-07-09 06:59:13 +00:00
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
2022-05-06 11:03:16 +00:00
if (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container && !needs_container && !changing_surfaceless)
2021-12-13 12:12:54 +00:00
{
DevCon.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
if (g_host_display->IsFullscreen())
g_host_display->SetFullscreen(false, 0, 0, 0.0f);
2021-12-13 12:12:54 +00:00
// since we don't destroy the display widget, we need to save it here
if (!is_fullscreen && !is_rendering_to_main)
saveDisplayWindowGeometryToConfig();
2021-12-13 12:12:54 +00:00
if (fullscreen)
{
2022-04-15 08:45:41 +00:00
container->showFullScreen();
2021-12-13 12:12:54 +00:00
}
else
{
restoreDisplayWindowGeometryFromConfig();
2022-04-15 08:45:41 +00:00
container->showNormal();
2021-12-13 12:12:54 +00:00
}
m_display_widget->setFocus();
m_display_widget->setShouldHideCursor(shouldHideMouseCursor());
m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused);
m_display_widget->updateCursor(s_vm_valid && !s_vm_paused);
updateWindowState();
2021-12-13 12:12:54 +00:00
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
return m_display_widget;
}
g_host_display->DestroySurface();
2021-12-13 12:12:54 +00:00
2022-07-09 06:59:13 +00:00
destroyDisplayWidget(surfaceless);
2021-12-13 12:12:54 +00:00
2022-05-06 11:03:16 +00:00
// if we're going to surfaceless, we're done here
if (surfaceless)
return nullptr;
2022-07-09 06:59:13 +00:00
createDisplayWidget(fullscreen, render_to_main, is_exclusive_fullscreen);
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
if (!wi.has_value())
{
QMessageBox::critical(this, tr("Error"), tr("Failed to get new window info from widget"));
destroyDisplayWidget(true);
return nullptr;
}
g_emu_thread->connectDisplaySignals(m_display_widget);
if (!g_host_display->ChangeWindow(wi.value()))
2022-07-09 06:59:13 +00:00
pxFailRel("Failed to recreate surface on new widget.");
if (is_exclusive_fullscreen)
setDisplayFullscreen(fullscreen_mode);
updateWindowTitle();
updateWindowState();
m_display_widget->setFocus();
m_display_widget->setShouldHideCursor(shouldHideMouseCursor());
m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused);
m_display_widget->updateCursor(s_vm_valid && !s_vm_paused);
return m_display_widget;
}
void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool is_exclusive_fullscreen)
{
// If we're rendering to main and were hidden (e.g. coming back from fullscreen),
// make sure we're visible before trying to add ourselves. Otherwise Wayland breaks.
if (!fullscreen && render_to_main && !isVisible())
{
setVisible(true);
QGuiApplication::sync();
}
QWidget* container;
if (DisplayContainer::isNeeded(fullscreen, render_to_main))
2021-12-13 12:12:54 +00:00
{
m_display_container = new DisplayContainer();
m_display_widget = new DisplayWidget(m_display_container);
m_display_container->setDisplayWidget(m_display_widget);
container = m_display_container;
}
else
{
2022-07-09 06:59:13 +00:00
m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? getContentParent() : nullptr);
2021-12-13 12:12:54 +00:00
container = m_display_widget;
}
2022-05-06 11:03:16 +00:00
if (fullscreen || !render_to_main)
{
container->setWindowTitle(windowTitle());
container->setWindowIcon(windowIcon());
}
2021-12-13 12:12:54 +00:00
if (fullscreen)
{
// Don't risk doing this on Wayland, it really doesn't like window state changes,
// and positioning has no effect anyway.
if (!s_use_central_widget)
restoreDisplayWindowGeometryFromConfig();
2021-12-13 12:12:54 +00:00
if (!is_exclusive_fullscreen)
container->showFullScreen();
else
container->showNormal();
}
else if (!render_to_main)
{
restoreDisplayWindowGeometryFromConfig();
container->showNormal();
}
2022-07-09 06:59:13 +00:00
else if (s_use_central_widget)
2021-12-13 12:12:54 +00:00
{
2022-05-06 11:03:16 +00:00
m_game_list_widget->setVisible(false);
takeCentralWidget();
2022-07-09 06:59:13 +00:00
m_game_list_widget->setParent(this); // takeCentralWidget() removes parent
2022-05-06 11:03:16 +00:00
setCentralWidget(m_display_widget);
m_display_widget->setFocus();
2022-07-09 06:59:13 +00:00
update();
2021-12-13 12:12:54 +00:00
}
2022-07-09 06:59:13 +00:00
else
2021-12-13 12:12:54 +00:00
{
2022-07-09 06:59:13 +00:00
pxAssertRel(m_ui.mainContainer->count() == 1, "Has no display widget");
m_ui.mainContainer->addWidget(container);
m_ui.mainContainer->setCurrentIndex(1);
2021-12-13 12:12:54 +00:00
}
updateDisplayRelatedActions(true, render_to_main, fullscreen);
2022-07-09 06:59:13 +00:00
// We need the surface visible.
QGuiApplication::sync();
2021-12-13 12:12:54 +00:00
}
void MainWindow::displayResizeRequested(qint32 width, qint32 height)
{
if (!m_display_widget)
return;
// unapply the pixel scaling factor for hidpi
const float dpr = devicePixelRatioF();
width = static_cast<qint32>(std::max(static_cast<int>(std::lroundf(static_cast<float>(width) / dpr)), 1));
height = static_cast<qint32>(std::max(static_cast<int>(std::lroundf(static_cast<float>(height) / dpr)), 1));
if (m_display_container || !m_display_widget->parent())
{
// no parent - rendering to separate window. easy.
2022-06-28 12:16:45 +00:00
QtUtils::ResizePotentiallyFixedSizeWindow(getDisplayContainer(), width, height);
2021-12-13 12:12:54 +00:00
return;
}
// we are rendering to the main window. we have to add in the extra height from the toolbar/status bar.
const s32 extra_height = this->height() - m_display_widget->height();
2022-06-28 12:16:45 +00:00
QtUtils::ResizePotentiallyFixedSizeWindow(this, width, height + extra_height);
2021-12-13 12:12:54 +00:00
}
void MainWindow::destroyDisplay()
{
// Now we can safely destroy the display window.
2022-07-09 06:59:13 +00:00
destroyDisplayWidget(true);
m_display_created = false;
2022-05-06 11:03:16 +00:00
2022-07-09 06:59:13 +00:00
m_ui.actionViewSystemDisplay->setEnabled(false);
m_ui.actionFullscreen->setEnabled(false);
m_ui.actionStartFullscreenUI->setEnabled(true);
m_ui.actionStartFullscreenUI2->setEnabled(true);
2022-07-09 06:59:13 +00:00
}
void MainWindow::destroyDisplayWidget(bool show_game_list)
{
if (!m_display_widget)
return;
if (!isRenderingFullscreen() && !isRenderingToMain())
saveDisplayWindowGeometryToConfig();
if (m_display_container)
m_display_container->removeDisplayWidget();
if (isRenderingToMain())
2022-05-06 11:03:16 +00:00
{
2022-07-09 06:59:13 +00:00
if (s_use_central_widget)
{
pxAssertRel(centralWidget() == m_display_widget, "Display widget is currently central");
takeCentralWidget();
if (show_game_list)
{
m_game_list_widget->setVisible(true);
setCentralWidget(m_game_list_widget);
m_game_list_widget->resizeTableViewColumnsToFit();
2022-07-09 06:59:13 +00:00
}
}
else
{
pxAssertRel(m_ui.mainContainer->indexOf(m_display_widget) == 1, "Display widget in stack");
m_ui.mainContainer->removeWidget(m_display_widget);
if (show_game_list)
{
2022-07-09 06:59:13 +00:00
m_ui.mainContainer->setCurrentIndex(0);
m_game_list_widget->resizeTableViewColumnsToFit();
}
2022-07-09 06:59:13 +00:00
}
}
if (m_display_widget)
{
m_display_widget->deleteLater();
m_display_widget = nullptr;
}
if (m_display_container)
{
m_display_container->deleteLater();
m_display_container = nullptr;
2022-06-28 13:34:45 +00:00
}
updateDisplayRelatedActions(false, false, false);
}
2021-12-13 12:12:54 +00:00
void MainWindow::focusDisplayWidget()
{
2022-05-06 11:03:16 +00:00
if (!m_display_widget || centralWidget() != m_display_widget)
2021-12-13 12:12:54 +00:00
return;
m_display_widget->setFocus();
}
QWidget* MainWindow::getDisplayContainer() const
{
return (m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget));
}
void MainWindow::saveDisplayWindowGeometryToConfig()
{
QWidget* container = getDisplayContainer();
if (container->windowState() & Qt::WindowFullScreen)
{
// if we somehow ended up here, don't save the fullscreen state to the config
return;
}
2021-12-13 12:12:54 +00:00
const QByteArray geometry = getDisplayContainer()->saveGeometry();
const QByteArray geometry_b64 = geometry.toBase64();
const std::string old_geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
2021-12-13 12:12:54 +00:00
if (old_geometry_b64 != geometry_b64.constData())
2022-09-07 07:44:10 +00:00
{
Host::SetBaseStringSettingValue("UI", "DisplayWindowGeometry", geometry_b64.constData());
Host::CommitBaseSettingChanges();
}
2021-12-13 12:12:54 +00:00
}
void MainWindow::restoreDisplayWindowGeometryFromConfig()
{
const std::string geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
2021-12-13 12:12:54 +00:00
const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64));
QWidget* container = getDisplayContainer();
if (!geometry.isEmpty())
{
2021-12-13 12:12:54 +00:00
container->restoreGeometry(geometry);
// make sure we're not loading a dodgy config which had fullscreen set...
container->setWindowState(container->windowState() & ~(Qt::WindowFullScreen | Qt::WindowActive));
}
2021-12-13 12:12:54 +00:00
else
{
// default size
2021-12-13 12:12:54 +00:00
container->resize(640, 480);
}
2021-12-13 12:12:54 +00:00
}
void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode)
{
u32 width, height;
float refresh_rate;
if (HostDisplay::ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate))
{
if (g_host_display->SetFullscreen(true, width, height, refresh_rate))
2021-12-13 12:12:54 +00:00
{
Host::AddOSDMessage("Acquired exclusive fullscreen.", Host::OSD_INFO_DURATION);
2021-12-13 12:12:54 +00:00
}
else
{
Host::AddOSDMessage("Failed to acquire exclusive fullscreen.", Host::OSD_WARNING_DURATION);
2021-12-13 12:12:54 +00:00
}
}
}
SettingsDialog* MainWindow::getSettingsDialog()
{
if (!m_settings_dialog)
{
m_settings_dialog = new SettingsDialog(this);
2022-02-15 14:29:18 +00:00
connect(
m_settings_dialog->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::themeChanged, this, &MainWindow::updateTheme);
2021-12-13 12:12:54 +00:00
}
return m_settings_dialog;
}
2022-02-15 14:29:18 +00:00
void MainWindow::doSettings(const char* category /* = nullptr */)
2021-12-13 12:12:54 +00:00
{
SettingsDialog* dlg = getSettingsDialog();
if (!dlg->isVisible())
{
dlg->setModal(false);
dlg->show();
}
2022-02-15 14:29:18 +00:00
if (category)
2021-12-13 12:12:54 +00:00
dlg->setCategory(category);
}
ControllerSettingsDialog* MainWindow::getControllerSettingsDialog()
{
if (!m_controller_settings_dialog)
m_controller_settings_dialog = new ControllerSettingsDialog(this);
return m_controller_settings_dialog;
}
void MainWindow::doControllerSettings(ControllerSettingsDialog::Category category)
{
ControllerSettingsDialog* dlg = getControllerSettingsDialog();
if (!dlg->isVisible())
{
dlg->setModal(false);
dlg->show();
}
if (category != ControllerSettingsDialog::Category::Count)
dlg->setCategory(category);
}
2022-06-28 13:34:45 +00:00
QString MainWindow::getDiscDevicePath(const QString& title)
{
QString ret;
const std::vector<std::string> devices(GetOpticalDriveList());
if (devices.empty())
{
QMessageBox::critical(this, title,
tr("Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and "
"sufficient permissions to access it."));
return ret;
}
// if there's only one, select it automatically
if (devices.size() == 1)
{
ret = QString::fromStdString(devices.front());
return ret;
}
QStringList input_options;
for (const std::string& name : devices)
input_options.append(QString::fromStdString(name));
QInputDialog input_dialog(this);
input_dialog.setWindowTitle(title);
input_dialog.setLabelText(tr("Select disc drive:"));
input_dialog.setInputMode(QInputDialog::TextInput);
input_dialog.setOptions(QInputDialog::UseListViewForComboBoxItems);
input_dialog.setComboBoxEditable(false);
input_dialog.setComboBoxItems(std::move(input_options));
if (input_dialog.exec() == 0)
return ret;
ret = input_dialog.textValue();
return ret;
}
2022-02-15 14:29:18 +00:00
void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional<s32> save_slot, std::optional<bool> fast_boot)
2021-12-13 12:12:54 +00:00
{
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
params->fast_boot = fast_boot;
GameList::FillBootParametersForEntry(params.get(), entry);
if (save_slot.has_value() && !entry->serial.empty())
{
std::string state_filename = VMManager::GetSaveStateFileName(entry->serial.c_str(), entry->crc, save_slot.value());
if (!FileSystem::FileExists(state_filename.c_str()))
{
QMessageBox::critical(this, tr("Error"), tr("This save state does not exist."));
return;
}
params->save_state = std::move(state_filename);
}
g_emu_thread->startVM(std::move(params));
}
void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
{
2022-02-15 14:29:18 +00:00
const QString filename(QFileDialog::getOpenFileName(this, tr("Select Cover Image"), QString(), tr("All Cover Image Types (*.jpg *.jpeg *.png)")));
2021-12-13 12:12:54 +00:00
if (filename.isEmpty())
return;
if (!GameList::GetCoverImagePathForEntry(entry).empty())
{
2022-02-15 14:29:18 +00:00
if (QMessageBox::question(this, tr("Cover Already Exists"), tr("A cover image for this game already exists, do you wish to replace it?"),
2021-12-13 12:12:54 +00:00
QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
{
return;
}
}
const QString new_filename(QString::fromStdString(GameList::GetNewCoverImagePathForEntry(entry, filename.toUtf8().constData())));
if (new_filename.isEmpty())
return;
if (QFile::exists(new_filename) && !QFile::remove(new_filename))
{
QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove existing cover '%1'").arg(new_filename));
return;
}
if (!QFile::copy(filename, new_filename))
{
QMessageBox::critical(this, tr("Copy Error"), tr("Failed to copy '%1' to '%2'").arg(filename).arg(new_filename));
return;
}
m_game_list_widget->refreshGridCovers();
}
void MainWindow::clearGameListEntryPlayTime(const GameList::Entry* entry)
{
if (QMessageBox::question(this, tr("Confirm Reset"),
tr("Are you sure you want to reset the play time for '%1'?\n\nThis action cannot be undone.")
.arg(QString::fromStdString(entry->title))) != QMessageBox::Yes)
{
return;
}
GameList::ClearPlayedTimeForSerial(entry->serial);
m_game_list_widget->refresh(false);
}
2022-05-07 12:56:44 +00:00
std::optional<bool> MainWindow::promptForResumeState(const QString& save_state_path)
{
if (save_state_path.isEmpty())
return false;
QFileInfo fi(save_state_path);
if (!fi.exists())
return false;
QMessageBox msgbox(this);
msgbox.setIcon(QMessageBox::Question);
msgbox.setWindowTitle(tr("Load Resume State"));
msgbox.setText(
tr("A resume save state was found for this game, saved at:\n\n%1.\n\nDo you want to load this state, or start from a fresh boot?")
.arg(fi.lastModified().toLocalTime().toString()));
QPushButton* load = msgbox.addButton(tr("Load State"), QMessageBox::AcceptRole);
QPushButton* boot = msgbox.addButton(tr("Fresh Boot"), QMessageBox::RejectRole);
QPushButton* delboot = msgbox.addButton(tr("Delete And Boot"), QMessageBox::RejectRole);
msgbox.addButton(QMessageBox::Cancel);
2022-05-07 12:56:44 +00:00
msgbox.setDefaultButton(load);
msgbox.exec();
QAbstractButton* clicked = msgbox.clickedButton();
if (load == clicked)
{
return true;
}
else if (boot == clicked)
{
return false;
}
else if (delboot == clicked)
{
if (!QFile::remove(save_state_path))
QMessageBox::critical(this, tr("Error"), tr("Failed to delete save state file '%1'.").arg(save_state_path));
return false;
}
return std::nullopt;
}
2021-12-13 12:12:54 +00:00
void MainWindow::loadSaveStateSlot(s32 slot)
{
if (s_vm_valid)
2021-12-13 12:12:54 +00:00
{
// easy when we're running
g_emu_thread->loadStateFromSlot(slot);
return;
}
else
{
// we're not currently running, therefore we must've right clicked in the game list
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
if (!entry)
return;
startGameListEntry(entry, slot, std::nullopt);
}
}
void MainWindow::loadSaveStateFile(const QString& filename, const QString& state_filename)
{
if (s_vm_valid)
2021-12-13 12:12:54 +00:00
{
if (!filename.isEmpty() && m_current_disc_path != filename)
2022-06-28 13:34:45 +00:00
g_emu_thread->changeDisc(CDVD_SourceType::Iso, m_current_disc_path);
g_emu_thread->loadState(state_filename);
2021-12-13 12:12:54 +00:00
}
else
{
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
params->filename = filename.toStdString();
2021-12-13 12:12:54 +00:00
params->save_state = state_filename.toStdString();
g_emu_thread->startVM(std::move(params));
}
}
static QString formatTimestampForSaveStateMenu(time_t timestamp)
{
const QDateTime qtime(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(timestamp)));
return qtime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
}
void MainWindow::populateLoadStateMenu(QMenu* menu, const QString& filename, const QString& serial, quint32 crc)
{
if (serial.isEmpty())
return;
const bool is_right_click_menu = (menu != m_ui.menuLoadState);
bool has_any_states = false;
2021-12-13 12:12:54 +00:00
QAction* action = menu->addAction(is_right_click_menu ? tr("Load State File...") : tr("Load From File..."));
connect(action, &QAction::triggered, [this, filename]() {
2022-02-15 14:29:18 +00:00
const QString path(QFileDialog::getOpenFileName(this, tr("Select Save State File"), QString(), tr("Save States (*.p2s)")));
2021-12-13 12:12:54 +00:00
if (path.isEmpty())
return;
loadSaveStateFile(filename, path);
});
QAction* delete_save_states_action = menu->addAction(tr("Delete Save States..."));
2021-12-13 12:12:54 +00:00
// don't include undo in the right click menu
if (!is_right_click_menu)
{
QAction* load_undo_state = menu->addAction(tr("Undo Load State"));
load_undo_state->setEnabled(false); // CanUndoLoadState()
// connect(load_undo_state, &QAction::triggered, this, &QtHostInterface::undoLoadState);
menu->addSeparator();
}
const QByteArray game_serial_utf8(serial.toUtf8());
std::string state_filename;
FILESYSTEM_STAT_DATA sd;
if (is_right_click_menu)
{
state_filename = VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, -1);
if (FileSystem::StatFile(state_filename.c_str(), &sd))
{
action = menu->addAction(tr("Resume (%2)").arg(formatTimestampForSaveStateMenu(sd.ModificationTime)));
connect(action, &QAction::triggered, [this]() { loadSaveStateSlot(-1); });
// Make bold to indicate it's the default choice when double-clicking
2022-05-07 12:56:44 +00:00
QtUtils::MarkActionAsDefault(action);
has_any_states = true;
2021-12-13 12:12:54 +00:00
}
}
for (s32 i = 1; i <= VMManager::NUM_SAVE_STATE_SLOTS; i++)
2021-12-13 12:12:54 +00:00
{
FILESYSTEM_STAT_DATA sd;
state_filename = VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, i);
if (!FileSystem::StatFile(state_filename.c_str(), &sd))
continue;
action = menu->addAction(tr("Load Slot %1 (%2)").arg(i).arg(formatTimestampForSaveStateMenu(sd.ModificationTime)));
2021-12-13 12:12:54 +00:00
connect(action, &QAction::triggered, [this, i]() { loadSaveStateSlot(i); });
has_any_states = true;
}
delete_save_states_action->setEnabled(has_any_states);
if (has_any_states)
{
connect(delete_save_states_action, &QAction::triggered, this, [this, serial, crc] {
if (QMessageBox::warning(
this, tr("Delete Save States"),
tr("Are you sure you want to delete all save states for %1?\n\nThe saves will not be recoverable.")
.arg(serial),
QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
{
return;
}
const u32 deleted = VMManager::DeleteSaveStates(serial.toUtf8().constData(), crc, true);
QMessageBox::information(this, tr("Delete Save States"), tr("%1 save states deleted.").arg(deleted));
});
2021-12-13 12:12:54 +00:00
}
}
void MainWindow::populateSaveStateMenu(QMenu* menu, const QString& serial, quint32 crc)
{
if (serial.isEmpty())
return;
connect(menu->addAction(tr("Save To File...")), &QAction::triggered, [this]() {
2022-02-15 14:29:18 +00:00
const QString path(QFileDialog::getSaveFileName(this, tr("Select Save State File"), QString(), tr("Save States (*.p2s)")));
2021-12-13 12:12:54 +00:00
if (path.isEmpty())
return;
g_emu_thread->saveState(path);
});
menu->addSeparator();
const QByteArray game_serial_utf8(serial.toUtf8());
for (s32 i = 1; i <= VMManager::NUM_SAVE_STATE_SLOTS; i++)
2021-12-13 12:12:54 +00:00
{
std::string filename(VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, i));
FILESYSTEM_STAT_DATA sd;
QString timestamp;
if (FileSystem::StatFile(filename.c_str(), &sd))
timestamp = formatTimestampForSaveStateMenu(sd.ModificationTime);
else
timestamp = tr("Empty");
QString title(tr("Save Slot %1 (%2)").arg(i).arg(timestamp));
2022-03-04 08:04:05 +00:00
connect(menu->addAction(title), &QAction::triggered, [i]() { g_emu_thread->saveStateToSlot(i); });
2021-12-13 12:12:54 +00:00
}
}
void MainWindow::updateSaveStateMenus(const QString& filename, const QString& serial, quint32 crc)
{
const bool load_enabled = !serial.isEmpty();
const bool save_enabled = !serial.isEmpty() && s_vm_valid;
2021-12-13 12:12:54 +00:00
m_ui.menuLoadState->clear();
m_ui.menuLoadState->setEnabled(load_enabled);
m_ui.actionLoadState->setEnabled(load_enabled);
m_ui.menuSaveState->clear();
m_ui.menuSaveState->setEnabled(save_enabled);
m_ui.actionSaveState->setEnabled(save_enabled);
m_save_states_invalidated = false;
if (load_enabled)
populateLoadStateMenu(m_ui.menuLoadState, filename, serial, crc);
if (save_enabled)
populateSaveStateMenu(m_ui.menuSaveState, serial, crc);
}
2022-06-28 13:34:45 +00:00
void MainWindow::doStartFile(std::optional<CDVD_SourceType> source, const QString& path)
2022-05-26 07:49:40 +00:00
{
if (s_vm_valid)
2022-05-26 07:49:40 +00:00
return;
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
2022-06-28 13:34:45 +00:00
params->source_type = source;
2022-05-26 07:49:40 +00:00
params->filename = path.toStdString();
// we might still be saving a resume state...
VMManager::WaitForSaveStateFlush();
const std::optional<bool> resume(
promptForResumeState(
QString::fromStdString(VMManager::GetSaveStateFileName(params->filename.c_str(), -1))));
if (!resume.has_value())
return;
else if (resume.value())
params->state_index = -1;
g_emu_thread->startVM(std::move(params));
}
2022-06-28 13:34:45 +00:00
void MainWindow::doDiscChange(CDVD_SourceType source, const QString& path)
{
bool reset_system = false;
if (!m_was_disc_change_request)
{
const int choice = QMessageBox::question(this, tr("Confirm Disc Change"), tr("Do you want to swap discs or boot the new image (via system reset)?"),
tr("Swap Disc"), tr("Reset"), tr("Cancel"), 0, 2);
if (choice == 2)
return;
reset_system = (choice != 0);
}
switchToEmulationView();
2022-06-28 13:34:45 +00:00
g_emu_thread->changeDisc(source, path);
if (reset_system)
g_emu_thread->resetVM();
}
MainWindow::VMLock MainWindow::pauseAndLockVM()
{
const bool was_fullscreen = isRenderingFullscreen();
const bool was_paused = s_vm_paused;
// We use surfaceless rather than switching out of fullscreen, because
// we're paused, so we're not going to be rendering anyway.
if (was_fullscreen)
g_emu_thread->setSurfaceless(true);
if (!was_paused)
g_emu_thread->setVMPaused(true);
// We want to parent dialogs to the display widget, except if we were fullscreen,
// since it's going to get destroyed by the surfaceless call above.
QWidget* dialog_parent = was_fullscreen ? static_cast<QWidget*>(this) : getDisplayContainer();
return VMLock(dialog_parent, was_paused, was_fullscreen);
}
MainWindow::VMLock::VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen)
: m_dialog_parent(dialog_parent)
, m_was_paused(was_paused)
2022-06-28 13:34:45 +00:00
, m_was_fullscreen(was_fullscreen)
{
}
MainWindow::VMLock::VMLock(VMLock&& lock)
: m_dialog_parent(lock.m_dialog_parent)
, m_was_paused(lock.m_was_paused)
, m_was_fullscreen(lock.m_was_fullscreen)
{
lock.m_dialog_parent = nullptr;
lock.m_was_paused = true;
lock.m_was_fullscreen = false;
}
MainWindow::VMLock::~VMLock()
{
if (m_was_fullscreen)
g_emu_thread->setSurfaceless(false);
if (!m_was_paused)
g_emu_thread->setVMPaused(false);
}
void MainWindow::VMLock::cancelResume()
{
m_was_paused = true;
m_was_fullscreen = false;
}
bool QtHost::IsVMValid()
{
return s_vm_valid;
}
bool QtHost::IsVMPaused()
{
return s_vm_paused;
}