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