From b3fb58ebdb9b34ea05daf3b195d522d340b6d22a Mon Sep 17 00:00:00 2001
From: Aloshi <aloshi@aloshi>
Date: Sun, 22 Jul 2012 16:15:55 -0500
Subject: [PATCH] An input configuration GUI was added, which allows a user to
 map an SDL Joystick's buttons to InputManager buttons. This config file is
 saved/loaded from ./input.cfg. Coming soon - axis support!

---
 Makefile                          |   2 +-
 src/InputManager.cpp              | 142 ++++++++++++++++++++++--------
 src/InputManager.h                |  12 ++-
 src/Renderer.cpp                  |   9 ++
 src/Renderer.h                    |   1 +
 src/components/GuiInputConfig.cpp |  94 ++++++++++++++++++++
 src/components/GuiInputConfig.h   |  29 ++++++
 src/main.cpp                      |  12 ++-
 8 files changed, 260 insertions(+), 41 deletions(-)
 create mode 100644 src/components/GuiInputConfig.cpp
 create mode 100644 src/components/GuiInputConfig.h

diff --git a/Makefile b/Makefile
index e1ec770a0..fbc771a2d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 CC=g++
 CFLAGS=-c -Wall
 LDFLAGS=-lSDL -lSDL_ttf -lboost_system -lboost_filesystem
-SRCSOURCES=main.cpp Renderer.cpp Renderer_draw.cpp GuiComponent.cpp InputManager.cpp SystemData.cpp GameData.cpp components/GuiTitleScreen.cpp components/GuiList.cpp components/GuiGameList.cpp
+SRCSOURCES=main.cpp Renderer.cpp Renderer_draw.cpp GuiComponent.cpp InputManager.cpp SystemData.cpp GameData.cpp components/GuiTitleScreen.cpp components/GuiList.cpp components/GuiGameList.cpp components/GuiInputConfig.cpp
 SOURCES=$(addprefix src/,$(SRCSOURCES))
 OBJECTS=$(SOURCES:.cpp=.o)
 EXECUTABLE=emulationstation
diff --git a/src/InputManager.cpp b/src/InputManager.cpp
index 029858210..7f5c258e1 100644
--- a/src/InputManager.cpp
+++ b/src/InputManager.cpp
@@ -1,8 +1,13 @@
 #include "InputManager.h"
 #include "GuiComponent.h"
 #include <iostream>
+#include <fstream>
+#include <sstream>
 
 std::vector<GuiComponent*> InputManager::inputVector;
