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