Mercurial > projects > mde
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 } |