Qt: Implement save state menus

This commit is contained in:
Connor McLaughlin 2020-02-16 00:14:04 +09:00
parent 97ea851097
commit 1ce1e016ae
4 changed files with 142 additions and 61 deletions

View File

@ -22,7 +22,6 @@ MainWindow::MainWindow(QtHostInterface* host_interface) : QMainWindow(nullptr),
m_ui.setupUi(this); m_ui.setupUi(this);
setupAdditionalUi(); setupAdditionalUi();
connectSignals(); connectSignals();
populateLoadSaveStateMenus(QString());
resize(750, 690); 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) 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()) if (game_title.isEmpty())
setWindowTitle(tr("DuckStation")); setWindowTitle(tr("DuckStation"));
else else
@ -367,14 +366,16 @@ void MainWindow::connectSignals()
if (!entry) if (!entry)
{ {
m_ui.statusBar->clearMessage(); m_ui.statusBar->clearMessage();
populateLoadSaveStateMenus(QString()); m_host_interface->populateSaveStateMenus("", m_ui.menuLoadState, m_ui.menuSaveState);
return; return;
} }
m_ui.statusBar->showMessage(QString::fromStdString(entry->path)); 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.actionDebugShowVRAM, "Debug/ShowVRAM");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugDumpCPUtoVRAMCopies, SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.actionDebugDumpCPUtoVRAMCopies,
"Debug/DumpCPUToVRAMCopies"); "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) void MainWindow::closeEvent(QCloseEvent* event)
{ {
m_host_interface->powerOffSystem(true, true); m_host_interface->powerOffSystem(true, true);

View File

@ -54,7 +54,6 @@ private:
void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count); void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count);
void updateDebugMenuCPUExecutionMode(); void updateDebugMenuCPUExecutionMode();
void updateDebugMenuGPURenderer(); void updateDebugMenuGPURenderer();
void populateLoadSaveStateMenus(QString game_code);
Ui::MainWindow m_ui; Ui::MainWindow m_ui;

View File

@ -11,8 +11,10 @@
#include "qtsettingsinterface.h" #include "qtsettingsinterface.h"
#include "qtutils.h" #include "qtutils.h"
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QEventLoop> #include <QtCore/QEventLoop>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
#include <memory> #include <memory>
Log_SetChannel(QtHostInterface); Log_SetChannel(QtHostInterface);
@ -354,15 +356,22 @@ std::vector<QtHostInterface::HotkeyInfo> QtHostInterface::getHotkeyList() const
{QStringLiteral("DecreaseResolutionScale"), QStringLiteral("Decrease Resolution Scale"), {QStringLiteral("DecreaseResolutionScale"), QStringLiteral("Decrease Resolution Scale"),
QStringLiteral("Graphics")}}; 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( const bool global = ConvertToBoolUnchecked(global_i);
{QStringLiteral("LoadState%1").arg(i), QStringLiteral("Load State %1").arg(i), QStringLiteral("Save States")}); const u32 count = global ? GLOBAL_SAVE_STATE_SLOTS : PER_GAME_SAVE_STATE_SLOTS;
} for (u32 i = 1; i <= count; i++)
for (u32 i = 1; i <= NUM_SAVE_STATE_HOTKEYS; i++) {
{ hotkeys.push_back({QStringLiteral("Load%1State%2").arg(global ? "Global" : "Game").arg(i),
hotkeys.push_back( QStringLiteral("Load %1 State %2").arg(global ? tr("Global") : tr("Game")).arg(i),
{QStringLiteral("SaveState%1").arg(i), QStringLiteral("Save State %1").arg(i), QStringLiteral("Save States")}); 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; return hotkeys;
@ -408,17 +417,21 @@ void QtHostInterface::updateHotkeyInputMap()
ModifyResolutionScale(-1); 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) { const bool global = ConvertToBoolUnchecked(global_i);
if (!pressed) const u32 count = global ? GLOBAL_SAVE_STATE_SLOTS : PER_GAME_SAVE_STATE_SLOTS;
HostInterface::LoadState(StringUtil::StdStringFromFormat("savestate_%u.bin", i).c_str()); for (u32 slot = 1; slot <= count; slot++)
}); {
hk(QStringLiteral("Load%1State%2").arg(global ? "Global" : "Game").arg(slot), [this, global, slot](bool pressed) {
hk(QStringLiteral("SaveState%1").arg(i), [this, i](bool pressed) { if (!pressed)
if (!pressed) loadState(global, slot);
HostInterface::SaveState(StringUtil::StdStringFromFormat("savestate_%u.bin", i).c_str()); });
}); 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<SaveStateInfo> 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<qint64>(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() void QtHostInterface::createThread()
{ {
m_original_thread = QThread::currentThread(); m_original_thread = QThread::currentThread();

View File

@ -16,6 +16,7 @@
class ByteStream; class ByteStream;
class QEventLoop; class QEventLoop;
class QMenu;
class QWidget; class QWidget;
class GameList; class GameList;
@ -61,6 +62,8 @@ public:
}; };
std::vector<HotkeyInfo> getHotkeyList() const; std::vector<HotkeyInfo> getHotkeyList() const;
void populateSaveStateMenus(const char* game_code, QMenu* load_menu, QMenu* save_menu);
Q_SIGNALS: Q_SIGNALS:
void errorReported(QString message); void errorReported(QString message);
void messageReported(QString message); void messageReported(QString message);
@ -81,6 +84,9 @@ public Q_SLOTS:
void resetSystem(); void resetSystem();
void pauseSystem(bool paused); void pauseSystem(bool paused);
void changeDisc(QString new_disc_filename); 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: private Q_SLOTS:
void doStopThread(); void doStopThread();
@ -97,11 +103,6 @@ protected:
private: private:
using InputButtonHandler = std::function<void(bool)>; using InputButtonHandler = std::function<void(bool)>;
enum : u32
{
NUM_SAVE_STATE_HOTKEYS = 8
};
class Thread : public QThread class Thread : public QThread
{ {
public: public: