comparison mde/lookup/Options.d @ 94:9520cc0448e5

Boolean options are now encapsulated within a Content class (currently an experiment). This should facilitate generic option editing widgets.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 23 Oct 2008 17:45:49 +0100
parents 97e6dce08037
children 2a364c7d82c9
comparison
equal deleted inserted replaced
93:08a4ae11454b 94:9520cc0448e5
28 module mde.lookup.Options; 28 module mde.lookup.Options;
29 29
30 import mde.setup.paths; 30 import mde.setup.paths;
31 import mde.exception; 31 import mde.exception;
32 32
33 public import mde.gui.content.Content;
34
33 import mde.file.mergetag.Reader; 35 import mde.file.mergetag.Reader;
34 import mde.file.mergetag.Writer; 36 import mde.file.mergetag.Writer;
35 import mde.file.mergetag.DataSet; 37 import mde.file.mergetag.DataSet;
36 import mde.file.serialize; 38 import mde.file.serialize;
37 39
50 * 52 *
51 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references 53 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references
52 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from 54 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from
53 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will 55 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will
54 * not be saved; instead use set(), for example, miscOpts.set!(char[])("L10n","en-GB"). Use an 56 * not be saved; instead use set(), for example, miscOpts.set!(char[])("L10n","en-GB"). Use an
55 * example like OptionsMisc as a template for creating a new Options sub-class. 57 * example like MiscOptions as a template for creating a new Options sub-class.
56 * 58 *
57 * Optionally, overload the validate() function. This is called after loading, allowing conditions 59 * Optionally, overload the validate() function. This is called after loading, allowing conditions
58 * to be enforced on variables. Use set!()() to change the variables. If an exception is thrown, 60 * to be enforced on variables. Use set!()() to change the variables. If an exception is thrown,
59 * init will abort and the executable won't start. 61 * init will abort and the executable won't start.
60 * 62 *
77 protected this() {} /// Do not instantiate directly. 79 protected this() {} /// Do not instantiate directly.
78 80
79 // All supported types, for generic handling via templates. It should be possible to change 81 // All supported types, for generic handling via templates. It should be possible to change
80 // the supported types simply by changing this list now (untested). 82 // the supported types simply by changing this list now (untested).
81 template store(A...) { alias A store; } 83 template store(A...) { alias A store; }
82 alias store!(bool, int, double, char[]) TYPES; 84 alias store!(int, double, char[]) TYPES;//FIXME removed bool
83 //BEGIN Templates: internal 85 //BEGIN Templates: internal
84 private { 86 private {
85 // Get name of a type. Basically just stringof, but special handling for arrays. 87 // Get name of a type. Basically just stringof, but special handling for arrays.
86 // Use TName!(T) for a valid symbol name, and T.stringof for a type. 88 // Use TName!(T) for a valid symbol name, and T.stringof for a type.
87 template TName(T : T[]) { 89 template TName(T : T[]) {
90 template TName(T) { 92 template TName(T) {
91 const char[] TName = T.stringof; 93 const char[] TName = T.stringof;
92 } 94 }
93 95
94 // Pointer lists 96 // Pointer lists
95 template PLists(T, A...) { 97 template PLists(A...) {
96 static if (A.length) { 98 static if (A.length) {
97 const char[] PLists = T.stringof~"*[ID] opts"~TName!(T)~";\n" ~ PLists!(A); 99 static if (is (T == bool)) {
98 } else 100 const char[] PLists = PLists!(A[1..$]);
99 const char[] PLists = T.stringof~"*[ID] opts"~TName!(T)~";\n"; 101 } else
102 const char[] PLists = A[0].stringof~"*[ID] opts"~TName!(A[0])~";\n" ~ PLists!(A[1..$]);
103 } else
104 const char[] PLists = "";
100 } 105 }
101 106
102 // True if type is one of A 107 // True if type is one of A
103 template TIsIn(T, A...) { 108 template TIsIn(T, A...) {
104 static if (A.length) { 109 static if (A.length) {
110 const bool TIsIn = false; // no more possibilities 115 const bool TIsIn = false; // no more possibilities
111 } 116 }
112 117
113 // For addTag 118 // For addTag
114 template addTagMixin(T, A...) { 119 template addTagMixin(T, A...) {
115 const char[] ifBlock = `if (tp == "`~T.stringof~`") { 120 static if (is(T == bool)) {
116 `~T.stringof~`** p = id in opts`~TName!(T)~`; 121 const char[] ifBlock = `if (tp == "`~T.stringof~`") {
117 if (p !is null) **p = parseTo!(`~T.stringof~`) (dt); 122 auto p = id in opts;
118 }`; 123 if (p) {
124 auto q = cast(BoolContent) (*p);
125 if (q) q = parseTo!(`~T.stringof~`) (dt);
126 }
127 }`;
128 } else
129 const char[] ifBlock = `if (tp == "`~T.stringof~`") {
130 `~T.stringof~`** p = id in opts`~TName!(T)~`;
131 if (p !is null) **p = parseTo!(`~T.stringof~`) (dt);
132 }`;
119 static if (A.length) 133 static if (A.length)
120 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin; 134 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin;
121 else 135 else
122 const char[] addTagMixin = ifBlock; 136 const char[] addTagMixin = ifBlock;
123 } 137 }
127 static if (A.length) { 141 static if (A.length) {
128 const char[] writeAllMixin = 142 const char[] writeAllMixin =
129 `foreach (ID id, `~A[0].stringof~`* val; opts`~TName!(A[0])~`) dlg ("`~A[0].stringof~`", id, parseFrom!(`~A[0].stringof~`) (*val));` ~ writeAllMixin!(A[1..$]); 143 `foreach (ID id, `~A[0].stringof~`* val; opts`~TName!(A[0])~`) dlg ("`~A[0].stringof~`", id, parseFrom!(`~A[0].stringof~`) (*val));` ~ writeAllMixin!(A[1..$]);
130 } else 144 } else
131 const char[] writeAllMixin = ``; 145 const char[] writeAllMixin = ``;
146 }
147
148 // For list
149 template listMixin(A...) {
150 static if (A.length) {
151 const char[] listMixin = `ret ~= opts`~TName!(A[0])~`.keys;` ~ listMixin!(A[1..$]);
152 } else
153 const char[] listMixin = ``;
132 } 154 }
133 } 155 }
134 //END Templates: internal 156 //END Templates: internal
135 157
136 158
142 void addOptionsClass (Options c, char[] i) 164 void addOptionsClass (Options c, char[] i)
143 in { // Trap a couple of potential coding errors: 165 in { // Trap a couple of potential coding errors:
144 assert (c !is null); // Instance must be created before calling addOptionsClass 166 assert (c !is null); // Instance must be created before calling addOptionsClass
145 assert (((cast(ID) i) in subClasses) is null); // Don't allow a silent replacement 167 assert (((cast(ID) i) in subClasses) is null); // Don't allow a silent replacement
146 } body { 168 } body {
169 c.secName = i;
147 subClasses[cast(ID) i] = c; 170 subClasses[cast(ID) i] = c;
148 } 171 }
149 172
150 // Track all sections for saving/loading/other generic handling. 173 // Track all sections for saving/loading/other generic handling.
151 Options[ID] subClasses; 174 Options[ID] subClasses;
231 * 254 *
232 * Due to the way options are handled generically, string IDs must be used to access the options 255 * Due to the way options are handled generically, string IDs must be used to access the options
233 * via hash-maps, which is a little slower than direct access but necessary since the option 256 * via hash-maps, which is a little slower than direct access but necessary since the option
234 * must be changed in two separate places. */ 257 * must be changed in two separate places. */
235 void set(T) (char[] symbol, T val) { 258 void set(T) (char[] symbol, T val) {
236 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof); 259 static assert (TIsIn!(T,TYPES) && !is(T == bool), "Options does not support type "~T.stringof);
237 260
238 changed = true; // something got set (don't bother checking this isn't what it already was) 261 changed = true; // something got set (don't bother checking this isn't what it already was)
239 262
240 try { 263 try {
241 mixin (`*(opts`~TName!(T)~`[cast(ID) symbol]) = val;`); 264 mixin (`*(opts`~TName!(T)~`[cast(ID) symbol]) = val;`);
243 } catch (ArrayBoundsException) { 266 } catch (ArrayBoundsException) {
244 // log and ignore: 267 // log and ignore:
245 logger.error ("Options.set: invalid symbol"); 268 logger.error ("Options.set: invalid symbol");
246 } 269 }
247 } 270 }
248 271 /+
249 /** Get option symbol of an Options sub-class. 272 /** Get option symbol of an Options sub-class.
250 * 273 *
251 * Using this method to read an option is not necessary, but allows for generic use. */ 274 * Using this method to read an option is not necessary, but allows for generic use. */
252 T get(T) (char[] symbol) { 275 T get(T) (char[] symbol) {
253 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof); 276 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof);
258 return *(optsVars[cast(ID) symbol]); 281 return *(optsVars[cast(ID) symbol]);
259 } catch (ArrayBoundsException) { 282 } catch (ArrayBoundsException) {
260 // log and ignore: 283 // log and ignore:
261 logger.error ("Options.get: invalid symbol"); 284 logger.error ("Options.get: invalid symbol");
262 } 285 }
263 } 286 }+/
264 287
265 /** List the names of all options of a specific type. */ 288 /** List the names of all options of a specific type. */
266 char[][] list(T) () { 289 char[][] list () {
267 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof); 290 char[][] ret;
268 291 mixin (listMixin!(TYPES));
269 mixin (`alias opts`~TName!(T)~` optsVars;`); 292 return ret;
270 293 }
271 return optsVars.keys; 294
295 /// Get all Options stored with a ValueContent.
296 ValueContent[char[]] content() {
297 return opts;
272 } 298 }
273 299
274 /// Variable validate function. This implementation does nothing. 300 /// Variable validate function. This implementation does nothing.
275 void validate() {} 301 void validate() {}
276 302
277 protected { 303 protected {
304 char[] secName; // name of this option setting; set null after translation is loaded
278 OptionChanges optionChanges; // all changes to options (for saving) 305 OptionChanges optionChanges; // all changes to options (for saving)
279 306
280 // The "pointer lists", e.g. char[]*[ID] optscharA; 307 // The "pointer lists", e.g. char[]*[ID] optscharA;
281 mixin (PLists!(TYPES)); 308 mixin (PLists!(TYPES)); //FIXME adds unused optsbool
309 ValueContent[char[]] opts; // generic list of option values
282 } 310 }
283 311
284 //BEGIN Mergetag loading/saving code 312 //BEGIN Mergetag loading/saving code
285 void addTag (char[] tp, ID id, char[] dt) { 313 void addTag (char[] tp, ID id, char[] dt) {
286 mixin(addTagMixin!(TYPES).addTagMixin); 314 mixin(addTagMixin!(TYPES).addTagMixin);
293 //END Non-static 321 //END Non-static
294 322
295 323
296 //BEGIN Templates: impl & optionsThis 324 //BEGIN Templates: impl & optionsThis
297 private { 325 private {
326 // Replace, e.g., bool, with BoolContent
327 template contentName(A) {
328 static if (is(A == bool)) {
329 const char[] contentName = "BoolContent";
330 } else static if (is(A == int)) {
331 const char[] contentName = "int";// no IntContent yet
332 } else static if (is(A == double)) {
333 const char[] contentName = "double";
334 } else static if (is(A == char[])) {
335 const char[] contentName = "char[]";
336 } else
337 static assert (false, "unsuppurted type: "~ A);
338 }
298 // Return index of first comma, or halts if not found. 339 // Return index of first comma, or halts if not found.
299 template cIndex(char[] A) { 340 template cIndex(char[] A) {
300 static if (A.length == 0) 341 static if (A.length == 0)
301 static assert (false, "Error in implementation"); 342 static assert (false, "Error in implementation");
302 else static if (A[0] == ',') 343 else static if (A[0] == ',')
311 else static if (A[0] == ';') 352 else static if (A[0] == ';')
312 const size_t scIndex = 0; 353 const size_t scIndex = 0;
313 else 354 else
314 const size_t scIndex = 1 + scIndex!(A[1..$]); 355 const size_t scIndex = 1 + scIndex!(A[1..$]);
315 } 356 }
316 // Look for "type symbols;" in A and return symbols as a comma separated list of names 357 /* Look for "type symbols;" in A and return symbols as a comma separated list of names
317 // (even if type is encountered more than once). Output may contain spaces and, if 358 (even if type is encountered more than once). Output may contain spaces and will have a
318 // non-empty, will contain a trailing comma. Assumes scIndex always returns less than A.$. 359 trailing comma unless no match was found in which case an empty string is returned.
360 Assumes scIndex always returns less than A.$ . */
319 template parseT(char[] type, char[] A) { 361 template parseT(char[] type, char[] A) {
320 static if (A.length < type.length + 1) // end of input, no match 362 static if (A.length < type.length + 1) // end of input, no match
321 const char[] parseT = ""; 363 const char[] parseT = "";
322 else static if (A[0] == ' ') // leading whitespace: skip 364 else static if (A[0] == ' ') // leading whitespace: skip
323 const char[] parseT = parseT!(type, A[1..$]); 365 const char[] parseT = parseT!(type, A[1..$]);
325 const char[] parseT = A[type.length+1 .. scIndex!(A)] ~ "," ~ 367 const char[] parseT = A[type.length+1 .. scIndex!(A)] ~ "," ~
326 parseT!(type, A[scIndex!(A)+1 .. $]); 368 parseT!(type, A[scIndex!(A)+1 .. $]);
327 else // no match 369 else // no match
328 const char[] parseT = parseT!(type, A[scIndex!(A)+1 .. $]); 370 const char[] parseT = parseT!(type, A[scIndex!(A)+1 .. $]);
329 } 371 }
330 // May have a trailing comma. Assumes cIndex always returns less than A.$. 372 // May have a trailing comma. Assumes cIndex always returns less than A.$ .
331 template aaVars(char[] A) { 373 template aaVars(char[] A) {
332 static if (A.length == 0) 374 static if (A.length == 0)
333 const char[] aaVars = ""; 375 const char[] aaVars = "";
334 else static if (A[0] == ' ') 376 else static if (A[0] == ' ')
335 const char[] aaVars = aaVars!(A[1..$]); 377 const char[] aaVars = aaVars!(A[1..$]);
336 else 378 else
337 const char[] aaVars = "\""~A[0..cIndex!(A)]~"\"[]:&"~A[0..cIndex!(A)] ~ "," ~ 379 const char[] aaVars = "\""~A[0..cIndex!(A)]~"\"[]:&"~A[0..cIndex!(A)] ~ "," ~
338 aaVars!(A[cIndex!(A)+1..$]); 380 aaVars!(A[cIndex!(A)+1..$]);
381 }
382 // May have a trailing comma. Assumes cIndex always returns less than A.$ .
383 template aaVarsBool(char[] A) {//FIXME
384 static if (A.length == 0)
385 const char[] aaVarsBool = "";
386 else static if (A[0] == ' ')
387 const char[] aaVarsBool = aaVarsBool!(A[1..$]);
388 else
389 const char[] aaVarsBool = "\""~A[0..cIndex!(A)]~"\"[]:"~A[0..cIndex!(A)] ~ "," ~
390 aaVarsBool!(A[cIndex!(A)+1..$]);
339 } 391 }
340 // strip Trailing Comma 392 // strip Trailing Comma
341 template sTC(char[] A) { 393 template sTC(char[] A) {
342 static if (A.length && A[$-1] == ',') 394 static if (A.length && A[$-1] == ',')
343 const char[] sTC = A[0..$-1]; 395 const char[] sTC = A[0..$-1];
351 else static if (A[0] == ' ') 403 else static if (A[0] == ' ')
352 const char[] listOrNull = listOrNull!(A[1..$]); 404 const char[] listOrNull = listOrNull!(A[1..$]);
353 else 405 else
354 const char[] listOrNull = "["~A~"]"; 406 const char[] listOrNull = "["~A~"]";
355 } 407 }
408 // if B is empty return an empty string otherswise return what's below:
409 template catOrNothing(char[] A,char[] B) {
410 static if (B.length)
411 const char[] catOrNothing = A~` `~sTC!(B)~";\n";
412 else
413 const char[] catOrNothing = ``;
414 }
415 // foreach decl...
416 template createBCs(char[] A) {
417 static if (A.length == 0)
418 const char[] createBCs = "";
419 else static if (A[0] == ' ')
420 const char[] createBCs = createBCs!(A[1..$]);
421 else
422 const char[] createBCs = A[0..cIndex!(A)]~ " = new BoolContent (false);\n"~
423 createBCs!(A[cIndex!(A)+1..$]);
424 }
356 // for recursing on TYPES 425 // for recursing on TYPES
357 template optionsThisInternal(char[] A, B...) { 426 template optionsThisInternal(char[] A, B...) {
358 static if (B.length) { 427 static if (B.length) {
428 static if (is(B[0] == bool)) {//FIXME
429 const char[] optionsThisInternal = createBCs!(parseT!(B[0].stringof,A))~
430 `opts = `~listOrNull!(sTC!(aaVarsBool!(parseT!(B[0].stringof,A))))~";\n" ~
431 optionsThisInternal!(A,B[1..$]);
432 } else
359 const char[] optionsThisInternal = `opts`~TName!(B[0])~` = `~listOrNull!(sTC!(aaVars!(parseT!(B[0].stringof,A))))~";\n" ~ optionsThisInternal!(A,B[1..$]); 433 const char[] optionsThisInternal = `opts`~TName!(B[0])~` = `~listOrNull!(sTC!(aaVars!(parseT!(B[0].stringof,A))))~";\n" ~ optionsThisInternal!(A,B[1..$]);
360 } else 434 } else
361 const char[] optionsThisInternal = ``; 435 const char[] optionsThisInternal = ``;
362 } 436 }
437 template declValsInternal(char[] A, B...) {
438 static if (B.length) {
439 const char[] declValsInternal = catOrNothing!(contentName!(B[0]),parseT!(B[0].stringof,A)) ~ declValsInternal!(A,B[1..$]);
440 } else
441 const char[] declValsInternal = ``;
442 }
363 } protected { 443 } protected {
444 /** Declares the values.
445 *
446 * Basic types are replaced with a ValueContent class to keep the option synchronized and
447 * generalize use. */
448 template declVals(char[] A) {
449 const char[] declVals = declValsInternal!(A, TYPES,bool);
450 }
364 /** Produces the implementation code to go in the constuctor. */ 451 /** Produces the implementation code to go in the constuctor. */
365 template optionsThis(char[] A) { 452 template optionsThis(char[] A) {
366 const char[] optionsThis = 453 const char[] optionsThis =
367 "optionChanges = new OptionChanges;\n" ~ 454 "optionChanges = new OptionChanges;\n" ~
368 optionsThisInternal!(A,TYPES); 455 optionsThisInternal!(A,TYPES,bool);
369 } 456 }
370 /+ Needs too many custom parameters to be worth it? Plus makes class less readable. 457 /+ Needs too many custom parameters to be worth it? Plus makes class less readable.
371 /** Produces the implementation code to go in the static constuctor. */ 458 /** Produces the implementation code to go in the static constuctor. */
372 template optClassAdd(char[] symb) { 459 template optClassAdd(char[] symb) {
373 const char[] optClassAdd = symb ~ "=new "~classinfo(this).name~";\n";//Options.addOptionsClass("~symb~", );\n"; 460 const char[] optClassAdd = symb ~ "=new "~classinfo(this).name~";\n";//Options.addOptionsClass("~symb~", );\n";
379 * mixin (impl ("bool a, b; int i;")); 466 * mixin (impl ("bool a, b; int i;"));
380 * --- 467 * ---
381 * 468 *
382 * In case this() needs to be customized, mixin(impl!(A)) is equivalent to: 469 * In case this() needs to be customized, mixin(impl!(A)) is equivalent to:
383 * --- 470 * ---
384 * mixin (A~` 471 * mixin (declVals!(A)~`
385 this () { 472 this () {
386 `~optionsThis!(A)~` 473 `~optionsThis!(A)~`
387 }`); 474 }`);
388 * --- 475 * ---
389 * 476 *
393 * necessary. 480 * necessary.
394 * 481 *
395 * Extending: mixins could also be used for the static this() {...} or even the whole 482 * Extending: mixins could also be used for the static this() {...} or even the whole
396 * class, but doing so would rather decrease readability of any implementation. */ 483 * class, but doing so would rather decrease readability of any implementation. */
397 template impl(char[] A /+, char[] symb+/) { 484 template impl(char[] A /+, char[] symb+/) {
398 const char[] impl = A~"\nthis(){\n"~optionsThis!(A)~"}"; 485 const char[] impl = declVals!(A)~"\nthis(){\n"~optionsThis!(A)~"}";
399 // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}" 486 // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}"
400 } 487 }
401 } 488 }
402 //END Templates: impl & optionsThis 489 //END Templates: impl & optionsThis
403 } 490 }
464 * 551 *
465 * To create a new Options sub-class, just copy, paste and adjust. 552 * To create a new Options sub-class, just copy, paste and adjust.
466 */ 553 */
467 554
468 /** A home for all miscellaneous options, at least for now. */ 555 /** A home for all miscellaneous options, at least for now. */
469 OptionsMisc miscOpts; 556 MiscOptions miscOpts;
470 class OptionsMisc : Options { 557 class MiscOptions : Options {
471 mixin (impl!("bool exitImmediately; int maxThreads, logOptions; double pollInterval; char[] L10n, a,b,c,g,z;")); 558 const A = "bool exitImmediately; int maxThreads, logOptions; double pollInterval; char[] L10n;";
559 //pragma (msg, impl!(A));
560 mixin (impl!(A));
472 561
473 void validate() { 562 void validate() {
474 // Try to enforce sensible values, whilst being reasonably flexible: 563 // Try to enforce sensible values, whilst being reasonably flexible:
475 if (miscOpts.maxThreads < 1 || miscOpts.maxThreads > 64) { 564 if (miscOpts.maxThreads < 1 || miscOpts.maxThreads > 64) {
476 logger.warn ("maxThreads must be in the range 1-64. Defaulting to 4."); 565 logger.warn ("maxThreads must be in the range 1-64. Defaulting to 4.");
479 if (pollInterval !<= 1.0 || pollInterval !>= 0.0) 568 if (pollInterval !<= 1.0 || pollInterval !>= 0.0)
480 set!(double) ("pollInterval", 0.01); 569 set!(double) ("pollInterval", 0.01);
481 } 570 }
482 571
483 static this() { 572 static this() {
484 miscOpts = new OptionsMisc; 573 miscOpts = new MiscOptions;
485 Options.addOptionsClass (miscOpts, "misc"); 574 Options.addOptionsClass (miscOpts, "MiscOptions");
486 } 575 }
487 } 576 }