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