Maelstrom: Added binding of UI elements to preferences so we can automatically save/restore UI state to preferences.

https://github.com/libsdl-org/Maelstrom/commit/2e2ff5fb69af2963203fa18fb30d98b9927fe045

From 2e2ff5fb69af2963203fa18fb30d98b9927fe045 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 5 Nov 2011 00:08:43 -0400
Subject: [PATCH] Added binding of UI elements to preferences so we can
 automatically save/restore UI state to preferences. This was tested with the
 basic UI element label, checkboxes and editboxes.

---
 MaelstromUI.cpp                 |  2 +-
 MaelstromUI.h                   |  2 +-
 init.cpp                        |  2 +-
 netlogic/game.cpp               | 23 ++++++++++----------
 screenlib/UIBaseElement.cpp     | 16 ++++++++++++++
 screenlib/UIBaseElement.h       |  5 +++++
 screenlib/UIDialog.cpp          |  2 +-
 screenlib/UIElement.cpp         | 26 ++++++++++++++++++++++-
 screenlib/UIElement.h           |  8 +++++--
 screenlib/UIElementCheckbox.cpp | 30 +++++++++++++++++++++++++-
 screenlib/UIElementCheckbox.h   |  6 ++++++
 screenlib/UIManager.cpp         |  3 ++-
 screenlib/UIManager.h           |  7 ++++++-
 screenlib/UIPanel.cpp           | 17 +++++++++++----
 screenlib/UIPanel.h             |  5 ++++-
 utils/prefs.cpp                 | 37 ++++++++++++++++++++++++++++++++-
 utils/prefs.h                   | 10 ++++++++-
 17 files changed, 172 insertions(+), 29 deletions(-)

diff --git a/MaelstromUI.cpp b/MaelstromUI.cpp
index a9235f8e..00b7401c 100644
--- a/MaelstromUI.cpp
+++ b/MaelstromUI.cpp
@@ -50,7 +50,7 @@ hash_nuke_string_text(const void *key, const void *value, void *data)
 	fontserv->FreeText((SDL_Texture *)value);
 }
 
