1 jls.loader.provide('jls.gui.Element');
  2 
  3 jls.loader.require('jls.gui.Style');
  4 jls.loader.require('jls.gui.FlowLayout');
  5 
  6 /**
  7  * @namespace Provides graphical user interface.
  8  * @see jls.gui.Element
  9  * @name jls.gui
 10  */
 11 
 12 jls.gui.Element = jls.lang.Class.create( /** @lends jls.gui.Element.prototype */
 13 {
 14     /**
 15      * Creates an element.
 16      * 
 17      * @param {Object} [parameters] The element parameter.
 18      * @param {Object} [parameters.attributes] The element attributes.
 19      * @param {Object} [parameters.style] The element style.
 20      * @param {Array} [parameters.children] The element children.
 21      * @param {jls.gui.Element} [parent] The parent.
 22      * @constructs
 23 	 * @class This class represents a graphical element.
 24      */
 25     initialize : function(parameters, parent) {
 26         parameters = parameters || {};
 27         this._children = [];
 28         this._parent = parent || null;
 29         this._eventsHandlers = {};
 30         // Default parameters
 31         this._title = undefined;
 32         this._id = undefined;
 33         // Create style
 34         this._style = new jls.gui.Style(parameters.style);
 35         // Register style handler
 36     	this.getStyle().setHandler(this.onStyleChange.bind(this));
 37         if (this._parent) {
 38         	this._style.setParent(this._parent.getStyle());
 39         }
 40         // Create default layout
 41     	this._layout = this.createDefaultLayout();
 42     	// Set bounds
 43         this._x = this._style.getPropertyValue('left');
 44         this._y = this._style.getPropertyValue('top');
 45         this._w = this._style.getPropertyValue('width');
 46         this._h = this._style.getPropertyValue('height');
 47         // Set the attributes
 48         // TODO is this the right place?
 49         if (parameters.attributes) {
 50             this.setAttributes(parameters.attributes);
 51         }
 52         // Add this element to its parent
 53         // TODO is this the right place?
 54         if (this._parent != null) {
 55             this._parent.addChild(this);
 56         }
 57         // Create the concrete element
 58         this.onCreate();
 59         // Activate the style properties
 60         this._style.activate();
 61         // Add the children
 62         if (Object.isArray(parameters.children)) {
 63             this.addChildren(parameters.children);
 64         }
 65         if (this._layout != null) {
 66         	this._layout.onCreate();
 67         }
 68     },
 69     onCreate : jls.lang.Class.emptyFunction,
 70     getParent : function() {
 71         return this._parent;
 72     },
 73     /*
 74      * Attributes
 75      */
 76     /**
 77      * Sets an attribute.
 78      *
 79      * @param {String} key The attribute key.
 80      * @param {String} value The attribute value.
 81      */
 82     setAttribute : function(key, value) {
 83         /* look for a setter method in this */
 84         var setter = key.camelize('set');
 85         jls.logger.trace('setter: "' + setter + '"');
 86         if (Object.isFunction(this[setter])) {
 87             this[setter](value);
 88             return this;
 89         }
 90         this._setAttribute(key, value);
 91         return this;
 92     },
 93     _setAttribute : jls.lang.Class.emptyFunction,
 94     /**
 95      * Returns an attribute.
 96      *
 97      * @param {String} key The attribute key.
 98      * @returns {String} The attribute value.
 99      */
100     getAttribute : function(key) {
101         jls.logger.trace('getAttribute(' + key + ')');
102         /* look for a getter method in this */
103         var getter = key.camelize('get');
104         jls.logger.trace('getter: "' + getter + '"');
105         if (Object.isFunction(this[getter])) {
106             return this[getter]();
107         }
108         return this._getAttribute(key);
109     },
110     _getAttribute : jls.lang.Class.emptyFunction,
111     /**
112      * Sets some attributes.
113      *
114      * @param {Object} attributes The attributes to set.
115      */
116     setAttributes : function(attributes) {
117         for ( var k in attributes) {
118             this.setAttribute(k, attributes[k]);
119         }
120         return this;
121     },
122     /**
123      * Returns the element style.
124      * 
125      * @returns {jls.gui.Style} The element style.
126      */
127     getStyle : function() {
128         return this._style;
129     },
130     // onStyleChange(key, oldValue, newValue)
131     onStyleChange : jls.lang.Class.emptyFunction,
132     /**
133      * Returns the layout associated with this element.
134      * 
135      * @returns {jls.gui.Layout} The element layout or null if there is no layout.
136      */
137     getLayout : function() {
138         return this._layout;
139     },
140     createDefaultLayout : function() {
141         return null;
142     },
143     /**
144      * Sets the layout for this element.
145      * 
146      * @param {jls.gui.Layout} layout The layout to associate to this element.
147      */
148     setLayout : function(layout) {
149     	if (layout != null) {
150     		if (typeof layout == 'string') {
151     			var layoutClass = jls.loader.require(layout);
152     			layout = new layoutClass(this);
153     		} else if (layout instanceof jls.gui.Layout) {
154     			layout.setElement(this);
155     		} else {
156     			throw new jls.lang.Exception('Invalid layout');
157     		}
158     	}
159         this._layout = layout;
160         return this;
161     },
162     /*
163      * Element hierarchy
164      */
165     /**
166      * Adds a child.
167      *
168      * @param {Object} child The child to add.
169      */
170     addChild : function(child) {
171         if (child instanceof jls.gui.Element) {
172             jls.logger.trace('addChild for jls.gui.Element');
173             // TODO if the child already have a parent remove it
174             child._parent = this;
175             this._children.push(child);
176         } else if ((typeof child == "object") && ('classname' in child)) {
177             jls.logger.trace('addChild for object');
178             child = jls.lang.Class.newInstance(child, this);
179         } else {
180             throw new jls.lang.Exception('don\'t know how to add "' + (typeof child) + '" child type');
181         }
182         if (this._layout != null) {
183         	this._layout.onAddChild(child);
184         }
185         return child;
186     },
187     /**
188      * Adds children.
189      *
190      * @param {Object} children The children to add.
191      */
192     addChildren : function(children) {
193         for ( var i = 0; i < children.length; i++) {
194             this.addChild(children[i]);
195         }
196         return this;
197     },
198     /**
199      * Removes a child.
200      *
201      * @param {Object} child The child to remove.
202      */
203     removeChild : function(child) {
204         var index = 0;
205         if (typeof child == 'number') {
206             index = child;
207         } else {
208             for (index = this._children.length - 1; (index >= 0) && (this._children[index] !== child); index--);
209             if (index < 0) {
210                 throw new jls.lang.Exception('Child not found');
211             }
212         }
213         var removed = this._children[index];
214         this._children.splice(index, 1);
215         //jls.logger.warn('removeChild(' + index + '))');
216         if (removed && (removed instanceof jls.gui.Element)) {
217             removed._parent = null;
218             removed.onDestroy();
219         }
220         if (this._layout != null) {
221         	this._layout.onRemoveChild(child);
222         }
223         return removed;
224     },
225     /**
226      * Removes all children.
227      *
228      */
229     removeChildren : function() {
230         for ( var i = this._children.length - 1; i >= 0; i--) {
231             this.removeChild(i);
232         }
233     },
234     /**
235      * Returns all children.
236      *
237      */
238     getChildren : function() {
239         return this._children;
240     },
241     getChildCount : function() {
242         return this._children.length;
243     },
244     /**
245      * Gets a child.
246      *
247      * @param {Number} index The child index.
248      * @returns {Object} The child.
249      */
250     getChild : function(index) {
251         return this._children[index];
252     },
253     /**
254      * Destroys this element.
255      *
256      */
257     destroy : function() {
258         if (this.getParent() != null) {
259             this.getParent().removeChild(this);
260         } else {
261             this.onDestroy();
262         }
263         return this;
264     },
265     onDestroy : jls.lang.Class.emptyFunction,
266     /*
267      * Event
268      */
269     dispatch : function(event) {
270         if (event.type in this._eventsHandlers) {
271             var handlers = this._eventsHandlers[event.type];
272             for (var i = handlers.length - 1; i >= 0; i--) {
273                 handlers[i](event);
274             }
275         }
276         return this;
277     },
278     onStartObserving: jls.lang.Class.emptyFunction,
279     onStopObserving: jls.lang.Class.emptyFunction,
280     /**
281      * Register an event handler.
282      *
283      * @param {String} type The event type to observe.
284      * @param {Function} handler The handler to register.
285      * @returns {Function} The handler.
286      */
287     observe: function(type, handler) {
288         if (Object.keys(this._eventsHandlers).length == 0) {
289             this.onStartObserving();
290         }
291         if (! (type in this._eventsHandlers)) {
292             this._eventsHandlers[type] = [];
293         }
294         this._eventsHandlers[type].push(handler);
295         return handler;
296     },
297     /**
298      * Unregister an event handler.
299      *
300      * @param {String} type The event type to unregister.
301      * @param {Function} handler The handler to unregister.
302      * @returns {Array} The removed handlers.
303      */
304     unobserve: function(type, handler) {
305         var removed = [];
306         if (type in this._eventsHandlers) {
307             var handlers = this._eventsHandlers[type];
308             for (var i = handlers.length - 1; i >= 0; i--) {
309                 if (handler == handlers[i]) {
310                     removed.push(handlers[i]);
311                     handlers.splice(i, 1);
312                 }
313             }
314             if (handlers.length == 0) {
315                 delete this._eventsHandlers[type];
316             }
317         }
318         if (Object.keys(this._eventsHandlers).length == 0) {
319             this.onStopObserving();
320         }
321         return removed;
322     },
323     /*
324      * Update
325      */
326     // TODO doLayout ?
327     update : function() {
328         if (this._layout != null) {
329         	this._layout.onUpdate();
330         }
331     },
332     /*
333      * Default attributes
334      */
335     getX : function() {
336         return this._x;
337     },
338     getY : function() {
339         return this._y;
340     },
341     getW : function() {
342         return this._w;
343     },
344     getH : function() {
345         return this._h;
346     },
347     getBounds : function() {
348         return [this._x, this._y, this._w, this._h];
349     },
350     setBounds : function(rect) {
351         this._x = rect[0];
352         this._y = rect[1];
353         this._w = rect[2];
354         this._h = rect[3];
355         return this;
356     },
357     getLocation : function() {
358         return [this._x, this._y];
359     },
360     setLocation : function(point) {
361         this._x = point[0];
362         this._y = point[1];
363         return this;
364     },
365     getSize : function() {
366         return [this._w, this._h];
367     },
368     setSize : function(dim) {
369         this._w = dim[0];
370         this._h = dim[1];
371         return this;
372     },
373     getClientOffset : function() {
374         return [0, 0];
375     },
376     /*getClientBounds : function() {
377         return this.getBounds();
378     },*/
379     getClientSize : function() {
380         return this.getSize();
381     },
382     getTitle : function() {
383         return this._title;
384     },
385     setTitle : function(title) {
386         this._title = title;
387         return this;
388     }
389 });
390 
391 Object.extend(jls.gui.Element, /** @lends jls.gui.Element */
392 {
393     /**
394      * Calls a function synchronously in the GUI message thread.
395      *
396      * @param {Function} fn The function to call.
397      * @returns {Object} The function result if any.
398      * @function
399      */
400     invokeAndWait : jls.win32.Window.invokeAndWait,
401     /**
402      * Calls a function asynchronously in the GUI message thread.
403      *
404      * @param {Function} fn The handler to unregister.
405      * @function
406      */
407     invokeLater : jls.win32.Window.invokeLater
408 });
409