+SDL_Event* InputManager::lastEvent = NULL;
+
+std::map<int, InputManager::InputButton> InputManager::joystickButtonMap, InputManager::joystickAxisMap;
 
 void InputManager::registerComponent(GuiComponent* comp)
 {
@@ -24,49 +29,116 @@ void InputManager::unregisterComponent(GuiComponent* comp)
 void InputManager::processEvent(SDL_Event* event)
 {
 	bool keyDown = false;
-	if(event->key.state == SDL_PRESSED)
-		keyDown = true;
-
-	//get InputButton from the event
 	InputButton button;
-	switch(event->key.keysym.sym)
+
+	lastEvent = event;
+
+	//keyboard events
+	if(event->type == SDL_KEYDOWN || event->type == SDL_KEYUP)
 	{
-		case SDLK_LEFT:
-			button = LEFT;
-			break;
-		case SDLK_RIGHT:
-			button = RIGHT;
-			break;
-		case SDLK_UP:
-			button = UP;
-			break;
-		case SDLK_DOWN:
-			button = DOWN;
-			break;
-		case SDLK_RETURN:
-			button = BUTTON1;
-			break;
+		if(event->key.state == SDL_PRESSED)
+			keyDown = true;
 
-		//so the compiler doesn't complain
-		default:
-			break;
+		//get InputButton from the event
+		switch(event->key.keysym.sym)
+		{
+			case SDLK_LEFT:
+				button = LEFT;
+				break;
+			case SDLK_RIGHT:
+				button = RIGHT;
+				break;
+			case SDLK_UP:
+				button = UP;
+				break;
+			case SDLK_DOWN:
+				button = DOWN;
+				break;
+			case SDLK_RETURN:
+				button = BUTTON1;
+				break;
+
+			default:
+				button = UNKNOWN;
+				break;
+		}
+
+		//catch emergency quit event
+		if(event->key.keysym.sym == SDLK_F4)
+		{
+			//I have no idea if SDL will delete this event, but we're quitting, so I don't think it really matters
+			SDL_Event* quit = new SDL_Event();
+			quit->type = SDL_QUIT;
+			SDL_PushEvent(quit);
+			std::cout << "Pushing quit event\n";
+		}
+	}else{
+		if(event->type == SDL_JOYBUTTONDOWN || event->type == SDL_JOYBUTTONUP) //joystick button events
+		{
+			if(event->type == SDL_JOYBUTTONDOWN) //defaults to false, so no else
+				keyDown = true;
+
+			//hurr no config yet durr
+			button = joystickButtonMap[event->jbutton.button];
+		}
 	}
 
-
-	//catch emergency quit event
-	if(event->key.keysym.sym == SDLK_F4)
-	{
-		//I have no idea if SDL will delete this event, but we're quitting, so I don't think it really matters
-		SDL_Event* quit = new SDL_Event();
-		quit->type = SDL_QUIT;
-		SDL_PushEvent(quit);
-		std::cout << "Pushing quit event\n";
-	}
-
-
 	for(unsigned int i = 0; i < inputVector.size(); i++)
 	{
 		inputVector.at(i)->onInput(button, keyDown);
 	}
 }
 
+void InputManager::loadConfig(std::string path)
+{
+	//clear any old config
+	joystickButtonMap.clear();
+	joystickAxisMap.clear();
+
+	std::ifstream file(path.c_str());
+
+	while(file.good())
+	{
+		std::string line;
+		std::getline(file, line);
+
+		//skip blank lines and comments
+		if(line.empty() || line[0] == *"#")
+			continue;
+
+
+		//I know I could probably just read from the file stream directly, but I feel it would be harder to catch errors in a readable way
+		std::istringstream stream(line);
+
+		std::string token[3];
+		int tokNum = 0;
+
+		while(std::getline(stream, token[tokNum], ' '))
+		{
+			tokNum++;
+			if(tokNum > 3)
+			{
+				std::cerr << "Error - input config line \"" << line << "\" has more than three tokens!\n";
+				break;
+			}
+		}
+
+
+		if(token[0] == "BUTTON")
+		{
+			joystickButtonMap[atoi(token[1].c_str())] = (InputButton)atoi(token[2].c_str());
+		}else if(token[0] == "AXIS")
+		{
+			
+		}else{
+			std::cerr << "Invalid input type - " << token[0] << "\n";
+			return;
+		}
+
+	}
+
+	if(SDL_NumJoysticks() > 0)
+	{
+		SDL_JoystickOpen(0);
+	}
+}
diff --git a/src/InputManager.h b/src/InputManager.h
index eedc5b44c..eabb17256 100644
--- a/src/InputManager.h
+++ b/src/InputManager.h
@@ -3,6 +3,8 @@
 
 #include <vector>
 #include <SDL/SDL.h>
+#include <map>
+#include <string>
 
 class GuiComponent;
 
@@ -10,14 +12,18 @@ namespace InputManager {
 	void registerComponent(GuiComponent* comp);
 	void unregisterComponent(GuiComponent* comp);
 
+	void loadConfig(std::string path);
 
-	//enum for identifying input type, regardless of configuration
-	enum InputButton { UP, DOWN, LEFT, RIGHT, BUTTON1, BUTTON2};
-
+	//enum for identifying input, regardless of configuration
+	enum InputButton { UP, DOWN, LEFT, RIGHT, BUTTON1, BUTTON2, UNKNOWN};
 
 	void processEvent(SDL_Event* event);
 
 	extern std::vector<GuiComponent*> inputVector;
+	extern SDL_Event* lastEvent; //mostly for GuiInputConfig
+
+	extern std::map<int, InputButton> joystickButtonMap;
+	extern std::map<int, InputButton> joystickAxisMap;
 }
 
 #endif
diff --git a/src/Renderer.cpp b/src/Renderer.cpp
index 685779fd8..ad4c5bf45 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -23,6 +23,15 @@ void Renderer::unregisterComponent(GuiComponent* comp)
 	}
 }
 