-MaelstromUI::MaelstromUI(FrameBuf *screen) : UIManager(screen)
+MaelstromUI::MaelstromUI(FrameBuf *screen, Prefs *prefs) : UIManager(screen, prefs)
 {
 	/* Create our font hashtables */
 	m_fonts = hash_create(screen, hash_hash_string, hash_keymatch_string, hash_nuke_string_font);
diff --git a/MaelstromUI.h b/MaelstromUI.h
index 960df50e..10277c4a 100644
--- a/MaelstromUI.h
+++ b/MaelstromUI.h
@@ -30,7 +30,7 @@ class HashTable;
 class MaelstromUI : public UIManager
 {
 public:
-	MaelstromUI(FrameBuf *screen);
+	MaelstromUI(FrameBuf *screen, Prefs *prefs);
 	virtual ~MaelstromUI();
 
 	//
diff --git a/init.cpp b/init.cpp
index 99193e42..d7e372ce 100644
--- a/init.cpp
+++ b/init.cpp
@@ -758,7 +758,7 @@ int DoInitializations(Uint32 window_flags, Uint32 render_flags)
 	}
 
 	/* Create the UI manager */
-	ui = new MaelstromUI(screen);
+	ui = new MaelstromUI(screen, prefs);
 
 	/* -- We want to access the FULL screen! */
 	SetRect(&gScrnRect, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
diff --git a/netlogic/game.cpp b/netlogic/game.cpp
index 5ed528db..b52a82cf 100644
--- a/netlogic/game.cpp
+++ b/netlogic/game.cpp
@@ -32,7 +32,7 @@
 #define PREFERENCES_HANDLE "Handle"
 
 // Global variables set in this file...
-int	gScore;
+Uint32	gScore;
 int	gGameOn;
 int	gPaused;
 int	gWave;
@@ -908,27 +908,27 @@ static void DoGameOver(void)
 					(gNumPlayers == 1) && !gDeathMatch ) {
 		sound->PlaySound(gBonusShot, 5);
 
-		/* Get the previously used handle, if possible */
-		const char *text = prefs->GetString(PREFERENCES_HANDLE);
-		if (text) {
-			SDL_strlcpy(handle, text, sizeof(handle));
-		} else {
-			*handle = '\0';
-		}
-		chars_in_handle = SDL_strlen(handle);
-
 		/* -- Let them enter their name */
+		const char *text = NULL;
 		label = panel->GetElement<UIElement>("name_label");
 		if (label) {
 			label->Show();
 		}
 		label = panel->GetElement<UIElement>("name");
 		if (label) {
-			label->SetText(handle);
+			text = label->GetText();
 			label->Show();
 		}
 		ui->Draw();
 
+		/* Get the previously used handle, if possible */
+		if (text) {
+			SDL_strlcpy(handle, text, sizeof(handle));
+		} else {
+			*handle = '\0';
+		}
+		chars_in_handle = SDL_strlen(handle);
+
 		while ( screen->PollEvent(&event) ) /* Loop, flushing events */;
 		SDL_StartTextInput();
 		while ( label && !done ) {
@@ -978,7 +978,6 @@ static void DoGameOver(void)
 			hScores[which].wave = gWave;
 			hScores[which].score = OurShip->GetScore();
 			strcpy(hScores[which].name, handle);
-			prefs->SetString(PREFERENCES_HANDLE, handle);
 		}
 
 		sound->HaltSound();
diff --git a/screenlib/UIBaseElement.cpp b/screenlib/UIBaseElement.cpp
index 028e9124..889cd435 100644
--- a/screenlib/UIBaseElement.cpp
+++ b/screenlib/UIBaseElement.cpp
@@ -96,6 +96,22 @@ UIBaseElement::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
 	return true;
 }
 
+void
+UIBaseElement::LoadData(Prefs *prefs)
+{
+	for (int i = 0; i < m_elements.length(); ++i) {
+		m_elements[i]->LoadData(prefs);
+	}
+}
+
+void
+UIBaseElement::SaveData(Prefs *prefs)
+{
+	for (int i = 0; i < m_elements.length(); ++i) {
+		m_elements[i]->SaveData(prefs);
+	}
+}
+
 UIArea *
 UIBaseElement::GetAnchorElement(const char *name)
 {
diff --git a/screenlib/UIBaseElement.h b/screenlib/UIBaseElement.h
index 91559288..329d28b9 100644
--- a/screenlib/UIBaseElement.h
+++ b/screenlib/UIBaseElement.h
@@ -24,6 +24,7 @@
 
 #include "../utils/array.h"
 #include "../utils/rapidxml.h"
+#include "../utils/prefs.h"
 
 #include "SDL.h"
 #include "UIArea.h"
@@ -64,6 +65,10 @@ class UIBaseElement : public UIArea
 		return true;
 	}
 
+	// Bind any preferences variables to the preferences manager
+	virtual void LoadData(Prefs *prefs);
+	virtual void SaveData(Prefs *prefs);
+
 	virtual UIArea *GetAnchorElement(const char *name);
 
 	void AddElement(UIBaseElement *element) {
diff --git a/screenlib/UIDialog.cpp b/screenlib/UIDialog.cpp
index 93882e34..49028f4e 100644
--- a/screenlib/UIDialog.cpp
+++ b/screenlib/UIDialog.cpp
@@ -50,7 +50,7 @@ UIDialog::Show()
 void
 UIDialog::Hide()
 {
-	UIPanel::Hide();
+	UIPanel::Hide(m_status > 0);
 
 	if (m_handleDone) {
 		m_handleDone(this, m_status);
diff --git a/screenlib/UIElement.cpp b/screenlib/UIElement.cpp
index a084cc40..0a248c86 100644
--- a/screenlib/UIElement.cpp
+++ b/screenlib/UIElement.cpp
@@ -58,6 +58,7 @@ UIElement::UIElement(UIBaseElement *parent, const char *name, UIDrawEngine *draw
 	m_fontSize = 0;
 	m_fontStyle = UIFONT_STYLE_NORMAL;
 	m_text = NULL;
+	m_textBinding = NULL;
 	m_textShadowOffsetX = 0;
 	m_textShadowOffsetY = 0;
 	m_textShadowColor = m_screen->MapRGB(0x00, 0x00, 0x00);
@@ -81,6 +82,9 @@ UIElement::~UIElement()
 	if (m_text) {
 		SDL_free(m_text);
 	}
+	if (m_textBinding) {
+		SDL_free(m_textBinding);
+	}
 	if (m_image) {
 		m_screen->FreeImage(m_image);
 	}
@@ -133,6 +137,8 @@ UIElement::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
 		SetText(attr->value());
 	}
 
+	LoadString(node, "bindText", m_textBinding);
+
 	child = node->first_node("TextArea", 0, false);
 	if (child) {
 		if (!m_textArea.Load(child)) {
@@ -193,7 +199,25 @@ UIElement::FinishLoading()
 	if (m_drawEngine) {
 		m_drawEngine->OnLoad();
 	}
-	return true;
+	return UIBaseElement::FinishLoading();
+}
+
+void
+UIElement::LoadData(Prefs *prefs)
+{
+	if (m_textBinding) {
+		SetText(prefs->GetString(m_textBinding, GetText()));
+	}
+	UIBaseElement::LoadData(prefs);
+}
+
+void
+UIElement::SaveData(Prefs *prefs)
+{
+	if (m_textBinding) {
+		prefs->SetString(m_textBinding, GetText());
+	}
+	UIBaseElement::SaveData(prefs);
 }
 
 bool
diff --git a/screenlib/UIElement.h b/screenlib/UIElement.h
index 7acbd906..f70032c5 100644
--- a/screenlib/UIElement.h
+++ b/screenlib/UIElement.h
@@ -26,7 +26,6 @@
 #include "UIDrawEngine.h"
 #include "UIFontInterface.h"
 
-
 class UIClickDelegate
 {
 public:
@@ -48,6 +47,10 @@ DECLARE_TYPESAFE_CLASS(UIBaseElement)
 	override bool Load(rapidxml::xml_node<> *node, const UITemplates *templates);
 	override bool FinishLoading();
 
+	// Bind any preferences variables to the preferences manager
+	override void LoadData(Prefs *prefs);
+	override void SaveData(Prefs *prefs);
+
 	// Set the draw engine for this element
 	// This should be called before Load() so the draw engine can load too.
 	// Once set, the element owns the draw engine and will free it.
@@ -98,7 +101,7 @@ DECLARE_TYPESAFE_CLASS(UIBaseElement)
 		return m_fontStyle;
 	}
 
-	void SetText(const char *text);
+	virtual void SetText(const char *text);
 	const char *GetText() const {
 		return m_text;
 	}
@@ -151,6 +154,7 @@ DECLARE_TYPESAFE_CLASS(UIBaseElement)
 	int m_fontSize;
 	UIFontStyle m_fontStyle;
 	char *m_text;
+	char *m_textBinding;
 	UIArea m_textArea;
 	int m_textShadowOffsetX;
 	int m_textShadowOffsetY;
diff --git a/screenlib/UIElementCheckbox.cpp b/screenlib/UIElementCheckbox.cpp
index 8550cebd..a4b44189 100644
--- a/screenlib/UIElementCheckbox.cpp
+++ b/screenlib/UIElementCheckbox.cpp
@@ -28,6 +28,14 @@ UIElementCheckbox::UIElementCheckbox(UIBaseElement *parent, const char *name, UI
 	UIElementButton(parent, name, drawEngine)
 {
 	m_checked = false;
+	m_valueBinding = NULL;
+}
+
+UIElementCheckbox::~UIElementCheckbox()
+{
+	if (m_valueBinding) {
+		SDL_free(m_valueBinding);
+	}
 }
 
 bool
@@ -43,6 +51,8 @@ UIElementCheckbox::Load(rapidxml::xml_node<> *node, const UITemplates *templates
 		SetChecked(checked);
 	}
 
+	LoadString(node, "bindValue", m_valueBinding);
+
 	return true;
 }
 
@@ -55,7 +65,25 @@ UIElementCheckbox::FinishLoading()
 	} else {
 		assert(!"Need code for labels on the left");
 	}
-	return true;
+	return UIElementButton::FinishLoading();
+}
+
+void
+UIElementCheckbox::LoadData(Prefs *prefs)
+{
+	if (m_valueBinding) {
+		SetChecked(prefs->GetBool(m_valueBinding, IsChecked()));
+	}
+	UIElementButton::LoadData(prefs);
+}
+
+void
+UIElementCheckbox::SaveData(Prefs *prefs)
+{
+	if (m_valueBinding) {
+		prefs->SetBool(m_valueBinding, IsChecked());
+	}
+	UIElementButton::SaveData(prefs);
 }
 
 void
diff --git a/screenlib/UIElementCheckbox.h b/screenlib/UIElementCheckbox.h
index 8ada094c..f1d2e23f 100644
--- a/screenlib/UIElementCheckbox.h
+++ b/screenlib/UIElementCheckbox.h
@@ -30,10 +30,15 @@ class UIElementCheckbox : public UIElementButton
 DECLARE_TYPESAFE_CLASS(UIElementButton)
 public:
 	UIElementCheckbox(UIBaseElement *parent, const char *name, UIDrawEngine *drawEngine);
+	virtual ~UIElementCheckbox();
 
 	override bool Load(rapidxml::xml_node<> *node, const UITemplates *templates);
 	override bool FinishLoading();
 
+	// Bind any preferences variables to the preferences manager
+	override void LoadData(Prefs *prefs);
+	override void SaveData(Prefs *prefs);
+
 	void SetChecked(bool checked) {
 		if (checked != m_checked) {
 			m_checked = checked;
@@ -52,6 +57,7 @@ DECLARE_TYPESAFE_CLASS(UIElementButton)
 
 protected:
 	bool m_checked;
+	char *m_valueBinding;
 };
 
 #endif // _UIElementCheckbox_h
diff --git a/screenlib/UIManager.cpp b/screenlib/UIManager.cpp
index 5ea0a277..aa348b07 100644
--- a/screenlib/UIManager.cpp
+++ b/screenlib/UIManager.cpp
@@ -26,10 +26,11 @@
 #include "UIPanel.h"
 
 
-UIManager::UIManager(FrameBuf *screen) :
+UIManager::UIManager(FrameBuf *screen, Prefs *prefs) :
 	UIArea(NULL, screen->Width(), screen->Height())
 {
 	m_screen = screen;
+	m_prefs = prefs;
 	m_loadPath = new char[2];
 	strcpy(m_loadPath, ".");
 }
diff --git a/screenlib/UIManager.h b/screenlib/UIManager.h
index 91cf480f..4df91be5 100644
--- a/screenlib/UIManager.h
+++ b/screenlib/UIManager.h
@@ -33,16 +33,20 @@
 class FrameBuf;
 class UIBaseElement;
 class UIElement;
+class Prefs;
 
 class UIManager : public UIArea, public UIFontInterface, public UISoundInterface
 {
 public:
-	UIManager(FrameBuf *screen);
+	UIManager(FrameBuf *screen, Prefs *prefs);
 	virtual ~UIManager();
 
 	FrameBuf *GetScreen() const {
 		return m_screen;
 	}
+	Prefs *GetPrefs() const {
+		return m_prefs;
+	}
 	const UITemplates *GetTemplates() const {
 		return &m_templates;
 	}
@@ -102,6 +106,7 @@ class UIManager : public UIArea, public UIFontInterface, public UISoundInterface
 
 protected:
 	FrameBuf *m_screen;
+	Prefs *m_prefs;
 	char *m_loadPath;
 	UITemplates m_templates;
 	array<UIPanel *> m_panels;
diff --git a/screenlib/UIPanel.cpp b/screenlib/UIPanel.cpp
index 39e50d68..0db30d0a 100644
--- a/screenlib/UIPanel.cpp
+++ b/screenlib/UIPanel.cpp
@@ -68,10 +68,11 @@ bool
 UIPanel::FinishLoading()
 {
 	if (m_delegate) {
-		return m_delegate->OnLoad();
-	} else {
-		return true;
+		if (!m_delegate->OnLoad()) {
+			return false;
+		}
 	}
+	return UIBaseElement::FinishLoading();
 }
 
 void
@@ -91,6 +92,9 @@ UIPanel::Show()
 		m_ui->PlaySound(m_enterSound);
 	}
 
+	// Load data from preferences
+	LoadData(GetUI()->GetPrefs());
+
 	UIBaseElement::Show();
 
 	if (m_delegate) {
@@ -99,7 +103,7 @@ UIPanel::Show()
 }
 
 void
-UIPanel::Hide()
+UIPanel::Hide(bool saveData)
 {
 	if (m_leaveSound) {
 		m_ui->PlaySound(m_leaveSound);
@@ -107,6 +111,11 @@ UIPanel::Hide()
 
 	UIBaseElement::Hide();
 
+	// Save data to preferences
+	if (saveData) {
+		SaveData(GetUI()->GetPrefs());
+	}
+
 	if (m_delegate) {
 		m_delegate->OnHide();
 	}
diff --git a/screenlib/UIPanel.h b/screenlib/UIPanel.h
index 0a1b7cf5..8509c212 100644
--- a/screenlib/UIPanel.h
+++ b/screenlib/UIPanel.h
@@ -69,7 +69,10 @@ DECLARE_TYPESAFE_CLASS(UIBaseElement)
 	void SetPanelDelegate(UIPanelDelegate *delegate, bool autodelete = true);
 
 	override void Show();
-	override void Hide();
+	override void Hide() {
+		Hide(true);
+	}
+	void Hide(bool saveData);
 
 	void HideAll();
 
diff --git a/utils/prefs.cpp b/utils/prefs.cpp
index 112069bf..bf2c5e81 100644
--- a/utils/prefs.cpp
+++ b/utils/prefs.cpp
@@ -137,7 +137,17 @@ Prefs::Save()
 void
 Prefs::SetString(const char *key, const char *value)
 {
-	hash_remove(m_values, key);
+	const char *lastValue;
+
+	if (!value) {
+		value = "";
+	}
+	if (hash_find(m_values, key, (const void **)&lastValue)) {
+		if (SDL_strcmp(lastValue, value) == 0) {
+			return;
+		}
+		hash_remove(m_values, key);
+	}
 	hash_insert(m_values, SDL_strdup(key), SDL_strdup(value));
 }
 
@@ -150,6 +160,16 @@ Prefs::SetNumber(const char *key, int value)
 	SetString(key, buf);
 }
 
+void
+Prefs::SetBool(const char *key, bool value)
+{
+	if (value) {
+		SetString(key, "true");
+	} else {
+		SetString(key, "false");
+	}
+}
+
 const char *
 Prefs::GetString(const char *key, const char *defaultValue)
 {
@@ -171,3 +191,18 @@ Prefs::GetNumber(const char *key, int defaultValue)
 	}
 	return defaultValue;
 }
+
+bool
+Prefs::GetBool(const char *key, bool defaultValue)
+{
+	const char *value;
+
+	if (hash_find(m_values, key, (const void **)&value)) {
+		if (*value == '1' || *value == 't' || *value == 'T') {
+			return true;
+		} else if (*value == '0' || *value == 'f' || *value == 'F') {
+			return false;
+		}
+	}
+	return defaultValue;
+}
diff --git a/utils/prefs.h b/utils/prefs.h
index d11ec26a..8baf1d33 100644
--- a/utils/prefs.h
+++ b/utils/prefs.h
@@ -32,21 +32,29 @@ class Prefs
 
 	void SetString(const char *key, const char *value);
 	void SetNumber(const char *key, int value);
+	void SetBool(const char *key, bool value);
 	void Set(const char *key, const char *value) {
 		SetString(key, value);
 	}
 	void Set(const char *key, int value) {
 		SetNumber(key, value);
 	}
+	void Set(const char *key, bool value) {
+		SetBool(key, value);
+	}
 
 	const char *GetString(const char *key, const char *defaultValue = 0);
 	int GetNumber(const char *key, int defaultValue = 0);
+	bool GetBool(const char *key, bool defaultValue = false);
 	void Get(const char *key, const char *&value, const char *defaultValue) {
 		value = GetString(key, defaultValue);
 	}
 	void Get(const char *key, int &value, int defaultValue) {
 		value = GetNumber(key, defaultValue);
 	}
+	void Get(const char *key, bool &value, bool defaultValue) {
+		value = GetBool(key, defaultValue);
+	}
 
 protected:
 	char *m_file;
@@ -75,7 +83,7 @@ class PrefsVariable
 		return *this = rhs.m_value;
 	}
 
-	operator T() {
+	operator const T() const {
 		return m_value;
 	}