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