src/JsRender/NodeToGtk.vala
[app.Builder.js] / src / JsRender / NodeToGtk.vala
1 /*
2
3 * This code renders the Gtk tree into a set of Gtk elements.
4 * principle = one NodeToGtk wraps around the original 'node'
5 *  
6 * it's called by the view element with
7 *       var x = new JsRender.NodeToGtk(file.tree);
8      var obj = x.munge() as Gtk.Widget;
9        
10 *
11
12 * The idea behind the Javascript tools stuff is that we can 
13 * transform what is actually being requested to be rendered
14 * -- eg. an AboutBox, and turn that into load of real widgets..
15 * that could be displayed..
16
17 * we could go on the theory that we send the whole tree to the 'plugin'
18 * and that would do all the transformations before rendering..
19 * -- this would make more sense...
20 * -- otherwise we would call it on each element, and might get really confused
21 * about scope etc..
22
23
24
25 */
26 public class JsRender.NodeToGtk : Object {
27
28         Node node;
29         Object wrapped_object; 
30         NodeToGtk parentObj;
31         
32         Gee.ArrayList<NodeToGtk> children;
33         
34         Gee.ArrayList<string> els;
35         //Gee.ArrayList<string> skip;
36         Gee.HashMap<string,string> ar_props;
37         public static int vcnt = 0; 
38
39         public NodeToGtk( Node node , NodeToGtk? parent_obj = null) 
40         {
41                 this.node = node;
42                 this.els = new Gee.ArrayList<string>(); 
43                 this.children = new Gee.ArrayList<NodeToGtk>(); 
44                 //this.skip = new Gee.ArrayList<string>();
45                 this.ar_props = new Gee.HashMap<string,string>();
46                 this.parentObj = parent_obj;
47                 
48                 if (parent_obj == null) {
49                         // then serialize up the node,
50                         // send it to javascript for processsing,
51                         // then rebuild node from return value..
52                         try {
53                                 var ret = Palete.Javascript.singleton().executeFile(
54                                                 BuilderApplication.configDirectory() + "/resources/node_to_gtk.js",
55                                                 "node_to_gtk",
56                                                 node.toJsonString()
57                                 );
58                                 var new_node = new Node();
59                                 var pa = new Json.Parser();
60                                 pa.load_from_data(ret);
61                                 var rnode = pa.get_root();
62                            
63                                 
64                                 new_node.loadFromJson(rnode.get_object(), 2);
65                                 this.node = new_node;
66                                 
67                         } catch (Palete.JavascriptError e) {
68                                 print("Error: %s\n", e.message);
69                         }
70                         
71                         
72                 }
73                 
74         }
75         
76         public Object? munge ( )
77         {
78                 var ret = this.mungeNode();
79                 if (ret == null) {
80                         return null;
81                 }
82                         
83                 return ret.wrapped_object;
84                       
85         }
86         public NodeToGtk? mungeChild(  Node cnode)
87         {
88                 var x = new  NodeToGtk(cnode, this);
89                 
90                 return x.mungeNode();
91                 
92         }
93         
94         public NodeToGtk? mungeNode()
95         {
96                 
97                 var parent = this.parentObj != null ? this.parentObj.wrapped_object : null;
98                 var cls = this.node.fqn().replace(".", "");
99                 var ns = this.node.fqn().split(".")[0];
100                 var gtkbuilder = new global::Gtk.Builder();
101
102                 var cls_gtype = gtkbuilder.get_type_from_name(cls);
103                 print("Type: %s ?= %s\n", this.node.fqn(), cls_gtype.name());
104
105                 if (cls_gtype == GLib.Type.INVALID) {
106                         print("SKIP - gtype is invalid\n");
107                         return null;
108                 }
109                 // if it's a window...  -- things we can not render....
110
111                 if (cls_gtype.is_a(typeof(global::Gtk.Window))) {
112                         // what if it has none...
113                         if (this.node.items.size < 1) {
114                                 return null;
115                         }
116                         return this.mungeChild(this.node.items.get(0));
117                 }
118                 if (cls_gtype.is_a(typeof(global::Gtk.Popover))) {
119                         // what if it has none...
120                         if (this.node.items.size < 1) {
121                                 return null;
122                         }
123                         return this.mungeChild(this.node.items.get(0));
124                 }
125
126                 var ret = Object.new(cls_gtype);
127                 ret.ref(); //??? problematic?
128                 this.wrapped_object = ret;
129                 
130                  
131                 switch(cls) {
132                         // fixme
133                         //case "GtkTreeStore": // top level.. - named and referenced
134                         case "GtkListStore": // top level.. - named and referenced
135                         //case "GtkTreeViewColumn": // part of liststore?!?!
136                         //case "GtkMenu": // top level..
137                         //case "GtkCellRendererText":
138                         case "GtkSourceBuffer":                         
139                         case "GtkClutterActor"://fixme..
140                         case "GtkClutterEmbed"://fixme.. -- we can not nest embedded.. need to solve..
141                                         
142                                 return null;
143                 }
144
145                 this.packParent();
146                 
147
148                 // pack paramenters
149
150                 
151                 if (parent != null && parent.get_type().is_a(typeof(global::Gtk.Container))) {
152                         this.packContainerParams();
153                 }
154                 
155                 var cls_gir =Palete.Gir.factoryFqn(this.node.fqn()); 
156                 if (cls_gir == null) {
157                         return null;
158                 }
159                 //var id = this.node.uid();
160                 //var ret = @"$pad<object class=\"$cls\" id=\"$id\">\n";
161                 // properties..
162                 var props = cls_gir.props;
163                 
164               
165                 var pviter = props.map_iterator();
166                 while (pviter.next()) {
167                         
168                                 // print("Check: " +cls + "::(" + pviter.get_value().propertyof + ")" + pviter.get_key() + " " );
169                         var k = pviter.get_key();
170                         // skip items we have already handled..
171                         if  (!this.node.has(k)) {
172                                 continue;
173                         }
174                         // find out the type of the property...
175                         var type = pviter.get_value().type;
176                         type = Palete.Gir.fqtypeLookup(type, ns);
177
178                         var  ocl = (ObjectClass) cls_gtype.class_ref ();
179                         var ps = ocl.find_property(k);
180                         
181                         // attempt to read property type and enum...
182                         
183                         if (ps != null) {
184                                 var vt = ps.value_type;
185                                 if (vt.is_enum()) {
186                                         
187                                         var raw_val = this.node.get(k).strip();
188                                         var rv_s = raw_val.split(".")
189                                         if (rv_s.length > 0) {
190                                                 raw_val = rv_s[rv_s.length-1];                                  
191                                                 EnumClass ec = (EnumClass) vt.class_ref ();
192                                                 for (var i =0;i< ec.n_values; i++) {
193                                                         var ev = ec.values[i];
194                                                         var ev_s= ev.split("_");
195                                                         if (raw_val == ev_s[ev_s.length-1]) {
196                                                                 var sval = GLib.Value(typeof(int));
197                                                                 sval.set_int(int.parse(val));
198                                                                 ret.set_property(k, sval);
199                                                                 continue;
200                                                         }
201                                                 }
202                                         }
203                                 }
204                         }
205
206
207                         
208
209                         var val = this.toValue(this.node.get(k).strip(), type);
210                         if (val == null) {
211                                 print("skip (failed to transform value %s type = %s from %s\n", 
212                                         cls + "." + k, type,  this.node.get(k).strip());
213                                 continue;
214                         }
215                         print ("set_property ( %s , %s / %s)\n", k, this.node.get(k).strip(), val.strdup_contents());
216                         
217                         
218                         ret.set_property(k, val);  
219                         
220
221                 }
222                 // packing???
223                 // for now... - just try the builder style packing
224                 
225                 
226                  
227                 if (this.node.items.size < 1) {
228                         return this;
229                 }
230                 
231                 for (var i = 0; i < this.node.items.size; i++ ) {
232
233                          var ch = this.mungeChild(this.node.items.get(i));
234                          if (ch != null) {
235                                  this.children.add(ch);
236                          }
237                          
238                 }
239                 
240                 this.afterChildren();
241                 
242                 return this;
243                 
244
245                  
246
247         }
248         
249         public void  afterChildren()
250         {
251                 // things like GtkNotebook - we have to pack children after they have been created..
252                 var cls = this.node.fqn().replace(".", "");
253                 
254                 if (cls == "GtkNotebook") {
255                         this.afterChildrenGtkNotebook();
256                 }
257                 
258                 
259                 
260         }
261         
262         public void  afterChildrenGtkNotebook()
263         {
264                 // we have a number of children..
265                 // some are labels - this might need to be more complex...
266                 // perhaps labels should be a special property labels[] of the notebook..
267                 var labels = new Gee.ArrayList<NodeToGtk>();
268                 var bodies = new Gee.ArrayList<NodeToGtk>();
269                 for (var i = 0; i < this.children.size; i++) { 
270                         var cn = this.children.get(i).node.fqn().replace(".", "");
271                         if (cn != "GtkLabel") {
272                                 bodies.add(this.children.get(i));
273                                 continue;
274                         }
275                         labels.add(this.children.get(i));
276                 }
277                 for (var i = 0; i < bodies.size; i++) { 
278                         ((global::Gtk.Notebook)this.wrapped_object).append_page(
279                                  (global::Gtk.Notebook) bodies.get(i).wrapped_object,
280                                  (labels.size > i - 1) ? 
281                                         (global::Gtk.Notebook) labels.get(i).wrapped_object :
282                                         null
283                                         
284                         );
285                         
286                         
287                 }
288                 
289                 
290         }
291         
292         
293         /**
294          * called after the this.object  has been created
295          * and it needs to be packed onto parent.
296          */
297         public void packParent() 
298         {
299                 var cls = this.node.fqn().replace(".", "");
300                 
301                 var gtkbuilder = new global::Gtk.Builder();
302                 var cls_gtype = gtkbuilder.get_type_from_name(cls);
303
304                 if (this.parentObj == null) {
305                         return;
306                 }
307                                 
308                     
309                 var parent = this.parentObj.wrapped_object;
310                 
311                 var do_pack =true;
312
313                 if (parent == null) { // no parent.. can not pack.
314                         return; /// 
315                 }
316                 // -------------  handle various special parents .. -----------
317                 
318                 var par_type = this.parentObj.node.fqn().replace(".", "");
319                 
320                 if (par_type == "GtkNotebook") {
321                         // do not pack - it's done afterwards...
322                         return;
323                 }
324                 
325                 // -------------  handle various child types.. -----------
326                 // our overrides
327                 if (cls == "GtkMenu") {
328                         this.packMenu();
329                         return;
330                 }
331
332                 if (cls == "GtkTreeStore") { // other stores?
333                         // tree store is buildable??? --- 
334                         this.packTreeStore();
335                         return;
336                 }
337                 if (cls =="GtkTreeViewColumn") { // other stores?
338                         //?? treeview column is actually buildable -- but we do not use the builder???
339                         this.packTreeViewColumn();
340                         return;
341                 }
342                 if (cls_gtype.is_a(typeof(global::Gtk.CellRenderer))) { // other stores?
343                         this.packCellRenderer();
344                         return;
345                 }
346
347
348                 
349                 // -- handle buildable add_child..
350                 if (    cls_gtype.is_a(typeof(global::Gtk.Buildable))
351                      && 
352                         parent.get_type().is_a(typeof(global::Gtk.Buildable))
353                 )
354                 {
355                         ((global::Gtk.Buildable)parent).add_child(gtkbuilder, 
356                                                   this.wrapped_object, null);
357                         return;
358                 }
359                 // other packing?
360
361                 
362
363         }
364
365         public void packMenu()
366         {
367
368
369                 var parent = this.parentObj.wrapped_object;
370                 if (!parent.get_type().is_a(typeof(global::Gtk.Widget))) {
371                         print("skip menu pack - parent is not a widget");
372                         return;
373                 }
374                 
375                 var p = (global::Gtk.Menu)this.wrapped_object;
376                 ((global::Gtk.Widget)parent).button_press_event.connect((s, ev) => { 
377                         p.set_screen(Gdk.Screen.get_default());
378                         p.show_all();
379                         p.popup(null, null, null, ev.button, ev.time);
380                         return true;
381                 });
382         }
383
384         public void packTreeStore()
385         {
386                 var parent = this.parentObj.wrapped_object;
387                 if (!parent.get_type().is_a(typeof(global::Gtk.TreeView))) {
388                         print("skip treestore pack - parent is not a treeview");
389                         return;
390                 }
391                 ((global::Gtk.TreeView)parent).set_model((global::Gtk.TreeModel)this.wrapped_object);
392                 
393         }
394         public void packTreeViewColumn()
395         {
396                 var parent = this.parentObj.wrapped_object;
397                 if (!parent.get_type().is_a(typeof(global::Gtk.TreeView))) {
398                         print("skip packGtkViewColumn pack - parent is not a treeview");
399                         return;
400                 }
401                 ((global::Gtk.TreeView)parent).append_column((global::Gtk.TreeViewColumn)this.wrapped_object);
402                 // init contains the add_attribute for what to render...
403                 
404         }       
405
406
407         public void packCellRenderer()
408         {
409                 var parent = this.parentObj.wrapped_object;
410                 if (!parent.get_type().is_a(typeof(global::Gtk.TreeViewColumn))) {
411                         print("skip packGtkViewColumn pack - parent is not a treeview");
412                         return;
413                 }
414                 ((global::Gtk.TreeViewColumn)parent).pack_start((global::Gtk.CellRenderer)this.wrapped_object, false);
415                 // init contains the add_attribute for what to render...
416                 
417         }       
418
419
420         public void packContainerParams()
421         {
422          
423                 if (this.parentObj == null) {
424                         return;
425                 }
426                 // child must be a widget..
427                 if (!this.wrapped_object.get_type().is_a(typeof(global::Gtk.Widget))) {
428                         return;
429                 }
430                 
431                 var parent_gir = Palete.Gir.factoryFqn(this.parentObj.node.fqn());
432
433                 var parent = this.parentObj.wrapped_object;
434                 
435                 if (parent_gir == null) {
436                         return;
437                 }
438                 
439                 // let's test just setting expand to false...
440                 var cls_methods = parent_gir.methods;
441                 if (cls_methods == null) {
442                         return;
443                 }
444         
445                 if (!this.node.props.has_key("* pack") || 
446                                 this.node.props.get("* pack").length < 1) {
447                         return;
448                 }
449                 
450                 var ns = this.parentObj.node.fqn().split(".")[0];
451                  
452                 var pack = this.node.props.get("* pack").split(",");
453
454         
455                 if (cls_methods.has_key(pack[0])) {
456                         var mparams = cls_methods.get(pack[0]).paramset.params;
457                         for (var i = 1; i < mparams.size; i++ ) {
458                                 if (i > (pack.length -1)) {
459                                         continue;
460                                 }
461                         
462                                 var k = mparams.get(i).name;
463
464                                 Value cur_val;
465                                  
466                                 var type = mparams.get(i).type;
467                                 type = Palete.Gir.fqtypeLookup(type, ns);
468
469                                 var val = this.toValue(pack[i].strip(), type);
470                                 if (val == null) {
471                                         print("skip (failed to transform value %s type = %s from %s\n", 
472                                                 this.parentObj.node.fqn()  + "." + k, type, pack[i].strip());
473                                         continue;
474                                 }
475                                 print ("pack:set_property ( %s , %s / %s)\n", k, pack[i].strip(), val.strdup_contents());
476         
477                                 ((global::Gtk.Container)parent).child_set_property(
478                                         (global::Gtk.Widget)this.wrapped_object , k, val);
479                                  
480                         }
481                 
482                 }
483         
484
485
486                         
487         }
488                    
489
490         public GLib.Value? toValue(string val, string type) {
491
492                 
493                 /*
494                 if (type == "string") {
495                         var qret = GLib.Value(typeof(string));
496                         qret.set_string(val);
497                         return qret;
498                 }
499                 * */
500                 /*
501                  * 
502            * var gtkbuilder = new global::Gtk.Builder();
503                 var prop_gtype = gtkbuilder.get_type_from_name(type);
504                 
505
506                 if (prop_gtype == GLib.Type.INVALID) {
507                          
508                         return null;
509                 }
510                 */
511                 
512                  
513
514
515                 switch(type) {
516                         case "bool":
517                                 var ret = GLib.Value(typeof(bool));
518                                 ret.set_boolean(val.down() == "false" ? false : true);
519                                 return ret;
520                                 
521                         case "uint":
522                                 var ret = GLib.Value(typeof(uint));
523                                 ret.set_uint(int.parse(val));
524                                 return ret;
525                                 
526                         case "int":
527                                 var ret = GLib.Value(typeof(int));
528                                 ret.set_int(int.parse(val));
529                                 return ret;
530
531                         // uint64 ...??
532
533                         case "long":
534                                 var ret = GLib.Value(typeof(long));
535                                 ret.set_long((long)int64.parse(val));
536                                 return ret;
537                         
538                         case "ulong":
539                                 var ret = GLib.Value(typeof(ulong));
540                                 ret.set_ulong((ulong) uint64.parse(val));
541                                 return ret;
542
543                         case "float":
544                                 var ret = GLib.Value(typeof(float));
545                                 ret.set_float((float)double.parse(val));
546                                 return ret;
547                                 
548                         case "string":
549                                 var ret = GLib.Value(typeof(string));
550                                 ret.set_string(val);
551                                 return ret;
552
553                         default:
554                                 return null;
555                                 /*
556                                 var sval =  GLib.Value(typeof(string));
557                                 sval.set_string(val);
558                         
559                                 if (!sval.transform(ref ret)) {
560                                 
561                                         return null;
562                                 }
563                                 return ret;
564                                 */
565                 }
566                 // should not get here..
567                 return null;
568         }
569         
570          
571           
572                 
573 }