From fdf2ce299c8666486ac539d5750351f2ce62a670 Mon Sep 17 00:00:00 2001
From: Jeffrey Pfau <jeffrey@endrift.com>
Date: Tue, 14 Oct 2014 23:15:15 -0700
Subject: [PATCH] Add savestate load/saving in Qt frontend

---
 src/platform/qt/CMakeLists.txt    |   2 +
 src/platform/qt/GameController.h  |   1 +
 src/platform/qt/LoadSaveState.cpp |  76 +++++++
 src/platform/qt/LoadSaveState.h   |  42 ++++
 src/platform/qt/LoadSaveState.ui  | 321 ++++++++++++++++++++++++++++++
 src/platform/qt/Window.cpp        |  22 ++
 src/platform/qt/Window.h          |   3 +
 7 files changed, 467 insertions(+)
 create mode 100644 src/platform/qt/LoadSaveState.cpp
 create mode 100644 src/platform/qt/LoadSaveState.h
 create mode 100644 src/platform/qt/LoadSaveState.ui

diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt
index 802367ad6..2f5daa9e1 100644
--- a/src/platform/qt/CMakeLists.txt
+++ b/src/platform/qt/CMakeLists.txt
@@ -27,11 +27,13 @@ set(SOURCE_FILES
 	AudioProcessor.cpp
 	Display.cpp
 	GameController.cpp
+	LoadSaveState.cpp
 	LogView.cpp
 	Window.cpp
 	VFileDevice.cpp)
 
 qt5_wrap_ui(UI_FILES
+	LoadSaveState.ui
 	LogView.ui)
 
 if(USE_GDB_STUB)
diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h
index d44cb6d6b..e168ef49c 100644
--- a/src/platform/qt/GameController.h
+++ b/src/platform/qt/GameController.h
@@ -31,6 +31,7 @@ public:
 	~GameController();
 
 	const uint32_t* drawContext() const { return m_drawContext; }
+	GBAThread* thread() { return &m_threadContext; }
 
 	bool isPaused();
 
diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp
new file mode 100644
index 000000000..770fd2566
--- /dev/null
+++ b/src/platform/qt/LoadSaveState.cpp
@@ -0,0 +1,76 @@
+#include "LoadSaveState.h"
+
+#include "GameController.h"
+#include "VFileDevice.h"
+
+extern "C" {
+#include "gba-serialize.h"
+#include "gba-video.h"
+}
+
+using namespace QGBA;
+
+LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
+	: QWidget(parent)
+	, m_controller(controller)
+{
+	m_ui.setupUi(this);
+
+	QImage currentImage(reinterpret_cast<const uchar*>(controller->drawContext()), VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 1024, QImage::Format_RGB32);
+	m_currentImage.convertFromImage(currentImage.rgbSwapped());
+
+	m_slots[0] = m_ui.state1;
+	m_slots[1] = m_ui.state2;
+	m_slots[2] = m_ui.state3;
+	m_slots[3] = m_ui.state4;
+	m_slots[4] = m_ui.state5;
+	m_slots[5] = m_ui.state6;
+	m_slots[6] = m_ui.state7;
+	m_slots[7] = m_ui.state8;
+	m_slots[8] = m_ui.state9;
+
+	int i;
+	for (i = 0; i < NUM_SLOTS; ++i) {
+		loadState(i);
+		connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i); });
+	}
+}
+
+void LoadSaveState::setMode(LoadSave mode) {
+	m_mode = mode;
+	QString text = mode == LoadSave::LOAD ? tr("Load State") : tr("SaveState");
+	setWindowTitle(text);
+	m_ui.lsLabel->setText(text);
+}
+
+void LoadSaveState::loadState(int slot) {
+	GBAThread* thread = m_controller->thread();
+	VFile* vf = GBAGetState(thread->gba, thread->stateDir, slot, false);
+	if (!vf) {
+		return;
+	}
+	VFileDevice vdev(vf);
+	QImage stateImage;
+	stateImage.load(&vdev, "PNG");
+	if (!stateImage.isNull()) {
+		QPixmap statePixmap;
+		statePixmap.convertFromImage(stateImage);
+		m_slots[slot]->setIcon(statePixmap);
+		m_slots[slot]->setText(QString());
+	} else {
+		m_slots[slot]->setText(tr("Slot %1").arg(slot + 1));
+	}
+	m_slots[slot]->setShortcut(QString::number(slot + 1));
+}
+
+void LoadSaveState::triggerState(int slot) {
+	GBAThread* thread = m_controller->thread();
+	GBAThreadInterrupt(thread);
+	if (m_mode == LoadSave::SAVE) {
+		GBASaveState(thread->gba, thread->stateDir, slot, true);
+	} else {
+		GBALoadState(thread->gba, thread->stateDir, slot);
+	}
+	GBAThreadContinue(thread);
+	close();
+}
diff --git a/src/platform/qt/LoadSaveState.h b/src/platform/qt/LoadSaveState.h
new file mode 100644
index 000000000..20ea0f79a
--- /dev/null
+++ b/src/platform/qt/LoadSaveState.h
@@ -0,0 +1,42 @@
+#ifndef QGBA_LOAD_SAVE_STATE
+#define QGBA_LOAD_SAVE_STATE
+
+#include <QWidget>
+
+#include "ui_LoadSaveState.h"
+
+namespace QGBA {
+
+class GameController;
+
+enum class LoadSave {
+	LOAD,
+	SAVE
+};
+
+class LoadSaveState : public QWidget {
+Q_OBJECT
+
+public:
+
+	const static int NUM_SLOTS = 9;
+
+	LoadSaveState(GameController* controller, QWidget* parent = nullptr);
+
+	void setMode(LoadSave mode);
+
+private:
+	void loadState(int slot);
+	void triggerState(int slot);
+
+	Ui::LoadSaveState m_ui;
+	GameController* m_controller;
+	QPushButton* m_slots[NUM_SLOTS];
+	LoadSave m_mode;
+
+	QPixmap m_currentImage;
+};
+
+}
+
+#endif
diff --git a/src/platform/qt/LoadSaveState.ui b/src/platform/qt/LoadSaveState.ui
new file mode 100644
index 000000000..a456eeb71
--- /dev/null
+++ b/src/platform/qt/LoadSaveState.ui
@@ -0,0 +1,321 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LoadSaveState</class>
+ <widget class="QWidget" name="LoadSaveState">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>760</width>
+    <height>560</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>%1 State</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_2" rowstretch="1,0,0,0" columnstretch="0,0,0">
+   <property name="leftMargin">
+    <number>6</number>
+   </property>
+   <property name="topMargin">
+    <number>6</number>
+   </property>
+   <property name="rightMargin">
+    <number>6</number>
+   </property>
+   <property name="bottomMargin">
+    <number>6</number>
+   </property>
+   <property name="spacing">
+    <number>2</number>
+   </property>
+   <item row="1" column="0">
+    <widget class="QPushButton" name="state1">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>242</width>
+       <height>162</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>No Save</string>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>240</width>
+       <height>160</height>
+      </size>
+     </property>
+     <property name="shortcut">
+      <string>1</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="QPushButton" name="state2">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>242</width>
+       <height>162</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>No Save</string>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>240</width>
+       <height>160</height>
+      </size>
+     </property>
+     <property name="shortcut">
+      <string>2</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="0" colspan="3">
+    <widget class="QLabel" name="lsLabel">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="styleSheet">
+      <string notr="true">font-size: 30pt; font-weight: bold;</string>
+     </property>
+     <property name="text">
+      <string>%1 State</string>
+     </property>
+     <property name="scaledContents">
+      <bool>false</bool>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="2">
+    <widget class="QPushButton" name="state3">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>242</width>
+       <height>162</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>No Save</string>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>240</width>
+       <height>160</height>
+      </size>
+     </property>
+     <property name="shortcut">
+      <string>3</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QPushButton" name="state4">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>242</width>
+       <height>162</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>No Save</string>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>240</width>
+       <height>160</height>
+      </size>
+     </property>
+     <property name="shortcut">
+      <string>4</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QPushButton" name="state5">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>242</width>
+       <height>162</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>No Save</string>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>240</width>
+       <height>160</height>
+      </size>
+     </property>
+     <property name="shortcut">
+      <string>5</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="2">
+    <widget class="QPushButton" name="state6">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>242</width>
+       <height>162</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>No Save</string>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>240</width>
+       <height>160</height>
+      </size>
+     </property>
+     <property name="shortcut">
+      <string>6</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0">
+    <widget class="QPushButton" name="state7">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>242</width>
+       <height>162</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>No Save</string>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>240</width>
+       <height>160</height>
+      </size>
+     </property>
+     <property name="shortcut">
+      <string>7</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="1">
+    <widget class="QPushButton" name="state8">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>242</width>
+       <height>162</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>No Save</string>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>240</width>
+       <height>160</height>
+      </size>
+     </property>
+     <property name="shortcut">
+      <string>8</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="2">
+    <widget class="QPushButton" name="state9">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>242</width>
+       <height>162</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>No Save</string>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>240</width>
+       <height>160</height>
+      </size>
+     </property>
+     <property name="shortcut">
+      <string>9</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>state1</tabstop>
+  <tabstop>state2</tabstop>
+  <tabstop>state3</tabstop>
+  <tabstop>state4</tabstop>
+  <tabstop>state5</tabstop>
+  <tabstop>state6</tabstop>
+  <tabstop>state7</tabstop>
+  <tabstop>state8</tabstop>
+  <tabstop>state9</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp
index 0b6bd01bb..ec15b7932 100644
--- a/src/platform/qt/Window.cpp
+++ b/src/platform/qt/Window.cpp
@@ -8,6 +8,7 @@
 #include "GameController.h"
 #include "GDBWindow.h"
 #include "GDBController.h"
