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