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