You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by jk...@apache.org on 2006/06/10 15:59:38 UTC

svn commit: r413300 [11/16] - in /tapestry/tapestry4/trunk/framework/src/js: dojo/ dojo/src/ dojo/src/animation/ dojo/src/collections/ dojo/src/compat/ dojo/src/crypto/ dojo/src/data/ dojo/src/data/format/ dojo/src/data/provider/ dojo/src/debug/ dojo/s...

Added: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/RichText.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/RichText.js?rev=413300&view=auto
==============================================================================
--- tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/RichText.js (added)
+++ tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/RichText.js Sat Jun 10 06:59:28 2006
@@ -0,0 +1,1451 @@
+/*
+	Copyright (c) 2004-2006, The Dojo Foundation
+	All Rights Reserved.
+
+	Licensed under the Academic Free License version 2.1 or above OR the
+	modified BSD license. For more information on Dojo licensing, see:
+
+		http://dojotoolkit.org/community/licensing.shtml
+*/
+
+ /* -*- tab-width: 4 -*- */
+dojo.provide("dojo.widget.RichText");
+dojo.provide("dojo.widget.html.RichText");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.dom");
+dojo.require("dojo.html");
+dojo.require("dojo.event.*");
+dojo.require("dojo.style");
+dojo.require("dojo.string");
+
+// used to save content
+try {
+	document.write('<textarea id="dojo.widget.RichText.savedContent" ' +
+		'style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>');
+}catch(e){ }
+
+dojo.widget.defineWidget(
+	"dojo.widget.html.RichText",
+	dojo.widget.HtmlWidget,
+	{
+		/** whether to inherit the parent's width or simply use 100% */
+		inheritWidth: false,
+		focusOnLoad: true,
+		
+		/**
+		 * If a save name is specified the content is saved and restored if the
+		 * editor is not properly closed after editing has started.
+		 */
+		saveName: "",
+		_content: "",
+		
+		/* set height to fix the editor at a specific height, with scrolling */
+		height: null,
+
+		/** The minimum height that the editor should have */
+		minHeight: "1em",
+		
+		isClosed: true,
+		isLoaded: false,
+		
+		/** whether to use the active-x object in IE */
+		useActiveX: false,
+
+		/* whether to use relative URLs for images - if this is enabled
+       	images will be given absolute URLs when inside the editor but
+       	will be changed to use relative URLs (to the current page) on save
+		*/
+		relativeImageUrls: false,
+		
+		_SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@",
+
+		// contentFilters: [],
+
+		/*
+		defaultContentCleaner: function(content){
+			if(!dojo.render.html.ie){
+				return content;
+			}
+
+			content = content.replace(/\x20/g, " ");
+			// alert(content);
+			return content;
+		},
+		*/
+
+	/* Init
+	 *******/
+
+		fillInTemplate: function(){
+			this.open();
+
+			// add the formatting functions
+			var funcs = ["queryCommandEnabled", "queryCommandState",
+				"queryCommandValue", "execCommand"];
+			for(var i = 0; i < funcs.length; i++){
+				dojo.event.connect("around", this, funcs[i], this, "_normalizeCommand");
+			}
+			
+			// backwards compatibility, needs to be removed
+			dojo.event.connect(this, "onKeyPressed", this, "afterKeyPress");
+			dojo.event.connect(this, "onKeyPress", this, "keyPress");
+			dojo.event.connect(this, "onKeyDown", this, "keyDown");
+			dojo.event.connect(this, "onKeyUp", this, "keyUp");
+
+			// add default some key handlers		
+			var ctrl = this.KEY_CTRL;
+			var exec = function (cmd, arg) {
+				return arguments.length == 1 ? function () { this.execCommand(cmd); } :
+					function () { this.execCommand(cmd, arg); }
+			}
+				
+			this.addKeyHandler("b", ctrl, exec("bold"));
+			this.addKeyHandler("i", ctrl, exec("italic"));
+			this.addKeyHandler("u", ctrl, exec("underline"));
+			this.addKeyHandler("a", ctrl, exec("selectall"));
+			//this.addKeyHandler("k", ctrl, exec("createlink", ""));
+			//this.addKeyHandler("K", ctrl, exec("unlink"));
+			this.addKeyHandler("s", ctrl, function () { this.save(true); });
+			
+			this.addKeyHandler("1", ctrl, exec("formatblock", "h1"));
+			this.addKeyHandler("2", ctrl, exec("formatblock", "h2"));
+			this.addKeyHandler("3", ctrl, exec("formatblock", "h3"));
+			this.addKeyHandler("4", ctrl, exec("formatblock", "h4"));
+					
+			this.addKeyHandler("\\", ctrl, exec("insertunorderedlist"));
+			if(!dojo.render.html.ie){
+				this.addKeyHandler("Z", ctrl, exec("redo"));
+			}
+		},
+
+
+		events: ["onBlur", "onFocus", "onKeyPress", "onKeyDown", "onKeyUp", "onClick"],
+
+		/**
+		 * Transforms the node referenced in this.domNode into a rich text editing
+		 * node. This can result in the creation and replacement with an <iframe> if
+		 * designMode is used, an <object> and active-x component if inside of IE or
+		 * a reguler element if contentEditable is available.
+		 */
+		open: function (element) {
+			dojo.event.topic.publish("dojo.widget.RichText::open", this);
+
+			if (!this.isClosed) { this.close(); }
+			this._content = "";
+			if((arguments.length == 1)&&(element["nodeName"])){ this.domNode = element; } // else unchanged
+
+			if(	(this.domNode["nodeName"])&&
+				(this.domNode.nodeName.toLowerCase() == "textarea")){
+				this.textarea = this.domNode;
+				var html = dojo.string.trim(this.textarea.value);
+				if(html == ""){ html = "&nbsp;"; }
+				this.domNode = document.createElement("div");
+				with(this.textarea.style){
+					display = "block";
+					position = "absolute";
+					width = "1px";
+					height = "1px";
+					border = margin = padding = "0px";
+					visiblity = "hidden";
+					if(dojo.render.html.ie){
+						overflow = "hidden";
+					}
+				}
+				dojo.dom.insertBefore(this.domNode, this.textarea);
+				this.domNode.innerHTML = html;
+				
+				if(this.textarea.form){
+					dojo.event.connect(this.textarea.form, "onsubmit", 
+						// FIXME: should we be calling close() here instead?
+						dojo.lang.hitch(this, function(){
+							this.textarea.value = this.getEditorContent();
+						})
+					);
+				}
+				
+				// dojo plucks our original domNode from the document so we need
+				// to go back and put ourselves back in
+				var editor = this;
+				dojo.event.connect(this, "postCreate", function (){
+					dojo.dom.insertAfter(editor.textarea, editor.domNode);
+				});
+			}else{
+				var html = dojo.string.trim(this.domNode.innerHTML);
+				if(html == ""){ html = "&nbsp;"; }
+			}
+					
+			this._oldHeight = dojo.style.getContentHeight(this.domNode);
+			this._oldWidth = dojo.style.getContentWidth(this.domNode);
+
+			this._firstChildContributingMargin = this._getContributingMargin(this.domNode, "top");
+			this._lastChildContributingMargin = this._getContributingMargin(this.domNode, "bottom");
+
+			this.savedContent = document.createElement("div");
+			while (this.domNode.hasChildNodes()) {
+				this.savedContent.appendChild(this.domNode.firstChild);
+			}
+			
+			// If we're a list item we have to put in a blank line to force the
+			// bullet to nicely align at the top of text
+			if(	(this.domNode["nodeName"])&&
+				(this.domNode.nodeName == "LI")){
+				this.domNode.innerHTML = " <br>";
+			}
+					
+			if(this.saveName != ""){
+				var saveTextarea = document.getElementById("dojo.widget.RichText.savedContent");
+				if (saveTextarea.value != "") {
+					var datas = saveTextarea.value.split(this._SEPARATOR);
+					for (var i = 0; i < datas.length; i++) {
+						var data = datas[i].split(":");
+						if (data[0] == this.saveName) {
+							html = data[1];
+							datas.splice(i, 1);
+							break;
+						}
+					}				
+				}
+				dojo.event.connect("before", window, "onunload", this, "_saveContent");
+				// dojo.event.connect(window, "onunload", this, "_saveContent");
+			}
+
+			// Safari's selections go all out of whack if we do it inline,
+			// so for now IE is our only hero
+			//if (typeof document.body.contentEditable != "undefined") {
+			if (this.useActiveX && dojo.render.html.ie) { // active-x
+				this._drawObject(html);
+				// dojo.debug(this.object.document);
+			} else if (dojo.render.html.ie) { // contentEditable, easy
+				this.editNode = document.createElement("div");
+				with (this.editNode) {
+					innerHTML = html;
+					contentEditable = true;
+					style.height = this.height ? this.height : this.minHeight;
+				}
+
+				if(this.height){ this.editNode.style.overflowY="scroll"; }
+				// FIXME: setting contentEditable on switches this element to
+				// IE's hasLayout mode, triggering weird margin collapsing
+				// behavior. It's particularly bad if the element you're editing
+				// contains childnodes that don't have margin: defined in local
+				// css rules. It would be nice if it was possible to hack around
+				// this. Sadly _firstChildContributingMargin and 
+				// _lastChildContributingMargin don't work on IE unless all
+				// elements have margins set in CSS :-(
+
+				this.domNode.appendChild(this.editNode);
+
+				dojo.lang.forEach(this.events, function(e){
+					dojo.event.connect(this.editNode, e.toLowerCase(), this, e);
+				}, this);
+			
+				this.window = window;
+				this.document = document;
+				
+				this.onLoad();
+			} else { // designMode in iframe
+				this._drawIframe(html);
+			}
+
+			// TODO: this is a guess at the default line-height, kinda works
+			if (this.domNode.nodeName == "LI") { this.domNode.lastChild.style.marginTop = "-1.2em"; }
+			dojo.html.addClass(this.domNode, "RichTextEditable");
+			
+			this.isClosed = false;
+		},
+
+		_hasCollapseableMargin: function(element, side) {
+			// check if an element has padding or borders on the given side
+			// which would prevent it from collapsing margins
+			if (dojo.style.getPixelValue(element, 
+										 'border-'+side+'-width', 
+										 false)) {
+				return false;
+			} else if (dojo.style.getPixelValue(element, 
+												'padding-'+side,
+												false)) {
+				return false;
+			} else {
+				return true;
+			}
+		},
+
+		_getContributingMargin:	function(element, topOrBottom) {
+			// calculate how much margin this element and its first or last
+			// child are contributing to the total margin between this element
+			// and the adjacent node. CSS border collapsing makes this
+			// necessary.
+
+			if (topOrBottom == "top") {
+				var siblingAttr = "previousSibling";
+				var childSiblingAttr = "nextSibling";
+				var childAttr = "firstChild";
+				var marginProp = "margin-top";
+				var siblingMarginProp = "margin-bottom";
+			} else {
+				var siblingAttr = "nextSibling";
+				var childSiblingAttr = "previousSibling";
+				var childAttr = "lastChild";
+				var marginProp = "margin-bottom";
+				var siblingMarginProp = "margin-top";
+			}
+
+			var elementMargin = dojo.style.getPixelValue(element, marginProp, false);
+
+			function isSignificantNode(element) {
+				// see if an node is significant in the current context
+				// for calulating margins
+				return !(element.nodeType==3 && dojo.string.isBlank(element.data)) 
+					&& dojo.style.getStyle(element, "display") != "none" 
+					&& !dojo.style.isPositionAbsolute(element);
+			}
+
+			// walk throuh first/last children to find total collapsed margin size
+			var childMargin = 0;
+			var child = element[childAttr];
+			while (child) {
+				// skip over insignificant elements (whitespace, etc)
+				while ((!isSignificantNode(child)) && child[childSiblingAttr]) {
+					child = child[childSiblingAttr];
+				}
+						  
+				childMargin = Math.max(childMargin, dojo.style.getPixelValue(child, marginProp, false));
+				// stop if we hit a bordered/padded element
+				if (!this._hasCollapseableMargin(child, topOrBottom)) break;
+				child = child[childAttr];								   
+			}
+
+			// if this element has a border, return full child margin immediately
+			// as there won't be any margin collapsing
+			if (!this._hasCollapseableMargin(element, topOrBottom)){ return parseInt(childMargin); }
+
+			// find margin supplied by nearest sibling
+			var contextMargin = 0;
+			var sibling = element[siblingAttr];
+			while (sibling) {
+				if (isSignificantNode(sibling)) {
+					contextMargin = dojo.style.getPixelValue(sibling, 
+															 siblingMarginProp, 
+															 false);
+					break;
+				}
+				sibling = sibling[siblingAttr];
+			}
+			if (!sibling) { // no sibling, look at parent's margin instead
+				contextMargin = dojo.style.getPixelValue(element.parentNode, 
+												marginProp, false);
+			}
+
+			if (childMargin > elementMargin) {
+				return parseInt(Math.max((childMargin-elementMargin)-contextMargin, 0));
+			} else {
+				return 0;
+			}
+			
+		},
+		
+		/** Draws an iFrame using the existing one if one exists. 
+			Used by Mozilla, Safari, and Opera */
+		_drawIframe: function (html) {
+
+			// detect firefox < 1.5, which has some iframe loading issues
+			var oldMoz = Boolean(dojo.render.html.moz && (
+									typeof window.XML == 'undefined'))
+
+			if (!this.iframe) {
+				var currentDomain = (new dojo.uri.Uri(document.location)).host;
+				this.iframe = document.createElement("iframe");
+				with (this.iframe) {
+					scrolling = this.height ? "auto" : "no";
+					style.border = "none";
+					style.lineHeight = "0"; // squash line height
+					style.verticalAlign = "bottom";
+				}
+			}
+			// opera likes this to be outside the with block
+			this.iframe.src = dojo.uri.dojoUri("src/widget/templates/richtextframe.html") + "#" + ((document.domain != currentDomain) ? document.domain : "");
+			this.iframe.width = this.inheritWidth ? this._oldWidth : "100%";
+			if (this.height) {
+				this.iframe.style.height = this.height;
+			} else {
+				var height = this._oldHeight;
+				if (this._hasCollapseableMargin(this.domNode, 'top')) {
+					height += this._firstChildContributingMargin;
+				}
+				if (this._hasCollapseableMargin(this.domNode, 'bottom')) {
+					height += this._lastChildContributingMargin;
+				}
+				this.iframe.height = height;
+			}
+
+			var tmpContent = document.createElement('div');
+			tmpContent.innerHTML = html;
+
+			// make relative image urls absolute
+			if (this.relativeImageUrls) {
+				var imgs = tmpContent.getElementsByTagName('img');
+				for (var i=0; i<imgs.length; i++) {
+					imgs[i].src = (new dojo.uri.Uri(window.location, imgs[i].src)).toString();
+				}
+				html = tmpContent.innerHTML;
+			}
+
+			// fix margins on tmpContent
+			var firstChild = dojo.dom.firstElement(tmpContent);
+			var lastChild = dojo.dom.lastElement(tmpContent);
+			if(firstChild){
+				firstChild.style.marginTop = this._firstChildContributingMargin+"px";
+			}
+			if(lastChild){
+				lastChild.style.marginBottom = this._lastChildContributingMargin+"px";
+			}
+
+			// show existing content behind iframe for now
+			tmpContent.style.position = "absolute";
+			this.domNode.appendChild(tmpContent);
+			this.domNode.appendChild(this.iframe);
+
+			var _iframeInitialized = false;
+
+			// now we wait for onload. Janky hack!
+			var ifrFunc = dojo.lang.hitch(this, function(){
+				if(!_iframeInitialized){
+					_iframeInitialized = true;
+				}else{ return; }
+				if(!this.editNode){
+					if(this.iframe.contentWindow){
+						this.window = this.iframe.contentWindow;
+					}else{
+						// for opera
+						this.window = this.iframe.contentDocument.window;
+					}
+					if(dojo.render.html.moz){
+						this.document = this.iframe.contentWindow.document
+					}else{
+						this.document = this.iframe.contentDocument;
+					}
+
+					// curry the getStyle function
+					var getStyle = (function (domNode) { return function (style) {
+						return dojo.style.getStyle(domNode, style);
+					}; })(this.domNode);
+
+					var font =
+						getStyle('font-weight') + " " +
+						getStyle('font-size') + " " +
+						getStyle('font-family');
+					
+					// line height is tricky - applying a units value will mess things up.
+					// if we can't get a non-units value, bail out.
+					var lineHeight = "1.0";
+					var lineHeightStyle = dojo.style.getUnitValue(this.domNode, 'line-height');
+					if (lineHeightStyle.value && lineHeightStyle.units=="") {
+						lineHeight = lineHeightStyle.value;
+					}
+
+					dojo.style.insertCssText(
+						'    body,html { background: transparent; padding: 0; margin: 0; }\n' +
+						// TODO: left positioning will case contents to disappear out of view
+						//       if it gets too wide for the visible area
+						'    body { top: 0; left: 0; right: 0;' +
+						(this.height ? '' : ' position: fixed; ') + 
+						'        font: ' + font + ';\n' + 
+						'        min-height: ' + this.minHeight + '; \n' +
+						'        line-height: ' + lineHeight + '} \n' +
+						'    p { margin: 1em 0 !important; }\n' +
+						'    body > *:first-child { padding-top: 0 !important; margin-top: ' + this._firstChildContributingMargin + 'px !important; }\n' + // FIXME: test firstChild nodeType
+						'    body > *:last-child { padding-bottom: 0 !important; margin-bottom: ' + this._lastChildContributingMargin + 'px !important; }\n' +
+						'    li > ul:-moz-first-node, li > ol:-moz-first-node { padding-top: 1.2em; }\n' +
+						'    li { min-height: 1.2em; }\n' +
+						//'    p,ul,li { padding-top: 0; padding-bottom: 0; margin-top:0; margin-bottom: 0; }\n' + 
+						'', this.document);
+
+					tmpContent.parentNode.removeChild(tmpContent);
+					this.document.body.innerHTML = html;
+					if(oldMoz){
+						this.document.designMode = "on";
+					}
+					this.onLoad();
+				}else{
+					tmpContent.parentNode.removeChild(tmpContent);
+					this.editNode.innerHTML = html;
+					this.onDisplayChanged();
+				}
+			});
+
+			if(this.editNode){
+				ifrFunc(); // iframe already exists, just set content
+			}else if(dojo.render.html.moz){
+				// FIXME: if we put this on a delay, we get a height of 20px.
+				// Otherwise we get the correctly specified minHeight value.
+				this.iframe.onload = function(){
+					setTimeout(ifrFunc, 250);
+				}
+			}else{ // new mozillas, opera, safari
+				this.iframe.onload = ifrFunc;
+			}
+		},
+		
+		/** Draws an active x object, used by IE */
+		_drawObject: function (html) {
+			this.object = document.createElement("object");
+
+			with (this.object) {
+				classid = "clsid:2D360201-FFF5-11D1-8D03-00A0C959BC0A";
+				width = this.inheritWidth ? this._oldWidth : "100%";
+				style.height = this.height ? this.height : (this._oldHeight+"px");
+				Scrollbars = this.height ? true : false;
+				Appearance = this._activeX.appearance.flat;
+			}
+			this.domNode.appendChild(this.object);
+
+			this.object.attachEvent("DocumentComplete", dojo.lang.hitch(this, "onLoad"));
+			this.object.attachEvent("DisplayChanged", dojo.lang.hitch(this, "_updateHeight"));
+			this.object.attachEvent("DisplayChanged", dojo.lang.hitch(this, "onDisplayChanged"));
+
+			dojo.lang.forEach(this.events, function(e){
+				this.object.attachEvent(e.toLowerCase(), dojo.lang.hitch(this, e));
+			}, this);
+
+			this.object.DocumentHTML = '<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' +
+				'<title></title>' +
+				'<style type="text/css">' +
+				'    body,html { padding: 0; margin: 0; }' + //font: ' + font + '; }' +
+				(this.height ? '' : '    body { overflow: hidden; }') +
+				//'    #bodywrapper {  }' +
+				'</style>' +
+				//'<base href="' + window.location + '">' +
+				'<body><div id="bodywrapper">' + html + '</div></body>';
+		},
+
+	/* Event handlers
+	 *****************/
+
+	 	_isResized: function(){ return false; },
+
+		onLoad: function(e){
+			this.isLoaded = true;
+			if (this.object){
+				this.document = this.object.DOM;
+				this.window = this.document.parentWindow;
+				this.editNode = this.document.body.firstChild;
+				this.domNode.style.height = this.height ? this.height : this.minHeight;
+				this.connect(this, "onDisplayChanged", "_updateHeight");
+			}else if (this.iframe){
+				this.editNode = this.document.body;
+				this.connect(this, "onDisplayChanged", "_updateHeight");
+		
+				try { // sanity check for Mozilla
+					this.document.execCommand("useCSS", false, true); // old moz call
+					this.document.execCommand("styleWithCSS", false, false); // new moz call
+					//this.document.execCommand("insertBrOnReturn", false, false); // new moz call
+				}catch(e2){ }
+				
+				if (dojo.render.html.safari) {
+					/*
+					this.iframe.style.visiblity = "visible";
+					this.iframe.style.border = "1px solid black";
+					this.editNode.style.visiblity = "visible";
+					this.editNode.style.border = "1px solid black";
+					*/
+					// this.onDisplayChanged();
+					this.connect(this.editNode, "onblur", "onBlur");
+					this.connect(this.editNode, "onfocus", "onFocus");
+				
+					this.interval = setInterval(dojo.lang.hitch(this, "onDisplayChanged"), 750);
+					// dojo.raise("onload");
+					// dojo.debug(this.editNode.parentNode.parentNode.parentNode.nodeName);
+				} else if (dojo.render.html.mozilla || dojo.render.html.opera) {
+
+					// We need to unhook the blur event listener on close as we
+					// can encounter a garunteed crash in FF if another event is
+					// also fired
+					var doc = this.document;
+					var blurfp = dojo.event.browser.addListener(this.document, "blur", dojo.lang.hitch(this, "onBlur"));
+					var unBlur = { unBlur: function(e){
+							dojo.event.browser.removeListener(doc, "blur", blurfp);
+					} };
+					dojo.event.connect("before", this, "close", unBlur, "unBlur");
+					dojo.event.browser.addListener(this.document, "focus", dojo.lang.hitch(this, "onFocus"));
+				
+					// safari can't handle key listeners, it kills the speed
+					var addListener = dojo.event.browser.addListener;
+					addListener(this.document, "keypress", dojo.lang.hitch(this, "onKeyPress"));
+					addListener(this.document, "keydown", dojo.lang.hitch(this, "onKeyDown"));
+					addListener(this.document, "keyup", dojo.lang.hitch(this, "onKeyUp"));
+					addListener(this.document, "click", dojo.lang.hitch(this, "onClick"));
+				}
+
+				// FIXME: when scrollbars appear/disappear this needs to be fired						
+			}else if(dojo.render.html.ie){
+				// IE contentEditable
+				this.editNode.style.zoom = 1.0;
+			}
+			
+			if(this.focusOnLoad){
+				this.focus();
+			}
+			this.onDisplayChanged(e);
+		},
+
+		/** Fired on keydown */
+		onKeyDown: function(e){
+			if((!e)&&(this.object)){
+				e = dojo.event.browser.fixEvent(this.window.event);
+			}
+			dojo.debug("onkeydown:", e.keyCode);
+			// we need this event at the moment to get the events from control keys
+			// such as the backspace. It might be possible to add this to Dojo, so that
+			// keyPress events can be emulated by the keyDown and keyUp detection.
+			if((dojo.render.html.ie)&&(e.keyCode == e.KEY_TAB)){
+				e.preventDefault();
+				e.stopPropagation();
+				// FIXME: this is a poor-man's indent/outdent. It would be
+				// better if it added 4 "&nbsp;" chars in an undoable way.
+				// Unfortuantly pasteHTML does not prove to be undoable 
+				this.execCommand((e.shiftKey ? "outdent" : "indent"));
+			}else if(dojo.render.html.ie){
+				if((65 <= e.keyCode)&&(e.keyCode <= 90)){
+					e.charCode = e.keyCode;
+					this.onKeyPress(e);
+				}
+				// dojo.debug(e.ctrlKey);
+				// dojo.debug(e.keyCode);
+				// dojo.debug(e.charCode);
+				// this.onKeyPress(e);
+			}
+		},
+		
+		/** Fired on keyup */
+		onKeyUp: function(e){
+			return;
+		},
+		
+		KEY_CTRL: 1,
+		
+		/** Fired on keypress. */
+		onKeyPress: function(e){
+			if((!e)&&(this.object)){
+				e = dojo.event.browser.fixEvent(this.window.event);
+			}
+			// handle the various key events
+
+			var character = e.charCode > 0 ? String.fromCharCode(e.charCode) : null;
+			var code = e.keyCode;
+
+			var modifiers = e.ctrlKey ? this.KEY_CTRL : 0;
+
+			if (this._keyHandlers[character]) {
+				dojo.debug("char:", character);
+				var handlers = this._keyHandlers[character], i = 0, handler;
+				while (handler = handlers[i++]) {
+					if (modifiers == handler.modifiers) {
+						handler.handler.call(this);
+						e.preventDefault();
+						break;
+					}
+				}
+			}
+			
+			/*
+			// define some key combos
+			if (e.ctrlKey || e.metaKey) { // modifier pressed
+				switch (character) {
+					case "b": this.execCommand("bold"); break;
+					case "i": this.execCommand("italic"); break;
+					case "u": this.execCommand("underline"); break;
+					//case "a": this.execCommand("selectall"); break;
+					//case "k": this.execCommand("createlink", ""); break;
+					//case "K": this.execCommand("unlink"); break;
+					case "Z": this.execCommand("redo"); break;
+					case "s": this.close(true); break; // saves
+					
+					case "1": this.execCommand("formatblock", "h1"); break;
+					case "2": this.execCommand("formatblock", "h2"); break;
+					case "3": this.execCommand("formatblock", "h3"); break;
+					case "4": this.execCommand("formatblock", "h4"); break;
+					
+					case "\\": this.execCommand("insertunorderedlist"); break;
+					
+					default: switch (code) {
+						case e.KEY_LEFT_ARROW:
+						case e.KEY_RIGHT_ARROW:
+							//break; // preventDefault stops the browser
+								   // going through its history
+						default:
+							preventDefault = false; break; // didn't handle here
+					}
+				}
+			} else {
+				switch (code) {
+					case e.KEY_TAB:
+					  // commenting out bcs it's crashing FF
+						// this.execCommand(e.shiftKey ? "unindent" : "indent");
+						// break;
+					default:
+						preventDefault = false; break; // didn't handle here
+				}
+			}
+			
+			if (preventDefault) { e.preventDefault(); }
+			*/
+
+			// function call after the character has been inserted
+			dojo.lang.setTimeout(this, this.onKeyPressed, 1, e);
+		},
+		
+		addKeyHandler: function (key, modifiers, handler) {
+			if (!(this._keyHandlers[key] instanceof Array)) { this._keyHandlers[key] = []; }
+			this._keyHandlers[key].push({
+				modifiers: modifiers || 0,
+				handler: handler
+			});
+		},
+		
+		
+		
+		/**
+		 * Fired after a keypress event has occured and it's action taken. This
+		 * is useful if action needs to be taken after text operations have
+		 * finished
+		 */
+		onKeyPressed: function (e) {
+			// Mozilla adds a single <p> with an embedded <br> when you hit enter once:
+			//   <p><br>\n</p>
+			// when you hit enter again it adds another <br> inside your enter
+			//   <p><br>\n<br>\n</p>
+			// and if you hit enter again it splits the <br>s over 2 <p>s
+			//   <p><br>\n</p>\n<p><br>\n</p>
+			// now this assumes that <p>s have double the line-height of <br>s to work
+			// and so we need to remove the <p>s to ensure the position of the cursor
+			// changes from the users perspective when they hit enter, as the second two
+			// html snippets render the same when margins are set to 0.
+			
+			// TODO: doesn't really work; is this really needed?
+			//if (dojo.render.html.moz) {
+			//	for (var i = 0; i < this.document.getElementsByTagName("p").length; i++) {
+			//		var p = this.document.getElementsByTagName("p")[i];
+			//		if (p.innerHTML.match(/^<br>\s$/m)) {
+			//			while (p.hasChildNodes()) { p.parentNode.insertBefore(p.firstChild, p); }
+			//			p.parentNode.removeChild(p);
+			//		}
+			//	}
+			//}
+			this.onDisplayChanged(/*e*/); // can't pass in e
+		},
+		
+		onClick: function(e){ this.onDisplayChanged(e); },
+		onBlur: function(e){ },
+		_initialFocus: true,
+		onFocus: function(e){ 
+			if( (dojo.render.html.mozilla)&&(this._initialFocus) ){
+				this._initialFocus = false;
+				if(dojo.string.trim(this.editNode.innerHTML) == "&nbsp;"){
+					this.execCommand("selectall");
+					this.window.getSelection().collapseToStart();
+				}
+			}
+		},
+
+		blur: function () {
+			if (this.iframe) { this.window.blur(); }
+			else if (this.editNode) { this.editNode.blur(); }
+		},
+		
+		focus: function () {
+			if(this.iframe){
+				this.window.focus();
+			}else if(this.editNode){
+				this.editNode.focus();
+			}
+		},
+		
+		/** this event will be fired everytime the display context changes and the
+		 result needs to be reflected in the UI */
+		onDisplayChanged: function (e){ },
+		
+
+	/* Formatting commands
+	 **********************/
+		
+		/** IE's Active X codes */
+		_activeX: {
+			command: {
+				bold: 5000,
+				italic: 5023,
+				underline: 5048,
+
+				justifycenter: 5024,
+				justifyleft: 5025,
+				justifyright: 5026,
+
+				cut: 5003,
+				copy: 5002,
+				paste: 5032,
+				"delete": 5004,
+
+				undo: 5049,
+				redo: 5033,
+
+				removeformat: 5034,
+				selectall: 5035,
+				unlink: 5050,
+
+				indent: 5018,
+				outdent: 5031,
+
+				insertorderedlist: 5030,
+				insertunorderedlist: 5051,
+
+				// table commands
+				inserttable: 5022,
+				insertcell: 5019,
+				insertcol: 5020,
+				insertrow: 5021,
+				deletecells: 5005,
+				deletecols: 5006,
+				deleterows: 5007,
+				mergecells: 5029,
+				splitcell: 5047,
+				
+				// the command need mapping, they don't translate directly
+				// to the contentEditable commands
+				setblockformat: 5043,
+				getblockformat: 5011,
+				getblockformatnames: 5012,
+				setfontname: 5044,
+				getfontname: 5013,
+				setfontsize: 5045,
+				getfontsize: 5014,
+				setbackcolor: 5042,
+				getbackcolor: 5010,
+				setforecolor: 5046,
+				getforecolor: 5015,
+				
+				findtext: 5008,
+				font: 5009,
+				hyperlink: 5016,
+				image: 5017,
+				
+				lockelement: 5027,
+				makeabsolute: 5028,
+				sendbackward: 5036,
+				bringforward: 5037,
+				sendbelowtext: 5038,
+				bringabovetext: 5039,
+				sendtoback: 5040,
+				bringtofront: 5041,
+				
+				properties: 5052
+			},
+			
+			ui: {
+				"default": 0,
+				prompt: 1,
+				noprompt: 2
+			},
+			
+			status: {
+				notsupported: 0,
+				disabled: 1,
+				enabled: 3,
+				latched: 7,
+				ninched: 11
+			},
+			
+			appearance: {
+				flat: 0,
+				inset: 1
+			},
+			
+			state: {
+				unchecked: 0,
+				checked: 1,
+				gray: 2
+			}
+		},
+		
+		/**
+		 * Used as the advice function by dojo.event.connect to map our
+		 * normalized set of commands to those supported by the target
+		 * browser
+		 *
+		 * @param arugments The arguments Array, containing at least one
+		 *                  item, the command and an optional second item,
+		 *                  an argument.
+		 */
+		_normalizeCommand: function (joinObject){
+			var drh = dojo.render.html;
+			
+			var command = joinObject.args[0].toLowerCase();
+			if(command == "formatblock"){
+				if(drh.safari){ command = "heading"; }
+				if(drh.ie){ joinObject.args[1] = "<"+joinObject.args[1]+">"; }
+			}
+			if (command == "hilitecolor" && !drh.mozilla) { command = "backcolor"; }
+			joinObject.args[0] = command;
+			
+			if (joinObject.args.length > 1) { // a command was specified
+				var argument = joinObject.args[1];
+				if (command == "heading") { throw new Error("unimplemented"); }
+				joinObject.args[1] = argument;
+			}
+			
+			return joinObject.proceed();
+		},
+		
+		/**
+		 * Tests whether a command is supported by the host. Clients SHOULD check
+		 * whether a command is supported before attempting to use it, behaviour
+		 * for unsupported commands is undefined.
+		 *
+		 * @param command The command to test for
+		 * @return true if the command is supported, false otherwise
+		 */
+		queryCommandAvailable: function (command) {
+			var ie = 1;
+			var mozilla = 1 << 1;
+			var safari = 1 << 2;
+			var opera = 1 << 3;
+			function isSupportedBy (browsers) {
+				return {
+					ie: Boolean(browsers & ie),
+					mozilla: Boolean(browsers & mozilla),
+					safari: Boolean(browsers & safari),
+					opera: Boolean(browsers & opera)
+				}
+			}
+
+			var supportedBy = null;
+			
+			switch (command.toLowerCase()) {
+				case "bold": case "italic": case "underline":
+				case "subscript": case "superscript":
+				case "fontname": case "fontsize":
+				case "forecolor": case "hilitecolor":
+				case "justifycenter": case "justifyfull": case "justifyleft": 
+				case "justifyright": case "delete": case "undo": case "redo":
+					supportedBy = isSupportedBy(mozilla | ie | safari | opera);
+					break;
+					
+				case "createlink": case "unlink": case "removeformat":
+				case "inserthorizontalrule": case "insertimage":
+				case "insertorderedlist": case "insertunorderedlist":
+				case "indent": case "outdent": case "formatblock": 
+				case "inserthtml":
+					supportedBy = isSupportedBy(mozilla | ie | opera);
+					break;
+					
+				case "strikethrough": 
+					supportedBy = isSupportedBy(mozilla |  opera | (this.object ? 0 : ie));
+					break;
+
+				case "blockdirltr": case "blockdirrtl":
+				case "dirltr": case "dirrtl":
+				case "inlinedirltr": case "inlinedirrtl":
+				case "cut": case "copy": case "paste": 
+					supportedBy = isSupportedBy(ie);
+					break;
+				
+				case "inserttable":
+					supportedBy = isSupportedBy(mozilla | (this.object ? ie : 0));
+					break;
+				
+				case "insertcell": case "insertcol": case "insertrow":
+				case "deletecells": case "deletecols": case "deleterows":
+				case "mergecells": case "splitcell":
+					supportedBy = isSupportedBy(this.object ? ie : 0);
+					break;
+				
+				default: return false;
+			}
+			
+			return (dojo.render.html.ie && supportedBy.ie) ||
+				(dojo.render.html.mozilla && supportedBy.mozilla) ||
+				(dojo.render.html.safari && supportedBy.safari) ||
+				(dojo.render.html.opera && supportedBy.opera);
+		},
+
+		/**
+		 * Executes a command in the Rich Text area
+		 *
+		 * @param command The command to execute
+		 * @param argument An optional argument to the command
+		 */
+		execCommand: function (command, argument){
+			var returnValue;
+			if(this.object){
+				if(command == "forecolor"){
+					command = "setforecolor";
+				}else if(command == "backcolor"){
+					command = "setbackcolor";
+				}
+			
+				//if (typeof this._activeX.command[command] == "undefined") { return null; }
+			
+				if(command == "inserttable"){
+					var tableInfo = this.constructor._tableInfo;
+					if(!tableInfo){
+						tableInfo = document.createElement("object");
+						tableInfo.classid = "clsid:47B0DFC7-B7A3-11D1-ADC5-006008A5848C";
+						document.body.appendChild(tableInfo);
+						this.constructor._table = tableInfo;
+					}
+					
+					tableInfo.NumRows = argument["rows"];
+					tableInfo.NumCols = argument["cols"];
+					tableInfo.TableAttrs = argument["TableAttrs"];
+					tableInfo.CellAttrs = argument["CellAttrs"];
+					tableInfo.Caption = argument["Caption"];
+				}
+			
+				if(command == "inserthtml"){
+					var insertRange = this.document.selection.createRange();
+					insertRange.select();
+					insertRange.pasteHTML(argument);
+					insertRange.collapse(true);
+					return true;
+				}else if(arguments.length == 1){
+					return this.object.ExecCommand(this._activeX.command[command],
+						this._activeX.ui.noprompt);
+				}else{
+					return this.object.ExecCommand(this._activeX.command[command],
+						this._activeX.ui.noprompt, argument);
+				}
+		
+			/* */
+			}else if(command == "inserthtml"){
+				// on IE, we can use the pasteHTML method of the textRange object
+				// to get an undo-able innerHTML modification
+				if(dojo.render.html.ie){
+					dojo.debug("inserthtml breaks the undo stack when not using the ActiveX version of the control!");
+					var insertRange = this.document.selection.createRange();
+					insertRange.select();
+					insertRange.pasteHTML(argument);
+					insertRange.collapse(true);
+					return true;
+				}else{
+					return this.document.execCommand(command, false, argument);			
+				}
+			/* */
+			// fix up unlink in Mozilla to unlink the link and not just the selection
+			}else if((command == "unlink")&&
+				(this.queryCommandEnabled("unlink"))&&
+				(dojo.render.html.mozilla)){
+				// grab selection
+				// Mozilla gets upset if we just store the range so we have to
+				// get the basic properties and recreate to save the selection
+				var selection = this.window.getSelection();
+				var selectionRange = selection.getRangeAt(0);
+				var selectionStartContainer = selectionRange.startContainer;
+				var selectionStartOffset = selectionRange.startOffset;
+				var selectionEndContainer = selectionRange.endContainer;
+				var selectionEndOffset = selectionRange.endOffset;
+				
+				// select our link and unlink
+				var range = document.createRange();
+				var a = this.getSelectedNode();
+				while(a.nodeName != "A"){ a = a.parentNode; }
+				range.selectNode(a);
+				selection.removeAllRanges();
+				selection.addRange(range);
+				
+				returnValue = this.document.execCommand("unlink", false, null);
+				
+				// restore original selection
+				var selectionRange = document.createRange();
+				selectionRange.setStart(selectionStartContainer, selectionStartOffset);
+				selectionRange.setEnd(selectionEndContainer, selectionEndOffset);
+				selection.removeAllRanges();
+				selection.addRange(selectionRange);
+				
+				return returnValue;
+			}else if((command == "inserttable")&&(dojo.render.html.mozilla)){
+
+				var cols = "<tr>";
+				for (var i = 0; i < argument.cols; i++) { cols += "<td></td>"; }
+				cols += "</tr>";
+			
+				var table = "<table><tbody>";
+				for (var i = 0; i < argument.rows; i++) { table += cols; }
+				table += "</tbody></table>";
+				returnValue = this.document.execCommand("inserthtml", false, table);
+
+			}else if((command == "hilitecolor")&&(dojo.render.html.mozilla)){
+				// mozilla doesn't support hilitecolor properly when useCSS is
+				// set to false (bugzilla #279330)
+				
+				this.document.execCommand("useCSS", false, false);
+				returnValue = this.document.execCommand(command, false, argument);			
+				this.document.execCommand("useCSS", false, true);
+			
+			}else if((dojo.render.html.ie)&&( (command == "backcolor")||(command == "forecolor") )){
+				// IE weirdly collapses ranges when we exec these commands, so prevent it	
+				var tr = this.document.selection.createRange();
+				argument = arguments.length > 1 ? argument : null;
+				returnValue = this.document.execCommand(command, false, argument);
+				// timeout is workaround for weird IE behavior were the text
+				// selection gets correctly re-created, but subsequent input
+				// apparently isn't bound to it
+				setTimeout(function(){tr.select();}, 1);
+			}else{
+				// dojo.debug("command:", command, "arg:", argument);
+
+				argument = arguments.length > 1 ? argument : null;
+				if(dojo.render.html.moz){
+					this.document = this.iframe.contentWindow.document
+				}
+				returnValue = this.document.execCommand(command, false, argument);
+
+				// try{
+				// }catch(e){
+				// 	dojo.debug(e);
+				// }
+			}
+			
+			this.onDisplayChanged();
+			return returnValue;
+		},
+
+		queryCommandEnabled: function(command, argument){
+			if(this.object){
+				if(command == "forecolor"){
+					command = "setforecolor";
+				}else if(command == "backcolor"){
+					command = "setbackcolor";
+				}
+
+				if(typeof this._activeX.command[command] == "undefined"){ return false; }
+				var status = this.object.QueryStatus(this._activeX.command[command]);
+				return ((status != this.activeX.status.notsupported)&& 
+					(status != this.activeX.status.diabled));
+			}else{
+				// mozilla returns true always
+				if(command == "unlink" && dojo.render.html.mozilla){
+					var node = this.getSelectedNode();
+					while (node.parentNode && node.nodeName != "A") { node = node.parentNode; }
+					return node.nodeName == "A";
+				} else if (command == "inserttable" && dojo.render.html.mozilla) {
+					return true;
+				}
+
+				// return this.document.queryCommandEnabled(command);
+				var elem = (dojo.render.html.ie) ? this.document.selection.createRange() : this.document;
+				return elem.queryCommandEnabled(command);
+			}
+		},
+
+		queryCommandState: function(command, argument){
+			if(this.object){
+				if(command == "forecolor"){
+					command = "setforecolor";
+				}else if(command == "backcolor"){
+					command = "setbackcolor";
+				}
+
+				if(typeof this._activeX.command[command] == "undefined"){ return null; }
+				var status = this.object.QueryStatus(this._activeX.command[command]);
+				return ((status == this._activeX.status.enabled)||
+					(status == this._activeX.status.ninched));
+			}else{
+				return this.document.queryCommandState(command);
+			}
+		},
+
+		queryCommandValue: function (command, argument) {
+			if (this.object) {
+				switch (command) {
+					case "forecolor":
+					case "backcolor":
+					case "fontsize":
+					case "fontname":
+					case "blockformat":
+						command = "get" + command;
+						return this.object.execCommand(
+							this._activeX.command[command],
+							this._activeX.ui.noprompt);
+				}			
+			
+				//var status = this.object.QueryStatus(this._activeX.command[command]);
+			} else {
+				return this.document.queryCommandValue(command);
+			}
+		},
+		
+		
+	/* Misc.
+	 ********/
+
+		getSelectedNode: function(){
+			if(!this.isLoaded){ return; }
+			if(this.document.selection){
+				return this.document.selection.createRange().parentElement();
+			}else if(dojo.render.html.mozilla){
+				return this.window.getSelection().getRangeAt(0).commonAncestorContainer;
+			}
+			return this.editNode;
+		},
+		
+		placeCursorAtStart: function(){
+			if(!this.isLoaded){
+				dojo.event.connect(this, "onLoad", this, "placeCursorAtEnd");
+				return;
+			}
+			dojo.event.disconnect(this, "onLoad", this, "placeCursorAtEnd");
+			if(this.window.getSelection){
+				var selection = this.window.getSelection;
+				if(selection.removeAllRanges){ // Mozilla
+					var range = this.document.createRange();
+					range.selectNode(this.editNode.firstChild);
+					range.collapse(true);
+					var selection = this.window.getSelection();
+					selection.removeAllRanges();
+					selection.addRange(range);
+				}else{ // Safari
+					// not a great deal we can do
+				}
+			}else if(this.document.selection){ // IE
+				var range = this.document.body.createTextRange();
+				range.moveToElementText(this.editNode);
+				range.collapse(true);
+				range.select();
+			}
+		},
+
+		replaceEditorContent: function(html){
+			if(this.window.getSelection){
+				var selection = this.window.getSelection;
+				// if(selection.removeAllRanges){ // Mozilla			
+				if(dojo.render.html.moz){ // Mozilla			
+					var range = this.document.createRange();
+					range.selectNodeContents(this.editNode);
+					var selection = this.window.getSelection();
+					selection.removeAllRanges();
+					selection.addRange(range);
+					this.execCommand("inserthtml", html);
+				}else{ // Safari
+					// look ma! it's a totally f'd browser!
+					this.editNode.innerHTML = html;
+				}
+			}else if(this.document.selection){ // IE
+				var range = this.document.body.createTextRange();
+				range.moveToElementText(this.editNode);
+				range.select();
+				this.execCommand("inserthtml", html);
+			}
+		},
+		
+		placeCursorAtEnd: function(){
+			if(!this.isLoaded){
+				dojo.event.connect(this, "onLoad", this, "placeCursorAtEnd");
+				return;
+			}
+			dojo.event.disconnect(this, "onLoad", this, "placeCursorAtEnd");
+			if(this.window.getSelection){
+				var selection = this.window.getSelection;
+				if(selection.removeAllRanges){ // Mozilla
+					var range = this.document.createRange();
+					range.selectNode(this.editNode.lastChild);
+					range.collapse(false);
+					var selection = this.window.getSelection();
+					selection.removeAllRanges();
+					selection.addRange(range);
+				}else{ // Safari
+					// not a great deal we can do
+				}
+			}else if(this.document.selection){ // IE
+				var range = this.document.body.createTextRange();
+				range.moveToElementText(this.editNode);
+				range.collapse(true);
+				range.select();
+			}
+		},
+
+		_lastHeight: 0,
+
+		/** Updates the height of the iframe to fit the contents. */
+		_updateHeight: function(){
+			if(!this.isLoaded){ return; }
+			if(this.height){ return; }
+			if(this.iframe){
+				/*
+				if(!this.document.body["offsetHeight"]){
+					return;
+				}
+				*/
+				// The height includes the padding, borders and margins so these
+				// need to be added on
+				var heights = ["margin-top", "margin-bottom",
+					"padding-bottom", "padding-top",
+					"border-width-bottom", "border-width-top"];
+				for(var i = 0, chromeheight = 0; i < heights.length; i++){
+					var height = dojo.style.getStyle(this.iframe, heights[i]);
+					// Safari doesn't have all the heights so we have to test
+					if(height){
+						chromeheight += Number(height.replace(/[^0-9]/g, ""));
+					}
+				}
+
+				if(this.document.body["offsetHeight"]){
+					this._lastHeight = Math.max(this.document.body.scrollHeight, this.document.body.offsetHeight) + chromeheight;
+					this.iframe.height = this._lastHeight + "px";
+					this.window.scrollTo(0, 0);
+				}
+				// dojo.debug(this.iframe.height);
+			}else if(this.object){
+				this.object.style.height = dojo.style.getInnerHeight(this.editNode)+"px";
+			}
+		},
+		
+		/**
+		 * Saves the content in an onunload event if the editor has not been closed
+		 */
+		_saveContent: function(e){
+			var saveTextarea = document.getElementById("dojo.widget.RichText.savedContent");
+			saveTextarea.value += this._SEPARATOR + this.saveName + ":" + this.getEditorContent();
+		},
+
+		getEditorContent: function(){
+			var ec = "";
+			try{
+				ec = (this._content.length > 0) ? this._content : this.editNode.innerHTML;
+				if(dojo.string.trim(ec) == "&nbsp;"){ ec = ""; }
+			}catch(e){ /* squelch */ }
+
+			dojo.lang.forEach(this.contentFilters, function(ef){
+				ec = ef(ec);
+			});
+
+			if (this.relativeImageUrls) {
+				// why use a regexp instead of dom? because IE is stupid 
+				// and won't let us set img.src to a relative URL
+				// this comes after contentFilters because once content
+				// gets innerHTML'd img urls will be fully qualified
+				var siteBase = window.location.protocol + "//" + window.location.host;
+				var pathBase = window.location.pathname;
+				if (pathBase.match(/\/$/)) {
+					// ends with slash, match full path
+				} else {
+					// match parent path to find siblings
+					var pathParts = pathBase.split("/");
+					if (pathParts.length) {
+						pathParts.pop();
+					}
+					pathBase = pathParts.join("/") + "/";
+
+				}
+				
+				var sameSite = new RegExp("(<img[^>]*\ src=[\"'])("+siteBase+"("+pathBase+")?)", "ig");
+				ec = ec.replace(sameSite, "$1");
+			}
+			return ec;
+		},
+		
+		/**
+		 * Kills the editor and optionally writes back the modified contents to the 
+		 * element from which it originated.
+		 *
+		 * @param save Whether or not to save the changes. If false, the changes are
+		 *             discarded.
+		 * @return true if the contents has been modified, false otherwise
+		 */
+		close: function(save, force){
+			if(this.isClosed){return false; }
+
+			if (arguments.length == 0) { save = true; }
+			this._content = this.editNode.innerHTML;
+			var changed = (this.savedContent.innerHTML != this._content);
+			
+			// line height is squashed for iframes
+			// FIXME: why was this here? if (this.iframe){ this.domNode.style.lineHeight = null; }
+
+			if(this.interval){ clearInterval(this.interval); }
+			
+			if(dojo.render.html.ie && !this.object){
+				dojo.event.browser.clean(this.editNode);
+			}
+			
+			if (this.iframe) {
+				// FIXME: should keep iframe around for later re-use
+				delete this.iframe;
+			}
+			this.domNode.innerHTML = "";
+
+			if(save){
+				// kill listeners on the saved content
+				dojo.event.browser.clean(this.savedContent);
+				if(dojo.render.html.moz){
+					var nc = document.createElement("span");
+					this.domNode.appendChild(nc);
+					nc.innerHTML = this.editNode.innerHTML;
+				}else{
+					this.domNode.innerHTML = this._content;
+				}
+			} else {
+				while (this.savedContent.hasChildNodes()) {
+					this.domNode.appendChild(this.savedContent.firstChild);
+				}
+			}
+			delete this.savedContent;
+			
+			dojo.html.removeClass(this.domNode, "RichTextEditable");
+			this.isClosed = true;
+			this.isLoaded = false;
+			// FIXME: is this always the right thing to do?
+			delete this.editNode;
+
+			return changed;
+		},
+
+		destroyRendering: function(){}, // stub!
+		
+		destroy: function (){
+			this.destroyRendering();
+			if(!this.isClosed){ this.close(false); }
+		
+			// disconnect those listeners.
+			while(this._connected.length){
+				this.disconnect(this._connected[0],
+					this._connected[1], this._connected[2]);
+			}
+		},
+
+		_connected: [],
+		connect: function (targetObj, targetFunc, thisFunc) {
+			dojo.event.connect(targetObj, targetFunc, this, thisFunc);
+			// this._connected.push([targetObj, targetFunc, thisFunc]);	
+		},
+		
+		// FIXME: below two functions do not work with the above line commented out
+		disconnect: function (targetObj, targetFunc, thisFunc) {
+			for (var i = 0; i < this._connected.length; i++) {
+				if (this._connected[0] == targetObj &&
+					this._connected[1] == targetFunc &&
+					this._connected[2] == thisFunc) {
+					dojo.event.disconnect(targetObj, targetFunc, this, thisFunc);
+					this._connected.splice(i, 1);
+					break;
+				}
+			}
+		},
+		
+		disconnectAllWithRoot: function (targetObj) {
+			for (var i = 0; i < this._connected.length; i++) {
+				if (this._connected[0] == targetObj) {
+					dojo.event.disconnect(targetObj,
+						this._connected[1], this, this._connected[2]);
+					this._connected.splice(i, 1);
+				}
+			}	
+		}
+		
+	},
+	"html",
+	function(){
+		this.contentFilters = [];
+		// this.contentFilters.push(this.defaultContentCleaner);
+		
+		this._keyHandlers = {};
+	}
+);

Propchange: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/RichText.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Rounded.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Rounded.js?rev=413300&view=auto
==============================================================================
--- tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Rounded.js (added)
+++ tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Rounded.js Sat Jun 10 06:59:28 2006
@@ -0,0 +1,713 @@
+/*
+	Copyright (c) 2004-2006, The Dojo Foundation
+	All Rights Reserved.
+
+	Licensed under the Academic Free License version 2.1 or above OR the
+	modified BSD license. For more information on Dojo licensing, see:
+
+		http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.Rounded");
+dojo.widget.tags.addParseTreeHandler("dojo:rounded");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.html.ContentPane");
+dojo.require("dojo.html");
+dojo.require("dojo.style");
+
+/*
+ *	The following script is derived (with permission) from curvyCorners,
+ *	written by Cameron Cooke (CLA on file) and was adapted to Dojo by Brian
+ *	Lucas (CLA on file)
+ */
+
+dojo.widget.Rounded = function() {
+	dojo.widget.html.ContentPane.call(this);
+}
+
+dojo.inherits(dojo.widget.Rounded, dojo.widget.html.ContentPane);
+
+dojo.lang.extend(dojo.widget.Rounded, {
+	isSafari: dojo.render.html.safari,
+	widgetType: "Rounded",
+	boxMargin: "50px", // margin outside rounded corner box
+	radius: 14, // radius of corners
+	domNode: "",
+	corners: "TR,TL,BR,BL", // corner string to render
+	antiAlias: true, // false to disable anti-aliasing
+
+	fillInTemplate: function(args, frag) {
+		dojo.widget.Rounded.superclass.fillInTemplate.call(this, args, frag);
+
+		dojo.style.insertCssFile(this.templateCssPath);
+
+		// Magic to automatically calculate the box height/width if not supplied
+		if (this.domNode.style.height<=0) {
+			var minHeight = (this.radius*1)+this.domNode.clientHeight;
+			this.domNode.style.height = minHeight+"px";
+		}
+
+		if (this.domNode.style.width<=0) {
+			var minWidth = (this.radius*1)+this.domNode.clientWidth;
+			this.domNode.style.width = minWidth+"px";
+		}
+
+		var cornersAvailable = ["TR", "TL", "BR", "BL"];
+		var cornersPassed = this.corners.split(",");
+
+		this.settings = {
+			antiAlias: this.antiAlias
+		};
+
+		var setCorner = function(currentCorner) {
+			var val = currentCorner.toLowerCase();
+			if(dojo.lang.inArray(cornersPassed, currentCorner)) {
+				this.settings[val] = { radius: this.radius, enabled: true };
+			} else {
+				this.settings[val] = { radius: 0 }
+			}
+		}
+		dojo.lang.forEach(cornersAvailable, setCorner, this);
+
+		this.domNode.style.margin = this.boxMargin;
+		this.curvyCorners(this.settings);
+		this.applyCorners();
+	},
+
+	// ------------- curvyCorners OBJECT
+
+	curvyCorners: function(settings){	
+
+		// Setup Globals
+		this.box             = this.domNode;
+		this.topContainer    = null;
+		this.bottomContainer = null;
+		this.masterCorners   = [];
+
+		// Get box formatting details
+		var boxHeight       = dojo.style.getStyle(this.box, "height");
+		if(boxHeight=="") boxHeight="0px";
+		var boxWidth        = dojo.style.getStyle(this.box, "width");
+		var borderWidth     = dojo.style.getStyle(this.box, "borderTopWidth");
+		if(borderWidth=="") borderWidth="0px";
+		//alert(borderWidth);
+
+		var borderColour    = dojo.style.getStyle(this.box, "borderTopColor");
+		// Set to true if we have a border
+		if(borderWidth>0) this.antiAlias=true;
+
+		var boxColour       = dojo.style.getStyle(this.box, "backgroundColor");
+		var backgroundImage = dojo.style.getStyle(this.box, "backgroundImage");
+		var boxPosition     = dojo.style.getStyle(this.box, "position");
+
+		// Set formatting propertes
+		this.boxHeight       = parseInt(((boxHeight != "" && boxHeight != "auto" && boxHeight.indexOf("%") == -1)? boxHeight.substring(0, boxHeight.indexOf("px")) : this.box.scrollHeight));
+		this.boxWidth        = parseInt(((boxWidth != "" && boxWidth != "auto" && boxWidth.indexOf("%") == -1)? boxWidth.substring(0, boxWidth.indexOf("px")) : this.box.scrollWidth));
+		this.borderWidth     = parseInt(((borderWidth != "" && borderWidth.indexOf("px") !== -1)? borderWidth.slice(0, borderWidth.indexOf("px")) : 0));
+
+		// DEBUG ME?
+
+		//dojo.debug(this.rgb2Hex(boxColour));
+		var test  = new dojo.graphics.color.Color(boxColour);
+		//dojo.debug(test.toHex()); 
+
+		this.boxColour       = ((boxColour != "" && boxColour != "transparent")? ((boxColour.substr(0, 3) == "rgb")? this.rgb2Hex(boxColour) : boxColour) : "#ffffff");
+		this.borderColour    = ((borderColour != "" && borderColour != "transparent" && this.borderWidth > 0)? ((borderColour.substr(0, 3) == "rgb")? this.rgb2Hex(borderColour)  : borderColour) : this.boxColour);
+		this.borderString    = this.borderWidth + "px" + " solid " + this.borderColour;
+		this.backgroundImage = ((backgroundImage != "none")? backgroundImage : "");
+
+		// Make box relative if not already absolute
+		if(boxPosition != "absolute") this.box.style.position = "relative";
+
+		//This method creates the corners and
+		//applies them to the div element.
+
+		this.applyCorners = function() {
+			// Create top and bottom containers.
+			// These will be used as a parent for the corners and bars.
+			for(var t = 0; t < 2; t++) {
+			    switch(t) {
+			        // Top
+			        case 0:
+						// Only build top bar if a top corner is to be draw
+						if(this.settings.tl.enabled || this.settings.tr.enabled ) {
+							var newMainContainer = document.createElement("DIV");
+			
+							with(newMainContainer.style){
+								width    = "100%";
+								fontSize = "1px";
+								overflow = "hidden";
+								position = "absolute";
+								//backgroundColor = "#FFFFC4";
+								paddingLeft  = this.borderWidth + "px";
+								paddingRight = this.borderWidth + "px";
+								var topMaxRadius = Math.max(this.settings.tl ? this.settings.tl.radius : 0, this.settings.tr ? this.settings.tr.radius : 0);
+								height = topMaxRadius + "px";
+								top    = 0 - topMaxRadius + "px";
+								left   = 0 - this.borderWidth + "px";
+							}
+							
+							this.topContainer = this.box.appendChild(newMainContainer);
+						}
+			            break;
+	
+			        // Bottom
+			        case 1:      
+			            // Only build bottom bar if a top corner is to be draw
+			            if(this.settings.bl.enabled || this.settings.br.enabled) {
+							var newMainContainer = document.createElement("DIV");
+							with(newMainContainer.style){
+								width    = "100%";
+								fontSize = "1px";
+								overflow = "hidden";
+								position = "absolute";
+								//backgroundColor = "#FFFFC4";
+								paddingLeft  = this.borderWidth + "px";
+								paddingRight = this.borderWidth + "px";
+								var botMaxRadius = Math.max(this.settings.bl ? this.settings.bl.radius : 0, this.settings.br ? this.settings.br.radius : 0);
+								height  = botMaxRadius + "px";
+								bottom  =  0 - botMaxRadius + "px";
+								left    =  0 - this.borderWidth + "px";
+							}
+						this.bottomContainer = this.box.appendChild(newMainContainer);
+			            }
+		            break;
+			    }
+			}
+	
+			// Turn off current borders
+			if(this.topContainer) this.box.style.borderTopWidth = "0px";
+			if(this.bottomContainer) this.box.style.borderBottomWidth = "0px";
+	
+			// Create array of available corners
+			var corners = ["tr", "tl", "br", "bl"];
+		
+			//Loop for each corner
+	
+			for(var i in corners) {
+			    // Get current corner type from array
+			    var cc = corners[i];
+
+			    // Has the user requested the currentCorner be round?
+			    if(!this.settings[cc]) {
+			        // No
+			        if(((cc == "tr" || cc == "tl") && this.topContainer != null) || ((cc == "br" || cc == "bl") && this.bottomContainer != null)) {
+						// We need to create a filler div to fill the space upto the next horzontal corner.
+						var newCorner = document.createElement("DIV");
+		
+						// Setup corners properties
+						newCorner.style.position = "relative";
+						newCorner.style.fontSize = "1px";
+						newCorner.style.overflow = "hidden";
+		
+						// Add background image?
+						if(this.backgroundImage == "") {
+							newCorner.style.backgroundColor = this.boxColour;
+						} else {
+							newCorner.style.backgroundImage = this.backgroundImage;
+						}
+
+			            switch(cc) {
+							case "tl":
+								with(newCorner.style){
+									height      = topMaxRadius - this.borderWidth + "px";
+									marginRight = this.settings.tr.radius - (this.borderWidth*2) + "px";
+									borderLeft  = this.borderString;
+									borderTop   = this.borderString;
+									left         = -this.borderWidth + "px";
+								}
+							break;
+			
+							case "tr":
+								with(newCorner.style){
+									height      = topMaxRadius - this.borderWidth + "px";
+									marginLeft  = this.settings.tl.radius - (this.borderWidth*2) + "px";
+									borderRight = this.borderString;
+									borderTop   = this.borderString;
+									backgroundPosition  = "-" + this.boxWidth + "px 0px";
+									left         = this.borderWidth + "px";
+								}
+							break;
+	
+							case "bl":
+								with(newCorner.style){
+									height       = botMaxRadius - this.borderWidth + "px";
+									marginRight  = this.settings.br.radius - (this.borderWidth*2) + "px";
+									borderLeft   = this.borderString;
+									borderBottom = this.borderString;
+									left         = -this.borderWidth + "px";
+								}
+							break;
+			
+							case "br":
+								with(newCorner.style){
+									height       = botMaxRadius - this.borderWidth + "px";
+									marginLeft   = this.settings.bl.radius - (this.borderWidth*2) + "px";
+									borderRight  = this.borderString;
+									borderBottom = this.borderString;
+									left         = this.borderWidth + "px"
+								}
+							break;
+			            }
+			        }
+			    } else {
+			        /*
+			        PERFORMANCE NOTE:
+
+			        If more than one corner is requested and a corner has been already
+			        created for the same radius then that corner will be used as a master and cloned.
+			        The pixel bars will then be repositioned to form the new corner type.
+			        All new corners start as a bottom right corner.
+			        */
+			        if(this.masterCorners[this.settings[cc].radius]) {
+			            // Create clone of the master corner
+			            var newCorner = this.masterCorners[this.settings[cc].radius].cloneNode(true);
+			        } else {
+			            // Yes, we need to create a new corner
+			            var newCorner = document.createElement("DIV");
+						with(newCorner.style){
+							height = this.settings[cc].radius + "px";
+							width  = this.settings[cc].radius + "px";
+							position = "absolute";
+							fontSize = "1px";
+							overflow = "hidden";
+						}
+						// THE FOLLOWING BLOCK OF CODE CREATES A ROUNDED CORNER
+						// ---------------------------------------------------- TOP
+			
+						// Get border radius
+						var borderRadius = parseInt(this.settings[cc].radius - this.borderWidth);
+			
+						// Cycle the x-axis
+						for(var intx = 0, j = this.settings[cc].radius; intx < j; intx++) {
+							// Calculate the value of y1 which identifies the pixels inside the border
+							if((intx +1) >= borderRadius) {
+								var y1 = -1;
+							} else {
+								var y1 = (Math.floor(Math.sqrt(Math.pow(borderRadius, 2) - Math.pow((intx+1), 2))) - 1);
+							}
+			
+							// Only calculate y2 and y3 if there is a border defined
+							if(borderRadius != j) {
+								if((intx) >= borderRadius) {
+									var y2 = -1;
+								} else {
+									var y2 = Math.ceil(Math.sqrt(Math.pow(borderRadius,2) - Math.pow(intx, 2)));
+								}
+			
+								if((intx+1) >= j) {
+									var y3 = -1;
+								} else {
+									var y3 = (Math.floor(Math.sqrt(Math.pow(j ,2) - Math.pow((intx+1), 2))) - 1);
+								}
+							}
+
+							// Calculate y4
+							if((intx) >= j) {
+								var y4 = -1;
+							} else {
+								var y4 = Math.ceil(Math.sqrt(Math.pow(j ,2) - Math.pow(intx, 2)));
+							}
+
+							// Draw bar on inside of the border with foreground colour
+							if(y1 > -1) this.drawPixel(intx, 0, this.boxColour, 100, (y1+1), newCorner, -1, this.settings[cc].radius);
+	
+							// Only draw border/foreground antialiased pixels and border if there is a border defined
+							if(borderRadius != j) {
+								// Draw aa pixels?
+								if(this.antiAlias) {
+									// Cycle the y-axis
+									for(var inty = (y1 + 1); inty < y2; inty++) {
+										// For each of the pixels that need anti aliasing between the foreground and border colour draw single pixel divs
+										if(this.backgroundImage != "") {					
+											var borderFract = (this.pixelFraction(intx, inty, borderRadius) * 100);
+					
+											if (borderFract < 30) {
+												this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, 0, this.settings[cc].radius);
+											} else {
+												this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, -1, this.settings[cc].radius);
+											}
+										} else {
+											var pixelcolour = dojo.graphics.color.blend(this.boxColour, this.borderColour, this.pixelFraction(intx, inty, borderRadius));
+											this.drawPixel(intx, inty, pixelcolour, 100, 1, newCorner, 0, this.settings[cc].radius);
+										}
+									}
+								}
+
+								// Draw bar for the border
+								if(y3 >= y2) {
+									if (y1 == -1) {
+										y1 = 0;
+									}
+									this.drawPixel(intx, y2, this.borderColour, 100, (y3 - y2 + 1), newCorner, 0, this.settings[cc].radius);
+								}	
+								// Set the colour for the outside curve
+								var outsideColour = this.borderColour;
+							} else {
+								// Set the coour for the outside curve
+								var outsideColour = this.boxColour;
+								var y3 = y1;
+							}
+			
+							// Draw aa pixels?
+							if(this.antiAlias) {		
+								// Cycle the y-axis and draw the anti aliased pixels on the
+								// outside of the curve
+								for(var inty = (y3 + 1); inty < y4; inty++) {
+									// For each of the pixels that need anti aliasing between 
+									//the foreground/border colour & background draw single pixel divs
+									this.drawPixel(intx, inty, outsideColour, (this.pixelFraction(intx, inty , j) * 100), 1, newCorner, ((this.borderWidth > 0)? 0 : -1), this.settings[cc].radius);
+								}
+							}
+			            }
+
+			            // END OF CORNER CREATION
+			            // ---------------------------------------------------- END
+
+			            // We now need to store the current corner in the masterConers array
+			            this.masterCorners[this.settings[cc].radius] = newCorner.cloneNode(true);
+			        }
+			
+					//Now we have a new corner we need to reposition all the pixels unless
+					//the current corner is the bottom right.
+			        if(cc != "br") {	
+						// Loop through all children (pixel bars)
+						for(var t = 0, k = newCorner.childNodes.length; t < k; t++) {
+							// Get current pixel bar
+							var pixelBar = newCorner.childNodes[t];
+	
+							// Get current top and left properties
+							var pixelBarTop    = parseInt(pixelBar.style.top.substring(0, pixelBar.style.top.indexOf("px")));
+							var pixelBarLeft   = parseInt(pixelBar.style.left.substring(0, pixelBar.style.left.indexOf("px")));
+							var pixelBarHeight = parseInt(pixelBar.style.height.substring(0, pixelBar.style.height.indexOf("px")));
+							
+							// Reposition pixels
+							if(cc == "tl" || cc == "bl") {
+								pixelBar.style.left = this.settings[cc].radius -pixelBarLeft -1 + "px"; // Left
+							}
+							if(cc == "tr" || cc == "tl") {
+								pixelBar.style.top =  this.settings[cc].radius -pixelBarHeight -pixelBarTop + "px"; // Top
+							}
+							var value;
+					
+							switch(cc) {
+								case "tr":
+									value = (-1 *( Math.abs((this.boxWidth - this.settings[cc].radius + this.borderWidth) + pixelBarLeft) - (Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth))));
+									pixelBar.style.backgroundPosition  = value + "px";
+									
+								break;
+				
+								case "tl":
+									value = (-1 *( Math.abs((this.settings[cc].radius -pixelBarLeft -1)  - this.borderWidth) - (Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth))));
+									pixelBar.style.backgroundPosition  = value + "px";
+
+								break;
+				
+								case "bl":
+									value = (-1 *( Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) - (Math.abs((this.boxHeight + this.settings[cc].radius + pixelBarTop) -this.borderWidth))));
+									pixelBar.style.backgroundPosition  = value + "px";
+
+								break;
+							}
+						}
+					}
+				}
+				if(newCorner) {
+					// Position the container
+					switch(cc) {
+						case "tl":
+							if(newCorner.style.position == "absolute") newCorner.style.top  = "0px";
+							if(newCorner.style.position == "absolute") newCorner.style.left = "0px";
+							if(this.topContainer) this.topContainer.appendChild(newCorner);
+						break;
+
+						case "tr":
+							if(newCorner.style.position == "absolute") newCorner.style.top  = "0px";
+							if(newCorner.style.position == "absolute") newCorner.style.right = "0px";
+							if(this.topContainer) this.topContainer.appendChild(newCorner);
+						break;
+		
+						case "bl":
+							if(newCorner.style.position == "absolute") newCorner.style.bottom  = "0px";
+							if(newCorner.style.position == "absolute") newCorner.style.left = "0px";
+							if(this.bottomContainer) this.bottomContainer.appendChild(newCorner);
+						break;
+						
+						case "br":
+							if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px";
+							if(newCorner.style.position == "absolute") newCorner.style.right = "0px";
+							if(this.bottomContainer) this.bottomContainer.appendChild(newCorner);
+						break;
+					}
+				}
+			}
+			//The last thing to do is draw the rest of the filler DIVs.
+			//We only need to create a filler DIVs when two corners have
+			//diffrent radiuses in either the top or bottom container.
+	
+			// Find out which corner has the biiger radius and get the difference amount
+			var radiusDiff = [];
+			radiusDiff["t"] = this.settings.tl.enabled && this.settings.tr.enabled ? Math.abs(this.settings.tl.radius - this.settings.tr.radius) : 0;
+			radiusDiff["b"] = this.settings.bl.enabled && this.settings.br.enabled ? Math.abs(this.settings.bl.radius - this.settings.br.radius) : 0;
+
+			for(var z in radiusDiff) {
+				if(radiusDiff[z]) {
+					// Get the type of corner that is the smaller one
+					var smallerCornerType = ((this.settings[z + "l"].radius < this.settings[z + "r"].radius)? z +"l" : z +"r");
+
+					// First we need to create a DIV for the space under the smaller corner
+					var newFiller = document.createElement("DIV");
+					with(newFiller.style) {
+						height = radiusDiff[z] + "px";
+						width  =  this.settings[smallerCornerType].radius+ "px"
+						position = "absolute";
+						fontSize = "1px";
+						overflow = "hidden";
+						backgroundColor = this.boxColour;
+					}
+
+					// Position filler
+					switch(smallerCornerType) {
+						case "tl":
+							with(newFiller.style) {
+								bottom = "0px";
+								left   = "0px";
+								borderLeft = this.borderString;
+							}
+							this.topContainer.appendChild(newFiller);
+						break;
+	
+						case "tr":
+							with(newFiller.style) {
+								bottom = "0px";
+								right  = "0px";
+								borderRight = this.borderString;
+							}
+							this.topContainer.appendChild(newFiller);
+						break;
+
+						case "bl":
+							with(newFiller.style) {
+								top    = "0px";
+								left   = "0px";
+								borderLeft = this.borderString;
+							}
+							this.bottomContainer.appendChild(newFiller);
+						break;
+
+						case "br":
+							with(newFiller.style) {
+								top    = "0px";
+								right  = "0px";
+								borderRight = this.borderString;
+							}
+							this.bottomContainer.appendChild(newFiller);
+						break;
+					}
+			    }
+
+				// Create the bar to fill the gap between each corner horizontally
+				var newFillerBar = document.createElement("DIV");
+				with(newFillerBar.style) {
+					position = "relative";
+					fontSize = "1px";
+					overflow = "hidden";
+					backgroundColor = this.boxColour;
+				}
+
+				switch(z) {
+					case "t":
+						// Top Bar
+						if(this.topContainer) {
+							with(newFillerBar.style) {
+								height      = topMaxRadius - this.borderWidth + "px";
+								marginLeft  = this.settings.tl.radius - this.borderWidth + "px";
+								marginRight = this.settings.tr.radius - this.borderWidth + "px";
+								borderTop   = this.borderString;
+							}
+						this.topContainer.appendChild(newFillerBar);
+						}
+					break;
+
+					case "b":
+						if(this.bottomContainer) {
+						// Bottom Bar
+						with(newFillerBar.style) {
+							height       = botMaxRadius - this.borderWidth + "px";
+							marginLeft   = this.settings.bl.radius - this.borderWidth + "px";
+							marginRight  = this.settings.br.radius - this.borderWidth + "px";
+							borderBottom = this.borderString;
+						}
+						this.bottomContainer.appendChild(newFillerBar);
+					}
+					break;
+				}
+			}
+		}
+
+		// This function draws the pixles
+		this.drawPixel = function(intx, inty, colour, transAmount, height, newCorner, image, cornerRadius) {
+			// Create pixel
+			var pixel = document.createElement("DIV");
+
+			
+			// Section doesn't like with (pixel.style) { DEBUG?
+			pixel.style.height   = height + "px";
+			pixel.style.width    = "1px";
+			pixel.style.position = "absolute";
+			pixel.style.fontSize = "1px";
+			pixel.style.overflow = "hidden";
+			
+			// Dont apply background image to border pixels
+			if(image == -1 && this.backgroundImage != "") {
+				pixel.style.backgroundImage = this.backgroundImage;
+				pixel.style.backgroundPosition  = "-" + (this.boxWidth - (cornerRadius - intx) + this.borderWidth) + "px -" + ((this.boxHeight + cornerRadius + inty) -this.borderWidth) + "px";
+			} else {
+				pixel.style.backgroundColor = colour;
+			}
+			
+			// Set opacity if the transparency is anything other than 100
+			if (transAmount != 100) {
+				dojo.style.setOpacity(pixel, transAmount);
+			}
+			// Set the pixels position
+			pixel.style.top = inty + "px";
+			pixel.style.left = intx + "px";
+		
+			newCorner.appendChild(pixel);
+		}
+	},
+
+	//For a pixel cut by the line determines the fraction of the pixel on the 'inside' of the
+	//line.  Returns a number between 0 and 1
+	pixelFraction: function(x, y, r) {
+		var pixelfraction = 0;
+		
+		//determine the co-ordinates of the two points on the perimeter of the pixel that the
+		//circle crosses
+		
+		var xvalues = [];
+		var yvalues = [];
+		var point = 0;
+		var whatsides = "";
+
+		// x + 0 = Left
+		var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x,2)));
+
+		if ((intersect >= y) && (intersect < (y+1))) {
+			whatsides = "Left";
+			xvalues[point] = 0;
+			yvalues[point] = intersect - y;
+			point =  point + 1;
+		}
+
+		// y + 1 = Top
+		var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y+1,2)));
+		
+		if ((intersect >= x) && (intersect < (x+1))) {
+			whatsides = whatsides + "Top";
+			xvalues[point] = intersect - x;
+			yvalues[point] = 1;
+			point = point + 1;
+		}
+		// x + 1 = Right
+		var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x+1,2)));
+
+		if ((intersect >= y) && (intersect < (y+1))) {
+			whatsides = whatsides + "Right";
+			xvalues[point] = 1;
+			yvalues[point] = intersect - y;
+			point =  point + 1;
+		}
+		// y + 0 = Bottom
+		var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y,2)));
+
+		if ((intersect >= x) && (intersect < (x+1))) {
+			whatsides = whatsides + "Bottom";
+			xvalues[point] = intersect - x;
+			yvalues[point] = 0;
+		}
+
+	    //depending on which sides of the perimeter of the pixel the circle crosses calculate the
+	    //fraction of the pixel inside the circle
+
+		switch (whatsides) {
+			case "LeftRight":
+				pixelfraction = Math.min(yvalues[0],yvalues[1]) + ((Math.max(yvalues[0],yvalues[1]) - Math.min(yvalues[0],yvalues[1]))/2);
+			break;
+			
+			case "TopRight":
+				pixelfraction = 1-(((1-xvalues[0])*(1-yvalues[1]))/2);
+			break;
+			
+			case "TopBottom":
+				pixelfraction = Math.min(xvalues[0],xvalues[1]) + ((Math.max(xvalues[0],xvalues[1]) - Math.min(xvalues[0],xvalues[1]))/2);
+			break;
+			
+			case "LeftBottom":
+				pixelfraction = (yvalues[0]*xvalues[1])/2;
+			break;
+			
+			default:
+				pixelfraction = 1;
+	    }
+	    return pixelfraction;
+	},
+
+	// This function converts CSS rgb(x, x, x) to hexadecimal
+	rgb2Hex: function (rgbColour) {
+		try{	
+			// Get array of RGB values
+			var rgbArray = this.rgb2Array(rgbColour);
+			
+			// Get RGB values
+			var red   = parseInt(rgbArray[0]);
+			var green = parseInt(rgbArray[1]);
+			var blue  = parseInt(rgbArray[2]);
+			
+			// Build hex colour code
+			var hexColour = "#" + this.intToHex(red) + this.intToHex(green) + this.intToHex(blue);
+		}
+		catch(e){ alert("There was an error converting the RGB value to Hexadecimal in function rgb2Hex");
+		}
+		return hexColour;
+	},
+
+	//Converts a number to hexadecimal format
+
+	intToHex: function (strNum) {
+		var base = strNum / 16;
+		var rem = strNum % 16;
+		var base = base - (rem / 16);
+		var baseS = this.makeHex(base);
+		var remS = this.makeHex(rem);
+		return baseS + '' + remS;
+	},
+	//gets the hex bits of a number
+
+	makeHex: function(x) {
+		if((x >= 0) && (x <= 9)) {
+			return x;
+		} else {
+			switch(x) {
+				case 10: return "A";
+				case 11: return "B";
+				case 12: return "C";
+				case 13: return "D";
+				case 14: return "E";
+				case 15: return "F";
+			}
+		}
+	},
+
+	// Returns an array of rbg values
+	rgb2Array: function(rgbColour) {
+		// Remove rgb()
+		var rgbValues = rgbColour.substring(4, rgbColour.indexOf(")"));
+	
+		// Split RGB into array
+		var rgbArray = rgbValues.split(", ");
+		return rgbArray;
+	}
+}); // end function

Propchange: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Rounded.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Select.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Select.js?rev=413300&view=auto
==============================================================================
--- tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Select.js (added)
+++ tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Select.js Sat Jun 10 06:59:28 2006
@@ -0,0 +1,76 @@
+/*
+	Copyright (c) 2004-2006, The Dojo Foundation
+	All Rights Reserved.
+
+	Licensed under the Academic Free License version 2.1 or above OR the
+	modified BSD license. For more information on Dojo licensing, see:
+
+		http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.Select");
+dojo.provide("dojo.widget.html.Select");
+
+dojo.require("dojo.widget.html.ComboBox");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.html.stabile");
+
+/*
+ * The Select widget is an enhanced version of HTML's <select> tag.
+ *
+ * Similar features:
+ *   - There is a drop down list of possible values.
+ *   - You can only enter a value from the drop down list.  (You can't enter an arbitrary value.)
+ *   - The value submitted with the form is the hidden value (ex: CA),
+       not the displayed value a.k.a. label (ex: California)
+ *
+ * Enhancements over plain HTML version:
+ *   - If you type in some text then it will filter down the list of possible values in the drop down list.
+ *   - List can be specified either as a static list or via a javascript function (that can get the list from a server)
+ */
+
+dojo.widget.defineWidget(
+	"dojo.widget.html.Select",
+	dojo.widget.html.ComboBox,
+	{
+		widgetType: "Select",
+		forceValidOption: true,
+
+		setValue: function(value) {
+			this.comboBoxValue.value = value;
+			dojo.widget.html.stabile.setState(this.widgetId, this.getState(), true);
+		},
+
+		setLabel: function(value){
+			// FIXME, not sure what to do here!
+			this.comboBoxSelectionValue.value = value;
+			if (this.textInputNode.value != value) { // prevent mucking up of selection
+				this.textInputNode.value = value;
+			}
+		},	  
+
+		getLabel: function(){
+			return this.comboBoxSelectionValue.value;
+		},
+
+		getState: function() {
+			return {
+				value: this.getValue(),
+				label: this.getLabel()
+			};
+		},
+
+		onKeyUp: function(evt){
+			this.setLabel(this.textInputNode.value);
+		},
+
+		setState: function(state) {
+			this.setValue(state.value);
+			this.setLabel(state.label);
+		},
+
+		setAllValues: function(value1, value2){
+			this.setValue(value2);
+			this.setLabel(value1);
+		}
+	});

Added: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Show.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Show.js?rev=413300&view=auto
==============================================================================
--- tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Show.js (added)
+++ tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Show.js Sat Jun 10 06:59:28 2006
@@ -0,0 +1,45 @@
+/*
+	Copyright (c) 2004-2006, The Dojo Foundation
+	All Rights Reserved.
+
+	Licensed under the Academic Free License version 2.1 or above OR the
+	modified BSD license. For more information on Dojo licensing, see:
+
+		http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.Show");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.lang.common");
+
+dojo.widget.Show = function(){}
+dojo.lang.extend(dojo.widget.Show, {
+	isContainer: true,
+	_slide: -1,
+	_slides: [],
+	gotoSlide: function(/*int*/ slide){
+		this._slide = slide;
+		// summary: Placeholder
+	},
+	nextSlide: function(/*Event?*/ event){
+		if(!this._slides[this._slide].nextAction(event)){
+			if((this._slide + 1) != this._slides.length){
+				this.gotoSlide(this._slide + 1);
+				return true; // boolean
+			}
+			return false; // boolean
+		}
+	},
+	previousSlide: function(/*Event?*/ event){
+		if(!this._slides[this._slide].previousAction(event)){
+			if((this._slide - 1) != -1){
+				this.gotoSlide(this._slide - 1);
+				return true; // boolean
+			}
+			return false; // boolean
+		}
+	}
+});
+
+dojo.requireAfterIf("html", "dojo.widget.html.Show");
\ No newline at end of file

Propchange: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/Show.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/ShowSlide.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/ShowSlide.js?rev=413300&view=auto
==============================================================================
--- tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/ShowSlide.js (added)
+++ tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/ShowSlide.js Sat Jun 10 06:59:28 2006
@@ -0,0 +1,43 @@
+/*
+	Copyright (c) 2004-2006, The Dojo Foundation
+	All Rights Reserved.
+
+	Licensed under the Academic Free License version 2.1 or above OR the
+	modified BSD license. For more information on Dojo licensing, see:
+
+		http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.ShowSlide");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.lang.common");
+
+dojo.widget.ShowSlide = function(){
+}
+dojo.lang.extend(dojo.widget.ShowSlide, {
+	title: "",
+	_action: -1,
+	isContainer: true,
+	_components: {},
+	_actions: [],
+	gotoAction: function(/*int*/ action){
+		this._action = action;
+	},
+	nextAction: function(/*Event?*/ event){
+		if((this._action + 1) != this._actions.length){
+			++this._action;
+			return true; // boolean
+		}
+		return false; // boolean
+	},
+	previousAction: function(/*Event?*/ event){
+		if((this._action - 1) != -1){
+			--this._action;
+			return true; // boolean
+		}
+		return false; // boolean
+	}
+});
+
+dojo.requireAfterIf("html", "dojo.widget.html.ShowSlide");
\ No newline at end of file

Propchange: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/ShowSlide.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/SimpleDropdownButtons.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/SimpleDropdownButtons.js?rev=413300&view=auto
==============================================================================
--- tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/SimpleDropdownButtons.js (added)
+++ tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/SimpleDropdownButtons.js Sat Jun 10 06:59:28 2006
@@ -0,0 +1,158 @@
+/*
+	Copyright (c) 2004-2006, The Dojo Foundation
+	All Rights Reserved.
+
+	Licensed under the Academic Free License version 2.1 or above OR the
+	modified BSD license. For more information on Dojo licensing, see:
+
+		http://dojotoolkit.org/community/licensing.shtml
+*/
+
+/* TODO:
+ * - make the dropdowns "smart" so they can't get cutoff on bottom of page, sides of page, etc.
+ * - unify menus with the MenuItem and Menu classes so we can add stuff to all menus at once
+ * - allow buttons to be enabled/disabled at runtime
+ *     - this probably means creating all menus upfront and then triggering a disable action
+ *       for disabled buttons in the constructor loop. we'll need a disable and enable action anyway
+ * - should each button with menu be a widget object of it's own?
+ */
+dojo.provide("dojo.widget.SimpleDropdownButtons");
+dojo.provide("dojo.widget.HtmlSimpleDropdownButtons");
+
+dojo.deprecated("dojo.widget.SimpleDropdownButtons",  "use dojo.widget.DropDownButton", "0.4");
+
+dojo.require("dojo.event.*");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.uri.Uri");
+dojo.require("dojo.dom");
+dojo.require("dojo.style");
+dojo.require("dojo.html");
+
+dojo.widget.tags.addParseTreeHandler("dojo:simpledropdownbuttons");
+
+dojo.widget.HtmlSimpleDropdownButtons = function() {
+	dojo.widget.HtmlWidget.call(this);
+
+	this.widgetType = "SimpleDropdownButtons";
+	this.templateCssPath = dojo.uri.dojoUri("src/widget/templates/HtmlSimpleDropdownButtons.css");
+
+	this.menuTriggerClass = "dojoSimpleDropdownButtons";
+	this.menuClass = "dojoSimpleDropdownButtonsMenu";
+
+	// overwrite buildRendering so we don't clobber our list
+	this.buildRendering = function(args, frag) {
+		if(this.templateCssPath) {
+			dojo.style.insertCssFile(this.templateCssPath, null, true);
+		}
+		this.domNode = frag["dojo:"+this.widgetType.toLowerCase()]["nodeRef"];
+
+		var menu = this.domNode;
+		if( !dojo.html.hasClass(menu, this.menuTriggerClass) ) {
+			dojo.html.addClass(menu, this.menuTriggerClass);
+		}
+		var li = dojo.dom.getFirstChildElement(menu);
+		var menuIDs = [];
+		var arrowIDs = [];
+
+		while(li) {
+			if(li.getElementsByTagName("ul").length > 0) {
+				var a = dojo.dom.getFirstChildElement(li);
+				var arrow = document.createElement("a");
+				arrow.href = "javascript:;";
+				arrow.innerHTML = "&nbsp;";
+				dojo.html.setClass(arrow, "downArrow");
+				if(!arrow.id) {
+					arrow.id = dojo.dom.getUniqueId();
+				}
+				arrowIDs.push(arrow.id);
+				var submenu = dojo.dom.getNextSiblingElement(a);
+				if(!submenu.id) {
+					submenu.id = dojo.dom.getUniqueId();
+				}
+				menuIDs.push(submenu.id);
+
+				if( dojo.html.hasClass(a, "disabled") ) {
+					dojo.html.addClass(arrow, "disabled");
+					dojo.html.disableSelection(li);
+					arrow.onfocus = function(){ this.blur(); }
+				} else {
+					dojo.html.addClass(submenu, this.menuClass);
+					document.body.appendChild(submenu);
+					dojo.event.connect(arrow, "onmousedown", (function() {
+						var ar = arrow;
+						return function(e) {
+							dojo.html.addClass(ar, "pressed");
+						}
+					})());
+					dojo.event.connect(arrow, "onclick", (function() {
+						var aa = a;
+						var ar = arrow;
+						var sm = submenu;
+						var setWidth = false;
+
+						return function(e) {
+							hideAll(sm, ar);
+							sm.style.left = (dojo.html.getScrollLeft()
+								+ e.clientX - e.layerX + aa.offsetLeft) + "px";
+							sm.style.top = (dojo.html.getScrollTop() + e.clientY
+								- e.layerY + aa.offsetTop + aa.offsetHeight) + "px";
+							sm.style.display = sm.style.display == "block" ? "none" : "block";
+							if(sm.style.display == "none") {
+								dojo.html.removeClass(ar, "pressed");
+								e.target.blur()
+							}
+							if(!setWidth && sm.style.display == "block"
+								&& sm.offsetWidth < aa.offsetWidth + ar.offsetWidth) {
+								sm.style.width = aa.offsetWidth + ar.offsetWidth + "px";
+								setWidth = true;
+							}
+							e.preventDefault();
+						}
+					})());
+				}
+
+				dojo.event.connect(a, "onclick", function(e) {
+					if(e && e.target && e.target.blur) {
+						e.target.blur();
+					}
+				});
+
+				if(a.nextSibling) {
+					li.insertBefore(arrow, a.nextSibling);
+				} else {
+					li.appendChild(arrow);
+				}
+
+			}
+			li = dojo.dom.getNextSiblingElement(li);
+		}
+
+		function hideAll(excludeMenu, excludeArrow) {
+			// hide menus
+			for(var i = 0; i < menuIDs.length; i++) {
+				var m = document.getElementById(menuIDs[i]);
+				if(!excludeMenu || m != excludeMenu) {
+					document.getElementById(menuIDs[i]).style.display = "none";
+				}
+			}
+			// restore arrows to non-pressed state
+			for(var i = 0; i < arrowIDs.length; i++) {
+				var m = document.getElementById(arrowIDs[i]);
+				if(!excludeArrow || m != excludeArrow) {
+					dojo.html.removeClass(m, "pressed");
+				}
+			}
+		}
+
+		dojo.event.connect(document.documentElement, "onmousedown", function(e) {
+			if( dojo.html.hasClass(e.target, "downArrow") ) { return };
+			for(var i = 0; i < menuIDs.length; i++) {
+				if( dojo.dom.isDescendantOf(e.target, document.getElementById(menuIDs[i])) ) {
+					return;
+				}
+			}
+			hideAll();
+		});
+	}
+}
+dojo.inherits(dojo.widget.HtmlSimpleDropdownButtons, dojo.widget.HtmlWidget);

Propchange: tapestry/tapestry4/trunk/framework/src/js/dojo/src/widget/SimpleDropdownButtons.js
------------------------------------------------------------------------------
    svn:eol-style = native