Maelstrom: Switched the controls dialog over to the new UI system

https://github.com/libsdl-org/Maelstrom/commit/d2ee1758cb9fb832d003e38d3a4feae38bebef4e

From d2ee1758cb9fb832d003e38d3a4feae38bebef4e Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 29 Oct 2011 18:31:50 -0400
Subject: [PATCH] Switched the controls dialog over to the new UI system

---
 MacDialogCheckbox.cpp        |   4 -
 MacDialogCheckbox.h          |   1 -
 MacDialogRadioButton.cpp     |  84 ++++++++++
 MacDialogRadioButton.h       |  35 ++++
 MaelstromUI.cpp              |   6 +-
 Makefile.am                  |   4 +-
 UI/UITemplates.xml           |   5 +
 UI/controls.xml              |  84 ++++++++++
 controls.cpp                 | 304 ++++++++++++++++-------------------
 controls.h                   |  49 +++++-
 main.cpp                     |   6 +-
 screenlib/UIBaseElement.cpp  |   6 +-
 screenlib/UIDialog.h         |   3 +
 screenlib/UIElementRadio.cpp |  24 ++-
 screenlib/UIElementRadio.h   |   2 +
 screenlib/UIPanel.cpp        |  13 ++
 screenlib/UIPanel.h          |   2 +
 17 files changed, 454 insertions(+), 178 deletions(-)
 create mode 100644 MacDialogRadioButton.cpp
 create mode 100644 MacDialogRadioButton.h
 create mode 100644 UI/controls.xml

diff --git a/MacDialogCheckbox.cpp b/MacDialogCheckbox.cpp
index 46876a69..e91b2a93 100644
--- a/MacDialogCheckbox.cpp
+++ b/MacDialogCheckbox.cpp
@@ -18,10 +18,6 @@ MacDialogCheckbox::MacDialogCheckbox(UIBaseElement *parent, const char *name) :
 	SetSize(CHECKBOX_SIZE, CHECKBOX_SIZE);
 }
 
-MacDialogCheckbox::~MacDialogCheckbox()
-{
-}
-
 bool
 MacDialogCheckbox::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
 {
diff --git a/MacDialogCheckbox.h b/MacDialogCheckbox.h
index eb6bc6ac..34e4a712 100644
--- a/MacDialogCheckbox.h
+++ b/MacDialogCheckbox.h
@@ -8,7 +8,6 @@ class MacDialogCheckbox : public UIElementCheckbox
 {
 public:
 	MacDialogCheckbox(UIBaseElement *parent, const char *name = "");
-	virtual ~MacDialogCheckbox();
 
 	virtual bool IsA(UIElementType type) {
 		return UIElementCheckbox::IsA(type) || type == GetType();
diff --git a/MacDialogRadioButton.cpp b/MacDialogRadioButton.cpp
new file mode 100644
index 00000000..6cd30635
--- /dev/null
+++ b/MacDialogRadioButton.cpp
@@ -0,0 +1,84 @@
+
+#include "screenlib/SDL_FrameBuf.h"
+#include "MacDialogRadioButton.h"
+#include "MacDialogLabel.h"
+
+/* Default checkbox size */
+#define RADIOBUTTON_SIZE	20
+
+UIElementType MacDialogRadioButton::s_elementType;
+
+
+MacDialogRadioButton::MacDialogRadioButton(UIBaseElement *parent, const char *name) :
+	UIElementRadioButton(parent, name)
+{
+	m_color = m_screen->MapRGB(0x00, 0x00, 0x00);
+
+	SetSize(RADIOBUTTON_SIZE, RADIOBUTTON_SIZE);
+}
+
+bool
+MacDialogRadioButton::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
+{
+	rapidxml::xml_attribute<> *attr;
+
+	if (!UIElementRadioButton::Load(node, templates)) {
+		return false;
+	}
+
+	attr = node->first_attribute("text", 0, false);
+	if (attr) {
+		MacDialogLabel *label;
+
+		label = new MacDialogLabel(this, "label");
+		label->SetText(attr->value());
+		label->SetAnchor(TOPLEFT, TOPLEFT, this, 21, 3);
+		AddElement(label);
+
+		/* Extend the sensitive area to encompass the label */
+		SetWidth((label->X()+label->Width()) - X());
+	}
+
+	return true;
+}
+
+void
+MacDialogRadioButton::Draw()
+{
+	int x = X() + 5;
+	int y = Y() + 5;
+
+	/* Draw the circle */
+	m_screen->DrawLine(x+4, y, x+7, y, m_color);
+	m_screen->DrawLine(x+2, y+1, x+3, y+1, m_color);
+	m_screen->DrawLine(x+8, y+1, x+9, y+1, m_color);
+	m_screen->DrawLine(x+1, y+2, x+1, y+3, m_color);
+	m_screen->DrawLine(x+10, y+2, x+10, y+3, m_color);
+	m_screen->DrawLine(x, y+4, x, y+7, m_color);
+	m_screen->DrawLine(x+11, y+4, x+11, y+7, m_color);
+	m_screen->DrawLine(x+1, y+8, x+1, y+9, m_color);
+	m_screen->DrawLine(x+10, y+8, x+10, y+9, m_color);
+	m_screen->DrawLine(x+2, y+10, x+3, y+10, m_color);
+	m_screen->DrawLine(x+8, y+10, x+9, y+10, m_color);
+	m_screen->DrawLine(x+4, y+11, x+7, y+11, m_color);
+
+	if ( IsChecked() ) {
+		/* Draw the spot in the center */
+		x += 3;
+		y += 3;
+
+		m_screen->DrawLine(x+1, y, x+4, y, m_color);
+		++y;
+		m_screen->DrawLine(x, y, x+5, y, m_color);
+		++y;
+		m_screen->DrawLine(x, y, x+5, y, m_color);
+		++y;
+		m_screen->DrawLine(x, y, x+5, y, m_color);
+		++y;
+		m_screen->DrawLine(x, y, x+5, y, m_color);
+		++y;
+		m_screen->DrawLine(x+1, y, x+4, y, m_color);
+	}
+
+	UIElementRadioButton::Draw();
+}
diff --git a/MacDialogRadioButton.h b/MacDialogRadioButton.h
new file mode 100644
index 00000000..2076ba8a
--- /dev/null
+++ b/MacDialogRadioButton.h
@@ -0,0 +1,35 @@
+#ifndef _MacDialogRadioButton_h
+#define _MacDialogRadioButton_h
+
+#include "screenlib/UIElementRadio.h"
+
+
+class MacDialogRadioButton : public UIElementRadioButton
+{
+public:
+	MacDialogRadioButton(UIBaseElement *parent, const char *name = "");
+
+	virtual bool IsA(UIElementType type) {
+		return UIElementRadioButton::IsA(type) || type == GetType();
+	}
+
+	virtual bool Load(rapidxml::xml_node<> *node, const UITemplates *templates);
+
+	virtual void Draw();
+
+protected:
+	Uint32 m_color;
+
+protected:
+	static UIElementType s_elementType;
+
+public:
+	static UIElementType GetType() {
+		if (!s_elementType) {
+			s_elementType = GenerateType();
+		}
+		return s_elementType;
+	}
+};
+
+#endif // _MacDialogRadioButton_h
diff --git a/MaelstromUI.cpp b/MaelstromUI.cpp
index 1d14ba0a..880a1d8d 100644
--- a/MaelstromUI.cpp
+++ b/MaelstromUI.cpp
@@ -2,12 +2,14 @@
 #include "MaelstromUI.h"
 #include "Maelstrom_Globals.h"
 #include "main.h"
+#include "controls.h"
 #include "netlogic/about.h"
 #include "netlogic/game.h"
 #include "MacDialog.h"
 #include "MacDialogButton.h"
 #include "MacDialogCheckbox.h"
 #include "MacDialogLabel.h"
+#include "MacDialogRadioButton.h"
 #include "UIElementIcon.h"
 #include "UIElementKeyButton.h"
 #include "UIElementSprite.h"
@@ -158,6 +160,8 @@ MaelstromUI::CreatePanelDelegate(UIPanel *panel, const char *delegate)
 		return new AboutPanelDelegate(panel);
 	} else if (strcasecmp(delegate, "GamePanel") == 0) {
 		return new GamePanelDelegate(panel);
+	} else if (strcasecmp(delegate, "ControlsDialog") == 0) {
+		return new ControlsDialogDelegate(panel);
 	}
 	return UIManager::CreatePanelDelegate(panel, delegate);
 }
@@ -182,7 +186,7 @@ MaelstromUI::CreateElement(UIBaseElement *parent, const char *type, const char *
 	} else if (strcasecmp(type, "DialogRadioGroup") == 0) {
 		return new UIElementRadioGroup(parent, name);
 	} else if (strcasecmp(type, "DialogRadioButton") == 0) {
-		return new UIElementRadioButton(parent, name);
+		return new MacDialogRadioButton(parent, name);
 	} else if (strcasecmp(type, "KeyButton") == 0) {
 		return new UIElementKeyButton(parent, name);
 	} else if (strcasecmp(type, "Icon") == 0) {
diff --git a/Makefile.am b/Makefile.am
index eb1c70e3..c8b09a02 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -39,7 +39,9 @@ Maelstrom_SOURCES =		\
 	MacDialogCheckbox.cpp	\
 	MacDialogCheckbox.h	\
 	MacDialogLabel.cpp	\
-	MacDialogLabel.h		\
+	MacDialogLabel.h	\
+	MacDialogRadioButton.cpp\
+	MacDialogRadioButton.h	\
 	UIElementIcon.cpp	\
 	UIElementIcon.h		\
 	UIElementKeyButton.cpp	\
diff --git a/UI/UITemplates.xml b/UI/UITemplates.xml
index eb357f63..7e47e6ec 100644
--- a/UI/UITemplates.xml
+++ b/UI/UITemplates.xml
@@ -86,4 +86,9 @@
 			</Label>
 		</Elements>
 	</KeyButton>
+
+	<Rectangle templateName="ControlKeyBox">
+		<Size w="170" h="20"/>
+		<Color r="0x00" g="0x00" b="0x00"/>
+	</Rectangle>
 </UITemplates>
diff --git a/UI/controls.xml b/UI/controls.xml
new file mode 100644
index 00000000..8486206d
--- /dev/null
+++ b/UI/controls.xml
@@ -0,0 +1,84 @@
+<Dialog delegate="ControlsDialog">
+	<Size w="474" h="292"/>
+	<Elements>
+		<Title id="100">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="4" y="4"/>
+		</Icon>
+
+		<Rectangle name="control1_box" template="ControlKeyBox">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="92" y="71"/>
+		</Rectangle>
+		<DialogLabel name="control1">
+			<Anchor anchor="control1_box"/>
+		</DialogLabel>
+		<Rectangle name="control2_box" template="ControlKeyBox">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="BOTTOMLEFT" anchor="control1_box" y="3"/>
+		</Rectangle>
+		<DialogLabel name="control2">
+			<Anchor anchor="control2_box"/>
+		</DialogLabel>
+		<Rectangle name="control3_box" template="ControlKeyBox">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="BOTTOMLEFT" anchor="control2_box" y="3"/>
+		</Rectangle>
+		<DialogLabel name="control3">
+			<Anchor anchor="control3_box"/>
+		</DialogLabel>
+		<Rectangle name="control4_box" template="ControlKeyBox">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="BOTTOMLEFT" anchor="control3_box" y="3"/>
+		</Rectangle>
+		<DialogLabel name="control4">
+			<Anchor anchor="control4_box"/>
+		</DialogLabel>
+		<Rectangle name="control5_box" template="ControlKeyBox">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="BOTTOMLEFT" anchor="control4_box" y="3"/>
+		</Rectangle>
+		<DialogLabel name="control5">
+			<Anchor anchor="control5_box"/>
+		</DialogLabel>
+		<Rectangle name="control6_box" template="ControlKeyBox">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="BOTTOMLEFT" anchor="control5_box" y="3"/>
+		</Rectangle>
+		<DialogLabel name="control6">
+			<Anchor anchor="control6_box"/>
+		</DialogLabel>
+		<Rectangle name="control7_box" template="ControlKeyBox">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="BOTTOMLEFT" anchor="control6_box" y="3"/>
+		</Rectangle>
+		<DialogLabel name="control7">
+			<Anchor anchor="control7_box"/>
+		</DialogLabel>
+
+		<DialogRadioGroup name="controlsRadioGroup">
+			<Elements>
+				<DialogRadioButton text="Fire" id="1">
+					<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="control1_box"/>
+				</DialogRadioButton>
+				<DialogRadioButton text="Thrust" id="2">
+					<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="control2_box"/>
+				</DialogRadioButton>
+				<DialogRadioButton text="Shield" id="3">
+					<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="control3_box"/>
+				</DialogRadioButton>
+				<DialogRadioButton text="Turn Clockwise" id="4">
+					<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="control4_box"/>
+				</DialogRadioButton>
+				<DialogRadioButton text="Turn Counter-Clockwise" id="5">
+					<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="control5_box"/>
+				</DialogRadioButton>
+				<DialogRadioButton text="Pause" id="6">
+					<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="control6_box"/>
+				</DialogRadioButton>
+				<DialogRadioButton text="Abort Game" id="7">
+					<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="control7_box"/>
+				</DialogRadioButton>
+			</Elements>
+		</DialogRadioGroup>
+
+		<DialogButton name="cancelButton" text="Cancel">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="291" y="265"/>
+		</DialogButton>
+		<DialogButton text="OK" id="1">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="cancelButton"x="25"/>
+		</DialogButton>
+	</Elements>
+</Dialog>
diff --git a/controls.cpp b/controls.cpp
index d92fc8fe..82170413 100644
--- a/controls.cpp
+++ b/controls.cpp
@@ -8,6 +8,9 @@
 #include "Maelstrom_Globals.h"
 #include "load.h"
 #include "dialog.h"
+#include "screenlib/UIDialog.h"
+#include "screenlib/UIElementLabel.h"
+#include "screenlib/UIElementRadio.h"
 
 #define MAELSTROM_DATA	".Maelstrom-data"
 
@@ -131,186 +134,163 @@ void SaveControls(void)
 	fclose(data);
 }
 
-#define FIRE_CTL	0
-#define THRUST_CTL	1
-#define SHIELD_CTL	2
-#define TURNR_CTL	3
-#define TURNL_CTL	4
-#define PAUSE_CTL	5
-#define QUIT_CTL	6
-#define NUM_CTLS	7
-
-#define SP		3
-
-#define CTL_DIALOG_WIDTH	482
-#define CTL_DIALOG_HEIGHT	300
-
-Controls newcontrols;
-static struct {
-	const char *label;
-	int  yoffset;
-	SDL_Keycode *control;
-} checkboxes[] = {
-	{ "Fire",	0*BOX_HEIGHT+0*SP, &newcontrols.gFireControl },
-	{ "Thrust",	1*BOX_HEIGHT+1*SP, &newcontrols.gThrustControl },
-	{ "Shield",	2*BOX_HEIGHT+2*SP, &newcontrols.gShieldControl },
-	{ "Turn Clockwise", 3*BOX_HEIGHT+3*SP, &newcontrols.gTurnRControl },
-	{ "Turn Counter-Clockwise",
-			4*BOX_HEIGHT+4*SP, &newcontrols.gTurnLControl },
-	{ "Pause",	5*BOX_HEIGHT+5*SP, &newcontrols.gPauseControl },
-	{ "Abort Game",	6*BOX_HEIGHT+6*SP, &newcontrols.gQuitControl },
-};
-
-static int X=0;
-static int Y=0;
-static SDL_Texture *keynames[NUM_CTLS];
-static int currentbox, valid;
-
-static int OK_callback(void) {
-	valid = 1;
-	return(1);
-}
-static int Cancel_callback(void) {
-	valid = 0;
-	return(1);
+bool
+ControlsDialogDelegate::OnLoad()
+{
+	char name[32];
+
+	for (int i = 0; i < SDL_arraysize(m_controlKeys); ++i) {
+		sprintf(name, "control%d", 1+i);
+		m_controlKeys[i] = m_panel->GetElement<UIElementLabel>(name);
+		if (!m_controlKeys[i]) {
+			fprintf(stderr, "Warning: Couldn't find control key label '%s'\n", name);
+			return false;
+		}
+	}
+
+	m_radioGroup = m_panel->GetElement<UIElementRadioGroup>("controlsRadioGroup");
+	if (!m_radioGroup) {
+		fprintf(stderr, "Warning: Couldn't find 'controlsRadioGroup'\n");
+		return false;
+	}
+
+	return true;
 }
-static void BoxKeyPress(const SDL_Keysym &key, int *doneflag)
+
+void
+ControlsDialogDelegate::OnShow()
 {
-	int i;
-	SDL_Keycode sym = key.sym;
-	char keyname[128];
+	UIElementRadioButton *button;
 
-	if ( sym == *checkboxes[currentbox].control )
-		return;
+	button = m_radioGroup->GetRadioButton(1);
+	if (button) {
+		button->SetChecked(true);
+	}
 
-	/* Make sure the key isn't in use! */
-	for ( i=0; i<NUM_CTLS; ++i ) {
-		if ( sym == *checkboxes[i].control ) {
-			sym = *checkboxes[currentbox].control;
-
-			/* Clear the current text */
-			fontserv->FreeText(keynames[currentbox]);
-
-			/* Blit the new message */
-			strcpy(keyname, "That key is in use!");
-			keynames[currentbox] = fontserv->TextImage(keyname,
-					fonts[CHICAGO_12], STYLE_NORM, 0x00, 0x00, 0x00);
-			screen->QueueBlit(
-				X+96+(BOX_WIDTH-screen->GetImageWidth(keynames[currentbox]))/2, 
-				Y+75+SP+checkboxes[currentbox].yoffset,
-					keynames[currentbox], NOCLIP);
-			screen->Update();
-			SDL_Delay(1000);
-			break;
-		}
+	for (int i = 0; i < SDL_arraysize(m_keyinuseTimers); ++i) {
+		m_keyinuseTimers[i] = 0;
 	}
 
-	/* Clear the current text */
-	fontserv->FreeText(keynames[currentbox]);
-
-	/* Display the new key */
-	*checkboxes[currentbox].control = sym;
-	KeyName(*checkboxes[currentbox].control, keyname);
-	keynames[currentbox] = fontserv->TextImage(keyname,
-					fonts[CHICAGO_12], STYLE_NORM, 0x00, 0x00, 0x00);
-	screen->QueueBlit(X+96+(BOX_WIDTH-screen->GetImageWidth(keynames[currentbox]))/2, 
-				Y+75+SP+checkboxes[currentbox].yoffset,
-						keynames[currentbox], NOCLIP);
-	screen->Update();
+	m_controls = controls;
+
+	ShowKeyLabels();
 }
 
-void ConfigureControls(void)
+void
+ControlsDialogDelegate::OnHide()
 {
-#ifdef FAITHFUL_SPECS
-	static char *C_text1 = 
-		"While playing Maelstrom, CAPS LOCK pauses the game and";
-	static char *C_text2 = 
-		"ESC aborts the game.";
-	SDL_Texture *text1, *text2;
-#endif
-	MFont *chicago;
-	Uint32 black;
-	int i;
-	char keyname[128];
-	Maclike_Dialog *dialog;
-	SDL_Texture *splash;
-	Mac_Button *cancel, *okay;
-	Mac_RadioList *radiobuttons;
-	Mac_Dialog *boxes;
+	if (m_panel->IsA(UIDialog::GetType()) &&
+	    static_cast<UIDialog*>(m_panel)->GetDialogStatus() > 0) {
+		controls = m_controls;
+		SaveControls();
+	}
+}
 
+void
+ControlsDialogDelegate::OnTick()
+{
+	for (int i = 0; i < SDL_arraysize(m_keyinuseTimers); ++i) {
+		if (m_keyinuseTimers[i] && (SDL_GetTicks() - m_keyinuseTimers[i]) > 1000) {
+			m_keyinuseTimers[i] = 0;
+			ShowKeyLabel(i);
+		}
+	}
+}
 
-	/* Set up all the components of the dialog box */
-	black = screen->MapRGB(0x00, 0x00, 0x00);
-	chicago = fonts[CHICAGO_12];
-	if ( (splash = Load_Title(screen, 100)) == NULL ) {
-		error("Can't load configuration splash!\n");
-		return;
+bool
+ControlsDialogDelegate::HandleEvent(const SDL_Event &event)
+{
+	if (event.type == SDL_KEYDOWN) {
+		return true;
 	}
-	X=(SCREEN_WIDTH-CTL_DIALOG_WIDTH)/2;
-	Y=(SCREEN_HEIGHT-CTL_DIALOG_HEIGHT)/2;
-	dialog = new Maclike_Dialog(X, Y, CTL_DIALOG_WIDTH, CTL_DIALOG_HEIGHT,
-									screen);
-	dialog->Add_Image(splash, 4, 4);
-#ifdef FAITHFUL_SPECS
-	text1 = fontserv->TextImage(C_text1,chicago,STYLE_NORM,0x00,0x00,0x00);
-	text2 = fontserv->TextImage(C_text2,chicago,STYLE_NORM,0x00,0x00,0x00);
-	dialog->Add_Image(text1, 66, 216);
-	dialog->Add_Image(text2, 66, 232);
-#endif
-	valid = 0;
-	cancel = new Mac_Button(291, 265, BUTTON_WIDTH, BUTTON_HEIGHT,
-				"Cancel", chicago, fontserv, Cancel_callback);
-	dialog->Add_Dialog(cancel);
-	okay = new Mac_Button(291+BUTTON_WIDTH+26, 265, 
-					BUTTON_WIDTH, BUTTON_HEIGHT,
-				"OK", chicago, fontserv, OK_callback);
-	dialog->Add_Dialog(okay);
-	memcpy(&newcontrols, &controls, sizeof(controls));
-	radiobuttons = new Mac_RadioList(&currentbox, X+266, Y+75,
-							chicago, fontserv);
-	currentbox = FIRE_CTL;
-	for ( i=0; i<NUM_CTLS; ++i ) {
-		KeyName(*checkboxes[i].control, keyname);
-		keynames[i] = fontserv->TextImage(keyname, chicago, STYLE_NORM,
-							0x00, 0x00, 0x00);
-		/* Only display "Fire" through "Turn Counter-Clockwise" */
-#ifdef FAITHFUL_SPECS
-		if ( i <  PAUSE_CTL ) {
-#else
-		if ( i <  NUM_CTLS ) {
-#endif
-			radiobuttons->Add_Radio(263, 71+checkboxes[i].yoffset,
-							checkboxes[i].label);
-			dialog->Add_Rectangle(92, 71+checkboxes[i].yoffset,
-						BOX_WIDTH, BOX_HEIGHT, black);
-			dialog->Add_Image(keynames[i],
-					92+(BOX_WIDTH-screen->GetImageWidth(keynames[i]))/2, 
-						71+SP+checkboxes[i].yoffset);
+	if (event.type == SDL_KEYUP) {
+		int index;
+		SDL_Keycode key = event.key.keysym.sym;
+
+		index = m_radioGroup->GetValue() - 1;
+
+		/* See if this key is in use */
+		m_keyinuseTimers[index] = 0;
+		for (int i = 0; i < NUM_CTLS; ++i) {
+			if (i != index && key == GetKeycode(i)) {
+				m_keyinuseTimers[index] = SDL_GetTicks();
+				break;
+			}
 		}
+		if (!m_keyinuseTimers[index]) {
+			SetKeycode(index, key);
+		}
+		ShowKeyLabel(index);
+
+		return true;
 	}
-	dialog->Add_Dialog(radiobuttons);
-	boxes = new Mac_Dialog(92, 71);
-	boxes->SetKeyPress(BoxKeyPress);
-	dialog->Add_Dialog(boxes);
+	return false;
+}
 
-	/* Run the dialog box */
-	dialog->Run(EXPAND_STEPS);
+void
+ControlsDialogDelegate::ShowKeyLabel(int index)
+{
+	const char *text;
 
-	/* Clean up and return */
-	screen->FreeImage(splash);
-#ifdef FAITHFUL_SPECS
-	fontserv->FreeText(text1);
-	fontserv->FreeText(text2);
-#endif
-	for ( i=0; i<NUM_CTLS; ++i ) {
-		fontserv->FreeText(keynames[i]);
+	if (m_keyinuseTimers[index]) {
+		text = "That key is in use!";
+	} else {
+		text = SDL_GetKeyName(GetKeycode(index));
 	}
-	delete dialog;
-	if ( valid ) {
-		memcpy(&controls, &newcontrols, sizeof(controls));
-		SaveControls();
+	m_controlKeys[index]->SetText(text);
+}
+
+SDL_Keycode
+ControlsDialogDelegate::GetKeycode(int index)
+{
+	switch (index) {
+		case FIRE_CTL:
+			return m_controls.gFireControl;
+		case THRUST_CTL:
+			return m_controls.gThrustControl;
+		case SHIELD_CTL:
+			return m_controls.gShieldControl;
+		case TURNR_CTL:
+			return m_controls.gTurnRControl;
+		case TURNL_CTL:
+			return m_controls.gTurnLControl;
+		case PAUSE_CTL:
+			return m_controls.gPauseControl;
+		case QUIT_CTL:
+			return m_controls.gQuitControl;
+		default:
+			return SDLK_UNKNOWN;
+	}
+}
+
+void
+ControlsDialogDelegate::SetKeycode(int index, SDL_Keycode keycode)
+{
+	switch (index) {
+		case FIRE_CTL:
+			m_controls.gFireControl = keycode;
+			break;
+		case THRUST_CTL:
+			m_controls.gThrustControl = keycode;
+			break;
+		case SHIELD_CTL:
+			m_controls.gShieldControl = keycode;
+			break;
+		case TURNR_CTL:
+			m_controls.gTurnRControl = keycode;
+			break;
+		case TURNL_CTL:
+			m_controls.gTurnLControl = keycode;
+			break;
+		case PAUSE_CTL:
+			m_controls.gPauseControl = keycode;
+			break;
+		case QUIT_CTL:
+			m_controls.gQuitControl = keycode;
+			break;
+		default:
+			break;
 	}
-	return;
 }
 
 static void HandleEvent(SDL_Event *event)
diff --git a/controls.h b/controls.h
index 916ddcaa..f9816c1c 100644
--- a/controls.h
+++ b/controls.h
@@ -1,11 +1,13 @@
 
+#ifndef _controls_h
+#define _controls_h
+
 // Functions from controls.cc
 #ifdef USE_JOYSTICK
 extern void	CalibrateJoystick(char *joystick);
 #endif
 extern void	LoadControls(void);
 extern void	SaveControls(void);
-extern void	ConfigureControls(void);
 extern int	PollEvent(SDL_Event *event, int timeout);
 extern void	HandleEvents(int timeout);
 extern int	DropEvents(void);
@@ -31,3 +33,48 @@ typedef struct {
 	SDL_Keycode gQuitControl;
 } Controls;
 
+
+class UIElementLabel;
+class UIElementRadioGroup;
+
+class ControlsDialogDelegate : public UIPanelDelegate
+{
+public:
+	ControlsDialogDelegate(UIPanel *panel) : UIPanelDelegate(panel) { }
+
+	virtual bool OnLoad();
+	virtual void OnShow();
+	virtual void OnHide();
+	virtual void OnTick();
+	virtual bool HandleEvent(const SDL_Event &event);
+
+protected:
+	void ShowKeyLabel(int index);
+	void ShowKeyLabels() {
+		for (int i = 0; i < NUM_CTLS; ++i) {
+			ShowKeyLabel(i);
+		}
+	}
+
+	SDL_Keycode GetKeycode(int index);
+	void SetKeycode(int index, SDL_Keycode keycode);
+
+protected:
+	enum {
+		FIRE_CTL,
+		THRUST_CTL,
+		SHIELD_CTL,
+		TURNR_CTL,
+		TURNL_CTL,
+		PAUSE_CTL,
+		QUIT_CTL,
+		NUM_CTLS
+	};
+
+	Controls m_controls;
+	UIElementLabel *m_controlKeys[NUM_CTLS];
+	UIElementRadioGroup *m_radioGroup;
+	Uint32 m_keyinuseTimers[NUM_CTLS];
+};
+
+#endif /* _controls_h */
diff --git a/main.cpp b/main.cpp
index a73ccead..e29d239b 100644
--- a/main.cpp
+++ b/main.cpp
@@ -41,10 +41,6 @@ static void RunDoAbout(void)
 {
 	ui->ShowPanel(PANEL_ABOUT);
 }
-static void RunConfigureControls(void)
-{
-	ConfigureControls();
-}
 static void RunPlayGame(void)
 {
 	gStartLives = 3;
@@ -358,7 +354,7 @@ MainPanelDelegate::OnLoad()
 	}
 	button = m_panel->GetElement<UIElementButton>("ControlsButton");
 	if (button) {
-		button->SetClickCallback(RunConfigureControls);
+		button->SetButtonDelegate(new UIDialogLauncher(ui, DIALOG_CONTROLS));
 	}
 	button = m_panel->GetElement<UIElementButton>("ZapButton");
 	if (button) {
diff --git a/screenlib/UIBaseElement.cpp b/screenlib/UIBaseElement.cpp
index f6a93a0b..5f9b8412 100644
--- a/screenlib/UIBaseElement.cpp
+++ b/screenlib/UIBaseElement.cpp
@@ -87,7 +87,11 @@ UIBaseElement::GetAnchorElement(const char *name)
 {
 	if (name) {
 		if (m_parent) {
-			return m_parent->GetElement(name);
+			UIArea *element = m_parent->GetElement(name);
+			if (!element) {
+				element = m_parent->GetAnchorElement(name);
+			}
+			return element;
 		} else {
 			return NULL;
 		}
diff --git a/screenlib/UIDialog.h b/screenlib/UIDialog.h
index 22bc79ee..9e8d60eb 100644
--- a/screenlib/UIDialog.h
+++ b/screenlib/UIDialog.h
@@ -55,6 +55,9 @@ class UIDialog : public UIPanel
 	void SetDialogStatus(int status) {
 		m_status = status;
 	}
+	int GetDialogStatus() const {
+		return m_status;
+	}
 
 	virtual void Show();
 	virtual void Hide();
diff --git a/screenlib/UIElementRadio.cpp b/screenlib/UIElementRadio.cpp
index 763db024..601c8ecc 100644
--- a/screenlib/UIElementRadio.cpp
+++ b/screenlib/UIElementRadio.cpp
@@ -10,12 +10,32 @@ UIElementRadioGroup::UIElementRadioGroup(UIBaseElement *parent, const char *name
 	m_value = -1;
 }
 
+UIElementRadioButton *
+UIElementRadioGroup::GetRadioButton(int id)
+{
+	UIElementRadioButton *button;
+
+	for (unsigned i = 0; i < m_elements.length(); ++i) {
+		if (!m_elements[i]->IsA(UIElementRadioButton::GetType())) {
+			continue;
+		}
+
+		button = static_cast<UIElementRadioButton*>(m_elements[i]);
+		if (button->GetID() == id) {
+			return button;
+		}
+	}
+	return NULL;
+}
+
 void
 UIElementRadioGroup::RadioButtonChecked(UIElementRadioButton *button)
 {
 	for (unsigned i = 0; i < m_elements.length(); ++i) {
-		if (m_elements[i] != button &&
-		    m_elements[i]->IsA(UIElementRadioButton::GetType())) {
+		if (!m_elements[i]->IsA(UIElementRadioButton::GetType())) {
+			continue;
+		}
+		if (m_elements[i] != button) {
 			static_cast<UIElementRadioButton*>(m_elements[i])->SetChecked(false);
 		}
 	}
diff --git a/screenlib/UIElementRadio.h b/screenlib/UIElementRadio.h
index 0c336a17..08eb29b0 100644
--- a/screenlib/UIElementRadio.h
+++ b/screenlib/UIElementRadio.h
@@ -45,6 +45,8 @@ class UIElementRadioGroup : public UIElement
 		return UIElement::IsA(type) || type == GetType();
 	}
 
+	UIElementRadioButton *GetRadioButton(int id);
+
 	void RadioButtonChecked(UIElementRadioButton *button);
 
 	int GetValue() const {
diff --git a/screenlib/UIPanel.cpp b/screenlib/UIPanel.cpp
index 87e3502f..fca8e1df 100644
--- a/screenlib/UIPanel.cpp
+++ b/screenlib/UIPanel.cpp
@@ -134,3 +134,16 @@ UIPanel::Draw()
 		m_delegate->OnDraw();
 	}
 }
+
+bool
+UIPanel::HandleEvent(const SDL_Event &event)
+{
+	if (UIBaseElement::HandleEvent(event)) {
+		return true;
+	}
+
+	if (m_delegate) {
+		return m_delegate->HandleEvent(event);
+	}
+	return false;
+}
diff --git a/screenlib/UIPanel.h b/screenlib/UIPanel.h
index d334568d..d4cbb7ed 100644
--- a/screenlib/UIPanel.h
+++ b/screenlib/UIPanel.h
@@ -44,6 +44,7 @@ class UIPanelDelegate
 	virtual void OnHide() { }
 	virtual void OnTick() { }
 	virtual void OnDraw() { }
+	virtual bool HandleEvent(const SDL_Event &event) { return false; }
 
 protected:
 	UIPanel *m_panel;
@@ -73,6 +74,7 @@ class UIPanel : public UIBaseElement
 	void HideAll();
 
 	virtual void Draw();
+	virtual bool HandleEvent(const SDL_Event &event);
 
 protected:
 	bool m_fullscreen;