mirror of
https://github.com/PCSX2/pcsx2.git
synced 2025-12-16 04:08:48 +00:00
Qt: Add setting to show state load errors using a dialog or OSD message
This commit is contained in:
parent
92c7eaa383
commit
764875ddbf
@ -37,6 +37,7 @@
|
||||
#include "pcsx2/PerformanceMetrics.h"
|
||||
#include "pcsx2/Recording/InputRecording.h"
|
||||
#include "pcsx2/Recording/InputRecordingControls.h"
|
||||
#include "pcsx2/SaveState.h"
|
||||
#include "pcsx2/SIO/Sio.h"
|
||||
#include "pcsx2/GS/GSExtra.h"
|
||||
|
||||
@ -1213,6 +1214,58 @@ void MainWindow::onStatusMessage(const QString& message)
|
||||
m_ui.statusBar->showMessage(message);
|
||||
}
|
||||
|
||||
void MainWindow::reportStateLoadError(const QString& message, std::optional<s32> slot, bool backup)
|
||||
{
|
||||
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
|
||||
if (!prompt_on_error)
|
||||
{
|
||||
SaveState_ReportLoadErrorOSD(message.toStdString(), slot, backup);
|
||||
return;
|
||||
}
|
||||
|
||||
QString title;
|
||||
if (slot.has_value())
|
||||
{
|
||||
if (backup)
|
||||
title = tr("Failed to Load State From Backup Slot %1").arg(*slot);
|
||||
else
|
||||
title = tr("Failed to Load State From Slot %1").arg(*slot);
|
||||
}
|
||||
else
|
||||
{
|
||||
title = tr("Failed to Load State");
|
||||
}
|
||||
|
||||
VMLock lock(pauseAndLockVM());
|
||||
|
||||
QCheckBox* do_not_show_again = new QCheckBox(tr("Do not show again"));
|
||||
|
||||
QPointer<QMessageBox> message_box = new QMessageBox(this);
|
||||
message_box->setWindowTitle(title);
|
||||
message_box->setText(message);
|
||||
message_box->setIcon(QMessageBox::Critical);
|
||||
message_box->addButton(QMessageBox::Ok);
|
||||
message_box->setDefaultButton(QMessageBox::Ok);
|
||||
message_box->setCheckBox(do_not_show_again);
|
||||
|
||||
message_box->exec();
|
||||
if (message_box.isNull())
|
||||
return;
|
||||
|
||||
if (do_not_show_again->isChecked())
|
||||
{
|
||||
Host::SetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", false);
|
||||
Host::CommitBaseSettingChanges();
|
||||
if (m_settings_window)
|
||||
{
|
||||
InterfaceSettingsWidget* interface_settings = m_settings_window->getInterfaceSettingsWidget();
|
||||
interface_settings->updatePromptOnStateLoadSaveFailureCheckbox(Qt::Unchecked);
|
||||
}
|
||||
}
|
||||
|
||||
delete message_box;
|
||||
}
|
||||
|
||||
void MainWindow::runOnUIThread(const std::function<void()>& func)
|
||||
{
|
||||
func();
|
||||
|
||||
@ -120,6 +120,7 @@ public Q_SLOTS:
|
||||
void reportError(const QString& title, const QString& message);
|
||||
bool confirmMessage(const QString& title, const QString& message);
|
||||
void onStatusMessage(const QString& message);
|
||||
void reportStateLoadError(const QString& message, std::optional<s32> slot, bool backup);
|
||||
|
||||
void runOnUIThread(const std::function<void()>& func);
|
||||
void requestReset();
|
||||
|
||||
@ -299,7 +299,11 @@ void EmuThread::loadState(const QString& filename)
|
||||
|
||||
Error error;
|
||||
if (!VMManager::LoadState(filename.toUtf8().constData(), &error))
|
||||
Host::ReportErrorAsync(TRANSLATE_SV("QtHost", "Failed to Load State"), error.GetDescription());
|
||||
{
|
||||
QtHost::RunOnUIThread([message = QString::fromStdString(error.GetDescription())]() {
|
||||
g_main_window->reportStateLoadError(message, std::nullopt, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EmuThread::loadStateFromSlot(qint32 slot, bool load_backup)
|
||||
@ -315,7 +319,11 @@ void EmuThread::loadStateFromSlot(qint32 slot, bool load_backup)
|
||||
|
||||
Error error;
|
||||
if (!VMManager::LoadStateFromSlot(slot, load_backup, &error))
|
||||
Host::ReportErrorAsync(TRANSLATE_SV("QtHost", "Failed to Load State"), error.GetDescription());
|
||||
{
|
||||
QtHost::RunOnUIThread([message = QString::fromStdString(error.GetDescription()), slot, load_backup]() {
|
||||
g_main_window->reportStateLoadError(message, slot, load_backup);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EmuThread::saveState(const QString& filename)
|
||||
|
||||
@ -97,15 +97,16 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmShutdown, "UI", "ConfirmShutdown", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnControllerDisconnection, "UI", "PauseOnControllerDisconnection", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.promptOnStateLoadSaveFailure, "UI", "PromptOnStateLoadSaveFailure", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "EmuCore", "EnableDiscordPresence", false);
|
||||
|
||||
#ifdef __linux__ // Mouse locking is only supported on X11
|
||||
#ifdef __linux__ // Mouse locking is only supported on X11
|
||||
const bool mouse_lock_supported = QGuiApplication::platformName().toLower() == "xcb";
|
||||
#else
|
||||
const bool mouse_lock_supported = true;
|
||||
#endif
|
||||
|
||||
if(mouse_lock_supported)
|
||||
if (mouse_lock_supported)
|
||||
{
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.mouseLock, "EmuCore", "EnableMouseLock", false);
|
||||
connect(m_ui.mouseLock, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
@ -196,6 +197,8 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
"and unpauses when you switch back."));
|
||||
dialog()->registerWidgetHelp(m_ui.pauseOnControllerDisconnection, tr("Pause On Controller Disconnection"),
|
||||
tr("Unchecked"), tr("Pauses the emulator when a controller with bindings is disconnected."));
|
||||
dialog()->registerWidgetHelp(m_ui.promptOnStateLoadSaveFailure, tr("Pause On State Load/Save Failure"),
|
||||
tr("Checked"), tr("Display a modal dialog when a save state load/save operation fails."));
|
||||
dialog()->registerWidgetHelp(m_ui.startFullscreen, tr("Start Fullscreen"), tr("Unchecked"),
|
||||
tr("Automatically switches to fullscreen mode when a game is started."));
|
||||
dialog()->registerWidgetHelp(m_ui.hideMouseCursor, tr("Hide Cursor In Fullscreen"), tr("Unchecked"),
|
||||
@ -224,7 +227,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.backgroundBrowse, tr("Game List Background"), tr("None"),
|
||||
tr("Enable an animated/static background on the game list (where you launch your games).<br>"
|
||||
"This background is only visible in the library and will be hidden once a game is launched. It will also be paused when it's not in focus."));
|
||||
"This background is only visible in the library and will be hidden once a game is launched. It will also be paused when it's not in focus."));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.backgroundReset, tr("Disable/Reset Game List Background"), tr("None"),
|
||||
tr("Disable and reset the currently applied game list background."));
|
||||
@ -241,6 +244,12 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
|
||||
InterfaceSettingsWidget::~InterfaceSettingsWidget() = default;
|
||||
|
||||
void InterfaceSettingsWidget::updatePromptOnStateLoadSaveFailureCheckbox(Qt::CheckState state)
|
||||
{
|
||||
QSignalBlocker blocker(m_ui.promptOnStateLoadSaveFailure);
|
||||
m_ui.promptOnStateLoadSaveFailure->setCheckState(state);
|
||||
}
|
||||
|
||||
void InterfaceSettingsWidget::onRenderToSeparateWindowChanged()
|
||||
{
|
||||
m_ui.hideMainWindow->setEnabled(m_ui.renderToSeparateWindow->isChecked());
|
||||
|
||||
@ -15,6 +15,8 @@ public:
|
||||
InterfaceSettingsWidget(SettingsWindow* settings_dialog, QWidget* parent);
|
||||
~InterfaceSettingsWidget();
|
||||
|
||||
void updatePromptOnStateLoadSaveFailureCheckbox(Qt::CheckState state);
|
||||
|
||||
Q_SIGNALS:
|
||||
void themeChanged();
|
||||
void languageChanged();
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>725</width>
|
||||
<height>625</height>
|
||||
<height>637</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@ -78,6 +78,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="promptOnStateLoadSaveFailure">
|
||||
<property name="text">
|
||||
<string>Prompt On State Load/Save Failure</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -380,6 +387,7 @@
|
||||
<tabstop>discordPresence</tabstop>
|
||||
<tabstop>pauseOnControllerDisconnection</tabstop>
|
||||
<tabstop>mouseLock</tabstop>
|
||||
<tabstop>promptOnStateLoadSaveFailure</tabstop>
|
||||
<tabstop>startFullscreen</tabstop>
|
||||
<tabstop>doubleClickTogglesFullscreen</tabstop>
|
||||
<tabstop>renderToSeparateWindow</tabstop>
|
||||
|
||||
@ -106,8 +106,7 @@ static void HotkeyLoadStateSlot(s32 slot)
|
||||
|
||||
Error error;
|
||||
if (!VMManager::LoadStateFromSlot(slot, false, &error))
|
||||
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
|
||||
error.GetDescription(), Host::OSD_INFO_DURATION);
|
||||
FullscreenUI::ReportStateLoadError(error.GetDescription(), slot, false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -670,7 +670,7 @@ namespace FullscreenUI
|
||||
static void DrawSaveStateSelector(bool is_loading);
|
||||
static bool OpenLoadStateSelectorForGameResume(const GameList::Entry* entry);
|
||||
static void DrawResumeStateSelector();
|
||||
static void DoLoadState(std::string path);
|
||||
static void DoLoadState(std::string path, std::optional<s32> slot, bool backup);
|
||||
|
||||
static std::vector<SaveStateListEntry> s_save_state_selector_slots;
|
||||
static std::string s_save_state_selector_game_path;
|
||||
@ -4129,6 +4129,8 @@ void FullscreenUI::DrawInterfaceSettingsPage()
|
||||
FSUI_CSTR("Pauses the emulator when a controller with bindings is disconnected."), "UI", "PauseOnControllerDisconnection", false);
|
||||
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_RECTANGLE_LIST, "Pause On Menu"),
|
||||
FSUI_CSTR("Pauses the emulator when you open the quick menu, and unpauses when you close it."), "UI", "PauseOnMenu", true);
|
||||
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_FLOPPY_DISK, "Prompt On State Load/Save Failure"),
|
||||
FSUI_CSTR("Display a modal dialog when a save state load/save operation fails."), "UI", "PromptOnStateLoadSaveFailure", true);
|
||||
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_POWER_OFF, "Confirm Shutdown"),
|
||||
FSUI_CSTR("Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed."),
|
||||
"UI", "ConfirmShutdown", true);
|
||||
@ -7345,7 +7347,7 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading)
|
||||
false, is_loading ? !Achievements::IsHardcoreModeActive() : true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY))
|
||||
{
|
||||
if (is_loading)
|
||||
DoLoadState(std::move(entry.path));
|
||||
DoLoadState(std::move(entry.path), entry.slot, false);
|
||||
else
|
||||
Host::RunOnCPUThread([slot = entry.slot]() { VMManager::SaveStateToSlot(slot); });
|
||||
|
||||
@ -7483,7 +7485,7 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading)
|
||||
if (pressed)
|
||||
{
|
||||
if (is_loading)
|
||||
DoLoadState(entry.path);
|
||||
DoLoadState(entry.path, entry.slot, false);
|
||||
else
|
||||
Host::RunOnCPUThread([slot = entry.slot]() { VMManager::SaveStateToSlot(slot); });
|
||||
|
||||
@ -7639,19 +7641,16 @@ void FullscreenUI::DrawResumeStateSelector()
|
||||
}
|
||||
}
|
||||
|
||||
void FullscreenUI::DoLoadState(std::string path)
|
||||
void FullscreenUI::DoLoadState(std::string path, std::optional<s32> slot, bool backup)
|
||||
{
|
||||
Host::RunOnCPUThread([boot_path = s_save_state_selector_game_path, path = std::move(path)]() {
|
||||
std::string boot_path = s_save_state_selector_game_path;
|
||||
Host::RunOnCPUThread([boot_path = std::move(boot_path), path = std::move(path), slot, backup]() {
|
||||
if (VMManager::HasValidVM())
|
||||
{
|
||||
Error error;
|
||||
if (!VMManager::LoadState(path.c_str(), &error))
|
||||
{
|
||||
MTGS::RunOnGSThread([error = std::move(error)]() {
|
||||
ImGuiFullscreen::OpenInfoMessageDialog(
|
||||
FSUI_ICONSTR(ICON_FA_TRIANGLE_EXCLAMATION, "Failed to Load State"),
|
||||
error.GetDescription());
|
||||
});
|
||||
ReportStateLoadError(error.GetDescription(), slot, backup);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -9169,6 +9168,44 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
|
||||
EndMenuButtons();
|
||||
}
|
||||
|
||||
void FullscreenUI::ReportStateLoadError(std::string message, std::optional<s32> slot, bool backup)
|
||||
{
|
||||
MTGS::RunOnGSThread([message = std::move(message), slot, backup]() {
|
||||
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
|
||||
if (!prompt_on_error || !ImGuiManager::InitializeFullscreenUI())
|
||||
{
|
||||
SaveState_ReportLoadErrorOSD(message, slot, backup);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string title;
|
||||
if (slot.has_value())
|
||||
{
|
||||
if (backup)
|
||||
title = fmt::format(FSUI_FSTR("Failed to Load State From Backup Slot {}"), *slot);
|
||||
else
|
||||
title = fmt::format(FSUI_FSTR("Failed to Load State From Slot {}"), *slot);
|
||||
}
|
||||
else
|
||||
{
|
||||
title = FSUI_STR("Failed to Load State");
|
||||
}
|
||||
|
||||
ImGuiFullscreen::InfoMessageDialogCallback callback;
|
||||
if (VMManager::GetState() == VMState::Running)
|
||||
{
|
||||
Host::RunOnCPUThread([]() { VMManager::SetPaused(true); });
|
||||
callback = []() {
|
||||
Host::RunOnCPUThread([]() { VMManager::SetPaused(false); });
|
||||
};
|
||||
}
|
||||
|
||||
ImGuiFullscreen::OpenInfoMessageDialog(
|
||||
fmt::format("{} {}", ICON_FA_TRIANGLE_EXCLAMATION, title),
|
||||
std::move(message), std::move(callback));
|
||||
});
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Translation String Area
|
||||
// To avoid having to type T_RANSLATE("FullscreenUI", ...) everywhere, we use the shorter macros at the top
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
struct Pcsx2Config;
|
||||
|
||||
@ -26,6 +27,7 @@ namespace FullscreenUI
|
||||
void OpenPauseMenu();
|
||||
bool OpenAchievementsWindow();
|
||||
bool OpenLeaderboardsWindow();
|
||||
void ReportStateLoadError(std::string message, std::optional<s32> slot, bool backup);
|
||||
|
||||
// NOTE: Only call from GS thread.
|
||||
bool IsAchievementsWindowOpen();
|
||||
|
||||
@ -1382,8 +1382,7 @@ void SaveStateSelectorUI::LoadCurrentSlot()
|
||||
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
|
||||
Error error;
|
||||
if (!VMManager::LoadStateFromSlot(slot, false, &error))
|
||||
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
|
||||
error.GetDescription(), Host::OSD_INFO_DURATION);
|
||||
FullscreenUI::ReportStateLoadError(error.GetDescription(), slot, false);
|
||||
});
|
||||
Close();
|
||||
}
|
||||
@ -1393,8 +1392,7 @@ void SaveStateSelectorUI::LoadCurrentBackupSlot()
|
||||
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
|
||||
Error error;
|
||||
if (!VMManager::LoadStateFromSlot(slot, true, &error))
|
||||
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
|
||||
error.GetDescription(), Host::OSD_INFO_DURATION);
|
||||
FullscreenUI::ReportStateLoadError(error.GetDescription(), slot, true);
|
||||
});
|
||||
Close();
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "Host.h"
|
||||
#include "Memory.h"
|
||||
#include "Elfheader.h"
|
||||
#include "SaveState.h"
|
||||
#include "PINE.h"
|
||||
#include "VMManager.h"
|
||||
#include "common/Error.h"
|
||||
@ -19,7 +20,6 @@
|
||||
#include <thread>
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "IconsFontAwesome6.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define read_portable(a, b, c) (recv(a, (char*)b, c, 0))
|
||||
@ -659,8 +659,7 @@ PINEServer::IPCBuffer PINEServer::ParseCommand(std::span<u8> buf, std::vector<u8
|
||||
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] {
|
||||
Error state_error;
|
||||
if (!VMManager::LoadStateFromSlot(slot, false, &state_error))
|
||||
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
|
||||
state_error.GetDescription(), Host::OSD_INFO_DURATION);
|
||||
SaveState_ReportLoadErrorOSD(state_error.GetDescription(), slot, false);
|
||||
});
|
||||
buf_cnt += 1;
|
||||
break;
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/ZipHelpers.h"
|
||||
|
||||
#include "IconsFontAwesome6.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <csetjmp>
|
||||
@ -1231,3 +1232,24 @@ bool SaveState_UnzipFromDisk(const std::string& filename, Error* error)
|
||||
PostLoadPrep();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SaveState_ReportLoadErrorOSD(const std::string& message, std::optional<s32> slot, bool backup)
|
||||
{
|
||||
std::string full_message;
|
||||
if (slot.has_value())
|
||||
{
|
||||
if (backup)
|
||||
full_message = fmt::format(
|
||||
TRANSLATE_FS("SaveState", "Failed to load state from slot {}: {}"), *slot, message);
|
||||
else
|
||||
full_message = fmt::format(
|
||||
TRANSLATE_FS("SaveState", "Failed to load state from backup slot {}: {}"), *slot, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
full_message = fmt::format(TRANSLATE_FS("SaveState", "Failed to load state: {}"), message);
|
||||
}
|
||||
|
||||
Host::AddIconOSDMessage("LoadState", ICON_FA_TRIANGLE_EXCLAMATION,
|
||||
full_message, Host::OSD_WARNING_DURATION);
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -353,3 +354,5 @@ public:
|
||||
void FreezeMem(void* data, int size) override;
|
||||
bool IsSaving() const override { return false; }
|
||||
};
|
||||
|
||||
void SaveState_ReportLoadErrorOSD(const std::string& message, std::optional<s32> slot, bool backup);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user