Fix #8003 - undo code
[roobuilder] / 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 : GLib.Object {
93         
94
95         public static int uid_count = 0;
96         
97         public int oid { get; private set; }
98         public Node parent;
99         private Gee.ArrayList<Node> items; // child items..
100         public GLib.ListStore  childstore; // must be kept in sync with items
101         public GLib.ListStore?  propstore; // must be kept in sync with items
102         public string  xvala_cls; // set by node to vala
103         public string xvala_xcls; // 'Xcls_' + id; // set by nodetoVala
104         public string xvala_id; // item id or "" // set by nodetovala
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:....  // fixme - not needed as we can store line numbers in props now.
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 string node_pad = "";
116         
117         private int _updated_count = 0;
118         public int updated_count { 
119                 get {
120                         return this._updated_count; 
121                 }
122                 set  {
123                         this.nodeTitleProp = ""; // ?? should trigger set?
124                         this.iconFilename = "";
125                         this._updated_count = value;
126  
127                                 
128                         //GLib.debug("Update Node %d p%d - rev %d", this.oid, this.parent != null ? this.parent.oid : -1, value);
129                         if (this.parent != null) {
130                                 this.parent.updated_count++;
131                         }  else {
132                                 //GLib.debug("UNDO top node is %d", value);
133                                 this.version_changed();
134                         }
135                 }
136  
137         } // changes to this trigger updates on the tree..
138         
139         public string as_source = "";
140         public int as_source_version = -1;
141         
142         public signal void  version_changed();
143         
144         public Node()
145         {
146                 this.items = new Gee.ArrayList<Node>();
147                 //this._props = new Gee.HashMap<string,NodeProp>();
148                 //this._listeners = new Gee.HashMap<string,NodeProp>(); // Nodeprop can include line numbers..
149                 this.propstore = new GLib.ListStore(typeof(NodeProp)); // Nodeprop can include line numbers..
150                 this.xvala_cls = "";
151                 this.xvala_xcls = "";
152                 this.xvala_id = "";
153                 this.parent = null;
154                 this.line_start = -1;
155                 this.line_end = -1;             
156                 this.lines = new Gee.ArrayList<int>();
157                 this.line_map = new Gee.HashMap<int,string>();
158                 this.node_lines = new Gee.ArrayList<int>();
159                 this.node_lines_map = new Gee.HashMap<int,Node>();
160                 this.childstore = new GLib.ListStore( typeof(Node));
161                 this.oid = uid_count++;
162                 
163         }
164         
165         public bool has_parent(Node n) 
166         {
167                 if (this.parent == null) {
168                         return false;
169                 }
170                 if (this.parent.oid == n.oid) {
171                         return true;
172                 }
173                 
174
175                 return this.parent.has_parent(n);
176         }
177         
178         
179         public  Gee.ArrayList<Node> readItems()
180         {
181                 return this.items; // note should not modify add/remove from this directly..
182                 
183         }
184         public void setNodeLine(int line, Node node) {
185                 //print("Add node @ %d\n", line);
186                 if (this.node_lines_map.has_key(line)) {
187                         return;
188                 }
189                 this.node_lines.add(line);
190                 this.node_lines_map.set(line, node);
191                 
192         }
193         
194         public void setLine(int line, string type, string prop) 
195         {
196                 //GLib.debug("set prop %s (%s) to line %d", prop, type, line);
197                 if (this.line_map.has_key(line)) {
198                         if  (this.line_map.get(line) != "e:"  ) {
199                                 return;
200                         }
201                 } else {
202                         this.lines.add(line);
203                 }
204                 this.line_map.set(line, type + ":" + prop);
205                 if (type == "e" || type == "p" ) {
206                 
207                         if (prop == "" || !this.props.has_key(prop)) {
208                                 ///GLib.debug("cant find prop '%s'", prop);
209                                 return;
210                         }
211                         
212                         var prope = this.props.get(prop);
213                         if (prope != null && type =="p") { 
214                                 prope.start_line = line;
215                         }
216                         if (prope != null && type =="e") { 
217                                 prope.end_line = line;
218                         }       
219                         
220                 }
221                 if (type == "l" || type =="x") {
222                         if (prop == "" || !this.listeners.has_key(prop)) {
223                                 //GLib.debug("cant find listener '%s'", prop);
224                                 return;
225                         }
226                         
227                         var prope = this.listeners.get(prop);
228                         if (prope != null && type =="l") { 
229                                 prope.start_line = line;
230                         }
231                         if (prope != null && type =="x") { 
232                                 prope.end_line = line;
233                         }       
234                         
235                 
236                 }
237                 
238                 
239                 
240                 
241                 //GLib.debug("setLine %d, %s", line, type + ":" + prop);
242         }
243         public void sortLines() {
244                 //print("sortLines\n");
245                 this.lines.sort((a,b) => {   
246                         return (int)a-(int)b;
247                 });
248                 this.node_lines.sort((a,b) => {   
249                         return (int)a-(int)b;
250                 });
251         }
252         public Node? lineToNode(int line)
253         {
254                 //print("Searching for line %d\n",line);
255                 var l = -1;
256                 //foreach(int el in this.node_lines) {
257                         //print("all lines %d\n", el);
258                 //}
259                 
260                 
261                 foreach(int el in this.node_lines) {
262                         //print("?match %d\n", el);
263                         if (el < line) {
264                                 
265                                 l = el;
266                                 //print("LESS\n");
267                                 continue;
268                         }
269                         if (el == line) {
270                                 //print("SAME\n");
271                                 l = el;
272                                 break;
273                         }
274                         if (l > -1) {
275                                 var ret = this.node_lines_map.get(l);
276                                 if (line > ret.line_end) {
277                                         return null;
278                                 }
279                                 //print("RETURNING NODE ON LINE %d", l);
280                                 return ret;
281                         }
282                         return null;
283                         
284                 }
285                 if (l > -1) {
286                         var ret = this.node_lines_map.get(l);
287                         if (line > ret.line_end) {
288                                 return null;
289                         }
290                         //print("RETURNING NODE ON LINE %d", l);
291                         return ret;
292
293                 }
294                 return null;
295                 
296         }
297         
298         
299         public NodeProp? lineToProp(int line)
300         {
301                 
302                 for(var i= 0; i < this.propstore.get_n_items();i++) {
303                         var p = (NodeProp) this.propstore.get_item(i);
304                         GLib.debug("prop %s lines %d -> %d", p.name, p.start_line, p.end_line);
305                         if (p.start_line > line) {
306                                 continue;
307                         }
308                         if (line > p.end_line) {
309                                 continue;
310                         }
311                         return p;
312                 }
313                 return null;
314         }
315                 
316                  
317         
318         public bool getPropertyRange(string prop, out int start, out int end)
319         {
320                 end = 0;
321                 start = -1;
322                 foreach(int el in this.lines) {
323                         if (start < 0) {
324                                 if (this.line_map.get(el) == prop) {
325                                         start = el;
326                                         end = el;
327                                 }
328                                 continue;
329                         }
330                         end = el -1;
331                         break;
332                 }
333                 return start > -1;
334         
335         
336         }
337         
338         public void dumpProps(string indent = "")
339         {
340                 print("%s:\n" , this.fqn());
341                 foreach(int el in this.lines) {
342                         print("%d: %s%s\n", el, indent, this.line_map.get(el));
343                 }
344                 foreach(Node n in this.items) {
345                         n.dumpProps(indent + "  ");
346                 }
347         }
348         
349         
350         
351         public string uid()
352         {
353                 if (this.props.get("id") == null) {
354                         return "uid-%d".printf(this.oid);
355                 }
356                 return this.props.get("id").val;
357         }
358         
359         
360         public bool hasChildren()
361         {
362                 return this.items.size > 0;
363         }
364         public bool hasXnsType()
365         {
366                 if (this.props.get("xns") != null && this.props.get("xtype") != null) {
367                         return true;
368                         
369                 }
370                 return false;
371         }
372         
373         public string FQN { // for sorting
374                 owned get { return this.fqn(); }
375                 private set  {}
376         }
377         public string NS { // for sorting
378                 owned get { return this.props.has_key("xns") ? this.props.get("xns").val  : ""; }
379                 private set  {}
380         }
381         public string fqn()
382         {
383                 if (!this.hasXnsType ()) {
384                         return "";
385                 }
386                 return this.props.get("xns").val + "." + this.props.get("xtype").val; 
387
388         }
389         public void setFqn(string name)
390         {
391                 var ar = name.split(".");
392                 var l = name.length - (ar[ar.length-1].length +1);
393                 
394
395                 
396                 if (this.props.has_key("xtype")) {
397                         this.props.get("xtype").val = ar[ar.length-1];
398                 } else {
399                         this.add_prop(new NodeProp.prop("xtype", "",  ar[ar.length-1]));                
400                 }       
401                 if (this.props.has_key("xns")) {
402                         this.props.get("xns").val = name.substring(0, l);
403                 } else {
404                         this.add_prop(new NodeProp.raw("xns", "", name.substring(0, l)));               
405                 }       
406                 
407                 
408                 //print("setFQN %s to %s\n", name , this.fqn());
409                                
410
411         }
412         // wrapper around get props that returns empty string if not found.
413         //overrides Glib.object.get (hence new)
414         public new string get(string key)
415         {
416                 
417                 var v = this.props.get(key);
418                 return v == null ? "" : v.val;
419         }       
420                  
421         public  NodeProp? get_prop(string key)
422         {
423                 
424                 return this.props.get(key);
425                 
426         }
427         
428  
429         
430
431
432         public bool has(string key)
433         {
434                 return this.props.has_key(key);
435                  
436          
437         }
438
439         public void  remove()
440         {
441                 if (this.parent == null) {
442                         GLib.debug("remove - parent is null?");
443                         return;
444                 }
445                 var nlist = new Gee.ArrayList<Node>();
446                 for (var i =0;i < this.parent.items.size; i++) {
447                         if (this.parent.items.get(i) == this) {
448                                 continue;
449                         }
450                         nlist.add(this.parent.items.get(i));
451                 }
452                 uint pos;
453                 if ( this.parent.childstore.find(this, out pos)) {
454                         this.parent.childstore.remove(pos);
455                 } 
456                 this.parent.updated_count++;
457                 this.parent.items = nlist;
458                 this.parent = null;
459
460         }
461          
462         /* creates javascript based on the rules */
463         public Node? findProp(string n) {
464                 for(var i=0;i< this.items.size;i++) {
465                         var p = this.items.get(i).get("* prop");
466                         if (p  == null) {
467                                 continue;
468                         }
469                         if (p == n) {
470                                 return this.items.get(i);
471                         }
472                 }
473                 return null;
474
475         }
476
477         
478         
479          
480         static Json.Generator gen = null;
481         
482         public string quoteString(string str)
483         {
484                 if (Node.gen == null) {
485                         Node.gen = new Json.Generator();
486                 }
487                  var n = new Json.Node(Json.NodeType.VALUE);
488                 n.set_string(str);
489  
490                 Node.gen.set_root (n);
491                 return  Node.gen.to_data (null);   
492         }
493
494         public void loadFromJsonString(string str, int ver)
495         {
496                 var pa = new Json.Parser();
497                 try {
498                         pa.load_from_data(str);
499                 } catch (GLib.Error e) {
500                         GLib.debug("Error loading string?");
501                         return;
502                 }
503                 var new_node = pa.get_root();
504                 var obj = new_node.get_object ();
505                      
506                 this.loadFromJson(obj, ver);
507         }
508         
509  
510
511         public void loadFromJson(Json.Object obj, int version) {
512                  
513                 obj.foreach_member((o , key, value) => {
514                         //print(key+"\n");
515                         if (key == "items") {
516                                 var ar = value.get_array();
517                                 ar.foreach_element( (are, ix, el) => {
518                                         var node = new Node();
519                                         node.parent = this;
520                                         node.loadFromJson(el.get_object(), version);
521                                         this.items.add(node);
522                                         this.childstore.append(node);
523                                 });
524                                 return;
525                         }
526                         if (key == "listeners") {
527                                 var li = value.get_object();
528                                 li.foreach_member((lio , li_key, li_value) => {
529                                         this.add_prop(new NodeProp.listener(li_key, this.jsonNodeAsString(li_value)));
530                                         //this.listeners.set(li_key,  new NodeProp.listener(li_key, this.jsonNodeAsString(li_value)));
531                                 });
532                                 return;
533                         }
534                         
535
536                         var rkey = key;
537                         var sval = this.jsonNodeAsString(value);
538                 
539                         if (version == 1) {
540                                 rkey = this.upgradeKey(key, sval);
541                         }
542                         var n =  new NodeProp.from_json(rkey, sval);
543                                 
544                         this.add_prop(n );
545
546
547                 });
548                 
549                 
550                 
551
552
553
554         }
555         
556         // converts the array into a string with line breaks.
557         public string jsonNodeAsString(Json.Node node)
558         {
559                 
560                 if (node.get_node_type() == Json.NodeType.ARRAY) {
561                         var  buffer = new GLib.StringBuilder();
562                         var ar = node.get_array();
563                         for (var i = 0; i < ar.get_length(); i++) {
564                                 if (i >0 ) {
565                                         buffer.append_c('\n');
566                                 }
567                                 buffer.append(ar.get_string_element(i));
568                         }
569                         return buffer.str;
570                 }
571         // hopeflyu only type value..           
572                 var sv =  Value (typeof (string));                      
573                 var v = node.get_value();
574                 v.transform(ref sv);
575                 return (string)sv;
576
577         }
578         
579         // really old files...
580
581         public string upgradeKey(string key, string val)
582         {
583                 // convert V1 to V2
584                 if (key.length < 1) {
585                         return key;
586                 }
587                 switch(key) {
588                         case "*prop":
589                         case "*args":
590                         case ".ctor":
591                         case "|init":
592                                 return "* " + key.substring(1);
593                                 
594                         case "pack":
595                                 return "* " + key;
596                 }
597                 if (key[0] == '.') { // v2 does not start with '.' ?
598                         var bits = key.substring(1).split(":");
599                         if (bits[0] == "signal") {
600                                 return "@" + string.joinv(" ", bits).substring(bits[0].length);
601                         }
602                         return "# " + string.joinv(" ", bits);                  
603                 }
604                 if (key[0] != '|' || key[1] == ' ') { // might be a v2 file..
605                         return key;
606                 }
607                 var bits = key.substring(1).split(":");
608                 // two types '$' or '|' << for methods..
609                 // javascript 
610                 if  (Regex.match_simple ("^function\\s*(", val.strip())) {
611                         return "| " + key.substring(1);
612                 }
613                 // vala function..
614                 
615                 if  (Regex.match_simple ("^\\(", val.strip())) {
616                 
617                         return "| " + string.joinv(" ", bits);
618                 }
619                 
620                 // guessing it's a property..
621                 return "$ " + string.joinv(" ", bits);
622                 
623                 
624
625         }
626
627
628
629
630
631         
632         public Node  deepClone()
633         {
634                 var n = new Node();
635                 n.loadFromJson(this.toJsonObject(), 2);
636                 return n;
637
638         }
639         public string toJsonString()
640         {
641                 if (Node.gen == null) {
642                         Node.gen = new Json.Generator();
643                         gen.pretty =  true;
644                         gen.indent = 1;
645                 }
646                 var n = new Json.Node(Json.NodeType.OBJECT);
647                 n.set_object(this.toJsonObject () );
648                 Node.gen.set_root (n);
649                 return  Node.gen.to_data (null);   
650         }
651         
652         public void jsonObjectAddStringValue(Json.Object obj, string key, string v)
653         {
654                 if (v.index_of_char('\n',0) < 0) {
655                         obj.set_string_member(key,v);
656                         return;
657                 }
658                 var aro = new Json.Array();
659                 var ar = v.split("\n");
660                 for(var i =0;i < ar.length;i++) {
661                         aro.add_string_element(ar[i]);
662                 }
663                 obj.set_array_member(key,aro);
664         }
665         
666         public Json.Object toJsonObject()
667         {
668                 var ret = new Json.Object();
669
670                 // listeners...
671                 if (this.listeners.size > 0) {
672                         var li = new Json.Object();
673                         ret.set_object_member("listeners", li);
674                         var liter = this.listeners.map_iterator();
675                         while (liter.next()) {
676                                 this.jsonObjectAddStringValue(li, liter.get_value().to_json_key(), liter.get_value().val);
677                         }
678                 }
679                 //props
680                 if (this.props.size > 0 ) {
681                         var iter = this.props.map_iterator();
682                         while (iter.next()) {
683                                 this.jsonObjectsetMember(ret, iter.get_value().to_json_key(), iter.get_value().val);
684                         }
685                 }
686                 if (this.items.size > 0) {
687                         var ar = new Json.Array();
688                         ret.set_array_member("items", ar);
689                 
690                         // children..
691                         for(var i =0;i < this.items.size;i++) {
692                                 ar.add_object_element(this.items.get(i).toJsonObject());
693                         }
694                 }
695                 return ret;
696                 
697  
698         }
699          
700         public void jsonObjectsetMember(Json.Object o, string key, string val) {
701                 if (Lang.isBoolean(val)) {
702                         o.set_boolean_member(key, val.down() == "false" ? false : true);
703                         return;
704                 }
705                 
706                 
707                 if (Lang.isNumber(val)) {
708                         if (val.contains(".")) {
709                                 //print( "ADD " + key + "=" + val + " as a double?\n");
710                                 o.set_double_member(key, double.parse (val));
711                                 return;
712
713                         }
714                         //print( "ADD " + key + "=" + val + " as a int?\n")  ;
715                         o.set_int_member(key,long.parse(val));
716                         return;
717                 }
718                 ///print( "ADD " + key + "=" + val + " as a string?\n");
719                 this.jsonObjectAddStringValue(o,key,val);
720                 //o.set_string_member(key,val);
721                 
722         }
723         
724         
725         public string nodeTipProp { 
726                 set {
727                         // NOOp ??? should 
728                 }
729                 owned get {
730                          return  this.nodeTip();
731                 } 
732         }
733         // fixme this needs to better handle 'user defined types etc..
734         public string nodeTip()
735         {
736                 var ret = this.nodeTitle(true);
737                 var spec = "";
738                 var funcs = "";
739                 var props = "";
740                 var listen = "";
741  
742                 var uprops = "";
743                 // sort?
744                 
745                 var keys = new  Gee.ArrayList<string>();
746                 foreach(var k in this.props.keys) {
747                         keys.add(k);
748                 }
749                 keys.sort((a,b) => {
750                          return Posix.strcmp(a, b);
751                 
752                 });
753                 
754                 
755                 foreach(var pk in keys) {
756                          
757                         var prop = this.props.get(pk);
758                         var i = prop.name.strip();
759                         
760                         var val = prop.val;
761                         val = val == null ? "" : val;
762                         
763                         switch(prop.ptype) {
764                                 case PROP: 
765                                 case RAW: // should they be the same?
766                                 
767                                         props += "\n\t" + GLib.Markup.escape_text(prop.rtype) +
768                                                 " <b>" + GLib.Markup.escape_text(i) +"</b> : " + 
769                                                 GLib.Markup.escape_text(val.split("\n")[0]);
770                                                 
771                                         break;
772                                         
773                         
774                                 
775                                 case METHOD :
776                                         funcs += "\n\t" + GLib.Markup.escape_text(prop.rtype) +
777                                                 " <b>" + GLib.Markup.escape_text(i) +"</b> : "  +
778                                                 GLib.Markup.escape_text(val.split("\n")[0]);
779                                         break;
780                                         
781                                  
782                                 case USER : // user defined.
783                                         uprops += "\n\t<b>" + 
784                                                 GLib.Markup.escape_text(i) +"</b> : " + 
785                                                 GLib.Markup.escape_text(val.split("\n")[0]);
786                                         break;
787                                         
788                                 case SPECIAL : // * prop| args | ctor | init
789                                         spec += "\n\t<b>" + 
790                                                 GLib.Markup.escape_text(i) +"</b> : " + 
791                                                 GLib.Markup.escape_text(val.split("\n")[0]);
792                                         break;
793                                         
794                                 case LISTENER : return  "";  // always raw...
795                                 // not used
796                                 default:
797                                         break;;
798                         
799                         }
800                          
801                         
802                 }
803                 
804                 keys = new  Gee.ArrayList<string>();
805                 foreach(var k in this.listeners.keys) {
806                         keys.add(k);
807                 }
808                 keys.sort((a,b) => {
809                          return Posix.strcmp(a, b);
810                 
811                 });
812                 
813                 foreach(var pk in keys) {
814                          
815                         var prop = this.listeners.get(pk);
816                         var i =  prop.name.strip();
817                         
818                         var val = prop.val.strip();
819                         if (val == null || val.length < 1) {
820                                 continue;
821                         }
822                          listen += "\n\t<b>" + 
823                                         GLib.Markup.escape_text(i) +"</b> : " + 
824                                         GLib.Markup.escape_text(val.split("\n")[0]);
825                         
826                 }
827                 
828                 
829                 if (props.length > 0) {
830                         ret+="\n\nProperties:" + props;
831                 }
832                 if (uprops.length > 0) {
833                         ret+="\n\nUser defined Properties:" + uprops;
834                 } 
835                 
836                 
837                 if (funcs.length > 0) {
838                         ret+="\n\nMethods:" + funcs;
839                 } 
840                 if (listen.length > 0) {
841                         ret+="\n\nListeners:" + listen;
842                 } 
843                 if (spec.length > 0) {
844                         ret+="\n\nSpecial:" + spec;
845                 } 
846                 
847                 return ret;
848
849         }
850         
851         public string nodeTitleProp { 
852                 set {
853                         // NOOp ??? should 
854                 }
855                 owned get {
856                          return  this.nodeTitle();
857                 } 
858         }
859         
860         
861         
862         
863         
864         
865         public string nodeTitle(bool for_tip = false) 
866         {
867                 string[] txt = {};
868
869                 //var sr = (typeof(c['+buildershow']) != 'undefined') &&  !c['+buildershow'] ? true : false;
870                 //if (sr) txt.push('<s>');
871
872                 if (this.has("* prop"))   { txt += (GLib.Markup.escape_text(this.get("* prop")) + ":"); }
873                 
874                 //if (renderfull && c['|xns']) {
875                 var fqn = this.fqn();
876                 var fqn_ar = fqn.split(".");
877                 txt += for_tip || fqn.length < 1 ? fqn : fqn_ar[fqn_ar.length -1];
878                 
879                 if (fqn == "Roo.bootstrap.Element" && this.has("tag")) {
880                    txt = {};
881                    txt += GLib.Markup.escape_text(this.get("tag").up());
882                 }
883                 
884                 //if (c.xtype)    { txt.push(c.xtype); }
885                         
886                 if (this.has("id"))      { txt += ("<b>[id=" + GLib.Markup.escape_text(this.get("id")) + "]</b>"); }
887                 if (this.has("fieldLabel")){ txt += ("[" + GLib.Markup.escape_text(this.get("fieldLabel")) + "]"); }
888                 if (this.has("boxLabel"))  { txt += ("[" + GLib.Markup.escape_text(this.get("boxLabel"))+ "]"); }
889                 
890                 
891                 if (this.has("layout")) { txt += ("<i>" + GLib.Markup.escape_text(this.get("layout")) + "</i>"); }
892                 if (this.has("title"))   { txt += ("<b>" + GLib.Markup.escape_text(this.get("title")) + "</b>"); }
893                 if (this.has("html") && this.get("html").length > 0)     { 
894                         var ht = this.get("html").split("\n");
895                         if (ht.length > 1) {
896                                 txt += ("<b>" + GLib.Markup.escape_text(ht[0]) + "...</b>");
897                         } else { 
898                                 txt += ("<b>" + GLib.Markup.escape_text(this.get("html")) + "</b>");
899                         }
900                 }
901                 if (this.has("label"))   { txt += ("<b>" + GLib.Markup.escape_text(this.get("label"))+ "</b>"); }
902                 if (this.has("header"))   { txt += ("<b>" + GLib.Markup.escape_text(this.get("header")) + "</b>"); }
903                 if (this.has("legend"))  { txt += ("<b>" + GLib.Markup.escape_text(this.get("legend")) + "</b>"); }
904                 if (this.has("text"))     { txt += ("<b>" + GLib.Markup.escape_text(this.get("text")) + "</b>"); }
905                 if (this.has("name"))     { txt += ("<b>" + GLib.Markup.escape_text(this.get("name"))+ "</b>"); }
906                 if (this.has("region")) { txt += ("<i>(" + GLib.Markup.escape_text(this.get("region")) + ")</i>"); }
907                 if (this.has("dataIndex")){ txt += ("[" + GLib.Markup.escape_text(this.get("dataIndex")) + "]"); }
908                 // class is quite important on bootstrap..
909                 if (this.has("cls")){ txt += ("<b>[cls=" + GLib.Markup.escape_text(this.get("cls")) + "]</b>"); }               
910                 
911                 // other 'specials?'
912                 if (fqn == "Roo.bootstrap.Link") {
913                         txt += ("<b>href=" + (this.has("name") ?  GLib.Markup.escape_text(this.get("name")) : "?" ) + "</b>");
914                         if (this.has("fa")){ txt += ("<b>[fa=" + GLib.Markup.escape_text(this.get("fa")) + "]</b>"); }                                  
915                 }
916
917
918
919                 // for flat classes...
920                 //if (typeof(c["*class"]"))!= "undefined")  { txt += ("<b>" +  c["*class"]+  "</b>"); }
921                 //if (typeof(c["*extends"]"))!= "undefined")  { txt += (": <i>" +  c["*extends"]+  "</i>"); }
922                 
923                 
924                 //if (sr) txt.push('</s>');
925                 return (txt.length == 0) ? "Element" : string.joinv(" ", txt);
926         }
927         // used by trees to display icons?
928         // needs more thought?!?
929         public string iconFilename { 
930                 set {
931                         // NOOp ??? should 
932                 }
933                 owned get {
934                         var clsname = this.fqn();
935     
936                         var clsb = clsname.split(".");
937                     var sub = clsb.length > 1 ? clsb[1].down()  : "";
938                         var fn = "/usr/share/glade/pixmaps/hicolor/16x16/actions/widget-gtk-" + sub + ".png";
939                         //if (FileUtils.test (fn, FileTest.IS_REGULAR)) {
940                                 return fn;
941                         //}
942                         //return "/dev/null"; //???
943                 } 
944         }
945         
946          
947         
948         public void insertAfter(Node child, Node after) 
949         {
950                 this.insertChild(this.items.index_of(after) + 1, child);
951         }
952         public void insertBefore(Node child, Node before)       
953         {
954                 this.insertChild(this.items.index_of(before), child);
955         }
956         
957         public void insertChild(int pos, Node child)
958         {
959                 this.items.insert(pos, child);
960                 this.childstore.insert(pos, child);
961                 child.parent = this;
962         }
963         public void appendChild(Node child)
964         {
965                 this.items.add( child);
966                 this.childstore.append(child);
967                 child.parent = this;
968         }
969         
970         
971         /**
972         
973         properties
974                 previous we had listeners / and props
975                 
976                 we really need to store this as flat array - keep it simple!?
977                 
978                 getValue(key)
979                 update(key, value)
980                 
981                 
982         
983         */
984         
985
986         
987         
988         public void loadProps(GLib.ListStore model) 
989         {
990         
991                 // fixme sorting?? - no need to loop twice .. just use sorting.!
992                 var oldstore = this.propstore;
993                 this.propstore = model;
994                 for(var i =  0; i < oldstore.n_items; i++ ) {
995                         var it = (NodeProp) oldstore.get_item(i);
996                     model.append(it);
997                         
998                 }
999                 this.sortProps();
1000            
1001    }
1002    // used to replace propstore, so it does not get wiped by editing a node
1003    public void dupeProps()
1004    {
1005                 GLib.debug("dupeProps START");
1006                 var oldstore = this.propstore;
1007                 this.propstore = new GLib.ListStore(typeof(NodeProp));;
1008                 for(var i =  0; i < oldstore.n_items; i++ ) {
1009                         var it = (NodeProp) oldstore.get_item(i);
1010                         this.propstore.append(it);
1011                 }
1012                 GLib.debug("dupeProps END");
1013         }
1014         
1015    
1016    public void remove_prop(NodeProp prop)
1017         {
1018                 uint pos;
1019                 if (!this.propstore.find(prop, out pos)) {
1020                         return;
1021                 }
1022                 this.propstore.remove(pos);
1023                 this.updated_count++;
1024                 
1025         }   
1026    
1027         public bool has_prop_key(NodeProp prop) 
1028         {
1029                 for(var i =  0; i < this.propstore.n_items; i++ ) {
1030                         var it = (NodeProp) this.propstore.get_item(i);
1031                         if (it.ptype == prop.ptype && it.to_index_key() == prop.to_index_key()) {
1032                                 return true;
1033                         }
1034                         
1035                 }
1036                 return false;
1037            
1038         }
1039         
1040          
1041         
1042         
1043         public void add_prop(NodeProp prop)
1044         {
1045                 if (this.has_prop_key(prop) && !prop.to_index_key().has_suffix("[]")) {
1046                         GLib.warning("duplicate key' %s'- can not add - call has_prop_key first", prop.to_index_key());
1047                         return;
1048                 }
1049                 prop.parent = this;
1050                 this.propstore.append(prop);
1051                 this.sortProps();
1052                 
1053                 this.updated_count++;
1054                 
1055                 
1056         }
1057         
1058         int props_updated_count = -1;
1059         Gee.HashMap<string,NodeProp> props_cache;
1060         
1061         public Gee.HashMap<string,NodeProp> props {
1062                 owned get {
1063                         if (this.updated_count == this.props_updated_count) {
1064                                 return this.props_cache;
1065                         }
1066                          this.props_cache = new Gee.HashMap<string,NodeProp>(); // the properties..
1067
1068                         for(var i =  0; i < this.propstore.n_items; i++ ) {
1069                                 var it = (NodeProp) this.propstore.get_item(i);
1070                                 if (it.ptype != NodePropType.LISTENER) {
1071                                 //      GLib.debug("props add key %s", it.to_index_key());
1072                                         this.props_cache.set( it.to_index_key() , it);
1073                                 }
1074                         }
1075                         this.props_updated_count = this.updated_count;
1076                         return this.props_cache;
1077                 }
1078                 private set {
1079                         GLib.error("do not set listerners direclty");
1080                 }
1081         }
1082         
1083         int listeners_updated_count = -1;
1084         Gee.HashMap<string,NodeProp> listeners_cache;
1085         
1086         //private Gee.HashMap<string,NodeProp> _listeners; // the listeners..
1087         public Gee.HashMap<string,NodeProp> listeners {
1088                 owned get {
1089                         if (this.updated_count == this.listeners_updated_count) {
1090                                 return this.listeners_cache;
1091                         }
1092                         
1093                         this.listeners_cache = new Gee.HashMap<string,NodeProp>(); // the properties..
1094
1095                         for(var i =  0; i < this.propstore.n_items; i++ ) {
1096                                 var it = (NodeProp) this.propstore.get_item(i);
1097                                 if (it.ptype == NodePropType.LISTENER) {
1098                                         this.listeners_cache.set( it.to_index_key() , it);
1099                                 }
1100                         }
1101                         this.listeners_updated_count = this.updated_count;
1102                         return this.listeners_cache;;
1103                 }
1104                 private set {
1105                         GLib.error("do not set listerners direclty");
1106                 }
1107         }
1108         private void sortProps ()
1109         {
1110         
1111                 this.propstore.sort( (a, b) => {
1112
1113                         return Posix.strcmp( ((NodeProp)a).to_sort_key(),  ((NodeProp)b).to_sort_key());
1114                         
1115                 });
1116          
1117         
1118         }
1119 }