+void Renderer::deleteAll()
+{
+	for(unsigned int i = 0; i < renderVector.size(); i++)
+	{
+		delete renderVector.at(i);
+	}
+	renderVector.clear();
+}
+
 void Renderer::render()
 {
 	for(unsigned int layer = 0; layer < LAYER_COUNT; layer++)
diff --git a/src/Renderer.h b/src/Renderer.h
index ac34d09bf..4edc58519 100644
--- a/src/Renderer.h
+++ b/src/Renderer.h
@@ -16,6 +16,7 @@ namespace Renderer
 {
 	void registerComponent(GuiComponent* comp);
 	void unregisterComponent(GuiComponent* comp);
+	void deleteAll();
 
 	void render();
 
diff --git a/src/components/GuiInputConfig.cpp b/src/components/GuiInputConfig.cpp
new file mode 100644
index 000000000..a4041c20f
--- /dev/null
+++ b/src/components/GuiInputConfig.cpp
@@ -0,0 +1,94 @@
+#include "GuiInputConfig.h"
+#include "GuiGameList.h"
+#include <iostream>
+#include <fstream>
+
+std::string GuiInputConfig::sConfigPath = "./input.cfg";
+std::string GuiInputConfig::sInputs[] = { "UP", "DOWN", "LEFT", "RIGHT", "BUTTON1", "BUTTON2" };
+int GuiInputConfig::sInputCount = 6;
+
+GuiInputConfig::GuiInputConfig()
+{
+	mInputNum = 0;
+	mDone = false;
+
+	Renderer::registerComponent(this);
+	InputManager::registerComponent(this);
+
+	if(SDL_NumJoysticks() < 1)
+	{
+		std::cerr << "Error - GuiInputConfig found no SDL joysticks!\n";
+		mJoystick = NULL;
+		mDone = true;
+		return;
+	}else{
+		std::cout << "Opening joystick \"" << SDL_JoystickName(0) << "\"\n";
+		mJoystick = SDL_JoystickOpen(0);
+	}
+}
+
+GuiInputConfig::~GuiInputConfig()
+{
+	Renderer::unregisterComponent(this);
+	InputManager::unregisterComponent(this);
+}
+
+void GuiInputConfig::onRender()
+{
+	Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0xFFFFFF);
+
+	if(mDone)
+		Renderer::drawCenteredText("All done!", 2, 0x000000);
+	else
+		Renderer::drawCenteredText("Please press the axis/button for " + sInputs[mInputNum], 2, 0x000000);
+}
+
+void GuiInputConfig::onInput(InputManager::InputButton button, bool keyDown)
+{
+	if(mDone)
+	{
+		if(keyDown)
+		{
+			writeConfig(sConfigPath);
+
+			if(mJoystick)
+				SDL_JoystickClose(mJoystick);
+
+			InputManager::loadConfig(sConfigPath);
+			delete this;
+			new GuiGameList();
+		}
+		return;
+	}
+
+	SDL_Event* event = InputManager::lastEvent;
+	if(event->type == SDL_JOYBUTTONDOWN)
+	{
+		mButtonMap[event->jbutton.button] = (InputManager::InputButton)mInputNum;
+		std::cout << "	Mapping " << sInputs[mInputNum] << " to button " << event->jbutton.button << "\n";
+		mInputNum++;
+	}
+
+	if(mInputNum >= sInputCount)
+	{
+		mDone = true;
+		return;
+	}
+}
+
+void GuiInputConfig::writeConfig(std::string path)
+{
+	std::ofstream file(path.c_str());
+
+	typedef std::map<int, InputManager::InputButton>::iterator it_type;
+	for(it_type iter = mButtonMap.begin(); iter != mButtonMap.end(); iter++)
+	{
+		file << "BUTTON " << iter->first << " " << iter->second << "\n";
+	}
+
+	for(it_type iter = mAxisMap.begin(); iter != mAxisMap.end(); iter++)
+	{
+		file << "AXIS " << iter->first << " " << iter->second << "\n";
+	}
+
+}
diff --git a/src/components/GuiInputConfig.h b/src/components/GuiInputConfig.h
new file mode 100644
index 000000000..0e0624614
--- /dev/null
+++ b/src/components/GuiInputConfig.h
@@ -0,0 +1,29 @@
+#ifndef _GUIINPUTCONFIG_H_
+#define _GUIINPUTCONFIG_H_
+
+#include "../GuiComponent.h"
+#include "../InputManager.h"
+#include <map>
+#include <SDL/SDL.h>
+
+class GuiInputConfig : GuiComponent {
+public:
+	GuiInputConfig();
+	~GuiInputConfig();
+
+	void onRender();
+	void onInput(InputManager::InputButton button, bool keyDown);
+private:
+	bool mDone;
+	int mInputNum;
+	SDL_Joystick* mJoystick;
+	static std::string sInputs[];
+	static int sInputCount;
+	static std::string sConfigPath;
+
+	std::map<int, InputManager::InputButton> mButtonMap;
+	std::map<int, InputManager::InputButton> mAxisMap;
+	void writeConfig(std::string path);
+};
+
+#endif
diff --git a/src/main.cpp b/src/main.cpp
index bc243e446..7e1c460f6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,6 +5,8 @@
 #include "components/GuiGameList.h"
 #include "SystemData.h"
 
+#include "components/GuiInputConfig.h"
+
 int main()
 {
 	if(SDL_Init(SDL_INIT_EVERYTHING) != 0)
@@ -31,12 +33,16 @@ int main()
 
 	SDL_ShowCursor(false);
 	SDL_EnableKeyRepeat(500, 100);
+	SDL_JoystickEventState(SDL_ENABLE);
 
 	//GuiTitleScreen* testGui = new GuiTitleScreen();
 
 	SystemData::loadConfig("./systems.cfg");
-	GuiGameList* testGui = new GuiGameList();
 
+	//InputManager::loadConfig("./input.cfg");
+
+	//GuiGameList* testGui = new GuiGameList();
+	GuiInputConfig* testGui = new GuiInputConfig();
 
 	bool running = true;
 	while(running)
@@ -46,6 +52,8 @@ int main()
 		{
 			switch(event.type)
 			{
+				case SDL_JOYBUTTONDOWN:
+				case SDL_JOYBUTTONUP:
 				case SDL_KEYDOWN:
 					InputManager::processEvent(&event);
 					break;
@@ -63,7 +71,7 @@ int main()
 		SDL_Flip(Renderer::screen);
 	}
 
-	delete testGui;
+	Renderer::deleteAll();
 	SystemData::deleteSystems();
 
 	std::cout << "EmulationStation cleanly shutting down...\n";