src/JsRender/Node.vala
[app.Builder.js] / src / JsRender / Node.vala
1
2 // test..
3 // valac gitlive/app.Builder.js/JsRender/Lang.vala gitlive/app.Builder.js/JsRender/Node.vala --pkg gee-1.0 --pkg=json-glib-1.0 -o /tmp/Lang ;/tmp/Lang
4
5
6 /*
7  * 
8  * props:
9  * 
10  * key value view of properties.
11  * 
12  * Old standard..
13  * XXXXX : YYYYY  -- standard - should be rendered as XXXX : "YYYY" usually.
14  * |XXXXX : YYYYY  -- standard - should be rendered as XXXX : YYYY usually.
15  * |init  -- the initialization...
16  * *prop : a property which is actually an object definition... 
17  * *args : contructor args
18  * .ctor : Full contruct line...  
19  * 
20  * Newer code
21  * ".Gee.ArrayList<Xcls_fileitem>:fileitems" ==> # type  name 
22  * ".signal:void:open": "(JsRender.JsRender file)" ==> @ type name
23  *  "|void:clearFiles": "() .... some code...."  | type name
24  *
25  * 
26  * 
27  * 
28  * 
29  * Standardize this crap...
30  * 
31  * standard properties (use to set)
32  *          If they are long values show the dialog..
33  * 
34  * bool is_xxx  :: can show a pulldown.. (true/false)
35  * string html  
36  * $ string html  = string with value interpolated eg. baseURL + ".." 
37  *  Clutter.ActorAlign x_align  (typed)  -- shows pulldowns if type is ENUM? 
38  * $ untypedvalue = javascript untyped value... 
39  * 
40  * object properties (not part of the GOjbect being wrapped?
41  * # Gee.ArrayList<Xcls_fileitem> fileitems
42  * 
43  * signals
44  * @ void open 
45  * 
46  * methods -- always text editor..
47  * | void clearFiles
48  * | someJSmethod
49  * 
50  * specials
51  * * prop -- string
52  * * args  -- string
53  * * ctor -- string
54  * * init -- big string?
55  * 
56  * event handlers (listeners)
57  *   just shown 
58  * 
59  * -----------------
60  * special ID values
61  *  +XXXX -- indicates it's a instance property / not glob...
62  *  *XXXX -- skip writing glob property (used as classes that can be created...)
63  *  _XXXX -- (string) a translatable string.
64  * 
65  * 
66  *  FORMATING?
67 .method {
68          color : green;
69          font-weight: bold;      
70 }
71 .prop {
72         color : #333;
73 }
74 .prop-code {
75     font-style: italic;
76  }
77 .listener {
78     color: #600;
79     font-weight: bold;   
80 }
81 .special { 
82   color : #00c;    font-weight: bold;    
83
84
85 */
86
87
88
89
90
91
92 public class JsRender.Node : Object {
93         
94
95         public static int uid_count = 0;
96         
97         public Node parent;
98         public Gee.ArrayList<Node> items; // child items..
99         
100         public Gee.HashMap<string,string> props; // the properties..
101         public Gee.HashMap<string,string> listeners; // the listeners..
102         public string  xvala_cls;
103         public string xvala_xcls; // 'Xcls_' + id;
104         public string xvala_id; // item id or ""
105         
106         // line markers..
107         public int line_start;
108         public int line_end;
109         public Gee.ArrayList<int> lines;
110         public Gee.HashMap<int,string> line_map; // store of l:xxx or p:....
111         public Gee.ArrayList<int> node_lines;
112         public Gee.HashMap<int,Node> node_lines_map; // store of l:xxx or p:....
113         
114
115         public Node()
116         {
117                 this.items = new Gee.ArrayList<Node>();
118                 this.props = new Gee.HashMap<string,string>();
119                 this.listeners = new Gee.HashMap<string,string>();
120                 this.xvala_cls = "";
121                 this.xvala_xcls = "";
122                 this.xvala_id = "";
123                 this.parent = null;
124                 this.line_start = -1;
125                 this.line_end = -1;             
126                 this.lines = new Gee.ArrayList<int>();
127                 this.line_map = new Gee.HashMap<int,string>();
128                 this.node_lines = new Gee.ArrayList<int>();
129                 this.node_lines_map = new Gee.HashMap<int,Node>();
130                 
131         }
132         
133         public void setNodeLine(int line, Node node) {
134                 //print("Add node @ %d\n", line);
135                 if (this.node_line_map.has_key(line)) {
136                         return;
137                 }
138                 this.node_lines.add(line);
139                 this.node_lines_map.set(line, node);
140                 
141         }
142         
143         public void setLine(int line, string type, string prop) {
144                 this.lines.add(line);
145                 this.line_map.set(line, type + ":" + prop);
146                 GLib.debug("setLine %d, %s", line, type + ":" + prop);
147         }
148         public void sortLines() {
149                 //print("sortLines\n");
150                 this.lines.sort((a,b) => {   
151                         return (int)b-(int)a;
152                 });
153                 this.node_lines.sort((a,b) => {   
154                         return (int)a-(int)b;
155                 });
156         }
157         public Node? lineToNode(int line)
158         {
159                 //print("Searching for line %d\n",line);
160                 var l = -1;
161                 //foreach(int el in this.node_lines) {
162                         //print("all lines %d\n", el);
163                 //}
164                 
165                 
166                 foreach(int el in this.node_lines) {
167                         //print("?match %d\n", el);
168                         if (el < line) {
169                                 
170                                 l = el;
171                                 //print("LESS\n");
172                                 continue;
173                         }
174                         if (el == line) {
175                                 //print("SAME\n");
176                                 l = el;
177                                 break;
178                         }
179                         if (l > -1) {
180                                 //print("RETURNING NODE ON LINE %d", l);
181                                 return this.node_lines_map.get(l);
182                         }
183                         return null;
184                         
185                 }
186                 if (l > -1) {
187                         //print("RETURNING NODE ON LINE %d", l);
188                         return this.node_lines_map.get(l);
189                 }
190                 return null;
191                 
192         }
193         public string lineToProp(int line)
194         {
195                 // assume lineToNode called first...
196                 var l = -1;
197                 //foreach(int el in this.lines) {
198                 //      //print("all lines %d\n", el);
199                 //
200                 
201                 
202                 foreach(int el in this.lines) {
203                         //print("?match %d\n", el);
204                         if (el < line) {
205                                 
206                                 l = el;
207                                 //print("LESS\n");
208                                 continue;
209                         }
210                         if (el == line) {
211                                 //print("SAME\n");
212                                 l = el;
213                                 break;
214                         }
215                         if (l > -1) {
216                                 //print("RETURNING NODE ON LINE %d", l);
217                                 return this.line_map.get(l);
218                         }
219                         return null;
220                         
221                 }
222                 if (l > -1) {
223                         //print("RETURNING NODE ON LINE %d", l);
224                         return this.line_map.get(l);
225                 }
226                 return null;
227         
228         }
229         public void dumpProps(string indent = "")
230         {
231                 print("%s:\n" , this.fqn());
232                 foreach(int el in this.lines) {
233                         print("%d: %s%s\n", el, indent, this.line_map.get(el));
234                 }
235                 foreach(Node n in this.items) {
236                         n.dumpProps(indent + "  ");
237                 }
238         }
239         
240         
241         
242         public string uid()
243         {
244                 if (this.props.get("id") == null) {
245                         uid_count++;
246                         return "uid-%d".printf(uid_count);
247                 }
248                 return this.props.get("id");
249         }
250         
251         
252         public bool hasChildren()
253         {
254                 return this.items.size > 0;
255         }
256         public bool hasXnsType()
257         {
258                 if (this.props.get("$ xns") != null && this.props.get("xtype") != null) {
259                         return true;
260                         
261                 }
262                 return false;
263         }
264         public string fqn()
265         {
266                 if (!this.hasXnsType ()) {
267                         return "";
268                 }
269                 return this.props.get("$ xns") + "." + this.props.get("xtype"); 
270
271         }
272         public void setFqn(string name)
273         {
274                 var ar = name.split(".");
275                 this.props.set("xtype", ar[ar.length-1]);
276                 var l = name.length - (ar[ar.length-1].length +1);
277                 this.props.set("$ xns", name.substring(0, l));
278                 //print("setFQN %s to %s\n", name , this.fqn());
279                                
280
281         }
282         // wrapper around get props that returns empty string if not found.
283         public string get(string key)
284         {
285                 var k = this.props.get(key);
286                 if (k != null) {
287                         return k;
288                 }
289                 
290                 k = this.props.get("$ " + key);
291                 if (k != null) {
292                         return k;
293                 }
294                 
295                 var iter = this.props.map_iterator();
296                 while (iter.next()) {
297                         var kk = iter.get_key().split(" ");
298                         if (kk[kk.length-1] == key) {
299                                 return iter.get_value();
300                         }
301                 }
302                 
303                 
304                 return "";
305                 
306         }
307         
308         public string get_key(string key)
309         {
310                 var k = this.props.get(key);
311                 if (k != null) {
312                         return key;
313                 }
314                 
315                 k = this.props.get("$ " + key);
316                 if (k != null) {
317                         return "$ " + key;
318                 }
319                 
320                 var iter = this.props.map_iterator();
321                 while (iter.next()) {
322                         var kk = iter.get_key().split(" ");
323                         if (kk[kk.length-1] == key) {
324                                 return iter.get_key();
325                         }
326                 }
327                 
328                 
329                 return "";
330                 
331         }
332         public void normalize_key(string key, out string kname, out string kflag, out string ktype)
333         {
334                 // key formats : XXXX
335                 // XXX - plain
336                 // string XXX - with type
337                 // $ XXX - with flag (no type)
338                 // $ string XXX - with flag
339                 kname = "";
340                 ktype = ""; // these used to contain '-' ???
341                 kflag = ""; // these used to contain '-' ???
342                 var kkv = key.strip().split(" ");
343                 string[] kk = {};
344                 for (var i = 0; i < kkv.length; i++) {
345                         if (kkv[i].length > 0 ) {
346                                 kk+= kkv[i];
347                         }
348                 }
349                 //print("normalize %s => %s\n", key,string.joinv("=:=",kk));
350                 
351                 switch(kk.length) {
352                         case 1: 
353                                 kname = kk[0];
354                                 return;
355                         case 2: 
356                                 kname = kk[1];
357                                 if (kk[0].length > 1) {
358                                         ktype = kk[0];
359                                 } else {
360                                         kflag = kk[0];
361                                 }
362                                 return;
363                         case 3:
364                                 kname = kk[2];
365                                 kflag = kk[0];
366                                 ktype = kk[1];
367                                 return;
368                 }
369                 // everything blank otherwise...
370         }
371         public void set(string key, string value) {
372                 this.props.set(key,value);
373         }
374          public bool has(string key)
375         {
376                 var k = this.props.get(key);
377                 if (k != null) {
378                         return true;
379                 }
380                 var iter = this.props.map_iterator();
381                 while (iter.next()) {
382                         var kk = iter.get_key().strip().split(" ");
383                         if (kk[kk.length-1] == key) {
384                                 return true;
385                         }
386                 }
387                 
388                 return false;
389                 
390         }
391
392         public void  remove()
393         {
394                 if (this.parent == null) {
395                         
396                         
397                         return;
398                 }
399                 var nlist = new Gee.ArrayList<Node>();
400                 for (var i =0;i < this.parent.items.size; i++) {
401                         if (this.parent.items.get(i) == this) {
402                                 continue;
403                         }
404                         nlist.add(this.parent.items.get(i));
405                 }
406                 this.parent.items = nlist;
407                 this.parent = null;
408
409         }
410          
411         /* creates javascript based on the rules */
412         public Node? findProp(string n) {
413                 for(var i=0;i< this.items.size;i++) {
414                         var p = this.items.get(i).get("* prop");
415                         if (this.items.get(i).get("* prop").length < 1) {
416                                 continue;
417                         }
418                         if (p == n) {
419                                 return this.items.get(i);
420                         }
421                 }
422                 return null;
423
424         }
425
426         
427         
428          
429         static Json.Generator gen = null;
430         
431         public string quoteString(string str)
432         {
433                 if (Node.gen == null) {
434                         Node.gen = new Json.Generator();
435                 }
436                  var n = new Json.Node(Json.NodeType.VALUE);
437                 n.set_string(str);
438  
439                 Node.gen.set_root (n);
440                 return  Node.gen.to_data (null);   
441         }
442
443         public void loadFromJson(Json.Object obj, int version) {
444                 obj.foreach_member((o , key, value) => {
445                         //print(key+"\n");
446                         if (key == "items") {
447                                 var ar = value.get_array();
448                                 ar.foreach_element( (are, ix, el) => {
449                                         var node = new Node();
450                                         node.parent = this;
451                                         node.loadFromJson(el.get_object(), version);
452                                         this.items.add(node);
453                                 });
454                                 return;
455                         }
456                         if (key == "listeners") {
457                                 var li = value.get_object();
458                                 li.foreach_member((lio , li_key, li_value) => {
459                                         this.listeners.set(li_key, li_value.get_string());
460
461                                 });
462                                 return;
463                         }
464                         var v = value.get_value();
465                         var sv =  Value (typeof (string));
466                         v.transform(ref sv);
467
468                         var rkey = key;
469                         if (version == 1) {
470                                 rkey = this.upgradeKey(key, (string)sv);
471                         }
472
473                         
474                         this.props.set(rkey,  (string)sv);
475                 });
476                 
477
478
479
480         }
481
482         public string upgradeKey(string key, string val)
483         {
484                 // convert V1 to V2
485                 if (key.length < 1) {
486                         return key;
487                 }
488                 switch(key) {
489                         case "*prop":
490                         case "*args":
491                         case ".ctor":
492                         case "|init":
493                                 return "* " + key.substring(1);
494                                 
495                         case "pack":
496                                 return "* " + key;
497                 }
498                 if (key[0] == '.') { // v2 does not start with '.' ?
499                         var bits = key.substring(1).split(":");
500                         if (bits[0] == "signal") {
501                                 return "@" + string.joinv(" ", bits).substring(bits[0].length);
502                         }
503                         return "# " + string.joinv(" ", bits);                  
504                 }
505                 if (key[0] != '|' || key[1] == ' ') { // might be a v2 file..
506                         return key;
507                 }
508                 var bits = key.substring(1).split(":");
509                 // two types '$' or '|' << for methods..
510                 // javascript 
511                 if  (Regex.match_simple ("^function\\s*(", val.strip())) {
512                         return "| " + key.substring(1);
513                 }
514                 // vala function..
515                 
516                 if  (Regex.match_simple ("^\\(", val.strip())) {
517                 
518                         return "| " + string.joinv(" ", bits);
519                 }
520                 
521                 // guessing it's a property..
522                 return "$ " + string.joinv(" ", bits);
523                 
524                 
525
526         }
527
528
529
530
531
532         
533         public Node  deepClone()
534         {
535                 var n = new Node();
536                 n.loadFromJson(this.toJsonObject(), 2);
537                 return n;
538
539         }
540         public string toJsonString()
541         {
542                 if (Node.gen == null) {
543                         Node.gen = new Json.Generator();
544                         gen.pretty =  true;
545                         gen.indent = 1;
546                 }
547                 var n = new Json.Node(Json.NodeType.OBJECT);
548                 n.set_object(this.toJsonObject () );
549                 Node.gen.set_root (n);
550                 return  Node.gen.to_data (null);   
551         }
552         
553         public Json.Object toJsonObject()
554         {
555                 var ret = new Json.Object();
556
557                 // listeners...
558                 if (this.listeners.size > 0) {
559                         var li = new Json.Object();
560                         ret.set_object_member("listeners", li);
561                         var liter = this.listeners.map_iterator();
562                         while (liter.next()) {
563                                 li.set_string_member(liter.get_key(), liter.get_value());
564                         }
565                 }
566                 //props
567                 if (this.props.size > 0 ) {
568                         var iter = this.props.map_iterator();
569                         while (iter.next()) {
570                                 this.jsonObjectsetMember(ret, iter.get_key(), iter.get_value());
571                         }
572                 }
573                 if (this.items.size > 0) {
574                         var ar = new Json.Array();
575                         ret.set_array_member("items", ar);
576                 
577                         // children..
578                         for(var i =0;i < this.items.size;i++) {
579                                 ar.add_object_element(this.items.get(i).toJsonObject());
580                         }
581                 }
582                 return ret;
583                 
584  
585         }
586          
587         public void jsonObjectsetMember(Json.Object o, string key, string val) {
588                 if (Lang.isBoolean(val)) {
589                         o.set_boolean_member(key, val.down() == "false" ? false : true);
590                         return;
591                 }
592                 
593                 
594                 if (Lang.isNumber(val)) {
595                         if (val.contains(".")) {
596                                 //print( "ADD " + key + "=" + val + " as a double?\n");
597                                 o.set_double_member(key, double.parse (val));
598                                 return;
599
600                         }
601                         //print( "ADD " + key + "=" + val + " as a int?\n")  ;
602                         o.set_int_member(key,long.parse(val));
603                         return;
604                 }
605                 ///print( "ADD " + key + "=" + val + " as a string?\n");
606                 o.set_string_member(key,val);
607                 
608         }
609         public string nodeTip()
610         {
611                 var ret = this.nodeTitle(true);
612                 var funcs = "";
613                 var props = "";
614                 var listen = "";
615                 var iter = this.props.map_iterator();
616                 while (iter.next()) {
617                         var i =  iter.get_key().strip();
618                         var val = iter.get_value().strip();
619                         if (val == null || val.length < 1) {
620                                 continue;
621                         }
622                         if ( i[0] != '|') {
623                                 props += "\n\t<b>" + 
624                                         GLib.Markup.escape_text(i) +"</b> : " + 
625                                         GLib.Markup.escape_text(val.split("\n")[0]);
626                                  
627                                 continue;
628                         }
629                 
630                         //if (i == "* init") { 
631                         //      continue;
632                         //}
633                         
634                         if (Regex.match_simple("^\\s*function", val)) { 
635                                 funcs += "\n\t<b>" + 
636                                         GLib.Markup.escape_text(i.substring(1)).strip() +"</b> : " + 
637                                         GLib.Markup.escape_text(val.split("\n")[0]);
638                                 continue;
639                         }
640                         if (Regex.match_simple("^\\s*\\(", val)) {
641                                 funcs += "\n\t<b>" + GLib.Markup.escape_text(i.substring(1)).strip() +
642                                         "</b> : " + 
643                                         GLib.Markup.escape_text(val.split("\n")[0]);
644                                 continue;
645                         }
646                         
647                 }
648                 iter = this.listeners.map_iterator();
649                 while (iter.next()) {
650                         var i =  iter.get_key().strip();
651                         var val = iter.get_value().strip();
652                         if (val == null || val.length < 1) {
653                                 continue;
654                         }
655                          listen += "\n\t<b>" + 
656                                         GLib.Markup.escape_text(i) +"</b> : " + 
657                                         GLib.Markup.escape_text(val.split("\n")[0]);
658                         
659                 }
660                 
661                 
662                 if (props.length > 0) {
663                         ret+="\n\nProperties:" + props;
664                 } 
665                 if (funcs.length > 0) {
666                         ret+="\n\nMethods:" + funcs;
667                 } 
668                 if (listen.length > 0) {
669                         ret+="\n\nListeners:" + listen;
670                 } 
671                 return ret;
672
673         }
674         public string nodeTitle(bool for_tip = false) {
675                 string[] txt = {};
676
677                 //var sr = (typeof(c['+buildershow']) != 'undefined') &&  !c['+buildershow'] ? true : false;
678                 //if (sr) txt.push('<s>');
679
680                 if (this.has("* prop"))   { txt += (GLib.Markup.escape_text(this.get("* prop")) + ":"); }
681                 
682                 //if (renderfull && c['|xns']) {
683                 var fqn = this.fqn();
684                 var fqn_ar = fqn.split(".");
685                 txt += for_tip || fqn.length < 1 ? fqn : fqn_ar[fqn_ar.length -1];
686                         
687                 //}
688                 
689                 //if (c.xtype)    { txt.push(c.xtype); }
690                         
691                 if (this.has("id"))      { txt += ("<b>[id=" + GLib.Markup.escape_text(this.get("id")) + "]</b>"); }
692                 if (this.has("fieldLabel")){ txt += ("[" + GLib.Markup.escape_text(this.get("fieldLabel")) + "]"); }
693                 if (this.has("boxLabel"))  { txt += ("[" + GLib.Markup.escape_text(this.get("boxLabel"))+ "]"); }
694                 
695                 
696                 if (this.has("layout")) { txt += ("<i>" + GLib.Markup.escape_text(this.get("layout")) + "</i>"); }
697                 if (this.has("title"))   { txt += ("<b>" + GLib.Markup.escape_text(this.get("title")) + "</b>"); }
698                 if (this.has("html") && this.get("html").length > 0)     { 
699                         var ht = this.get("html").split("\n");
700                         if (ht.length > 1) {
701                                 txt += ("<b>" + GLib.Markup.escape_text(ht[0]) + "...</b>");
702                         } else { 
703                                 txt += ("<b>" + GLib.Markup.escape_text(this.get("html")) + "</b>");
704                         }
705                 }
706                 if (this.has("label"))   { txt += ("<b>" + GLib.Markup.escape_text(this.get("label"))+ "</b>"); }
707                 if (this.has("header"))   { txt += ("<b>" + GLib.Markup.escape_text(this.get("header")) + "</b>"); }
708                 if (this.has("legend"))  { txt += ("<b>" + GLib.Markup.escape_text(this.get("legend")) + "</b>"); }
709                 if (this.has("text"))     { txt += ("<b>" + GLib.Markup.escape_text(this.get("text")) + "</b>"); }
710                 if (this.has("name"))     { txt += ("<b>" + GLib.Markup.escape_text(this.get("name"))+ "</b>"); }
711                 if (this.has("region")) { txt += ("<i>(" + GLib.Markup.escape_text(this.get("region")) + ")</i>"); }
712                 if (this.has("dataIndex")){ txt += ("[" + GLib.Markup.escape_text(this.get("dataIndex")) + "]"); }
713                 
714                 // for flat classes...
715                 //if (typeof(c["*class"]"))!= "undefined")  { txt += ("<b>" +  c["*class"]+  "</b>"); }
716                 //if (typeof(c["*extends"]"))!= "undefined")  { txt += (": <i>" +  c["*extends"]+  "</i>"); }
717                 
718                 
719                 //if (sr) txt.push('</s>');
720                 return (txt.length == 0) ? "Element" : string.joinv(" ", txt);
721         }
722
723 }