Qt: Implement save state menus
This commit is contained in:
parent
97ea851097
commit
1ce1e016ae
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue