diff 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
line wrap: on
line diff
--- a/mde/content/AStringContent.d	Fri May 22 19:59:22 2009 +0200
+++ b/mde/content/AStringContent.d	Sat May 23 15:47:32 2009 +0200
@@ -18,9 +18,12 @@
 module mde.content.AStringContent;
 public import mde.content.Content;
 
+import Ascii = tango.text.Ascii;
+import Util = tango.text.Util;
 //FIXME: efficient conversions? Need to dup result when formatting a string anyway?
 import Int = tango.text.convert.Integer;
 import Float = tango.text.convert.Float;
+import Math = tango.math.Math;
 import derelict.sdl.keysym;
 
 import tango.util.log.Log : Log, Logger;
@@ -29,6 +32,17 @@
     logger = Log.getLogger ("mde.content.AStringContent");
 }
 
+/** Union of all content types here - for dynamic cast checking against several
+ * types. */
+union UnionContent {
+    AStringContent asc;
+    BoolContent bc;
+    StringContent sc;
+    IntContent ic;
+    DoubleContent dc;
+    EnumContent ec;
+}
+
 /** Base class for content containing a simple value editable as text.
  *
  * Derived classes should implement endEdit to convert sv and assign its value
@@ -52,16 +66,11 @@
     }
     
     /** Set the content via conversion to/from string. */
-    override bool setContent (IContent c) {
-	AStringContent asc = cast (AStringContent) c;
-	if (asc !is null) {
-	    try {
-		sv = asc.toString (0);
-		endEdit;
-	    } catch (Exception) {	// invalid conversion; just reject c
-		return false;
-	    }
-	    return true;
+    override bool set (IContent c) {
+	//AStringContent asc = cast (AStringContent) c;
+	if (c !is null) {
+	    sv = c.toString (0).dup;
+	    return endEdit;
 	}
 	return false;
     }
@@ -81,10 +90,11 @@
 		sv = sv[0..pos] ~ sv[p..$];
 	    } else {			// insert character
 		char[] tail = sv[pos..$];
+		//NOTE: reallocating each keypress isn't optimal
 		sv.length = sv.length + i.length;
 		size_t npos = pos+i.length;
 		if (tail) sv[npos..$] = tail.dup;	// cannot assign with overlapping ranges
-		    sv[pos..npos] = i;
+		sv[pos..npos] = i;
 		pos = npos;
 	    }
 	} else {			// use sym; many keys output 0
@@ -136,11 +146,19 @@
         }
     }
     
-    /** Call after editing a string; return new string (may be changed/reverted). */
-    char[] endEdit ();
+    /** Call after editing a string.
+     *
+     * Returns: true if string successfully converted to value.
+     * 
+     * Should never throw; should reset sv at least when returning false. */
+    bool endEdit ();
     
 protected:
-    char[] sv;		// string of value; updated on assignment for displaying and editing
+    /* String version of value (for toString(0) and editing).
+     * WARNING: This must point to mutable memory!
+     * (Actually this isn't usually required now, but after optimising will be.)
+     * TODO: provide a buffer, for use when editing sv. */
+    char[] sv;
     size_t pos;		// editing position; used by keyStroke
 }
 
@@ -151,14 +169,14 @@
         auto valp = symbol in changed.boolData;
         if (valp)
             v = *valp;
-        sv = v ? "true" : "false";
+        sv = (v ? "true" : "false").dup;
 	super (symbol);
     }
     
     // Assign without adding change to save changeset
     void assignNoCng (bool val) {
 	v = val;
-	sv = v ? "true" : "false";
+	sv = (v ? "true" : "false").dup;
 	if (pos > sv.length) pos = sv.length;
         endEvent;
     }
@@ -171,12 +189,24 @@
     }
     alias opCall opCast;
     
-    override char[] endEdit () {
-	v = sv && (sv[0] == 't' || sv[0] == 'T' || sv[0] == '1');
-        sv = v ? "true" : "false";
+    override bool endEdit () {
+	try {
+	    sv = Util.trim (Ascii.toLower (sv));	// NOTE: sv must be in mutable memory
+	    if (sv == "false")
+		v = 0;
+	    else if (sv == "true")
+		v = 1;
+	    else 	// throws if can't convert to int:
+		v = (Int.toLong (sv) != 0);
+	} catch (Exception e) {
+	    logger.error (e.msg);
+	    sv = (v ? "true" : "false").dup;
+	    return false;
+	}
+	sv = (v ? "true" : "false").dup;
         endEvent;
         endCng;
-	return sv;
+	return true;
     }
     
     // Add change to changeset
@@ -212,10 +242,10 @@
     }
     alias opCall opCast;
     
-    override char[] endEdit () {
+    override bool endEdit () {
 	endEvent;
         endCng;
-	return sv;
+	return true;
     }
     
     void endCng () {
@@ -238,15 +268,24 @@
 	super (symbol);
     }
     
-    // NOTE: the only point of this method is to avoid int->string->int conversions,
-    // and perhaps specialise (double->int rounding?)
-    override bool setContent (IContent c) {
-	IntContent ic = cast (IntContent) c;
-	if (ic !is null) {
-	    this = ic();
+    override bool set (IContent c) {
+	UnionContent uc;
+	uc.bc = cast (BoolContent) c;
+	if (uc.bc !is null) {
+	    this = uc.bc();
 	    return true;
 	}
-	return super.setContent (c);
+	uc.ic = cast (IntContent) c;
+	if (uc.ic !is null) {
+	    this = uc.ic();
+	    return true;
+	}
+	uc.dc = cast (DoubleContent) c;
+	if (uc.dc !is null) {
+	    this = Math.rndint (uc.dc());	// round to nearest
+	    return true;
+	}
+	return super.set (c);
     }
     
     void assignNoCng (int val) {
@@ -264,16 +303,18 @@
     }
     alias opCall opCast;
     
-    override char[] endEdit () {
+    override bool endEdit () {
 	try {
             v = Int.toInt (sv);
         } catch (Exception e) {
-            logger.warn (e.msg);
-        }
+            logger.error (e.msg);
+	    sv = Int.toString (v);
+	    return false;
+	}
         sv = Int.toString (v);
         endEvent;
         endCng;
-	return sv;
+	return true;
     }
     
     void endCng () {
@@ -284,7 +325,7 @@
     int v;
 }
 
-/** Double content. */
+/** Floating-point content. */
 class DoubleContent : AStringContent
 {
     /** Create a content with _symbol name symbol. */
@@ -296,6 +337,26 @@
 	super (symbol);
     }
     
+    override bool set (IContent c) {
+	UnionContent uc;
+	uc.bc = cast (BoolContent) c;
+	if (uc.bc !is null) {
+	    this = uc.bc();
+	    return true;
+	}
+	uc.ic = cast (IntContent) c;
+	if (uc.ic !is null) {
+	    this = uc.ic();
+	    return true;
+	}
+	uc.dc = cast (DoubleContent) c;
+	if (uc.dc !is null) {
+	    this = uc.dc();
+	    return true;
+	}
+	return super.set (c);
+    }
+    
     void assignNoCng (double val) {
 	v = val;
 	sv = Float.toString (v, 8, 4);
@@ -311,16 +372,18 @@
     }
     alias opCall opCast;
     
-    override char[] endEdit () {
+    override bool endEdit () {
         try {
             v = Float.toFloat (sv);
         } catch (Exception e) {
-            logger.warn (e.msg);
-        }
+            logger.error (e.msg);
+	    sv = Float.toString (v, 8, 4);
+	    return false;
+	}
         sv = Float.toString (v, 8, 4);
         endEvent;
         endCng;
-	return sv;
+	return true;
     }
     
     void endCng () {
@@ -349,7 +412,7 @@
             e = new EnumValueContent (this, i, symPeriod~enumSymbols[i]);
         }
         enums[v].assignFromParent (true);
-        sv = enums[v].name_;
+        sv = enums[v].name_.dup;
         // Re-set the value if a saved value is found:
         auto valp = symbol in changed.enumValData;
         if (valp)
@@ -368,7 +431,7 @@
                 return;
             }
         }
-        logger.warn ("EnumContent {} assigned invalid enumeration: {}; valid: {}", symbol, enumSym, enumSymbols);
+        logger.error ("EnumContent {} assigned invalid enumeration: {}; valid: {}", symbol, enumSym, enumSymbols);
     }
     size_t opCall () {
         //debug logger.trace ("EnumContent {} returning value: {} ({})",symbol, enumSymbols[v], v);
@@ -383,25 +446,26 @@
         enums[v]  .assignFromParent (false);
         enums[val].assignFromParent (true);
 	v = val;
-        sv = enums[v].name_;
+        sv = enums[v].name_.dup;
         if (pos > sv.length) pos = sv.length;
         endEvent;
     }
     
-    override char[] endEdit () {
+    override bool endEdit () {
 	foreach (i,e; enums)
 	    if (sv == e.name_) {
 		assignNoCng (i);
 		goto break1;
 	    }
 	
-        sv = enums[v].name_;	// sv was edited; revert
+        sv = enums[v].name_.dup;	// sv was edited; revert
         logger.error ("EnumContent "~name_~" assigned invalid value; keeping value: "~sv);
 	if (pos > sv.length) pos = sv.length;
+	return false;
 	
 	break1:
 	endCng;
-	return sv;
+	return true;
     }
     
     void endCng () {
@@ -452,10 +516,12 @@
             super.assignNoCng (val);
         }
         
-        override char[] endEdit () {
-            v = sv && (sv[0] == 't' || sv[0] == 'T' || sv[0] == '1');
-            parent.childAssign (i);
-            return sv;
+        override bool endEdit () {
+	    if (super.endEdit) {
+		parent.childAssign (i);
+		return true;	// value accepted by BoolContent, not necessarily by EnumContent
+	    }
+            return false;
         }
     
     protected:
@@ -463,3 +529,53 @@
         size_t i;
     }
 }
+
+debug (mdeUnitTest) {
+    unittest {
+	bool throws (void delegate() dg) {
+	    bool r = false;
+	    try {
+		dg();
+	    } catch (Exception e) {
+		r = true;
+	    }
+	    return r;
+	}
+	
+	StringContent sc = new StringContent ("unittest.sc");
+	IntContent ic = new IntContent ("unittest.ic");
+	BoolContent bc = new BoolContent ("unittest.bc");
+	DoubleContent dc = new DoubleContent ("unittest.dc");
+	
+	logger.info ("You should see some \"invalid literal\" errors:");
+	sc = "16";
+	ic.set = sc;
+	assert (ic() == 16);
+	sc = "five";	// fails
+	ic.set = sc;
+	assert (ic.toString(0) == "16");
+	
+	bc.set = ic;
+	assert (bc());
+	sc = "fALse";
+	bc.set = sc;
+	assert (!bc());
+	
+	sc = "31.5";
+	ic.set = sc;	// parses as int which fails
+	assert (ic() == 16);
+	dc.set = sc;
+	ic.set = dc;	// rounds to even
+	assert (ic() == 32);
+	dc = -1.5;
+	ic.set = dc;	// rounds to even
+	assert (ic() == -2);
+	
+	bc.set = dc;	// fails: not included conversion
+	assert (!bc());
+	bc.set = ic;
+	assert (bc());
+	
+	logger.info ("Unittest complete.");
+    }
+}
\ No newline at end of file