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