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