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