1 /*
2  * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license.
3  *
4  * Pixel Perfect Engine, config module
5  */
6 
7 module PixelPerfectEngine.system.config;
8 
9 //import std.xml;
10 import std.file;
11 import std.stdio;
12 import std..string;
13 import std.conv;
14 //import std.csv;
15 
16 import PixelPerfectEngine.system.inputHandler;
17 import PixelPerfectEngine.system.exc;
18 import PixelPerfectEngine.system.etc;
19 import PixelPerfectEngine.system.dictionary;
20 import PixelPerfectEngine.graphics.outputScreen;
21 
22 import derelict.sdl2.sdl;
23 
24 import sdlang;
25 
26 public class ConfigurationProfile{
27 	public static const ushort[string] keymodifierStrings; 
28 	public static const string[ushort] joymodifierStrings; 
29 	public static const string[Devicetype] devicetypeStrings;
30 	private static Dictionary keyNameDict, joyButtonNameDict, joyAxisNameDict;
31 	public int sfxVol, musicVol;
32 	public int threads;
33 	public string screenMode, resolution, scalingQuality, driver;
34 	//public string[string] videoSettings;
35 	public KeyBinding[] keyBindingList;
36 	public InputDeviceData[] inputDevices;
37 	private string path;
38 	public AuxillaryElements[] auxillaryParameters;
39 	private static string vaultPath;
40 	private SDL_DisplayMode[] videoModes;
41 	//public AuxillaryElements auxillaryElements[];
42 	public string appName;
43 	public string appVers;
44 	/// Initializes a basic configuration profile. If [vaultPath] doesn't have any configfiles, restores it from defaults.
45 	public this(){
46 		path = vaultPath ~ "config.sdl";
47 		if(exists(path)){
48 			restore();
49 		}else{
50 			std.file.copy("system/defaultConfig.sdl",path);
51 			restore();
52 		}
53 	}
54 
55 	static this(){
56 		keymodifierStrings = 
57 				["NONE"	: 0x0000, "LSHIFT": 0x0001, "RSHIFT": 0x0002, "LCTRL": 0x0040, "RCTRL": 0x0080, "CTRL": 0x0020, "LALT": 0x0100, "RALT": 0x0200 , "LGUI": 0x0400,
58 					"RGUI": 0x0800, "NUM": 0x1000, "CAPS": 0x2000, "MODE": 0x4000, "RESERVED": 0x8000, "ANY": 0xFFFF];
59 		joymodifierStrings = [0x0000: "buttons",0x0004: "dpad",0x0008: "axis"];
60 		devicetypeStrings = [Devicetype.JOYSTICK: "joystick", Devicetype.KEYBOARD: "keyboard", Devicetype.MOUSE: "mouse", Devicetype.TOUCHSCREEN: "touchscreen" ];
61 		keyNameDict = new Dictionary("system/keycodeNamings.sdl");
62 		joyButtonNameDict = new Dictionary("system/joyButtonNamings.sdl");
63 		joyAxisNameDict = new Dictionary("system/joyAxisNamings.sdl");
64 	}
65 	///Restores configuration profile
66 	public void restore(){
67 		
68 		//string s = cast(string)std.file.read(configFile);
69 		
70 		Tag root;
71 
72 		try{
73 			root = parseFile(path);
74 			foreach(Tag t0; root.tags){
75 				if(t0.name == "audio"){		//get values for the audio subsystem
76 					sfxVol = t0.getTagValue!int("soundVol", 100);
77 					musicVol = t0.getTagValue!int("musicVol", 100);
78 				}else if(t0.name == "video"){	//get values for the video subsystem
79 					foreach(Tag t1; t0.tags){
80 						switch(t1.name){
81 							case "driver": driver = t1.getValue!string("software"); break;
82 							case "scaling": scalingQuality = t1.getValue!string("nearest"); break;
83 							case "screenMode": driver = t1.getValue!string("windowed"); break;
84 							case "resolution": driver = t1.getValue!string("0"); break;
85 							case "threads": threads = t1.getValue!int(-1); break;
86 							default: break;
87 						}
88 					}
89 				}else if(t0.name == "input"){
90 					foreach(Tag t1; t0.tags){
91 						switch(t1.name){
92 							case "device": 
93 								InputDeviceData device;
94 								string name = t1.getValue!string("");
95 								int devicenumber = t1.getAttribute!int("devNum");
96 								switch(t1.expectAttribute!string("type")){
97 									case "keyboard": 
98 										device = InputDeviceData(devicenumber, Devicetype.KEYBOARD, name);
99 										foreach(Tag t2; t1.tags){
100 											if(t2.name is null){
101 												uint scanCode = t2.getAttribute!int("keyCode", keyNameDict.decode(t2.getAttribute!string("keyName")));
102 												keyBindingList ~= KeyBinding(stringToKeymod(t2.getAttribute!string("keyMod","NONE;")), scanCode, devicenumber, t2.expectValue!string(), Devicetype.KEYBOARD, stringToKeymod(t2.getAttribute!string("keyMod","ALL;")));
103 											}
104 										}
105 										break;
106 									case "joystick":
107 										device = InputDeviceData(devicenumber, Devicetype.JOYSTICK, name);
108 										foreach(Tag t2; t1.tags){
109 											if(t2.name is null){
110 												switch(t2.getAttribute!string("keymodifier")){
111 													case "buttons":
112 														uint scanCode = t2.getAttribute!int("keyCode", joyButtonNameDict.decode(t2.getAttribute!string("keyName")));
113 														keyBindingList ~= KeyBinding(0, scanCode, devicenumber, t2.expectValue!string(), Devicetype.JOYSTICK);
114 														break;
115 													case "dpad": 
116 														uint scanCode = t2.getAttribute!int("keyCode");
117 														keyBindingList ~= KeyBinding(4, scanCode, devicenumber, t2.expectValue!string(), Devicetype.JOYSTICK);
118 														break;
119 													case "axis":
120 														uint scanCode = t2.getAttribute!int("keyCode", joyAxisNameDict.decode(t2.getAttribute!string("keyName")));
121 														keyBindingList ~= KeyBinding(8, scanCode, devicenumber, t2.expectValue!string(), Devicetype.JOYSTICK);
122 														break;
123 													default:
124 														uint scanCode = t2.getAttribute!int("keyCode");
125 														keyBindingList ~= KeyBinding(0, scanCode, devicenumber, t2.expectValue!string(), Devicetype.JOYSTICK);
126 														break;
127 												}
128 											}else if(t2.name == "enableForceFeedback"){
129 												device.enableForceFeedback = t2.getValue!bool(true);
130 											}else if(t2.name == "axisDeadzone"){
131 												device.axisDeadZonePlus[t2.getAttribute!int("axisNumber", joyAxisNameDict.decode(t2.getAttribute!string("axisName")))] = t2.expectAttribute!int("plus");
132 												device.axisDeadZoneMinus[t2.getAttribute!int("axisNumber", joyAxisNameDict.decode(t2.getAttribute!string("axisName")))] = t2.expectAttribute!int("minus");
133 											}
134 										}
135 										break;
136 									case "mouse":
137 										device = InputDeviceData(devicenumber, Devicetype.MOUSE, name);
138 										foreach(Tag t2; t1.tags){
139 											if(t2.name is null){
140 												uint scanCode = t2.getAttribute!int("keyCode");
141 												keyBindingList ~= KeyBinding(0, scanCode, devicenumber, t2.expectValue!string(), Devicetype.MOUSE);
142 											}
143 										}
144 										break;
145 									default: 
146 										device = InputDeviceData(devicenumber, Devicetype.KEYBOARD, name);
147 										break;
148 								}
149 								inputDevices ~= device;
150 								break;
151 							default: break;
152 						}
153 					}
154 				}else if(t0.name == "etc"){
155 					foreach(Tag t1; t0.tags){
156 						auxillaryParameters ~= AuxillaryElements(t1.name(), t1.getValue!string());
157 					}
158 				}
159 			}
160 		}
161 		catch(ParseException e){
162 			writeln(e.msg);
163 		}
164 		
165 		
166 		
167 	}
168 	public void store(){
169 		
170 		
171 		Tag root = new Tag(null, null);		//, [Value(appName), Value(appVers)]
172 		
173 		Tag t0 = new Tag(root, null, "audio");
174 		Tag t0_0 = new Tag(t0, null, "soundVol", [Value(sfxVol)]);
175 		Tag t0_1 = new Tag(t0, null, "musicVolt", [Value(musicVol)]);
176 
177 		Tag t1 = new Tag(root, null, "video");
178 		Tag t1_0 = new Tag(t1, null, "driver", [Value(driver)]);
179 		Tag t1_1 = new Tag(t1, null, "scaling", [Value(scalingQuality)]);
180 		Tag t1_2 = new Tag(t1, null, "screenMode", [Value(screenMode)]);
181 		Tag t1_3 = new Tag(t1, null, "resolution", [Value(resolution)]);
182 		Tag t1_4 = new Tag(t1, null, "threads", [Value(threads)]);
183 
184 		Tag t2 = new Tag(root, null, "input");
185 		foreach(InputDeviceData idd; inputDevices){
186 			string devType = devicetypeStrings[idd.type];
187 			Tag t2_0 = new Tag(t2, null, "device", null, [new Attribute(null, "name",Value(idd.name)), new Attribute(null, "type", Value(devType)), new Attribute(null, "devNum", Value(idd.deviceNumber))]);
188 			if(idd.type == Devicetype.KEYBOARD){
189 				foreach(KeyBinding k; keyBindingList){
190 					if(k.devicetype == idd.type && k.devicenumber == idd.deviceNumber){
191 						Attribute key;
192 						string s = keyNameDict.encode(k.scancode);
193 						if(s is null){
194 							key = new Attribute(null, "keyCode", Value(to!int(k.scancode)));
195 						}else{
196 							key = new Attribute(null, "keyName", Value(s));
197 						}
198 						new Tag(t2_0, null, null, [Value(k.ID)], [key, new Attribute(null, "keyMod", Value(keymodToString(k.keymod))), new Attribute(null, "keyModIgnore", Value(keymodToString(k.keymodIgnore)))]);
199 					}
200 				}
201 			}else if(idd.type == Devicetype.JOYSTICK){
202 				foreach(KeyBinding k; keyBindingList){
203 					if(k.devicetype == idd.type && k.devicenumber == idd.deviceNumber){
204 						new Tag(t2_0, null, null, [Value(k.ID)], [new Attribute(null, "keyCode", Value(to!int(k.scancode))), new Attribute(null, "keyMod", Value(joymodifierStrings[k.keymod]))]);
205 						
206 					}
207 				}
208 				new Tag(t2_0, null, "enableForceFeedback", [Value(idd.enableForceFeedback)]);
209 				foreach(int i; idd.axisDeadZonePlus.byKey){
210 					new Tag(t2_0, null, "axisDeadzone", null, [new Attribute(null, "axisNumber", Value(i) ), new Attribute(null, "plus", Value(idd.axisDeadZonePlus[i]) ), new Attribute(null, "minus", Value(idd.axisDeadZoneMinus[i]) )]);
211 				}
212 			}else if(idd.type == Devicetype.MOUSE){
213 				foreach(KeyBinding k; keyBindingList){
214 					if(k.devicetype == idd.type && k.devicenumber == idd.deviceNumber){
215 						new Tag(t2_0, null, null, [Value(k.ID)], [new Attribute(null, "keyCode", Value(to!int(k.scancode)))]);
216 					}
217 				}
218 			}
219 		}
220 		Tag t3 = new Tag(root, null, "etc");
221 		foreach(AuxillaryElements ae; auxillaryParameters){
222 			new Tag(t3, null, ae.name, [Value(ae.value)]);
223 		}
224 		string data = root.toSDLDocument();
225 		std.file.write(path, data);
226 	}
227 	
228 	public ushort stringToKeymod(string s){
229 		if(s == "NONE;")	return KeyModifier.NONE;
230 		if(s == "ANY;")		return KeyModifier.ANY;
231 		string[] values = csvParser(s, ';');
232 		int result;
233 		foreach(t ; values){
234 			result += keymodifierStrings[t];
235 		}
236 		return to!ushort(result);
237 	}
238 
239 	public string keymodToString(ushort keymod){
240 		if(keymod == KeyModifier.NONE)
241 			return "NONE;";
242 		if(keymod == KeyModifier.ANY)
243 			return "ANY;";
244 		string result;
245 		if(keymod & KeyModifier.LSHIFT){
246 			result ~= "LSHIFT;";
247 		}
248 		if(keymod & KeyModifier.RSHIFT){
249 			result ~= "RSHIFT;";
250 		}
251 		if(keymod & KeyModifier.LCTRL){
252 			result ~= "LCTRL;";
253 		}
254 		if(keymod & KeyModifier.RCTRL){
255 			result ~= "RCTRL;";
256 		}
257 		if(keymod & KeyModifier.LALT){
258 			result ~= "LALT;";
259 		}
260 		if(keymod & KeyModifier.RALT){
261 			result ~= "RALT;";
262 		}
263 		if(keymod & KeyModifier.LGUI){
264 			result ~= "LGUI;";
265 		}
266 		if(keymod & KeyModifier.RGUI){
267 			result ~= "RGUI;";
268 		}
269 		if(keymod & KeyModifier.NUM){
270 			result ~= "NUM;";
271 		}
272 		if(keymod & KeyModifier.CAPS){
273 			result ~= "CAPS;";
274 		}
275 		if(keymod & KeyModifier.MODE){
276 			result ~= "MODE;";
277 		}
278 		if(keymod & KeyModifier.RESERVED){
279 			result ~= "RESERVED;";
280 		}
281 		return result;
282 	}
283 
284 	public string joymodToString(ushort s){
285 		switch(s){
286 			case JoyModifier.AXIS: return "AXIS";
287 			case JoyModifier.DPAD: return "DPAD";
288 			default: return "BUTTONS";
289 		}
290 	}
291 	public void useVideoMode(int mode, OutputScreen window){
292 		
293 	}
294 	public void autodetectVideoModes(int display = 0){
295 		int displaymodes = SDL_GetNumDisplayModes(display);
296 		//writeln(displaymodes);
297 		//writeln(to!string(SDL_GetError()));
298 		for(int i ; i <= displaymodes ; i++){
299 			SDL_DisplayMode d = SDL_DisplayMode();
300 			if(SDL_GetDisplayMode(display,i,&d) == 0){
301 				
302 				videoModes ~= d;
303 				
304 			}
305 		}
306 	}
307 	public int getNumOfVideoModes(){
308 		return videoModes.length;
309 	}
310 	public string videoModeToString(size_t n){
311 		return to!string(videoModes[n].w) ~ "x" ~ to!string(videoModes[n].h) ~ "@" ~ to!string(videoModes[n].refresh_rate) ~ "Hz";
312 	}
313 	public static void setVaultPath(const char* developer, const char* application){
314 		vaultPath = to!string(SDL_GetPrefPath(developer, application));
315 	}
316 	public static string getVaultPath(){
317 		return vaultPath;
318 	}
319 
320 }
321 /**
322  * Deprecated, not up to current standards, will be upgraded to take advantage of the SDLang format.
323  */
324 public struct AuxillaryElements{
325 	public string value, name;
326 	public this(string name, string value){
327 		this.name = name;
328 		this.value = value;
329 	}
330 }
331 
332 /**
333  * Stores basic InputDevice info alongside with some additional settings
334  */
335 public struct InputDeviceData{
336 	public int deviceNumber;
337 	public Devicetype type;
338 	public string name;
339 	public bool enableForceFeedback;
340 	public int[int] axisDeadZonePlus, axisDeadZoneMinus;
341 	public this(int deviceNumber, Devicetype type, string name){
342 		this.deviceNumber = deviceNumber;
343 		this.type = type;
344 		this.name = name;
345 	}
346 }
347 
348 /*public class VideoMode{
349 	public int displayIndex, modeIndex;
350 	public SDL_DisplayMode displaymode;
351 	public this (){
352 
353 	}
354 	public override string toString(){
355 		return to!string(displaymode.w) ~ "x" ~ to!string(displaymode.h) ~ "@" ~ to!string(displaymode.refresh_rate) ~ "Hz";
356 	}
357 }*/
358 /**
359  * Default keywords to look up for common video settings
360  */
361 public enum VideoConfigDefaults : string{
362 	SCREENMODE		=	"screenMode",
363 	RESOLUTION		=	"resolution",
364 	SCALINGQUALITY	=	"scalingQuality",
365 	DRIVER			=	"driver",
366 	THREADS			=	"threads",
367 }