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