documentor: Fixes to display template const types parameters
[sdk] / documentor / src / Documentor.ec
1 import "ecere"
2 import "ec"
3 import "HTMLView"
4 import "IDESettings"
5 import "SettingsDialog"
6
7 static Context globalContext { };
8 static OldList defines { };
9 static OldList imports { };
10 static NameSpace globalData;
11 static OldList excludedSymbols { offset = (uint)(uintptr)&((Symbol)0).left };
12 static bool readOnly;
13
14 define app = (GuiApplication)__thisModule.application;
15
16 #define UTF8_NUM_BYTES(x)  (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
17
18 default:
19 /*extern */int __ecereVMethodID_class_OnGetString;
20 private:
21
22
23 static __attribute__((unused)) void Dummy()
24 {
25    int a;
26    a.OnGetString(null, null, null);
27 }
28
29 static bool editing = true;
30
31 enum CodeObjectType { typeClass, typeData, typeMethod, typeEvent, typeProperty, typeNameSpace, typeDataType, typeEnumValue, typeDataPrivate, typeMethodPrivate, typePropertyPrivate };
32
33 static const char * iconNames[CodeObjectType] =
34 {
35    "<:ecere>constructs/class.png",
36    "<:ecere>constructs/data.png",
37    "<:ecere>constructs/method.png",
38    "<:ecere>constructs/event.png",
39    "<:ecere>constructs/property.png",
40    "<:ecere>constructs/namespace.png",
41    "<:ecere>constructs/dataType.png",
42    "<:ecere>constructs/enumValue.png",
43    "<:ecere>constructs/dataPrivate.png",
44    "<:ecere>constructs/methodPrivate.png",
45    "<:ecere>constructs/propertyPrivate.png"
46 };
47
48 IDESettings settings { }; // instantiate the IDESettings class from the IDESettings.ec file. Do this at a global level so that all methods can access settings.
49
50 IDESettingsContainer settingsContainer
51 {
52    driver = "JSON";
53    data = settings;
54    dataOwner = &settings;
55 };
56
57 void GetTemplateString(Class c, char * templateString)
58 {
59    Module m = c.module.application;
60    const char * n = c.name;
61    char * lt = strchr(n, '<');
62    char * s;
63    char ch;
64    char curName[256];
65    int len = 0;
66
67    memcpy(templateString, n, lt-n);
68    templateString[lt-n] = 0;
69    strcat(templateString, "</a>");
70
71    for(s = lt; (ch = *s); s++)
72    {
73       if(ch == '<' || ch == '>' || ch == ',')
74       {
75          if(len)
76          {
77             Class pc;
78             char * d = templateString + strlen(templateString);
79             curName[len] = 0;
80             TrimLSpaces(curName, curName);
81             TrimRSpaces(curName, curName);
82             pc = eSystem_FindClass(m, curName);
83             if(pc)
84                sprintf(d, "%s<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a>", !strncmp(curName, "const ", 6) ? "const " : "", pc, pc.name);
85             else
86                strcat(d, curName);
87          }
88          if(ch == '<')
89             strcat(templateString, "&lt;");
90          else if(ch == '>')
91             strcat(templateString, "&gt;");
92          else
93             strcat(templateString, ", ");
94          len = 0;
95       }
96       else if(ch == '=')
97       {
98          curName[len++] = ' ';
99          curName[len++] = ch;
100          curName[len++] = ' ';
101          curName[0] = 0;
102          strcat(templateString, curName);
103          len = 0;
104       }
105       else
106          curName[len++] = ch;
107    }
108 }
109
110 // WARNING : This function expects a null terminated string since it recursively concatenate...
111 static void _PrintType(Type type, char * string, bool printName, bool printFunction, bool fullName)
112 {
113    if(type)
114    {
115       if(type.constant && (type.kind != pointerType && type.kind != arrayType))
116          strcat(string, "const ");
117       switch(type.kind)
118       {
119          case classType:
120             if(type._class && type._class.string)
121             {
122                if(fullName)
123                   strcat(string, type._class.string);
124                else
125                {
126                   if(type._class.registered)
127                   {
128                      char hex[20];
129                      const char * s = type._class.registered.name;
130                      sprintf(hex, "%p", type._class.registered.templateClass ? type._class.registered.templateClass : type._class.registered);
131                      strcat(string, "<a href=\"api://");
132                      strcat(string, hex);
133                      strcat(string, "\" style=\"text-decoration: none;\">");
134                      if(strchr(s, '<'))
135                      {
136                         char n[1024];
137                         GetTemplateString(type._class.registered, n);
138                         strcat(string, n);
139                      }
140                      else
141                         strcat(string, type._class.registered.name);
142                      strcat(string, "</a>");
143                   }
144                   else
145                      strcat(string, type._class.string);
146                }
147             }
148             break;
149          case pointerType:
150          {
151             /*Type funcType;
152             for(funcType = type; funcType && (funcType.kind == pointerType || funcType.kind == arrayType); funcType = funcType.type);
153             if(funcType && funcType.kind == functionType)
154             {
155                Type param;
156                DocPrintType(funcType.returnType, string, false, fullName);
157                strcat(string, "(*");
158                if(printName || funcType.thisClass)
159                {
160                   strcat(string, " ");
161                   if(funcType.thisClass)
162                   {
163                      strcat(string, funcType.thisClass.string);
164                      strcat(string, "::");
165                   }
166                   if(type.name)
167                      strcat(string, type.name);
168                }
169                strcat(string, ")(");
170                for(param = funcType.params.first; param; param = param.next)
171                {
172                   DocPrintType(param, string, false, fullName);
173                   if(param.next) strcat(string, ", ");
174                }
175                strcat(string, ")");
176             }
177             else*/
178             {
179                _PrintType(type.type, string, false /*printName*/, printFunction, fullName);
180                if(string[strlen(string)-1] == '(')
181                   strcat(string, "*");
182                else
183                   strcat(string, " *");
184             }
185             break;
186          }
187          case voidType: strcat(string, "void"); break;
188          case intType:  strcat(string, type.isSigned ? "int" : "uint"); break;
189          case int64Type:  strcat(string, type.isSigned ? "int64" : "uint64"); break;
190          case charType: strcat(string, type.isSigned ? "char" : "byte"); break;
191          case shortType: strcat(string, type.isSigned ? "short" : "uint16"); break;
192          case floatType: strcat(string, "float"); break;
193          case doubleType: strcat(string, "double"); break;
194          case structType:
195             if(type.enumName)
196             {
197                strcat(string, "struct ");
198                strcat(string, type.enumName);
199             }
200             else if(type.typeName)
201             {
202                strcat(string, type.typeName);
203             }
204             else
205             {
206                /*
207                strcat(string, "struct ");
208                strcat(string,"(unnamed)");
209                */
210                Type member;
211                strcat(string, "struct {");
212                for(member = type.members.first; member; member = member.next)
213                {
214                   DocPrintType(member, string, true, fullName);
215                   strcat(string,"; ");
216                }
217                strcat(string,"}");
218             }
219             break;
220          case unionType:
221             if(type.enumName)
222             {
223                strcat(string, "union ");
224                strcat(string, type.enumName);
225             }
226             else if(type.typeName)
227             {
228                strcat(string, type.typeName);
229             }
230             else
231             {
232                strcat(string, "union ");
233                strcat(string,"(unnamed)");
234             }
235             break;
236          case enumType:
237             if(type.enumName)
238             {
239                strcat(string, "enum ");
240                strcat(string, type.enumName);
241             }
242             else if(type.typeName)
243             {
244                strcat(string, type.typeName);
245             }
246             else
247                strcat(string, "enum");
248             break;
249          case functionType:
250          {
251             if(printFunction)
252             {
253                if(type.dllExport)
254                   strcat(string, "dllexport ");
255                DocPrintType(type.returnType, string, false, fullName);
256                strcat(string, " ");
257             }
258
259             // DANGER: Testing This
260             if(printName)
261             {
262                if(type.name)
263                {
264                   if(fullName)
265                      strcat(string, type.name);
266                   else
267                   {
268                      char * name = RSearchString(type.name, "::", strlen(type.name), true, false);
269                      if(name) name += 2; else name = type.name;
270                      strcat(string, "<b>");
271                      strcat(string, name);
272                      strcat(string, "</b>");
273                   }
274                }
275             }
276
277             if(printFunction)
278             {
279                Type param;
280                strcat(string, "(");
281                for(param = type.params.first; param; param = param.next)
282                {
283                   DocPrintType(param, string, true, fullName);
284                   if(param.next) strcat(string, ", ");
285                }
286                strcat(string, ")");
287             }
288             break;
289          }
290          case arrayType:
291          {
292             /*Type funcType;
293             for(funcType = type; funcType && (funcType.kind == pointerType || funcType.kind == arrayType); funcType = funcType.type);
294             if(funcType && funcType.kind == functionType)
295             {
296                Type param;
297                DocPrintType(funcType.returnType, string, false, fullName);
298                strcat(string, "(*");
299                if(printName || funcType.thisClass)
300                {
301                   strcat(string, " ");
302                   if(funcType.thisClass)
303                   {
304                      strcat(string, funcType.thisClass.string);
305                      strcat(string, "::");
306                   }
307                   if(type.name)
308                      strcat(string, type.name);
309                }
310                strcat(string, ")(");
311                for(param = funcType.params.first; param; param = param.next)
312                {
313                   DocPrintType(param, string, false, fullName);
314                   if(param.next) strcat(string, ", ");
315                }
316                strcat(string, ")");
317             }
318             else*/
319             {
320                char baseType[1024], size[256];
321                Type arrayType = type;
322                baseType[0] = '\0';
323                size[0] = '\0';
324
325                while(arrayType.kind == TypeKind::arrayType)
326                {
327                   strcat(size, "[");
328                   if(arrayType.enumClass)
329                      strcat(size, arrayType.enumClass.string);
330                   else if(arrayType.arraySizeExp)
331                      PrintExpression(arrayType.arraySizeExp, size);
332                   //sprintf(string, "%s[%s]", baseType, size);
333                   strcat(size, "]");
334
335                   arrayType = arrayType.arrayType;
336                }
337                _PrintType(arrayType, baseType, printName, printFunction, fullName);
338                strcat(string, baseType);
339                strcat(string, size);
340             }
341
342             /*
343                DocPrintType(type.arrayType, baseType, printName, fullName);
344                if(type.enumClass)
345                   strcpy(size, type.enumClass.string);
346                else if(type.arraySizeExp)
347                   PrintExpression(type.arraySizeExp, size);
348                //sprintf(string, "%s[%s]", baseType, size);
349                strcat(string, baseType);
350                strcat(string, "[");
351                strcat(string, size);
352                strcat(string, "]");
353                */
354
355             printName = false;
356             break;
357          }
358          case ellipsisType:
359             strcat(string, "...");
360             break;
361          case methodType:
362             _PrintType(type.method.dataType, string, false, printFunction, fullName);
363             break;
364          case subClassType:
365             strcat(string, "subclass(");
366             strcat(string, type._class ? type._class.string : "int");
367             strcat(string, ")");
368             break;
369          default:
370             break;
371       }
372       if(type.name && printName && type.kind != functionType && (type.kind != pointerType || type.type.kind != functionType))
373       {
374          strcat(string, " ");
375          strcat(string, type.name);
376       }
377    }
378 }
379
380 void DocPrintType(Type type, char * string, bool printName, bool fullName)
381 {
382    Type funcType;
383    for(funcType = type; funcType && (funcType.kind == pointerType || funcType.kind == arrayType); funcType = funcType.type);
384    if(funcType && funcType.kind == functionType && type != funcType)
385    {
386       Type param;
387
388       DocPrintType(funcType.returnType, string, false, fullName);
389       strcat(string, "(");
390       _PrintType(type, string, printName, false, fullName);
391       strcat(string, ")");
392       /*
393       if(type.name)
394          strcat(string, type.name);
395       else
396       {
397          printf("");
398       }
399       */
400       strcat(string, "(");
401       for(param = funcType.params.first; param; param = param.next)
402       {
403          DocPrintType(param, string, true, fullName);
404          if(param.next) strcat(string, ", ");
405       }
406       strcat(string, ")");
407    }
408    else
409       _PrintType(type, string, printName, true, fullName);
410 }
411
412 void AddComponents(Module module, bool isDll)
413 {
414    DataRow row = null;
415    SubModule m;
416
417    if(module.name && (!strcmp(module.name, "ecere") || !strcmp(module.name, "ecereCOM")))
418    {
419       row = mainForm.browser.AddRow();
420       row.SetData(null, APIPageNameSpace { name = "ecereCOM", nameSpace = &module.application.systemNameSpace });
421       row.tag = (int64)null;
422       AddNameSpace(row, null, module.application.systemNameSpace, null, "", !isDll);
423    }
424
425    for(m = module.modules.first; m; m = m.next)
426    {
427       if(m.importMode == publicAccess || !isDll)
428          AddComponents(m.module, true);
429    }
430
431    // PUT MODULE DESCRIPTION HERE
432    if(module.name && strcmp(module.name, "ecereCOM"))
433    {
434       row = mainForm.browser.AddRow();
435       row.SetData(null, APIPageNameSpace { name = module.name, module = module, nameSpace = &module.publicNameSpace });
436       row.tag = (int64)module;
437       AddNameSpace(row, module, module.publicNameSpace, null /*module.application.systemNameSpace*/, "", !isDll);
438       if(!isDll)
439          AddNameSpace(row, module, module.privateNameSpace, null /*module.application.systemNameSpace*/, "", !isDll);
440    }
441 }
442
443 class APIPage
444 {
445 public:
446    const char * name;
447    APIPage page;
448    const char * label;
449    bool showPrivate;
450
451    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
452    {
453       return name;
454    }
455
456    virtual void Generate(File f)
457    {
458       page.Generate(f);
459    }
460
461    virtual Module GetModule()
462    {
463       return page ? page.GetModule() : null;
464    }
465
466    virtual NameSpace * GetNameSpace()
467    {
468       return page ? page.GetNameSpace() : null;
469    }
470 };
471
472 enum DocumentationType
473 {
474    nameSpaceDoc,
475    classDoc,
476    functionDoc,
477    methodDoc
478 };
479
480 enum DocumentationItem
481 {
482    description,
483    usage,
484    remarks,
485    example,
486    seeAlso,
487    enumerationValue,
488    definition,
489    conversion,
490    memberDescription,
491    propertyDescription,
492    parameter,
493    returnValue
494 };
495
496 static void FigureFileName(char * fileName, Module module, DocumentationType type, void * object, DocumentationItem item, void * data)
497 {
498    NameSpace * nameSpace, * ns;
499    Class cl = null;
500    Method method = null;
501    GlobalFunction function = null;
502    char nsName[1024], temp[1024];
503    char docFile[1024];
504
505
506    switch(type)
507    {
508       case nameSpaceDoc: nameSpace = object; break;
509       case classDoc:     cl = (Class)object; nameSpace = cl.nameSpace; break;
510       case functionDoc:  function = object; nameSpace = function.nameSpace; break;
511       case methodDoc:    method = object; cl = method._class; nameSpace = cl.nameSpace; break;
512    }
513
514    nsName[0] = 0;
515    ns = nameSpace;
516    while(ns && ns->name)
517    {
518       strcpy(temp, "namespaces/");
519       strcat(temp, ns->name);
520       strcat(temp, "/");
521       strcat(temp, nsName);
522       strcpy(nsName, temp);
523       ns = ns->parent;
524    }
525    sprintf(docFile, "%s.eCdoc", (!module || !module.name || !strcmp(nsName, "namespaces/ecere/namespaces/com")) ? "ecereCOM" : module.name);
526    if(strchr(docFile, DIR_SEP))
527    {
528       GetLastDirectory(docFile, temp);
529       strcpy(docFile, temp);
530    }
531
532    sprintf(fileName, "<%s/%s>", settings.docDir, docFile); // Note that in the ecereIDE.ini file, there can be no quotes around the path, and there needs to be the final backslash. Otherwise this does not work.
533    strcat(fileName, nsName);
534
535    if(cl)
536    {
537       strcat(fileName, "classes/");
538       strcat(fileName, cl.name);
539       strcat(fileName, "/");
540    }
541
542    if(method)
543    {
544       strcat(fileName, "methods/");
545       strcat(fileName, method.name);
546       strcat(fileName, "/");
547    }
548    else if(function)
549    {
550       const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
551       if(name) name += 2; else name = function.name;
552       strcat(fileName, "functions/");
553       strcat(fileName, name);
554       strcat(fileName, "/");
555    }
556
557    switch(item)
558    {
559       case description: strcat(fileName, "description"); break;
560       case usage: strcat(fileName, "usage"); break;
561       case remarks: strcat(fileName, "remarks"); break;
562       case example: strcat(fileName, "example"); break;
563       case seeAlso: strcat(fileName, "seeAlso"); break;
564       case returnValue: strcat(fileName, "returnValue"); break;
565       case enumerationValue:
566          strcat(fileName, "enumeration values/");
567          strcat(fileName, ((NamedLink)data).name);
568          break;
569       case definition:
570          strcat(fileName, "definitions/");
571          strcat(fileName, ((Definition)data).name);
572          break;
573       case conversion:
574       {
575          const char * name = RSearchString(((Property)data).name, "::", strlen(((Property)data).name), true, false);
576          if(name) name += 2; else name = ((Property)data).name;
577          strcat(fileName, "conversions/");
578          strcat(fileName, name);
579          break;
580       }
581       case memberDescription:
582          strcat(fileName, "data members/");
583          strcat(fileName, ((DataMember)data).name);
584          break;
585       case propertyDescription:
586          strcat(fileName, "properties/");
587          strcat(fileName, ((Property)data).name);
588          break;
589       case parameter:
590       {
591          int count;
592          char name[1024];
593          Type prev;
594          strcat(fileName, "parameters/");
595          for(prev = data, count = 0; prev; prev = prev.prev, count++);
596          sprintf(name, "%s.%d", ((Type)data).name, count);
597          strcat(fileName, name);
598          break;
599       }
600    }
601 }
602
603 static char * ReadDoc(Module module, DocumentationType type, void * object, DocumentationItem item, void * data)
604 {
605    char fileName[MAX_LOCATION];
606    String contents = null;
607    File file;
608
609    FigureFileName(fileName, module, type, object, item, data);
610    file = FileOpen(fileName, read);
611    if(file)
612    {
613       uint len;
614       if((len = file.GetSize()))
615       {
616          contents = new char[len+1];
617          file.Read(contents, 1, len);
618          contents[len] = '\0';
619       }
620       delete file;
621    }
622    if(contents)
623    {
624       int c;
625       for(c = 0; contents[c]; c++)
626          if(!isspace(contents[c])) break;
627       if(!contents[c])
628          delete contents;
629    }
630    if(editing && !contents && !readOnly)
631       contents = CopyString($"[Add Text]");
632    return contents;
633 }
634
635 class APIPageNameSpace : APIPage
636 {
637    NameSpace * nameSpace;
638    Module module;
639
640    Module GetModule()
641    {
642       return module;
643    }
644
645    NameSpace * GetNameSpace()
646    {
647       return nameSpace;
648    }
649
650    void Generate(File f)
651    {
652       char nsName[1024], temp[1024];
653       NameSpace * ns;
654       BTNamedLink link;
655
656       nsName[0] = 0;
657       ns = nameSpace;
658       while(ns && ns->name)
659       {
660          strcpy(temp, ns->name);
661          if(nsName[0]) strcat(temp, "::");
662          strcat(temp, nsName);
663          strcpy(nsName, temp);
664          ns = ns->parent;
665       }
666       // Generate Class Page
667       f.Printf($"<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
668       if(nsName[0])
669       {
670          f.Printf("<FONT FACE=\"Arial\" SIZE=\"6\">%s</FONT><br><br>\n", nsName );
671          f.Printf($"Module: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", (module && module.name) ? module : null, (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
672       }
673       else
674          f.Printf($"<FONT FACE=\"Arial\" SIZE=\"6\">Module %s</FONT><br>\n", (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
675
676       nsName[0] = 0;
677       ns = nameSpace->parent;
678       while(ns && ns->name)
679       {
680          strcpy(temp, ns->name);
681          if(nsName[0]) strcat(temp, "::");
682          strcat(temp, nsName);
683          strcpy(nsName, temp);
684          ns = ns->parent;
685       }
686       if(nsName[0])
687          f.Printf($"Parent namespace: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", nameSpace->parent, nsName);
688
689       f.Printf("<br>");
690       {
691          char * desc = ReadDoc(module, nameSpaceDoc, nameSpace, description, null);
692          if(desc)
693          {
694             f.Printf($"<H3>Description</H3><br><br>\n");
695             if(editing)
696             {
697                char fileName[MAX_LOCATION];
698                FigureFileName(fileName, module, nameSpaceDoc, nameSpace, description, null);
699                f.Printf("<a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
700                f.Puts(desc);
701                f.Printf("</a><br><br>");
702             }
703             else
704                f.Printf("%s<br><br>", desc);
705             delete desc;
706          }
707       }
708
709       if(nameSpace->nameSpaces.first)
710       {
711          bool first = true;
712          for(ns = (NameSpace *)nameSpace->nameSpaces.first; ns; ns = (NameSpace *)((BTNode)ns).next)
713          {
714             char * desc = ReadDoc(module, nameSpaceDoc, ns, description, null);
715             if(first)
716             {
717                f.Printf($"<H3>Sub Namespaces</H3><br><br>\n");
718                f.Printf("<TABLE>\n");
719                first = false;
720             }
721             f.Printf("<TR>");
722             f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", iconNames[typeNameSpace], ns, ns->name);
723             if(desc)
724             {
725                if(editing)
726                {
727                   char fileName[MAX_LOCATION];
728                   FigureFileName(fileName, module, nameSpaceDoc, ns, description, null);
729                   f.Printf("<TD valign=top height=22> <a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
730                   f.Puts(desc);
731                   f.Printf("</a></TD>");
732                }
733                else
734                   f.Printf("<TD valign=top height=22> %s</TD>", desc);
735                delete desc;
736             }
737             f.Printf("</TR><br>\n");
738          }
739          if(!first)
740             f.Printf("</TABLE><br>\n");
741       }
742
743       if(nameSpace->classes.first)
744       {
745          bool first = true;
746          for(link = (BTNamedLink)nameSpace->classes.first; link; link = (BTNamedLink)((BTNode)link).next)
747          {
748             Class cl = link.data;
749             if(!cl.templateClass)
750             {
751                char * desc = ReadDoc(module, classDoc, cl, description, null);
752
753                if(first)
754                {
755                   f.Printf($"<a name=Classes></a><H3>Classes</H3><br><br>\n");
756                   f.Printf("<TABLE>\n");
757                   first = false;
758                }
759
760                f.Printf("<TR>");
761
762                f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", (cl.type == enumClass || cl.type == unitClass || cl.type == systemClass) ? iconNames[typeDataType] : iconNames[typeClass], cl, cl.name);
763                if(desc)
764                {
765                   if(editing)
766                   {
767                      char fileName[MAX_LOCATION];
768                      FigureFileName(fileName, module, classDoc, cl, description, null);
769                      f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
770                      f.Puts(desc);
771                      f.Printf("</a></TD>");
772                   }
773                   else
774                      f.Printf("<TD valign=top height=22>%s</TD>", desc);
775                   delete desc;
776                }
777                f.Printf("</TR>\n");
778             }
779          }
780          if(!first)
781             f.Printf("</TABLE><br>\n");
782       }
783
784       if(nameSpace->functions.first)
785       {
786          bool first = true;
787          for(link = (BTNamedLink)nameSpace->functions.first; link; link = (BTNamedLink)((BTNode)link).next)
788          {
789             GlobalFunction function = link.data;
790             char * desc = ReadDoc(module, functionDoc, function, description, null);
791             const char * name = RSearchString(function.name, "::", strlen(function.name), true, false);
792             if(name) name += 2; else name = function.name;
793             if(first)
794             {
795                f.Printf($"<a name=Functions></a><H3>Functions</H3><br><br>\n");
796                f.Printf("<TABLE>\n");
797                first = false;
798             }
799             f.Printf("<TR>");
800             f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", iconNames[typeMethod], function, name);
801             if(desc)
802             {
803                if(editing)
804                {
805                   char fileName[MAX_LOCATION];
806                   FigureFileName(fileName, module, functionDoc, function, description, null);
807                   f.Printf("<TD valign=top height=22> <a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
808                   f.Puts(desc);
809                   f.Printf("</a></TD>");
810                }
811                else
812                   f.Printf("<TD valign=top height=22> %s</TD>", desc);
813                delete desc;
814             }
815             f.Printf("</TR><br>\n");
816          }
817          if(!first)
818             f.Printf("</TABLE><br>\n");
819       }
820
821       if(nameSpace->defines.first)
822       {
823          bool first = true;
824          for(link = (BTNamedLink)nameSpace->defines.first; link; link = (BTNamedLink)((BTNode)link).next)
825          {
826             DefinedExpression def = link.data;
827             char * desc = ReadDoc(module, nameSpaceDoc, nameSpace, definition, def);
828             if(first)
829             {
830                f.Printf($"<a name=Definitions></a><H3>Definitions</H3><br><br>\n");
831                f.Printf("<TABLE>\n");
832                first = false;
833             }
834             f.Printf("<TR>");
835             f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", def, iconNames[typeData], def.name);
836             f.Printf("<TD valign=top height=22>%s</TD>", def.value);
837             if(desc)
838             {
839                if(editing)
840                {
841                   char fileName[MAX_LOCATION];
842                   FigureFileName(fileName, module, nameSpaceDoc, nameSpace, definition, def);
843                   f.Printf("<TD valign=top height=22> <a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
844                   f.Puts(desc);
845                   f.Printf("</a></TD>");
846                }
847                else
848                   f.Printf("<TD valign=top height=22> %s</TD>", desc);
849                delete desc;
850             }
851             f.Printf("</TR><br>\n");
852          }
853          if(!first)
854             f.Printf("</TABLE><br>\n");
855       }
856
857       f.Printf("</FONT></BODY></HTML>\n");
858    }
859 }
860
861 class APIPageClass : APIPage
862 {
863    Class cl;
864
865    Module GetModule()
866    {
867       return cl.module;
868    }
869
870    NameSpace * GetNameSpace()
871    {
872       return cl.nameSpace;
873    }
874
875    void Generate(File f)
876    {
877       char string[1024];
878       Method method;
879       Property prop;
880       char nsName[1024], temp[1024];
881       NameSpace * ns = cl.nameSpace;
882       Module module = cl.module;
883
884       nsName[0] = 0;
885       while(ns && ns->name)
886       {
887          strcpy(temp, ns->name);
888          if(nsName[0]) strcat(temp, "::");
889          strcat(temp, nsName);
890          strcpy(nsName, temp);
891          ns = ns->parent;
892       }
893       // Generate Class Page
894       f.Printf($"<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
895       f.Printf("<FONT FACE=\"Arial\" SIZE=\"6\">%s</FONT><br><br>\n", name);
896
897       f.Printf($"Module: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", (module && module.name) ? module : null, (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
898       if(nsName[0])
899          f.Printf($"Namespace: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", cl.nameSpace, nsName);
900
901       {
902          const char * classType = null;
903          switch(cl.type)
904          {
905             case bitClass:
906                classType = $"Bit Collection";
907                break;
908             case enumClass:
909                classType = $"Enumeration";
910                break;
911             case structClass:
912                classType = $"Structure";
913                break;
914             case normalClass:
915                classType = $"Class";
916                break;
917             case noHeadClass:
918                classType = $"Class (No header)";
919                break;
920             case unitClass:
921                classType = $"Unit";
922                break;
923             case systemClass:
924                classType = $"Basic Data Type";
925                break;
926          }
927          f.Printf($"Type: %s<br>\n", classType);
928       }
929
930       if(cl.type != systemClass && cl.base)
931       {
932          f.Printf($"Base Class: ");
933          if(!strcmp(cl.base.name, "struct") || !strcmp(cl.base.name, "class"))
934          {
935             f.Printf(cl.type == bitClass ? cl.dataTypeString : $"None");
936          }
937          else if(cl.type == enumClass && !strcmp(cl.base.name, "enum"))
938             f.Printf("%s", cl.dataTypeString);
939          else
940             f.Printf("<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a>", cl.base.templateClass ? cl.base.templateClass : cl.base, cl.base.name);
941          f.Printf("<br>\n");
942       }
943
944       {
945          char * desc = ReadDoc(module, classDoc, cl, description, null);
946          if(desc)
947          {
948             f.Printf($"<br><H3>Description</H3><br><br>\n");
949             if(editing)
950             {
951                char fileName[MAX_LOCATION];
952                FigureFileName(fileName, module, classDoc, cl, description, null);
953                f.Printf("<a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
954                f.Puts(desc);
955                f.Printf("</a><br><br>");
956             }
957             else
958                f.Printf("%s<br><br>", desc);
959             delete desc;
960          }
961       }
962
963       if(cl.type == enumClass)
964       {
965          EnumClassData enumeration = (EnumClassData)cl.data;
966          if(enumeration.values.first)
967          {
968             NamedLink item;
969
970             f.Printf($"<a name=EnumerationValues></a><H3>Enumeration Values</H3><br><br>\n");
971             f.Printf("<TABLE>\n");
972
973             for(item = enumeration.values.first; item; item = item.next)
974             {
975                char * desc = ReadDoc(module, classDoc, cl, enumerationValue, item);
976                bool needClass = true;
977                Class dataClass;
978                Class base = cl;
979                char tempString[1024];
980                String s;
981                while(base.type == enumClass) base = base.base;
982
983                if(base.type == systemClass ||
984                   (base.type == bitClass && base.membersAndProperties.first && !strcmp(cl.fullName, ((DataMember)base.membersAndProperties.first).dataTypeString)))
985                {
986                   if(!base.dataType)
987                      base.dataType = ProcessTypeString(base.dataTypeString, false);
988
989                   if(base.dataType.kind != classType)
990                   {
991                      char string[256];
992                      Symbol classSym;
993                      string[0] = '\0';
994                      PrintType(base.dataType, string, false, true);
995                      classSym = FindClass(string);
996                      dataClass = classSym ? classSym.registered : null;
997                   }
998                   else
999                      dataClass = base.dataType._class ? base.dataType._class.registered : null;
1000                }
1001                else
1002                   dataClass = base;
1003
1004                f.Printf("<TR>");
1005                f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", item, iconNames[typeEnumValue], item.name);
1006                if(dataClass.type == systemClass)
1007                {
1008                   needClass = false;
1009                   s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)dataClass._vTbl[__ecereVMethodID_class_OnGetString])(dataClass, &item.data, tempString, null, &needClass);
1010                }
1011                else
1012                   s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)eSystem_FindClass(componentsApp, "class")._vTbl[__ecereVMethodID_class_OnGetString])(dataClass, &item.data, tempString, null, &needClass);
1013                if(needClass)
1014                   f.Printf("<TD valign=top height=22 nowrap=1>%s { %s }</TD>", dataClass.name, s);
1015                else
1016                   f.Printf("<TD valign=top height=22 nowrap=1>%s</TD>", s);
1017                if(desc)
1018                {
1019                   if(editing)
1020                   {
1021                      char fileName[MAX_LOCATION];
1022                      FigureFileName(fileName, module, classDoc, cl, enumerationValue, item);
1023                      f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1024                      f.Puts(desc);
1025                      f.Printf("</a></TD>");
1026                   }
1027                   else
1028                      f.Printf("<TD valign=top height=22>%s</TD>", desc);
1029                   delete desc;
1030                }
1031                f.Printf("</TR>");
1032             }
1033             f.Printf("</TABLE><BR>\n");
1034          }
1035       }
1036
1037       if(cl.conversions.first)
1038       {
1039          f.Printf($"<a name=Conversions></a><H3>Conversions</H3><br><br>\n");
1040          f.Printf("<TABLE>\n");
1041          for(prop = cl.conversions.first; prop; prop = prop.next)
1042          {
1043             if((prop.memberAccess == publicAccess || (prop.memberAccess == privateAccess && showPrivate)) && prop.name)
1044             {
1045                char * desc = ReadDoc(module, classDoc, cl, conversion, prop);
1046                const char * name;
1047                Type type = ProcessTypeString(prop.name, false);
1048                name = RSearchString(prop.name, "::", strlen(prop.name), true, false);
1049                if(name) name += 2; else name = prop.name;
1050
1051                f.Printf("<TR>");
1052
1053                string[0] = 0;
1054                DocPrintType(type, string, true, false);
1055
1056                f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", prop, iconNames[typeDataType], string);
1057                if(desc)
1058                {
1059                   if(editing)
1060                   {
1061                      char fileName[MAX_LOCATION];
1062                      FigureFileName(fileName, module, classDoc, cl, conversion, prop);
1063                      f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1064                      f.Puts(desc);
1065                      f.Printf("</a></TD>");
1066                   }
1067                   else
1068                      f.Printf("<TD valign=top height=22>%s</TD>", desc);
1069                   delete desc;
1070                }
1071
1072                f.Printf("</TR>\n");
1073
1074                FreeType(type);
1075             }
1076          }
1077          f.Printf("</TABLE><br>\n");
1078       }
1079
1080       if(cl.membersAndProperties.first)
1081       {
1082          bool first = true;
1083          for(prop = (Property)cl.membersAndProperties.first; prop; prop = prop.next)
1084          {
1085             if(prop.memberAccess == publicAccess || (prop.memberAccess == privateAccess && showPrivate))
1086             {
1087                if(first)
1088                {
1089                   f.Printf($"<a name=Members></a><H3>Properties and Members</H3><br><br>\n");
1090                   f.Printf("<TABLE>\n");
1091                   first = false;
1092                }
1093
1094                if(prop.isProperty)
1095                {
1096                   char * desc = ReadDoc(module, classDoc, cl, propertyDescription, prop);
1097                   if(!prop.dataType)
1098                      prop.dataType = ProcessTypeString(prop.dataTypeString, false);
1099
1100                   f.Printf("<TR>");
1101                   string[0] = 0;
1102                   DocPrintType(prop.dataType, string, true, false);
1103
1104                   f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a><img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", prop, iconNames[typeProperty], prop.name);
1105                   f.Printf("<TD valign=top height=22 nowrap=1>%s</TD>", string);
1106                   if(desc)
1107                   {
1108                      if(editing)
1109                      {
1110                         char fileName[MAX_LOCATION];
1111                         FigureFileName(fileName, module, classDoc, cl, propertyDescription, prop);
1112                         f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1113                         f.Puts(desc);
1114                         f.Printf("</a></TD>");
1115                      }
1116                      else
1117                         f.Printf("<TD valign=top height=22>%s</TD>", desc);
1118                      delete desc;
1119                   }
1120                   f.Printf("</TR>\n");
1121                }
1122                else
1123                {
1124                   AddDataMemberToPage(f, (DataMember)prop, 0, showPrivate);
1125                }
1126             }
1127          }
1128          if(!first)
1129             f.Printf("</TABLE><br>\n");
1130       }
1131
1132       if(cl.methods.first)
1133       {
1134          bool first = true;
1135          // Virtual Methods
1136          for(method = (Method)cl.methods.first; method; method = (Method)((BTNode)method).next)
1137          {
1138             if((method.memberAccess == publicAccess || (method.memberAccess == privateAccess && showPrivate)) && method.type == virtualMethod)
1139             {
1140                char * desc = ReadDoc(module, methodDoc, method, description, null);
1141                if(first)
1142                {
1143                   f.Printf($"<a name=VirtualMethods></a><H3>Virtual Methods</H3><br><br>\n");
1144                   f.Printf("<TABLE>\n");
1145                   first = false;
1146                }
1147                if(!method.dataType)
1148                   ProcessMethodType(method);
1149
1150                f.Printf("<TR>");
1151                f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", method.dataType.thisClass ? iconNames[typeEvent] : iconNames[typeMethod], method, method.name);
1152                if(desc)
1153                {
1154                   if(editing)
1155                   {
1156                      char fileName[MAX_LOCATION];
1157                      FigureFileName(fileName, module, methodDoc, method, description, null);
1158                      f.Printf("<TD valign=top height=22> <a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1159                      f.Puts(desc);
1160                      f.Printf("</a></TD>");
1161                   }
1162                   else
1163                      f.Printf("<TD valign=top height=22> %s</TD>", desc);
1164                   delete desc;
1165                }
1166                f.Printf("</TR><br>\n");
1167             }
1168          }
1169          if(!first)
1170             f.Printf("</TABLE><br>\n");
1171
1172          // Non-Virtual Methods
1173          first = true;
1174          for(method = (Method)cl.methods.first; method; method = (Method)((BTNode)method).next)
1175          {
1176             if((method.memberAccess == publicAccess || (method.memberAccess == privateAccess && showPrivate)) && method.type != virtualMethod)
1177             {
1178                char * desc = ReadDoc(module, methodDoc, method, description, null);
1179                if(first)
1180                {
1181                   f.Printf($"<a name=Methods></a><H3>Non-Virtual Methods</H3><br><br>\n");
1182                   f.Printf("<TABLE>\n");
1183                   first = false;
1184                }
1185
1186                if(!method.dataType)
1187                   ProcessMethodType(method);
1188
1189                f.Printf("<TR>");
1190                f.Printf("<TD valign=top height=22 nowrap=1><img valign=center src=\"%s\">&nbsp;&nbsp;<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a></TD>", iconNames[typeMethod], method, method.name);
1191                if(desc)
1192                {
1193                   if(editing)
1194                   {
1195                      char fileName[MAX_LOCATION];
1196                      FigureFileName(fileName, module, methodDoc, method, description, null);
1197                      f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1198                      f.Puts(desc);
1199                      f.Printf("</a></TD>");
1200                   }
1201                   else
1202                      f.Printf("<TD valign=top height=22>%s</TD>", desc);
1203                   delete desc;
1204                }
1205
1206                f.Printf("</TR><br>\n");
1207             }
1208          }
1209          if(!first)
1210             f.Printf("</TABLE><br>\n");
1211       }
1212       {
1213          char * usageDoc = ReadDoc(module, classDoc, cl, usage, null);
1214          if(usageDoc)
1215          {
1216             f.Printf($"<H3>Usage</H3><br>\n");
1217             if(editing)
1218             {
1219                char fileName[MAX_LOCATION];
1220                FigureFileName(fileName, module, classDoc, cl, usage, null);
1221                f.Printf("<br><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1222                f.Puts(usageDoc);
1223                f.Printf("</a>\n");
1224             }
1225             else
1226                f.Printf("<br>%s\n", usageDoc);
1227             f.Printf("<br><br>\n");
1228             delete usageDoc;
1229          }
1230       }
1231       {
1232          char * exampleDoc = ReadDoc(module, classDoc, cl, example, null);
1233          if(exampleDoc)
1234          {
1235             f.Printf($"<H3>Example</H3><br>\n");
1236             f.Printf($"<FONT face=\"Courier New\">\n");
1237             f.Printf("<br><TABLE>\n");
1238             if(editing)
1239             {
1240                char fileName[MAX_LOCATION];
1241                FigureFileName(fileName, module, classDoc, cl, example, null);
1242                f.Printf("<TR><TD><CODE><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1243                f.Puts(exampleDoc);
1244                f.Printf("</a></CODE></TD></TR>\n"); // bgcolor=#CFC9C0
1245             }
1246             else
1247                f.Printf("<TR><TD><CODE>%s</CODE></TD></TR>\n", exampleDoc);   // bgcolor=#CFC9C0
1248
1249             f.Printf("</TABLE></FONT>\n");
1250             f.Printf("<br>\n");
1251             delete exampleDoc;
1252          }
1253       }
1254       {
1255          char * remarksDoc = ReadDoc(module, classDoc, cl, remarks, null);
1256
1257          if(remarksDoc)
1258          {
1259             f.Printf($"<H3>Remarks</H3><br>\n");
1260             if(editing)
1261             {
1262                char fileName[MAX_LOCATION];
1263                FigureFileName(fileName, module, classDoc, cl, remarks, null);
1264                f.Printf("<br><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1265                f.Puts(remarksDoc);
1266                f.Printf("</a>\n");
1267             }
1268             else
1269                f.Printf("<br>%s\n", remarksDoc);
1270             f.Printf("<br><br>\n");
1271             delete remarksDoc;
1272          }
1273       }
1274
1275       if(cl.type != systemClass)
1276       {
1277          bool first = true;
1278          OldLink c;
1279          for(c = cl.derivatives.first; c; c = c.next)
1280          {
1281             Class deriv = c.data;
1282             // TO VERIFY: Does this properly check public status?
1283             if(eSystem_FindClass(componentsApp, deriv.fullName))
1284             {
1285                if(first)
1286                {
1287                   f.Printf($"<H3>Derived Classes</H3><br>\n");
1288                   f.Printf("<br>");
1289                   first = false;
1290                }
1291                else
1292                   f.Printf(", ");
1293                f.Printf("<a href=\"api://%p\" style=\"text-decoration: none;\">%s</a>", deriv, deriv.name);
1294              }
1295          }
1296          if(!first)
1297             f.Printf("<br><br>\n");
1298       }
1299       {
1300          char * seeAlsoDoc = ReadDoc(module, classDoc, cl, seeAlso, null);
1301          if(seeAlsoDoc)
1302          {
1303             f.Printf($"<H3>See Also</H3><br>\n");
1304             if(editing)
1305             {
1306                char fileName[MAX_LOCATION];
1307                FigureFileName(fileName, module, classDoc, cl, seeAlso, null);
1308                f.Printf("<br><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1309                f.Puts(seeAlsoDoc);
1310                f.Printf("</a>\n");
1311             }
1312             else
1313                f.Printf("<br>%s\n", seeAlsoDoc);
1314             f.Printf("<br><br>\n");
1315             delete seeAlsoDoc;
1316          }
1317       }
1318       f.Printf("</FONT></BODY></HTML>\n");
1319    }
1320 }
1321
1322 class APIPageMethod : APIPage
1323 {
1324    Method method;
1325
1326    Module GetModule()
1327    {
1328       return method._class.module;
1329    }
1330
1331    NameSpace * GetNameSpace()
1332    {
1333       return method._class.nameSpace;
1334    }
1335
1336    void Generate(File f)
1337    {
1338       Class cl = method._class;
1339       char string[1024];
1340       Module module = cl.module;
1341       Type param;
1342       char nsName[1024], temp[1024];
1343       NameSpace * ns = cl.nameSpace;
1344
1345       nsName[0] = 0;
1346       while(ns && ns->name)
1347       {
1348          strcpy(temp, ns->name);
1349          if(nsName[0]) strcat(temp, "::");
1350          strcat(temp, nsName);
1351          strcpy(nsName, temp);
1352          ns = ns->parent;
1353       }
1354
1355       f.Printf($"<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
1356       f.Printf("<FONT FACE=\"Arial\" SIZE=\"6\">%s</FONT><br><br>\n", name);
1357
1358       f.Printf($"Module: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", (module && module.name) ? module : null, (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
1359       if(nsName[0])
1360          f.Printf($"Namespace: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", cl.nameSpace, nsName);
1361       f.Printf("Class: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", cl, cl.name);
1362       if(method.dataType.staticMethod)
1363       {
1364          f.Printf($"this pointer class: None<br>\n");
1365       }
1366       else if(method.dataType.thisClass && method.dataType.thisClass.registered && (method.dataType.thisClass.registered != method._class || method.type == virtualMethod))
1367       {
1368          f.Printf($"this pointer class: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", method.dataType.thisClass.registered, method.dataType.thisClass.registered.name);
1369       }
1370
1371       // Generate Method Page
1372       string[0] = 0;
1373       if(!method.dataType.name)
1374          method.dataType.name = CopyString(method.name);
1375       DocPrintType(method.dataType, string, true, false);
1376       f.Printf("<br>%s", string);
1377
1378       {
1379          char * desc = ReadDoc(module, methodDoc, method, description, null);
1380          if(desc)
1381          {
1382             f.Printf($"<br><br><H3>Description</H3><br><br>\n");
1383             if(editing)
1384             {
1385                char fileName[MAX_LOCATION];
1386                FigureFileName(fileName, module, methodDoc, method, description, null);
1387                f.Printf("<a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1388                f.Puts(desc);
1389                f.Printf("</a>");
1390             }
1391             else
1392                f.Printf("%s", desc);
1393             delete desc;
1394          }
1395       }
1396
1397       f.Printf("<br><br>\n");
1398       if(method.dataType.params.first && ((Type)method.dataType.params.first).kind != voidType)
1399       {
1400          f.Printf($"<H3>Parameters</H3><br><br>\n");
1401       }
1402       if((method.dataType.returnType && method.dataType.returnType.kind != voidType) ||
1403          (method.dataType.params.first && ((Type)method.dataType.params.first).kind != voidType))
1404       {
1405          f.Printf("<TABLE  valign=center>\n");
1406       }
1407
1408       for(param = method.dataType.params.first; param; param = param.next)
1409       {
1410          // ADD DESCRIPTION HERE
1411          if(param.kind != voidType)
1412          {
1413             char * desc = ReadDoc(module, methodDoc, method, parameter, param);
1414             f.Printf("<TR>");
1415             string[0] = 0;
1416             DocPrintType(param, string, false, false);
1417
1418             f.Printf("<TD valign=top height=22 nowrap=1>%s&nbsp;</TD>\n", param.name ? param.name : "");
1419             f.Printf("<TD valign=top height=22 nowrap=1>%s&nbsp;</TD>\n", string);
1420             if(desc)
1421             {
1422                if(editing)
1423                {
1424                   char fileName[MAX_LOCATION];
1425                   FigureFileName(fileName, module, methodDoc, method, parameter, param);
1426                   f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1427                   f.Puts(desc);
1428                   f.Printf("</a></TD>\n");
1429                }
1430                else
1431                   f.Printf("<TD valign=top height=22>%s&nbsp;</TD>\n", desc);
1432                delete desc;
1433             }
1434
1435             f.Printf("</TR>\n");
1436          }
1437       }
1438       if(method.dataType.returnType && method.dataType.returnType.kind != voidType)
1439       {
1440          char * desc = ReadDoc(module, methodDoc, method, returnValue, null);
1441          if(method.dataType.params.first && ((Type)method.dataType.params.first).kind != voidType)
1442          {
1443             f.Printf("<TR><TD>&nbsp;</TD></TR>");
1444          }
1445          f.Printf("<TR>");
1446          f.Printf($"<TD valign=top height=22 nowrap=1><B>Return Value</B></TD>\n");
1447          string[0] = 0;
1448          DocPrintType(method.dataType.returnType, string, false, false);
1449          f.Printf("<TD valign=top height=22>%s&nbsp;</TD>\n", string);
1450          if(desc)
1451          {
1452             if(editing)
1453             {
1454                char fileName[MAX_LOCATION];
1455                FigureFileName(fileName, module, methodDoc, method, returnValue, null);
1456                f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1457                f.Puts(desc);
1458                f.Printf("</a>&nbsp;</TD>\n");
1459             }
1460             else
1461                f.Printf("<TD valign=top height=22>%s&nbsp;</TD>\n", desc);
1462             delete desc;
1463          }
1464          f.Printf("</TR>\n");
1465          f.Printf("</TABLE>\n");
1466       }
1467       if((method.dataType.returnType && method.dataType.returnType.kind != voidType) ||
1468          (method.dataType.params.first && ((Type)method.dataType.params.first).kind != voidType))
1469       {
1470          f.Printf("</TABLE><br>\n");
1471       }
1472       {
1473          char * usageDoc = ReadDoc(module, methodDoc, method, usage, null);
1474          if(usageDoc)
1475          {
1476             f.Printf($"<H3>Usage</H3><br>\n");
1477             if(editing)
1478             {
1479                char fileName[MAX_LOCATION];
1480                FigureFileName(fileName, module, methodDoc, method, usage, null);
1481                f.Printf("<br><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1482                f.Puts(usageDoc);
1483                f.Printf("</a>\n");
1484             }
1485             else
1486                f.Printf("<br>%s\n", usageDoc);
1487             f.Printf("<br><br>\n");
1488             delete usageDoc;
1489          }
1490       }
1491       {
1492          char * exampleDoc = ReadDoc(module, methodDoc, method, example, null);
1493          if(exampleDoc)
1494          {
1495             f.Printf($"<H3>Example</H3><br>\n");
1496             f.Printf($"<FONT face=\"Courier New\">\n");
1497             f.Printf("<br><TABLE>\n");
1498             if(editing)
1499             {
1500                char fileName[MAX_LOCATION];
1501                FigureFileName(fileName, module, methodDoc, method, example, null);
1502                f.Printf("<TR><TD><CODE><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1503                f.Puts(exampleDoc);
1504                f.Printf("</a></CODE></TD></TR>\n");   // bgcolor=#CFC9C0
1505             }
1506             else
1507                f.Printf("<TR><TD><CODE>%s</CODE></TD></TR>\n", exampleDoc);   // bgcolor=#CFC9C0
1508             f.Printf("</TABLE></FONT>\n");
1509             f.Printf("<br>\n");
1510             delete exampleDoc;
1511          }
1512       }
1513       {
1514          char * remarksDoc = ReadDoc(module, methodDoc, method, remarks, null);
1515          if(remarksDoc)
1516          {
1517             f.Printf($"<H3>Remarks</H3><br>\n");
1518             if(editing)
1519             {
1520                char fileName[MAX_LOCATION];
1521                FigureFileName(fileName, module, methodDoc, method, remarks, null);
1522                f.Printf("<br><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1523                f.Puts(remarksDoc);
1524                f.Printf("</a>\n");
1525             }
1526             else
1527                f.Printf("<br>%s\n", method, remarksDoc);
1528             f.Printf("<br><br>\n");
1529             delete remarksDoc;
1530          }
1531       }
1532       {
1533          char * seeAlsoDoc = ReadDoc(module, methodDoc, method, seeAlso, null);
1534          if(seeAlsoDoc)
1535          {
1536             f.Printf($"<H3>See Also</H3><br>\n");
1537             if(editing)
1538             {
1539                char fileName[MAX_LOCATION];
1540                FigureFileName(fileName, module, methodDoc, method, seeAlso, null);
1541                f.Printf("<br><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1542                f.Puts(seeAlsoDoc);
1543                f.Printf("</a>\n");
1544             }
1545             else
1546                f.Printf("<br>%s\n", method, seeAlsoDoc);
1547
1548             f.Printf("<br><br>\n");
1549             delete seeAlsoDoc;
1550          }
1551       }
1552       f.Printf("</FONT></BODY></HTML>\n");
1553    }
1554 }
1555
1556 class APIPageFunction : APIPage
1557 {
1558    GlobalFunction function;
1559
1560    Module GetModule()
1561    {
1562       return function.module;
1563    }
1564
1565    NameSpace * GetNameSpace()
1566    {
1567       return function.nameSpace;
1568    }
1569
1570    void Generate(File f)
1571    {
1572       char string[1024];
1573       Module module = function.module;
1574       Type param;
1575       char nsName[1024], temp[1024];
1576       NameSpace * ns = function.nameSpace;
1577
1578       nsName[0] = 0;
1579       while(ns && ns->name)
1580       {
1581          strcpy(temp, ns->name);
1582          if(nsName[0]) strcat(temp, "::");
1583          strcat(temp, nsName);
1584          strcpy(nsName, temp);
1585          ns = ns->parent;
1586       }
1587
1588       f.Printf($"<HTML><HEAD><TITLE>API Reference</TITLE></HEAD>\n<BODY><FONT SIZE=\"3\">\n");
1589       f.Printf("<FONT FACE=\"Arial\" SIZE=\"6\">%s</FONT><br><br>\n", name);
1590
1591       f.Printf($"Module: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", (module && module.name) ? module : null, (!module || !module.name || !strcmp(nsName, "ecere::com")) ? "ecereCOM" : module.name);
1592
1593       if(nsName[0])
1594          f.Printf($"Namespace: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", function.nameSpace, nsName);
1595
1596       if(!function.dataType)
1597          function.dataType = ProcessTypeString(function.dataTypeString, false);
1598
1599       if(function.dataType.thisClass && function.dataType.thisClass.registered)
1600       {
1601          f.Printf($"this pointer class: <a href=\"api://%p\" style=\"text-decoration: none;\">%s</a><br>\n", function.dataType.thisClass.registered, function.dataType.thisClass.registered.name);
1602       }
1603
1604       // Generate Method Page
1605       string[0] = 0;
1606       if(!function.dataType.name)
1607          function.dataType.name = CopyString(function.name);
1608       DocPrintType(function.dataType, string, true, false);
1609       f.Printf("<br>%s", string);
1610
1611       {
1612          char * desc = ReadDoc(module, functionDoc, function, description, null);
1613          if(desc)
1614          {
1615             f.Printf($"<br><br><H3>Description</H3><br><br>\n");
1616             if(editing)
1617             {
1618                char fileName[MAX_LOCATION];
1619                FigureFileName(fileName, module, functionDoc, function, description, null);
1620                f.Printf("<a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1621                f.Puts(desc);
1622                f.Printf("</a>");
1623             }
1624             else
1625                f.Printf("%s", desc);
1626             delete desc;
1627          }
1628       }
1629       f.Printf("<br><br>\n");
1630       if(function.dataType.params.first && ((Type)function.dataType.params.first).kind != voidType)
1631       {
1632          f.Printf($"<H3>Parameters</H3><br><br>\n");
1633       }
1634       if((function.dataType.returnType && function.dataType.returnType.kind != voidType) ||
1635          (function.dataType.params.first && ((Type)function.dataType.params.first).kind != voidType))
1636       {
1637          f.Printf("<TABLE  valign=center>\n");
1638       }
1639
1640       for(param = function.dataType.params.first; param; param = param.next)
1641       {
1642          // ADD DESCRIPTION HERE
1643          if(param.kind != voidType)
1644          {
1645             char * desc = ReadDoc(module, functionDoc, function, parameter, param);
1646             f.Printf("<TR>");
1647             string[0] = 0;
1648             DocPrintType(param, string, false, false);
1649
1650             f.Printf("<TD valign=top height=22 nowrap=1>%s&nbsp;</TD>\n", param.name ? param.name : "");
1651             f.Printf("<TD valign=top height=22 nowrap=1>%s&nbsp;</TD>\n", string);
1652             if(param)
1653             {
1654                if(editing)
1655                {
1656                   char fileName[MAX_LOCATION];
1657                   FigureFileName(fileName, module, functionDoc, function, parameter, param);
1658                   f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1659                   if(desc)
1660                      f.Puts(desc);
1661                   f.Printf("</a>&nbsp;</TD>\n");
1662                }
1663                else
1664                   f.Printf("<TD valign=top height=22>%s&nbsp;</TD>\n", desc);
1665                delete desc;
1666             }
1667             f.Printf("</TR>\n");
1668          }
1669       }
1670       if(function.dataType.returnType && function.dataType.returnType.kind != voidType)
1671       {
1672          char * desc = ReadDoc(module, functionDoc, function, returnValue, null);
1673          if(function.dataType.params.first && ((Type)function.dataType.params.first).kind != voidType)
1674          {
1675             f.Printf("<TR><TD>&nbsp;</TD></TR>");
1676          }
1677          f.Printf("<TR>");
1678          f.Printf($"<TD valign=top height=22 nowrap=1><B>Return Value</B></TD>\n");
1679          string[0] = 0;
1680          DocPrintType(function.dataType.returnType, string, false, false);
1681          f.Printf("<TD valign=top height=22>%s&nbsp;</TD>\n", string);
1682          if(desc)
1683          {
1684             if(editing)
1685             {
1686                char fileName[MAX_LOCATION];
1687                FigureFileName(fileName, module, functionDoc, function, returnValue, null);
1688                f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1689                f.Puts(desc);
1690                f.Printf("</a>&nbsp;</TD>\n");
1691             }
1692             else
1693                f.Printf("<TD valign=top height=22>%s&nbsp;</TD>\n", function, desc);
1694             delete desc;
1695          }
1696          f.Printf("</TR>\n");
1697          f.Printf("</TABLE>\n");
1698       }
1699       if((function.dataType.returnType && function.dataType.returnType.kind != voidType) ||
1700          (function.dataType.params.first && ((Type)function.dataType.params.first).kind != voidType))
1701       {
1702          f.Printf("</TABLE><br>\n");
1703       }
1704       {
1705          char * usageDoc = ReadDoc(module, functionDoc, function, usage, null);
1706          if(usageDoc)
1707          {
1708             f.Printf($"<H3>Usage</H3><br>\n");
1709             if(editing)
1710             {
1711                char fileName[MAX_LOCATION];
1712                FigureFileName(fileName, module, functionDoc, function, usage, null);
1713                f.Printf("<br><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1714                f.Puts(usageDoc);
1715                f.Printf("</a>\n");
1716             }
1717             else
1718                f.Printf("<br>%s\n", usageDoc);
1719             f.Printf("<br><br>\n");
1720             delete usageDoc;
1721          }
1722       }
1723       {
1724          char * exampleDoc = ReadDoc(module, functionDoc, function, example, null);
1725          if(exampleDoc)
1726          {
1727             f.Printf($"<H3>Example</H3><br>\n");
1728             f.Printf($"<FONT face=\"Courier New\">\n");
1729             f.Printf("<br><TABLE>\n");
1730             if(editing)
1731             {
1732                char fileName[MAX_LOCATION];
1733                FigureFileName(fileName, module, functionDoc, function, example, null);
1734                f.Printf("<TR><TD><CODE><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1735                f.Puts(exampleDoc);
1736                f.Printf("</a></CODE></TD></TR>\n");   // bgcolor=#CFC9C0
1737             }
1738             else
1739                f.Printf("<TR><TD><CODE>%s</CODE></TD></TR>\n", exampleDoc);   // bgcolor=#CFC9C0
1740             f.Printf("</TABLE></FONT>\n");
1741             f.Printf("<br>\n");
1742             delete exampleDoc;
1743          }
1744       }
1745       {
1746          char * remarksDoc = ReadDoc(module, functionDoc, function, remarks, null);
1747          if(remarksDoc)
1748          {
1749             f.Printf($"<H3>Remarks</H3><br>\n");
1750             if(editing)
1751             {
1752                char fileName[MAX_LOCATION];
1753                FigureFileName(fileName, module, functionDoc, function, remarks, null);
1754                f.Printf("<br><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1755                f.Puts(remarksDoc);
1756                f.Printf("</a>\n");
1757             }
1758             else
1759                f.Printf("<br>%s\n", remarksDoc);
1760             f.Printf("<br><br>\n");
1761             delete remarksDoc;
1762          }
1763       }
1764       {
1765          char * seeAlsoDoc = ReadDoc(module, functionDoc, function, seeAlso, null);
1766          if(seeAlsoDoc)
1767          {
1768             f.Printf($"<H3>See Also</H3><br>\n");
1769             if(editing)
1770             {
1771                char fileName[MAX_LOCATION];
1772                FigureFileName(fileName, module, functionDoc, function, seeAlso, null);
1773                f.Printf("<br><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1774                f.Puts(seeAlsoDoc);
1775                f.Printf("</a>\n");
1776             }
1777             else
1778                f.Printf("<br>%s\n", seeAlsoDoc);
1779             f.Printf("<br><br>\n");
1780             delete seeAlsoDoc;
1781          }
1782       }
1783       f.Printf("</FONT></BODY></HTML>\n");
1784    }
1785 }
1786
1787 static void AddNameSpace(DataRow parentRow, Module module, NameSpace mainNameSpace, NameSpace comNameSpace, const char * parentName, bool showPrivate)
1788 {
1789    char nsName[1024];
1790    NameSpace * ns;
1791    NameSpace * nameSpace = mainNameSpace;
1792    DataRow row;
1793    DataRow classesRow = null;
1794    DataRow functionsRow = null, definesRow = null;
1795    APIPage page;
1796
1797    strcpy(nsName, parentName ? parentName : "");
1798    if(nameSpace->name)
1799    {
1800       if(nsName[0])
1801          strcat(nsName, "::");
1802       strcat(nsName, nameSpace->name);
1803    }
1804
1805    if(nsName[0])
1806    {
1807       row = parentRow.AddRow();
1808       row.SetData(null, (page = APIPageNameSpace { nameSpace->name, module = module, nameSpace = nameSpace, showPrivate = showPrivate }));
1809       row.tag = (int64)nameSpace;
1810       row.icon = mainForm.icons[typeNameSpace];
1811    }
1812    else
1813    {
1814       // "Global NameSpace"
1815       row = parentRow;
1816       page = parentRow.GetData(null);
1817    }
1818
1819    for(ns = (NameSpace *)mainNameSpace.nameSpaces.first; ns; ns = (NameSpace *)((BTNode)ns).next)
1820    {
1821       NameSpace * comNS = (comNameSpace != null) ? (NameSpace *)comNameSpace.nameSpaces.FindString(ns->name) : null;
1822       AddNameSpace(row, module, ns, comNS, nsName, showPrivate);
1823    }
1824    if(comNameSpace != null)
1825    {
1826       for(ns = (NameSpace *)comNameSpace.nameSpaces.first; ns; ns = (NameSpace *)((BTNode)ns).next)
1827       {
1828          if(!mainNameSpace.nameSpaces.FindString(ns->name))
1829          {
1830             AddNameSpace(row, module, ns, null, nsName, showPrivate);
1831          }
1832       }
1833    }
1834
1835    if(mainNameSpace.classes.first || (comNameSpace && comNameSpace.classes.first))
1836    {
1837       for(nameSpace = mainNameSpace ; nameSpace; nameSpace = (nameSpace == mainNameSpace) ? comNameSpace : null)
1838       {
1839          if(nameSpace->classes.first)
1840          {
1841             BTNamedLink link;
1842             Class cl;
1843             for(link = (BTNamedLink)nameSpace->classes.first; link; link = (BTNamedLink)((BTNode)link).next)
1844             {
1845                cl = link.data;
1846                if(!cl.templateClass && (!module || cl.module == module || (!cl.module.name && !strcmp(module.name, "ecere"))))
1847                {
1848                   if(!classesRow) { classesRow = row.AddRow(); classesRow.SetData(null, APIPage { $"Classes", page = page }); classesRow.collapsed = true; classesRow.icon = mainForm.icons[typeClass]; classesRow.tag = 1; }
1849                   AddClass(classesRow, module, cl, nsName, showPrivate);
1850                }
1851             }
1852          }
1853       }
1854    }
1855
1856    if(mainNameSpace.functions.first || (comNameSpace && comNameSpace.functions.first))
1857    {
1858       for(nameSpace = mainNameSpace ; nameSpace; nameSpace = (nameSpace == mainNameSpace) ? comNameSpace : null)
1859       {
1860          if(nameSpace->functions.first)
1861          {
1862             BTNamedLink link;
1863             GlobalFunction fn;
1864             for(link = (BTNamedLink)nameSpace->functions.first; link; link = (BTNamedLink)((BTNode)link).next)
1865             {
1866                fn = link.data;
1867                if(!module || fn.module == module || (!fn.module.name && !strcmp(module.name, "ecere")))
1868                {
1869                   const char * name = ( name = RSearchString(fn.name, "::", strlen(fn.name), false, false), name ? name + 2 : fn.name);
1870                   DataRow fnRow;
1871                   if(!functionsRow) { functionsRow = row.AddRow(); functionsRow.SetData(null, APIPage { $"Functions", page = page }); functionsRow.collapsed = true; functionsRow.icon = mainForm.icons[typeMethod];  functionsRow.tag = 2; };
1872                   fnRow = functionsRow.AddRow(); fnRow.SetData(null, APIPageFunction { name, function = fn }); fnRow.icon = mainForm.icons[typeMethod]; fnRow.tag = (int64)fn;
1873                }
1874             }
1875          }
1876       }
1877    }
1878
1879    if(mainNameSpace.defines.first || (comNameSpace && comNameSpace.defines.first))
1880    {
1881       for(nameSpace = mainNameSpace ; nameSpace; nameSpace = (nameSpace == mainNameSpace) ? comNameSpace : null)
1882       {
1883          if(nameSpace->defines.first)
1884          {
1885             BTNamedLink link;
1886             Definition def;
1887             for(link = (BTNamedLink)nameSpace->defines.first; link; link = (BTNamedLink)((BTNode)link).next)
1888             {
1889                def = link.data;
1890                //if(def.module == module)
1891                {
1892                   char * name = ( name = RSearchString(def.name, "::", strlen(def.name), false, false), name ? name + 2 : def.name);
1893                   DataRow defRow;
1894                   if(!definesRow) { definesRow = row.AddRow(); definesRow.SetData(null, APIPage { $"Definitions", page = page }); definesRow.collapsed = true; definesRow.icon = mainForm.icons[typeData]; definesRow.tag = 3; };
1895                   defRow = definesRow.AddRow(); defRow.SetData(null, APIPage { name, page = page }); defRow.icon = mainForm.icons[typeData]; defRow.tag = (int64)def;
1896                }
1897             }
1898          }
1899       }
1900    }
1901 }
1902
1903 static void AddDataMemberToPage(File f, DataMember member, int indent, bool showPrivate)
1904 {
1905    char string[1024];
1906    int c;
1907    if(!member.dataType)
1908       member.dataType = ProcessTypeString(member.dataTypeString, false);
1909
1910    f.Printf("<TR>");
1911    string[0] = 0;
1912    DocPrintType(member.dataType, string, true, false);
1913
1914    f.Printf("<TD valign=top height=22 nowrap=1><a name=%p></a>", member);
1915    for(c = 0; c<indent; c++)
1916       f.Printf("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
1917    f.Printf("<img valign=center src=\"%s\">&nbsp;&nbsp;%s</TD>", iconNames[typeData], member.name ? member.name : ((member.type == structMember) ? "(struct)" : "(union)"));
1918    f.Printf("<TD valign=top height=22 nowrap=1>%s</TD>", (member.type == normalMember) ? string : "");
1919    if(member.type == normalMember)
1920    {
1921       char * desc = ReadDoc(member._class.module, classDoc, member._class, memberDescription, member);
1922       if(desc)
1923       {
1924          if(editing)
1925          {
1926             char fileName[MAX_LOCATION];
1927             FigureFileName(fileName, member._class.module, classDoc, member._class, memberDescription, member);
1928             f.Printf("<TD valign=top height=22><a style=\"text-decoration:none;\" href=\"edit://%s\">", fileName);
1929             f.Puts(desc);
1930             f.Printf("</a></TD>");
1931          }
1932          else
1933             f.Printf("<TD valign=top height=22>%s</TD>", desc);
1934          delete desc;
1935       }
1936    }
1937    else
1938       f.Printf("<TD valign=top height=22></TD>");
1939
1940    if(member.type != normalMember)
1941    {
1942       DataMember subMember;
1943       for(subMember = member.members.first; subMember; subMember = subMember.next)
1944       {
1945          if((subMember.memberAccess == publicAccess || (subMember.memberAccess == privateAccess && showPrivate)))
1946          {
1947             AddDataMemberToPage(f, subMember, indent + 1, showPrivate);
1948          }
1949       }
1950    }
1951    f.Printf("</TR><br>\n");
1952 }
1953
1954 static void AddDataMember(DataRow parentRow, APIPage page, DataMember member)
1955 {
1956    DataRow row;
1957    if(member.type == normalMember)
1958    {
1959       row = parentRow.AddRow(); row.SetData(null, APIPage { member.name, page = page }); row.icon = mainForm.icons[typeData];
1960       row.tag = (int64)member;
1961    }
1962    else
1963    {
1964       DataMember m;
1965       row = parentRow.AddRow(); row.SetData(null, APIPage { (member.type == unionMember) ? "(union)" : "(struct)", page });
1966       row.icon = mainForm.icons[typeData];
1967       row.tag = (int64)member;
1968
1969       for(m = member.members.first; m; m = m.next)
1970       {
1971          if(m.memberAccess == publicAccess || (m.memberAccess == privateAccess && page.showPrivate))
1972             AddDataMember(row, page, m);
1973       }
1974    }
1975 }
1976
1977 static void AddClass(DataRow parentRow, Module module, Class cl, char * nsName, bool showPrivate)
1978 {
1979    Method method;
1980    Property prop;
1981    DataRow row;
1982    DataRow methodsRow = null, virtualsRow = null, eventsRow = null;
1983    DataRow propertiesRow = null, membersRow = null, conversionsRow = null, enumRow = null;
1984    APIPage page;
1985
1986    row = parentRow.AddRow();
1987    row.SetData(null, (page = APIPageClass { cl.name, cl = cl, showPrivate = showPrivate }));
1988    row.tag = (int64)cl;
1989    row.collapsed = true;
1990    row.icon = (cl.type == enumClass || cl.type == unitClass || cl.type == systemClass) ? mainForm.icons[typeDataType] : mainForm.icons[typeClass];
1991
1992    // METHODS
1993    if(cl.methods.first)
1994    {
1995       for(method = (Method)cl.methods.first; method; method = (Method)((BTNode)method).next)
1996       {
1997          if(method.memberAccess == publicAccess || (method.memberAccess == privateAccess && showPrivate))
1998          {
1999             DataRow mRow;
2000             if(!method.dataType)
2001                ProcessMethodType(method);
2002             if(method.type == virtualMethod)
2003             {
2004                if(method.dataType.thisClass)
2005                {
2006                   if(!eventsRow) { eventsRow = row.AddRow(); eventsRow.SetData(null, APIPage { $"Events", page = page }); eventsRow.collapsed = true; eventsRow.icon = mainForm.icons[typeEvent];  eventsRow.tag = 4; }
2007                   mRow = eventsRow.AddRow(); mRow.SetData(null, APIPageMethod { method.name, method = method }); mRow.icon = mainForm.icons[typeEvent];
2008                   mRow.tag = (int64)method;
2009                }
2010                else
2011                {
2012                   if(!virtualsRow) { virtualsRow = row.AddRow(); virtualsRow.SetData(null, APIPage { $"Virtual Methods", page = page }); virtualsRow.collapsed = true; virtualsRow.icon = mainForm.icons[typeMethod]; virtualsRow.tag = 4; }
2013                   mRow = virtualsRow.AddRow(); mRow.SetData(null, APIPageMethod { method.name, method = method }); mRow.icon = mainForm.icons[typeMethod];
2014                   mRow.tag = (int64)method;
2015                }
2016             }
2017             else
2018             {
2019                if(!methodsRow) { methodsRow = row.AddRow(); methodsRow.SetData(null, APIPage { $"Methods", page = page }); methodsRow.collapsed = true; methodsRow.icon = mainForm.icons[typeMethod]; methodsRow.tag = 5; }
2020                mRow = methodsRow.AddRow(); mRow.SetData(null, APIPageMethod { method.name, method = method }); mRow.icon = mainForm.icons[typeMethod];
2021                mRow.tag = (int64)method;
2022             }
2023          }
2024       }
2025    }
2026
2027    if(cl.membersAndProperties.first)
2028    {
2029       for(prop = (Property)cl.membersAndProperties.first; prop; prop = prop.next)
2030       {
2031          if(prop.memberAccess == publicAccess || (prop.memberAccess == privateAccess && showPrivate))
2032          {
2033             if(!prop.dataType)
2034                prop.dataType = ProcessTypeString(prop.dataTypeString, false);
2035             if(prop.isProperty)
2036             {
2037                DataRow mRow;
2038                if(!propertiesRow) { propertiesRow = row.AddRow(); propertiesRow.SetData(null, APIPage { $"Properties", page = page }); propertiesRow.collapsed = true; propertiesRow.icon = mainForm.icons[typeProperty]; propertiesRow.tag = 6; }
2039                mRow = propertiesRow.AddRow(); mRow.SetData(null, APIPage { prop.name, page }); mRow.icon = mainForm.icons[typeProperty];
2040                mRow.tag = (int64)prop;
2041             }
2042             else
2043             {
2044                if(!membersRow) { membersRow = row.AddRow(); membersRow.SetData(null, APIPage { $"Data Members", page = page }); membersRow.collapsed = true; membersRow.icon = mainForm.icons[typeData]; membersRow.tag = 6; }
2045                AddDataMember(membersRow, page, (DataMember)prop);
2046             }
2047          }
2048       }
2049    }
2050
2051    if(cl.conversions.first)
2052    {
2053       for(prop = cl.conversions.first; prop; prop = prop.next)
2054       {
2055          DataRow mRow;
2056          const char * name;
2057          if(!conversionsRow) { conversionsRow = row.AddRow(); conversionsRow.SetData(null, APIPage { $"Conversions", page = page }); conversionsRow.collapsed = true; conversionsRow.icon = mainForm.icons[typeDataType]; conversionsRow.tag = 7; }
2058          name = RSearchString(prop.name, "::", strlen(prop.name), true, false);
2059          if(name) name += 2; else name = prop.name;
2060          mRow = conversionsRow.AddRow(); mRow.SetData(null, APIPage { name, page = page }); mRow.icon = mainForm.icons[typeDataType];
2061          mRow.tag = (int64)prop;
2062       }
2063    }
2064    if(cl.type == enumClass)
2065    {
2066       EnumClassData enumeration = (EnumClassData)cl.data;
2067       NamedLink item;
2068       for(item = enumeration.values.first; item; item = item.next)
2069       {
2070          DataRow mRow;
2071          if(!enumRow) { enumRow = row.AddRow(); enumRow.SetData(null, APIPage { $"Enumeration Values", page = page }); enumRow.collapsed = true; enumRow.icon = mainForm.icons[typeEnumValue]; enumRow.tag = 8; }
2072          mRow = enumRow.AddRow(); mRow.SetData(null, APIPage { item.name, page = page }); mRow.icon = mainForm.icons[typeEnumValue];
2073          mRow.tag = (int64)item;
2074       }
2075    }
2076 }
2077
2078 class AddressBar : Window
2079 {
2080    background = activeBorder;
2081    tabCycle = true;
2082    Button open
2083    {
2084       this, bevelOver = true, inactive = true, anchor = Anchor { left = 0, top = 0, bottom = 0 }, size = Size { 24 }, bitmap = { ":actions/docOpen.png" };
2085
2086       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
2087       {
2088          MainForm mainForm = (MainForm)parent;
2089          FileDialog fileDialog = mainForm.fileDialog;
2090          if(fileDialog.Modal() == ok)
2091             mainForm.OpenModule(fileDialog.filePath);
2092          return true;
2093       }
2094    };
2095    Button back
2096    {
2097       this, bevelOver = true, inactive = true, anchor = Anchor { left = 28, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = altLeft, bitmap = { "<:ecere>actions/goPrevious.png" };
2098       disabled = true;
2099
2100       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
2101       {
2102          ((MainForm)parent).Back();
2103          return true;
2104       }
2105    };
2106    Button forward
2107    {
2108       this, bevelOver = true, inactive = true, anchor = Anchor { left = 52, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = altRight, bitmap = { "<:ecere>actions/goNext.png" };
2109       disabled = true;
2110
2111       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
2112       {
2113          ((MainForm)parent).Forward();
2114          return true;
2115       }
2116    };
2117    Button home
2118    {
2119       this, bevelOver = true, inactive = true, anchor = Anchor { left = 80, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = ctrlH, bitmap = { "<:ecere>actions/goHome.png" };
2120
2121       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
2122       {
2123          ((MainForm)parent).Home();
2124          return true;
2125       }
2126    };
2127    /* TODO: Search (#143/#441)
2128       When there's something in the search box, list matching sections, the exact match first,instead of the Hierarchy in the ListBox.
2129       Update this in the NotifyUpdate. Enter goes to the exact match.
2130
2131    Label { this, anchor = Anchor { left = (124+12) }, labeledWindow = search };
2132
2133    EditBox search
2134    {
2135       this, text = "Search:", anchor = Anchor { left = (16+48+124), right = 60, top = 0, bottom = 0 }, hotKey = altD;
2136
2137       bool NotifyKeyDown(EditBox editBox, Key key, unichar ch)
2138       {
2139          if(!disabled && (SmartKey)key == enter)
2140             ((MainForm)parent).Go(editBox.contents);
2141          return true;
2142       }
2143
2144       void NotifyUpdate(EditBox editBox)
2145       {
2146          String location = ((MainForm)parent).view.location;
2147          disabled = !strcmp(location ? location : "", editBox.contents);
2148       }
2149    };
2150    */
2151
2152    bool OnKeyHit(Key key, unichar ch)
2153    {
2154       if(key == escape)
2155          ((MainForm)parent).view.MakeActive();
2156       return true;
2157    }
2158 }
2159
2160 class MainForm : Window
2161 {
2162    size = { 1000, 600 };
2163    hasClose = true;
2164    borderStyle = sizable;
2165    hasMaximize = true;
2166    hasMinimize = true;
2167    icon = { ":documentorIcon.png" };
2168    text = $"API Documentation Browser";
2169
2170    BitmapResource icons[CodeObjectType];
2171
2172    MainForm()
2173    {
2174       CodeObjectType c;
2175       for(c = 0; c < CodeObjectType::enumSize; c++)
2176       {
2177          icons[c] = BitmapResource { iconNames[c], window = this, alphaBlend = true };
2178       }
2179       browser.AddField(DataField { dataType = class(APIPage) });
2180    }
2181
2182    hasMenuBar = true;
2183    menu = Menu { };
2184    Menu fileMenu { menu, $"File", f };
2185    Array<FileFilter> fileFilters
2186    { [
2187       { $"eC Shared Library files (*.dll, *.so, *.dylib)", "dll, so, dylib" },
2188       { $"eC Symbol files (*.sym)", "sym" }
2189    ] };
2190
2191    FileDialog fileDialog
2192    {
2193       filters = fileFilters.array, sizeFilters = fileFilters.count * sizeof(FileFilter)
2194    };
2195    MenuItem fileOpenItem
2196    {
2197       fileMenu, $"Open...", o, ctrlO;
2198
2199       bool NotifySelect(MenuItem selection, Modifiers mods)
2200       {
2201          if(fileDialog.Modal() == ok)
2202          {
2203             OpenModule(fileDialog.filePath);
2204          }
2205          return true;
2206       }
2207    };
2208    MenuItem fileSettingsItem
2209    {
2210       fileMenu, $"Settings...", s, ctrlS; // set the Settings item to the file menu with shortcut keys:s and ctrl+s
2211
2212       bool NotifySelect(MenuItem selection, Modifiers mods)
2213       {
2214          if(SettingsDialog { master = this }.Modal() == ok) // Open the settings dialog to allow the user to change the directory for the eCdoc files
2215          {
2216             // Refresh docs
2217             view.edit = false;
2218             view.Destroy(0);
2219             view.Create();
2220          }
2221          return true;
2222       }
2223    };
2224    MenuDivider { fileMenu };
2225    MenuItem fileExit { fileMenu, $"Exit", x, altF4, NotifySelect = MenuFileExit };
2226
2227    void OpenModule(const char * filePath)
2228    {
2229       char moduleName[MAX_LOCATION];
2230       char extension[MAX_EXTENSION];
2231       Module module = null;
2232       static char symbolsDir[MAX_LOCATION];
2233
2234       history.size = 0;
2235
2236       FreeContext(globalContext);
2237       FreeExcludedSymbols(excludedSymbols);
2238       ::defines.Free(FreeModuleDefine);
2239       imports.Free(FreeModuleImport);
2240
2241       FreeGlobalData(globalData);
2242       FreeIncludeFiles();
2243       if(componentsApp)
2244       {
2245          FreeTypeData(componentsApp);
2246          delete componentsApp;
2247       }
2248
2249       componentsApp = __ecere_COM_Initialize(false, 1, null);
2250       SetPrivateModule(componentsApp);
2251
2252       StripLastDirectory(filePath, symbolsDir);
2253       SetSymbolsDir(symbolsDir);
2254
2255       GetExtension(filePath, extension);
2256
2257       mainForm.browser.Clear();
2258
2259       ImportModule(filePath, normalImport, publicAccess, false);
2260
2261       if(extension[0] && strcmpi(extension, "so") && strcmpi(extension, "dll") && strcmpi(extension, "dylib"))
2262          componentsApp.name = CopyString(filePath);
2263
2264       for(module = componentsApp.allModules.first; module; module = module.next)
2265       {
2266          if(module.name && (!strcmp(module.name, "ecere") || !strcmp(module.name, "ecereCOM")))
2267             break;
2268       }
2269       if(!module)
2270          eModule_LoadStrict(componentsApp, "ecereCOM", publicAccess /*privateAccess*/);
2271       AddComponents(componentsApp, false);
2272
2273       GetLastDirectory(filePath, moduleName);
2274       // Extension, path and lib prefix get removed in Module::name
2275       if(extension[0])
2276       {
2277          StripExtension(moduleName);
2278          if((!strcmpi(extension, "so") || !strcmpi(extension, "dylib")) && strstr(moduleName, "lib") == moduleName)
2279          {
2280             int len = strlen(moduleName) - 3;
2281             memmove(moduleName, moduleName + 3, len);
2282             moduleName[len] = 0;
2283          }
2284       }
2285
2286       for(module = componentsApp.allModules.first; module; module = module.next)
2287       {
2288          if(module.name && (!strcmp(module.name, moduleName)))
2289             break;
2290       }
2291       if(!module) module = componentsApp;
2292       homeModule = module;
2293       mainForm.browser.SelectRow(mainForm.browser.FindSubRow((int64)module));
2294
2295       SetSymbolsDir(null);
2296    }
2297
2298    AddressBar addressBar { this, borderStyle = bevel, anchor = Anchor { top = 0, left = 0, right = 0 }, size.h = 26, hotKey = altD };
2299    ListBox browser
2300    {
2301       this, anchor = { left = 0, top = 26, bottom = 0 }, borderStyle = 0, background = aliceBlue;
2302       treeBranches = true; collapseControl = true; fullRowSelect = false; rootCollapseButton = true;
2303       hotKey = alt0;
2304
2305       bool NotifySelect(ListBox listBox, DataRow row, Modifiers mods)
2306       {
2307          APIPage page = row.GetData(null);
2308          if(view.edit) view.OnLeftButtonDown(0,0,0);
2309          if(page && page.page) page = page.page;
2310          view.edit = false;
2311          view.PositionCaret(true);
2312          if(page != view.page)
2313          {
2314             Window activeChild = this.activeChild;
2315
2316             // Back / Forward Support
2317             if(row && !dontRecordHistory)
2318             {
2319                if(history.count > historyPos+1)
2320                   history.count = historyPos+1;
2321                historyPos = history.count-1;
2322                addressBar.back.disabled = (historyPos == 0);
2323                addressBar.forward.disabled = (historyPos >= history.count-1);
2324
2325                history.Add((Instance)(uint64)row.tag);
2326                historyPos = history.count-1;
2327
2328                addressBar.back.disabled = (historyPos == 0);
2329                addressBar.forward.disabled = (historyPos >= history.count-1);
2330             }
2331
2332             view.Destroy(0);
2333             if(page)
2334                view.Create();
2335             activeChild.Activate();
2336          }
2337          else if(!view.created)
2338             view.Create();
2339
2340          {
2341             page = row.GetData(null);
2342             if(page && page.page)
2343             {
2344                switch(row.tag)
2345                {
2346                   case 1: view.GoToAnchor("Classes"); break;
2347                   case 2: view.GoToAnchor("Functions"); break;
2348                   case 3: view.GoToAnchor("Definitions"); break;
2349                   case 4: view.GoToAnchor("VirtualMethods"); break;
2350                   case 5: view.GoToAnchor("Methods"); break;
2351                   case 6: view.GoToAnchor("Members"); break;
2352                   case 7: view.GoToAnchor("Conversions"); break;
2353                   case 8: view.GoToAnchor("EnumerationValues"); break;
2354                   default:
2355                   {
2356                      char hex[20];
2357                      sprintf(hex, "%p", (void *)(uintptr)row.tag);
2358                      view.GoToAnchor(hex);
2359                   }
2360                }
2361             }
2362             else
2363             {
2364                view.SetScrollPosition(0, 0);
2365             }
2366          }
2367          return true;
2368       }
2369    };
2370    HelpView view
2371    {
2372       this, anchor = { top = 26, bottom = 0, right = 0 };
2373       hotKey = escape;
2374    };
2375    PaneSplitter slider
2376    {
2377       this, anchor.top = 26, leftPane = browser, rightPane = view, split = 300 /*scaleSplit = 0.3 */
2378    };
2379
2380    bool OnClose(bool parentClosing)
2381    {
2382       if(view.edit)
2383          view.OnLeftButtonDown(0,0,0);
2384       return true;
2385    }
2386
2387    bool OnPostCreate()
2388    {
2389       mainForm.OpenModule((((GuiApplication)__thisModule).argc > 1) ? ((GuiApplication)__thisModule).argv[1] : "ecere");
2390       //mainForm.OpenModule("ec");
2391       //mainForm.OpenModule("c:/games/chess/debug/chess.sym");
2392       //mainForm.OpenModule("c:/ide/Objects.IDE.Win32.Debug/ide.sym");
2393       {
2394          int index = mainForm.browser.currentRow.index;
2395          int rowHeight = mainForm.browser.rowHeight;
2396          int height = mainForm.browser.clientSize.h;
2397
2398          mainForm.browser.scroll = { 0, index * rowHeight - height / 2 };
2399       }
2400       return true;
2401    }
2402
2403    Array<Instance> history { };
2404    int historyPos;
2405    bool dontRecordHistory;
2406    Module homeModule;
2407
2408    bool Forward()
2409    {
2410       if(historyPos < history.count-1)
2411       {
2412          char location[64];
2413          historyPos++;
2414          addressBar.back.disabled = (historyPos == 0);
2415          addressBar.forward.disabled = (historyPos >= history.count-1);
2416          sprintf(location, "api://%p", history[historyPos]);
2417          dontRecordHistory = true;
2418          view.OnOpen(location);
2419          dontRecordHistory = false;
2420          return true;
2421       }
2422       return false;
2423    }
2424
2425    bool Back()
2426    {
2427       if(historyPos > 0)
2428       {
2429          char location[64];
2430          historyPos--;
2431          addressBar.back.disabled = (historyPos == 0);
2432          addressBar.forward.disabled = (historyPos >= history.count-1);
2433          sprintf(location, "api://%p", history[historyPos]);
2434          dontRecordHistory = true;
2435          view.OnOpen(location);
2436          dontRecordHistory = false;
2437          return true;
2438       }
2439       return false;
2440    }
2441
2442    void Home()
2443    {
2444       mainForm.browser.SelectRow(mainForm.browser.FindSubRow((int64)homeModule));
2445    }
2446 };
2447
2448 class EditDialog : Window
2449 {
2450    borderStyle = sizable;
2451    size = { 600, 400 };
2452    autoCreate = false;
2453
2454    EditBox editBox
2455    {
2456       this, anchor = { left = 16, top = 16, right = 18, bottom = 61 }
2457    };
2458    Button saveChanges
2459    {
2460       this, text = $"Save Changes", anchor = { horz = 184, vert = 160 }
2461    };
2462    Button cancel
2463    {
2464       this, text = $"Cancel", anchor = { horz = 254, vert = 160 }
2465    };
2466 }
2467
2468 #define UTF8_IS_FIRST(x)   (__extension__({ byte b = x; (!(b) || !((b) & 0x80) || (b) & 0x40); }))
2469 #define UTF8_NUM_BYTES(x)  (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
2470
2471 class HelpView : HTMLView
2472 {
2473    APIPage page;
2474
2475    hasVertScroll = true;
2476    hasHorzScroll = true;
2477    bool edit;
2478    char editString[MAX_LOCATION];
2479
2480    bool OnCreate()
2481    {
2482       TempFile f { };
2483
2484       page = mainForm.browser.currentRow.GetData(null);
2485       if(page)
2486       {
2487          // Writability test
2488          {
2489             char docFile[MAX_LOCATION];
2490             Archive archive;
2491             Module module = page ? page.GetModule() : null;
2492             NameSpace * ns = page ? page.GetNameSpace() : null;
2493
2494             sprintf(docFile, "%s/%s.eCdoc", settings.docDir, (!module || !module.name || (ns && ns->name && !strcmp(ns->name, "namespaces/ecere/namespaces/com"))) ? "ecereCOM" : module.name);
2495             if(FileExists(docFile))
2496             {
2497                archive = ArchiveOpen(docFile, { true } );
2498                readOnly = archive == null;
2499                delete archive;
2500             }
2501             else
2502             {
2503                readOnly = true;
2504                archive = ArchiveOpen(docFile, { true } );
2505                if(archive)
2506                {
2507                   // Must create root directory on archive creation
2508                   ArchiveDir dir = archive.OpenDirectory("", null, replace);
2509                   if(dir)
2510                      readOnly = false;
2511                   delete dir;
2512                }
2513                delete archive;
2514             }
2515          }
2516
2517          page.Generate(f);
2518          f.Seek(0, start);
2519          OpenFile(f, null);
2520          GoToAnchor(page.label);
2521          // Go to label...
2522          if(page.page) page = page.page;
2523       }
2524       delete f;
2525       return HTMLView::OnCreate();
2526    }
2527    EditDialog dialog
2528    {
2529
2530    };
2531
2532    void SaveEdit()
2533    {
2534       char archiveFile[MAX_LOCATION];
2535       char fileName[MAX_FILENAME];
2536       char directory[MAX_LOCATION];
2537       const char * location;
2538       Archive archive = null;
2539       if(SplitArchivePath(editString, archiveFile, &location))
2540       {
2541          GetLastDirectory(location, fileName);
2542          StripLastDirectory(location, directory);
2543          archive = ArchiveOpen(archiveFile, { true } );
2544       }
2545       {
2546          TempFile f { };
2547          Block block;
2548          bool empty = true;
2549          for(block = textBlock.parent.subBlocks.first; block; block = block.next)
2550          {
2551             if(block.type == TEXT && block.textLen)
2552             {
2553                empty = false;
2554                break;
2555             }
2556          }
2557          if(!empty)
2558          {
2559             for(block = textBlock.parent.subBlocks.first; block; block = block.next)
2560             {
2561                if(block.type == BR)
2562                   f.Puts("<br>");
2563                else if(block.type == TEXT)
2564                   f.Write(block.text, 1, block.textLen);
2565             }
2566          }
2567          f.Seek(0, start);
2568
2569          if(!empty || archive.FileExists(location))
2570          {
2571             ArchiveDir dir = archive ? archive.OpenDirectory(directory, null, replace) : null;
2572             if(dir)
2573                dir.AddFromFile(fileName, f, null, replace, 0, null, null);
2574             delete dir;
2575          }
2576          delete archive;
2577          delete f;
2578          if(empty)
2579          {
2580             Block parent = textBlock.parent;
2581             while((block = parent.subBlocks.first))
2582             {
2583                parent.subBlocks.Remove(block);
2584                delete block;
2585             }
2586             textBlock = Block { type = TEXT, parent = parent, font = parent.font };
2587             textBlock.text = CopyString($"[Add Text]");
2588             textBlock.textLen = strlen(textBlock.text);
2589             parent.subBlocks.Add(textBlock);
2590          }
2591
2592          edit = false;
2593          if(created)
2594          {
2595             ComputeMinSizes();
2596             ComputeSizes();
2597             PositionCaret(true);
2598             Update(null);
2599          }
2600       }
2601    }
2602
2603    bool OnLeftButtonDown(int x, int y, Modifiers mods)
2604    {
2605       bool result = true;
2606
2607       if(edit && (!textBlock || overLink != textBlock.parent))
2608       {
2609          if(!readOnly)
2610             SaveEdit();
2611          HTMLView::OnLeftButtonDown(x, y, mods);
2612          selPosition = curPosition = 0;
2613          selBlock = textBlock;
2614          Update(null);
2615       }
2616       else
2617          result = HTMLView::OnLeftButtonDown(x, y, mods);
2618
2619       if(!edit && clickedLink)
2620       {
2621          ReleaseCapture();
2622          if(clickedLink == overLink && clickedLink.href)
2623          {
2624             if(OnOpen(clickedLink.href))
2625                Update(null);
2626          }
2627       }
2628
2629       if(edit)
2630       {
2631          // Update overLink
2632          if(textBlock && overLink == textBlock.parent)
2633          {
2634             selPosition = curPosition = TextPosFromPoint(x, y, &textBlock, true);
2635             selBlock = textBlock;
2636             PositionCaret(true);
2637             selecting = true;
2638             Update(null);
2639          }
2640       }
2641       return result;
2642    }
2643
2644    bool OnLeftButtonUp(int x, int y, Modifiers mods)
2645    {
2646       if(!edit || !textBlock || clickedLink != textBlock.parent)
2647       {
2648          HTMLView::OnLeftButtonUp(x, y, mods);
2649          if(edit)
2650          {
2651             selPosition = curPosition = TextPosFromPoint(x, y, &textBlock, true);
2652             selBlock = textBlock;
2653             PositionCaret(true);
2654             Update(null);
2655          }
2656       }
2657       else
2658          ReleaseCapture();
2659       selecting = false;
2660       return true;
2661    }
2662    bool selecting;
2663
2664    bool OnMouseMove(int x, int y, Modifiers mods)
2665    {
2666       if(edit && selecting)
2667       {
2668          curPosition = TextPosFromPoint(x, y, &textBlock, true);
2669          PositionCaret(true);
2670          Update(null);
2671       }
2672       return HTMLView::OnMouseMove(x, y, mods);
2673    }
2674
2675    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
2676    {
2677       if(edit && textBlock)
2678       {
2679          int c;
2680          int start = -1;
2681          int numBytes;
2682
2683          selPosition = curPosition = TextPosFromPoint(mx, my, &textBlock, false);
2684          selBlock = textBlock;
2685          for(c = curPosition; c >= 0; c--)
2686          {
2687             unichar ch;
2688             while(c > 0 && !UTF8_IS_FIRST(textBlock.text[c])) c--;
2689             ch = UTF8GetChar(textBlock.text + c, &numBytes);
2690             if(!CharMatchCategories(ch, letters|numbers|marks|connector))
2691                break;
2692             start = c;
2693          }
2694          if(start != -1)
2695          {
2696             for(c = start; c < textBlock.textLen; c += numBytes)
2697             {
2698                unichar ch = UTF8GetChar(textBlock.text + c, &numBytes);
2699                if(!CharMatchCategories(ch, letters|numbers|marks|connector))
2700                   break;
2701             }
2702             selPosition = start;
2703             curPosition = c;
2704
2705             PositionCaret(true);
2706             Update(null);
2707             return false;
2708          }
2709       }
2710       return true;
2711    }
2712
2713    bool OnOpen(char * href)
2714    {
2715       if(!strncmp(href, "api://", 6))
2716       {
2717          int64 tag = (int64)strtoull(href + 6, null, 16);
2718          DataRow row = mainForm.browser.FindSubRow(tag);
2719          if(row)
2720          {
2721             edit = false;
2722             mainForm.browser.SelectRow(row);
2723             while((row = row.parent))
2724                row.collapsed = false;
2725             row = mainForm.browser.currentRow;
2726             mainForm.browser.scroll = { 0, row.index * mainForm.browser.rowHeight - mainForm.browser.clientSize.h / 2 };
2727          }
2728       }
2729       else if(!strncmp(href, "edit://", 7))
2730       {
2731          Block block;
2732          int startX = clickedLink.startX, startY = clickedLink.startY;
2733          for(block = (Block)clickedLink.subBlocks.first; block; block = block.next)
2734          {
2735             if(block.type == TEXT) startX = block.startX, startY = block.startY;
2736             if(block.type == BR && (!block.prev || !block.next || block.next.type != TEXT))
2737             {
2738                Block newBlock { type = TEXT, parent = block.parent, font = block.parent.font };
2739                int th = 0;
2740                display.FontExtent(block.font.font, " ", 1, null, &th);
2741                if(!block.prev)
2742                {
2743                   block.parent.subBlocks.Insert(null, newBlock);
2744                   block = newBlock;
2745                }
2746                else
2747                {
2748                   block.parent.subBlocks.Insert(block, newBlock);
2749                   startY += block.prev.height;
2750                }
2751                newBlock.startX = startX;
2752                newBlock.startY = startY;
2753                newBlock.text = new0 char[1];
2754             }
2755          }
2756
2757          textBlock = (Block)clickedLink.subBlocks.first;
2758          if(!strcmp(textBlock.text, $"[Add Text]"))
2759          {
2760             textBlock.text[0] = 0;
2761             textBlock.textLen = 0;
2762          }
2763
2764          strcpy(editString, href + 7);
2765          selPosition = curPosition = 0;
2766          selBlock = textBlock;
2767          // dialog.Create();
2768          edit = true;
2769          // PositionCaret(true);
2770       }
2771       return true;
2772    }
2773
2774    char * text;
2775
2776    void DeleteSelection()
2777    {
2778       if(textBlock != selBlock || curPosition != selPosition)
2779       {
2780          if(textBlock == selBlock)
2781          {
2782             // Within same block
2783             int start = Min(curPosition, selPosition);
2784             int end = Max(curPosition, selPosition);
2785             memmove(textBlock.text + start, textBlock.text + end, textBlock.textLen - end);
2786             textBlock.textLen -= end-start;
2787             textBlock.text = renew textBlock.text char[textBlock.textLen + 1];
2788             curPosition = start;
2789             selPosition = start;
2790          }
2791          else
2792          {
2793             int startSel, endSel;
2794             Block startSelBlock = null, endSelBlock = null, b, next;
2795
2796             NormalizeSelection(&startSelBlock, &startSel, &endSelBlock, &endSel);
2797
2798             startSelBlock.text = renew startSelBlock.text char[startSel + endSelBlock.textLen - endSel + 1];
2799             memcpy(startSelBlock.text + startSel, endSelBlock.text + endSel, endSelBlock.textLen - endSel + 1);
2800
2801             startSelBlock.textLen = startSel + endSelBlock.textLen - endSel;
2802             for(b = startSelBlock.next; b; b = next)
2803             {
2804                bool isEnd = b == endSelBlock;
2805                next = GetNextBlock(b);
2806                b.parent.subBlocks.Remove(b);
2807                delete b;
2808                if(isEnd)
2809                   break;
2810             }
2811             textBlock = startSelBlock;
2812             selBlock = startSelBlock;
2813             curPosition = startSel;
2814             selPosition = startSel;
2815          }
2816          ComputeMinSizes();
2817          ComputeSizes();
2818          PositionCaret(true);
2819          Update(null);
2820       }
2821    }
2822
2823    String GetSelectionString()
2824    {
2825       String selection = null;
2826       if(textBlock == selBlock)
2827       {
2828          // Within same block
2829          int start = Min(curPosition, selPosition);
2830          int end = Max(curPosition, selPosition);
2831          int len = end - start;
2832          selection = new char[len + 1];
2833          memcpy(selection, textBlock.text + start, len);
2834          selection[len] = 0;
2835       }
2836       else
2837       {
2838          int startSel, endSel;
2839          Block startSelBlock = null, endSelBlock = null, b;
2840          int totalLen = 0;
2841
2842          NormalizeSelection(&startSelBlock, &startSel, &endSelBlock, &endSel);
2843
2844          // Compute length
2845          for(b = startSelBlock; b; b = GetNextBlock(b))
2846          {
2847             int start = (b == startSelBlock) ? startSel : 0;
2848             int end = (b == endSelBlock) ? endSel : b.textLen;
2849             int len = end - start;
2850             totalLen += len;
2851             if(b == endSelBlock)
2852                break;
2853             else if(b.type == TEXT)
2854                totalLen++;
2855          }
2856
2857          selection = new char[totalLen + 1];
2858          totalLen = 0;
2859          for(b = startSelBlock; b; b = GetNextBlock(b))
2860          {
2861             int start = (b == startSelBlock) ? startSel : 0;
2862             int end = (b == endSelBlock) ? endSel : b.textLen;
2863             int len = end - start;
2864             memcpy(selection + totalLen, b.text + start, len);
2865             totalLen += len;
2866             if(b == endSelBlock)
2867                break;
2868             else if(b.type == TEXT)
2869                selection[totalLen++] = '\n';
2870          }
2871          selection[totalLen] = 0;
2872       }
2873       return selection;
2874    }
2875
2876    void CopySelection()
2877    {
2878       String s = GetSelectionString();
2879       if(s)
2880       {
2881          int len = strlen(s);
2882          ClipBoard cb { };
2883          if(cb.Allocate(len + 1))
2884          {
2885             memcpy(cb.text, s, len + 1);
2886             cb.Save();
2887          }
2888          delete cb;
2889          delete s;
2890       }
2891    }
2892
2893    bool OnKeyDown(Key key, unichar ch)
2894    {
2895       if(edit)
2896       {
2897          switch(key)
2898          {
2899             case escape:
2900                OnLeftButtonDown(0,0,0);
2901                return false;
2902             case Key { end, shift = true }:
2903             case end:
2904                curPosition = textBlock.textLen;
2905                if(!key.shift)
2906                {
2907                   selPosition = curPosition;
2908                   selBlock = textBlock;
2909                }
2910                PositionCaret(true);
2911                Update(null);
2912                break;
2913             case Key { home, shift = true }:
2914             case home:
2915                curPosition = 0;
2916                if(!key.shift)
2917                {
2918                   selPosition = curPosition;
2919                   selBlock = textBlock;
2920                }
2921                PositionCaret(true);
2922                Update(null);
2923                break;
2924             case Key { home, ctrl = true, shift = true }:
2925             case ctrlHome:
2926                curPosition = 0;
2927                while(textBlock.prev)
2928                   textBlock = textBlock.prev.prev;
2929                if(!key.shift)
2930                {
2931                   selPosition = curPosition;
2932                   selBlock = textBlock;
2933                }
2934                PositionCaret(true);
2935                Update(null);
2936                return false;
2937             case Key { end, ctrl = true, shift = true }:
2938             case ctrlEnd:
2939                while(textBlock.next && textBlock.next.next)
2940                   textBlock = textBlock.next.next;
2941                curPosition = textBlock.textLen;
2942                if(!key.shift)
2943                {
2944                   selPosition = curPosition;
2945                   selBlock = textBlock;
2946                }
2947                PositionCaret(true);
2948                Update(null);
2949                return false;
2950          }
2951       }
2952       else
2953          return HTMLView::OnKeyDown(key, ch);
2954       return true;
2955    }
2956
2957    bool OnKeyHit(Key key, unichar ch)
2958    {
2959       if(edit)
2960       {
2961          switch(key)
2962          {
2963             case Key { up, shift = true }:
2964             case up:
2965             {
2966                if(caretY == textBlock.startY)
2967                {
2968                   if(textBlock.prev)
2969                   {
2970                      textBlock = textBlock.prev.prev;
2971                      curPosition = Min(curPosition, textBlock.textLen);
2972                      if(!key.shift)
2973                      {
2974                         selPosition = curPosition;
2975                         selBlock = textBlock;
2976                      }
2977                      Update(null);
2978                      PositionCaret(false);
2979                      caretY = MAXINT;
2980                   }
2981                   else
2982                      return false;
2983                }
2984
2985                {
2986                   int th = 0;
2987                   int textPos = 0;
2988                   int sx = textBlock.startX, sy = textBlock.startY;
2989                   char * text = textBlock.text;
2990                   int maxW;
2991                   Block block = textBlock;
2992                   while(block && block.type != TD) block = block.parent;
2993                   if(block)
2994                   {
2995                      Block table = block;
2996                      while(table && table.type != TABLE) table = table.parent;
2997                      if(table)
2998                         maxW = block.w - 2* table.cellPadding;
2999                      else
3000                         maxW = clientSize.w - 10 - sx;
3001                   }
3002                   else
3003                      maxW = clientSize.w - 10 - sx;
3004                   display.FontExtent(textBlock.font.font, " ", 1, null, &th);
3005
3006                   do
3007                   {
3008                      int startPos = textPos;
3009                      int width = 0;
3010                      int x = 0;
3011                      bool lineComplete = false;
3012                      for(; textPos<textBlock.textLen && !lineComplete;)
3013                      {
3014                         int w;
3015                         int len;
3016                         char * nextSpace = strchr(text + textPos, ' ');
3017
3018                         if(nextSpace)
3019                            len = (nextSpace - (text + textPos)) + 1;
3020                         else
3021                            len = textBlock.textLen - textPos;
3022
3023                         display.FontExtent(textBlock.font.font, text + textPos, len, &w, &th);
3024
3025                         if(x + width + w > maxW && x > 0)
3026                         {
3027                            lineComplete = true;
3028                            break;
3029                         }
3030                         textPos += len;
3031                         width += w;
3032                         if(nextSpace)
3033                         {
3034                            x += width;
3035                            width = 0;
3036                         }
3037                         if(textPos == textBlock.textLen || (sy == caretY - th && caretX <= x + width + sx))
3038                         {
3039                            x += width;
3040                            curPosition = textPos;
3041                            while(curPosition > 0 && x + sx > caretX && textPos > startPos)
3042                            {
3043                               int len;
3044                               while(curPosition > 0 && !UTF8_IS_FIRST(text[--curPosition]));
3045                               len = curPosition - startPos;
3046                               display.FontExtent(textBlock.font.font, text + startPos, len, &x, null);
3047                            }
3048                            if(!key.shift)
3049                            {
3050                               selPosition = curPosition;
3051                               selBlock = textBlock;
3052                            }
3053                            Update(null);
3054
3055                            PositionCaret(false);
3056                            return false;
3057                         }
3058                      }
3059                      if(sy == caretY - th || textPos == textBlock.textLen)
3060                      {
3061                         if(textPos != textBlock.textLen)
3062                         {
3063                            int c = textPos - 1;
3064                            while(c > 0 && text[c] == ' ') c--;
3065                            curPosition = c + 1;
3066                            if(!key.shift)
3067                            {
3068                               selPosition = curPosition;
3069                               selBlock = textBlock;
3070                            }
3071                            Update(null);
3072                         }
3073                         else
3074                         {
3075                            curPosition = textBlock.textLen;
3076                            if(!key.shift)
3077                            {
3078                               selPosition = curPosition;
3079                               selBlock = textBlock;
3080                            }
3081                            Update(null);
3082                         }
3083                         PositionCaret(false);
3084                         return false;
3085                      }
3086                      sy += th;
3087                      sx = textBlock.startX;
3088                   } while(textPos < textBlock.textLen);
3089                   return false;
3090                }
3091                return false;
3092             }
3093             case Key { down, shift = true }:
3094             case down:
3095             {
3096                int th = 0;
3097                int textPos = 0;
3098                int sx = textBlock.startX, sy = textBlock.startY;
3099                char * text = textBlock.text;
3100                int maxW;
3101                Block block = textBlock;
3102                while(block && block.type != TD) block = block.parent;
3103                if(block)
3104                {
3105                   Block table = block;
3106                   while(table && table.type != TABLE) table = table.parent;
3107                   if(table)
3108                      maxW = block.w - 2* table.cellPadding;
3109                   else
3110                      maxW = clientSize.w - 10 - sx;
3111                }
3112                else
3113                   maxW = clientSize.w - 10 - sx;
3114                display.FontExtent(textBlock.font.font, " ", 1, null, &th);
3115
3116                while(!textPos || textPos < textBlock.textLen)
3117                {
3118                   int startPos = textPos;
3119                   int width = 0;
3120                   int x = 0;
3121                   bool lineComplete = false;
3122                   for(; (textPos < textBlock.textLen) && !lineComplete;)
3123                   {
3124                      int w;
3125                      int len;
3126                      char * nextSpace = strchr(text + textPos, ' ');
3127
3128                      if(nextSpace)
3129                         len = (nextSpace - (text + textPos)) + 1;
3130                      else
3131                         len = textBlock.textLen - textPos;
3132
3133                      display.FontExtent(textBlock.font.font, text + textPos, len, &w, &th);
3134
3135                      if(x + width + w > maxW && x > 0)
3136                      {
3137                         lineComplete = true;
3138                         break;
3139                      }
3140                      textPos += len;
3141                      width += w;
3142                      if(nextSpace)
3143                      {
3144                         x += width;
3145                         width = 0;
3146                      }
3147                      if(sy > caretY && (textPos == textBlock.textLen || caretX <= x + width + sx))
3148                      {
3149                         curPosition = textPos;
3150                         x += width;
3151                         while(curPosition > 0 && x + sx > caretX && textPos > startPos)
3152                         {
3153                            int len;
3154                            while(curPosition > 0 && !UTF8_IS_FIRST(text[--curPosition]));
3155                            len = curPosition - startPos;
3156                            display.FontExtent(textBlock.font.font, text + startPos, len, &x, null);
3157                         }
3158                         if(!key.shift)
3159                         {
3160                            selPosition = curPosition;
3161                            selBlock = textBlock;
3162                         }
3163                         Update(null);
3164                         PositionCaret(false);
3165                         return false;
3166                      }
3167                   }
3168                   if(sy > caretY)
3169                   {
3170                      curPosition = textBlock.textLen;
3171                      if(!key.shift)
3172                      {
3173                         selPosition = curPosition;
3174                         selBlock = textBlock;
3175                      }
3176                      Update(null);
3177                      PositionCaret(false);
3178                      return false;
3179                   }
3180                   else if(textPos == textBlock.textLen && textBlock.next && textBlock.next.next)
3181                   {
3182                      startPos = 0;
3183                      textPos = 0;
3184                      textBlock = textBlock.next.next;
3185                      sy = textBlock.startY;
3186                      sx = textBlock.startX;
3187                      text = textBlock.text;
3188                   }
3189                   else
3190                   {
3191                      sy += th;
3192                      sx = textBlock.startX;
3193                   }
3194                }
3195
3196                /*if(textBlock.next && textBlock.next.next)
3197                {
3198                   textBlock = textBlock.next.next;
3199                   selPosition = curPosition = Min(curPosition, textBlock.textLen);
3200                   selBlock = textBlock;
3201                   PositionCaret(false);
3202                }*/
3203                break;
3204             }
3205             case Key { right, shift = true, ctrl = true }:
3206             case ctrlRight:
3207             {
3208                bool foundAlpha = false;
3209                bool found = false;
3210                Block line, lastLine;
3211                int lastC;
3212
3213                for(line = textBlock; (line && !found); line = line.next ? line.next.next : null)
3214                {
3215                   int start = (line == textBlock) ? curPosition : 0;
3216                   int c;
3217                   for(c = start; c < line.textLen; c++)
3218                   {
3219                      char ch = line.text[c];
3220                      bool isAlUnder = CharMatchCategories(ch, letters|numbers|marks|connector);
3221                      if(key.shift ? isAlUnder : !isAlUnder)
3222                      {
3223                         foundAlpha = true;
3224                         lastC = c;
3225                         lastLine = line;
3226                      }
3227                      else if(foundAlpha)
3228                      {
3229                         found = true;
3230                         if(!key.shift)
3231                         {
3232                            curPosition = c;
3233                            if(!key.shift)
3234                            {
3235                               selPosition = curPosition;
3236                               selBlock = textBlock;
3237                            }
3238                            Update(null);
3239                            textBlock = line;
3240                            PositionCaret(true);
3241                         }
3242                         break;
3243                      }
3244                   }
3245                   // No next word found,
3246                   if(!found && (c != curPosition || line != textBlock))
3247                   {
3248                      found = true;
3249                      lastLine = line;
3250                      lastC = line.textLen-1;
3251                      if(key.shift)
3252                         break;
3253                      else
3254                      {
3255                         curPosition = line.textLen;
3256                         if(!key.shift)
3257                         {
3258                            selPosition = curPosition;
3259                            selBlock = textBlock;
3260                         }
3261                         Update(null);
3262
3263                         textBlock = line;
3264                         PositionCaret(true);
3265                      }
3266                   }
3267                   if(!key.shift)
3268                      foundAlpha = true;
3269                }
3270                if(key.shift && found)
3271                {
3272                   curPosition = lastC+1;
3273                   textBlock = lastLine;
3274                   PositionCaret(true);
3275                   Update(null);
3276                }
3277                break;
3278             }
3279             case Key { left, ctrl = true, shift = true }:
3280             case ctrlLeft:
3281             {
3282                bool foundAlpha = false;
3283                bool found = false;
3284                Block line, lastLine;
3285                int lastC;
3286
3287                for(line = textBlock; (line && !found); line = line.prev ? line.prev.prev : null)
3288                {
3289                   int start, c;
3290                   if(curPosition == 0 && line != textBlock)
3291                   {
3292                      foundAlpha = true;
3293                      lastC = line.textLen;
3294                      lastLine = line;
3295                      break;
3296                   }
3297                   if(line == textBlock) start = curPosition-1; else start = line.textLen-1;
3298                   for(c = start; c>=0; c--)
3299                   {
3300                      if(CharMatchCategories(line.text[c], letters|numbers|marks|connector))
3301                      {
3302                         foundAlpha = true;
3303                         lastC = c;
3304                         lastLine = line;
3305                      }
3306                      else
3307                      {
3308                         if(foundAlpha)
3309                         {
3310                            found = true;
3311                            break;
3312                         }
3313                      }
3314                   }
3315                   // No next word found,
3316                   if(!found && curPosition > 0)
3317                   {
3318                      foundAlpha = true;
3319                      lastC = 0;
3320                      lastLine = line;
3321                      break;
3322                   }
3323                }
3324                if(foundAlpha)
3325                {
3326                   textBlock = lastLine;
3327                   curPosition = lastC;
3328                   if(!key.shift)
3329                   {
3330                      selPosition = curPosition;
3331                      selBlock = textBlock;
3332                   }
3333                   PositionCaret(true);
3334                   Update(null);
3335                }
3336                break;
3337             }
3338             case Key { right, shift = true }:
3339             case right:
3340                if(curPosition < textBlock.textLen)
3341                {
3342                   curPosition += UTF8_NUM_BYTES(textBlock.text[curPosition]);
3343                   if(!key.shift)
3344                   {
3345                      selPosition = curPosition;
3346                      selBlock = textBlock;
3347                   }
3348                   PositionCaret(true);
3349                   Update(null);
3350                }
3351                else if(textBlock.next && textBlock.next.next)
3352                {
3353                   textBlock = textBlock.next.next;
3354                   curPosition = 0;
3355                   if(!key.shift)
3356                   {
3357                      selPosition = curPosition;
3358                      selBlock = textBlock;
3359                   }
3360                   PositionCaret(true);
3361                   Update(null);
3362                }
3363                break;
3364             case Key { left, shift = true }:
3365             case left:
3366                if(curPosition > 0)
3367                {
3368                   while(curPosition > 0 && !UTF8_IS_FIRST(textBlock.text[--curPosition]));
3369                   if(!key.shift)
3370                   {
3371                      selPosition = curPosition;
3372                      selBlock = textBlock;
3373                   }
3374                   PositionCaret(true);
3375                   Update(null);
3376                }
3377                else if(textBlock.prev)
3378                {
3379                   textBlock = textBlock.prev.prev;
3380                   curPosition = textBlock.textLen;
3381                   if(!key.shift)
3382                   {
3383                      selPosition = curPosition;
3384                      selBlock = textBlock;
3385                   }
3386                   PositionCaret(true);
3387                   Update(null);
3388                }
3389                break;
3390             case backSpace:
3391                if(readOnly) break;
3392                if(textBlock == selBlock && curPosition == selPosition)
3393                {
3394                   if(curPosition)
3395                   {
3396                      int c = curPosition;
3397                      int nb = 1;
3398                      while(c > 0 && !UTF8_IS_FIRST(textBlock.text[--c])) nb++;
3399                      memmove(textBlock.text + curPosition - nb, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
3400                      textBlock.textLen -= nb;
3401                      textBlock.text = renew textBlock.text char[textBlock.textLen + 1];
3402                      curPosition -= nb;
3403                      selPosition = curPosition;
3404                      selBlock = textBlock;
3405
3406                      ComputeMinSizes();
3407                      ComputeSizes();
3408                      PositionCaret(true);
3409                      Update(null);
3410                   }
3411                   else if(textBlock.prev)
3412                   {
3413                      Block prev = textBlock.prev, prevBlock = textBlock.prev.prev;
3414                      prevBlock.text = renew prevBlock.text char[prevBlock.textLen + textBlock.textLen + 1];
3415                      memcpy(prevBlock.text + prevBlock.textLen, textBlock.text, textBlock.textLen + 1);
3416
3417                      selPosition = curPosition = prevBlock.textLen;
3418                      selBlock = textBlock;
3419                      prevBlock.textLen += textBlock.textLen;
3420                      textBlock.parent.subBlocks.Remove(prev);
3421                      if(prev == selBlock)
3422                      {
3423                         selBlock = textBlock;
3424                         selPosition = curPosition;
3425                      }
3426                      delete prev;
3427                      textBlock.parent.subBlocks.Remove(textBlock);
3428                      if(textBlock == selBlock)
3429                      {
3430                         selBlock = prevBlock;
3431                         selPosition = curPosition;
3432                      }
3433                      delete textBlock;
3434                      textBlock = prevBlock;
3435
3436                      ComputeMinSizes();
3437                      ComputeSizes();
3438                      PositionCaret(true);
3439                      Update(null);
3440                   }
3441                }
3442                else
3443                   DeleteSelection();
3444                break;
3445             case del:
3446                if(readOnly) break;
3447                if(textBlock != selBlock || curPosition != selPosition)
3448                   DeleteSelection();
3449                else if(textBlock.textLen > curPosition)
3450                {
3451                   int nb = UTF8_NUM_BYTES(textBlock.text[curPosition]);
3452                   memmove(textBlock.text + curPosition, textBlock.text + curPosition + nb, textBlock.textLen - curPosition + 1 - nb + 1);
3453                   textBlock.textLen -= nb;
3454                   textBlock.text = renew textBlock.text char[textBlock.textLen + 1];
3455
3456                   ComputeMinSizes();
3457                   ComputeSizes();
3458
3459                   PositionCaret(true);
3460                   Update(null);
3461                }
3462                else if(textBlock.next && textBlock.next.next)
3463                {
3464                   Block next = textBlock.next, nextBlock = textBlock.next.next;
3465                   textBlock.text = renew textBlock.text char[textBlock.textLen + nextBlock.textLen + 1];
3466                   memcpy(textBlock.text + textBlock.textLen, nextBlock.text, nextBlock.textLen + 1);
3467
3468                   textBlock.textLen += nextBlock.textLen;
3469                   textBlock.parent.subBlocks.Remove(next);
3470                   if(next == selBlock)
3471                   {
3472                      selBlock = textBlock;
3473                      selPosition = curPosition;
3474                   }
3475                   delete next;
3476                   textBlock.parent.subBlocks.Remove(nextBlock);
3477                   if(nextBlock == selBlock)
3478                   {
3479                      selBlock = textBlock;
3480                      selPosition = curPosition;
3481                   }
3482                   delete nextBlock;
3483
3484                   ComputeMinSizes();
3485                   ComputeSizes();
3486                   PositionCaret(true);
3487                   Update(null);
3488                }
3489                break;
3490             case enter:
3491             {
3492                int th = 0;
3493                Block block;
3494                Block newBlock;
3495                int startY, startX;
3496
3497                if(readOnly) break;
3498                DeleteSelection();
3499
3500                block = { type = BR, parent = textBlock.parent, font = textBlock.font };
3501                newBlock = { type = TEXT, parent = textBlock.parent, font = textBlock.font };
3502                startY = textBlock.startY;
3503                startX = textBlock.startX;
3504
3505                display.FontExtent(textBlock.font.font, " ", 1, null, &th);
3506                textBlock.parent.subBlocks.Insert(textBlock, block);
3507                textBlock.parent.subBlocks.Insert(block, newBlock);
3508
3509                startY += th;
3510
3511                newBlock.textLen = textBlock.textLen - curPosition;
3512                newBlock.text = new char[newBlock.textLen+1];
3513                memcpy(newBlock.text, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
3514                textBlock.textLen = curPosition;
3515                textBlock.text[curPosition] = 0;
3516
3517                newBlock.startY = startY;
3518                newBlock.startX = startX;
3519                selPosition = curPosition = 0;
3520
3521                ComputeMinSizes();
3522                ComputeSizes();
3523
3524                textBlock = newBlock;
3525                selBlock = textBlock;
3526                PositionCaret(true);
3527                Update(null);
3528                break;
3529             }
3530             case ctrlX:
3531             case Key { del, shift = true }:
3532                if(readOnly) break;
3533                // Cut
3534                CopySelection();
3535                DeleteSelection();
3536                break;
3537             case ctrlC:
3538             case ctrlInsert:
3539                // Copy
3540                CopySelection();
3541                break;
3542             case shiftInsert:
3543             case ctrlV:
3544                if(!readOnly)
3545                {
3546                   ClipBoard clipBoard { };
3547                   if(clipBoard.Load())
3548                   {
3549                      int c;
3550                      char * text = clipBoard.memory;
3551                      char ch;
3552                      int start = 0;
3553                      Block parent;
3554                      FontEntry font;
3555
3556                      DeleteSelection();
3557
3558                      parent = textBlock.parent;
3559                      font = textBlock.font;
3560
3561                      for(c = 0; ; c++)
3562                      {
3563                         ch = text[c];
3564                         if(ch == '\n' || ch == '\r' || !ch)
3565                         {
3566                            int len = c - start;
3567                            textBlock.text = renew textBlock.text char[textBlock.textLen + 1 + len];
3568                            memmove(textBlock.text + curPosition + len, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
3569                            memcpy(textBlock.text + curPosition, text + start, len);
3570                            textBlock.textLen += len;
3571                            curPosition += len;
3572                            selPosition = curPosition;
3573                            selBlock = textBlock;
3574                            if(!ch) break;
3575                            {
3576                               Block block { type = BR, parent = parent, font = font };
3577                               Block newBlock { type = TEXT, parent = parent, font = font };
3578                               int startY = textBlock.startY, startX = textBlock.startX;
3579                               int th = 0;
3580
3581                               display.FontExtent(textBlock.font.font, " ", 1, null, &th);
3582                               textBlock.parent.subBlocks.Insert(textBlock, block);
3583                               textBlock.parent.subBlocks.Insert(block, newBlock);
3584
3585                               startY += th;
3586
3587                               newBlock.textLen = textBlock.textLen - curPosition;
3588                               newBlock.text = new char[newBlock.textLen+1];
3589                               memcpy(newBlock.text, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
3590                               textBlock.textLen = curPosition;
3591                               textBlock.text[curPosition] = 0;
3592
3593                               newBlock.startY = startY;
3594                               newBlock.startX = startX;
3595                               selPosition = curPosition = 0;
3596                               selBlock = textBlock;
3597                               textBlock = newBlock;
3598                            }
3599                            if(ch == '\r' && text[c+1] == '\n') c++;
3600                            start = c + 1;
3601                         }
3602                      }
3603                      ComputeMinSizes();
3604                      ComputeSizes();
3605                      PositionCaret(true);
3606                      Update(null);
3607                   }
3608                   delete clipBoard;
3609                }
3610                break;
3611             default:
3612             {
3613                // eC BUG HERE: (Should be fixed)
3614                if(!readOnly && !key.ctrl && !key.alt && ch >= 32 && ch != 128 /*&& ch < 128*/)
3615                {
3616                   char string[5];
3617                   int len = UTF32toUTF8Len(&ch, 1, string, 5);
3618                   int c;
3619
3620                   DeleteSelection();
3621
3622                   textBlock.text = renew textBlock.text char[textBlock.textLen + len + 1];
3623                   memmove(textBlock.text + curPosition + len, textBlock.text + curPosition, textBlock.textLen - curPosition + 1);
3624
3625                   for(c = 0; c<len; c++)
3626                   {
3627                      textBlock.text[curPosition] = string[c];
3628                      textBlock.textLen++;
3629                      curPosition++;
3630                   }
3631                   selPosition = curPosition;
3632                   selBlock = textBlock;
3633
3634                   {
3635                      //Clear(html.block);
3636                      //CreateForms(html.block);
3637                      ComputeMinSizes();
3638                      ComputeSizes();
3639                      //PositionForms();
3640                   }
3641                   PositionCaret(true);
3642                   Update(null);
3643                }
3644             }
3645          }
3646       }
3647       return true;
3648    }
3649
3650    void OnResize(int width, int height)
3651    {
3652       HTMLView::OnResize(width, height);
3653       PositionCaret(true);
3654    }
3655
3656    int caretX, caretY;
3657    void PositionCaret(bool setCaretX)
3658    {
3659       if(edit)
3660       {
3661          int tw = 0, th = 0;
3662          int textPos = 0;
3663          int sx = textBlock.startX, sy = textBlock.startY;
3664          char * text = textBlock.text;
3665          int maxW;
3666          Block block = textBlock;
3667          while(block && block.type != TD) block = block.parent;
3668          if(block)
3669          {
3670             Block table = block;
3671             while(table && table.type != TABLE) table = table.parent;
3672             if(table)
3673                maxW = block.w - 2* table.cellPadding;
3674             else
3675                maxW = clientSize.w - 10 - sx;
3676          }
3677          else
3678             maxW = clientSize.w - 10 - sx;
3679
3680          display.FontExtent(textBlock.font.font, " ", 1, null, &th);
3681
3682          while(textPos < textBlock.textLen)
3683          {
3684             int startPos = textPos;
3685             int width = 0;
3686             int x = 0;
3687             bool lineComplete = false;
3688
3689             for(; textPos<textBlock.textLen && !lineComplete;)
3690             {
3691                int w;
3692                int len;
3693                char * nextSpace = strchr(text + textPos, ' ');
3694
3695                if(nextSpace)
3696                   len = (nextSpace - (text + textPos)) + 1;
3697                else
3698                   len = textBlock.textLen - textPos;
3699
3700                display.FontExtent(textBlock.font.font, text + textPos, len, &w, &th);
3701
3702                if(x + width + w > maxW && x > 0)
3703                {
3704                   lineComplete = true;
3705                   break;
3706                }
3707                textPos += len;
3708
3709                width += w;
3710
3711                if(nextSpace)
3712                {
3713                   x += width;
3714                   width = 0;
3715                }
3716             }
3717             if(curPosition < textPos || textPos == textBlock.textLen)
3718             {
3719                int len = curPosition - startPos;
3720                display.FontExtent(textBlock.font.font, text + startPos, len, &tw, null);
3721                sx += tw;
3722                break;
3723             }
3724             sy += th;
3725             sx = textBlock.startX;
3726          }
3727          if(setCaretX)
3728             caretX = sx;
3729          caretY = sy;
3730          SetCaret(sx, sy, th);
3731          {
3732             Point scrollPos = scroll;
3733             bool doScroll = false;
3734             if(sy - scroll.y + th > clientSize.h)
3735             {
3736                scrollPos.y = sy + th - clientSize.h;
3737                doScroll = true;
3738             }
3739             else if(sy - scroll.y < 0)
3740             {
3741                scrollPos.y = sy;
3742                doScroll = true;
3743             }
3744             if(sx - scroll.x + 10 > clientSize.w)
3745             {
3746                scrollPos.x = sx + 10 - clientSize.w;
3747                doScroll = true;
3748             }
3749             else if(sx - scroll.x < 10)
3750             {
3751                scrollPos.x = sx - 10;
3752                doScroll = true;
3753             }
3754             if(doScroll)
3755                scroll = scrollPos;
3756          }
3757       }
3758       else
3759          SetCaret(0,0,0);
3760    }
3761
3762    // Returns a character offset into the TextBlock from a window coordinate
3763    int TextPosFromPoint(int px, int py, Block * block, bool half)
3764    {
3765       Block parentBlock = this.textBlock.parent;
3766       Block textBlock;
3767       int result = 0;
3768       *block = this.textBlock;
3769
3770       px += scroll.x;
3771       py += scroll.y;
3772
3773       for(textBlock = parentBlock.subBlocks.first; textBlock; textBlock = textBlock.next)
3774       {
3775          int sx = textBlock.startX, sy = textBlock.startY;
3776          int th = 0;
3777          int textPos = 0;
3778          char * text = textBlock.text;
3779          int maxW;
3780          Block b = textBlock;
3781          int space;
3782
3783          if(textBlock.type != TEXT) continue;
3784
3785          while(b && b.type != TD) b = b.parent;
3786          if(b)
3787          {
3788             Block table = b;
3789             while(table && table.type != TABLE) table = table.parent;
3790             if(table)
3791                maxW = b.w - 2* table.cellPadding;
3792             else
3793                maxW = clientSize.w - 10 - sx;
3794          }
3795          else
3796             maxW = clientSize.w - 10 - sx;
3797
3798          display.FontExtent(textBlock.font.font, " ", 1, &space, &th);
3799          //space = space/2+2;
3800          space = 2;
3801
3802          while(textPos < textBlock.textLen)
3803          {
3804             int width = 0;
3805             int x = 0;
3806             bool lineComplete = false;
3807
3808             for(; textPos<textBlock.textLen && !lineComplete;)
3809             {
3810                int w;
3811                int len;
3812                char * nextSpace = strchr(text + textPos, ' ');
3813
3814                if(nextSpace)
3815                   len = (nextSpace - (text + textPos)) + 1;
3816                else
3817                   len = textBlock.textLen - textPos;
3818
3819                display.FontExtent(textBlock.font.font, text + textPos, len, &w, &th);
3820
3821                sx = x + textBlock.startX;
3822                if(/*py >= sy && */py < sy + th && /*px >= sx-space && */px < sx + w-space)
3823                {
3824                   int c, numBytes;
3825                   char ch;
3826                   *block = textBlock;
3827                   for(c = textPos; (ch = text[c]); c += numBytes)
3828                   {
3829                      numBytes = UTF8_NUM_BYTES(ch);
3830                      display.FontExtent(textBlock.font.font, text + c, numBytes, &w, &th);
3831                      if(/*py >= sy && */py < sy + th && /*px >= sx-w/2-space && */px < sx + (half ? w/2 : w) -space)
3832                         break;
3833                      sx += w;
3834                   }
3835                   return c;
3836                }
3837
3838                if(x + width + w > maxW && x > 0)
3839                {
3840                   lineComplete = true;
3841                   break;
3842                }
3843                textPos += len;
3844
3845                width += w;
3846
3847                if(nextSpace)
3848                {
3849                   x += width;
3850                   width = 0;
3851                }
3852             }
3853             if(/*py >= sy && */py < sy + th)
3854             {
3855                *block = textBlock;
3856                return textBlock.textLen;
3857             }
3858             sy += th;
3859          }
3860          *block = textBlock;
3861          result = textBlock.textLen;
3862       }
3863       return result;
3864    }
3865 }
3866
3867 Application componentsApp;
3868
3869 class Documentor : GuiApplication
3870 {
3871    bool Init()
3872    {
3873       Platform os = __runtimePlatform;
3874       SetGlobalContext(globalContext);
3875       SetExcludedSymbols(&excludedSymbols);
3876       SetDefines(&::defines);
3877       SetImports(&imports);
3878       SetInDocumentor(true);
3879
3880       SetGlobalData(globalData);
3881
3882       settingsContainer.dataOwner = &settings;
3883       settingsContainer.Load();
3884       if(!settings.docDir || !settings.docDir[0] )
3885       {
3886          if(os == win32) // if Windows OS then
3887          {
3888             char programFilesDir[MAX_LOCATION];
3889             char appData[MAX_LOCATION];
3890             char homeDrive[MAX_LOCATION];
3891             char winDir[MAX_LOCATION];
3892             GetEnvironment("APPDATA", appData, sizeof(appData));
3893             GetEnvironment("HOMEDRIVE", homeDrive, sizeof(homeDrive));
3894             GetEnvironment("windir", winDir, sizeof(winDir));
3895             if(GetEnvironment("ProgramFiles", programFilesDir, MAX_LOCATION))
3896             {
3897                PathCat(programFilesDir, "ECERE SDK\\doc");
3898                settings.docDir = programFilesDir;
3899             }
3900             else if(homeDrive[0])
3901             {
3902                PathCat(homeDrive, "ECERE SDK\\doc");
3903                settings.docDir = homeDrive;
3904             }
3905             else if(winDir[0])
3906             {
3907                PathCat(winDir, "..\\ECERE SDK\\doc");
3908                settings.docDir = winDir;
3909             }
3910             else
3911                settings.docDir = "C:\\ECERE SDK\\doc";
3912          }
3913          else // if Os is Linux, or Mac OSX or something else
3914             settings.docDir = "/usr/share/ecere/doc/";
3915          settingsContainer.Save();
3916       }
3917
3918       //if(argc > 1)
3919       {
3920       #if 0
3921          Module module = eModule_Load(componentsApp, "ecere" /*argv[1]*/, privateAccess);
3922          DataRow row;
3923          AddComponents(module, true);
3924          mainForm.browser.currentRow = row = mainForm.browser.FindSubRow((int64)module);
3925          // mainForm.browser.currentRow = row = mainForm.browser.FindSubRow((int64)eSystem_FindClass(componentsApp, "Window"));
3926          while((row = row.parent))
3927             row.collapsed = false;
3928       #endif
3929       }
3930
3931       commandThread.Create();
3932       return true;
3933    }
3934
3935    bool Cycle(bool idle)
3936    {
3937       if(quit)
3938          mainForm.Destroy(0);
3939       return true;
3940    }
3941
3942    void Terminate()
3943    {
3944       PrintLn("Exited");
3945       console.Flush();
3946       quit = true;
3947       if(commandThread.created)
3948       {
3949          console.CloseInput();
3950          console.CloseOutput();
3951          app.Unlock();
3952          commandThread.Wait();
3953          app.Lock();
3954       }
3955
3956       FreeContext(globalContext);
3957       FreeExcludedSymbols(excludedSymbols);
3958       ::defines.Free(FreeModuleDefine);
3959       imports.Free(FreeModuleImport);
3960
3961       FreeGlobalData(globalData);
3962       FreeTypeData(componentsApp);
3963       FreeIncludeFiles();
3964       delete componentsApp;
3965    }
3966 }
3967
3968 ConsoleFile console { };
3969 MainForm mainForm { };
3970 bool quit;
3971
3972 Thread commandThread
3973 {
3974    unsigned int Main()
3975    {
3976       while(!quit)
3977       {
3978          char command[1024];
3979          console.GetLine(command, sizeof(command));
3980          if(!quit && command[0])
3981          {
3982             app.Lock();
3983             if(!strcmpi(command, "Activate"))
3984                mainForm.Activate();
3985             else if(!strcmpi(command, "Quit"))
3986                quit = true;
3987             app.Unlock();
3988          }
3989       }
3990       return 0;
3991    }
3992 };