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