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