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