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