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