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