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