debug/log changes
[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         if (this.parent && this.parent.xtype == 'GtkTable' && item.pack == 'add') {
378             var c = n % this.parent.config.n_columns;
379             var r = Math.floor(pos/_this.parent.config.n_columns);
380             item.pack = [ 'attach', c, c+1, r, r+1, 
381                     typeof(item.x_options) == 'undefined' ?  5 : item.x_options,
382                     typeof(item.y_options) == 'undefined' ?  5 : item.y_options,
383                     typeof(item.x_padding) == 'undefined' ?  0 : item.x_padding,
384                     typeof(item.x_padding) == 'undefined' ?  0 : item.x_padding
385             
386             ];
387         }
388         
389         
390         
391         
392         XObject.log(pack_m + '[' + args.join(',') +']');
393         //Seed.print('args: ' + args.length);
394         if (pack_m) {
395             this.el[pack_m].apply(this.el, args);
396         }
397         
398        
399         
400     },
401     /**
402       * @method addListener
403       * Connects a method to a signal. (gjs/Seed aware)
404       * 
405       * @arg sig  {String} name of signal
406       * @arg fn  {Function} handler.
407       */
408     addListener  : function(sig, fn) 
409     {
410  
411         XObject.log("Add signal " + sig);
412         fn.id= sig;
413         var _li = XObject.createDelegate(fn,this);
414         // private listeners that are not copied to GTk.
415         
416         if (typeof(Seed) != 'undefined') {
417           //   Seed.print(typeof(_li));
418             this.el.signal[sig].connect(_li);
419         } else {
420             this.el.connect( sig, _li);
421         }
422              
423         
424     },
425      /**
426       * @method get
427       * Finds an object in the child elements using xid of object.
428       * prefix with '.' to look up the tree.. 
429       * prefix with multiple '..' to look further up..
430       * prefix with '/' to look from the top, eg. '^LeftTree.model'
431       * 
432       * @arg name  {String} name of signal
433       * @return   {XObject|false} the object if found.
434       */
435     get : function(xid)
436     {
437         XObject.log("SEARCH FOR " + xid + " in " + this.id);
438         var ret=  false;
439         var oid = '' + xid;
440         if (!xid.length) {
441             throw {
442                 name: "ArgumentError", 
443                 message : "ID not found : empty id"
444             }
445         }
446         
447         if (xid[0] == '.') {
448             return this.parent.get(xid.substring(1));
449         }
450         if (xid[0] == '/') {
451             
452             if (typeof(XObject.cache[xid]) != 'undefined') {
453                 return XObject.cache[xid]; 
454             }
455             if (xid.indexOf('.') > -1) {
456                 
457                 var child = xid.split('.');
458                 var nxid = child.shift();
459                     
460                 child = child.join('.');
461                 if (typeof(XObject.cache[nxid]) != 'undefined') {
462                     return XObject.cache[nxid].get(child);
463                 }
464                 
465                 
466             }
467             var e = this;
468             while (e.parent) {
469                 e = e.parent;
470             }
471             
472             try {
473                 ret = e.get(xid.substring(1));
474             } catch (ex) { }
475             
476             if (!ret) {
477                 throw {
478                     name: "ArgumentError", 
479                     message : "ID not found : " + oid
480                 }
481             }
482             XObject.cache[xid] = ret;
483             return XObject.cache[xid];
484         }
485         var child = false;
486         
487         if (xid.indexOf('.') > -1) {
488             child = xid.split('.');
489             xid = child.shift();
490             
491             child = child.join('.');
492             
493         }
494         if (xid == this.id) {
495             try {
496                 return child === false ? this : this.get(child);
497             } catch (ex) {
498                 throw {
499                     name: "ArgumentError", 
500                     message : "ID not found : " + oid
501                 }
502             }
503             
504         }
505         
506         
507         this.items.forEach(function(ch) {
508             if (ret) {
509                 return;
510             }
511             if (ch.id == xid) {
512                 ret = ch;
513             }
514         })
515         if (ret) {
516             try {
517                 return child === false ? ret : ret.get(child);
518             } catch (ex) {
519                 throw {
520                     name: "ArgumentError", 
521                     message : "ID not found : " + oid
522                 }
523             }
524             
525         }
526         // iterate children.
527         var _this = this;
528         this.items.forEach(function(ch) {
529             if (ret) {
530                 return;
531             }
532             if (!ch.get) {
533                 XObject.error("invalid item...");
534                 imports.console.dump(_this);
535                 Seed.quit();
536             }
537             try {
538                 ret = ch.get(xid);
539             } catch (ex) { }
540             
541             
542         });
543         if (!ret) {
544             throw {
545                 name: "ArgumentError", 
546                 message : "ID not found : " + oid
547             }
548         }
549         try {
550             return child === false ? ret : ret.get(child);
551         } catch (ex) {
552             throw {
553                 name: "ArgumentError", 
554                 message : "ID not found : " + oid
555             }
556         }
557     }
558       
559       
560
561          
562      
563 /**
564  * Copies all the properties of config to obj.
565  *
566  * Pretty much the same as JQuery/Prototype.. or Roo.apply
567  * @param {Object} obj The receiver of the properties
568  * @param {Object} config The source of the properties
569  * @param {Object} defaults A different object that will also be applied for default values
570  * @return {Object} returns obj
571  * @member XObject extend
572  */
573
574
575 XObject.extend = function(o, c, defaults){
576     if(defaults){
577         // no "this" reference for friendly out of scope calls
578         XObject.extend(o, defaults);
579     }
580     if(o && c && typeof c == 'object'){
581         for(var p in c){
582             o[p] = c[p];
583         }
584     }
585     return o;
586 };
587
588 XObject.extend(XObject,
589 {
590      
591     /**
592      * @property {Boolean} debug XObject  debugging.  - set to true to debug.
593      * 
594      */
595     debug : false,
596     /**
597      * @property {Object} cache - cache of object ids
598      * 
599      */
600     cache: { },
601     /**
602      * Empty function
603      * 
604      */
605     emptyFn : function () { },
606       
607       
608       
609     /**
610      * Debug Logging
611      * @param {String|Object} output String to print.
612      */
613     log : function(output)
614     {
615         if (!this.debug) {
616             return;
617         }
618         print("LOG:" + output);  
619     },
620      
621     /**
622      * Error Logging
623      * @param {String|Object} output String to print.
624      */
625     error : function(output)
626     {
627         print("ERROR: " + output);  
628     },
629      
630    
631     /**
632      * Copies all the properties of config to obj, if the do not exist.
633      * @param {Object} obj The receiver of the properties
634      * @param {Object} config The source of the properties
635      * @return {Object} returns obj
636      * @member Object extendIf
637      */
638
639
640     extendIf : function(o, c)
641     {
642
643         if(!o || !c || typeof c != 'object'){
644             return o;
645         }
646         for(var p in c){
647             if (typeof(o[p]) != 'undefined') {
648                 continue;
649             }
650             o[p] = c[p];
651         }
652         return o;
653     },
654
655  
656
657     /**
658      * Extends one class with another class and optionally overrides members with the passed literal. This class
659      * also adds the function "override()" to the class that can be used to override
660      * members on an instance.
661      *
662      * usage:
663      * MyObject = Object.define(
664      *     function(...) {
665      *          ....
666      *     },
667      *     parentClass, // or Object
668      *     {
669      *        ... methods and properties.
670      *     }
671      * });
672      * @param {Function} constructor The class inheriting the functionality
673      * @param {Object} superclass The class being extended
674      * @param {Object} overrides (optional) A literal with members
675      * @return {Function} constructor (eg. class
676      * @method define
677      */
678     define : function()
679     {
680         // inline overrides
681         var io = function(o){
682             for(var m in o){
683                 this[m] = o[m];
684             }
685         };
686         return function(constructor, parentClass, overrides) {
687             if (typeof(parentClass) == 'undefined') {
688                 XObject.error("XObject.define: Missing parentClass: when applying: " );
689                 XObject.error(new String(constructor));
690                 Seed.quit(); 
691             }
692             if (typeof(parentClass.prototype) == 'undefined') {
693                 XObject.error("Missing protype: when applying: " );
694                 XObject.error(new String(constructor));
695                 XObject.error(new String(parentClass));
696                 Seed.quit(); 
697             }
698             var F = function(){};
699             var sbp;
700             var spp = parentClass.prototype;
701             
702             F.prototype = spp;
703             sbp = constructor.prototype = new F();
704             sbp.constructor=constructor;
705             constructor.superclass=spp;
706
707             // extends Object.
708             if(spp.constructor == Object.prototype.constructor){
709                 spp.constructor=parentClass;
710             }
711             
712             constructor.override = function(o){
713                 Object.extend(constructor.prototype, o);
714             };
715             sbp.override = io;
716             XObject.extend(constructor.prototype, overrides);
717             return constructor;
718         };
719     }(),
720
721          
722     /**
723      * returns a list of keys of the object.
724      * @param {Object} obj object to inspect
725      * @return {Array} returns list of kyes
726      * @member XObject keys
727      */
728     keys : function(o)
729     {
730         var ret = [];
731         for(var i in o) {
732             ret.push(i);
733         }
734         return ret;
735     },
736       
737     /**
738      * @member XObject createDelegate
739      * creates a delage metdhod
740      * @param {Function} method to wrap
741      * @param {Object} scope 
742      * @param {Array} args to add
743      * @param {Boolean|Number} append arguments or replace after N arguments.
744      * @return {Function} returns the delegate
745      */
746
747     createDelegate : function(method, obj, args, appendArgs){
748         
749         return function() {
750             XObject.log("CALL: " + obj.id + ':'+ method.id);
751             
752             var callArgs = args || arguments;
753             if(appendArgs === true){
754                 callArgs = Array.prototype.slice.call(arguments, 0);
755                 callArgs = callArgs.concat(args);
756             }else if(typeof appendArgs == "number"){
757                 callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
758                     var applyArgs = [appendArgs, 0].concat(args); // create method call params
759                     Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
760                 }
761                 return method.apply(obj || window, callArgs);
762             };
763     }
764     
765 });