      1 /*
      2  *  Copyright (C) 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.widget.Container");
     19
     20 /**
     21 	Base class for widget containers.
     22 	@name wm.Container
     23 	@class
     24 	@extends wm.Control
     25 */
     26 wm.define("wm.Container", wm.Control, {
     27 	/** @lends wm.Container.prototype */
     28 	published: {
     29 		invalid: { ignore: 1, bindSource: 1, readonly: 1, type: "Boolean" },
     30 		lock: { order: 0 },
     31 		freeze: { order: 5 },
     32 		box: { ignore: 1 },
     33 		boxPosition: { ignore: 1},
     34 		disabled: { ignore: 1 }
     35 	},
     36 	imageList: "",
     37 	border: 0,
     38 	container: true,
     39 	lock: false,
     40 	freeze: false,
     41 	classNames: "wmcontainer",
     42 	constructor: function() {
     43 		this.c$ = [];
     44 	},
     45 	init: function() {
     46 		this.inherited(arguments);
     47 		// NOTE: we require certain side-effects of property setters at init time, but not others.
     48 		// E.g., we don't want to 'reflow' for every property setter.
     49 		// Strategy is to use flags to change setter behavior.
     50 		// FIXME (naming): Control is setting _cupdating flag during init.
     51 		this.setLayoutKind(this.layoutKind);
     52 		this.domNode.box = this.box = "";
     53 	},
     54 	postInit: function() {
     55 	//loaded: function() {
     56 		if (this.isDesignLoaded())
     57 			this.setLock(this.lock);
     58 		this.inherited(arguments);
     59 	},
     60 	// backward-compatibility fixups
     61 	afterPaletteDrop: function() {
     62 		if (this.verticalAlign == "justified")
     63 			this.verticalAlign = "top";
     64 		if (this.horizontalAlign == "justified")
     65 			this.horizontalAlign = "left";
     66 	},
     67 	bc: function() {
     68 		this.inherited(arguments);
     69 		/*
     70 		if (this.verticalAlign == "justified") {
     71 			this.verticalAlign = "top";
     72 		}
     73 		if (this.horizontalAlign == "justified") {
     74 			this.horizontalAlign = "left";
     75 		}
     76 		*/
     77 		delete this.layoutJustify;
     78 		if (this.layoutAlign) {
     79 			this.contentAlign = this.layoutAlign;
     80 			delete this.layoutAlign;
     81 		}
     82 		if (this.layoutFit) {
     83 			this.fitToContent = this.layoutFit;
     84 			delete this.layoutFit;
     85 		}
     86 		if (this.box == "h") {
     87 			this.layoutKind = "left-to-right";
     88 			//this.layout = wm.Container.vBox;
     89 		}
     90 		if (this.boxPosition) {
     91 			var
     92 				boxPositions = ['topLeft', 'center', 'bottomRight'],
     93 				vAligns = ["top", "middle", "bottom"],
     94 				hAligns = ["left", "center", "right"],
     95 				h = this.layoutKind == "left-to-right",
     96 				i = dojo.indexOf(boxPositions, this.boxPosition);
     97 			if (i != -1) {
     98 				if (h)
     99 					this.horizontalAlign = hAligns[i];
    100 				else
    101 					this.verticalAlign = vAligns[i];
    102 			}
    103 		}
    104 	},
    105 	//
    106 	// Child Controls
    107 	//
    108 	addWidget: function(inWidget){
    109 		this.inherited(arguments);
    110 		if (!inWidget.autoSize) {
    111 			if (this.box == 'h' && !inWidget.width)
    112 				inWidget.setProp("width", "64px");
    113 			else if (this.box == 'v' && !inWidget.height)
    114 				inWidget.setProp("height", "64px");
    115 			//inWidget.setSize(inWidget.size);
    116 		}
    117 	},
    118 	getOrderedWidgets: function() {
    119 		return this.c$;
    120 	},
    121 	addControl: function(inControl) {
    122 		this.c$.push(inControl);
    123 		//this.dom.append(inControl.dom);
    124 	},
    125 	removeControl: function(inControl) {
    126 		//this.dom.remove(inControl.dom);
    127 		for (var i=0, c; c=this.c$[i]; i++){
    128 			if (c == inControl) {
    129 				this.c$.splice(i, 1);
    130 				return i;
    131 			}
    132 		}
    133 	},
    134         // Added by michael k 5/15/09 to support the PopupHelp dialog
    135 	removeAllControls: function() {
    136 		    while (this.c$.length) {
    137 			  var c = this.c$[0];
    138 			  this.removeControl(c);
    139 			  c.destroy();
    140 		    }
    141 			  /*
    142 	      while (this.c$.length) this.removeControl(this.c$[0]);
    143 	      for (var n in this.widgets)
    144 		    this.removeWidget(this.widgets[n]);
    145 	      while (this.domNode.firstChild) this.domNode.removeChild(this.domNode.firstChild);
    146 			  */
    147 	      this.reflow();
    148 	},
    149 	insertControl: function(inControl, inIndex) {
    150 		this.c$.splice(inIndex, 0, inControl);
    151 		//this.dom.append(inControl.dom);
    152 	},
    153 	moveControl: function(inControl, inIndex) {
    154 		var i0 = this.removeControl(inControl);
    155 		if (i0 < inIndex)
    156 			inIndex--;
    157 		this.c$.splice(inIndex, 0, inControl);
    158 	},
    159 	indexOfControl: function(inControl) {
    160 		for (var i=0, c; c=this.c$[i]; i++){
    161 			if (c == inControl) {
    162 				return i;
    163 			}
    164 		}
    165 		return -1;
    166 	},
    167 	nextSibling: function(inControl) {
    168 		for (var i=0, c; c=this.c$[i]; i++){
    169 			if (c == inControl) {
    170 				return this.c$[i+1];
    171 			}
    172 		}
    173 	},
    174 	prevSibling: function(inControl) {
    175 		for (var i=0, c; c=this.c$[i]; i++){
    176 			if (c == inControl) {
    177 				return this.c$[i-1];
    178 			}
    179 		}
    180 	},
    181 	//
    182 	// Flow
    183 	//
    184 	reflow: function() {
    185 		this._boundsDirty = true;
    186 		if (this._cupdating)
    187 			return;
    188 		if (this.isDesignLoaded() && this.parent && (this.fitToContent || this.parent.fitToContent)) {
    189 			this.parent.reflow();
    190 			if (this.fitToContent) {
    191 				this.calcFitToContent();
    192 			}
    193 		} else {
    194 			this.flow();
    195 		}
    196 	},
    197 	flow: function() {
    198 		if (this._boundsDirty && !this._cupdating) {
    199 			this.layout.flow(this);
    200 		}
    201 		//else (!this._boundsDirty)
    202 		//	console.log(this.name, ": not flowing (clean bounds)");
    203 	},
    204 	renderControls: function() {
    205 		for (var i=0, c; c=this.c$[i]; i++) {
    206 			c.render();
    207 		}
    208 	},
    209 	// bc
    210 	nodeBoundsChange: function() {
    211 		// should be caused by box layout flow
    212 		/*
    213 		this.setBounds(dojo.marginBox(this.domNode));
    214 		this.flow();
    215 		*/
    216 	},
    217 	//
    218 	// Image list
    219 	//
    220 	imageListChanged: function() {
    221 		for (var i=0, c; c=this.c$[i]; i++) {
    222 			wm.fire(c, "imageListChanged");
    223 		}
    224 	},
    225 	setImageList: function(inImageList) {
    226 		this.imageList = inImageList;
    227 		this.imageListChanged();
    228 	},
    229 	//
    230 	// validation
    231 	//
    232 	validate: function() {
    233 		this.setValue("invalid", this.getInvalid());
    234 		wm.fire(this.parent, "validate");
    235 	},
    236 	getInvalid: function() {
    237 		for (var i in this.widgets) {
    238 			var w = this.widgets[i];
    239 			if (w.getInvalid && w.getInvalid())
    240 				return true;
    241 		}
    242 		return false;
    243 	},
    244 	//
    245 	// Lock/freeze
    246 	//
    247 	getLock: function() {
    248 		return this.lock || (this.parent && wm.fire(this.parent, "getLock"));
    249 	},
    250 	setLock: function(inLock) {
    251 		this.lock = inLock;
    252 		studio.refreshComponentOnTree(this);
    253 	},
    254 	getFreeze: function() {
    255 		return this.freeze || this.getLock();
    256 	},
    257 	// FIXME: design only? vestigal?
    258 	/*
    259 	findContainer: function(inType) {
    260 		if (!this.lock) {
    261 			if (this.freeze || !this.isWidgetTypeAllowed(inType)) {
    262 				for (var i in this.widgets) {
    263 					var w = this.widgets[i];
    264 					if (w.container) {
    265 						var r = w.findContainer(inType);
    266 						if (r)
    267 							return r;
    268 					}
    269 				}
    270 			} else {
    271 				return this;
    272 			}
    273 		}
    274 	},
    275 	*/
    276 	// used by paste
    277 	isWidgetTypeAllowed: function(inType) {
    278 		// subclasses should override this to enforce only certain widget types
    279 		// are allowed to be added to the container.
    280 		return true;
    281 	},
    282 	/*
    283 	setBox: function(inBox) {
    284 		if (this.box != inBox) {
    285 			this.box = (this.containerNode || this.domNode).box = inBox;
    286 			// FIXME: wtf?
    287 			//if (this.isSizeable() || !this.isMoveable())
    288 				this._reorientChildren(this.box);
    289 			this.reflow();
    290 		}
    291 	},
    292 	*/
    293 	/*
    294 	_reorientChildren: function(inBox) {
    295 		var b = inBox, bp = wm.Box.prototype, bw = bp.width, bh = bp.height;
    296 		var parentNode = this.containerNode || this.domNode;
    297 		wm.forEachProperty(this.widgets, function(w) {
    298 			if (w.domNode.parentNode != parentNode)
    299 				return;
    300 			var s = w.domNode.style, f = (b == 'flow' || b == '');
    301 			if (f) {
    302 				s.position = 'static';
    303 				w.left = w.top = '';
    304 				w.updateBounds();
    305 			} else
    306 				s.position = 'absolute';
    307 			w.moveable = !f;
    308 			if (b == 'h' || b == 'v') {
    309 				w.width = bw;
    310 				w.height = bh;
    311 				w.updateBounds();
    312 			}
    313 		});
    314 	},*/
    315 	_reorientChildren: function(inBox) {
    316 		var parentNode = this.containerNode || this.domNode;
    317 		wm.forEachProperty(this.widgets, function(w) {
    318 			if (w.domNode.parentNode != parentNode)
    319 				return;
    320 			var ww = w.width;
    321 			w.width = w.height;
    322 			w.height = ww;
    323 			w.updateBounds();
    324 		});
    325 	},
    326 	clearData: function() {
    327 		var clear = function(w) {
    328 			if (w instanceof wm.Editor)
    329 				w.clear();
    330 		}
    331 		this.forEachWidget(clear);
    332 	}
    333 });
    334
    335 // Design
    336
    337 wm.Container.extend({
    338 	listProperties: function() {
    339 		var p = this.inherited(arguments);
    340 		p.freeze.ignore = this.schema.freeze.ignore || this.getLock();
    341 		return p;
    342 	},
    343 	writeChildren: function(inNode, inIndent, inOptions) {
    344 		var s = [];
    345 		wm.forEach(this.getOrderedWidgets(), function(c) {
    346 			if (wm.isDesignable(c) && !c.flags.notStreamable)
    347 				s.push(c.write(inIndent, inOptions));
    348 		});
    349 		return s;
    350 	},
    351 	suggestDropRect: function(inControl, ioInfo) {
    352 		this.layout.suggest(this, inControl, ioInfo);
    353 	},
    354 	suggestSize: function(inControl, ioInfo) {
    355 		this.layout.suggestSize(this, inControl, ioInfo);
    356 	},
    357 	designMoveControl: function(inControl, inDropInfo) {
    358 		info = {l:inDropInfo.l, t:inDropInfo.t, i: inDropInfo.i};
    359 		if (inControl.parent == this) {
    360 			// inDropInfo.index 'i' may be counting inControl
    361 			this.moveControl(inControl, info.i || 0);
    362 		} else {
    363 			var p = inControl.parent;
    364 			inControl.setParent(this);
    365 			inControl.designWrapper.controlParentChanged();
    366 			// inDropInfo.index 'i' is never counting inControl
    367 			this.removeControl(inControl);
    368 			this.insertControl(inControl, info.i || 0);
    369 			if (p)
    370 				p.reflow();
    371 		}
    372 		if (this.layout.insert) {
    373 			this.layout.insert(this, inControl, inDropInfo);
    374 			//return;
    375 		}
    376 		this.reflow();
    377 	},
    378 	resizeUpdate: function(inBounds) {
    379 		// update the boundary rectangle highlight only
    380 		this.designWrapper._setBounds(inBounds);
    381 	}
    382 });
    383
    384 // bc
    385 wm.Container.prototype.clearEditors = wm.Container.prototype.clearData;
    386
    387 // this stuff is layout specific
    388
    389 wm.Container.extend({
    390 	layoutKind: "top-to-bottom",
    391 	//layoutFit: false,
    392 	//contentAlign: "leftTop",
    393 	fitToContent: false,
    394 	horizontalAlign: "justified",
    395 	verticalAlign: "justified",
    396 	//horizontalAlign: "left",
    397 	//verticalAlign: "top",
    398 	makePropEdit: function(inName, inValue, inDefault) {
    399 		switch (inName) {
    400 			case "layoutKind":
    401 				return new wm.propEdit.Select({component: this, value: inValue, name: inName, options: wm.layout.listLayouts()});
    402 			case "horizontalAlign":
    403 				return new wm.propEdit.Select({component: this, value: inValue, name: inName, options: ["left", "center", "right"/*, "justified"*/]});
    404 			case "verticalAlign":
    405 				return new wm.propEdit.Select({component: this, value: inValue, name: inName, options: ["top", "middle", "bottom"/*, "justified"*/]});
    406 		}
    407 		return this.inherited(arguments);
    408 	},
    409 	setLayoutKind: function(inLayoutKind) {
    410 		if (this.layoutKind != inLayoutKind || !this.layout) {
    411 			var ctor = wm.layout.registry[inLayoutKind];
    412 			if (!ctor) {
    413 				return;
    414 			}
    415 			this.layoutKind = inLayoutKind;
    416 			this.layout = new ctor();
    417 		}
    418 		this.reflow();
    419 	},
    420 	setHorizontalAlign: function(inHorizAlign) {
    421 		this.horizontalAlign = inHorizAlign;
    422 		this.reflow();
    423 	},
    424 	setVerticalAlign: function(inVertAlign) {
    425 		this.verticalAlign = inVertAlign;
    426 		this.reflow();
    427 	},
    428 	setFitToContent: function(inFitToContent) {
    429 		this.fitToContent = inFitToContent;
    430 		this.updateBounds();
    431 		this.reflowParent();
    432 		this.calcFitToContent();
    433 		this.reflowParent();
    434 	},
    435 	calcFitToContent: function() {
    436 		if (this.layoutKind == "top-to-bottom")
    437 			this.height = this.bounds.h + "px";
    438 		else
    439 			this.width = this.bounds.w + "px";
    440 	}
    441 });
    442
    443 wm.Object.extendSchema(wm.Container, {
    444 	layoutKind: {group: "layout", order: 100},
    445 	horizontalAlign: {group: "layout", order: 110},
    446 	verticalAlign: {group: "layout", order: 120},
    447 	fitToContent: {group: "layout", order: 90}
    448 });
    449