XObject.js
[app.Builder.js] / XObject.js
1 //<script type="text/javascript">
2 GIRepository = imports.gi.GIRepository;
3 GObject = imports.gi.GObject;
4 /**
5  * XObject
6  * Yet another attempt to create a usable object construction library for seed..
7  * 
8  * Why is this useful?
9  * A) It turns rather messy code into a tree structure, making it easy to find code relating to 
10  *    an interface element
11  * B) In theory it should be gjs/Seed compatible..
12  * C) It provides getElementById style lookups for elements.
13  * D) It provides classic OO constructors for Javascript (extend/define)
14  * E) It does not modify any buildin prototypes.. 
15  *
16  * Extend this.. to use it's wonderful features..
17  * 
18  * normal usage:
19  * XObject = imports.XObject.XObject;
20  * 
21  * Xyz = new XObject({
22  *     xtype: Gtk.Window,
23  *     id : 'window',
24  *     items : [
25  *     
26  *     ]
27  *  });
28  *  Xyz.init(); // create and show.
29  * 
30  * 
31  * use XObject.debug = 1 to turn on debugging
32  * 
33  * If XObject/[xns]/[xtype].js exists, it will use this to override properties..
34  * 
35  * 
36  * He's some questions.
37  * - should we have a special property to use as the constructor / gobject.properties rather
38  *   than sending all basic types to this?
39  * 
40  * @cfg xtype {String|Function} constructor or string.
41  * @cfg id {String}  (optional) id for registry
42  * @cfg xns {String|Object}   (optional) namespace eg. Gtk or 'Gtk' - used with xtype.
43  * @cfg items {Array}   (optional) list of child elements which will be constructed.. using XObject
44  * @cfg listeners {Object}   (optional) map Gobject signals to functions
45  * @cfg pack {Function|String|Array}   (optional) how this object gets added to it's parent
46  * @cfg el {Object}   (optional) premade GObject
47  * 
48  * 
49  * 
50  * 
51  * 
52  * 
53  */
54
55 function XObject (cfg) {
56     // first apply cfg if set.
57       //print("new XOBJECT!!!");
58       
59     //print ("XObject ctr");
60       
61     this.config = {};
62     this.constructor = XObject;
63     
64     
65     
66     // start by seeing if we have a base class....
67     try {
68         // loocks for XObject/Gtk/TreeView.js [   TreeView = { .... } ]
69         // xns is not a string!!!?
70         var gname = false;
71         if (typeof(cfg.xtype) == 'object') {
72             gname = GObject.type_name(cfg.xtype.type);
73            // print("GNAME:" +gname + " GTYPE:"+cfg.xtype.type);
74         }
75         
76         var base = gname  ? imports.XObjectBase[gname][gname] : false;
77         if (base) {
78           //  print("Overlaying XOBJBECT-BASE."  + cfg.xtype);
79             XObject.extend(this,base);
80         }
81         
82     } catch (e) {
83         // if debug?
84         XObject.log("error finding " + gname + " - " + e.toString());
85     }
86     
87     
88     // copy down all elements into self..
89     
90     for (var i in cfg) {
91         this[i] = cfg[i];
92         if (typeof(cfg[i]) == 'function') { // do we skip objects.
93             continue;
94         }
95         // these properties are not copied to cfg.
96         if (    i == 'pack' ||
97                 i == 'items' ||
98                 i == 'id' ||
99                 i == 'xtype' ||
100                 i == 'xdebug' ||
101                 i == 'xns') {
102             continue;
103         }
104         
105         
106         this.config[i] = cfg[i];
107     }
108     
109     
110     this.items = this.items || [];
111     
112     
113     // pack can be false!
114     if (typeof(this.pack) == 'undefined') {
115         
116         this.pack = [ 'add' ]
117         /*
118         var Gtk  = imports.gi.Gtk;
119         switch (true) {
120             // any others!!
121             case (this.xtype == Gtk.MenuItem):  this.pack = [ 'append' ]; break;
122             
123         }
124         */
125         
126     }
127     
128     // interesting question should we call constructor on items here...
129     // as the real work is done in init anyway..
130     var _this= this;
131     
132     var items = [];
133     this.items.forEach(function(i) {
134         items.push(i);
135     });
136     this.items = [];
137     items.forEach(function(i,n) {
138         /*
139         
140         if (type == 'GtkTable' && i.pack == 'add') {
141             var c = n % _this.config.n_columns;
142             var r = Math.floor(n/_this.config.n_columns);
143             i.pack = [ 'attach', c, c+1, r, r+1, 
144                     typeof(i.x_options) == 'undefined' ?  5 : i.x_options,
145                     typeof(i.y_options) == 'undefined' ?  5 : i.y_options,
146                     typeof(i.x_padding) == 'undefined' ?  0 : i.x_padding,
147                     typeof(i.x_padding) == 'undefined' ?  0 : i.x_padding
148                    
149             ]
150         }
151         */
152         var item = (i.constructor == XObject) ? o : new XObject(i);
153         item.parent = _this;
154         _this.items.push(item);
155         //_this.addItem(i);
156     });
157     if (this.onConstruct) {
158         this.onConstruct.call(this);
159     }
160     
161 }
162
163
164
165 XObject.prototype = {
166     /**
167      * @property el {GObject} the Gtk / etc. element.
168      */
169     el : false, 
170     /*
171      * @property items {Array} list of sub elements
172      */
173     /**
174      * @property parent {XObject} parent Element
175      */
176      
177      /**
178      * @property config {Object} the construction configuration.
179      */
180      /**
181       * @method init
182       * Initializes the Element (el) hooks up all the listeners
183       * and packs the children.
184       * you can override this, in child objects, then 
185       * do this to do thi initaliztion.
186       * 
187       * XObject.prototype.init.call(this); 
188       * 
189       */ 
190     init : function()
191     {
192          
193        // var items = [];
194         //this.items.forEach(function(i) {
195         //    items.push(i);
196         //});
197         // remove items.
198         this.listeners = this.listeners || {}; 
199         //this.items = [];
200          
201         // do we need to call 'beforeInit here?'
202          
203         // handle include?
204         //if ((this.xtype == 'Include')) {
205         //    o = this.pre_registry[cls];
206         //}
207         var isSeed = typeof(Seed) != 'undefined';
208          
209         // xtype= Gtk.Menu ?? what about c_new stuff?
210         XObject.log("init: ID:"+ this.id +" typeof(xtype): "  + typeof(this.xtype));
211         if (!this.el && typeof(this.xtype) == 'function') {
212             XObject.log("func?"  + XObject.keys(this.config).join(','));
213             this.el = this.xtype(this.config);
214            
215         }
216         if (!this.el && typeof(this.xtype) == 'object') {
217             XObject.log("obj?"  + XObject.keys(this.config).join(','));
218             this.el = new (this.xtype)(this.config);
219       
220         }
221         //print(this.el);
222         if (!this.el && this.xns) {
223             
224             var NS = imports.gi[this.xns];
225             if (!NS) {
226                 XObject.error('Invalid xns: ' + this.xns, true);
227             }
228             constructor = NS[this.xtype];
229             if (!constructor) {
230                 XObject.error('Invalid xtype: ' + this.xns + '.' + this.xtype);
231             }
232             this.el  =   isSeed ? new constructor(this.config) : new constructor();
233             
234         }
235         XObject.log("init: ID:"+ this.id +" typeof(el):" + this.el);
236         
237         // always overlay props..
238         // check for 'write' on object..
239         /*
240         if (typeof(XObject.writeablePropsCache[this.xtype.type]) == 'undefined') {
241                 
242             var gi = GIRepository.IRepository.get_default();
243             var ty = gi.find_by_gtype(this.xtype.type);
244             var write = [];
245             for (var i =0; i < GIRepository.object_info_get_n_properties(ty);i++) {
246                 var p =   GIRepository.object_info_get_property(ty,i);
247                 if (GIRepository.property_info_get_flags(p) & 2) {
248                     write.push(GIRepository.base_info_get_name(p));
249                 }
250             }
251             XObject.writeablePropsCache[this.xtype.type] = write;
252             print(write.join(", "));
253         }
254         
255         */
256         
257          
258         for (var i in this.config) {
259             if (i == 'type') { // problem with Gtk.Window... - not decided on a better way to handle this.
260                 continue;
261             }
262             if (i == 'buttons') { // problem with Gtk.MessageDialog..
263                 continue;
264             }
265             if (i[0] == '.') { // parent? - 
266                 continue;
267             }
268             this.el[i] = this.config[i];
269         }
270         
271         // register it!
272         //if (o.xnsid  && o.id) {
273          //   XObject.registry = XObject.registry || { };
274          //   XObject.registry[o.xnsid] = XObject.registry[o.xnsid] || {}; 
275          //   XObject.registry[o.xnsid][o.id] = this;
276         //}
277         
278         var type = this.xtype.type ? GObject.type_name(this.xtype.type) : '';
279         XObject.log("add children to " + type);
280         
281         var _this=this;
282         this.items.forEach(function(i,n) {
283             _this.addItem(i,n);
284         })
285             
286         
287         for (var i in this.listeners) {
288             this.addListener(i, this.listeners[i]);
289         }
290         
291         this.init = XObject.emptyFn;
292            
293         // delete this.listeners ?
294         // do again so child props work!
295        
296         // do we need to call 'init here?'
297     },
298       
299      
300      /**
301       * @method addItem
302       * Adds an item to the object using a new XObject
303       * uses pack property to determine how to add it.
304       * @arg cfg {Object} same as XObject constructor.
305       */
306     addItem : function(item, pos) 
307     {
308         
309         if (typeof(item) == 'undefined') {
310             XObject.error("Invalid Item added to this!");
311             imports.console.dump(this);
312             Seed.quit();
313         }
314         // what about extended items!?!?!?
315        
316         item.init();
317         //print("CTR:PROTO:" + ( item.id ? item.id : '??'));
318        // print("addItem - call init [" + item.pack.join(',') + ']');
319         if (!item.el) {
320             XObject.err("NO EL!");
321             imports.console.dump(item);
322             Seed.quit();
323         }
324          
325         
326         if (item.pack===false) {  // no packing.. various items have this ..
327             return;
328         }
329         
330         if (typeof(item.pack) == 'function') { // pack is a function..
331             // parent, child
332             item.pack.apply(item, [ this , item  ]);
333             item.parent = this;
334             return;
335         }
336         
337         // pack =  'add,x,y'
338         var args = [];
339         var pack_m  = false;
340         if (typeof(item.pack) == 'string') {
341              
342             item.pack.split(',').forEach(function(e, i) {
343                 
344                 if (e == 'false') { args.push( false); return; }
345                 if (e == 'true') {  args.push( true);  return; }
346                 if (!isNaN(parseInt(e))) { args.push( parseInt(e)); return; }
347                 args.push(e);
348             });
349             //print(args.join(","));
350             
351             pack_m = args.shift();
352         } else {
353             pack_m = item.pack.shift();
354             args = item.pack;
355         }
356         
357         // handle error.
358         if (pack_m && typeof(this.el[pack_m]) == 'undefined') {
359             
360             throw {
361                 name: "ArgumentError", 
362                 message : 'pack method not available : ' + this.id + " : " + this.xtype + '.' +  pack_m + " ADDING " + item.el
363                     
364             }
365            
366             return;
367         }
368         
369         
370         // finally call the pack method 
371         //Seed.print('Pack ' + this.el + '.'+ pack_m + '(' + item.el + ')');
372         
373         args.unshift(item.el);
374         
375         
376         /*
377         THIS IS IN THE GtkTable wrapper now.
378         if (this.parent && this.parent.xtype == 'GtkTable' && item.pack == 'add') {
379             var c = n % this.parent.config.n_columns;
380             var r = Math.floor(pos/_this.parent.config.n_columns);
381             item.pack = [ 'attach', c, c+1, r, r+1, 
382                     typeof(item.x_options) == 'undefined' ?  5 : item.x_options,
383                     typeof(item.y_options) == 'undefined' ?  5 : item.y_options,
384                     typeof(item.x_padding) == 'undefined' ?  0 : item.x_padding,
385                     typeof(item.x_padding) == 'undefined' ?  0 : item.x_padding
386             
387             ];
388         }
389         */
390         
391         
392         
393         XObject.log(pack_m + '[' + args.join(',') +']');
394         //Seed.print('args: ' + args.length);
395         if (pack_m) {
396             this.el[pack_m].apply(this.el, args);
397         }
398         
399        
400         
401     },
402     /**
403       * @method addListener
404       * Connects a method to a signal. (gjs/Seed aware)
405       * 
406       * @arg sig  {String} name of signal
407       * @arg fn  {Function} handler.
408       */
409     addListener  : function(sig, fn) 
410     {
411  
412         XObject.log("Add signal " + sig);
413         fn.id= sig;
414         var _li = XObject.createDelegate(fn,this);
415         // private listeners that are not copied to GTk.
416         
417         if (typeof(Seed) != 'undefined') {
418           //   Seed.print(typeof(_li));
419             this.el.signal[sig].connect(_li);
420         } else {
421             this.el.connect( sig, _li);
422         }
423              
424         
425     },
426      /**
427       * @method get
428       * Finds an object in the child elements using xid of object.
429       * prefix with '.' to look up the tree.. 
430       * prefix with multiple '..' to look further up..
431       * prefix with '/' to look from the top, eg. '^LeftTree.model'
432       * 
433       * @arg name  {String} name of signal
434       * @return   {XObject|false} the object if found.
435       */
436     get : function(xid)
437     {
438         XObject.log("SEARCH FOR " + xid + " in " + this.id);
439         var ret=  false;
440         var oid = '' + xid;
441         if (!xid.length) {
442             throw {
443                 name: "ArgumentError", 
444                 message : "ID not found : empty id"
445             }
446         }
447         
448         if (xid[0] == '.') {
449             return this.parent.get(xid.substring(1));
450         }
451         if (xid[0] == '/') {
452             
453             if (typeof(XObject.cache[xid]) != 'undefined') {
454                 return XObject.cache[xid]; 
455             }
456             if (xid.indexOf('.') > -1) {
457                 
458                 var child = xid.split('.');
459                 var nxid = child.shift();
460                     
461                 child = child.join('.');
462                 if (typeof(XObject.cache[nxid]) != 'undefined') {
463                     return XObject.cache[nxid].get(child);
464                 }
465                 
466                 
467             }
468             var e = this;
469             while (e.parent) {
470                 e = e.parent;
471             }
472             
473             try {
474                 ret = e.get(xid.substring(1));
475             } catch (ex) { }
476             
477             if (!ret) {
478                 throw {
479                     name: "ArgumentError", 
480                     message : "ID not found : " + oid
481                 }
482             }
483             XObject.cache[xid] = ret;
484             return XObject.cache[xid];
485         }
486         var child = false;
487         
488         if (xid.indexOf('.') > -1) {
489             child = xid.split('.');
490             xid = child.shift();
491             
492             child = child.join('.');
493             
494         }
495         if (xid == this.id) {
496             try {
497                 return child === false ? this : this.get(child);
498             } catch (ex) {
499                 throw {
500                     name: "ArgumentError", 
501                     message : "ID not found : " + oid
502                 }
503             }
504             
505         }
506         
507         
508         this.items.forEach(function(ch) {
509             if (ret) {
510                 return;
511             }
512             if (ch.id == xid) {
513                 ret = ch;
514             }
515         })
516         if (ret) {
517             try {
518                 return child === false ? ret : ret.get(child);
519             } catch (ex) {
520                 throw {
521                     name: "ArgumentError", 
522                     message : "ID not found : " + oid
523                 }
524             }
525             
526         }
527         // iterate children.
528         var _this = this;
529         this.items.forEach(function(ch) {
530             if (ret) {
531                 return;
532             }
533             if (!ch.get) {
534                 XObject.error("invalid item...");
535                 imports.console.dump(_this);
536                 Seed.quit();
537             }
538             try {
539                 ret = ch.get(xid);
540             } catch (ex) { }
541             
542             
543         });
544         if (!ret) {
545             throw {
546                 name: "ArgumentError", 
547                 message : "ID not found : " + oid
548             }
549         }
550         try {
551             return child === false ? ret : ret.get(child);
552         } catch (ex) {
553             throw {
554                 name: "ArgumentError", 
555                 message : "ID not found : " + oid
556             }
557         }
558     }
559       
560       
561
562          
563      
564 /**
565  * Copies all the properties of config to obj.
566  *
567  * Pretty much the same as JQuery/Prototype.. or Roo.apply
568  * @param {Object} obj The receiver of the properties
569  * @param {Object} config The source of the properties
570  * @param {Object} defaults A different object that will also be applied for default values
571  * @return {Object} returns obj
572  * @member XObject extend
573  */
574
575
576 XObject.extend = function(o, c, defaults){
577     if(defaults){
578         // no "this" reference for friendly out of scope calls
579         XObject.extend(o, defaults);
580     }
581     if(o && c && typeof c == 'object'){
582         for(var p in c){
583             o[p] = c[p];
584         }
585     }
586     return o;
587 };
588
589 XObject.extend(XObject,
590 {
591      
592     /**
593      * @property {Boolean} debug XObject  debugging.  - set to true to debug.
594      * 
595      */
596     debug : false,
597     /**
598      * @property {Object} cache - cache of object ids
599      * 
600      */
601     cache: { },
602     /**
603      * Empty function
604      * 
605      */
606     emptyFn : function () { },
607       
608       
609       
610     /**
611      * Debug Logging
612      * @param {String|Object} output String to print.
613      */
614     log : function(output)
615     {
616         if (!this.debug) {
617             return;
618         }
619         print("LOG:" + output);  
620     },
621      
622     /**
623      * Error Logging
624      * @param {String|Object} output String to print.
625      */
626     error : function(output)
627     {
628         print("ERROR: " + output);  
629     },
630      
631    
632     /**
633      * Copies all the properties of config to obj, if the do not exist.
634      * @param {Object} obj The receiver of the properties
635      * @param {Object} config The source of the properties
636      * @return {Object} returns obj
637      * @member Object extendIf
638      */
639
640
641     extendIf : function(o, c)
642     {
643
644         if(!o || !c || typeof c != 'object'){
645             return o;
646         }
647         for(var p in c){
648             if (typeof(o[p]) != 'undefined') {
649                 continue;
650             }
651             o[p] = c[p];
652         }
653         return o;
654     },
655
656  
657
658     /**
659      * Extends one class with another class and optionally overrides members with the passed literal. This class
660      * also adds the function "override()" to the class that can be used to override
661      * members on an instance.
662      *
663      * usage:
664      * MyObject = Object.define(
665      *     function(...) {
666      *          ....
667      *     },
668      *     parentClass, // or Object
669      *     {
670      *        ... methods and properties.
671      *     }
672      * });
673      * @param {Function} constructor The class inheriting the functionality
674      * @param {Object} superclass The class being extended
675      * @param {Object} overrides (optional) A literal with members
676      * @return {Function} constructor (eg. class
677      * @method define
678      */
679     define : function()
680     {
681         // inline overrides
682         var io = function(o){
683             for(var m in o){
684                 this[m] = o[m];
685             }
686         };
687         return function(constructor, parentClass, overrides) {
688             if (typeof(parentClass) == 'undefined') {
689                 XObject.error("XObject.define: Missing parentClass: when applying: " );
690                 XObject.error(new String(constructor));
691                 Seed.quit(); 
692             }
693             if (typeof(parentClass.prototype) == 'undefined') {
694                 XObject.error("Missing protype: when applying: " );
695                 XObject.error(new String(constructor));
696                 XObject.error(new String(parentClass));
697                 Seed.quit(); 
698             }
699             var F = function(){};
700             var sbp;
701             var spp = parentClass.prototype;
702             
703             F.prototype = spp;
704             sbp = constructor.prototype = new F();
705             sbp.constructor=constructor;
706             constructor.superclass=spp;
707
708             // extends Object.
709             if(spp.constructor == Object.prototype.constructor){
710                 spp.constructor=parentClass;
711             }
712             
713             constructor.override = function(o){
714                 Object.extend(constructor.prototype, o);
715             };
716             sbp.override = io;
717             XObject.extend(constructor.prototype, overrides);
718             return constructor;
719         };
720     }(),
721
722          
723     /**
724      * returns a list of keys of the object.
725      * @param {Object} obj object to inspect
726      * @return {Array} returns list of kyes
727      * @member XObject keys
728      */
729     keys : function(o)
730     {
731         var ret = [];
732         for(var i in o) {
733             ret.push(i);
734         }
735         return ret;
736     },
737       
738     /**
739      * @member XObject createDelegate
740      * creates a delage metdhod
741      * @param {Function} method to wrap
742      * @param {Object} scope 
743      * @param {Array} args to add
744      * @param {Boolean|Number} append arguments or replace after N arguments.
745      * @return {Function} returns the delegate
746      */
747
748     createDelegate : function(method, obj, args, appendArgs){
749         
750         return function() {
751             XObject.log("CALL: " + obj.id + ':'+ method.id);
752             
753             var callArgs = args || arguments;
754             if(appendArgs === true){
755                 callArgs = Array.prototype.slice.call(arguments, 0);
756                 callArgs = callArgs.concat(args);
757             }else if(typeof appendArgs == "number"){
758                 callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
759                     var applyArgs = [appendArgs, 0].concat(args); // create method call params
760                     Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
761                 }
762                 return method.apply(obj || window, callArgs);
763             };
764     }
765     
766 });