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