diff options
author | Derrell Lipman <derrell@samba.org> | 2006-12-30 05:09:59 +0000 |
---|---|---|
committer | Gerald (Jerry) Carter <jerry@samba.org> | 2007-10-10 14:30:29 -0500 |
commit | 1170417ceeb8c49a46cda522a38eaa71c9cae30c (patch) | |
tree | dbf4c3fdcdb1af928dbd55f642ea40f00d09283a /webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util | |
parent | 23ccdca41670085da4486841b0d4900b4c8b02f3 (diff) | |
download | samba-1170417ceeb8c49a46cda522a38eaa71c9cae30c.tar.gz samba-1170417ceeb8c49a46cda522a38eaa71c9cae30c.tar.bz2 samba-1170417ceeb8c49a46cda522a38eaa71c9cae30c.zip |
r20414: Start to make SWAT usable by others. This is just a start...
(This used to be commit 26a34037a7ca6fbd05c5a6f7c2d5973e34bc6918)
Diffstat (limited to 'webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util')
14 files changed, 4311 insertions, 0 deletions
diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/ColorUtil.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/ColorUtil.js new file mode 100644 index 0000000000..3a8406f029 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/ColorUtil.js @@ -0,0 +1,162 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2004-2006 by 1&1 Internet AG, Germany, http://www.1and1.org + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Sebastian Werner (wpbasti) + * Andreas Ecker (ecker) + +************************************************************************ */ + +/* ************************************************************************ + + +************************************************************************ */ + +qx.OO.defineClass("qx.util.ColorUtil"); + +qx.Class.rgb2hsb = function(vRed, vGreen, vBlue) +{ + var vHue, vSaturation, vBrightness; + + vRed = parseFloat(vRed); + vGreen = parseFloat(vGreen); + vBlue = parseFloat(vBlue); + + var cmax = (vRed > vGreen) ? vRed : vGreen; + if (vBlue > cmax) { + cmax = vBlue; + } + + var cmin = (vRed < vGreen) ? vRed : vGreen; + if (vBlue < cmin) { + cmin = vBlue; + } + + vBrightness = cmax / 255.0; + + if (cmax != 0) + { + vSaturation = (cmax - cmin) / cmax; + } + else + { + vSaturation = 0; + } + + if (vSaturation == 0) + { + vHue = 0; + } + else + { + var redc = (cmax - vRed) / (cmax - cmin); + var greenc = (cmax - vGreen) / (cmax - cmin); + var bluec = (cmax - vBlue) / (cmax - cmin); + + if (vRed == cmax) + { + vHue = bluec - greenc; + } + else if (vGreen == cmax) + { + vHue = 2.0 + redc - bluec; + } + else + { + vHue = 4.0 + greenc - redc; + } + + vHue = vHue / 6.0; + if (vHue < 0) vHue = vHue + 1.0; + } + + return { + hue : Math.round(vHue * 360), + saturation : Math.round(vSaturation * 100), + brightness : Math.round(vBrightness * 100) + } +} + +qx.Class.hsb2rgb = function(vHue, vSaturation, vBrightness) +{ + var i, f, p, q, t, vReturn; + + vHue = parseFloat(vHue/360); + vSaturation = parseFloat(vSaturation/100); + vBrightness = parseFloat(vBrightness/100); + + if(vHue >= 1.0) vHue %= 1.0; + if(vSaturation > 1.0) vSaturation = 1.0; + if(vBrightness > 1.0) vBrightness = 1.0; + + var tov = Math.floor(255 * vBrightness); + + var vReturn = {}; + + if(vSaturation == 0.0) + { + vReturn.red = vReturn.green = vReturn.blue = tov; + } + else + { + vHue *= 6.0; + + i = Math.floor(vHue); + + f = vHue - i; + + p = Math.floor(tov * (1.0 - vSaturation)); + q = Math.floor(tov * (1.0 - (vSaturation * f))); + t = Math.floor(tov * (1.0 - (vSaturation * (1.0 - f)))); + + switch(i) + { + case 0: + vReturn.red = tov; + vReturn.green = t; + vReturn.blue = p; + break; + + case 1: + vReturn.red = q; + vReturn.green = tov; + vReturn.blue = p; + break; + + case 2: + vReturn.red = p; + vReturn.green = tov; + vReturn.blue = t; + break; + + case 3: + vReturn.red = p; + vReturn.green = q; + vReturn.blue = tov; + break; + + case 4: + vReturn.red = t; + vReturn.green = p; + vReturn.blue = tov; + break; + + case 5: + vReturn.red = tov; + vReturn.green = p; + vReturn.blue = q; + break; + } + } + + return vReturn; +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Compare.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Compare.js new file mode 100644 index 0000000000..3de325d717 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Compare.js @@ -0,0 +1,86 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2004-2006 by 1&1 Internet AG, Germany, http://www.1and1.org + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Sebastian Werner (wpbasti) + * Andreas Ecker (ecker) + +************************************************************************ */ + +/* ************************************************************************ + + +************************************************************************ */ + +qx.OO.defineClass("qx.util.Compare"); + +qx.util.Compare.byString = function(a, b) { + return a==b ? 0 : a > b ? 1 : -1; +} + +qx.util.Compare.byStringCaseInsensitive = function(a, b) { + return qx.util.Compare.byString(a.toLowerCase(), b.toLowerCase()); +} + +qx.util.Compare.byStringUmlautsShort = function(a, b) { + return qx.util.Compare.byString(qx.util.Normalization.umlautsShort(a), qx.util.Normalization.umlautsShort(b)); +} + +qx.util.Compare.byStringUmlautsShortCaseInsensitive = function(a, b) { + return qx.util.Compare.byString(qx.util.Normalization.umlautsShort(a).toLowerCase(), qx.util.Normalization.umlautsShort(b).toLowerCase()); +} + +qx.util.Compare.byStringUmlautsLong = function(a, b) { + return qx.util.Compare.byString(qx.util.Normalization.umlautsLong(a), qx.util.Normalization.umlautsLong(b)); +} + +qx.util.Compare.byStringUmlautsLongCaseInsensitive = function(a, b) { + return qx.util.Compare.byString(qx.util.Normalization.umlautsLong(a).toLowerCase(), qx.util.Normalization.umlautsLong(b).toLowerCase()); +} + +qx.util.Compare.byFloat = function(a, b) { + return a - b; +} + +qx.util.Compare.byInteger = qx.util.Compare.byNumber = qx.util.Compare.byFloat; + +qx.util.Compare.byIntegerString = function(a, b) { + return parseInt(a) - parseInt(b); +} + +qx.util.Compare.byFloatString = function(a, b) { + return parseFloat(a) - parseFloat(b); +} + +qx.util.Compare.byNumberString = qx.util.Compare.byFloatString; + +qx.util.Compare.byIPv4 = function(a, b) +{ + var ipa = a.split(".", 4); + var ipb = b.split(".", 4); + + for (var i=0; i<3; i++) + { + a = parseInt(ipa[i]); + b = parseInt(ipb[i]); + + if (a != b) { + return a - b; + } + } + + return parseInt(ipa[3]) - parseInt(ipb[3]); +} + +qx.util.Compare.byZIndex = function(a, b) { + return a.getZIndex() - b.getZIndex(); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/FormUtil.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/FormUtil.js new file mode 100644 index 0000000000..2d4913008d --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/FormUtil.js @@ -0,0 +1,109 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2004-2006 by 1&1 Internet AG, Germany, http://www.1and1.org + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Sebastian Werner (wpbasti) + * Andreas Ecker (ecker) + +************************************************************************ */ + +/* ************************************************************************ + +#module(io_remote) + +************************************************************************ */ + +qx.OO.defineClass("qx.util.FormUtil"); + +qx.Class.ignoreInputTypes = [ "file", "submit", "image", "reset", "button" ]; +qx.Class.ignoreElementTypes = [ "fieldset" ]; +qx.Class.checkElementTypes = [ "radio", "checkbox" ]; +qx.Class.multiSelectType = "select-multiple"; + +qx.Class.inputFilter = function(vNode) +{ + if (vNode.disabled) { + return false; + } + + var vTag = (vNode.tagName || "").toLowerCase(); + + if (qx.lang.Array.contains(qx.util.FormUtil.ignoreElementTypes, vTag)) { + return false; + } + + var vType = vNode.type.toLowerCase(); + + if (qx.lang.Array.contains(qx.util.FormUtil.ignoreInputTypes, vType)) { + return false; + } + + if (!vNode.checked && qx.lang.Array.contains(qx.util.FormUtil.checkElementTypes, vType)) { + return false; + } + + return true; +} + +qx.Class.getFields = function(vForm) { + return Array.filter(vForm.elements, qx.util.FormUtil.inputFilter); +} + +qx.Class.encodeField = function(vNode) +{ + var vName = vNode.name || ""; + var vType = (vNode.type || "").toLowerCase(); + + if(vType === qx.util.FormUtil.multiSelectType) + { + var vValues = []; + + for(var i=0; i<vNode.options.length; i++) + { + if(vNode.options[i].selected) { + vValues.push(vName + "=" + vNode.options[i].value); + } + } + + return vValues.join("&"); + } + else + { + return vName + "=" + vNode.value; + } +} + +qx.Class.encodeForm = function(vForm) +{ + var vFields = qx.util.FormUtil.getFields(vForm); + var vAll = []; + + for (var i=0, l=vFields.length; i<l; i++) { + vAll.push(qx.util.FormUtil.encodeField(vFields[i])); + } + + return vAll.join("&"); +} + +qx.Class.bind = function(vForm, vMethod) +{ + qx.dom.EventRegistration.addEventListener(vForm, "submit", function(e) + { + e.returnValue = false; + + if (typeof e.preventDefault === "function") { + e.preventDefault(); + } + + return vMethod(e); + }); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Normalization.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Normalization.js new file mode 100644 index 0000000000..1d94b5dcfc --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Normalization.js @@ -0,0 +1,56 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2004-2006 by 1&1 Internet AG, Germany, http://www.1and1.org + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Sebastian Werner (wpbasti) + * Andreas Ecker (ecker) + +************************************************************************ */ + +/* ************************************************************************ + + +************************************************************************ */ + +qx.OO.defineClass("qx.util.Normalization"); + + + + + +/* +--------------------------------------------------------------------------- + HANDLING OF UMLAUTS +--------------------------------------------------------------------------- +*/ + +qx.util.Normalization._umlautsRegExp = /[\xE4\xF6\xFC\xDF\xC4\xD6\xDC]/g; + +qx.util.Normalization._umlautsShortData = { "\xC4": "A", "\xD6": "O", "\xDC": "U", "\xE4": "a", "\xF6": "o", "\xFC": "u", "\xDF": "s" } + +qx.util.Normalization._umlautsShort = function(vChar) { + return qx.util.Normalization._umlautsShortData[vChar]; +} + +qx.util.Normalization.umlautsShort = function(vString) { + return vString.replace(qx.util.Normalization._umlautsRegExp, qx.util.Normalization._umlautsShort); +} + +qx.util.Normalization._umlautsLongData = { "\xC4": "Ae", "\xD6": "Oe", "\xDC": "Ue", "\xE4": "ae", "\xF6": "oe", "\xFC": "ue", "\xDF": "ss" } + +qx.util.Normalization._umlautsLong = function(vChar) { + return qx.util.Normalization._umlautsLongData[vChar]; +} + +qx.util.Normalization.umlautsLong = function(vString) { + return vString.replace(qx.util.Normalization._umlautsRegExp, qx.util.Normalization._umlautsLong); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Return.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Return.js new file mode 100644 index 0000000000..2f5c7dd1e2 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Return.js @@ -0,0 +1,74 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2004-2006 by 1&1 Internet AG, Germany, http://www.1and1.org + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Sebastian Werner (wpbasti) + * Andreas Ecker (ecker) + +************************************************************************ */ + +/* ************************************************************************ + +#module(core) + +************************************************************************ */ + +qx.OO.defineClass("qx.util.Return"); + + + + + +/* +--------------------------------------------------------------------------- + SIMPLE RETURN METHODS +--------------------------------------------------------------------------- +*/ + +qx.util.Return.returnTrue = function() { + return true; +}; + +qx.util.Return.returnFalse = function() { + return false; +}; + +qx.util.Return.returnNull = function() { + return null; +}; + +qx.util.Return.returnThis = function() { + return this; +}; + +qx.util.Return.returnInstance = function() +{ + if (!this._instance) + { + this._instance = new this; + + /* + if (this._instance.debug) { + this._instance.debug("Created..."); + }*/ + } + + return this._instance; +}; + +qx.util.Return.returnZero = function() { + return 0; +}; + +qx.util.Return.returnNegativeIndex = function() { + return -1; +}; diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Textile.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Textile.js new file mode 100644 index 0000000000..1bf42982c9 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Textile.js @@ -0,0 +1,178 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2004-2006 by 1&1 Internet AG, Germany, http://www.1and1.org + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Sebastian Werner (wpbasti) + * Andreas Ecker (ecker) + +************************************************************************ */ + +/* ************************************************************************ + + +************************************************************************ */ + +/** + * Optimized version of SuperTextile + */ +qx.OO.defineClass("qx.util.Textile"); + +/** + * ATTENTION: Deprecated module. Will be removed with qooxdoo 0.7. + * + * Textilizes a string + * http://www.creatimation.net/journal/textile-live-preview (offline) + * + * @param s {string} Text to be textilized. + * @return {string} The textilized text. + */ +qx.util.Textile.textilize = function(s) +{ + var r = s; + + // quick tags first + var qtags = [ + ["\\*", "strong"], + ["\\?\\?", "cite"], + ["\\+", "ins"], + ["~", "sub"], + ["\\^", "sup"], + ["@", "code"] + ]; + + var ttag, htag, re, line, lines, nr, changed, inlist, listtype; + + for (var i=0;i<qtags.length;i++) { + ttag = qtags[i][0]; htag = qtags[i][1]; + re = new RegExp(ttag+"\\b(.+?)\\b"+ttag,"g"); + r = r.replace(re,"<"+htag+">"+"$1"+"</"+htag+">"); + } + + // underscores count as part of a word, so do them separately + re = new RegExp("\\b_(.+?)_\\b","g"); + r = r.replace(re,"<em>$1</em>"); + + // jeff: so do dashes + re = new RegExp("[\s\n]-(.+?)-[\s\n]","g"); + r = r.replace(re,"<del>$1</del>"); + + // links + re = new RegExp('"\\b(.+?)\\(\\b(.+?)\\b\\)":([^\\s]+)','g'); + r = r.replace(re,'<a href="$3" title="$2">$1</a>'); + re = new RegExp('"\\b(.+?)\\b":([^\\s]+)','g'); + r = r.replace(re,'<a href="$2">$1</a>'); + + // images + re = new RegExp("!\\b(.+?)\\(\\b(.+?)\\b\\)!","g"); + r = r.replace(re,'<img src="$1" alt="$2">'); + re = new RegExp("!\\b(.+?)\\b!","g"); + r = r.replace(re,'<img src="$1">'); + + // block level formatting + + // Jeff's hack to show single line breaks as they should. + // insert breaks - but you get some....stupid ones + re = new RegExp("(.*)\n([^#\*\n].*)","g"); + r = r.replace(re,"$1<br />$2"); + + // remove the stupid breaks. + re = new RegExp("\n<br />","g"); + r = r.replace(re,"\n"); + + lines = r.split("\n"); + nr = ""; + + for (var i=0;i<lines.length;i++) + { + line = lines[i].replace(/\s*$/,""); + changed = 0; + + if (line.search(/^\s*bq\.\s+/) != -1) + { + line = line.replace(/^\s*bq\.\s+/,"\t<blockquote>")+"</blockquote>"; + changed = 1; + } + + // jeff adds h#. + if (line.search(/^\s*h[1-6]\.\s+/) != -1) + { + re = new RegExp("h([1-6])\.(.+)","g"); + line = line.replace(re,"<h$1>$2</h$1>"); + changed = 1; + } + + if (line.search(/^\s*\*\s+/) != -1) + { + // for bullet list; make up an liu tag to be fixed later + line = line.replace(/^\s*\*\s+/,"\t<liu>") + "</liu>"; + changed = 1; + } + + if (line.search(/^\s*#\s+/) != -1) + { + // # for numeric list; make up an lio tag to be fixed later + line = line.replace(/^\s*#\s+/,"\t<lio>") + "</lio>"; + changed = 1; + } + + if (!changed && (line.replace(/\s/g,"").length > 0)) + { + line = "<p>"+line+"</p>"; + } + + lines[i] = line + "\n"; + } + + // Second pass to do lists + inlist = 0; + listtype = ""; + + for (var i=0;i<lines.length;i++) + { + line = lines[i]; + + if (inlist && listtype == "ul" && !line.match(/^\t<liu/)) + { + line = "</ul>\n" + line; + inlist = 0; + } + + if (inlist && listtype == "ol" && !line.match(/^\t<lio/)) + { + line = "</ol>\n" + line; + inlist = 0; + } + + if (!inlist && line.match(/^\t<liu/)) + { + line = "<ul>" + line; + inlist = 1; + listtype = "ul"; + } + + if (!inlist && line.match(/^\t<lio/)) + { + line = "<ol>" + line; + inlist = 1; + listtype = "ol"; + } + + lines[i] = line; + } + + r = lines.join("\n"); + + // jeff added : will correctly replace <li(o|u)> AND </li(o|u)> + r = r.replace(/li[o|u]>/g, "li>"); + + return r; +}
\ No newline at end of file diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Validation.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Validation.js new file mode 100644 index 0000000000..1fadd1d686 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/Validation.js @@ -0,0 +1,172 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2004-2006 by 1&1 Internet AG, Germany, http://www.1and1.org + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Sebastian Werner (wpbasti) + * Andreas Ecker (ecker) + +************************************************************************ */ + +/* ************************************************************************ + +#module(core) + +************************************************************************ */ + +qx.OO.defineClass("qx.util.Validation"); + +/* + All methods use the strict comparison operators as all modern + browsers (needs support for JavaScript 1.3) seems to support this. + + http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Operators:Comparison_Operators +*/ + +qx.util.Validation.isValid = function(v) +{ + switch(typeof v) + { + case "undefined": + return false; + + case "object": + return v !== null; + + case "string": + return v !== ""; + + case "number": + return !isNaN(v); + + case "function": + case "boolean": + return true; + } + + return false; +} + +qx.util.Validation.isInvalid = function(v) +{ + switch(typeof v) + { + case "undefined": + return true; + + case "object": + return v === null; + + case "string": + return v === ""; + + case "number": + return isNaN(v); + + case "function": + case "boolean": + return false; + } + + return true; +} + +qx.util.Validation.isValidNumber = function(v) { + return typeof v === "number" && !isNaN(v); +} + +qx.util.Validation.isInvalidNumber = function(v) { + return typeof v !== "number" || isNaN(v); +} + +qx.util.Validation.isValidString = function(v) { + return typeof v === "string" && v !== ""; +} + +qx.util.Validation.isInvalidString = function(v) { + return typeof v !== "string" || v === ""; +} + +qx.util.Validation.isValidArray = function(v) { + return typeof v === "object" && v !== null && v instanceof Array; +} + +qx.util.Validation.isInvalidArray = function(v) { + return typeof v !== "object" || v === null || !(v instanceof Array); +} + +qx.util.Validation.isValidObject = function(v) { + return typeof v === "object" && v !== null && !(v instanceof Array); +} + +qx.util.Validation.isInvalidObject = function(v) { + return typeof v !== "object" || v === null || v instanceof Array; +} + +qx.util.Validation.isValidNode = function(v) { + return typeof v === "object" && v !== null; +} + +qx.util.Validation.isInvalidNode = function(v) { + return typeof v !== "object" || v === null; +} + +qx.util.Validation.isValidElement = function(v) { + return typeof v === "object" && v !== null || v.nodeType !== 1; +} + +qx.util.Validation.isInvalidElement = function(v) { + return typeof v !== "object" || v === null || v.nodeType !== 1; +} + +qx.util.Validation.isValidFunction = function(v) { + return typeof v === "function"; +} + +qx.util.Validation.isInvalidFunction = function(v) { + return typeof v !== "function"; +} + +qx.util.Validation.isValidBoolean = function(v) { + return typeof v === "boolean"; +} + +qx.util.Validation.isInvalidBoolean = function(v) { + return typeof v !== "boolean"; +} + +qx.util.Validation.isValidStringOrNumber = function(v) +{ + switch(typeof v) + { + case "string": + return v !== ""; + + case "number": + return !isNaN(v); + } + + return false; +} + +qx.util.Validation.isInvalidStringOrNumber = function(v) +{ + switch(typeof v) + { + case "string": + return v === ""; + + case "number": + return isNaN(v); + } + + return false; +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/format/DateFormat.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/format/DateFormat.js new file mode 100644 index 0000000000..7460ea4467 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/format/DateFormat.js @@ -0,0 +1,614 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2006 by STZ-IDA, Germany, http://www.stz-ida.de + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Til Schneider (til132) + +************************************************************************ */ + +/* ************************************************************************ + + +************************************************************************ */ + +/** + * A formatter and parser for dates + * + * @param format {string} The format to use. If null, the + * {@link #DEFAULT_DATE_TIME_FORMAT} is used. + */ +qx.OO.defineClass("qx.util.format.DateFormat", qx.util.format.Format, +function(format) { + qx.util.format.Format.call(this); + + this._format = (format != null) ? format : qx.util.format.DateFormat.DEFAULT_DATE_TIME_FORMAT; +}); + + +/** + * Fills a number with leading zeros ("25" -> "0025"). + * + * @param number {int} the number to fill. + * @param minSize {int} the minimum size the returned string should have. + * @return {string} the filled number as string. + */ +qx.Proto._fillNumber = function(number, minSize) { + var str = "" + number; + while (str.length < minSize) { + str = "0" + str; + } + return str; +} + + +/** + * Returns the day in year of a date. + * + * @param date {Date} the date. + * @return {int} the day in year. + */ +qx.Proto._getDayInYear = function(date) { + var helpDate = new Date(date.getTime()); + var day = helpDate.getDate(); + while (helpDate.getMonth() != 0) { + // Set the date to the last day of the previous month + helpDate.setDate(-1); + day += helpDate.getDate() + 1; + } + return day; +} + + +/** + * Returns the thursday in the same week as the date. + * + * @param date {Date} the date to get the thursday of. + * @return {Date} the thursday in the same week as the date. + */ +qx.Proto._thursdayOfSameWeek = function(date) { + return new Date(date.getTime() + (3 - ((date.getDay() + 6) % 7)) * 86400000); +} + + +/** + * Returns the week in year of a date. + * + * @param date {Date} the date to get the week in year of. + * @return {int} the week in year. + */ +qx.Proto._getWeekInYear = function(date) { + // This algorithm gets the correct calendar week after ISO 8601. + // This standard is used in almost all european countries. + // TODO: In the US week in year is calculated different! + // See http://www.merlyn.demon.co.uk/weekinfo.htm + + // The following algorithm comes from http://www.salesianer.de/util/kalwoch.html + + // Get the thursday of the week the date belongs to + var thursdayDate = this._thursdayOfSameWeek(date); + // Get the year the thursday (and therefor the week) belongs to + var weekYear = thursdayDate.getFullYear(); + // Get the thursday of the week january 4th belongs to + // (which defines week 1 of a year) + var thursdayWeek1 = this._thursdayOfSameWeek(new Date(weekYear, 0, 4)); + // Calculate the calendar week + return Math.floor(1.5 + (thursdayDate.getTime() - thursdayWeek1.getTime()) / 86400000 / 7) +} + + +/** + * Formats a date. + * <p> + * Uses the same syntax as + * <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html" target="_blank"> + * the SimpleDateFormat class in Java</a>. + * + * @param date {Date} The date to format. + * @return {string} the formatted date. + */ +qx.Proto.format = function(date) { + var DateFormat = qx.util.format.DateFormat; + + var fullYear = date.getFullYear(); + var month = date.getMonth(); + var dayOfMonth = date.getDate(); + var dayOfWeek = date.getDay(); + var hours = date.getHours(); + var minutes = date.getMinutes(); + var seconds = date.getSeconds(); + var ms = date.getMilliseconds(); + var timezone = date.getTimezoneOffset() / 60; + + // Create the output + this._initFormatTree(); + var output = ""; + for (var i = 0; i < this._formatTree.length; i++) { + var currAtom = this._formatTree[i]; + + if (currAtom.type == "literal") { + output += currAtom.text; + } else { + // This is a wildcard + var wildcardChar = currAtom.character; + var wildcardSize = currAtom.size; + + // Get its replacement + var replacement = "?"; + switch (wildcardChar) { + // TODO: G - Era designator (e.g. AD). Problem: Not covered by JScript Date class + // TODO: W - Week in month (e.g. 2) + // TODO: F - Day of week in month (e.g. 2). Problem: What is this? + + case 'y': // Year + if (wildcardSize == 2) { + replacement = this._fillNumber(fullYear % 100, 2); + } else if (wildcardSize == 4) { + replacement = fullYear; + } + break; + case 'D': // Day in year (e.g. 189) + replacement = this._fillNumber(this._getDayInYear(date), wildcardSize); break; + case 'd': // Day in month + replacement = this._fillNumber(dayOfMonth, wildcardSize); break; + case 'w': // Week in year (e.g. 27) + replacement = this._fillNumber(this._getWeekInYear(date), wildcardSize); break; + case 'E': // Day in week + if (wildcardSize == 2) { + replacement = DateFormat.SHORT_DAY_OF_WEEK_NAMES[dayOfWeek]; + } else if (wildcardSize == 3) { + replacement = DateFormat.MEDIUM_DAY_OF_WEEK_NAMES[dayOfWeek]; + } else if (wildcardSize == 4) { + replacement = DateFormat.FULL_DAY_OF_WEEK_NAMES[dayOfWeek]; + } + break; + case 'M': // Month + if (wildcardSize == 1 || wildcardSize == 2) { + replacement = this._fillNumber(month + 1, wildcardSize); + } else if (wildcardSize == 3) { + replacement = DateFormat.SHORT_MONTH_NAMES[month]; + } else if (wildcardSize == 4) { + replacement = DateFormat.FULL_MONTH_NAMES[month]; + } + break; + case 'a': // am/pm marker + // NOTE: 0:00 is am, 12:00 is pm + replacement = (hours < 12) ? DateFormat.AM_MARKER : DateFormat.PM_MARKER; break; + case 'H': // Hour in day (0-23) + replacement = this._fillNumber(hours, wildcardSize); break; + case 'k': // Hour in day (1-24) + replacement = this._fillNumber((hours == 0) ? 24 : hours, wildcardSize); break; + case 'K': // Hour in am/pm (0-11) + replacement = this._fillNumber(hours % 12, wildcardSize); break; + case 'h': // Hour in am/pm (1-12) + replacement = this._fillNumber(((hours % 12) == 0) ? 12 : (hours % 12), wildcardSize); break; + case 'm': // Minute in hour + replacement = this._fillNumber(minutes, wildcardSize); break; + case 's': // Second in minute + replacement = this._fillNumber(seconds, wildcardSize); break; + case 'S': // Millisecond + replacement = this._fillNumber(ms, wildcardSize); break; + case 'z': // Time zone + if (wildcardSize == 1) { + replacement = "GMT" + ((timezone < 0) ? "-" : "+") + this._fillNumber(timezone) + ":00"; + } else if (wildcardSize == 2) { + replacement = DateFormat.MEDIUM_TIMEZONE_NAMES[timezone]; + } else if (wildcardSize == 3) { + replacement = DateFormat.FULL_TIMEZONE_NAMES[timezone]; + } + break; + case 'Z': // RFC 822 time zone + replacement = ((timezone < 0) ? "-" : "+") + this._fillNumber(timezone, 2) + "00"; + } + output += replacement; + } + } + + return output; +} + + +/** + * Parses a date. + * <p> + * Uses the same syntax as + * <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html" target="_blank"> + * the SimpleDateFormat class in Java</a>. + * + * @param dateStr {string} the date to parse. + * @return {Date} the parsed date. + * @throws If the format is not well formed or if the date string does not + * match to the format. + */ +qx.Proto.parse = function(dateStr) { + this._initParseFeed(); + + // Apply the regex + var hit = this._parseFeed.regex.exec(dateStr); + if (hit == null) { + throw new Error("Date string '" + dateStr + "' does not match the date format: " + this._format); + } + + // Apply the rules + var dateValues = { year:1970, month:0, day:1, hour:0, ispm:false, min:0, sec:0, ms:0 } + var currGroup = 1; + for (var i = 0; i < this._parseFeed.usedRules.length; i++) { + var rule = this._parseFeed.usedRules[i]; + + var value = hit[currGroup]; + if (rule.field != null) { + dateValues[rule.field] = parseInt(value, 10); + } else { + rule.manipulator(dateValues, value); + } + + currGroup += (rule.groups == null) ? 1 : rule.groups; + } + + var date = new Date(dateValues.year, dateValues.month, dateValues.day, + (dateValues.ispm) ? (dateValues.hour + 12) : dateValues.hour, + dateValues.min, dateValues.sec, dateValues.ms); + if (dateValues.month != date.getMonth() || dateValues.year != date.getFullYear()) { + // TODO: check if this is also necessary for the time components + throw new Error("Error parsing date '" + dateStr + "': the value for day or month is too large"); + } + + return date; +} + + + +/** + * Helper method for {@link #format()} and {@link #parse()}. + * Parses the date format. + */ +qx.Proto._initFormatTree = function() { + if (this._formatTree != null) { + return; + } + + this._formatTree = []; + + var currWildcardChar; + var currWildcardSize; + var currLiteral = ""; + var format = this._format; + for (var i = 0; i < format.length; i++) { + var currChar = format.charAt(i); + + // Check whether we are currently in a wildcard + if (currWildcardChar != null) { + // Check whether the currChar belongs to that wildcard + if (currChar == currWildcardChar) { + // It does -> Raise the size + currWildcardSize++; + } else { + // It does not -> The current wildcard is done + this._formatTree.push({ type:"wildcard", character:currWildcardChar, size:currWildcardSize }); + currWildcardChar = null; + } + } + + if (currWildcardChar == null) { + // We are not (any more) in a wildcard -> Check what's starting here + if ((currChar >= 'a' && currChar <= 'z') || (currChar >= 'A' && currChar <= 'Z')) { + // This is a letter -> All letters are wildcards + + // Add the literal + if (currLiteral.length > 0) { + this._formatTree.push({ type:"literal", text:currLiteral }); + currLiteral = ""; + } + + // Start a new wildcard + currWildcardChar = currChar; + currWildcardSize = 1; + } else { + // This is a literal -> Add it to the current literal + currLiteral += currChar; + } + } + } + + // Add the last wildcard or literal + if (currWildcardChar != null) { + this._formatTree.push({ type:"wildcard", character:currWildcardChar, size:currWildcardSize }); + } else if (currLiteral.length > 0) { + this._formatTree.push({ type:"literal", text:currLiteral }); + } +} + + +/** + * Initializes the parse feed. + * <p> + * The parse contains everything needed for parsing: The regular expression + * (in compiled and uncompiled form) and the used rules. + * + * @return {Map} the parse feed. + */ +qx.Proto._initParseFeed = function() { + if (this._parseFeed != null) { + // We already have the farse feed + return; + } + + var DateFormat = qx.util.format.DateFormat; + + // Initialize the rules + this._initParseRules(); + this._initFormatTree(); + + // Get the used rules and construct the regex pattern + var usedRules = []; + var pattern = "^"; + for (var atomIdx = 0; atomIdx < this._formatTree.length; atomIdx++) { + var currAtom = this._formatTree[atomIdx]; + + if (currAtom.type == "literal") { + pattern += qx.lang.String.escapeRegexpChars(currAtom.text); + } else { + // This is a wildcard + var wildcardChar = currAtom.character; + var wildcardSize = currAtom.size; + + // Get the rule for this wildcard + var wildcardRule; + for (var ruleIdx = 0; ruleIdx < DateFormat._parseRules.length; ruleIdx++) { + var rule = DateFormat._parseRules[ruleIdx]; + if (wildcardChar == rule.pattern.charAt(0) && wildcardSize == rule.pattern.length) { + // We found the right rule for the wildcard + wildcardRule = rule; + break; + } + } + + // Check the rule + if (wildcardRule == null) { + // We have no rule for that wildcard -> Malformed date format + var wildcardStr = ""; + for (var i = 0; i < wildcardSize; i++) { + wildcardStr += wildcardChar; + } + throw new Error("Malformed date format: " + format + ". Wildcard " + + wildcardStr + " is not supported"); + } else { + // Add the rule to the pattern + usedRules.push(wildcardRule); + pattern += wildcardRule.regex; + } + } + } + pattern += "$"; + + // Create the regex + var regex; + try { + regex = new RegExp(pattern); + } + catch (exc) { + throw new Error("Malformed date format: " + format); + } + + // Create the this._parseFeed + this._parseFeed = { regex:regex, "usedRules":usedRules, pattern:pattern } +} + + +/** + * Initializes the static parse rules. + */ +qx.Proto._initParseRules = function() { + var DateFormat = qx.util.format.DateFormat; + + if (DateFormat._parseRules != null) { + // The parse rules are already initialized + return; + } + + DateFormat._parseRules = []; + + var yearManipulator = function(dateValues, value) { + value = parseInt(value, 10); + if (value < DateFormat.ASSUME_YEAR_2000_THRESHOLD) { + value += 2000; + } else if (value < 100) { + value += 1900; + } + + dateValues.year = value; + } + + var monthManipulator = function(dateValues, value) { + dateValues.month = parseInt(value, 10) - 1; + } + + var ampmManipulator = function(dateValues, value) { + dateValues.ispm = (value == DateFormat.PM_MARKER); + } + + var noZeroHourManipulator = function(dateValues, value) { + dateValues.hour = parseInt(value, 10) % 24; + } + + var noZeroAmPmHourManipulator = function(dateValues, value) { + dateValues.hour = parseInt(value, 10) % 12; + } + + // Unsupported: w (Week in year), W (Week in month), D (Day in year), + // F (Day of week in month), z (time zone) reason: no setter in Date class, + // Z (RFC 822 time zone) reason: no setter in Date class + + DateFormat._parseRules.push({ pattern:"yyyy", regex:"(\\d\\d(\\d\\d)?)", + groups:2, manipulator:yearManipulator } ); + DateFormat._parseRules.push({ pattern:"yy", regex:"(\\d\\d)", manipulator:yearManipulator } ); + // TODO: "MMMM", "MMM" (Month names) + DateFormat._parseRules.push({ pattern:"MM", regex:"(\\d\\d?)", manipulator:monthManipulator }); + DateFormat._parseRules.push({ pattern:"dd", regex:"(\\d\\d?)", field:"day" }); + DateFormat._parseRules.push({ pattern:"d", regex:"(\\d\\d?)", field:"day" }); + // TODO: "EEEE", "EEE", "EE" (Day in week names) + DateFormat._parseRules.push({ pattern:"a", + regex:"(" + DateFormat.AM_MARKER + "|" + DateFormat.PM_MARKER + ")", + manipulator:ampmManipulator }); + DateFormat._parseRules.push({ pattern:"HH", regex:"(\\d\\d?)", field:"hour" }); + DateFormat._parseRules.push({ pattern:"H", regex:"(\\d\\d?)", field:"hour" }); + DateFormat._parseRules.push({ pattern:"kk", regex:"(\\d\\d?)", manipulator:noZeroHourManipulator }); + DateFormat._parseRules.push({ pattern:"k", regex:"(\\d\\d?)", manipulator:noZeroHourManipulator }); + DateFormat._parseRules.push({ pattern:"KK", regex:"(\\d\\d?)", field:"hour" }); + DateFormat._parseRules.push({ pattern:"K", regex:"(\\d\\d?)", field:"hour" }); + DateFormat._parseRules.push({ pattern:"hh", regex:"(\\d\\d?)", manipulator:noZeroAmPmHourManipulator }); + DateFormat._parseRules.push({ pattern:"h", regex:"(\\d\\d?)", manipulator:noZeroAmPmHourManipulator }); + DateFormat._parseRules.push({ pattern:"mm", regex:"(\\d\\d?)", field:"min" }); + DateFormat._parseRules.push({ pattern:"m", regex:"(\\d\\d?)", field:"min" }); + DateFormat._parseRules.push({ pattern:"ss", regex:"(\\d\\d?)", field:"sec" }); + DateFormat._parseRules.push({ pattern:"s", regex:"(\\d\\d?)", field:"sec" }); + DateFormat._parseRules.push({ pattern:"SSS", regex:"(\\d\\d?\\d?)", field:"ms" }); + DateFormat._parseRules.push({ pattern:"SS", regex:"(\\d\\d?\\d?)", field:"ms" }); + DateFormat._parseRules.push({ pattern:"S", regex:"(\\d\\d?\\d?)", field:"ms" }); +} + + +/** + * Returns a <code>DateFomat</code> instance that uses the + * {@link #DEFAULT_DATE_TIME_FORMAT}. + * + * @return {string} the date/time instance. + */ +qx.Class.getDateTimeInstance = function() { + var DateFormat = qx.util.format.DateFormat; + + if (DateFormat._dateTimeInstance == null) { + DateFormat._dateTimeInstance = new DateFormat(); + } + return DateFormat._dateTimeInstance; +} + + +/** + * Returns a <code>DateFomat</code> instance that uses the + * {@link #DEFAULT_DATE_FORMAT}. + * + * @return {string} the date instance. + */ +qx.Class.getDateInstance = function() { + var DateFormat = qx.util.format.DateFormat; + + if (DateFormat._dateInstance == null) { + DateFormat._dateInstance = new DateFormat(DateFormat.DEFAULT_DATE_FORMAT); + } + return DateFormat._dateInstance; +} + + +/** + * (int) The threshold until when a year should be assumed to belong to the + * 21st century (e.g. 12 -> 2012). Years over this threshold but below 100 will be + * assumed to belong to the 20th century (e.g. 88 -> 1988). Years over 100 will be + * used unchanged (e.g. 1792 -> 1792). + */ +qx.Class.ASSUME_YEAR_2000_THRESHOLD = 30; + +/** {string} The short date format. */ +qx.Class.SHORT_DATE_FORMAT = "MM/dd/yyyy"; + +/** {string} The medium date format. */ +qx.Class.MEDIUM_DATE_FORMAT = "MMM dd, yyyy"; + +/** {string} The long date format. */ +qx.Class.LONG_DATE_FORMAT = "MMMM dd, yyyy"; + +/** {string} The full date format. */ +qx.Class.FULL_DATE_FORMAT = "EEEE, MMMM dd, yyyy"; + +/** {string} The short time format. */ +qx.Class.SHORT_TIME_FORMAT = "HH:mm"; + +/** {string} The medium time format. */ +qx.Class.MEDIUM_TIME_FORMAT = qx.util.format.DateFormat.SHORT_TIME_FORMAT; + +/** {string} The long time format. */ +qx.Class.LONG_TIME_FORMAT = "HH:mm:ss"; + +/** {string} The full time format. */ +qx.Class.FULL_TIME_FORMAT = "HH:mm:ss zz"; + +/** {string} The short date-time format. */ +qx.Class.SHORT_DATE_TIME_FORMAT + = qx.util.format.DateFormat.SHORT_DATE_FORMAT + " " + + qx.util.format.DateFormat.SHORT_TIME_FORMAT; + +/** {string} The medium date-time format. */ +qx.Class.MEDIUM_DATE_TIME_FORMAT + = qx.util.format.DateFormat.MEDIUM_DATE_FORMAT + " " + + qx.util.format.DateFormat.MEDIUM_TIME_FORMAT; + +/** {string} The long date-time format. */ +qx.Class.LONG_DATE_TIME_FORMAT + = qx.util.format.DateFormat.LONG_DATE_FORMAT + " " + + qx.util.format.DateFormat.LONG_TIME_FORMAT; + +/** {string} The full date-time format. */ +qx.Class.FULL_DATE_TIME_FORMAT + = qx.util.format.DateFormat.FULL_DATE_FORMAT + " " + + qx.util.format.DateFormat.FULL_TIME_FORMAT; + + +/** {string} The date format used for logging. */ +qx.Class.LOGGING_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + +/** {string} The default date/time format. */ +qx.Class.DEFAULT_DATE_TIME_FORMAT = qx.util.format.DateFormat.LOGGING_DATE_TIME_FORMAT; + +/** {string} The default date format. */ +qx.Class.DEFAULT_DATE_FORMAT = qx.util.format.DateFormat.SHORT_DATE_FORMAT; + +/** {string} The am marker. */ +qx.Class.AM_MARKER = "am"; + +/** {string} The pm marker. */ +qx.Class.PM_MARKER = "pm"; + +/** {string[]} The full month names. */ +qx.Class.FULL_MONTH_NAMES = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +]; + +/** {string[]} The short month names. */ +qx.Class.SHORT_MONTH_NAMES = [ + "Jan", "Feb", "Mar", "Apr", "Mai", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +]; + +/** {string[]} The short (two letter) day of week names. */ +qx.Class.SHORT_DAY_OF_WEEK_NAMES = [ + "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" +]; + +/** {string[]} The medium (three letter) day of week names. */ +qx.Class.MEDIUM_DAY_OF_WEEK_NAMES = [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +]; + +/** {string[]} The full day of week names. */ +qx.Class.FULL_DAY_OF_WEEK_NAMES = [ + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" +]; + +/** {string[]} The medium (three letter) timezone names. */ +qx.Class.MEDIUM_TIMEZONE_NAMES = [ + "GMT" // TODO: fill up +]; + +/** {string[]} The full timezone names. */ +qx.Class.FULL_TIMEZONE_NAMES = [ + "Greenwich Mean Time" // TODO: fill up +]; diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/format/Format.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/format/Format.js new file mode 100644 index 0000000000..463d2b3595 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/format/Format.js @@ -0,0 +1,51 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2006 by STZ-IDA, Germany, http://www.stz-ida.de + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Til Schneider (til132) + +************************************************************************ */ + +/* ************************************************************************ + + +************************************************************************ */ + +/** + * Superclass for formatters and parsers. + */ +qx.OO.defineClass("qx.util.format.Format", qx.core.Object, +function() { + qx.core.Object.call(this); +}); + + +/** + * Formats an object. + * + * @param obj {var} The object to format. + * @return {string} the formatted object. + */ +qx.Proto.format = function(obj) { + throw new Error("format is abstract"); +} + + +/** + * Parses an object. + * + * @param str {string} the string to parse. + * @return {var} the parsed object. + */ +qx.Proto.parse = function(str) { + throw new Error("parse is abstract"); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/format/NumberFormat.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/format/NumberFormat.js new file mode 100644 index 0000000000..2b3f2c954a --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/format/NumberFormat.js @@ -0,0 +1,216 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2006 by STZ-IDA, Germany, http://www.stz-ida.de + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Til Schneider (til132) + +************************************************************************ */ + +/* ************************************************************************ + + +************************************************************************ */ + +/** + * A formatter and parser for numbers. + */ +qx.OO.defineClass("qx.util.format.NumberFormat", qx.util.format.Format, +function() { + qx.util.format.Format.call(this); +}); + + +/** + * The minimum number of integer digits (digits before the decimal separator). + * Missing digits will be filled up with 0 ("19" -> "0019"). + */ +qx.OO.addProperty({ name:"minimumIntegerDigits", type:"number", defaultValue:0, allowNull:false }); + +/** + * The maximum number of integer digits (superfluos digits will be cut off + * ("1923" -> "23"). + */ +qx.OO.addProperty({ name:"maximumIntegerDigits", type:"number", defaultValue:null }); + +/** + * The minimum number of fraction digits (digits after the decimal separator). + * Missing digits will be filled up with 0 ("1.5" -> "1.500") + */ +qx.OO.addProperty({ name:"minimumFractionDigits", type:"number", defaultValue:0, allowNull:false }); + +/** + * The maximum number of fraction digits (digits after the decimal separator). + * Superflous digits will cause rounding ("1.8277" -> "1.83") + */ +qx.OO.addProperty({ name:"maximumFractionDigits", type:"number", defaultValue:null }); + +/** Whether thousand groupings should be used {e.g. "1,432,234.65"}. */ +qx.OO.addProperty({ name:"groupingUsed", type:"boolean", defaultValue:true, allowNull:false }); + +/** The prefix to put before the number {"EUR " -> "EUR 12.31"}. */ +qx.OO.addProperty({ name:"prefix", type:"string", defaultValue:"", allowNull:false }); + +/** Sets the postfix to put after the number {" %" -> "56.13 %"}. */ +qx.OO.addProperty({ name:"postfix", type:"string", defaultValue:"", allowNull:false }); + + +/** + * Formats a number. + * + * @param num {number} the number to format. + * @return {string} the formatted number as a string. + */ +qx.Proto.format = function(num) { + var NumberFormat = qx.util.format.NumberFormat; + + var negative = (num < 0); + if (negative) { + num = -num; + } + if (this.getMaximumFractionDigits() != null) { + // Do the rounding + var mover = Math.pow(10, this.getMaximumFractionDigits()); + num = Math.round(num * mover) / mover; + } + + if (num != 0) { // Math.log(0) = -Infinity + var integerDigits = Math.max(parseInt(Math.log(num) / Math.LN10) + 1, 1); + } else { + integerDigits = 1; + } + + var numStr = "" + num; + + // Prepare the integer part + var integerStr = numStr.substring(0, integerDigits); + while (integerStr.length < this.getMinimumIntegerDigits()) { + integerStr = "0" + integerStr; + } + if (this.getMaximumIntegerDigits() != null && integerStr.length > this.getMaximumIntegerDigits()) { + // NOTE: We cut off even though we did rounding before, because there + // may be rounding errors ("12.24000000000001" -> "12.24") + integerStr = integerStr.substring(integerStr.length - this.getMaximumIntegerDigits()); + } + + // Prepare the fraction part + var fractionStr = numStr.substring(integerDigits + 1); + while (fractionStr.length < this.getMinimumFractionDigits()) { + fractionStr += "0"; + } + if (this.getMaximumFractionDigits() != -1 && fractionStr.length > this.getMaximumFractionDigits()) { + // We have already rounded -> Just cut off the rest + fractionStr = fractionStr.substring(0, this.getMaximumFractionDigits()); + } + + // Add the thousand groupings + if (this.getGroupingUsed()) { + var origIntegerStr = integerStr; + integerStr = ""; + var groupPos; + for (groupPos = origIntegerStr.length; groupPos > 3; groupPos -= 3) { + integerStr = NumberFormat.GROUPING_SEPARATOR + + origIntegerStr.substring(groupPos - 3, groupPos) + integerStr; + } + integerStr = origIntegerStr.substring(0, groupPos) + integerStr; + } + + // Workaround: prefix and postfix are null even their defaultValue is "" and + // allowNull is set to false?!? + var prefix = this.getPrefix() ? this.getPrefix() : ""; + var postfix = this.getPostfix() ? this.getPostfix() : ""; + + // Assemble the number + var str = prefix + (negative ? "-" : "") + integerStr; + if (fractionStr.length > 0) { + str += NumberFormat.DECIMAL_SEPARATOR + fractionStr; + } + str += postfix; + + return str; +} + + +/** + * Parses a number. + * + * @param str {string} the string to parse. + * + * @return {double} the number. + */ +qx.Proto.parse = function(str) { + var NumberFormat = qx.util.format.NumberFormat; + + // use the escaped separators for regexp + var groupSepEsc = qx.lang.String.escapeRegexpChars(NumberFormat.GROUPING_SEPARATOR); + var decimalSepEsc = qx.lang.String.escapeRegexpChars(NumberFormat.DECIMAL_SEPARATOR); + + var regex = new RegExp(qx.lang.String.escapeRegexpChars(this.getPrefix()) + + '(-)?([0-9' + groupSepEsc + ']+)' + + '(' + decimalSepEsc + '\\d+)?' + + qx.lang.String.escapeRegexpChars(this.getPostfix())); + + var hit = regex.exec(str); + if (hit == null) { + throw new Error("Number string '" + str + "' does not match the number format"); + } + + var negative = (hit[1] == "-"); + var integerStr = hit[2]; + var fractionStr = hit[3]; + + // Remove the thousand groupings + integerStr = integerStr.replace(new RegExp(groupSepEsc), ""); + + var asStr = (negative ? "-" : "") + integerStr; + if (fractionStr != null && fractionStr.length != 0) { + // Remove the leading decimal separator from the fractions string + fractionStr = fractionStr.replace(new RegExp(decimalSepEsc),""); + asStr += "." + fractionStr; + } + return parseFloat(asStr); +} + + +/** + * Returns the default number format. + * + * @return {NumberFormat} the default number format. + */ +qx.Class.getInstance = function() { + var NumberFormat = qx.util.format.NumberFormat; + if (NumberFormat._instance == null) { + NumberFormat._instance = new NumberFormat(); + } + return NumberFormat._instance; +} + + +/** + * Returns an integer number format. + * + * @return {NumberFormat} an integer number format. + */ +qx.Class.getIntegerInstance = function() { + var NumberFormat = qx.util.format.NumberFormat; + if (NumberFormat._integerInstance == null) { + NumberFormat._integerInstance = new NumberFormat(); + NumberFormat._integerInstance.setMaximumFractionDigits(0); + } + return NumberFormat._integerInstance; +} + + +/** {string} The decimal separator. */ +qx.Class.DECIMAL_SEPARATOR = "."; + +/** {string} The thousand grouping separator. */ +qx.Class.GROUPING_SEPARATOR = ","; diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/FiniteStateMachine.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/FiniteStateMachine.js new file mode 100644 index 0000000000..7c293eb76b --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/FiniteStateMachine.js @@ -0,0 +1,1386 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2006 by Derrell Lipman + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Derrell Lipman (derrell) + +************************************************************************ */ + +/* ************************************************************************ + +#module(util_fsm) + +************************************************************************ */ + +/** + * A finite state machine. + * + * See {@see qx.util.finitestatemacine.State} for details on creating States, + * and {@see qx.util.finitestatemacine.Transitions} for details on creating + * transitions between states. + * + * *EXPERIMENTAL* + * The interface to the finite state machine, states, and transitions is + * experimental. It may change in non-backward-compatible ways as more + * experience is gained in its use. + * + * @param machineName {string} The name of this finite state machine + * + */ +qx.OO.defineClass("qx.util.fsm.FiniteStateMachine", qx.core.Target, +function(machineName) +{ + // Call our superclass' constructor + qx.core.Target.call(this); + + // Save the machine name + this.setName(machineName); + + // Initialize the states object + this._states = { }; + + // Initialize the saved-states stack + this._savedStates = [ ]; + + // Initialize the pending event queue + this._eventQueue = [ ]; + + // Initialize the blocked events queue + this._blockedEvents = [ ]; + + // Create the friendlyToObject" object. Each object has as its property + // name, the friendly name of the object; and as its property value, the + // object itself. + this._friendlyToObject = { }; + + // Create the "friendlyToHash" object. Each object has as its property + // name, the friendly name of the object; and as its property value, the + // hash code of the object. + this._friendlyToHash = { }; + + // Create the "hashToFriendly" object. Each object has as its property + // name, the hash code of the object; and as its property value, the + // friendly name of the object. + this._hashToFriendly = { }; + + // Friendly names can be added to groups, for easy manipulation of enabling + // and disabling groups of widgets. Track which friendly names are in which + // group. + this._groupToFriendly = { }; + + // We also need to be able to map back from friendly name to the groups it + // is in. + this._friendlyToGroups = { }; +}); + + +/* +--------------------------------------------------------------------------- + PROPERTIES +--------------------------------------------------------------------------- +*/ + +/** + * The name of this finite state machine (for debug messages) + */ +qx.OO.addProperty( + { + name : "name", + type : "string" + }); + +/** + * The current state of the finite state machine. + */ +qx.OO.addProperty( + { + name : "state", + type : "string" + }); + +/** + * The previous state of the finite state machine, i.e. the state from which + * we most recently transitioned. Note that this could be the same as the + * current state if a successful transition brought us back to the same + * state. + */ +qx.OO.addProperty( + { + name : "previousState", + type : "string" + }); + +/** + * The state to which we will be transitioning. This property is valid only + * during a Transition's ontransition function and a State's onexit function. + * At all other times, it is null. + */ +qx.OO.addProperty( + { + name : "nextState", + type : "string" + }); + + +/** + * The maximum number of states which may pushed onto the state-stack. It is + * generally a poor idea to have very many states saved on a stack. Following + * program logic becomes very difficult, and the code can be highly + * unmaintainable. The default should be more than adequate. You've been + * warned. + */ +qx.OO.addProperty( + { + name : "maxSavedStates", + type : "number", + defaultValue : 2 + }); + +/* +--------------------------------------------------------------------------- + MODIFIER +--------------------------------------------------------------------------- +*/ + + +/* +--------------------------------------------------------------------------- + UTILITIES +--------------------------------------------------------------------------- +*/ + + +/** + * Add a state to the finite state machine. + * + * @param state {qx.util.fsm.State} + * An object of class qx.util.fsm.State representing a state + * which is to be a part of this finite state machine. + */ +qx.Proto.addState = function(state) +{ + // Ensure that we got valid state info + if (! state instanceof qx.util.fsm.State) + { + throw new Error("Invalid state: not an instance of " + + "qx.util.fsm.State"); + } + + // Retrieve the name of this state + var stateName = state.getName(); + + // Ensure that the state name doesn't already exist + if (stateName in this._states) + { + throw new Error("State " + state + " already exists"); + } + + // Add the new state object to the finite state machine + this._states[stateName] = state; +}; + + +/** + * Replace a state in the finite state machine. This is useful if initially + * "dummy" states are created which load the real state table for a series of + * operations (and possibly also load the gui associated with the new states + * at the same time). Having portions of the finite state machine and their + * associated gui pages loaded at run time can help prevent long delays at + * application start-up time. + * + * @param state {qx.util.fsm.State} + * An object of class qx.util.fsm.State representing a state + * which is to be a part of this finite state machine. + * + * @param bDispose {boolean} + * If <i>true</i>, then dispose the old state object. If <i>false</i>, the + * old state object is returned for disposing by the caller. + * + * @return {Object} + * The old state object if it was not disposed; otherwise null. + */ +qx.Proto.replaceState = function(state, bDispose) +{ + // Ensure that we got valid state info + if (! state instanceof qx.util.fsm.State) + { + throw new Error("Invalid state: not an instance of " + + "qx.util.fsm.State"); + } + + // Retrieve the name of this state + var stateName = state.getName(); + + // Save the old state object, so we can return it to be disposed + var oldState = this._states[stateName]; + + // Replace the old state with the new state object. + this._states[stateName] = state; + + // Did they request that the old state be disposed? + if (bDispose) + { + // Yup. Mark it to be disposed. + oldState._needDispose; + } + + return oldState; +}; + + + +/** + * Add an object (typically a widget) that is to be accessed during state + * transitions, to the finite state machine. + * + * @param friendlyName {string} + * The friendly name to used for access to the object being added. + * + * @param obj {Object} + * The object to associate with the specified friendly name + * + * @param groupNames {Array} + * An optional list of group names of which this object is a member. + */ +qx.Proto.addObject = function(friendlyName, obj, groupNames) +{ + var hash = obj.toHashCode(); + this._friendlyToHash[friendlyName] = hash; + this._hashToFriendly[hash] = friendlyName; + this._friendlyToObject[friendlyName] = obj; + + // If no groupNames are specified, we're done. + if (! groupNames) + { + return; + } + + // Allow either a single group name or an array of group names. If the + // former, we convert it to the latter to make the subsequent code simpler. + if (typeof(groupNames) == "string") + { + groupNames = [ groupNames ]; + } + + // For each group that this friendly name is to be a member of... + for (var i = 0; i < groupNames.length; i++) + { + var groupName = groupNames[i]; + + // If the group name doesn't yet exist... + if (! this._groupToFriendly[groupName]) + { + // ... then create it. + this._groupToFriendly[groupName] = { }; + } + + // Add the friendly name to the list of names in this group + this._groupToFriendly[groupName][friendlyName] = true; + + // If the friendly name group mapping doesn't yet exist... + if (! this._friendlyToGroups[friendlyName]) + { + // ... then create it. + this._friendlyToGroups[friendlyName] = [ ]; + } + + // Append this group name to the list of groups this friendly name is in + this._friendlyToGroups[friendlyName] = + this._friendlyToGroups[friendlyName].concat(groupNames); + } +}; + + +/** + * Remove an object which had previously been added by {@see #addObject}. + * + * @param friendlyName {string} + * The friendly name associated with an object, specifying which object is + * to be removed. + */ +qx.Proto.removeObject = function(friendlyName) +{ + var hash = this._friendlyToHash[friendlyName]; + + // Delete references to any groupos this friendly name was in + if (this._friendlyToGroups[friendlyName]) + { + for (groupName in this._friendlyToGroups[friendlyName]) + { + delete this._groupToFriendly[groupName]; + } + + delete this._friendlyToGroups[friendlyName]; + } + + // Delete the friendly name + delete this._hashToFriendly[hash]; + delete this._friendlyToHash[friendlyName]; + delete this._friendlyToObject[friendlyName]; +}; + + +/** + * Retrieve an object previously saved via {@see #addObject}, using its + * Friendly Name. + * + * @param friendlyName {string} + * The friendly name of the object to be retrieved. + * + * @return {Object} + * The object which has the specified friendly name, or undefined if no + * object has been associated with that name. + */ +qx.Proto.getObject = function(friendlyName) +{ + return this._friendlyToObject[friendlyName]; +}; + + +/** + * Get the friendly name of an object. + * + * @param obj {Object} The object for which the friendly name is desired + * + * @return {string} + * If the object has been previously registered via {@see #addObject}, then + * the friendly name of the object is returned; otherwise, null. + */ +qx.Proto.getFriendlyName = function(obj) +{ + var hash = obj.toHashCode(); + return hash ? this._hashToFriendly[hash] : null; +}; + + +/** + * Retrieve the list of objects which have registered, via {@see addObject} as + * being members of the specified group. + * + * @param groupName {string} + * The name of the group for which the member list is desired. + * + * @return {Array} + * An array containing the friendly names of any objects which are members + * of the specified group. The resultant array may be empty. + */ +qx.Proto.getGroupObjects = function(groupName) +{ + var a = [ ]; + + for (var name in this._groupToFriendly[groupName]) + { + a.push(name); + } + + return a; +}; + + +/** + * Display all of the saved objects and their reverse mappings. + */ +qx.Proto.displayAllObjects = function() +{ + for (var friendlyName in this._friendlyToHash) + { + var hash = this._friendlyToHash[friendlyName]; + var obj = this.getObject(friendlyName); + this.debug(friendlyName + + " => " + + hash); + this.debug(" " + hash + + " => " + + this._hashToFriendly[hash]); + this.debug(" " + friendlyName + + " => " + + this.getObject(friendlyName)); + this.debug(" " + this.getObject(friendlyName) + + " => " + + this.getFriendlyName(obj)); + } +}; + + +/** + * Recursively display an object (as debug messages) + * + * @param obj {Object} + * The object to be recursively displayed + */ +qx.Proto.debugObject = function(obj) +{ + thisClass = this; + + var displayObj = function(obj, level) + { + var indentStr = ""; + for (var i = 0; i < level; i++) + { + indentStr += " "; + } + + if (typeof(obj) != "object") + { + thisClass.debug(indentStr, obj); + return; + } + + for (var prop in obj) + { + if (typeof(obj[prop]) == "object") + { + if (obj[prop] instanceof Array) + { + thisClass.debug(indentStr + prop + ": " + "Array"); + } + else + { + thisClass.debug(indentStr + prop + ": " + "Object"); + } + + displayObj(obj[prop], level + 1); + } + else + { + thisClass.debug(indentStr + prop + ": " + obj[prop]); + } + } + } + + displayObj(obj, 0); +}; + + + +/** + * Start (or restart, after it has terminated) the finite state machine from + * the starting state. The starting state is defined as the first state added + * to the finite state machine. + */ +qx.Proto.start = function() +{ + var stateName; + + // Set the start state to be the first state which was added to the machine + for (stateName in this._states) + { + this.setState(stateName); + this.setPreviousState(null); + this.setNextState(null); + break; + } + + if (! stateName) + { + throw new Error("Machine started with no available states"); + } + + var debugFunctions = + (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine", + "debugFlags") & + qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL); + + // Run the actionsBeforeOnentry actions for the initial state + if (debugFunctions) + { + this.debug(this.getName() + "#" + stateName + "#actionsBeforeOnentry"); + } + this._states[stateName].getAutoActionsBeforeOnentry()(this); + + // Run the entry function for the new state, if one is specified + if (debugFunctions) + { + this.debug(this.getName() + "#" + stateName + "#entry"); + } + this._states[stateName].getOnentry()(this, null); + + // Run the actionsAfterOnentry actions for the initial state + if (debugFunctions) + { + this.debug(this.getName() + "#" + stateName + "#actionsAfterOnentry"); + } + this._states[stateName].getAutoActionsAfterOnentry()(this); + +}; + + +/** + * Save the current or previous state on the saved-state stack. A future + * transition can then provide, as its nextState value, the class constant: + * + * qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK + * + * which will cause the next state to be whatever is at the top of the + * saved-state stack, and remove that top element from the saved-state stack. + * + * @param bCurrent {boolean} + * When <i>true</i>, then push the current state onto the stack. This might + * be used in a transition, before the state has changed. When + * <i>false</i>, then push the previous state onto the stack. This might be + * used in an on entry function to save the previous state to return to. + */ +qx.Proto.pushState = function(bCurrent) +{ + // See if there's room on the state stack for a new state + if (this._savedStates.length >= this.getMaxSavedStates()) + { + // Nope. Programmer error. + throw new Error("Saved-state stack is full"); + } + + if (bCurrent) + { + // Push the current state onto the saved-state stack + this._savedStates.push(this.getState()); + } + else + { + // Push the previous state onto the saved-state stack + this._savedStates.push(this.getPreviousState()); + } +}; + + +/** + * Add the specified event to a list of events to be passed to the next state + * following state transition. + * + * @param event {qx.event.type.Event} + * The event to add to the event queue for processing after state change. + */ +qx.Proto.postponeEvent = function(event) +{ + // Add this event to the blocked event queue, so it will be passed to the + // next state upon transition. + this._blockedEvents.unshift(event); +}; + + +/** + * Copy an event + * + * @param event {qx.event.type.Event} + * The event to be copied + * + * @return {qx.event.type.Event} + * The new copy of the provided event + */ +qx.Proto.copyEvent = function(event) +{ + var e = { }; + for (var prop in event) + { + e[prop] = event[prop]; + } + + return e; +}; + + +/** + * Enqueue an event for processing + * + * @param event {qx.event.type.Event} + * The event to be enqueued + * + * @param bAddAtHead {boolean} + * If <i>true</i>, put the event at the head of the queue for immediate + * processing. If <i>false</i>, place the event at the tail of the queue so + * that it receives in-order processing. + */ +qx.Proto.enqueueEvent = function(event, bAddAtHead) +{ + // Add the event to the event queue + if (bAddAtHead) + { + // Put event at the head of the queue + this._eventQueue.push(event); + } + else + { + // Put event at the tail of the queue + this._eventQueue.unshift(event); + } + + if (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine", + "debugFlags") & + qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS) + { + if (bAddAtHead) + { + this.debug(this.getName() + ": Pushed event: " + event.getType()); + } + else + { + this.debug(this.getName() + ": Queued event: " + event.getType()); + } + } +}; + + +/** + * Event listener for all event types in the finite state machine + * + * @param event {qx.event.type.Event} + * The event that was dispatched. + */ +qx.Proto.eventListener = function(event) +{ + // Events are enqueued upon receipt. Some events are then processed + // immediately; other events get processed later. We need to allow the + // event dispatcher to free the source event upon our return, so we'll clone + // it and enqueue our clone. The source event can then be disposed upon our + // return. + var e = this.copyEvent(event); + + // Enqueue the new event on the tail of the queue + this.enqueueEvent(e, false); + + // Process events + this._processEvents(); +}; + + +/** + * Process all of the events on the event queue. + */ +qx.Proto._processEvents = function() +{ + // eventListener() can potentially be called while we're processing events + if (this._eventProcessingInProgress) + { + // We were processing already, so don't process concurrently. + return; + } + + // Track that we're processing events + this._eventProcessingInProgress = true; + + // Process each of the events on the event queue + while (this._eventQueue.length > 0) + { + // Pull the next event from the pending event queue + var event = this._eventQueue.pop(); + + // Run the finite state machine with this event + this._run(event); + + // We can now dispose the event + event.dispose(); + } + + // We're no longer processing events + this._eventProcessingInProgress = false; +}; + +/** + * Run the finite state machine to process a single event. + * + * @param event {qx.event.type.Event} + * An event that has been dispatched. The event may be handled (if the + * current state handles this event type), queued (if the current state + * blocks this event type), or discarded (if the current state neither + * handles nor blocks this event type). + */ +qx.Proto._run = function(event) +{ + // For use in generated functions... + var fsm = this; + + // State name variables + var thisState; + var nextState; + var prevState; + + // The current State object + var currentState; + + // The transitions available in the current State + var transitions; + + // Events handled by the current State + var e; + + // The action to take place upon receipt of a particular event + var action; + + // Get the debug flags + var debugFlags = + (qx.Settings.getValueOfClass("qx.util.fsm.FiniteStateMachine", + "debugFlags")); + + // Allow slightly faster access to determine if debug is enableda + var debugEvents = + debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS; + var debugTransitions = + debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS; + var debugFunctions = + debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL; + var debugObjectNotFound = + debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND; + + if (debugEvents) + { + this.debug(this.getName() + ": Process event: " + event.getType()); + } + + // Get the current state name + thisState = this.getState(); + + // Get the current State object + currentState = this._states[thisState]; + + // Get a list of the transitions available from this state + transitions = currentState.transitions; + + // Determine how to handle this event + e = currentState.getEvents()[event.getType()]; + + // See if we actually found this event type + if (! e) + { + if (this.debugEvents) + { + this.debug(this.getName() + ": Event '" + event.getType() + "'" + + " not handled. Ignoring."); + } + return; + } + + // We might have found a constant (PREDICATE or BLOCKED) or an object with + // each property name being the friendly name of a saved object, and the + // property value being one of the constants (PREDICATE or BLOCKED). + if (typeof(e) == "object") + { + // Individual objects are listed. Ensure target is a saved object + var friendly = this.getFriendlyName(event.getTarget()); + if (! friendly) + { + // Nope, it doesn't seem so. Just discard it. + if (debugObjectNotFound) + { + this.debug(this.getName() + ": Could not find friendly name for '" + + event.getType() + "' on '" + event.getTarget() + "'"); + } + return; + } + + action = e[friendly]; + } + else + { + action = e; + } + + switch(action) + { + case qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE: + // Process this event. One of the transitions should handle it. + break; + + case qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED: + // This event is blocked. Enqueue it for later, and get outta here. + this._blockedEvents.unshift(event); + return; + + default: + // See if we've been given an explicit transition name + if (typeof(action) == "string") + { + // Yup! Ensure that it exists + if (transitions[action]) + { + // Yup. Create a transitions object containing only this transition. + var trans = transitions[action]; + transitions = { }; + transitions[action] = trans; + } + else + { + throw new Error("Explicit transition " + action + " does not exist"); + } + + break; + } + } + + // We handle the event. Try each transition in turn until we find one that + // is acceptable. + for (var t in transitions) + { + var trans = transitions[t]; + + // Does the predicate allow use of this transition? + switch(trans.getPredicate()(this, event)) + { + case true: + // Transition is allowed. Proceed. + break; + + case false: + // Transition is not allowed. Try next transition. + continue; + + case null: + // Transition indicates not to try further transitions + return; + + default: + throw new Error("Transition " + thisState + ":" + t + + " returned a value other than true, false, or null."); + return; + } + + // We think we can transition to the next state. Set next state. + nextState = trans.getNextState(); + if (typeof(nextState) == "string") + { + // We found a literal state name. Ensure it exists. + if (! nextState in this._states) + { + throw new Error("Attempt to transition to nonexistent state " + + nextState); + } + + // It exists. Track it being the next state. + this.setNextState(nextState); + } + else + { + // If it's not a string, nextState must be a StateChange constant + switch(nextState) + { + case qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE: + // They want to remain in the same state. + nextState = thisState; + this.setNextState(nextState) + break; + + case qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK: + // Switch to the state at the top of the state stack. + if (this._savedStates.length == 0) + { + throw new Error("Attempt to transition to POP_STATE_STACK " + + "while state stack is empty."); + } + + // Pop the state stack to retrieve the state to transition to + nextState = this._savedStates.pop(); + this.setNextState(nextState); + break; + + default: + throw new Error("Internal error: invalid nextState"); + break; + } + } + + // Run the actionsBeforeOntransition actions for this transition + if (debugFunctions) + { + this.debug(this.getName() + "#" + thisState + "#" + t + + "#autoActionsBeforeOntransition"); + } + trans.getAutoActionsBeforeOntransition()(this); + + // Run the 'ontransition' function + if (debugFunctions) + { + this.debug(this.getName() + "#" + thisState + "#" + t + "#ontransition"); + } + trans.getOntransition()(this, event); + + // Run the autoActionsAfterOntransition actions for this transition + if (debugFunctions) + { + this.debug(this.getName() + "#" + thisState + "#" + t + + "#autoActionsAfterOntransition"); + } + trans.getAutoActionsAfterOntransition()(this); + + // Run the autoActionsBeforeOnexit actions for the old state + if (debugFunctions) + { + this.debug(this.getName() + "#" + thisState + + "#autoActionsBeforeOnexit"); + } + currentState.getAutoActionsBeforeOnexit()(this); + + // Run the exit function for the old state + if (debugFunctions) + { + this.debug(this.getName() + "#" + thisState + "#exit"); + } + currentState.getOnexit()(this, event); + + // Run the autoActionsAfterOnexit actions for the old state + if (debugFunctions) + { + this.debug(this.getName() + "#" + thisState + "#autoActionsAfterOnexit"); + } + currentState.getAutoActionsAfterOnexit()(this); + + // If this state has been replaced and we're supposed to dispose it... + if (currentState._needDispose) + { + // ... then dispose it now that it's no longer in use + currentState.dispose(); + } + + // Reset currentState to the new state object + currentState = this._states[this.getNextState()]; + + // set previousState and state, and clear nextState, for transition + this.setPreviousState(thisState); + this.setState(this.getNextState()); + this.setNextState(null); + prevState = thisState; + thisState = nextState; + nextState = undefined; + + // Run the autoActionsBeforeOnentry actions for the new state + if (debugFunctions) + { + this.debug(this.getName() + "#" + thisState + + "#autoActionsBeforeOnentry"); + } + currentState.getAutoActionsBeforeOnentry()(this); + + // Run the entry function for the new state, if one is specified + if (debugFunctions) + { + this.debug(this.getName() + "#" + thisState + "#entry"); + } + currentState.getOnentry()(this, event); + + // Run the autoActionsAfterOnentry actions for the new state + if (debugFunctions) + { + this.debug(this.getName() + "#" + thisState + + "#autoActionsAfterOnentry"); + } + currentState.getAutoActionsAfterOnentry()(this); + + // Add the blocked events to the pending event queue + if (this._blockedEvents.length > 0) + { + this._eventQueue.unshift(this._blockedEvents); + } + + // The blocked event list is now empty + this._blockedEvents = [ ]; + + // Ensure that all actions have been flushed + qx.ui.core.Widget.flushGlobalQueues(); + + if (debugTransitions) + { + this.debug(this.getName() + "#" + prevState + " => " + + this.getName() + "#" + thisState); + } + + // See ya! + return; + } + + if (debugTransitions) + { + this.debug(this.getName() + "#" + thisState + + ": event '" + event.getType() + "'" + + ": no transition found. No state change."); + } +}; + + + +/* +--------------------------------------------------------------------------- + EVENT LISTENERS +--------------------------------------------------------------------------- +*/ + + + +/* +--------------------------------------------------------------------------- + CLASS CONSTANTS +--------------------------------------------------------------------------- +*/ + +/** + * Constants which may be values of the nextState member in the transitionInfo + * parameter of the Transition constructor. + */ +qx.Class.StateChange = +{ + /** When used as a nextState value, means remain in current state */ + CURRENT_STATE : 1, + + /** When used as a nextState value, means go to most-recently pushed state */ + POP_STATE_STACK : 2, + + /** When used as a nextState value, means terminate this state machine */ + TERMINATE : 3 +}; + + +/** + * Constants for use in the events member of the transitionInfo parameter of + * the Transition constructor. + */ +qx.Class.EventHandling = +{ + /** + * This event is handled by this state, but the predicate of a transition + * will determine whether to use that transition. + */ + PREDICATE : 1, + + /** Enqueue this event for possible use by the next state */ + BLOCKED : 2 +}; + +/** + * Debug bitmask values. Set the debug flags from the application by or-ing + * together bits, akin to this: + * + * qx.Settings.setCustomOfClass( + * "qx.util.fsm.FiniteStateMachine", + * "debugFlags", + * (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS | + * qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS | + * qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL | + * qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND)); + */ +qx.Class.DebugFlags = +{ + /** Show events */ + EVENTS : 1, + + /** Show transitions */ + TRANSITIONS : 2, + + /** Show individual function invocations during transitions */ + FUNCTION_DETAIL : 4, + + /** When object friendly names are referenced but not found, show message */ + OBJECT_NOT_FOUND : 8 +}; + + +/* +--------------------------------------------------------------------------- + CLASS DEFAULT SETTINGS +--------------------------------------------------------------------------- +*/ + +/** + * Debug flags: bitmap of DebugFlags (see Class Constants). + */ +qx.Settings.setDefault( + "debugFlags", + (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS | + qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS | + qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND)); + + +/* +--------------------------------------------------------------------------- + CLASS FUNCTIONS +--------------------------------------------------------------------------- +*/ + +/** + * Common function used by {qx.util.fsm.State} and + * {qx.util.fsm.Transition} for checking the value provided for + * auto actions. + * + * Auto-action property values passed to us look akin to: + * + * <pre> + * { + * // The name of a function. + * "setEnabled" : + * [ + * { + * // The parameter value(s), thus "setEnabled(true);" + * "parameters" : [ true ], + * + * // The function would be called on each object: + * // this.getObject("obj1").setEnabled(true); + * // this.getObject("obj2").setEnabled(true); + * "objects" : [ "obj1", "obj2" ] + * + * // And similarly for each object in each specified group. + * "groups" : [ "group1", "group2" ], + * } + * ]; + * + * "setColor" : + * [ + * { + * "parameters" : [ "blue" ] + * "groups" : [ "group3", "group4" ], + * "objects" : [ "obj3", "obj4" ] + * } + * ]; + * }; + * </pre> + * + * @param actionType {string} + * The name of the action being validated (for debug messages) + * + * @param propValue {Object} + * The property value which is being validated + * + * @param propData + * Not used + */ +qx.Class._commonCheckAutoActions = function(actionType, propValue, propData) +{ + // Validate that we received an object property value + if (typeof(propValue) != "object") + { + throw new Error("Invalid " + actionType + " value: " + typeof(propValue)); + } + + // We'll create a function to do the requested actions. Initialize the + // string into which we'll generate the common fragment added to the + // function for each object. + var funcFragment; + + // Here, we'll keep the function body. Initialize a try block. + var func = + "try" + + "{"; + + var param; + var objectAndGroupList; + + // Retrieve the function request, e.g. + // "enabled" : + for (var f in propValue) + { + // Get the function request value object, e.g. + // "setEnabled" : + // [ + // { + // "parameters" : [ true ], + // "objects" : [ "obj1", "obj2" ] + // "groups" : [ "group1", "group2" ], + // } + // ]; + var functionRequest = propValue[f]; + + // The function request value should be an object + if (! functionRequest instanceof Array) + { + throw new Error("Invalid function request type: " + + "expected array, found " + typeof(functionRequest)); + } + + // For each function request... + for (var i = 0; i < functionRequest.length; i++) + { + // Retreive the object and group list object + objectAndGroupList = functionRequest[i]; + + // The object and group list should be an object, e.g. + // { + // "parameters" : [ true ], + // "objects" : [ "obj1", "obj2" ] + // "groups" : [ "group1", "group2" ], + // } + if (typeof(objectAndGroupList) != "object") + { + throw new Error("Invalid function request parameter type: " + + "expected object, found " + + typeof(functionRequest[param])); + } + + // Retrieve the parameter list + params = objectAndGroupList["parameters"]; + + // If it didn't exist, ... + if (! params) + { + // ... use an empty array. + params = [ ]; + } + else + { + // otherwise, ensure we got an array + if (! params instanceof Array) + { + throw new Error("Invalid function parameters: " + + "expected array, found " + typeof(params)); + } + } + + // Create the function to call on each object. The object on which the + // function is called will be prepended later. + funcFragment = f + "("; + + // For each parameter... + for (var j = 0; j < params.length; j++) + { + // If this isn't the first parameter, add a separator + if (j != 0) + { + funcFragment += ","; + } + + if (typeof(params[j]) == "function") + { + // If the parameter is a function, arrange for it to be called + // at run time. + funcFragment += "(" + params[j] + ")(fsm)"; + } + else if (typeof(params[j]) == "string") + { + // If the parameter is a string, quote it. + funcFragment += '"' + params[j] + '"'; + } + else + { + // Otherwise, just add the parameter's literal value + funcFragment += params[j]; + } + } + + // Complete the function call + funcFragment += ")"; + + // Get the "objects" list, e.g. + // "objects" : [ "obj1", "obj2" ] + var a = objectAndGroupList["objects"]; + + // Was there an "objects" list? + if (! a) + { + // Nope. Simplify code by creating an empty array. + a = [ ]; + } + else if (! a instanceof Array) + { + throw new Error("Invalid 'objects' list: expected array, got " + + typeof(a)); + } + + for (var j = 0; j < a.length; j++) + { + // Ensure we got a string + if (typeof(a[j]) != "string") + { + throw new Error("Invalid friendly name in 'objects' list: " + a[j]); + } + + func += " fsm.getObject('" + a[j] + "')." + funcFragment + ";"; + } + + // Get the "groups" list, e.g. + // "groups" : [ "group1, "group2" ] + var g = objectAndGroupList["groups"]; + + // Was a "groups" list found? + if (g) + { + // Yup. Ensure it's an array. + if (! g instanceof Array) + { + throw new Error("Invalid 'groups' list: expected array, got " + + typeof(g)); + } + + for (var groupName in g) + { + // Arrange to call the function on each object in each group + func += + " var groupObjects = " + + " fsm.getGroupObjects('" + g[groupName] + "');" + + " for (var i = 0; i < groupObjects.length; i++)" + + " {" + + " var objName = groupObjects[i];" + + " fsm.getObject(objName)." + funcFragment + ";" + + " }"; + } + } + } + } + + // Terminate the try block for function invocations + func += + "}" + + "catch(e)" + + "{" + + " fsm.debug(e);" + + "}"; + +// o = new qx.core.Object(); +// o.debug("Dynamically created " + actionType + "(fsm) { " + func + " }"); + + // We've now built the entire body of a function that implements calls to + // each of the requested automatic actions. Create and return the function, + // which will become the property value. + return new Function("fsm", func); +}; + + + +/* +--------------------------------------------------------------------------- + DISPOSER +--------------------------------------------------------------------------- +*/ + +qx.Proto.dispose = function() +{ + var e; + var s; + + if (this.getDisposed()) { + return true; + } + + while (this._savedStates.length > 0) + { + s = this._savedStates.pop(); + s = null; + } + this._savedStates = null; + + while (this._eventQueue.length > 0) + { + e = this._eventQueue.pop(); + e.dispose(); + e = null; + } + this._eventQueue = null; + + while (this._blockedEvents.length > 0) + { + e = this._blockedEvents.pop(); + e.dispose(); + e = null; + } + + for (var s in this._states) + { + this._states[s].dispose(); + this._states[s] = null; + delete this._states[s]; + } + this._states = null; + + return qx.core.Target.prototype.dispose.call(this); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/State.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/State.js new file mode 100644 index 0000000000..fc054e304a --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/State.js @@ -0,0 +1,613 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2006 by Derrell Lipman + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Derrell Lipman (derrell) + +************************************************************************ */ + +/* ************************************************************************ + +#module(util_fsm) +#require(qx.util.fsm.FiniteStateMachine) + +************************************************************************ */ + +/** + * Create a new state which may be added to a finite state machine. + * + * *EXPERIMENTAL* + * The interface to the finite state machine, states, and transitions is + * experimental. It may change in non-backward-compatible ways as more + * experience is gained in its use. + * + * @param + * stateName - + * The name of this state. This is the name which may be referenced in + * objects of class qx.util.fsm.Transition, when passing of + * the the transition's predicate means transition to this state. + * + * @param + * stateInfo - + * An object containing any of the following properties: + * + * onentry - + * A function which is called upon entry to the state. Its signature is + * function(fsm, event) and it is saved in the onentry property of the + * state object. (This function is called after the Transition's action + * function and after the previous state's onexit function.) + * + * In the onentry function: + * + * fsm - + * The finite state machine object to which this state is attached. + * + * event - + * The event that caused the finite state machine to run + * + * onexit - + * A function which is called upon exit from the state. Its signature + * is function(fsm, event) and it is saved in the onexit property of the + * state object. (This function is called after the Transition's action + * function and before the next state's onentry function.) + * + * In the onexit function: + * + * fsm - + * The finite state machine object to which this state is attached. + * + * event - + * The event that caused the finite state machine to run + * + * autoActionsBeforeOnentry - + * autoActionsAfterOnentry - + * autoActionsBeforeOnexit - + * autoActionsAfterOnexit - + * Automatic actions which take place at the time specified by the + * property name. In all cases, the action takes place immediately + * before or after the specified function. + * + * The property value for each of these properties is an object which + * describes some number of functions to invoke on a set of specified + * objects (typically widgets). + * + * An example, using autoActionsBeforeOnentry, might look like this: + * + * "autoActionsBeforeOnentry" : + * { + * // The name of a function. + * "enabled" : + * [ + * { + * // The parameter value, thus "setEnabled(true);" + * "parameters" : [ true ], + * + * // The function would be called on each object: + * // this.getObject("obj1").setEnabled(true); + * // this.getObject("obj2").setEnabled(true); + * "objects" : [ "obj1", "obj2" ], + * + * // And similarly for each object in each specified group. + * "groups" : [ "group1", "group2" ] + * } + * ], + * + * // The name of another function. + * "visible" : + * [ + * { + * // The parameter value, thus "setEnabled(true);" + * "parameters" : [ false ], + * + * // The function would be called on each object and group, as + * // described above. + * "objects" : [ "obj3", "obj4" ], + * "groups" : [ "group3", "group4" ] + * } + * ] + * }; + * + * + * events (required) - + * A description to the finite state machine of how to handle a + * particular event, optionally associated with a specific target object + * on which the event was dispatched. This should be an object + * containing one property for each event which is either handled or + * blocked. The property name should be the event name. The property + * value should be one of: + * + * (a) qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE + * + * (b) qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED + * + * (c) a string containing the name of an explicit Transition to use + * + * (d) an object where each property name is the Friendly Name of an + * object (meaning that this rule applies if both the event and + * the event's target object's Friendly Name match), and its + * property value is one of (a), (b) or (c), above. + * + * This object is saved in the events property of the state object. + * + * Additional properties may be provided in stateInfo. They will not be + * used by the finite state machine, but will be available via + * this.getUserData("<propertyName>") during the state's onentry and + * onexit functions. + */ +qx.OO.defineClass("qx.util.fsm.State", qx.core.Object, +function(stateName, stateInfo) +{ + // Call our superclass' constructor + qx.core.Object.call(this, true); + + // Save the state name + this.setName(stateName); + + // Ensure they passed in an object + if (typeof(stateInfo) != "object") + { + throw new Error("State info must be an object"); + } + + // Save data from the stateInfo object + for (var field in stateInfo) + { + // If we find one of our properties, call its setter. + switch(field) + { + case "onentry": + this.setOnentry(stateInfo[field]); + break; + + case "onexit": + this.setOnexit(stateInfo[field]); + break; + + case "autoActionsBeforeOnentry": + this.setAutoActionsBeforeOnentry(stateInfo[field]); + break; + + case "autoActionsAfterOnentry": + this.setAutoActionsAfterOnentry(stateInfo[field]); + break; + + case "autoActionsBeforeOnexit": + this.setAutoActionsBeforeOnexit(stateInfo[field]); + break; + + case "autoActionsAfterOnexit": + this.setAutoActionsAfterOnexit(stateInfo[field]); + break; + + case "events": + this.setEvents(stateInfo[field]); + break; + + default: + // Anything else is user-provided data for their own use. Save it. + this.setUserData(field, stateInfo[field]); + + // Log it in case it was a typo and they intended a built-in field + this.debug("State " + stateName + ": " + + "Adding user-provided field to state: " + field); + + break; + } + } + + + // Check for required but missing properties + if (! this.getEvents()) + { + throw new Error("The events object must be provided in new state info"); + } + + + // Initialize the transition list + this.transitions = { }; +}); + + + + +/* +--------------------------------------------------------------------------- + PROPERTIES +--------------------------------------------------------------------------- +*/ + +/** + * The name of this state. This name may be used as a Transition's nextState + * value, or an explicit next state in the 'events' handling list in a State. + */ +qx.OO.addProperty( + { + name : "name", + type : "string" + }); + +/** + * The onentry function for this state. This is documented in the + * constructor, and is typically provided through the constructor's stateInfo + * object, but it is also possible (but highly NOT recommended) to change this + * dynamically. + */ +qx.OO.addProperty( + { + name : "onentry", + defaultValue : function(fsm, event) { } + }); + +/** + * The onexit function for this state. This is documented in the constructor, + * and is typically provided through the constructor's stateInfo object, but + * it is also possible (but highly NOT recommended) to change this + * dynamically. + */ +qx.OO.addProperty( + { + name : "onexit", + defaultValue : function(fsm, event) { } + }); + +/** + * Automatic actions to take prior to calling the state's onentry function. + * + * The value passed to setAutoActionsBeforeOnentry() should like something + * akin to: + * + * "autoActionsBeforeOnentry" : + * { + * // The name of a function. This would become "setEnabled(" + * "enabled" : + * [ + * { + * // The parameter value, thus "setEnabled(true);" + * "parameters" : [ true ], + * + * // The function would be called on each object: + * // this.getObject("obj1").setEnabled(true); + * // this.getObject("obj2").setEnabled(true); + * "objects" : [ "obj1", "obj2" ] + * + * // And similarly for each object in each specified group. + * "groups" : [ "group1", "group2" ], + * } + * ]; + * }; + */ +qx.OO.addProperty( + { + name : "autoActionsBeforeOnentry", + defaultValue : function(fsm, event) { } + }); + +/** + * Automatic actions to take after return from the state's onentry function. + * + * The value passed to setAutoActionsAfterOnentry() should like something akin + * to: + * + * "autoActionsAfterOnentry" : + * { + * // The name of a function. This would become "setEnabled(" + * "enabled" : + * [ + * { + * // The parameter value, thus "setEnabled(true);" + * "parameters" : [ true ], + * + * // The function would be called on each object: + * // this.getObject("obj1").setEnabled(true); + * // this.getObject("obj2").setEnabled(true); + * "objects" : [ "obj1", "obj2" ] + * + * // And similarly for each object in each specified group. + * "groups" : [ "group1", "group2" ], + * } + * ]; + * }; + */ +qx.OO.addProperty( + { + name : "autoActionsAfterOnentry", + defaultValue : function(fsm, event) { } + }); + +/** + * Automatic actions to take prior to calling the state's onexit function. + * + * The value passed to setAutoActionsBeforeOnexit() should like something akin + * to: + * + * "autoActionsBeforeOnexit" : + * { + * // The name of a function. This would become "setEnabled(" + * "enabled" : + * [ + * { + * // The parameter value, thus "setEnabled(true);" + * "parameters" : [ true ], + * + * // The function would be called on each object: + * // this.getObject("obj1").setEnabled(true); + * // this.getObject("obj2").setEnabled(true); + * "objects" : [ "obj1", "obj2" ] + * + * // And similarly for each object in each specified group. + * "groups" : [ "group1", "group2" ], + * } + * ]; + * }; + */ +qx.OO.addProperty( + { + name : "autoActionsBeforeOnexit", + defaultValue : function(fsm, event) { } + }); + + +/** + * Automatic actions to take after returning from the state's onexit function. + * + * The value passed to setAutoActionsAfterOnexit() should like something akin + * to: + * + * "autoActionsBeforeOnexit" : + * { + * // The name of a function. This would become "setEnabled(" + * "enabled" : + * [ + * { + * // The parameter value, thus "setEnabled(true);" + * "parameters" : [ true ], + * + * // The function would be called on each object: + * // this.getObject("obj1").setEnabled(true); + * // this.getObject("obj2").setEnabled(true); + * "objects" : [ "obj1", "obj2" ] + * + * // And similarly for each object in each specified group. + * "groups" : [ "group1", "group2" ], + * } + * ]; + * }; + */ +qx.OO.addProperty( + { + name : "autoActionsAfterOnexit", + defaultValue : function(fsm, event) { } + }); + + +/** + * The object representing handled and blocked events for this state. This is + * documented in the constructor, and is typically provided through the + * constructor's stateInfo object, but it is also possible (but highly NOT + * recommended) to change this dynamically. + */ +qx.OO.addProperty( + { + name : "events" + }); + + + +/* +--------------------------------------------------------------------------- + MODIFIER +--------------------------------------------------------------------------- +*/ + +qx.Proto._checkName = function(propValue, propData) +{ + // Ensure that we got a valid state name + if (typeof(propValue) != "string" || propValue.length < 1) + { + throw new Error("Invalid state name"); + } + + return propValue; +}; + +qx.Proto._checkOnentry = function(propValue, propData) +{ + // Validate the onentry function + switch(typeof(propValue)) + { + case "undefined": + // None provided. Convert it to a null function + return function(fsm, event) {}; + + case "function": + // We're cool. No changes required + return propValue; + + default: + throw new Error("Invalid onentry type: " + typeof(propValue)); + return null; + } +}; + +qx.Proto._checkOnexit = function(propValue, propData) +{ + // Validate the onexit function + switch(typeof(propValue)) + { + case "undefined": + // None provided. Convert it to a null function + return function(fsm, event) {}; + + case "function": + // We're cool. No changes required + return propValue; + + default: + throw new Error("Invalid onexit type: " + typeof(propValue)); + return null; + } +}; + +qx.Proto._checkEvents = function(propValue, propData) +{ + // Validate that events is an object + if (typeof(propValue) != "object") + { + throw new Error("events must be an object"); + } + + // Confirm that each property is a valid value + // The property value should be one of: + // + // (a) qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE + // + // (b) qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED + // + // (c) a string containing the name of an explicit Transition to use + // + // (d) an object where each property name is the Friendly Name of an + // object (meaning that this rule applies if both the event and + // the event's target object's Friendly Name match), and its + // property value is one of (a), (b) or (c), above. + for (var e in propValue) + { + var action = propValue[e]; + if (typeof(action) == "number" && + action != qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE && + action != qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED) + { + throw new Error("Invalid numeric value in events object: " + + e + ": " + action); + } + else if (typeof(action) == "object") + { + for (action_e in action) + { + if (typeof(action[action_e]) == "number" && + action != qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE && + action != qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED) + { + throw new Error("Invalid numeric value in events object " + + "(" + e + "): " + + action_e + ": " + action[action_e]); + } + else if (typeof(action[action_e]) != "string") + { + throw new Error("Invalid value in events object " + + "(" + e + "): " + + action_e + ": " + action[action_e]); + } + } + } + else if (typeof(action) != "string") + { + throw new Error("Invalid value in events object: " + + e + ": " + propValue[e]); + } + } + + // We're cool. No changes required. + return propValue; +}; + +qx.Proto._checkAutoActionsBeforeOnentry = function(propValue, propData) +{ + return qx.util.fsm.FiniteStateMachine._commonCheckAutoActions( + "autoActionsBeforeOnentry", + propValue, + propData); +}; + +qx.Proto._checkAutoActionsAfterOnentry = function(propValue, propData) +{ + return qx.util.fsm.FiniteStateMachine._commonCheckAutoActions( + "autoActionsAfterOnentry", + propValue, + propData); +}; + +qx.Proto._checkAutoActionsBeforeOnexit = function(propValue, propData) +{ + return qx.util.fsm.FiniteStateMachine._commonCheckAutoActions( + "autoActionsBeforeOnexit", + propValue, + propData); +}; + +qx.Proto._checkAutoActionsAfterOnexit = function(propValue, propData) +{ + return qx.util.fsm.FiniteStateMachine._commonCheckAutoActions( + "autoActionsAfterOnexit", + propValue, + propData); +}; + + +/* +--------------------------------------------------------------------------- + UTILITIES +--------------------------------------------------------------------------- +*/ + +/** + * Add a transition to a state + * + * @param trans {qx.util.fsm.Transition} + * An object of class qx.util.fsm.Transition representing a + * transition which is to be a part of this state. + */ +qx.Proto.addTransition = function(trans) +{ + // Ensure that we got valid transition info + if (! trans instanceof qx.util.fsm.Transition) + { + throw new Error("Invalid transition: not an instance of " + + "qx.util.fsm.Transition"); + } + + // Add the new transition object to the state + this.transitions[trans.getName()] = trans; +}; + + + + +/* +--------------------------------------------------------------------------- + EVENT LISTENERS +--------------------------------------------------------------------------- +*/ + + + +/* +--------------------------------------------------------------------------- + CLASS CONSTANTS +--------------------------------------------------------------------------- +*/ + + + +/* +--------------------------------------------------------------------------- + DISPOSER +--------------------------------------------------------------------------- +*/ + +qx.Proto.dispose = function() +{ + if (this.getDisposed()) { + return true; + } + + return qx.core.Object.prototype.dispose.call(this); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/Transition.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/Transition.js new file mode 100644 index 0000000000..e417e23298 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/Transition.js @@ -0,0 +1,384 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2006 by Derrell Lipman + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * Derrell Lipman (derrell) + +************************************************************************ */ + +/* ************************************************************************ + +#module(util_fsm) +#require(qx.util.fsm.FiniteStateMachine) + +************************************************************************ */ + +/** + * Create a new possible transition from one state to another. + * + * *EXPERIMENTAL* + * The interface to the finite state machine, states, and transitions is + * experimental. It may change in non-backward-compatible ways as more + * experience is gained in its use. + * + * @param transitionName {string} + * The name of this transition, used in debug messages. + * + * @param transitionInfo {Object} + * An object optionally containing any of the following properties: + * + * predicate - + * A function which is called to determine whether this transition is + * acceptable. An acceptable transition will cause the transition's + * "ontransition" function to be run, the current state's "onexit" + * function to be run, and the new state's "onentry" function to be run. + * + * The predicate function's signature is function(fsm, event) and it is + * saved in the predicate property of the transition object. In the + * predicate function: + * + * fsm - + * The finite state machine object to which this state is attached. + * + * event - + * The event that caused a run of the finite state machine + * + * The predicate function should return one of the following three + * values: + * + * - true means the transition is acceptable + * + * - false means the transition is not acceptable, and the next + * transition (if one exists) should be tried to determine if it is + * acceptable + * + * - null means that the transition determined that no further + * transitions should be tried. This might be used when the + * transition ascertained that the event is for a target that is not + * available in the current state, and the event has called + * fsm.queueEvent() to have the event delivered upon state + * transition. + * + * It is possible to create a default predicate -- one that will cause a + * transition to be acceptable always -- by either not providing a + * predicate property, or by explicitely either setting the predicate + * property to 'true' or setting it to a function that unconditionally + * returns 'true'. This default transition should, of course, always be + * the last transition added to a state, since no transition added after + * it will ever be tried. + * + * nextState - + * The state to which we transition, if the predicate returns true + * (meaning the transition is acceptable). The value of nextState may + * be: + * + * - a string, the state name of the state to transition to + * + * - One of the constants: + * - qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE: + * Remain in whatever is the current state + * - qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK: + * Transition to the state at the top of the saved-state stack, + * and remove the top element from the saved-state stack. + * Elements are added to the saved-state stack using + * fsm.pushState(). It is an error if no state exists on the + * saved-state stack. + * - qx.util.fsm.FiniteStateMachine.StateChange.TERMINATE: + * TBD + * + * autoActionsBeforeOntransition - + * autoActionsAfterOntransition - + * Automatic actions which take place at the time specified by the + * property name. In all cases, the action takes place immediately + * before or after the specified function. + * + * The property value for each of these properties is an object which + * describes some number of functions to invoke on a set of specified + * objects (typically widgets). + * + * See {@see qx.util.fsm.State} for an example of autoActions. + * + * ontransition - + * A function which is called if the predicate function for this + * transition returns true. Its signature is function(fsm, event) and + * it is saved in the ontransition property of the transition object. + * In the ontransition function: + * + * fsm - + * The finite state machine object to which this state is attached. + * + * event - + * The event that caused a run of the finite state machine + * + * Additional properties may be provided in transInfo. They will not be + * used by the finite state machine, but will be available via + * this.getUserData("<propertyName>") during the transition's predicate + * and ontransition functions. + */ +qx.OO.defineClass("qx.util.fsm.Transition", qx.core.Object, +function(transitionName, transitionInfo) +{ + // Call our superclass' constructor + qx.core.Object.call(this, true); + + // Save the state name + this.setName(transitionName); + + // Save data from the transitionInfo object + for (var field in transitionInfo) + { + // If we find one of our properties, call its setter. + switch(field) + { + case "predicate": + this.setPredicate(transitionInfo[field]); + break; + + case "nextState": + this.setNextState(transitionInfo[field]); + break; + + case "autoActionsBeforeOntransition": + this.setAutoActionsBeforeOntransition(transitionInfo[field]); + break; + + case "autoActionsAfterOntransition": + this.setAutoActionsAfterOntransition(transitionInfo[field]); + break; + + case "ontransition": + this.setOntransition(transitionInfo[field]); + break; + + default: + // Anything else is user-provided data for their own use. Save it. + this.setUserData(field, transitionInfo[field]); + + // Log it in case it was a typo and they intended a built-in field + this.debug("Transition " + transitionName + ": " + + "Adding user-provided field to transition: " + field); + + break; + } + } +}); + + + + +/* +--------------------------------------------------------------------------- + PROPERTIES +--------------------------------------------------------------------------- +*/ + +/** + * The name of this transition + */ +qx.OO.addProperty( + { + name : "name", + type : "string" + }); + +/** + * The predicate function for this transition. This is documented in the + * constructor, and is typically provided through the constructor's + * transitionInfo object, but it is also possible (but highly NOT recommended) + * to change this dynamically. + */ +qx.OO.addProperty( + { + name : "predicate", + defaultValue : function(fsm, event) { return true; } + }); + +/** + * The state to transition to, if the predicate determines that this + * transition is acceptable. This is documented in the constructor, and is + * typically provided through the constructor's transitionInfo object, but it + * is also possible (but highly NOT recommended) to change this dynamically. + */ +qx.OO.addProperty( + { + name : "nextState", + defaultValue : qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE + }); + +/** + * Automatic actions to take prior to calling the transition's ontransition + * function. This is documented in the constructor, and is typically provided + * through the constructor's transitionInfo object, but it is also possible + * (but highly NOT recommended) to change this dynamically. + */ +qx.OO.addProperty( + { + name : "autoActionsBeforeOntransition", + defaultValue : function(fsm, event) { } + }); + +/** + * Automatic actions to take immediately after calling the transition's + * ontransition function. This is documented in the constructor, and is + * typically provided through the constructor's transitionInfo object, but it + * is also possible (but highly NOT recommended) to change this dynamically. + */ +qx.OO.addProperty( + { + name : "autoActionsAfterOntransition", + defaultValue : function(fsm, event) { } + }); + + +/** + * The function run when the transition is accepted. This is documented in + * the constructor, and is typically provided through the constructor's + * transitionInfo object, but it is also possible (but highly NOT recommended) + * to change this dynamically. + */ +qx.OO.addProperty( + { + name : "ontransition", + defaultValue : function(fsm, event) { } + }); + + + + +/* +--------------------------------------------------------------------------- + MODIFIER +--------------------------------------------------------------------------- +*/ + +qx.Proto._checkName = function(propValue, propData) +{ + // Ensure that we got a valid state name + if (typeof(propValue) != "string" || propValue.length < 1) + { + throw new Error("Invalid transition name"); + } + + return propValue; +}; + +qx.Proto._checkPredicate = function(propValue, propData) +{ + // Validate the predicate. Convert all valid types to function. + switch(typeof(propValue)) + { + case "undefined": + // No predicate means predicate passes + return function(fsm, event) { return true; }; + + case "boolean": + // Convert boolean predicate to a function which returns that value + return function(fsm, event) { return propValue; }; + + case "function": + // Use user-provided function. + return propValue; + + default: + throw new Error("Invalid transition predicate type: " + + typeof(propValue)); + break; + } +}; + +qx.Proto._checkNextState = function(propValue, propData) +{ + // Validate nextState. It must be a string or a number. + switch(typeof(propValue)) + { + case "string": + return propValue; + + case "number": + // Ensure that it's one of the possible state-change constants + switch(propValue) + { + case qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE: + case qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK: + case qx.util.fsm.FiniteStateMachine.StateChange.TERMINATE: + return propValue; + + default: + throw new Error("Invalid transition nextState value: " + + propValue + + ": nextState must be an explicit state name, " + + "or one of the Fsm.StateChange constants"); + } + break; + + default: + throw new Error("Invalid transition nextState type: " + typeof(propValue)); + break; + } +}; + +qx.Proto._checkOntransition = function(propValue, propData) +{ + // Validate the ontransition function. Convert undefined to function. + switch(typeof(propValue) ) + { + case "undefined": + // No provided function just means do nothing. Use a null function. + return function(fsm, event) { }; + + case "function": + // Use user-provided function. + return propValue; + + default: + throw new Error("Invalid ontransition type: " + typeof(propValue)); + break; + } +}; + +/* +--------------------------------------------------------------------------- + UTILITIES +--------------------------------------------------------------------------- +*/ + + +/* +--------------------------------------------------------------------------- + EVENT LISTENERS +--------------------------------------------------------------------------- +*/ + + + +/* +--------------------------------------------------------------------------- + CLASS CONSTANTS +--------------------------------------------------------------------------- +*/ + + + +/* +--------------------------------------------------------------------------- + DISPOSER +--------------------------------------------------------------------------- +*/ + +qx.Proto.dispose = function() +{ + if (this.getDisposed()) { + return true; + } + + return qx.core.Object.prototype.dispose.call(this); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/example.txt b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/example.txt new file mode 100644 index 0000000000..bb92f70083 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/util/fsm/example.txt @@ -0,0 +1,210 @@ +var fsm; +var state; +var trans; + +// Create a new finite state machine called "Test Machine" +fsm = new qx.util.finitestatemachine.Fsm("Test machine"); + +// State S1 +state = new qx.util.finitestatemachine.State( + // State name + "S1", + + // Object with state information + { + // Function called on entry to this state + "onentry" : + function(fsm, event) + { + alert("Previous state: " + fsm.getPreviousState()); + }; + + // Function called on exit from this state + "onexit" : + function(fsm, event) + { + alert("Next state: " + fsm.getNextState()); + }; + + // Automatic actions to take place before a (possibly) new state's onentry + // function is called. + "autoActionsBeforeOnentry" : + { + // The name of a function. + "setEnabled" : + [ + { + // The parameter value(s), thus "setEnabled(true);" + "parameters" : [ true ], + + // The function would be called on each object: + // this.getObject("obj1").setEnabled(true); + // this.getObject("obj2").setEnabled(true); + "objects" : [ "obj1", "obj2" ] + + // And similarly for each object in each specified group. + "groups" : [ "group1", "group2" ], + } + ]; + + "setColor" : + [ + { + "parameters" : [ "blue" ] + "groups" : [ "group3", "group4" ], + "objects" : [ "obj3", "obj4" ] + } + ]; + }; + + // also available, in same format as actionsBeforeOnentry: + // "actionsAfterOnentry", + // "actionsBeforeOnexit" + // "actionsAfterOnexit" + + // Events handled by this state, or queued for processing by a future state + "events" : + { + // The event type "compete" is handled by one of the transitions in this + // state. The transitions will be searched in order of their addition + // to the state, until the predicate for a transition returns true (or + // no predicate is specified for the transition, which is an implicit + // "true") That transition will be used. + "complete" : qx.util.finitestatemachine.Fsm.EventHandling.PREDICATE, + + // The event type "interval" has two objects specified by their + // "friendly name". The action when an event of type "interval" occurs + // depends on which object was the target of the event. + "interval" : + { + // If the target of the event was the object to which we have given + // the friendly name "flash" then use a transition specified by name + "flash" : "S1_S3_interval_flash", + + // If the target of the event was the object to which we have given + // the friendly name "timeout", then enqueue this event for possible + // processing by a future state. + "timeout" : qx.util.finitestatemachine.Fsm.EventHandling.BLOCKED + }, + + // The event type "execute", too, has two objects specified by their + // "friendly name". + "execute" : + { + // If the target of the event was the object to which we have given + // the friend name "ok", search the transitions in order looking for + // one where the predicate is true + "ok" : qx.util.finitestatemachine.Fsm.EventHandling.PREDICATE + + // If the target of the event was the object to which we have given + // the friendly name "restart", then enqueue this event for possible + // processing by a future state. + "restart" : qx.util.finitestatemachine.Fsm.EventHandling.BLOCKED + } + + // all events other than those which are handled or blocked are ignored. + }; + }); + +// Add State S1 to the finite state machine. +fsm.addState(state); + +// Transition from S1 to S2 due to event 1 +trans = new qx.util.finitestatemachine.Transition( + // Transition name + "S1_S2_ev1", + + // Object with transition information + { + // return TRUE to pass + "predicate" : + function(fsm, event) + { + var type = event.getType(); + if (type == "somethingWeCareAbout") + { + return true; + } + else if (type == "somethingToHandleInAnotherState") + { + // reattempt event delivery following state transition + fsm.postponeEvent(event); + + // do no further transition attempts for this event for now + return null; + } + else + { + return false; + } + }, + + // if event matches and predicate passes, pop the state stack and go to + // the state which was found at the top of the stack. States are added to + // the state stack by calling fsm.pushState() during a state's onexit + // function or by a transition's action function. + "nextState" : qx.util.finintestatemachine.Fsm.StateChange.POP_STATE_STACK, + + // action taken during transisition + "action" : + function(fsm, event) + { + // save current state so a future transition can get back to + // this saved state + fsm.pushState(); + } + }); +state.addTransition(trans); + +// Default transition (any event): remain in current state +trans = new qx.util.finitestatemachine.Transition( + "S1_S1_default", + { + // true or undefined : always pass + "predicate" : + function(fsm, event) + { + // This predicate does not pass, and we return null to tell the finite + // state machine that no additional transitions in the transition list + // should be tested. (Note that the next transition is the one + // explicitly called for by the "interval" event on the object with + // friendly name "flash". We do not want a predicate search to find + // it. + return null; + }, + + // return to current state + "nextState" : qx.util.finitestatemacine.CURRENT_STATE, + }); +state.addTransition(trans); + +// Transition from S1 to S2 due to event 2. Since the previous transition +// returned null in its predicate function, the only way to get to this +// transition is when it is called out explicitly in the state's event list. +// This one was specified for the "interval" event on the object with friendly +// name "flash". +trans = new qx.util.finitestatememachine.Transition( + "S1_S3_interval_flash", + { + // No predicate or a value of 'true' means that the predicate passes as if + // a predicate function returned true. + "predicate" : true, + + // if event matches, go to this state + "nextState" : "S2", + + // action taken during transisition + "action" : + function(fsm, event) + { + alert(this.getName() + "action function"); + } + }); +state.addTransition(trans); + +// We would, of course, need to add state S2 since it is specified in a +// nextState property. That is left as an exercise for the reader. + + +// Initialize and start the machine running +fsm.start(); |