comparison mde/Options.d @ 32:316b0230a849

Lots more work on the GUI. Also renamed lots of files. Lots of changes to the GUI. Renderer is now used exclusively for rendering and WidgetDecoration is gone. Renamed lots of files to conform to case policies. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 30 Apr 2008 18:05:56 +0100
parents
children 57d000574d75
comparison
equal deleted inserted replaced
31:baa87e68d7dc 32:316b0230a849
1 /* LICENSE BLOCK
2 Part of mde: a Modular D game-oriented Engine
3 Copyright © 2007-2008 Diggory Hardy
4
5 This program is free software: you can redistribute it and/or modify it under the terms
6 of the GNU General Public License as published by the Free Software Foundation, either
7 version 2 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */
15
16 /** This module handles stored options, currently all except input maps.
17 *
18 * The purpose of having all options centrally controlled is to allow generic handling by the GUI
19 * and ease saving and loading of values. The Options class is only really designed around handling
20 * small numbers of variables for now.
21 */
22 module mde.Options;
23
24 import mde.exception;
25
26 import mde.mergetag.Reader;
27 import mde.mergetag.Writer;
28 import mde.mergetag.DataSet;
29 import mde.mergetag.exception;
30 import mde.resource.paths;
31
32 import tango.scrapple.text.convert.parseTo : parseTo;
33 import tango.scrapple.text.convert.parseFrom : parseFrom;
34
35 import tango.core.Exception : ArrayBoundsException;
36 import tango.util.log.Log : Log, Logger;
37
38 /** Base class for handling options.
39 *
40 * This class itself handles no options and should not be instantiated, but provides a sub-classable
41 * base for generic options handling. Also, the static portion of this class tracks sub-class
42 * instances and provides loading and saving methods.
43 *
44 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references
45 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from
46 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will
47 * not be saved; use, for example, Options.setBool(...).
48 *
49 * Details: Options sub-classes hold associative arrays of pointers to all option variables, with a
50 * char[] id. This list is used for saving, loading and to provide generic GUI options screens. The
51 * built-in support in Options is only for bool, int and char[] types (a float type may get added).
52 * Further to this, a generic class is used to store all options which have been changed, and if any
53 * have been changed, is merged with options from the user conf dir and saved on exit.
54 */
55 class Options : IDataSection
56 {
57 // No actual options are stored by this class. However, much of the infrastructure is
58 // present since it need not be redefined in sub-classes.
59
60 // The "pointer lists":
61 protected bool* [ID] optsBool;
62 protected char[]*[ID] optsCharA;
63 protected int* [ID] optsInt;
64
65 //BEGIN Mergetag loading/saving code
66 void addTag (char[] tp, ID id, char[] dt) {
67 if (tp == "bool") {
68 bool** p = id in optsBool;
69 if (p !is null) **p = parseTo!(bool) (dt);
70 } else if (tp == "char[]") {
71 char[]** p = id in optsCharA;
72 if (p !is null) **p = parseTo!(char[]) (dt);
73 } else if (tp == "int") {
74 int** p = id in optsInt;
75 if (p !is null) **p = parseTo!(int) (dt);
76 }
77 }
78 void writeAll (ItemDelg dlg) {
79 foreach (ID id, bool* val; optsBool) dlg ("bool" , id, parseFrom!(bool ) (*val));
80 foreach (ID id, char[]* val; optsCharA) dlg ("char[]", id, parseFrom!(char[]) (*val));
81 foreach (ID id, int* val; optsInt) dlg ("int" , id, parseFrom!(int ) (*val));
82 }
83 //END Mergetag loading/saving code
84
85 //BEGIN Static
86 /** Add an options sub-class to the list for loading and saving.
87 *
88 * Call from static this() (before Init calls load()). */
89 static void addOptionsClass (Options c, char[] i)
90 in { // Trap a couple of potential coding errors:
91 assert (c !is null); // Instance must be created before calling addOptionsClass
92 assert (((cast(ID) i) in subClasses) is null); // Don't allow a silent replacement
93 } body {
94 subClasses[cast(ID) i] = c;
95 subClassChanges[cast(ID) i] = new OptionsGeneric;
96 }
97
98 /** Set option symbol of Options class subClass to val.
99 *
100 * Due to the way options are handled generically, string IDs must be used to access the options
101 * via hash-maps, which is a little slower than direct access but necessary since the option
102 * must be changed in two separate places. */
103 private static const ERR_MSG = "Options.setXXX called with incorrect parameters!";
104 static void setBool (char[] subClass, char[] symbol, bool val) {
105 changed = true; // something got set (don't bother checking this isn't what it already was)
106
107 try {
108 *(subClasses[cast(ID) subClass].optsBool[cast(ID) symbol]) = val;
109 subClassChanges[cast(ID) subClass].setBool (cast(ID) symbol, val);
110 } catch (ArrayBoundsException) {
111 // log and ignore:
112 logger.error (ERR_MSG);
113 }
114 }
115 static void setInt (char[] subClass, char[] symbol, int val) {
116 changed = true; // something got set (don't bother checking this isn't what it already was)
117
118 try {
119 *(subClasses[cast(ID) subClass].optsInt[cast(ID) symbol]) = val;
120 subClassChanges[cast(ID) subClass].setInt (cast(ID) symbol, val);
121 } catch (ArrayBoundsException) {
122 // log and ignore:
123 logger.error (ERR_MSG);
124 }
125 }
126 static void setCharA (char[] subClass, char[] symbol, char[] val) {
127 changed = true; // something got set (don't bother checking this isn't what it already was)
128
129 try {
130 *(subClasses[cast(ID) subClass].optsCharA[cast(ID) symbol]) = val;
131 subClassChanges[cast(ID) subClass].setCharA (cast(ID) symbol, val);
132 } catch (ArrayBoundsException) {
133 // log and ignore:
134 logger.error (ERR_MSG);
135 }
136 }
137
138 // Track all sections for saving/loading/other generic handling.
139 static Options[ID] subClasses;
140 static OptionsGeneric[ID] subClassChanges;
141 static bool changed = false; // any changes at all, i.e. do we need to save?
142
143 /* Load/save options from file.
144 *
145 * If the file doesn't exist, no reading is attempted (options are left at default values).
146 */
147 private static const fileName = "options";
148 private static const MT_LOAD_EXC = "Loading options aborted:";
149 static void load () {
150 // Check it exists (if not it should still be created on exit).
151 // Don't bother checking it's not a folder, because it could still be a block or something.
152 if (!confDir.exists (fileName)) return;
153
154 try {
155 IReader reader;
156 reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
157 reader.dataSecCreator = delegate IDataSection(ID id) {
158 /* Recognise each defined section, and return null for unrecognised sections. */
159 Options* p = id in subClasses;
160 if (p !is null) return *p;
161 else return null;
162 };
163 reader.read;
164 } catch (MTException e) {
165 logger.fatal (MT_LOAD_EXC);
166 logger.fatal (e.msg);
167 throw new optionsLoadException ("Mergetag exception (see above message)");
168 }
169 }
170 static void save () {
171 if (!changed) return; // no changes to save
172
173 DataSet ds = new DataSet();
174 foreach (id, sec; subClassChanges) ds.sec[id] = sec;
175
176 // Read locally-stored options
177 try {
178 IReader reader;
179 reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, ds);
180 reader.dataSecCreator = delegate IDataSection(ID id) {
181 return null; // All recognised sections are already in the dataset.
182 };
183 reader.read;
184 } catch (MTFileIOException) {
185 // File either didn't exist or couldn't be opened.
186 // Presuming the former, this is not a problem.
187 } catch (MTException e) {
188 // Log a message and continue, overwriting the file:
189 logger.error (MT_LOAD_EXC);
190 logger.error (e.msg);
191 }
192
193 try {
194 IWriter writer;
195 writer = confDir.makeMTWriter (fileName, ds);
196 writer.write();
197 } catch (MTException e) {
198 logger.error ("Saving options aborted! Reason:");
199 logger.error (e.msg);
200 }
201 }
202
203 private static Logger logger;
204 static this() {
205 logger = Log.getLogger ("mde.options");
206 }
207 //END Static
208
209 //BEGIN Templates
210 template store(A...) {
211 alias A a;
212 }
213 template decRecurse(char[] A, B...) {
214 static if (B.length) const char[] decRecurse = A ~ ", " ~ decRecurse!(B);
215 else const char[] decRecurse = A;
216 }
217 template decBool(A...) {
218 const char[] decBool = "bool " ~ decRecurse!(A) ~ ";\n";
219 }
220 template decCharA(A...) {
221 const char[] decCharA = "char[] " ~ decRecurse!(A) ~ ";\n";
222 }
223 template decInt(A...) {
224 const char[] decInt = "int " ~ decRecurse!(A) ~ ";\n";
225 }
226 template aaRecurse(char[] A, B...) {
227 static if (B.length) const char[] aaRecurse = "\""~A~"\"[]:&"~A ~ ", " ~ aaRecurse!(B);
228 else const char[] aaRecurse = "\""~A~"\"[]:&"~A;
229 }
230 template aaBool(A...) {
231 const char[] aaBool = "optsBool = [" ~ aaRecurse!(A) ~ "];\n";
232 }
233 template aaInt(A...) {
234 const char[] aaInt = "optsInt = [" ~ aaRecurse!(A) ~ "];\n";
235 }
236 template aaCharA(A...) {
237 const char[] aaCharA = "optsCharA = [" ~ aaRecurse!(A) ~ "];\n";
238 }
239 //END Templates
240 }
241
242 /* Special class to store all locally changed options, whatever the section. */
243 class OptionsGeneric : Options {
244 // These store the actual values, but are never accessed directly except when initially added.
245 // optsX store pointers to each item added along with the ID and are used for access.
246 bool[] bools;
247 int[] ints;
248 char[][] strings;
249
250 this () {}
251
252 void setBool (ID id, bool x) {
253 bool** p = id in optsBool;
254 if (p !is null) **p = x;
255 else {
256 bools ~= x;
257 optsBool[id] = &bools[$-1];
258 }
259 }
260 void setInt (ID id, int x) {
261 int** p = id in optsInt;
262 if (p !is null) **p = x;
263 else {
264 ints ~= x;
265 optsInt[id] = &ints[$-1];
266 }
267 }
268 void setCharA (ID id, char[] x) {
269 char[]** p = id in optsCharA;
270 if (p !is null) **p = x;
271 else {
272 strings ~= x;
273 optsCharA[id] = &strings[$-1];
274 }
275 }
276
277 //BEGIN Mergetag loading/saving code
278 // Reverse priority: only load symbols not currently existing
279 void addTag (char[] tp, ID id, char[] dt) {
280 if (tp == "bool") {
281 if ((id in optsBool) is null) {
282 bools ~= parseTo!(bool) (dt);
283 optsBool[id] = &bools[$-1];
284 }
285 } else if (tp == "char[]") {
286 if ((id in optsCharA) is null) {
287 strings ~= parseTo!(char[]) (dt);
288 optsCharA[id] = &strings[$-1];
289 }
290 char[]** p = id in optsCharA;
291 if (p !is null) **p = parseTo!(char[]) (dt);
292 } else if (tp == "int") {
293 if ((id in optsInt) is null) {
294 ints ~= parseTo!(int) (dt);
295 optsInt[id] = &ints[$-1];
296 }
297 }
298 }
299 //END Mergetag loading/saving code
300 }
301
302 /* NOTE: These Options classes use templates to ease inserting contents.
303 *
304 * Each entry has an I18nTranslation entry; see data/L10n/ClassName.mtt for descriptions.
305 *
306 * To create a new class, just copy and paste anywhere and adjust.
307 */
308
309 /** A home for all miscellaneous options, at least for now. */
310 OptionsMisc miscOpts;
311 class OptionsMisc : Options {
312 alias store!("useThreads") BOOL;
313 alias store!("logLevel") INT;
314 alias store!("L10n") CHARA;
315
316 mixin (decBool!(BOOL.a));
317 mixin (decInt!(INT.a));
318 mixin (decCharA!(CHARA.a));
319
320 this () {
321 mixin (aaBool!(BOOL.a));
322 mixin (aaInt!(INT.a));
323 mixin (aaCharA!(CHARA.a));
324 }
325
326 static this() {
327 miscOpts = new OptionsMisc;
328 Options.addOptionsClass (miscOpts, "misc");
329 }
330 }