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