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