comparison mde/content/AStringContent.d @ 163:24d77c52243f

Provided sensible conversions for setting the value of one AStringContent from another, along with unittest. Updated layout and Translation unittests to run.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 23 May 2009 15:47:32 +0200
parents 2476790223b8
children c13bded1bed3
comparison
equal deleted inserted replaced
162:2476790223b8 163:24d77c52243f
16 /** The content system − string-based editable content. 16 /** The content system − string-based editable content.
17 */ 17 */
18 module mde.content.AStringContent; 18 module mde.content.AStringContent;
19 public import mde.content.Content; 19 public import mde.content.Content;
20 20
21 import Ascii = tango.text.Ascii;
22 import Util = tango.text.Util;
21 //FIXME: efficient conversions? Need to dup result when formatting a string anyway? 23 //FIXME: efficient conversions? Need to dup result when formatting a string anyway?
22 import Int = tango.text.convert.Integer; 24 import Int = tango.text.convert.Integer;
23 import Float = tango.text.convert.Float; 25 import Float = tango.text.convert.Float;
26 import Math = tango.math.Math;
24 import derelict.sdl.keysym; 27 import derelict.sdl.keysym;
25 28
26 import tango.util.log.Log : Log, Logger; 29 import tango.util.log.Log : Log, Logger;
27 private Logger logger; 30 private Logger logger;
28 static this () { 31 static this () {
29 logger = Log.getLogger ("mde.content.AStringContent"); 32 logger = Log.getLogger ("mde.content.AStringContent");
33 }
34
35 /** Union of all content types here - for dynamic cast checking against several
36 * types. */
37 union UnionContent {
38 AStringContent asc;
39 BoolContent bc;
40 StringContent sc;
41 IntContent ic;
42 DoubleContent dc;
43 EnumContent ec;
30 } 44 }
31 45
32 /** Base class for content containing a simple value editable as text. 46 /** Base class for content containing a simple value editable as text.
33 * 47 *
34 * Derived classes should implement endEdit to convert sv and assign its value 48 * Derived classes should implement endEdit to convert sv and assign its value
50 : i == 2 ? desc_ 64 : i == 2 ? desc_
51 : null; 65 : null;
52 } 66 }
53 67
54 /** Set the content via conversion to/from string. */ 68 /** Set the content via conversion to/from string. */
55 override bool setContent (IContent c) { 69 override bool set (IContent c) {
56 AStringContent asc = cast (AStringContent) c; 70 //AStringContent asc = cast (AStringContent) c;
57 if (asc !is null) { 71 if (c !is null) {
58 try { 72 sv = c.toString (0).dup;
59 sv = asc.toString (0); 73 return endEdit;
60 endEdit;
61 } catch (Exception) { // invalid conversion; just reject c
62 return false;
63 }
64 return true;
65 } 74 }
66 return false; 75 return false;
67 } 76 }
68 77
69 /** Acts on a keystroke and returns the new value. 78 /** Acts on a keystroke and returns the new value.
79 while (p < sv.length && (sv[p] & 0x80) && !(sv[p] & 0x40)) 88 while (p < sv.length && (sv[p] & 0x80) && !(sv[p] & 0x40))
80 ++p; 89 ++p;
81 sv = sv[0..pos] ~ sv[p..$]; 90 sv = sv[0..pos] ~ sv[p..$];
82 } else { // insert character 91 } else { // insert character
83 char[] tail = sv[pos..$]; 92 char[] tail = sv[pos..$];
93 //NOTE: reallocating each keypress isn't optimal
84 sv.length = sv.length + i.length; 94 sv.length = sv.length + i.length;
85 size_t npos = pos+i.length; 95 size_t npos = pos+i.length;
86 if (tail) sv[npos..$] = tail.dup; // cannot assign with overlapping ranges 96 if (tail) sv[npos..$] = tail.dup; // cannot assign with overlapping ranges
87 sv[pos..npos] = i; 97 sv[pos..npos] = i;
88 pos = npos; 98 pos = npos;
89 } 99 }
90 } else { // use sym; many keys output 0 100 } else { // use sym; many keys output 0
91 if (sym >= SDLK_RSHIFT && (sym <= SDLK_COMPOSE || sym == SDLK_MENU)) { 101 if (sym >= SDLK_RSHIFT && (sym <= SDLK_COMPOSE || sym == SDLK_MENU)) {
92 } // all modifier keys and contect menu key; should be ignored 102 } // all modifier keys and contect menu key; should be ignored
134 while (pos < sv.length && (sv[pos] & 0x80) && !(sv[pos] & 0x40)) 144 while (pos < sv.length && (sv[pos] & 0x80) && !(sv[pos] & 0x40))
135 ++pos; 145 ++pos;
136 } 146 }
137 } 147 }
138 148
139 /** Call after editing a string; return new string (may be changed/reverted). */ 149 /** Call after editing a string.
140 char[] endEdit (); 150 *
151 * Returns: true if string successfully converted to value.
152 *
153 * Should never throw; should reset sv at least when returning false. */
154 bool endEdit ();
141 155
142 protected: 156 protected:
143 char[] sv; // string of value; updated on assignment for displaying and editing 157 /* String version of value (for toString(0) and editing).
158 * WARNING: This must point to mutable memory!
159 * (Actually this isn't usually required now, but after optimising will be.)
160 * TODO: provide a buffer, for use when editing sv. */
161 char[] sv;
144 size_t pos; // editing position; used by keyStroke 162 size_t pos; // editing position; used by keyStroke
145 } 163 }
146 164
147 class BoolContent : AStringContent 165 class BoolContent : AStringContent
148 { 166 {
149 /** Create a content with _symbol name symbol. */ 167 /** Create a content with _symbol name symbol. */
150 this (char[] symbol) { 168 this (char[] symbol) {
151 auto valp = symbol in changed.boolData; 169 auto valp = symbol in changed.boolData;
152 if (valp) 170 if (valp)
153 v = *valp; 171 v = *valp;
154 sv = v ? "true" : "false"; 172 sv = (v ? "true" : "false").dup;
155 super (symbol); 173 super (symbol);
156 } 174 }
157 175
158 // Assign without adding change to save changeset 176 // Assign without adding change to save changeset
159 void assignNoCng (bool val) { 177 void assignNoCng (bool val) {
160 v = val; 178 v = val;
161 sv = v ? "true" : "false"; 179 sv = (v ? "true" : "false").dup;
162 if (pos > sv.length) pos = sv.length; 180 if (pos > sv.length) pos = sv.length;
163 endEvent; 181 endEvent;
164 } 182 }
165 void opAssign (bool val) { 183 void opAssign (bool val) {
166 assignNoCng (val); 184 assignNoCng (val);
169 bool opCall () { 187 bool opCall () {
170 return v; 188 return v;
171 } 189 }
172 alias opCall opCast; 190 alias opCall opCast;
173 191
174 override char[] endEdit () { 192 override bool endEdit () {
175 v = sv && (sv[0] == 't' || sv[0] == 'T' || sv[0] == '1'); 193 try {
176 sv = v ? "true" : "false"; 194 sv = Util.trim (Ascii.toLower (sv)); // NOTE: sv must be in mutable memory
177 endEvent; 195 if (sv == "false")
178 endCng; 196 v = 0;
179 return sv; 197 else if (sv == "true")
198 v = 1;
199 else // throws if can't convert to int:
200 v = (Int.toLong (sv) != 0);
201 } catch (Exception e) {
202 logger.error (e.msg);
203 sv = (v ? "true" : "false").dup;
204 return false;
205 }
206 sv = (v ? "true" : "false").dup;
207 endEvent;
208 endCng;
209 return true;
180 } 210 }
181 211
182 // Add change to changeset 212 // Add change to changeset
183 void endCng () { 213 void endCng () {
184 changed.boolData[symbol] = v; 214 changed.boolData[symbol] = v;
210 char[] opCall () { 240 char[] opCall () {
211 return v; 241 return v;
212 } 242 }
213 alias opCall opCast; 243 alias opCall opCast;
214 244
215 override char[] endEdit () { 245 override bool endEdit () {
216 endEvent; 246 endEvent;
217 endCng; 247 endCng;
218 return sv; 248 return true;
219 } 249 }
220 250
221 void endCng () { 251 void endCng () {
222 changed.charAData[symbol] = v; 252 changed.charAData[symbol] = v;
223 } 253 }
236 v = *valp; 266 v = *valp;
237 sv = Int.toString (v); 267 sv = Int.toString (v);
238 super (symbol); 268 super (symbol);
239 } 269 }
240 270
241 // NOTE: the only point of this method is to avoid int->string->int conversions, 271 override bool set (IContent c) {
242 // and perhaps specialise (double->int rounding?) 272 UnionContent uc;
243 override bool setContent (IContent c) { 273 uc.bc = cast (BoolContent) c;
244 IntContent ic = cast (IntContent) c; 274 if (uc.bc !is null) {
245 if (ic !is null) { 275 this = uc.bc();
246 this = ic();
247 return true; 276 return true;
248 } 277 }
249 return super.setContent (c); 278 uc.ic = cast (IntContent) c;
279 if (uc.ic !is null) {
280 this = uc.ic();
281 return true;
282 }
283 uc.dc = cast (DoubleContent) c;
284 if (uc.dc !is null) {
285 this = Math.rndint (uc.dc()); // round to nearest
286 return true;
287 }
288 return super.set (c);
250 } 289 }
251 290
252 void assignNoCng (int val) { 291 void assignNoCng (int val) {
253 v = val; 292 v = val;
254 sv = Int.toString (v); 293 sv = Int.toString (v);
262 int opCall () { 301 int opCall () {
263 return v; 302 return v;
264 } 303 }
265 alias opCall opCast; 304 alias opCall opCast;
266 305
267 override char[] endEdit () { 306 override bool endEdit () {
268 try { 307 try {
269 v = Int.toInt (sv); 308 v = Int.toInt (sv);
270 } catch (Exception e) { 309 } catch (Exception e) {
271 logger.warn (e.msg); 310 logger.error (e.msg);
272 } 311 sv = Int.toString (v);
312 return false;
313 }
273 sv = Int.toString (v); 314 sv = Int.toString (v);
274 endEvent; 315 endEvent;
275 endCng; 316 endCng;
276 return sv; 317 return true;
277 } 318 }
278 319
279 void endCng () { 320 void endCng () {
280 changed.intData[symbol] = v; 321 changed.intData[symbol] = v;
281 } 322 }
282 323
283 protected: 324 protected:
284 int v; 325 int v;
285 } 326 }
286 327
287 /** Double content. */ 328 /** Floating-point content. */
288 class DoubleContent : AStringContent 329 class DoubleContent : AStringContent
289 { 330 {
290 /** Create a content with _symbol name symbol. */ 331 /** Create a content with _symbol name symbol. */
291 this (char[] symbol) { 332 this (char[] symbol) {
292 auto valp = symbol in changed.doubleData; 333 auto valp = symbol in changed.doubleData;
294 v = *valp; 335 v = *valp;
295 sv = Float.toString (v, 8, 4); 336 sv = Float.toString (v, 8, 4);
296 super (symbol); 337 super (symbol);
297 } 338 }
298 339
340 override bool set (IContent c) {
341 UnionContent uc;
342 uc.bc = cast (BoolContent) c;
343 if (uc.bc !is null) {
344 this = uc.bc();
345 return true;
346 }
347 uc.ic = cast (IntContent) c;
348 if (uc.ic !is null) {
349 this = uc.ic();
350 return true;
351 }
352 uc.dc = cast (DoubleContent) c;
353 if (uc.dc !is null) {
354 this = uc.dc();
355 return true;
356 }
357 return super.set (c);
358 }
359
299 void assignNoCng (double val) { 360 void assignNoCng (double val) {
300 v = val; 361 v = val;
301 sv = Float.toString (v, 8, 4); 362 sv = Float.toString (v, 8, 4);
302 if (pos > sv.length) pos = sv.length; 363 if (pos > sv.length) pos = sv.length;
303 endEvent; 364 endEvent;
309 double opCall () { 370 double opCall () {
310 return v; 371 return v;
311 } 372 }
312 alias opCall opCast; 373 alias opCall opCast;
313 374
314 override char[] endEdit () { 375 override bool endEdit () {
315 try { 376 try {
316 v = Float.toFloat (sv); 377 v = Float.toFloat (sv);
317 } catch (Exception e) { 378 } catch (Exception e) {
318 logger.warn (e.msg); 379 logger.error (e.msg);
319 } 380 sv = Float.toString (v, 8, 4);
381 return false;
382 }
320 sv = Float.toString (v, 8, 4); 383 sv = Float.toString (v, 8, 4);
321 endEvent; 384 endEvent;
322 endCng; 385 endCng;
323 return sv; 386 return true;
324 } 387 }
325 388
326 void endCng () { 389 void endCng () {
327 changed.doubleData[symbol] = v; 390 changed.doubleData[symbol] = v;
328 } 391 }
347 char[] symPeriod = symbol~'.'; 410 char[] symPeriod = symbol~'.';
348 foreach (i, ref e; enums) { 411 foreach (i, ref e; enums) {
349 e = new EnumValueContent (this, i, symPeriod~enumSymbols[i]); 412 e = new EnumValueContent (this, i, symPeriod~enumSymbols[i]);
350 } 413 }
351 enums[v].assignFromParent (true); 414 enums[v].assignFromParent (true);
352 sv = enums[v].name_; 415 sv = enums[v].name_.dup;
353 // Re-set the value if a saved value is found: 416 // Re-set the value if a saved value is found:
354 auto valp = symbol in changed.enumValData; 417 auto valp = symbol in changed.enumValData;
355 if (valp) 418 if (valp)
356 assignNoCng = *valp; 419 assignNoCng = *valp;
357 } 420 }
366 if (e == enumSym) { 429 if (e == enumSym) {
367 assignNoCng (i); 430 assignNoCng (i);
368 return; 431 return;
369 } 432 }
370 } 433 }
371 logger.warn ("EnumContent {} assigned invalid enumeration: {}; valid: {}", symbol, enumSym, enumSymbols); 434 logger.error ("EnumContent {} assigned invalid enumeration: {}; valid: {}", symbol, enumSym, enumSymbols);
372 } 435 }
373 size_t opCall () { 436 size_t opCall () {
374 //debug logger.trace ("EnumContent {} returning value: {} ({})",symbol, enumSymbols[v], v); 437 //debug logger.trace ("EnumContent {} returning value: {} ({})",symbol, enumSymbols[v], v);
375 return v; 438 return v;
376 } 439 }
381 return; 444 return;
382 } 445 }
383 enums[v] .assignFromParent (false); 446 enums[v] .assignFromParent (false);
384 enums[val].assignFromParent (true); 447 enums[val].assignFromParent (true);
385 v = val; 448 v = val;
386 sv = enums[v].name_; 449 sv = enums[v].name_.dup;
387 if (pos > sv.length) pos = sv.length; 450 if (pos > sv.length) pos = sv.length;
388 endEvent; 451 endEvent;
389 } 452 }
390 453
391 override char[] endEdit () { 454 override bool endEdit () {
392 foreach (i,e; enums) 455 foreach (i,e; enums)
393 if (sv == e.name_) { 456 if (sv == e.name_) {
394 assignNoCng (i); 457 assignNoCng (i);
395 goto break1; 458 goto break1;
396 } 459 }
397 460
398 sv = enums[v].name_; // sv was edited; revert 461 sv = enums[v].name_.dup; // sv was edited; revert
399 logger.error ("EnumContent "~name_~" assigned invalid value; keeping value: "~sv); 462 logger.error ("EnumContent "~name_~" assigned invalid value; keeping value: "~sv);
400 if (pos > sv.length) pos = sv.length; 463 if (pos > sv.length) pos = sv.length;
464 return false;
401 465
402 break1: 466 break1:
403 endCng; 467 endCng;
404 return sv; 468 return true;
405 } 469 }
406 470
407 void endCng () { 471 void endCng () {
408 changed.enumValData[symbol] = enumSymbols[v]; 472 changed.enumValData[symbol] = enumSymbols[v];
409 } 473 }
450 } 514 }
451 void assignFromParent (bool val) { // don't call back to parent 515 void assignFromParent (bool val) { // don't call back to parent
452 super.assignNoCng (val); 516 super.assignNoCng (val);
453 } 517 }
454 518
455 override char[] endEdit () { 519 override bool endEdit () {
456 v = sv && (sv[0] == 't' || sv[0] == 'T' || sv[0] == '1'); 520 if (super.endEdit) {
457 parent.childAssign (i); 521 parent.childAssign (i);
458 return sv; 522 return true; // value accepted by BoolContent, not necessarily by EnumContent
523 }
524 return false;
459 } 525 }
460 526
461 protected: 527 protected:
462 EnumContent parent; 528 EnumContent parent;
463 size_t i; 529 size_t i;
464 } 530 }
465 } 531 }
532
533 debug (mdeUnitTest) {
534 unittest {
535 bool throws (void delegate() dg) {
536 bool r = false;
537 try {
538 dg();
539 } catch (Exception e) {
540 r = true;
541 }
542 return r;
543 }
544
545 StringContent sc = new StringContent ("unittest.sc");
546 IntContent ic = new IntContent ("unittest.ic");
547 BoolContent bc = new BoolContent ("unittest.bc");
548 DoubleContent dc = new DoubleContent ("unittest.dc");
549
550 logger.info ("You should see some \"invalid literal\" errors:");
551 sc = "16";
552 ic.set = sc;
553 assert (ic() == 16);
554 sc = "five"; // fails
555 ic.set = sc;
556 assert (ic.toString(0) == "16");
557
558 bc.set = ic;
559 assert (bc());
560 sc = "fALse";
561 bc.set = sc;
562 assert (!bc());
563
564 sc = "31.5";
565 ic.set = sc; // parses as int which fails
566 assert (ic() == 16);
567 dc.set = sc;
568 ic.set = dc; // rounds to even
569 assert (ic() == 32);
570 dc = -1.5;
571 ic.set = dc; // rounds to even
572 assert (ic() == -2);
573
574 bc.set = dc; // fails: not included conversion
575 assert (!bc());
576 bc.set = ic;
577 assert (bc());
578
579 logger.info ("Unittest complete.");
580 }
581 }