From 1ce1e016ae20fbd2e386b741427bd9fb8edd8815 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 16 Feb 2020 00:14:04 +0900 Subject: [PATCH] Qt: Implement save state menus --- src/duckstation-qt/mainwindow.cpp | 42 +------ src/duckstation-qt/mainwindow.h | 1 - src/duckstation-qt/qthostinterface.cpp | 149 ++++++++++++++++++++++--- src/duckstation-qt/qthostinterface.h | 11 +- 4 files changed, 142 insertions(+), 61 deletions(-) diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 3fa9beef5..6bd691226 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -22,7 +22,6 @@ MainWindow::MainWindow(QtHostInterface* host_interface) : QMainWindow(nullptr), m_ui.setupUi(this); setupAdditionalUi(); connectSignals(); - populateLoadSaveStateMenus(QString()); resize(750, 690); } @@ -136,7 +135,7 @@ void MainWindow::onSystemPerformanceCountersUpdated(float speed, float fps, floa void MainWindow::onRunningGameChanged(QString filename, QString game_code, QString game_title) { - populateLoadSaveStateMenus(game_code); + m_host_interface->populateSaveStateMenus(game_code.toStdString().c_str(), m_ui.menuLoadState, m_ui.menuSaveState); if (game_title.isEmpty()) setWindowTitle(tr("DuckStation")); else @@ -367,14 +366,16 @@ void MainWindow::connectSignals() if (!entry) { m_ui.statusBar->clearMessage(); - populateLoadSaveStateMenus(QString()); + m_host_interface->populateSaveStateMenus("", m_ui.menuLoadState, m_ui.menuSaveState); return; } m_ui.statusBar->showMessage(QString::fromStdString(entry->path)); - populateLoadSaveStateMenus(QString::fromStdString(entry->code)); + m_host_interface->populateSaveStateMenus(entry->code.c_str(), m_ui.menuLoadState, m_ui.menuSaveState); }); + m_host_interface->populateSaveStateMenus(nullptr, m_ui.menuLoadState, m_ui.menuSaveState); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugShowVRAM, "Debug/ShowVRAM"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugDumpCPUtoVRAMCopies, "Debug/DumpCPUToVRAMCopies"); @@ -443,39 +444,6 @@ void MainWindow::updateDebugMenuGPURenderer() } } -void MainWindow::populateLoadSaveStateMenus(QString game_code) -{ - static constexpr int NUM_SAVE_STATE_SLOTS = 10; - - QMenu* const load_menu = m_ui.menuLoadState; - QMenu* const save_menu = m_ui.menuSaveState; - - load_menu->clear(); - save_menu->clear(); - - load_menu->addAction(tr("Resume State")); - load_menu->addSeparator(); - - for (int i = 0; i < NUM_SAVE_STATE_SLOTS; i++) - { - // TODO: Do we want to test for the existance of these on disk here? - load_menu->addAction(tr("Global Save %1 (2020-01-01 00:01:02)").arg(i + 1)); - save_menu->addAction(tr("Global Save %1").arg(i + 1)); - } - - if (!game_code.isEmpty()) - { - load_menu->addSeparator(); - save_menu->addSeparator(); - - for (int i = 0; i < NUM_SAVE_STATE_SLOTS; i++) - { - load_menu->addAction(tr("Game Save %1 (2020-01-01 00:01:02)").arg(i + 1)); - save_menu->addAction(tr("Game Save %1").arg(i + 1)); - } - } -} - void MainWindow::closeEvent(QCloseEvent* event) { m_host_interface->powerOffSystem(true, true); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 758b78cb3..a6f1f92e3 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -54,7 +54,6 @@ private: void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count); void updateDebugMenuCPUExecutionMode(); void updateDebugMenuGPURenderer(); - void populateLoadSaveStateMenus(QString game_code); Ui::MainWindow m_ui; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 35628b442..cef694278 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -11,8 +11,10 @@ #include "qtsettingsinterface.h" #include "qtutils.h" #include +#include #include #include +#include #include #include Log_SetChannel(QtHostInterface); @@ -354,15 +356,22 @@ std::vector QtHostInterface::getHotkeyList() const {QStringLiteral("DecreaseResolutionScale"), QStringLiteral("Decrease Resolution Scale"), QStringLiteral("Graphics")}}; - for (u32 i = 1; i <= NUM_SAVE_STATE_HOTKEYS; i++) + for (u32 global_i = 0; global_i < 2; global_i++) { - hotkeys.push_back( - {QStringLiteral("LoadState%1").arg(i), QStringLiteral("Load State %1").arg(i), QStringLiteral("Save States")}); - } - for (u32 i = 1; i <= NUM_SAVE_STATE_HOTKEYS; i++) - { - hotkeys.push_back( - {QStringLiteral("SaveState%1").arg(i), QStringLiteral("Save State %1").arg(i), QStringLiteral("Save States")}); + const bool global = ConvertToBoolUnchecked(global_i); + const u32 count = global ? GLOBAL_SAVE_STATE_SLOTS : PER_GAME_SAVE_STATE_SLOTS; + for (u32 i = 1; i <= count; i++) + { + hotkeys.push_back({QStringLiteral("Load%1State%2").arg(global ? "Global" : "Game").arg(i), + QStringLiteral("Load %1 State %2").arg(global ? tr("Global") : tr("Game")).arg(i), + QStringLiteral("Save States")}); + } + for (u32 slot = 1; slot <= count; slot++) + { + hotkeys.push_back({QStringLiteral("Save%1State%2").arg(global ? "Global" : "Game").arg(slot), + QStringLiteral("Save %1 State %2").arg(global ? tr("Global") : tr("Game")).arg(slot), + QStringLiteral("Save States")}); + } } return hotkeys; @@ -408,17 +417,21 @@ void QtHostInterface::updateHotkeyInputMap() ModifyResolutionScale(-1); }); - for (u32 i = 1; i <= NUM_SAVE_STATE_HOTKEYS; i++) + for (u32 global_i = 0; global_i < 2; global_i++) { - hk(QStringLiteral("LoadState%1").arg(i), [this, i](bool pressed) { - if (!pressed) - HostInterface::LoadState(StringUtil::StdStringFromFormat("savestate_%u.bin", i).c_str()); - }); - - hk(QStringLiteral("SaveState%1").arg(i), [this, i](bool pressed) { - if (!pressed) - HostInterface::SaveState(StringUtil::StdStringFromFormat("savestate_%u.bin", i).c_str()); - }); + const bool global = ConvertToBoolUnchecked(global_i); + const u32 count = global ? GLOBAL_SAVE_STATE_SLOTS : PER_GAME_SAVE_STATE_SLOTS; + for (u32 slot = 1; slot <= count; slot++) + { + hk(QStringLiteral("Load%1State%2").arg(global ? "Global" : "Game").arg(slot), [this, global, slot](bool pressed) { + if (!pressed) + loadState(global, slot); + }); + hk(QStringLiteral("Save%1State%2").arg(global ? "Global" : "Game").arg(slot), [this, global, slot](bool pressed) { + if (!pressed) + saveState(global, slot); + }); + } } } @@ -555,6 +568,106 @@ void QtHostInterface::createAudioStream() } } +void QtHostInterface::populateSaveStateMenus(const char* game_code, QMenu* load_menu, QMenu* save_menu) +{ + const std::vector available_states(GetAvailableSaveStates(game_code)); + + load_menu->clear(); + if (!available_states.empty()) + { + bool last_global = available_states.front().global; + for (const SaveStateInfo& ssi : available_states) + { + const s32 slot = ssi.slot; + const bool global = ssi.global; + const QDateTime timestamp(QDateTime::fromSecsSinceEpoch(static_cast(ssi.timestamp))); + const QString path(QString::fromStdString(ssi.path)); + + QString title = tr("%1 Save %2 (%3)") + .arg(global ? tr("Global") : tr("Game")) + .arg(slot) + .arg(timestamp.toString(Qt::SystemLocaleShortDate)); + + if (global != last_global) + { + load_menu->addSeparator(); + last_global = global; + } + + QAction* action = load_menu->addAction(title); + connect(action, &QAction::triggered, [this, path]() { loadState(path); }); + } + } + + save_menu->clear(); + if (game_code && std::strlen(game_code) > 0) + { + for (s32 i = 1; i <= PER_GAME_SAVE_STATE_SLOTS; i++) + { + QAction* action = save_menu->addAction(tr("Game Save %1").arg(i)); + connect(action, &QAction::triggered, [this, i]() { saveState(i, false); }); + } + + save_menu->addSeparator(); + } + + for (s32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++) + { + QAction* action = save_menu->addAction(tr("Global Save %1").arg(i)); + connect(action, &QAction::triggered, [this, i]() { saveState(i, true); }); + } +} + +void QtHostInterface::loadState(QString filename) +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "loadState", Qt::QueuedConnection, Q_ARG(QString, filename)); + return; + } + + if (m_system) + LoadState(filename.toStdString().c_str()); + else + doBootSystem(QString(), filename); +} + +void QtHostInterface::loadState(bool global, qint32 slot) +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "loadState", Qt::QueuedConnection, Q_ARG(bool, global), Q_ARG(qint32, slot)); + return; + } + + if (m_system) + { + LoadState(slot, global); + return; + } + + if (!global) + { + // can't load a non-global system without a game code + return; + } + + loadState(QString::fromStdString(GetGlobalSaveStateFileName(slot))); +} + +void QtHostInterface::saveState(bool global, qint32 slot, bool block_until_done /* = false */) +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "saveState", block_until_done ? Qt::BlockingQueuedConnection : Qt::QueuedConnection, + Q_ARG(bool, global), Q_ARG(qint32, slot), Q_ARG(bool, block_until_done)); + return; + } + + if (m_system) + SaveState(global, slot); +} + void QtHostInterface::createThread() { m_original_thread = QThread::currentThread(); diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 337d42776..63d3aa698 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -16,6 +16,7 @@ class ByteStream; class QEventLoop; +class QMenu; class QWidget; class GameList; @@ -61,6 +62,8 @@ public: }; std::vector getHotkeyList() const; + void populateSaveStateMenus(const char* game_code, QMenu* load_menu, QMenu* save_menu); + Q_SIGNALS: void errorReported(QString message); void messageReported(QString message); @@ -81,6 +84,9 @@ public Q_SLOTS: void resetSystem(); void pauseSystem(bool paused); void changeDisc(QString new_disc_filename); + void loadState(QString filename); + void loadState(bool global, qint32 slot); + void saveState(bool global, qint32 slot, bool block_until_done = false); private Q_SLOTS: void doStopThread(); @@ -97,11 +103,6 @@ protected: private: using InputButtonHandler = std::function; - enum : u32 - { - NUM_SAVE_STATE_HOTKEYS = 8 - }; - class Thread : public QThread { public: