      1 /*
      2  *  Copyright (C) 2008-2009 WaveMaker Software, Inc.
      3  *
      4  *  This file is part of the WaveMaker Client Runtime.
      5  *
      6  *  Licensed under the Apache License, Version 2.0 (the "License");
      7  *  you may not use this file except in compliance with the License.
      8  *  You may obtain a copy of the License at
      9  *
     10  *      http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  *  Unless required by applicable law or agreed to in writing, software
     13  *  distributed under the License is distributed on an "AS IS" BASIS,
     14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  *  See the License for the specific language governing permissions and
     16  *  limitations under the License.
     17  */
     18 dojo.provide("wm.base.Control");
     19
     20 wm.splitUnits = function(inUnitValue) {
     21 	var m = (inUnitValue || "").match(wm.splitUnits.Rx);
     22 	return { value: Number(m[1]) || 0, units: m[2] || "px" };
     23 }
     24 wm.splitUnits.Rx = /(\d*)(.*)/;
     25
     26 /**
     27 	Manages geometry for a rectangle, including margins, borders, and padding and frame-of-reference calculations.
     28 	@class
     29 	@name wm.Bounds
     30 */
     31 dojo.declare("wm.Bounds", null, {
     32 	/** @lends wm.Bounds.prototype */
     33 	padding: "",
     34 	border: "",
     35 	margin: "",
     36 	constructor: function() {
     37 		this.bounds = {l:0, t:0, w:96, h:64};
     38 		this.borderExtents = {l:0, t:0, r:0, b: 0};
     39 		this.paddingExtents = {l:0, t:0, r:0, b: 0};
     40 		this.marginExtents = {l:0, t:0, r:0, b: 0, w: 0, h:0};
     41 		this.padBorderMargin = {};
     42 		this.calcPadBorderMargin();
     43 	},
     44 	getBounds: function() {
     45 		return this.bounds;
     46 	},
     47 	/**
     48 		Set the outermost area of this box, including margin, border, and padding.
     49 		l, t describe the position of the outer most corner of this box.
     50 		w, h describe the size of the box, including margin, border, and padding.
     51 		@param {Object} inBox {l: Number, t: Number, w: Number, h: Number }
     52 	*/
     53 	setBounds: function(inL, inT, inW, inH) {
     54 		if (arguments.length == 1) {
     55 			return this.setBounds(inL.l, inL.t, inL.w, inL.h)
     56 		}
     57 		var b = this.bounds;
     58 		if (!isNaN(inL) && b.l != inL) {
     59 			b.l = inL;
     60 		}
     61 		if (!isNaN(inT) && b.t != inT) {
     62 			b.t = inT;
     63 		}
     64 		if (inW >= 0 && b.w != inW) {
     65 			b.w = inW;
     66 			this._boundsDirty = true;
     67 		}
     68 		if (inH >= 0 && b.h != inH) {
     69 			b.h = inH;
     70 			this._boundsDirty = true;
     71 		}
     72 		b.r = b.l + b.w;
     73 		b.b = b.t + b.h;
     74 		return b;
     75 	},
     76 	setContentBounds: function(inBox) {
     77 		var b= {};
     78 		var sm = this.getScrollMargins();
     79 		if ("w" in inBox) {
     80 			b.w = inBox.w + this.padBorderMargin.w + sm.w;
     81 		}
     82 		if ("h" in inBox) {
     83 			b.h = inBox.h + this.padBorderMargin.h + sm.h;
     84 		}
     85 		return this.setBounds(b);
     86 	},
     87 	_parseExtents: function(inExtents) {
     88 		var r = {};
     89 		if (typeof inExtents == "number")
     90 			r = { l: inExtents, t: inExtents, r: inExtents, b: inExtents };
     91 		else {
     92 			var ex = inExtents.split(",");
     93 			var l = ex.length;
     94 			r.t = parseFloat(ex[0]) || 0;
     95 			r.r = l < 2 ? r.t : parseFloat(ex[1]) || 0;
     96 			r.b = l < 3 ? r.t : parseFloat(ex[2]) || 0;
     97 			r.l = l < 4 ? r.r : parseFloat(ex[3]) || 0;
     98 		}
     99 		return r;
    100 	},
    101 	/**
    102 		Set padding extents in pixels.
    103 		@param {String||Number} inPadding "t, <r, b, l>" || Number
    104 	*/
    105 	setPadding: function(inPadding) {
    106 		this.padding = String(inPadding);
    107 		this.paddingExtents = this._parseExtents(this.padding);
    108 		this.padBorderMarginChanged();
    109 	},
    110 	/**
    111 		Set border extents in pixels.
    112 		@param {String||Number} inBorder "t, <r, b, l>" || Number
    113 	*/
    114 	setBorder: function(inBorder) {
    115 		this.border = String(inBorder);
    116 		this.borderExtents = this._parseExtents(this.border);
    117 		this.padBorderMarginChanged();
    118 	},
    119 	/**
    120 		Set margin extents in pixels.
    121 		@param {String||Number} inMargin "t, <r, b, l>" || Number
    122 	*/
    123 	setMargin: function(inMargin) {
    124 		this.margin = String(inMargin);
    125 		var me = this.marginExtents = this._parseExtents(inMargin);
    126 		me.h = me.t + me.b;
    127 		me.w = me.l + me.r;
    128 		this.padBorderMarginChanged();
    129 	},
    130 	/**
    131 		Update metrics when padBorderMargin has changed.
    132 		@protected
    133 	*/
    134 	padBorderMarginChanged: function() {
    135 		this.calcPadBorderMargin();
    136 	},
    137 	/**
    138 		Accumulate padBorderMargin extents.
    139 		@private
    140 	*/
    141 	_edges: {l:1, t:1, r:1, b:1},
    142 	calcPadBorderMargin: function() {
    143 		var pbm = this.padBorderMargin;
    144 		for(var e in this._edges)
    145 			pbm[e] = this.borderExtents[e] + this.paddingExtents[e] + this.marginExtents[e];
    146 		pbm.w = pbm.l + pbm.r;
    147 		pbm.h = pbm.t + pbm.b;
    148 	},
    149 	getScrollMargins: function() {
    150 		return {w:0, h:0};
    151 	},
    152 	/**
    153 		Get an object describing the content-box area.
    154 		l, t describe the position of the origin for objects in this frame.
    155 		w, h describe the size of the content area of the box (inside margin, border, padding, and scrollbars).
    156 		@return {Object} {l: Number, t: Number, w: Number, h: Number}
    157 	*/
    158 	getContentBounds: function() {
    159 		var sm = this.getScrollMargins();
    160 		var b = {
    161 			l: this.paddingExtents.l,
    162 			t: this.paddingExtents.t,
    163 			w: this.bounds.w - this.padBorderMargin.w - sm.w,
    164 			h: this.bounds.h - this.padBorderMargin.h - sm.h
    165 		};
    166 		b.r = b.l + b.w;
    167 		b.b = b.t + b.h;
    168 		return b;
    169 	},
    170 	getStyleBounds: function() {
    171 		var pbm = (this.dom.node.tagName.toLowerCase() == "button") ? this.marginExtents : this.padBorderMargin;
    172 		var b = {
    173 			l: this.bounds.l,
    174 			t: this.bounds.t,
    175 			w: this.bounds.w - pbm.w,
    176 			h: this.bounds.h - pbm.h
    177 		};
    178 		b.r = b.l + b.w;
    179 		b.b = b.t + b.h;
    180 		return b;
    181 	},
    182 	cloneBounds: function() {
    183 		with (this.bounds) {
    184 			return {l:l, t:t, w:w, h:h, r:r, b:b};
    185 		}
    186 	}
    187 });
    188
    189 dojo.declare("wm.DomNode", null, {
    190 	constructor: function(inNode) {
    191 		this.node = inNode || document.createElement('div');
    192 	},
    193 	append: function(inDomNode) {
    194 		this.node.appendChild(inDomNode.node);
    195 	},
    196 	remove: function(inDomNode) {
    197 		this.node.removeChild(inDomNode.node);
    198 	},
    199 	getWidth: function() {
    200 		return this.node.offsetWidth;
    201 	},
    202 	getHeight: function() {
    203 		return this.node.offsetHeight;
    204 	},
    205 	setBox: function(inBox, inSingleLine) {
    206 		var s = this.node.style;
    207 		var bl = inBox.l + "px";
    208 		if (!isNaN(inBox.l) && s.left != bl) {
    209 			s.left = bl;
    210 		}
    211 		var bt = inBox.t + "px";
    212 		if (!isNaN(inBox.t) && s.top != bt) {
    213 			s.top = bt;
    214 		}
    215 		var bw = inBox.w + "px";
    216 		if (inBox.w >=0 && s.width != bw) {
    217 			s.width = bw;
    218 		}
    219 		var bh = inBox.h + "px";
    220 		if (inBox.h >= 0) {
    221 			//if (s.height != bh)
    222 				s.height = bh;
    223 			s.lineHeight = inSingleLine ? bh : "normal";
    224 		} else if (!inSingleLine) {
    225 			//s.lineHeight = "normal";
    226 		}
    227 	},
    228 	setCssText: function(inText) {
    229 		this.node.style.cssText += ";" + inText;
    230 	},
    231 	addCssText: function(inText) {
    232 		this.node.style.cssText += inText;
    233 	}
    234 });
    235
    236 wm.aligns = [
    237 	"topLeft", "center", "bottomRight", "justified"
    238 ];
    239
    240 /**
    241 	Base class for all <i>visual</i> components.
    242 	@name wm.Control
    243 	@class
    244 	@extends wm.Component
    245 */
    246 wm.define("wm.Control", [wm.Component, wm.Bounds], {
    247 	/** @lends wm.Control.prototype */
    248 	published: {
    249 		autoScroll: {ignore: 1},
    250 		bounds: {ignore: 1},
    251 		border: {group: "style"},
    252 		borderColor: {group: "style"},
    253 		//backgroundColor: {group: "style"},
    254 		backgroundColor: {ignore: 1},
    255 		margin: {group: "style"},
    256 		padding: {group: "style"},
    257 		scrollX: {group: "style"},
    258 		scrollY: {group: "style"},
    259 		left: {writeonly: 1, ignore: 1},
    260 		top: {writeonly: 1, ignore: 1}
    261 	},
    262 	backgroundColor: "",
    263 	//border: 1,
    264 	borderColor: "#F0F0F0",
    265 	binding: '(data binding)',
    266 	classNames: '',
    267 	id: '',
    268 	autoSize: false,
    269 	/*
    270 	flex: '',
    271 	left: '',
    272 	top: '',
    273 	*/
    274 	/**
    275 		Display width specified as a string with units.<br>
    276 		<br>
    277 		Supports CSS units and <i>flex</i> units.<br>
    278 		@example
    279 this.button.setValue("width", "96px");
    280 this.text.setValue("width", "4em");
    281 this.box.setValue("width", "1flex");
    282 		@type String
    283 	*/
    284 	width: '',
    285 	/**
    286 		Display height specified as a string with units.<br>
    287 		<br>
    288 		Supports CSS units and <i>flex</i> units.<br>
    289 		@example
    290 this.button.setValue("height", "96px");
    291 this.text.setValue("height", "4em");
    292 this.box.setValue("height", "1flex");
    293 		@type String
    294 	*/
    295 	height: '',
    296 	left: 0,
    297 	top: 0,
    298 	group: '',
    299 	styles: '',
    300 	state : null,
    301 	/**
    302 		Showing state.<br>
    303 		<br>
    304 		Whether the widget if shown on the display.<br>
    305 		@see <a href="#hide">hide</a>, <a href="#show">show</a>.
    306 		@example
    307 this.button.setValue("showing", false);
    308 this.panel.show();
    309 this.label.hide();
    310 		@type Boolean
    311 	*/
    312 	showing: true,
    313 	/**
    314 		Disabled state.<br>
    315 		<br>
    316 		Some widgets change behavior or display based on the disabled state.<br>
    317 		@see <a href="#disable">disable</a>, <a href="#enable">enable</a>.
    318 		@example
    319 this.button.setValue("disabled", true);
    320 this.panel.disable();
    321 this.label.enable();
    322 		@type Boolean
    323 	*/
    324 	disabled: false,
    325 	container: false,
    326 	_classes: null,
    327 	scrollX: false,
    328 	scrollY: false,
    329 	//===========================================================================
    330 	// Construction
    331 	//===========================================================================
    332 	constructor: function() {
    333 		this.widgets = {};
    334 		this._classes = dojo.mixin({}, this._classes);
    335 	},
    336 	postscript: function(inProps) {
    337 		this.inherited(arguments);
    338 	},
    339 	create: function() {
    340 		this._cupdating = true;
    341 		this.inherited(arguments);
    342 	},
    343 	build: function() {
    344 		this.domNode = dojo.byId(this.domNode||/*this.id||*/undefined);
    345 		if (!this.domNode)
    346 			this.domNode = document.createElement('div');
    347 	},
    348 	init: function() {
    349 		this.dom = new wm.DomNode(this.domNode);
    350 		this.inherited(arguments);
    351 		this.bc();
    352 		//
    353 		this.domNode.style.position = "absolute";
    354 		this.setBorder(this.border);
    355 		this.setMargin(this.margin);
    356 		this.setPadding(this.padding);
    357 		//
    358 //		if (this.domNode) {
    359 			this.setParent(this.parent);
    360 			this.setDomNode(this.domNode);
    361 			//if (this.autoSize)
    362 			//	this.size = "";
    363 			//this.setFlex(this.flex)
    364 			// BC
    365 			this.doSetSizeBc();
    366 			//this._flexChanged();
    367 			// FIXME: need a generalized way of processing properties at creation time
    368 			// setShowing will nop unless this.showing would be changed
    369 			if (!this.showing) {
    370 				this.showing = true;
    371 				this.setShowing(false);
    372 			}
    373 			this.setDisabled(this.disabled);
    374 //		}
    375 		if (this.styles) {
    376 			this.set_styles(this.styles);
    377 			this.styles = "";
    378 		}
    379 	},
    380 	bc: function() {
    381 		// do BC fixups
    382 		/*if (this.layoutFlex) {
    383 			this.fluidSize = this.layoutFlex;
    384 		}*/
    385 		delete this.layoutFlex;
    386 		delete this.fluidSize;
    387 	},
    388 	postInit: function() {
    389 		this._cupdating = false;
    390 		this.inherited(arguments);
    391 		if (!this.$.binding)
    392 			new wm.Binding({name: "binding", owner: this});
    393 	},
    394 	destroy: function() {
    395 		var wids = [];
    396 		for (var n in this.widgets)
    397 			wids.push(this.widgets[n]);
    398 		for(var i=0, w; (w=wids[i]); i++)
    399 			w.destroy();
    400 		this.parentNode = null
    401 		this.setParent(null);
    402 		dojo._destroyElement(this.domNode);
    403 		this.inherited(arguments);
    404 	},
    405 	loaded: function() {
    406 		this.inherited(arguments);
    407 		this.initUserClasses();
    408 	},
    409 	setDomNode: function(inDomNode) {
    410 		var n = this.domNode = inDomNode;
    411 		if (dojo.isIE) {
    412 			// forcing a size on the node now seems to help IE
    413 			// honor auto sizing later
    414 			n.style.width = "0px";
    415 		}
    416 		// id
    417 		this.updateId();
    418 		// classes
    419 		dojo.addClass(n, this.classNames + (this.owner ? ' ' + this.owner.declaredClass + '-' + this.name : ''));
    420 		this.initUserClasses();
    421 		this.updateBounds();
    422 	},
    423 	//===========================================================================
    424 	// Name & Id
    425 	//===========================================================================
    426 	updateId: function() {
    427 		this.inherited(arguments);
    428 		if (this.domNode) {
    429 			var rid = this.getRuntimeId();
    430 			this.domNode.rid = rid;
    431 			this.domNode.id = rid.replace(/\./g, "_");
    432 		}
    433 	},
    434 	//===========================================================================
    435 	// Ownership
    436 	//===========================================================================
    437 	getUniqueName: function(inName) {
    438 		return wm.findUniqueName(inName, [this, this.components, this.widgets]);
    439 	},
    440 	//===========================================================================
    441 	// Parentage
    442 	//===========================================================================
    443 	setName: function(inName) {
    444 		if (!inName)
    445 			return;
    446 		if (this.parent)
    447 			this.parent.removeWidget(this);
    448 		this.addRemoveDefaultCssClass(false);
    449 		this.inherited(arguments);
    450 		if (this.parent)
    451 			this.parent.addWidget(this);
    452 		this.addRemoveDefaultCssClass(true);
    453 	},
    454 	addWidget: function(inWidget){
    455 		this.widgets[inWidget.name] = inWidget;
    456 		var p = this.containerNode || this.domNode;
    457 		if (inWidget.domNode.parentNode != p)
    458 			p.appendChild(inWidget.domNode);
    459 	},
    460 	removeWidget: function(inWidget){
    461 		delete this.widgets[inWidget.name];
    462 	},
    463 	adjustChildProps: function(inCtor, inProps) {
    464 		if (inCtor.prototype instanceof wm.Widget)
    465 			dojo.mixin(inProps, {owner: this.owner, parent: this});
    466 		else
    467 			this.inherited(arguments);
    468 	},
    469 	//=======================================================
    470 	// Properties
    471 	//=======================================================
    472 	listProperties: function() {
    473 		var p = this.inherited(arguments);
    474 		p.autoSize.ignore = (!this.isSizeable() && !this.autoSize) || (this.schema.autoSize && this.schema.autoSize.ignore);
    475 		//p.width.ignore = p.width.writeonly = !this.isSizeable() || !this.canSetWidth();
    476 		//p.height.ignore = p.height.writeonly = !this.isSizeable() || !this.canSetHeight();
    477 		p.width.ignore = p.width.writeonly = !this.isSizeable() || !this.canSetWidth();
    478 		p.height.ignore = p.height.writeonly = !this.isSizeable() || !this.canSetHeight();
    479 		// _classes as array for bc; now an object that supports storing sets of classes
    480 		p._classes.writeonly = (dojo.isArray(this._classes) && this._classes.length) || !wm.isEmpty(this._classes);
    481 		return p;
    482 	},
    483 	//===========================================================================
    484 	// Custom Node Events / Node Bounds
    485 	//===========================================================================
    486 	/*
    487 	nodeAutoSize: function(inAtWidth, inAtHeight) {
    488 		if (this.autoSize) {
    489 			var s = this.domNode.style;
    490 			if (dojo.isFF && dojo.isFF < 3)
    491 				s.overflow = "visible";
    492 			if (!inAtWidth)
    493 				s.width = "";
    494 			if (!inAtHeight)
    495 				s.height = "";
    496 			this.doAutoSize(inAtWidth, inAtHeight);
    497 			if (dojo.isFF && dojo.isFF < 3)
    498 				s.overflow = "";
    499 		}
    500 	},
    501 	*/
    502 	doAutoSize: function(inAtWidth, inAtHeight) {
    503 		this.sizeFromNode();
    504 	},
    505 	//===========================================================================
    506 	// Bounds
    507 	//===========================================================================
    508 	// BC -->
    509 	doSetSizeBc: function() {
    510 		/*if (!this.width) {
    511 			this.setSizeProp("width", "100%");
    512 		}
    513 		if (!this.height) {
    514 			this.setSizeProp("height", "100%");
    515 		}*/
    516 		if (this.sizeUnits == "flex") {
    517 			this.setFlex(this.size);
    518 		} else if (this.sizeUnits) {
    519 			var b = this.getParentBox(), p = {v: "height", h: "width"}[b];
    520 			this.setSizeProp(p, this.size + this.sizeUnits);
    521 		} else if (this.flex) {
    522 			this.setFlex(this.flex);
    523 		}
    524 	},
    525 	setFlex: function(inFlex) {
    526 		var box = this.getParentBox();
    527 		if (box) {
    528 			var ex = {h: "width", v: "height"}[box];
    529 			this.setSizeProp(ex, inFlex*100 + "%");
    530 			this._boundsDirty = true;
    531 		} else {
    532 			this.setSizeProp("width", inFlex*100 + "%");
    533 			this.setSizeProp("height", inFlex*100 + "%");
    534 		}
    535 	},
    536 	isFlex: function() {
    537 		var box = this.getParentBox();
    538 		if (!box)
    539 			return false;
    540 		var ex = {h: "width", v: "height"}[box];
    541 		return (this[ex].indexOf("flex")>=0);
    542 	},
    543 	// <-- BC
    544 	getScrollMargins: function() {
    545 		return {w: this.scrollY ? 17 : 0, h: this.scrollX ? 17 : 0};
    546 	},
    547 	padBorderMarginChanged: function() {
    548 		this.inherited(arguments);
    549 		if (!this._cupdating) {
    550 			this.parent ? this.parent.reflow() : this.render(), wm.fire(this, "flow");
    551 		}
    552 	},
    553 	/**
    554 		Update width and height properties after bounds change.
    555 	*/
    556 	boundsResized: function() {
    557 		var box = dojo.marginBox(this.dom.node);
    558 		if (this.bounds.w != box.w) {
    559 			this.width = this.bounds.w + "px";
    560 		}
    561 		if (this.bounds.h != box.h) {
    562 			this.height = this.bounds.h + "px";
    563 		}
    564 		this.updateBounds();
    565 	},
    566 	/**
    567 		Update bounds and flex properties based on width/height properties
    568 	*/
    569 	updateBounds: function() {
    570 		//this.domNode.flex = 0;
    571 		//this.fluidSize = 0;
    572 		this._percEx = {w:0, h: 0};
    573 		//
    574 		//var pd = this.getParentBox();
    575 		//
    576 		var su = wm.splitUnits(this.width);
    577 		var w = su.value;
    578 		switch (su.units) {
    579 			// FIXME: 'flex' and 'em' are deprecated, probably this should be in BC block
    580 			case "flex":
    581 				w *= 100;
    582 				this._percEx.w = w;
    583 				this.width = w + "%";
    584 				w = NaN;
    585 				break;
    586 			case "em":
    587 				w *= 18;
    588 				this.width = w + "px";
    589 				break;
    590 			case "%":
    591 				this._percEx.w = w;
    592 				w = NaN;
    593 				break;
    594 		}
    595 		//
    596 		su = wm.splitUnits(this.height);
    597 		var h = su.value;
    598 		switch (su.units) {
    599 			// FIXME: 'flex' and 'em' are deprecated, probably this should be in BC block
    600 			case "flex":
    601 				h *= 100;
    602 				this._percEx.h = h;
    603 				this.height = h + "%";
    604 				h = NaN;
    605 				break;
    606 			case "em":
    607 				h *= h * 18;
    608 				this.height = h + "px";
    609 				break;
    610 			case "%":
    611 				this._percEx.h = h;
    612 				h = NaN;
    613 				break;
    614 		}
    615 		//console.log(w, h);
    616 		//this.setBounds(NaN, NaN, w, h);
    617 		this.setBounds(this.left, this.top, w, h);
    618 	},
    619 	// return the 'box' setting of our parentNode
    620 	getParentBox: function() {
    621 		var n = (this.domNode || 0).parentNode;
    622 		return n && (n.box || (n.getAttribute && n.getAttribute("box"))) || (this.parent||0).box || '';
    623 	},
    624 	// return true if width is not controlled by layout
    625 	canSetWidth: function() {
    626 		return this.getParentBox() != 'v';
    627 	},
    628 	// return true if height is not controlled by layout
    629 	canSetHeight: function() {
    630 		return this.getParentBox() != 'h';
    631 	},
    632 	setSizeProp: function(n, v) {
    633 		/*
    634 		var ex = {h: "width", v: "height"}[this.getParentBox()];
    635 		if (ex == n)
    636 			this.autoSize = false;
    637 		*/
    638 		this[n] = v;
    639 		this.updateBounds();
    640 		if (!this._cupdating)
    641 			this.reflowParent();
    642 	},
    643 	setWidth: function(inWidth) {
    644 		this.setSizeProp("width", inWidth);
    645 	},
    646 	setHeight: function(inHeight) {
    647 		this.setSizeProp("height", inHeight);
    648 	},
    649 	sizeFromNode: function() {
    650 		//var box = wm.getNaturalBox(this.domNode);
    651 		var box = dojo.marginBox(this.domNode);
    652 		var pb = this.getParentBox();
    653 		if (pb) {
    654 			p = {v:"h", h:"w"}[pb],
    655 			sz = box[p];
    656 			p = {v: "height", h: "width"}[pb];
    657 			this[p] = (sz > 0 ? sz : 16) + "px";
    658 		} else {
    659 			this.width = box.w + 'px';
    660 			this.height = box.h + 'px';
    661 		}
    662 	},
    663 	// FIXME: need custom auto-fit for widgets whose size is not automatic
    664 	// (e.g. contain abs position children)
    665 	setAutoSize: function(inAutoSize) {
    666 		this.autoSize = inAutoSize;
    667 		if (this.autoSize)
    668 			this.reflowParent();
    669 	},
    670 	//===========================================================================
    671 	// Rendering
    672 	//===========================================================================
    673 	render: function() {
    674 		//this.renderHtml();
    675 		this.renderCss();
    676 		return true;
    677 	},
    678 	renderCss: function() {
    679 		this.dom.setCssText(this.getCssText());
    680 		this.renderBounds();
    681 	},
    682 	getCssText: function() {
    683 		var t =
    684 			"margin:" + (this.margin.split(",").join("px ") || 0) + "px;"
    685 			+ "padding:" + (this.padding.split(",").join("px ") || 0) + "px;"
    686 			+ "border:0 solid;"
    687 			+ "border-width:" + (this.border.split(",").join("px ") || 0) + "px;"
    688 			+ "border-color:" + this.borderColor + ";"
    689 			+ (this.backgroundColor ? "background-color:" + this.backgroundColor + ";" : "")
    690 			+ "overflow:" + (this.autoScroll ? "auto" : "hidden") + ";"
    691 			+ (this.scrollX ? "overflow-x:scroll;" : "")
    692 			+ (this.scrollY ? "overflow-y:scroll;" : "")
    693 			+ (this.cssText || "")
    694 		;
    695 		return t;
    696 	},
    697 	renderBounds: function() {
    698 		this.dom.setBox(this.getStyleBounds(), this.singleLine);
    699 		// bc
    700 		if (this.designWrapper)
    701 			this.designWrapper.controlBoundsChange();
    702 	},
    703 	//===========================================================================
    704 	// Flow
    705 	//===========================================================================
    706 	// FIXME: controversial update implementation cribbed from Layers.js
    707 	/*
    708 	beginUpdate: function() {
    709 		this.domNode._reflowing = true;
    710 	},
    711 	endUpdate: function() {
    712 		this.domNode._reflowing = false;
    713 	},
    714 	*/
    715 	reflow: function() {
    716 		//wm.fire(this.domNode, "reflow");
    717 	},
    718 	reflowParent: function() {
    719 		wm.fire(this.parent, "reflow");
    720 		//wm.fire(this.domNode.parentNode, "reflow");
    721 	},
    722 	setScrollX: function(inScrollX) {
    723 		this.scrollX = inScrollX;
    724 		this.reflowParent();
    725 	},
    726 	setScrollY: function(inScrollY) {
    727 		this.scrollY = inScrollY;
    728 		this.reflowParent();
    729 	},
    730 	//===========================================================================
    731 	// Groups
    732 	//===========================================================================
    733 	groupHandler: function(inMessage, inArgument) {
    734 		switch(inMessage){
    735 			case "disabled":
    736 				this.setDisabled(inDisabled);
    737 				break;
    738 		}
    739 	},
    740 	//===========================================================================
    741 	// Convenience
    742 	//===========================================================================
    743 	/**
    744 		Set <a href="#showing">showing</a> property true.
    745 	*/
    746 	show: function() {
    747 		this.setValue("showing", true);
    748 	},
    749 	/**
    750 		Set <a href="#showing">showing</a> property false.
    751 	*/
    752 	hide: function() {
    753 		this.setValue("showing", false);
    754 	},
    755 	/**
    756 		Set <a href="#disabled">disabled</a> property true.
    757 	*/
    758 	disable: function() {
    759 		this.setValue("disabled", true);
    760 	},
    761 	/**
    762 		Set <a href="#disabled">disabled</a> property false.
    763 	*/
    764 	enable: function() {
    765 		this.setValue("disabled", false);
    766 	},
    767 	//===========================================================================
    768 	// Setters
    769 	//===========================================================================
    770 	setParent: function(inParent) {
    771 		var op = this.parent, p = this.parent = inParent;
    772 		if (op && op != p) {
    773 			op.removeWidget(this);
    774 			// BC: we still have non-container parents (e.g. wm.Dialog)
    775 			if (op.removeControl)
    776 				op.removeControl(this);
    777 		}
    778 		if (p) {
    779 			p.addWidget(this);
    780 			// BC: we still have non-container parents (e.g. wm.Dialog)
    781 			if (p.addControl)
    782 				p.addControl(this);
    783 		}
    784 		// BC: wm.Layout
    785 		else if (this.parentNode && this.domNode) {
    786 			this.parentNode.appendChild(this.domNode);
    787 		}
    788 		if (p && op)
    789 			dojo.publish("wmwidget-parentChange", [op, p, this]);
    790 	},
    791 	canChangeShowing: function() {
    792 		return true;
    793 	},
    794 	setShowing: function(inShowing) {
    795 		if (!this.canChangeShowing())
    796 			return;
    797 		if (this.showing != inShowing) {
    798 			this.showing = inShowing;
    799 			this.domNode.style.display = inShowing ? '' : 'none';
    800 			this.reflowParent();
    801 		}
    802 	},
    803 	/**
    804 		Set disabled property for this widget.<br/>
    805 		<br/>
    806 		Some widgets change behavior or display based on the disabled state.<br>
    807 		@param {Boolean} inDisabled True to set disabled.
    808 	*/
    809 	setDisabled: function(inDisabled) {
    810 		for (var i in this.widgets) {
    811 			this.widgets[i].setDisabled(inDisabled);
    812 		}
    813 		this.disabled = inDisabled;
    814 	},
    815 	setGroup: function(inGroup) {
    816 		this.group = inGroup;
    817 		dojo.unsubscribe(this._subscription);
    818 		if (this.group)
    819 			this._subscription = dojo.subscribe(this.group, this, "groupHandler");
    820 	},
    821 	setBackgroundColor: function(inColor) {
    822 		this.backgroundColor = inColor;
    823 		this.render();
    824 	},
    825 	setBorderColor: function(inColor) {
    826 		this.borderColor = inColor;
    827 		this.render();
    828 	},
    829 	//===========================================================================
    830 	// Default and User Style Classes
    831 	//===========================================================================
    832 	addRemoveDefaultCssClass: function(inAdd) {
    833 		if (this.owner)
    834 			dojo[inAdd ? "addClass" : "removeClass"](this.domNode, this.owner.declaredClass + '-' + this.name);
    835 	},
    836 	getUserNodeClasses: function(inNodeName) {
    837 		var klasses = this._classes;
    838 		for (var i in klasses) {
    839 			if (inNodeName == i)
    840 				return klasses[i].join(' ');
    841 		}
    842 		return "";
    843 	},
    844 	initUserClasses: function() {
    845 		// bc
    846 		if (dojo.isArray(this._classes))
    847 			this._classes = {domNode: this._classes};
    848 		var klasses = this._classes;
    849 		for (var i in klasses)
    850 			this.initUserNodeClasses(klasses[i], i);
    851 	},
    852 	initUserNodeClasses: function(inClasses, inNodeName) {
    853 		var k = inClasses || [], n = this[inNodeName];
    854 		if (n)
    855 			// add classes together for speed; we don't care about checking if the class is already on the node
    856 			dojo.addClass(n, k.join(' '));
    857 	},
    858 	/**
    859 		Add CSS class to a widget node.<br/>
    860 		@param {String} inClass The class to add.
    861 		@param {String} inNodeName (Optional) a property in this widget that references a node.
    862 		If ommitted, the default node is used.
    863 		@example this.panel.addUserClass("hilite-border");
    864 	*/
    865 	addUserClass: function(inClass, inNodeName) {
    866 		inNodeName = inNodeName || "domNode";
    867 		var cs = this._classes[inNodeName] = this._classes[inNodeName] || [];
    868 		cs.push(inClass);
    869 		var n = this[inNodeName];
    870 		if (n)
    871 			dojo.addClass(n, inClass);
    872 	},
    873 	/**
    874 		Remove a CSS class from a widget node.<br/>
    875 		@param {String} inClass The class to remove.
    876 		@param {String} inNodeName (Optional) a property in this widget that references a node.
    877 		If ommitted, the default node is used.
    878 		@example this.panel.removeUserClass("hilite-border");
    879 	*/
    880 	removeUserClass: function(inClass, inNodeName) {
    881 		inNodeName = inNodeName || "domNode";
    882 		var n = this[inNodeName];
    883 		if (n)
    884 			dojo.removeClass(n, inClass);
    885 		var cs = this._classes[inNodeName] || [];
    886 		for (var i=0, c; c=cs[i]; i++)
    887 			if (c == inClass)
    888 				cs.splice(i--, 1);
    889 		if (!cs.length)
    890 			delete this._classes[inNodeName];
    891 	},
    892 	getOrderedWidgets: function() {
    893 		return [];
    894 	}
    895 });
    896
    897 // layout specific
    898
    899 /*
    900 wm.Control.extend({
    901 	//fluidSize: 0,
    902 	//alignInParent: "justified",
    903 	//setFluidSize: function(inFluidSize) {
    904 	//	this.fluidSize = inFluidSize;
    905 	//	this.reflowParent();
    906 	//}
    907 });
    908
    909 wm.Object.extendSchema(wm.Control, {
    910 	//fluidSize: {group: "layout"},
    911 });
    912 */
    913
    914 wm.Widget = wm.Control;
    915