+#include "LoadSaveState.h"
 #include "LogView.h"
 
 using namespace QGBA;
@@ -143,6 +144,14 @@ void Window::gameStopped() {
 	}
 }
 
+void Window::openStateWindow(LoadSave ls) {
+	LoadSaveState* window = new LoadSaveState(m_controller);
+	window->setAttribute(Qt::WA_DeleteOnClose);
+	connect(this, SIGNAL(shutdown()), window, SLOT(hide()));
+	window->setMode(ls);
+	window->show();
+}
+
 void Window::setupMenu(QMenuBar* menubar) {
 	menubar->clear();
 	QMenu* fileMenu = menubar->addMenu(tr("&File"));
@@ -161,6 +170,19 @@ void Window::setupMenu(QMenuBar* menubar) {
 	emulationMenu->addAction(shutdown);
 	emulationMenu->addSeparator();
 
+	QAction* loadState = new QAction(tr("&Load state"), emulationMenu);
+	loadState->setShortcut(tr("Ctrl+L"));
+	connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); });
+	m_gameActions.append(loadState);
+	emulationMenu->addAction(loadState);
+
+	QAction* saveState = new QAction(tr("&Save state"), emulationMenu);
+	saveState->setShortcut(tr("Ctrl+S"));
+	connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); });
+	m_gameActions.append(saveState);
+	emulationMenu->addAction(saveState);
+	emulationMenu->addSeparator();
+
 	QAction* pause = new QAction(tr("&Pause"), emulationMenu);
 	pause->setChecked(false);
 	pause->setCheckable(true);
diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h
index 9db37b133..1baaf8ad3 100644
--- a/src/platform/qt/Window.h
+++ b/src/platform/qt/Window.h
@@ -9,6 +9,7 @@ extern "C" {
 }
 
 #include "Display.h"
+#include "LoadSaveState.h"
 
 namespace QGBA {
 
@@ -51,6 +52,8 @@ private slots:
 
 private:
 	void setupMenu(QMenuBar*);
+	void openStateWindow(LoadSave);
+
 	GameController* m_controller;
 	Display* m_display;
 	QList<QAction*> m_gameActions;