Fix #7250 - better handling of adding properties
[roobuilder] / src / Palete / Roo.vala
1 using Gtk;
2
3 namespace Palete {
4
5         
6 /*      
7         
8         
9     public class Introspect.El : Object
10     {
11         public enum eltype { 
12             NS,
13             CLASS,
14             METHOD,
15             PROP
16         }
17                 
18             
19         public eltype type;
20     }
21
22 */
23     public class Roo : Palete {
24                 
25                 Gee.ArrayList<string> top_classes;
26                 
27                 
28         public Roo(Project.Project project)
29         {
30
31
32             
33             aconstruct(project);
34             this.name = "Roo";
35                         this.top_classes =  new Gee.ArrayList<string>();
36         }
37
38                 Gee.HashMap<string,GirObject> propsFromJSONArray(string type, Json.Array ar, GirObject cls)
39                 {
40
41                         var ret = new Gee.HashMap<string,GirObject>();
42                         
43                         for (var i =0 ; i < ar.get_length(); i++) {
44                                 var o = ar.get_object_element(i);
45                                 var name = o.get_string_member("name"); 
46                                 var prop = new GirObject(type, name );  
47                                  
48                                 prop.type        = o.get_string_member("type");
49                                 prop.doctxt  = o.get_string_member("desc");
50                                 prop.propertyof = o.has_member("memberOf") ? o.get_string_member("memberOf") : "";
51                                 if (prop.propertyof.length < 1)  {
52                                         prop.propertyof = cls.name;
53                                 }
54                                 
55                                 // this is the function default.
56                                 prop.sig = o.has_member("sig") ? o.get_string_member("sig") : "";
57                                 
58                                 if (o.has_member("optvals")  ) {
59                                         var oar = o.get_array_member("optvals");
60                                         
61                                         for (var oi = 0; oi < oar.get_length(); oi++) {
62                                                 prop.optvalues.add(oar.get_string_element(oi));
63                                         }
64                                         
65                                 }       
66                                 
67                                 //print(type + ":" + name +"\n");
68                                 ret.set(name,prop);
69                         }
70                         return ret;
71                 }
72                 
73                 
74                 public override void  load () {
75
76                         if (this.classes != null) {
77                                 return;
78                         }
79                         // this.loadUsageFile(BuilderApplication.configDirectory() + "/resources/RooUsage.txt");
80                         this.classes = new Gee.HashMap<string,GirObject>();
81                         var add_to =  new Gee.HashMap<string,Gee.ArrayList<string>>();
82                                 
83                         var pa = new Json.Parser();
84                         pa.load_from_file(BuilderApplication.configDirectory() + "/resources/roodata.json");
85                         var node = pa.get_root();
86
87                         var clist =  node.get_object(); /// was in data... .get_object_member("data");
88                         clist.foreach_member((o , key, value) => {
89                                 //print("cls:" + key+"\n");
90                          
91                                 var cls = new GirObject("class", key);  
92                                 cls.props = this.propsFromJSONArray("prop", value.get_object().get_array_member("props"),cls);
93                                 cls.signals = this.propsFromJSONArray("signal", value.get_object().get_array_member("events"),cls);
94                                 
95                                 
96                                 if (value.get_object().has_member("methods")) {
97                                         cls.methods = this.propsFromJSONArray("method", value.get_object().get_array_member("methods"),cls);
98                                 }
99                                 if (value.get_object().has_member("implementations")) {
100                                         var vcn = value.get_object().get_array_member("implementations");
101                                         for (var i =0 ; i < vcn.get_length(); i++) {
102                                                 cls.implementations.add(vcn.get_string_element(i));
103                                                 //break; << why!?!
104                                         }                               
105                                 }
106                                 // tree children = 
107                                 
108                                 if (value.get_object().has_member("tree_children")) {
109                                         var vcn = value.get_object().get_array_member("tree_children");                         
110                                         for (var i =0 ; i < vcn.get_length(); i++) {
111                                                 var ad_c = vcn.get_string_element(i);
112                                                 if (!cls.valid_cn.contains(ad_c)) {
113                                                         cls.valid_cn.add( ad_c );
114                                                 }
115                                                 if (!add_to.has_key(ad_c)) {
116                                                         add_to.set(ad_c, new Gee.ArrayList<string>());
117                                                 }
118                                                 if (!add_to.get(ad_c).contains(cls.name)) {
119                                                         add_to.get(ad_c).add(cls.name);
120                                                 }
121                                         }
122                                 }
123                                 
124                                 
125                                 
126                                 
127                                 // tree parent
128                                 
129                                 if (value.get_object().has_member("tree_parent")) {
130                                         var vcn = value.get_object().get_array_member("tree_parent");
131                                         for (var i =0 ; i < vcn.get_length(); i++) {
132                                                 if ("builder" == vcn.get_string_element(i)) {
133                                                         // this class can be added to the top level.
134                                                         GLib.debug("Add %s to *top", cls.name);
135                                                         
136                                                         this.top_classes.add(cls.name);
137                                                         break;
138                                                 }
139                                                 
140                                         }
141                                 }
142  
143                                 this.classes.set(key, cls);
144                         });
145                         
146                         // look for properties of classes, that are atually clasess
147                         // eg. Roo.data.Store has proxy and reader..
148                         
149                         
150                         foreach(var cls in this.classes.values) {
151                                 foreach(var gir_obj in cls.props.values) {
152                                         var types = gir_obj.type.split("|");
153                                         for(var i =0; i < types.length; i++) {
154                                                 var type = types[i];
155                                         
156                                                 if (/^Roo\./.match(type) && classes.has_key(type)) {
157                                                         
158                                                          
159                                                         cls.valid_cn.add(type + ":" +   gir_obj.name );
160                                                         // Roo.bootstrap.panel.Content:east
161                                                         // also means that  Roo.bootstrap.panel.Grid:east works
162                                                         var prop_type = classes.get(type);
163                                                         foreach(var imp_str in prop_type.implementations) {
164                                                                 //GLib.debug("addChild for %s - child=  %s:%s", cls.name, imp_str, gir_obj.name);
165                                                                 cls.valid_cn.add(imp_str + ":" +    gir_obj.name);
166                                                                 if (!add_to.has_key(imp_str)) {
167                                                                         add_to.set( imp_str, new Gee.ArrayList<string>());
168                                                                 }
169                                                                 if (!add_to.get( imp_str).contains(cls.name)) {
170                                                                         add_to.get( imp_str ).add(cls.name );
171                                                                 }
172                                                                 
173                                                         }
174                                                         
175                                                         
176                                                         if (!add_to.has_key( type)) {
177                                                                 add_to.set( type, new Gee.ArrayList<string>());
178                                                         }
179                                                         if (!add_to.get(type).contains(cls.name)) {
180                                                                 add_to.get( type ).add(cls.name );
181                                                         }
182                                                 }
183                                         }
184                                 }
185                                  
186                         }
187                         foreach(var cls in this.classes.values) {
188                                 if (add_to.has_key(cls.name)) {
189                                         cls.can_drop_onto = add_to.get(cls.name);
190                                 }
191                         }
192                                  
193                 }
194                   
195                         
196                 public string doc(string what) {
197                         return "";
198                         /*var ns = what.split(".")[0];
199
200
201                         
202                         
203                                 var gir =  Gir.factory(ns);
204                                 return   gir.doc(what);
205                                 */
206                                 
207                         //return typeof(this.comments[ns][what]) == 'undefined' ?  '' : this.comments[ns][what];
208                 }
209
210                 // does not handle implements...
211                 public override GirObject? getClass(string ename)
212                 {
213                         this.load();
214                         return this.classes.get(ename);
215                         
216                 }
217                 
218                 public override Gee.HashMap<string,GirObject> getPropertiesFor(string ename, JsRender.NodePropType ptype)
219                 {
220                         //print("Loading for " + ename);
221                         
222
223                         this.load();
224                                         // if (typeof(this.proplist[ename]) != 'undefined') {
225                                         //print("using cache");
226                                  //   return this.proplist[ename][type];
227                                 //}
228                                 // use introspection to get lists..
229                  
230                         
231                         var cls = this.classes.get(ename);
232                         var ret = new Gee.HashMap<string,GirObject>();
233                         if (cls == null) {
234                                 print("could not find class: %s\n", ename);
235                                 return ret;
236                                 //throw new Error.INVALID_VALUE( "Could not find class: " + ename);
237                 
238                         }
239
240                         //cls.parseProps();
241                         //cls.parseSignals(); // ?? needed for add handler..
242                         //cls.parseMethods(); // ?? needed for ??..
243                         //cls.parseConstructors(); // ?? needed for ??..
244
245                         //cls.overlayParent();
246
247                         switch  (ptype) {
248                                 
249                                 
250                                 case JsRender.NodePropType.PROP:
251                                         return cls.props;
252                                 case JsRender.NodePropType.LISTENER:
253                                         return cls.signals;
254                                 case JsRender.NodePropType.METHOD:
255                                         return ret;
256                                 case JsRender.NodePropType.CTOR:
257                                         return ret;
258                                 default:
259                                         throw new Error.INVALID_VALUE( "getPropertiesFor called with: " + ptype.to_string());
260                                         //var ret = new Gee.HashMap<string,GirObject>();
261                                         //return ret;
262                         
263                         }
264                 
265         
266                 //cls.overlayInterfaces(gir);
267
268
269                          
270                 }
271                 public string[] getInheritsFor(string ename)
272                 {
273                         string[] ret = {};
274                         var es = ename.split(".");
275                         var gir = Gir.factory(null, es[0]);
276                         
277                         var cls = gir.classes.get(es[1]);
278                         if (cls == null) {
279                                 return ret;
280                         }
281                         return cls.inheritsToStringArray();
282                         
283
284                 }
285
286
287                 public override void fillPack(JsRender.Node node,JsRender.Node parent)
288                 {   
289
290                          return;
291                 }
292                 /*
293                  *  Pulldown options for type
294                  */
295                 public override bool typeOptions(string fqn, string key, string type, out string[] opts) 
296                 {
297                         opts = {};
298                         print("get typeOptions %s (%s)%s", fqn, type, key);
299                         if (type.up() == "BOOL" || type.up() == "BOOLEAN") {
300                                 opts = { "true", "false" };
301                                 return true;
302                          }
303                          
304                          var props = this.getPropertiesFor(fqn, JsRender.NodePropType.PROP);
305                          if (!props.has_key(key)) {
306                                  print("prop %s does not have key %s\n", fqn, key);
307                                  return false;
308                          }
309                          var pr = props.get(key);
310                          if (pr.optvalues.size < 1) {
311                                  print("prop %s no optvalues for %s\n", fqn, key);
312                                  return false;
313                          }
314                          string[] ret = {};
315                          for(var i = 0; i < pr.optvalues.size; i++) {
316                                  ret += pr.optvalues.get(i);
317                          }
318                          opts = ret;
319                          print("prop %s returning optvalues for %s\n", fqn, key);
320                          return true;
321                          
322                 }
323                 public override  List<SourceCompletionItem> suggestComplete(
324                                 JsRender.JsRender file,
325                                 JsRender.Node? node,
326                                 JsRender.NodeProp? xxprop,
327                                 string complete_string
328                 ) { 
329                         
330                         var ret =  new List<SourceCompletionItem>();
331                         // completion rules??
332                         
333                         // Roo......
334                         
335                         // this. (based on the node type)
336                         // this.xxx // Node and any determination...
337                         
338                         if (complete_string.index_of(".",0) < 0) {
339                                 // string does not have a '.'
340                                 // offer up this / Roo / javascript keywords... / look for var string = .. in the code..
341                                 for(var i = 0; i <  JsRender.Lang.match_strings.size ; i++) {
342                                         var str = JsRender.Lang.match_strings.get(i);
343                                         if (complete_string != str && str.index_of(complete_string,0) == 0 ) { // should we ignore exact matches... ???
344                                                 ret.append(new SourceCompletionItem (str, str, null, "javascript : " + str));
345                                         }
346                                         
347                                         
348                                 }
349                                 if (complete_string != "Roo" && "Roo".index_of(complete_string,0) == 0 ) { // should we ignore exact matches... ???
350                                         ret.append(new SourceCompletionItem ("Roo - A Roo class", "Roo", null, "Roo library"));
351                                 }
352                                 if (complete_string != "_this" && "_this".index_of(complete_string,0) == 0 ) { // should we ignore exact matches... ???
353                                         ret.append(new SourceCompletionItem ("_this - the top level element", "_this", null, "Top level element"));
354                                 }
355                                 return ret;
356                         }
357                         // got at least one ".".
358                         var parts = complete_string.split(".");
359                         var curtype = "";
360                         var cur_instance = false;
361                         if (parts[0] == "this") {
362                                 // work out from the node, what the type is...
363                                 if (node == null) {
364                                         print("node is empty - no return\n");
365                                         return ret; // no idea..
366                                 }
367                                 curtype = node.fqn();
368                                 cur_instance = true;
369                         }
370                         if (parts[0] == "Roo") {        
371                                 curtype = "Roo";
372                                 cur_instance = false;
373                         }
374                         
375                         var prevbits = parts[0] + ".";
376                         for(var i =1; i < parts.length; i++) {
377                                 print("matching %d/%d\n", i, parts.length);
378                                 var is_last = i == parts.length -1;
379                                 
380                                 // look up all the properties of the type...
381                                 var cls = this.getClass(curtype);
382                                 if (cls == null) {
383                                         print("could not get class of curtype %s\n", curtype);
384                                         return ret;
385                                 }
386
387                                 if (!is_last) {
388                                 
389                                         // only exact matches from here on...
390                                         if (cur_instance) {
391                                                 if (cls.props.has_key(parts[i])) {
392                                                         var prop = cls.props.get(parts[i]);
393                                                         if (prop.type.index_of(".",0) > -1) {
394                                                                 // type is another roo object..
395                                                                 curtype = prop.type;
396                                                                 prevbits += parts[i] + ".";
397                                                                 continue;
398                                                         }
399                                                         return ret;
400                                                 }
401                                                 
402                                                 
403                                                 
404                                                 // check methods?? - we do not export that at present..
405                                                 return ret;      //no idea...
406                                         }
407                                 
408                                         // not a instance..
409                                         //look for child classes.
410                                         var citer = this.classes.map_iterator();
411                                         var foundit = false;
412                                         while (citer.next()) {
413                                                 var scls = citer.get_key();
414                                                 var look = prevbits + parts[i];
415                                                 if (scls.index_of(look,0) != 0) {
416                                                         continue;
417                                                 }
418                                                 // got a starting match..
419                                                 curtype = look;
420                                                 cur_instance = false;
421                                                 foundit =true;
422                                                 break;
423                                         }
424                                         if (!foundit) {
425                                                 return ret;
426                                         }
427                                         prevbits += parts[i] + ".";
428                                         continue;
429                                 }
430                                 // got to the last element..
431                                 print("Got last element\n");
432                                 if (curtype == "") { // should not happen.. we would have returned already..
433                                         return ret;
434                                 }
435                                 print("Got last element type %s\n",curtype);
436                                 if (!cur_instance) {
437                                         print("matching instance");
438                                         // it's a static reference..
439                                         var citer = this.classes.map_iterator();
440                                         while (citer.next()) {
441                                                 var scls = citer.get_key();
442                                                 var look = prevbits + parts[i];
443                                                 if (parts[i].length > 0 && scls.index_of(look,0) != 0) {
444                                                         continue;
445                                                 }
446                                                 // got a starting match..
447                                                 ret.append(new SourceCompletionItem (
448                                                         scls,
449                                                         scls, 
450                                                         null, 
451                                                         scls));
452                                         }
453                                         return ret;
454                                 }
455                                 print("matching property");
456                                 
457                                 
458                                 
459                                 var citer = cls.methods.map_iterator();
460                                 while (citer.next()) {
461                                         var prop = citer.get_value();
462                                         // does the name start with ...
463                                         if (parts[i].length > 0 && prop.name.index_of(parts[i],0) != 0) {
464                                                 continue;
465                                         }
466                                         // got a matching property...
467                                         // return type?
468                                         ret.append(new SourceCompletionItem (
469                                                          prop.name + prop.sig + " :  ("+ prop.propertyof + ")", 
470                                                         prevbits + prop.name + "(", 
471                                                         null, 
472                                                         prop.doctxt));
473                                 }
474                                 
475                                 // get the properties / methods and subclasses.. of cls..
476                                 // we have cls.. - see if the string matches any of the properties..
477                                 citer = cls.props.map_iterator();
478                                 while (citer.next()) {
479                                         var prop = citer.get_value();
480                                         // does the name start with ...
481                                         if (parts[i].length > 0 && prop.name.index_of(parts[i],0) != 0) {
482                                                 continue;
483                                         }
484                                         // got a matching property...
485                                         
486                                         ret.append(new SourceCompletionItem (
487                                                          prop.name + " : " + prop.type + " ("+ prop.propertyof + ")", 
488                                                         prevbits + prop.name, 
489                                                         null, 
490                                                         prop.doctxt));
491                                 }
492                                          
493                                         
494                                 return ret;     
495                                         
496                                         
497                                 
498                                         
499                                 
500                         }
501                         
502                          
503                         
504                         
505                         
506                         
507                         return ret;
508                 }
509                 public override string[] getChildList(string in_rval)
510         {
511                 if (this.top_classes.size < 1) {
512                         this.load();
513                 }
514                 
515                 
516                 string[] ret = {};
517                 var ar = this.top_classes;
518                 if (in_rval != "*top") {
519                         if (this.classes.has_key(in_rval)) {
520                            // some of these children will be eg: Roo.bootstrap.layout.Region:center
521                                 ar = this.classes.get(in_rval).valid_cn;
522                         } else {
523                                 ar = new Gee.ArrayList<string>();
524                         }
525                 }
526                 
527                 foreach(var str in ar) {
528                         ret += str;
529                 } 
530                 GLib.debug("getChildList for %s returns %s", in_rval, string.joinv(", ", ret));
531                 return ret;     
532                 
533                 //return this.original_getChildList(  in_rval);
534         }
535                 public override string[] getDropList(string rval)
536                 {
537                         // we might be dragging  Roo.bootstrap.layout.Region:center
538                         // in which case we need to lookup Roo.bootstrap.layout.Region
539                         // and see if it's has can_drop_onto
540                         string[] ret = {};
541                         var cls = this.classes.get(rval);
542                         // cls can be null.
543                         if (cls == null && rval.contains(":")) {
544                                 var rr = rval.substring(0,rval.index_of(":"));
545                                 GLib.debug("Converted classname to %s", rr);
546                                 cls = this.classes.get(rr);
547                     }
548                         if (cls == null) {
549                                 return ret; //nothing..
550                         }
551                         
552                         foreach(var str in cls.can_drop_onto) {
553
554                                 ret += str;
555                         }
556                         GLib.debug("getDropList for %s return[] %s", rval, string.joinv(", ", ret));
557                         return ret;
558                                 
559                         
560                         
561                         //return this.default_getDropList(rval);
562                 }       
563     }
564 }
565