ide/settings: Memory leak fixes
[sdk] / ide / src / IDESettings.ec
1 #ifdef ECERE_STATIC
2 public import static "ecere"
3 #else
4 public import "ecere"
5 #endif
6
7 // *** Color Schemes ***
8 class IDEColorScheme
9 {
10    String name;
11    SyntaxColorScheme syntaxColors;
12 public:
13
14    property const String name
15    {
16       set { delete name; name = CopyString(value); }
17       get { return name; }
18    }
19
20    Color selectionColor;
21    Color selectionText;
22    Color viewsBackground;
23    Color viewsText;
24    Color outputBackground;
25    Color outputText;
26    Color projectViewBackground;
27    Color projectViewText;
28    Color codeEditorBG;
29    Color codeEditorFG;
30    Color marginColor;
31    Color selectedMarginColor;
32    Color lineNumbersColor;
33    Color sheetSelectionColor;
34    Color sheetSelectionText;
35
36    property SyntaxColorScheme syntaxColors
37    {
38       set { delete syntaxColors; syntaxColors = value; if(value) incref value; }
39       get { return syntaxColors; }
40    }
41
42    ~IDEColorScheme()
43    {
44       delete syntaxColors;
45       delete name;
46    }
47 }
48
49 IDEColorScheme colorScheme;
50
51 // Default Color Schemes
52 IDEColorScheme darkColorScheme
53 {
54    name = "Classic Dark";
55    selectionColor = lightYellow;
56    selectionText = Color { 30, 40, 50 };
57    viewsBackground = Color { 30, 40, 50 };
58    viewsText = lightGray;
59    outputBackground = black;
60    outputText = lime;
61    sheetSelectionColor = Color { 170, 220, 255 };
62    sheetSelectionText = black;
63    projectViewBackground = Color { 30, 40, 50 };
64    projectViewText = lightGray;
65    codeEditorBG = black;
66    codeEditorFG = ivory;
67    marginColor = Color {24, 24, 24};
68    selectedMarginColor = Color {64, 64, 64};
69    lineNumbersColor = Color {160, 160, 160};
70    syntaxColors =
71    {
72       keywordColors = [ skyBlue, skyBlue ];
73       commentColor = Color { 125, 125, 125 };
74       charLiteralColor = Color { 245, 50, 245 };
75       stringLiteralColor = Color { 245, 50, 245 };
76       preprocessorColor = { 120, 220, 140 };
77       numberColor = Color {   0, 192, 192 };
78    };
79 };
80
81 IDEColorScheme lightColorScheme
82 {
83    name = "Classic Light";
84    selectionColor = Color { 10, 36, 106 };
85    selectionText = white;
86    viewsBackground = white;
87    viewsText = black;
88    outputBackground = white;
89    outputText = black;
90    sheetSelectionColor = Color { 170, 220, 255 };
91    sheetSelectionText = black;
92    projectViewBackground = white;
93    projectViewText = black;
94    codeEditorBG = white;
95    codeEditorFG = black;
96    marginColor = Color {230, 230, 230};
97    selectedMarginColor = Color {200, 200, 200};
98    lineNumbersColor = Color {60, 60, 60};
99    syntaxColors =
100    {
101       keywordColors = [ blue, blue ];
102       commentColor = dimGray;
103       charLiteralColor = crimson;
104       stringLiteralColor = crimson;
105       preprocessorColor = green;
106       numberColor = teal;
107    };
108 };
109
110 IDEColorScheme greenColorScheme
111 {
112    name = "Green",
113    selectionColor = 0x00FFFFE0,
114    selectionText = 0x001E2832,
115    viewsBackground = 0x001E2832,
116    viewsText = 0x00D3D3D3,
117    outputBackground = 0x00000000,
118    outputText = 0x0000FF00,
119    projectViewBackground = 0x001E2832,
120    projectViewText = 0x00D3D3D3,
121    codeEditorBG = 0x00000000,
122    codeEditorFG = 0x00FFFFF0,
123    marginColor = 0x001100,
124    selectedMarginColor = 0x002200,
125    lineNumbersColor = 0x00FF00,
126    sheetSelectionColor = 0x00AADCFF,
127    sheetSelectionText = 0x00000000,
128    syntaxColors = {
129       commentColor = 0x008c00,
130       charLiteralColor = 0x89de00,
131       stringLiteralColor = 0x89de00,
132       preprocessorColor = 0x0078DC8C,
133       numberColor = { 8, 237, 141 },
134       keywordColors = [
135          0x00e09d,
136          0x00e09d
137       ]
138    }
139 };
140
141 IDEColorScheme grayColorScheme
142 {
143    name = "Gray & Orange",
144    selectionColor = 0x00FFFFE0,
145    selectionText = 0x001E2832,
146    viewsBackground = 0x001E2832,
147    viewsText = 0x00D3D3D3,
148    outputBackground = 0x00000000,
149    outputText = 0x0000FF00,
150    projectViewBackground = 0x001E2832,
151    projectViewText = 0x00D3D3D3,
152    codeEditorBG = 0x00202020,
153    codeEditorFG = 0x00E2E2E5,
154    marginColor = 0x00303030,
155    selectedMarginColor = 0x00404040,
156    lineNumbersColor = 0x00939395,
157    sheetSelectionColor = 0x00AADCFF,
158    sheetSelectionText = 0x00000000,
159    syntaxColors = {
160       commentColor = 0x005F5F5F,
161       charLiteralColor = 0x0089DE00,
162       stringLiteralColor = 0x00707070,
163       preprocessorColor = 0x00FAF4C6,
164       numberColor = 0x00FF9800,
165       keywordColors = [
166          0x00FF9800,
167          0x00FF9800
168       ]
169    }
170 };
171
172 define ecpDefaultCommand = "ecp";
173 define eccDefaultCommand = "ecc";
174 define ecsDefaultCommand = "ecs";
175 define earDefaultCommand = "ear";
176 define cppDefaultCommand = "gcc"; // As per #624 we decided to default to "gcc"...
177 define ccDefaultCommand = "gcc";
178 define cxxDefaultCommand = "g++";
179 //define ldDefaultCommand = "gcc";
180 define arDefaultCommand = "ar";
181 define objectDefaultFileExt = "o";
182 define outputDefaultFileExt = "";
183
184 import "StringsBox"
185
186 import "OldIDESettings"
187
188 #ifdef __WIN32__
189 #define WIN32_LEAN_AND_MEAN
190 #define Sleep _Sleep
191 #include <windows.h>
192 #undef MoveFileEx
193 #undef Sleep
194 #undef MessageBox
195 #undef GetObject
196 #endif
197
198 define MaxRecent = 9;
199
200 enum DirTypes { includes, libraries, executables };
201
202 define defaultCompilerName = "Default";
203
204 define defaultObjDirExpression = "obj/$(CONFIG).$(PLATFORM)$(COMPILER_SUFFIX)$(DEBUG_SUFFIX)";
205
206 const char * settingsDirectoryNames[DirTypes] =
207 {
208    "Include Files",
209    "Library Files",
210    "Executable Files"
211 };
212
213 // This function cannot accept same pointer for source and output
214 // todo: rename ReplaceSpaces to EscapeSpaceAndSpecialChars or something
215 void ReplaceSpaces(char * output, const char * source)
216 {
217    int c, dc;
218    char ch, pch = 0;
219
220    for(c = 0, dc = 0; (ch = source[c]); c++, dc++)
221    {
222       if(ch == ' ') output[dc++] = '\\';
223       if(ch == '\"') output[dc++] = '\\';
224       if(ch == '&') output[dc++] = '\\';
225       if(pch != '$')
226       {
227          if(ch == '(' || ch == ')') output[dc++] = '\\';
228          pch = ch;
229       }
230       else if(ch == ')')
231          pch = 0;
232       output[dc] = ch;
233    }
234    output[dc] = '\0';
235 }
236
237
238 enum GlobalSettingsChange { none, editorSettings, projectOptions, compilerSettings };
239
240 enum PathRelationship { unrelated, identical, siblings, subPath, parentPath, insuficientInput, pathEmpty, toEmpty, pathNull, toNull, bothEmpty, bothNull };
241 PathRelationship eString_PathRelated(const char * path, const char * to, char * pathDiff)
242 {
243    PathRelationship result;
244    if(pathDiff) *pathDiff = '\0';
245    if(path && *path && to && *to)
246    {
247       char toPart[MAX_FILENAME], toRest[MAX_LOCATION];
248       char pathPart[MAX_FILENAME], pathRest[MAX_LOCATION];
249       strcpy(toRest, to);
250       strcpy(pathRest, path);
251       for(; toRest[0] && pathRest[0];)
252       {
253          SplitDirectory(toRest, toPart, toRest);
254          SplitDirectory(pathRest, pathPart, pathRest);
255          if(!fstrcmp(pathPart, toPart)) result = siblings;
256          else break;
257       }
258       if(result == siblings)
259       {
260          if(!*toRest && !*pathRest) result = identical;
261          else if(!*pathRest) result = parentPath;
262          else result = subPath;
263          if(pathDiff && result != identical) strcpy(pathDiff, *pathRest == '\0' ? toRest : pathRest);
264       }
265       else result = unrelated;
266    }
267    else
268    {
269       if(path && to)
270       {
271          if(!*path && !*to) result = bothEmpty;
272          else if(!*path) result = pathEmpty;
273          else result = toEmpty;
274       }
275       else if(!path && !to) result = bothNull;
276       else if(!path) result = pathNull;
277       else result = toNull;
278    }
279    return result;
280 }
281
282 char * CopyValidateMakefilePath(const char * path)
283 {
284    const int map[]  =    {           0,           1,             2,             3,           4,                    5,                 6,            0,                   1,                    2,        7 };
285    const char * vars[] = { "$(MODULE)", "$(CONFIG)", "$(PLATFORM)", "$(COMPILER)", "$(TARGET)", "$(COMPILER_SUFFIX)", "$(DEBUG_SUFFIX)", "$(PROJECT)",  "$(CONFIGURATION)", "$(TARGET_PLATFORM)",(char *)0 };
286
287    char * copy = null;
288    if(path)
289    {
290       int len;
291       len = (int)strlen(path);
292       copy = CopyString(path);
293       if(len)
294       {
295          int c;
296          char * tmp = copy;
297          char * start = tmp;
298          Array<const char *> parts { };
299
300          for(c=0; c<len; c++)
301          {
302             if(tmp[c] == '$')
303             {
304                int v;
305                for(v=0; vars[v]; v++)
306                {
307                   if(SearchString(&tmp[c], 0, vars[v], false, false) == &tmp[c])
308                   {
309                      tmp[c] = '\0';
310                      parts.Add(start);
311                      parts.Add(vars[map[v]]);
312                      c += strlen(vars[v]);
313                      start = &tmp[c];
314                      c--;
315                      break;
316                   }
317                }
318             }
319          }
320          if(start[0])
321             parts.Add(start);
322
323          if(parts.count)
324          {
325             /*int c, */len = 0;
326             for(c=0; c<parts.count; c++) len += strlen(parts[c]);
327             copy = new char[++len];
328             copy[0] = '\0';
329             for(c=0; c<parts.count; c++) strcat(copy, parts[c]);
330          }
331          else
332             copy = null;
333          delete parts;
334          delete tmp;
335       }
336    }
337    return copy;
338 }
339
340 void ValidPathBufCopy(char *output, const char *input)
341 {
342 #ifdef __WIN32__
343    bool volumePath = false;
344 #endif
345    strcpy(output, input);
346    TrimLSpaces(output, output);
347    TrimRSpaces(output, output);
348    MakeSystemPath(output);
349 #ifdef __WIN32__
350    if(output[0] && output[1] == ':')
351    {
352       output[1] = '_';
353       volumePath = true;
354    }
355 #endif
356    {
357       const char * chars = "*|:\",<>?";
358       char ch, * s = output, * o = output;
359       while((ch = *s++)) { if(!strchr(chars, ch)) *o++ = ch; }
360       *o = '\0';
361    }
362 #ifdef __WIN32__
363    if(volumePath && output[0])
364       output[1] = ':';
365 #endif
366 }
367
368 void RemoveTrailingPathSeparator(char *path)
369 {
370    int len;
371    len = (int)strlen(path);
372    if(len>1 && path[len-1] == DIR_SEP)
373       path[--len] = '\0';
374 }
375
376 void BasicValidatePathBoxPath(PathBox pathBox)
377 {
378    char path[MAX_LOCATION];
379    ValidPathBufCopy(path, pathBox.path);
380    RemoveTrailingPathSeparator(path);
381    pathBox.path = path;
382 }
383
384 CompilerConfig MakeDefaultCompiler(const char * name, bool readOnly)
385 {
386    CompilerConfig defaultCompiler
387    {
388       name,
389       readOnly,
390       gcc,
391       __runtimePlatform,
392       1,
393       makeDefaultCommand,
394       ecpDefaultCommand,
395       eccDefaultCommand,
396       ecsDefaultCommand,
397       earDefaultCommand,
398       cppDefaultCommand,
399       ccDefaultCommand,
400       cxxDefaultCommand,
401       arDefaultCommand
402       //ldDefaultCommand
403    };
404    incref defaultCompiler;
405    return defaultCompiler;
406 }
407
408 //#define SETTINGS_TEST
409 #ifdef SETTINGS_TEST
410 define settingsDir = ".ecereIDE-SettingsTest";
411 define ideSettingsName = "ecereIDE-SettingsTest";
412 #else
413 define settingsDir = ".ecereIDE";
414 define ideSettingsName = "ecereIDE";
415 #endif
416
417 class IDEConfigHolder
418 {
419    CompilerConfigs compilers { };
420    RecentFiles recentFiles { };
421    RecentWorkspaces recentWorkspaces { };
422
423    property CompilerConfigs compilers
424    {
425       set { compilers.Free(); delete compilers; compilers = value; }
426       get { return compilers; }
427    }
428    property RecentFiles recentFiles
429    {
430       set { recentFiles.Free(); delete recentFiles; recentFiles = value; }
431       get { return recentFiles; }
432    }
433    property RecentWorkspaces recentProjects
434    {
435       set { recentWorkspaces.Free(); delete recentWorkspaces; recentWorkspaces = value; }
436       get { return recentWorkspaces; }
437    }
438
439    ~IDEConfigHolder()
440    {
441       compilers.Free();
442       recentFiles.Free();
443       recentWorkspaces.Free();
444    }
445
446    void forcePathSeparatorStyle(bool unixStyle)
447    {
448       char from, to;
449       if(unixStyle)
450          from = '\\', to = '/';
451       else
452          from = '/', to = '\\';
453       recentFiles.changeChar(from, to);
454       recentWorkspaces.changeChar(from, to);
455    }
456 }
457
458 class IDESettingsContainer : GlobalSettings
459 {
460    virtual void onLoadCompilerConfigs();
461    virtual void onLoadRecentFiles();
462    virtual void onLoadRecentProjects();
463    virtual void onLoad();
464
465    CompilerConfigs compilerConfigs;
466    RecentFiles recentFiles;
467    RecentWorkspaces recentProjects;
468
469    property bool useNewConfigurationFiles
470    {
471       set
472       {
473          if(value)
474          {
475             driver = "ECON";
476             settingsName = "config";
477             settingsExtension = "econ";
478             settingsDirectory = settingsDir;
479          }
480          else
481          {
482             driver = "JSON";
483             settingsName = ideSettingsName;
484             settingsExtension = null;
485             settingsDirectory = null;
486          }
487       }
488    }
489
490    char moduleLocation[MAX_LOCATION];
491
492    FileMonitor recentFilesMonitor
493    {
494       this, fileChange = { modified = true };
495
496       bool OnFileNotify(FileChange action, const char * param)
497       {
498          File f;
499          recentFilesMonitor.StopMonitoring();
500          f = FileOpen(recentFilesMonitor.fileName, read);
501          if(f)
502          {
503             int c;
504             bool locked;
505             for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
506             recentFiles.read(this);
507             f.Unlock(0,0,true);
508             delete f;
509          }
510          return true;
511       }
512    };
513
514    FileMonitor recentProjectsMonitor
515    {
516       this, fileChange = { modified = true };
517
518       bool OnFileNotify(FileChange action, const char * param)
519       {
520          File f;
521          recentProjectsMonitor.StopMonitoring();
522          f = FileOpen(recentProjectsMonitor.fileName, read);
523          if(f)
524          {
525             int c;
526             bool locked;
527             for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
528             recentProjects.read(this);
529             f.Unlock(0,0,true);
530             delete f;
531          }
532          return true;
533       }
534    };
535
536    static void getConfigFilePath(char * path, Class _class, char * dir, const char * configName)
537    {
538       if(dir) *dir = 0;
539       strcpy(path, settingsFilePath);
540       StripLastDirectory(path, path);
541       if(oldConfig)
542          PathCatSlash(path, settingsDir);
543       if(_class == class(CompilerConfig))
544       {
545          PathCatSlash(path, "compilerConfigs");
546          if(dir)
547             strcpy(dir, path);
548          if(configName)
549          {
550             PathCatSlash(path, configName);
551             strcat(path, ".econ");
552          }
553       }
554       else if(_class == class(RecentFilesData))
555          PathCatSlash(path, "recentFiles.econ");
556       else if(_class == class(RecentWorkspacesData))
557          PathCatSlash(path, "recentWorkspaces.econ");
558    }
559
560 private:
561    bool oldConfig;
562    FileSize settingsFileSize;
563
564    IDESettingsContainer()
565    {
566       char path[MAX_LOCATION];
567       char * start;
568       LocateModule(null, moduleLocation);
569       strcpy(path, moduleLocation);
570       StripLastDirectory(moduleLocation, moduleLocation);
571       ChangeCh(moduleLocation, '\\', '/');
572       // PortableApps.com directory structure
573       if((start = strstr(path, "\\App\\EcereSDK\\bin\\ecere-ide.exe")))
574       {
575          char configFilePath[MAX_LOCATION];
576          char defaultConfigFilePath[MAX_LOCATION];
577
578          start[0] = '\0';
579
580          strcpy(configFilePath, path);
581          PathCat(configFilePath, "Data");
582          PathCat(configFilePath, ideSettingsName);
583          ChangeExtension(configFilePath, "ini", configFilePath);
584
585          strcpy(defaultConfigFilePath, path);
586          PathCat(defaultConfigFilePath, "App");
587          PathCat(defaultConfigFilePath, "DefaultData");
588          PathCat(defaultConfigFilePath, ideSettingsName);
589          ChangeExtension(defaultConfigFilePath, "ini", defaultConfigFilePath);
590
591          if(FileExists(defaultConfigFilePath))
592          {
593             if(!FileExists(configFilePath))
594             {
595                File f = FileOpen(defaultConfigFilePath, read);
596                f.CopyTo(configFilePath);
597                f.Flush();
598                delete f;
599             }
600             PathCat(path, "Data");
601             // the forced settings location will only be
602             // used if the running ide's path matches
603             // the PortableApps.com directory structure
604             // and the default ini file is found in
605             // the DefaultData directory
606             settingsLocation = path;
607             portable = true;
608          }
609       }
610    }
611
612    void OnAskReloadSettings()
613    {
614       FileSize newSettingsFileSize;
615
616       if(OpenAndLock(&newSettingsFileSize))
617       {
618          //if((double)settingsFileSize - (double)newSettingsFileSize < 2048)
619             Load();
620             onLoad();
621          /*else
622          {
623             GuiApplication app = ((GuiApplication)__thisModule.application);
624             Window w;
625             for(w = app.desktop.firstChild; w && (!w.created || !w.visible); w = w.next);
626
627             CloseAndMonitor();
628
629             MessageBox { master = w, type = ok, isModal = true,
630                   creationActivation = flash,
631                   text = "Global Settings Modified Externally",
632                   contents = "The global settings were modified by another process and a drastic shrinking of the settings file was detected.\n"
633                   "The new settings will not be loaded to prevent loss of your ide settings.\n"
634                   "Please check your settings file and make sure to save this IDE's global settings if your settings file has been compromised."
635                   }.Create();
636          }*/
637       }
638    }
639
640    SettingsIOResult Load()
641    {
642       IDESettings data;
643       SettingsIOResult result;
644       useNewConfigurationFiles = true;
645       result = GlobalSettings::Load();
646       data = (IDESettings)this.data;
647       oldConfig = false;
648       if(result == fileNotFound)
649       {
650          oldConfig = true;
651          useNewConfigurationFiles = false;
652          result = GlobalSettings::Load();
653       }
654       data = (IDESettings)this.data;
655       if(!data)
656       {
657          this.data = IDESettings { };
658          if(dataOwner)
659             *dataOwner = this.data;
660
661          if(result == fileNotCompatibleWithDriver)
662          {
663             bool loaded;
664             OldIDESettings oldSettings { };
665             Close();
666             loaded = oldSettings.Load() == success;
667             oldSettings.Close();
668             if(loaded)
669             {
670                data = (IDESettings)this.data;
671
672                for(c : oldSettings.compilerConfigs)
673                   data.compilerConfigs.Add(c.Copy());
674
675                for(s : oldSettings.recentFiles) data.recentFiles.Add(s);
676                for(s : oldSettings.recentProjects) data.recentProjects.Add(s);
677
678                data.docDir = oldSettings.docDir;
679                data.ideFileDialogLocation = oldSettings.ideFileDialogLocation;
680                data.ideProjectFileDialogLocation = oldSettings.ideProjectFileDialogLocation;
681                data.useFreeCaret = oldSettings.useFreeCaret;
682                data.showLineNumbers = oldSettings.showLineNumbers;
683                data.caretFollowsScrolling = oldSettings.caretFollowsScrolling;
684                delete data.displayDriver; data.displayDriver = CopyString(oldSettings.displayDriver);
685                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
686                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
687
688                Save();
689                result = success;
690             }
691             delete oldSettings;
692          }
693          if(result == fileNotFound || !data)
694          {
695             data = (IDESettings)this.data;
696             data.useFreeCaret = false; //true;
697             data.showLineNumbers = true;
698             data.caretFollowsScrolling = false; //true;
699          }
700       }
701
702       CloseAndMonitor();
703       FileGetSize(settingsFilePath, &settingsFileSize);
704       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
705          data.ManagePortablePaths(moduleLocation, true);
706       data.ForcePathSeparatorStyle(true);
707
708       if(!data.colorSchemes || !data.colorSchemes.count)
709       {
710          if(!data.colorSchemes) data.colorSchemes = { };
711
712          data.colorSchemes.Add(darkColorScheme);   incref darkColorScheme;
713          data.colorSchemes.Add(lightColorScheme);  incref lightColorScheme;
714          data.colorSchemes.Add(greenColorScheme);  incref greenColorScheme;
715          data.colorSchemes.Add(grayColorScheme);   incref grayColorScheme;
716       }
717       colorScheme = null;
718       if(data.activeColorScheme)
719       {
720          for(cs : data.colorSchemes; cs.name && !strcmp(cs.name, data.activeColorScheme))
721          {
722             colorScheme = cs;
723             break;
724          }
725       }
726       if(!colorScheme)
727       {
728          colorScheme = data.colorSchemes[0];
729          data.activeColorScheme = colorScheme.name;
730       }
731
732       // Import from previous ecereIDE settings
733       if(oldConfig)
734       {
735          // Save first so that settingsFilePath get set up
736          Save();
737
738          data.compilerConfigs.ensureDefaults();
739          data.compilerConfigs.write(this, null);
740          data.compilerConfigs.Free();
741
742          data.recentFiles.write(this);
743          data.recentFiles.Free();
744
745          data.recentProjects.write(this);
746          data.recentProjects.Free();
747       }
748       return result;
749    }
750
751    SettingsIOResult Save()
752    {
753       SettingsIOResult result;
754       IDESettings data;
755       useNewConfigurationFiles = true;
756       data = (IDESettings)this.data;
757       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
758          data.ManagePortablePaths(moduleLocation, false);
759       data.ForcePathSeparatorStyle(true);
760       if(oldConfig)
761          settingsFilePath = null;
762       result = GlobalSettings::Save();
763       if(result != success)
764          PrintLn("Error saving IDE settings");
765       else
766          oldConfig = false;
767       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
768          data.ManagePortablePaths(moduleLocation, true);
769
770       CloseAndMonitor();
771       FileGetSize(settingsFilePath, &settingsFileSize);
772
773       return result;
774    }
775 }
776
777 static Map<String, String> getCompilerConfigFilePathsByName(const char * path)
778 {
779    Map<String, String> map { };
780    FileListing fl { path, extensions = "econ" };
781    while(fl.Find())
782    {
783       if(fl.stats.attribs.isFile)
784       {
785          char name[MAX_FILENAME];
786          char * path = CopyString(fl.path);
787          MakeSlashPath(path);
788          GetLastDirectory(path, name);
789          StripExtension(name);
790          map[name] = path;
791       }
792    }
793    return map;
794 }
795
796 static Map<String, CompilerConfig> getCompilerConfigsByName(const char * path)
797 {
798    Map<String, CompilerConfig> map { };
799    FileListing fl { path, extensions = "econ" };
800    while(fl.Find())
801    {
802       if(fl.stats.attribs.isFile)
803       {
804          char name[MAX_FILENAME];
805          char * path = CopyString(fl.path);
806          MakeSlashPath(path);
807          GetLastDirectory(path, name);
808          StripExtension(name);
809          {
810             CompilerConfig ccfg = CompilerConfig::read(path);
811             if(ccfg)
812                map[name] = ccfg;
813          }
814          delete path;
815       }
816    }
817    return map;
818 }
819
820 static SettingsIOResult writeConfigFile(const char * path, Class dataType, void * data)
821 {
822    SettingsIOResult result = error;
823    SafeFile sf = SafeFile::open(path, write);
824    if(sf)
825    {
826       WriteECONObject(sf.file, dataType, data, 0);
827       sf.sync();
828       delete sf;
829       result = success;
830    }
831    else
832       PrintLn($"error: could not safely open file for writing configuration: ", path);
833    return result;
834 }
835
836 static SettingsIOResult readConfigFile(const char * path, Class dataType, void ** data)
837 {
838    SettingsIOResult result = error;
839    SafeFile sf;
840    if(!FileExists(path))
841       result = fileNotFound;
842    else if((sf = SafeFile::open(path, read)))
843    {
844       JSONResult jsonResult;
845       {
846          ECONParser parser { f = sf.file };
847          sf.file.Seek(0, start);
848          jsonResult = parser.GetObject(dataType, data);
849          if(jsonResult != success)
850             delete *data;
851          delete parser;
852       }
853       if(jsonResult == success)
854          result = success;
855       else
856       {
857          result = fileNotCompatibleWithDriver;
858          PrintLn($"error: could not parse configuration file: ", path);
859       }
860       delete sf;
861    }
862    return result;
863 }
864
865 class SafeFile
866 {
867    File file;
868    FileOpenMode mode;
869    char path[MAX_LOCATION];
870    char tmp[MAX_LOCATION];
871
872    SafeFile ::open(const char * path, FileOpenMode mode)
873    {
874       SafeFile result = null;
875       if(mode == write || mode == read)
876       {
877          SafeFile sf { mode = mode };
878          int c;
879          bool locked = false;
880          FileLock lockType = mode == write ? exclusive : shared;
881
882          strcpy(sf.path, path);
883          strcpy(sf.tmp, path);
884          strcat(sf.tmp, ".tmp");
885          if(mode == write && FileExists(sf.tmp).isFile)
886             DeleteFile(sf.tmp);
887
888          if(mode == write)
889          {
890             sf.file = FileOpen(sf.tmp, readWrite);
891             if(!sf.file)
892             {
893                sf.file = FileOpen(sf.tmp, writeRead);
894                if(sf.file)
895                {
896                   delete sf.file;
897                   sf.file = FileOpen(sf.tmp, readWrite);
898                }
899             }
900          }
901          else
902             sf.file = FileOpen(path, mode);
903          if(sf.file)
904          {
905             for(c = 0; c < 10 && !(locked = sf.file.Lock(lockType, 0, 0, false)); c++) Sleep(0.01);
906             if(locked)
907             {
908                sf.file.Truncate(0);
909                sf.file.Seek(0, start);
910                result = sf;
911             }
912             else if(mode == write)
913                PrintLn($"warning: SafeFile::open: unable to obtain exclusive lock on temporary file for writing: ", sf.tmp);
914             else
915                PrintLn($"warning: SafeFile::open: unable to obtain shared lock on file for reading: ", path);
916          }
917          else if(mode == write)
918             PrintLn($"warning: SafeFile::open: unable to open temporary file for writing: ", sf.tmp);
919          else
920             PrintLn($"warning: SafeFile::open: unable to open file for reading: ", path);
921
922          if(!result)
923             delete sf;
924       }
925       else
926          PrintLn($"warning: SafeFile::open: does not yet support FileOpenMode::", mode);
927       return result;
928    }
929
930    void sync()
931    {
932       if(file && mode == write)
933       {
934          int c;
935          File f = FileOpen(path, readWrite);
936          if(!f)
937          {
938             f = FileOpen(path, writeRead);
939             if(f)
940             {
941                delete f;
942                f = FileOpen(path, readWrite);
943             }
944          }
945          if(f)
946          {
947             bool locked = true;
948             for(c = 0; c < 10 && !(locked = f.Lock(exclusive, 0,0, false)); c++) Sleep(0.01);
949
950             if(locked)
951             {
952                f.Unlock(0,0, false);
953                delete f;
954                file.Unlock(0,0, false);
955                delete file;
956
957                for(c = 0; c < 10; c++)
958                {
959                   if(MoveFileEx(tmp, path, { true, true }))
960                      break;
961                   else
962                      Sleep(0.01);
963                }
964             }
965             else
966             {
967                delete f;
968                PrintLn($"warning: SafeFile::sync: failed to lock file for ", mode);
969             }
970          }
971       }
972    }
973
974
975    ~SafeFile()
976    {
977       if(file)
978       {
979          file.Unlock(0,0, false);
980          delete file;
981       }
982    }
983 }
984
985 class RecentFilesData
986 {
987 public:
988    RecentFiles recentFiles;
989
990    ~RecentFilesData()
991    {
992       if(recentFiles) recentFiles.Free();
993       delete recentFiles;
994    }
995 }
996
997 class RecentWorkspacesData
998 {
999 public:
1000    RecentWorkspaces recentWorkspaces;
1001
1002    ~RecentWorkspacesData()
1003    {
1004       if(recentWorkspaces) recentWorkspaces.Free();
1005       delete recentWorkspaces;
1006    }
1007 }
1008
1009 class IDESettings : GlobalSettingsData
1010 {
1011 public:
1012    property CompilerConfigs compilerConfigs
1013    {
1014       set { /*if(settingsContainer.oldConfig)*/ { if(compilerConfigs) compilerConfigs.Free(); delete compilerConfigs; if(value) compilerConfigs = value; } }
1015       get { return compilerConfigs; }
1016       isset { return false; }
1017    }
1018    property RecentFiles recentFiles
1019    {
1020       set { if(recentFiles) recentFiles.Free(); delete recentFiles; if(value) recentFiles = value; }
1021       get { return recentFiles; }
1022       isset { return false; }
1023    }
1024    property RecentWorkspaces recentProjects
1025    {
1026       set { if(recentProjects) recentProjects.Free(); delete recentProjects; if(value) recentProjects = value; }
1027       get { return recentProjects; }
1028       isset { return false; }
1029    }
1030    property const char * docDir
1031    {
1032       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
1033       get { return docDir ? docDir : ""; }
1034       isset { return docDir && docDir[0]; }
1035    }
1036    property const char * ideFileDialogLocation
1037    {
1038       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
1039       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
1040       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
1041    }
1042    property const char * ideProjectFileDialogLocation
1043    {
1044       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
1045       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
1046       isset { return ideProjectFileDialogLocation && ideProjectFileDialogLocation[0]; }
1047    }
1048    bool useFreeCaret;
1049    bool showLineNumbers;
1050    bool caretFollowsScrolling;
1051    char * displayDriver;
1052
1053    // TODO: Classify settings
1054    //EditorSettings editor { };
1055
1056    property const char * projectDefaultTargetDir
1057    {
1058       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
1059       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
1060       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
1061    }
1062    property const char * projectDefaultIntermediateObjDir
1063    {
1064       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
1065       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
1066       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
1067    }
1068
1069    property const char * compilerConfigsDir
1070    {
1071       set { delete compilerConfigsDir; if(value && value[0]) compilerConfigsDir = CopyString(value); }
1072       get { return compilerConfigsDir ? compilerConfigsDir : ""; }
1073       isset { return compilerConfigsDir && compilerConfigsDir[0]; }
1074    }
1075
1076    property const char * defaultCompiler
1077    {
1078       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
1079       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
1080       isset { return defaultCompiler && defaultCompiler[0]; }
1081    }
1082
1083    property const String language
1084    {
1085       set
1086       {
1087          delete language;
1088          language = CopyString(value);
1089       }
1090       get { return language; }
1091       isset { return language != null; }
1092    }
1093
1094    property const String codeEditorFont
1095    {
1096       set
1097       {
1098          delete codeEditorFont;
1099          codeEditorFont = CopyString(value);
1100       }
1101       get { return codeEditorFont; }
1102    }
1103
1104    float codeEditorFontSize;
1105    bool showFixedPitchFontsOnly;
1106
1107    property Array<IDEColorScheme> colorSchemes
1108    {
1109       set
1110       {
1111          if(colorSchemes && colorSchemes._refCount < 2)
1112             colorSchemes.Free();
1113          delete colorSchemes;
1114          colorSchemes = value;
1115          if(value)
1116             incref colorSchemes;
1117       }
1118       get { return colorSchemes; }
1119    }
1120
1121    property const String activeColorScheme
1122    {
1123       set
1124       {
1125          delete activeColorScheme;
1126          activeColorScheme = CopyString(value);
1127       }
1128       get { return activeColorScheme; }
1129    }
1130
1131 private:
1132    CompilerConfigs compilerConfigs { };
1133    char * docDir;
1134    char * ideFileDialogLocation;
1135    char * ideProjectFileDialogLocation;
1136    char * projectDefaultTargetDir;
1137    char * projectDefaultIntermediateObjDir;
1138    char * compilerConfigsDir;
1139    char * defaultCompiler;
1140    String language;
1141    RecentFiles recentFiles { };
1142    RecentWorkspaces recentProjects { };
1143
1144    Array<IDEColorScheme> colorSchemes;
1145
1146    String codeEditorFont;
1147
1148    String activeColorScheme;
1149
1150    showFixedPitchFontsOnly = true;
1151    codeEditorFontSize = 12;
1152    codeEditorFont = CopyString("Courier New");
1153
1154    ~IDESettings()
1155    {
1156       compilerConfigs.Free();
1157       delete compilerConfigs;
1158       if(recentProjects) { recentFiles.Free(); delete recentFiles; }
1159       if(recentProjects) { recentProjects.Free(); delete recentProjects; }
1160       delete docDir;
1161
1162       delete projectDefaultTargetDir;
1163       delete projectDefaultIntermediateObjDir;
1164       delete compilerConfigsDir;
1165       delete defaultCompiler;
1166       delete language;
1167
1168       delete ideFileDialogLocation;
1169       delete ideProjectFileDialogLocation;
1170       delete displayDriver;
1171
1172       delete codeEditorFont;
1173
1174       colorSchemes.Free();
1175       delete activeColorScheme;
1176    }
1177
1178    void ForcePathSeparatorStyle(bool unixStyle)
1179    {
1180       char from, to;
1181       if(unixStyle)
1182          from = '\\', to = '/';
1183       else
1184          from = '/', to = '\\';
1185       if(compilerConfigs && compilerConfigs.count)
1186       {
1187          int i;
1188          for(config : compilerConfigs)
1189          {
1190             if(config.includeDirs && config.includeDirs.count)
1191             {
1192                for(i = 0; i < config.includeDirs.count; i++)
1193                {
1194                   if(config.includeDirs[i] && config.includeDirs[i][0])
1195                      ChangeCh(config.includeDirs[i], from, to);
1196                }
1197             }
1198             if(config.libraryDirs && config.libraryDirs.count)
1199             {
1200                for(i = 0; i < config.libraryDirs.count; i++)
1201                {
1202                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
1203                      ChangeCh(config.libraryDirs[i], from, to);
1204                }
1205             }
1206             if(config.executableDirs && config.executableDirs.count)
1207             {
1208                for(i = 0; i < config.executableDirs.count; i++)
1209                {
1210                   if(config.executableDirs[i] && config.executableDirs[i][0])
1211                      ChangeCh(config.executableDirs[i], from, to);
1212                }
1213             }
1214          }
1215       }
1216       recentFiles.changeChar(from, to);
1217       recentProjects.changeChar(from, to);
1218       if(docDir && docDir[0])
1219          ChangeCh(docDir, from, to);
1220       if(ideFileDialogLocation && ideFileDialogLocation[0])
1221          ChangeCh(ideFileDialogLocation, from, to);
1222       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
1223          ChangeCh(ideProjectFileDialogLocation, from, to);
1224
1225       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
1226          ChangeCh(projectDefaultTargetDir, from, to);
1227       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
1228          ChangeCh(projectDefaultIntermediateObjDir, from, to);
1229
1230       if(compilerConfigsDir && compilerConfigsDir[0])
1231          ChangeCh(compilerConfigsDir, from, to);
1232    }
1233
1234    void ManagePortablePaths(char * location, bool makeAbsolute)
1235    {
1236       int c;
1237       if(compilerConfigs && compilerConfigs.count)
1238       {
1239          for(config : compilerConfigs)
1240          {
1241             DirTypes t;
1242             for(t = 0; t < DirTypes::enumSize; t++)
1243             {
1244                Array<String> dirs = null;
1245                if(t == executables) dirs = config.executableDirs;
1246                else if(t == includes) dirs = config.includeDirs;
1247                else if(t == libraries) dirs = config.libraryDirs;
1248                if(dirs && dirs.count)
1249                {
1250                   for(c = 0; c < dirs.count; c++)
1251                   {
1252                      if(dirs[c] && dirs[c][0])
1253                         dirs[c] = UpdatePortablePath(dirs[c], location, makeAbsolute);
1254                   }
1255                }
1256             }
1257          }
1258       }
1259       if(recentFiles && recentFiles.count)
1260       {
1261          for(c = 0; c < recentFiles.count; c++)
1262          {
1263             if(recentFiles[c] && recentFiles[c][0])
1264                recentFiles[c] = UpdatePortablePath(recentFiles[c], location, makeAbsolute);
1265          }
1266       }
1267       if(recentProjects && recentProjects.count)
1268       {
1269          for(c = 0; c < recentProjects.count; c++)
1270          {
1271             if(recentProjects[c] && recentProjects[c][0])
1272                recentProjects[c] = UpdatePortablePath(recentProjects[c], location, makeAbsolute);
1273          }
1274       }
1275       if(docDir && docDir[0])
1276          docDir = UpdatePortablePath(docDir, location, makeAbsolute);
1277       if(ideFileDialogLocation && ideFileDialogLocation[0])
1278          ideFileDialogLocation = UpdatePortablePath(ideFileDialogLocation, location, makeAbsolute);
1279       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
1280          ideProjectFileDialogLocation = UpdatePortablePath(ideProjectFileDialogLocation, location, makeAbsolute);
1281
1282       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
1283          projectDefaultTargetDir = UpdatePortablePath(projectDefaultTargetDir, location, makeAbsolute);
1284       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
1285          projectDefaultIntermediateObjDir = UpdatePortablePath(projectDefaultIntermediateObjDir, location, makeAbsolute);
1286
1287       if(compilerConfigsDir && compilerConfigsDir[0])
1288          compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
1289    }
1290
1291    char * UpdatePortablePath(char * path, const char * location, bool makeAbsolute)
1292    {
1293       char * output;
1294       if(makeAbsolute)
1295       {
1296          char p[MAX_LOCATION];
1297          strcpy(p, location);
1298          PathCatSlash(p, path);
1299          delete path;
1300          output = CopyString(p);
1301       }
1302       else
1303       {
1304          PathRelationship rel = eString_PathRelated(path, location, null);
1305          if(rel == subPath || rel == identical)
1306          {
1307             char p[MAX_LOCATION];
1308             MakePathRelative(path, location, p);
1309             if(!*p) strcpy(p, "./");
1310             else ChangeCh(p, '\\', '/');
1311             delete path;
1312             output = CopyString(p);
1313          }
1314          else
1315             output = path;
1316       }
1317       return output;
1318    }
1319 }
1320
1321 class RecentFiles : RecentPaths
1322 {
1323    void read(IDESettingsContainer settingsContainer)
1324    {
1325       char path[MAX_LOCATION];
1326       RecentFilesData d = null;
1327       Class _class = class(RecentFilesData);
1328       settingsContainer.getConfigFilePath(path, _class, null, null);
1329       readConfigFile(path, _class, &d);
1330       if(d && d.recentFiles && d.recentFiles.count)
1331       {
1332          Free();
1333          Copy((void *)d.recentFiles);
1334          d.recentFiles.RemoveAll();
1335          settingsContainer.recentFiles = this; // Merge IDEConfigHolder / IDESettingsContainer?
1336       }
1337       delete d;
1338       settingsContainer.recentFilesMonitor.fileName = path;
1339       settingsContainer.recentFilesMonitor.StartMonitoring();
1340       settingsContainer.onLoadRecentFiles();
1341    }
1342
1343    void write(IDESettingsContainer settingsContainer)
1344    {
1345       char path[MAX_LOCATION];
1346       RecentFilesData d { };
1347       Class _class = class(RecentFilesData);
1348       settingsContainer.getConfigFilePath(path, _class, null, null);
1349       d.recentFiles = this;
1350       writeConfigFile(path, _class, d);
1351       d.recentFiles = null;
1352       delete d;
1353    }
1354 }
1355
1356 class RecentWorkspaces : RecentPaths
1357 {
1358    void read(IDESettingsContainer settingsContainer)
1359    {
1360       char path[MAX_LOCATION];
1361       RecentWorkspacesData d = null;
1362       Class _class = class(RecentWorkspacesData);
1363       settingsContainer.getConfigFilePath(path, _class, null, null);
1364       readConfigFile(path, _class, &d);
1365       if(d && d.recentWorkspaces && d.recentWorkspaces.count)
1366       {
1367          Free();
1368          Copy((void *)d.recentWorkspaces);
1369          d.recentWorkspaces.RemoveAll();
1370          settingsContainer.recentProjects = this; // Merge IDEConfigHolder / IDESettingsContainer?
1371       }
1372       delete d;
1373       settingsContainer.recentProjectsMonitor.fileName = path;
1374       settingsContainer.recentProjectsMonitor.StartMonitoring();
1375       settingsContainer.onLoadRecentProjects();
1376    }
1377
1378    void write(IDESettingsContainer settingsContainer)
1379    {
1380       char path[MAX_LOCATION];
1381       RecentWorkspacesData d { };
1382       Class _class = class(RecentWorkspacesData);
1383       settingsContainer.getConfigFilePath(path, _class, null, null);
1384       d.recentWorkspaces = this;
1385       writeConfigFile(path, _class, d);
1386       d.recentWorkspaces = null;
1387       delete d;
1388    }
1389 }
1390
1391 class RecentPaths : Array<String>
1392 {
1393    IteratorPointer Add(T value)
1394    {
1395       int c;
1396       char * filePath = (char *)value;
1397       ChangeCh(filePath, '\\', '/');
1398       for(c = 0; c < count; c++)
1399       {
1400          if(this[c] && !fstrcmp(this[c], filePath))
1401          {
1402             Delete((void *)&this[c]);
1403             c--;
1404          }
1405       }
1406       return Array::Add((T)filePath);
1407    }
1408
1409    IteratorPointer addRecent(const String value)
1410    {
1411       int c;
1412       char * filePath = CopyString((char *)value);
1413       IteratorPointer ip;
1414       ChangeCh(filePath, '\\', '/');
1415       for(c = 0; c < count; c++)
1416       {
1417          if(this[c] && !fstrcmp(this[c], filePath))
1418          {
1419             Delete((void *)&this[c]);
1420             c--;
1421          }
1422       }
1423       while(count >= MaxRecent)
1424          Delete(GetLast());
1425       ip = Insert(null, filePath);
1426       return ip;
1427    }
1428
1429    void changeChar(char from, char to)
1430    {
1431       if(this && count)
1432       {
1433          int c;
1434          for(c = 0; c < count; c++)
1435          {
1436             if(this[c] && this[c][0])
1437                ChangeCh(this[c], from, to);
1438          }
1439       }
1440    }
1441 }
1442
1443 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
1444 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
1445 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
1446 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
1447 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
1448 // TODO: i18n with Array
1449 static Array<const String> compilerTypeLongNames
1450 { [
1451    $"GNU Compiler Collection (GCC) / GNU Make",
1452    $"Tiny C Compiler / GNU Make",
1453    $"Portable C Compiler / GNU Make",
1454    $"Microsoft Visual Studio 2005 (8.0) Compiler",
1455    $"Microsoft Visual Studio 2008 (9.0) Compiler",
1456    $"Microsoft Visual Studio 2010 (10.0) Compiler"
1457 ] };
1458 const CompilerType firstCompilerType = gcc;
1459 const CompilerType lastCompilerType = vs10;
1460 public enum CompilerType
1461 {
1462    gcc, tcc, pcc, vs8, vs9, vs10;
1463
1464    property bool isVC
1465    {
1466       get { return this == vs8 || this == vs9 || this == vs10; }
1467    }
1468
1469    property const char *
1470    {
1471       get { return OnGetString(null, null, null); }
1472       set
1473       {
1474          if(value)
1475          {
1476             CompilerType c;
1477             for(c = firstCompilerType; c <= lastCompilerType; c++)
1478                if(!strcmpi(value, compilerTypeNames[c]))
1479                   return c;
1480          }
1481          return gcc;
1482       }
1483    };
1484
1485    property const char * longName { get { return OnGetString(null, (void*)1, null); } };
1486    property const char * versionString { get { return OnGetString(null, (void*)2, null); } };
1487    property const char * yearString { get { return OnGetString(null, (void*)3, null); } };
1488    property const char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
1489    property const char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
1490
1491    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
1492    {
1493       if(this >= firstCompilerType && this <= lastCompilerType)
1494       {
1495          if(tempString)
1496             strcpy(tempString, compilerTypeNames[this]);
1497          if(fieldData == null)
1498             return compilerTypeNames[this];
1499          else if(fieldData == (void*)1)
1500             return compilerTypeLongNames[this];
1501          else if(fieldData == (void*)2)
1502             return compilerTypeVersionString[this];
1503          else if(fieldData == (void*)3)
1504             return compilerTypeYearString[this];
1505          else if(fieldData == (void*)4)
1506             return compilerTypeProjectFileExtension[this];
1507          else if(fieldData == (void*)5)
1508             return compilerTypeSolutionFileVersionString[this];
1509       }
1510       return null;
1511    }
1512 };
1513
1514 class CompilerConfig
1515 {
1516    class_no_expansion;
1517
1518    numJobs = 1;
1519 public:
1520    property const char * name
1521    {
1522       set { delete name; if(value) name = CopyString(value); }
1523       get { return name; }
1524    }
1525    bool readOnly;
1526    CompilerType type;
1527    Platform targetPlatform;
1528    int numJobs;
1529    property const char * makeCommand
1530    {
1531       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
1532       get { return makeCommand; }
1533       isset { return makeCommand && makeCommand[0]; }
1534    }
1535    property const char * ecpCommand
1536    {
1537       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
1538       get { return ecpCommand; }
1539       isset { return ecpCommand && ecpCommand[0]; }
1540    }
1541    property const char * eccCommand
1542    {
1543       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
1544       get { return eccCommand; }
1545       isset { return eccCommand && eccCommand[0]; }
1546    }
1547    property const char * ecsCommand
1548    {
1549       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
1550       get { return ecsCommand; }
1551       isset { return ecsCommand && ecsCommand[0]; }
1552    }
1553    property const char * earCommand
1554    {
1555       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
1556       get { return earCommand; }
1557       isset { return earCommand && earCommand[0]; }
1558    }
1559    property const char * cppCommand
1560    {
1561       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
1562       get { return cppCommand; }
1563       isset { return cppCommand && cppCommand[0]; }
1564    }
1565    property const char * ccCommand
1566    {
1567       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
1568       get { return ccCommand; }
1569       isset { return ccCommand && ccCommand[0]; }
1570    }
1571    property const char * cxxCommand
1572    {
1573       set { delete cxxCommand; if(value && value[0]) cxxCommand = CopyString(value); }
1574       get { return cxxCommand; }
1575       isset { return cxxCommand && cxxCommand[0]; }
1576    }
1577    property const char * arCommand
1578    {
1579       set { delete arCommand; if(value && value[0]) arCommand = CopyString(value); }
1580       get { return arCommand; }
1581       isset { return arCommand && arCommand[0]; }
1582    }
1583    property const char * ldCommand
1584    {
1585       set { delete ldCommand; if(value && value[0]) ldCommand = CopyString(value); }
1586       get { return ldCommand; }
1587       isset { return ldCommand && ldCommand[0]; }
1588    }
1589    property const char * objectFileExt
1590    {
1591       set { delete objectFileExt; if(value && value[0]) objectFileExt = CopyString(value); }
1592       get { return objectFileExt && objectFileExt[0] ? objectFileExt : objectDefaultFileExt ; }
1593       isset { return objectFileExt && objectFileExt[0] && strcmp(objectFileExt, objectDefaultFileExt); }
1594    }
1595    property const char * staticLibFileExt
1596    {
1597       set { delete staticLibFileExt; if(value && value[0]) staticLibFileExt = CopyString(value); }
1598       get { return staticLibFileExt; }
1599       isset { return staticLibFileExt && staticLibFileExt[0]; }
1600    }
1601    property const char * sharedLibFileExt
1602    {
1603       set { delete sharedLibFileExt; if(value && value[0]) sharedLibFileExt = CopyString(value); }
1604       get { return sharedLibFileExt; }
1605       isset { return sharedLibFileExt && sharedLibFileExt[0]; }
1606    }
1607    property const char * executableFileExt
1608    {
1609       set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
1610       get { return executableFileExt; }
1611       isset { return executableFileExt && executableFileExt[0]; }
1612    }
1613    property const char * executableLauncher
1614    {
1615       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
1616       get { return executableLauncher; }
1617       isset { return executableLauncher && executableLauncher[0]; }
1618    }
1619    // TODO: implement CompilerConfig::windresCommand
1620    bool ccacheEnabled;
1621    bool distccEnabled;
1622    // deprecated
1623    property bool supportsBitDepth { set { } get { return true; } isset { return false; } }
1624
1625    property const char * distccHosts
1626    {
1627       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
1628       get { return distccHosts; }
1629       isset { return distccHosts && distccHosts[0]; }
1630    }
1631    property const char * gnuToolchainPrefix
1632    {
1633       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
1634       get { return gnuToolchainPrefix; }
1635       isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
1636    }
1637    property const char * sysroot
1638    {
1639       set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
1640       get { return sysroot; }
1641       isset { return sysroot && sysroot[0]; }
1642    }
1643    bool resourcesDotEar;
1644    bool noStripTarget;
1645    property Array<String> includeDirs
1646    {
1647       set
1648       {
1649          includeDirs.Free();
1650          if(value)
1651          {
1652             delete includeDirs;
1653             includeDirs = value;
1654          }
1655       }
1656       get { return includeDirs; }
1657       isset { return includeDirs.count != 0; }
1658    }
1659    property Array<String> libraryDirs
1660    {
1661       set
1662       {
1663          libraryDirs.Free();
1664          if(value)
1665          {
1666             delete libraryDirs;
1667             libraryDirs = value;
1668          }
1669       }
1670       get { return libraryDirs; }
1671       isset { return libraryDirs.count != 0; }
1672    }
1673    property Array<String> executableDirs
1674    {
1675       set
1676       {
1677          executableDirs.Free();
1678          if(value)
1679          {
1680             delete executableDirs;
1681             executableDirs = value;
1682          }
1683       }
1684       get { return executableDirs; }
1685       isset { return executableDirs.count != 0; }
1686    }
1687    property Array<NamedString> environmentVars
1688    {
1689       set
1690       {
1691          environmentVars.Free();
1692          if(value)
1693          {
1694             delete environmentVars;
1695             environmentVars = value;
1696          }
1697       }
1698       get { return environmentVars; }
1699       isset { return environmentVars.count != 0; }
1700    }
1701    property Array<String> prepDirectives
1702    {
1703       set
1704       {
1705          prepDirectives.Free();
1706          if(value)
1707          {
1708             delete prepDirectives;
1709             prepDirectives = value;
1710          }
1711       }
1712       get { return prepDirectives; }
1713       isset { return prepDirectives.count != 0; }
1714    }
1715    property Array<String> excludeLibs
1716    {
1717       set
1718       {
1719          excludeLibs.Free();
1720          if(value)
1721          {
1722             delete excludeLibs;
1723             excludeLibs = value;
1724          }
1725       }
1726       get { return excludeLibs; }
1727       isset { return excludeLibs.count != 0; }
1728    }
1729    property Array<String> eCcompilerFlags
1730    {
1731       set
1732       {
1733          eCcompilerFlags.Free();
1734          if(value)
1735          {
1736             delete eCcompilerFlags;
1737             eCcompilerFlags = value;
1738          }
1739       }
1740       get { return eCcompilerFlags; }
1741       isset { return eCcompilerFlags.count != 0; }
1742    }
1743    property Array<String> compilerFlags
1744    {
1745       set
1746       {
1747          compilerFlags.Free();
1748          if(value)
1749          {
1750             delete compilerFlags;
1751             compilerFlags = value;
1752          }
1753       }
1754       get { return compilerFlags; }
1755       isset { return compilerFlags.count != 0; }
1756    }
1757    property Array<String> cxxFlags
1758    {
1759       set
1760       {
1761          cxxFlags.Free();
1762          if(value)
1763          {
1764             delete cxxFlags;
1765             cxxFlags = value;
1766          }
1767       }
1768       get { return cxxFlags; }
1769       isset { return cxxFlags.count != 0; }
1770    }
1771    property Array<String> linkerFlags
1772    {
1773       set
1774       {
1775          linkerFlags.Free();
1776          if(value)
1777          {
1778             delete linkerFlags;
1779             linkerFlags = value;
1780          }
1781       }
1782       get { return linkerFlags; }
1783       isset { return linkerFlags.count != 0; }
1784    }
1785    // json backward compatibility
1786    property const char * gccPrefix
1787    {
1788       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
1789       get { return gnuToolchainPrefix; }
1790       isset { return false; }
1791    }
1792    property const char * execPrefixCommand
1793    {
1794       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
1795       get { return executableLauncher; }
1796       isset { return false; }
1797    }
1798    property const char * outputFileExt
1799    {
1800       set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
1801       get { return executableFileExt; }
1802       isset { return false; }
1803    }
1804    // utility
1805    property bool hasDocumentOutput
1806    {
1807       get
1808       {
1809          bool result = executableFileExt && executableFileExt[0] &&
1810                (!strcmpi(executableFileExt, "htm") || !strcmpi(executableFileExt, "html"));
1811          return result;
1812       }
1813       isset { return false; }
1814    }
1815 private:
1816    Array<String> includeDirs { };
1817    Array<String> libraryDirs { };
1818    Array<String> executableDirs { };
1819    // TODO: Can JSON parse and serialize maps?
1820    //EnvironmentVariables { };
1821    Array<NamedString> environmentVars { };
1822    Array<String> prepDirectives { };
1823    Array<String> excludeLibs { };
1824    Array<String> eCcompilerFlags { };
1825    Array<String> compilerFlags { };
1826    Array<String> cxxFlags { };
1827    Array<String> linkerFlags { };
1828    char * name;
1829    char * makeCommand;
1830    char * ecpCommand;
1831    char * eccCommand;
1832    char * ecsCommand;
1833    char * earCommand;
1834    char * cppCommand;
1835    char * ccCommand;
1836    char * cxxCommand;
1837    char * ldCommand;
1838    char * arCommand;
1839    char * objectFileExt;
1840    char * staticLibFileExt;
1841    char * sharedLibFileExt;
1842    char * executableFileExt;
1843    char * executableLauncher;
1844    char * distccHosts;
1845    char * gnuToolchainPrefix;
1846    char * sysroot;
1847    /*union
1848    {
1849       struct { Array<String> includes, libraries, executables; };
1850       Array<String> dirs[DirTypes];
1851    }*/
1852
1853    ~CompilerConfig()
1854    {
1855       delete name;
1856       delete ecpCommand;
1857       delete eccCommand;
1858       delete ecsCommand;
1859       delete earCommand;
1860       delete cppCommand;
1861       delete ccCommand;
1862       delete cxxCommand;
1863       delete ldCommand;
1864       delete arCommand;
1865       delete objectFileExt;
1866       delete staticLibFileExt;
1867       delete sharedLibFileExt;
1868       delete executableFileExt;
1869       delete makeCommand;
1870       delete executableLauncher;
1871       delete distccHosts;
1872       delete gnuToolchainPrefix;
1873       delete sysroot;
1874       if(environmentVars) environmentVars.Free();
1875       if(includeDirs) { includeDirs.Free(); }
1876       if(libraryDirs) { libraryDirs.Free(); }
1877       if(executableDirs) { executableDirs.Free(); }
1878       if(prepDirectives) { prepDirectives.Free(); }
1879       if(excludeLibs) { excludeLibs.Free(); }
1880       if(compilerFlags) { compilerFlags.Free(); }
1881       if(cxxFlags) { cxxFlags.Free(); }
1882       if(eCcompilerFlags) { eCcompilerFlags.Free(); }
1883       if(linkerFlags) { linkerFlags.Free(); }
1884    }
1885
1886    int OnCompare(CompilerConfig b)
1887    {
1888       int result;
1889       if(
1890          !(result = type.OnCompare(b.type)) &&
1891          !(result = targetPlatform.OnCompare(b.targetPlatform)) &&
1892          !(result = numJobs.OnCompare(b.numJobs)) &&
1893          !(result = ccacheEnabled.OnCompare(b.ccacheEnabled)) &&
1894          !(result = distccEnabled.OnCompare(b.distccEnabled)) &&
1895          !(result = resourcesDotEar.OnCompare(b.resourcesDotEar)) &&
1896          !(result = noStripTarget.OnCompare(b.noStripTarget))
1897          );
1898
1899       if(!result &&
1900          !(result = name.OnCompare(b.name)) &&
1901          !(result = ecpCommand.OnCompare(b.ecpCommand)) &&
1902          !(result = eccCommand.OnCompare(b.eccCommand)) &&
1903          !(result = ecsCommand.OnCompare(b.ecsCommand)) &&
1904          !(result = earCommand.OnCompare(b.earCommand)) &&
1905          !(result = cppCommand.OnCompare(b.cppCommand)) &&
1906          !(result = ccCommand.OnCompare(b.ccCommand)) &&
1907          !(result = cxxCommand.OnCompare(b.cxxCommand)) &&
1908          !(result = ldCommand.OnCompare(b.ldCommand)) &&
1909          !(result = arCommand.OnCompare(b.arCommand)) &&
1910          !(result = objectFileExt.OnCompare(b.objectFileExt)) &&
1911          !(result = outputFileExt.OnCompare(b.outputFileExt)) &&
1912          !(result = makeCommand.OnCompare(b.makeCommand)) &&
1913          !(result = executableLauncher.OnCompare(b.executableLauncher)) &&
1914          !(result = distccHosts.OnCompare(b.distccHosts)) &&
1915          !(result = gnuToolchainPrefix.OnCompare(b.gnuToolchainPrefix)) &&
1916          !(result = sysroot.OnCompare(b.sysroot)));
1917
1918       if(!result &&
1919          !(result = includeDirs.OnCompare(b.includeDirs)) &&
1920          !(result = libraryDirs.OnCompare(b.libraryDirs)) &&
1921          !(result = executableDirs.OnCompare(b.executableDirs)) &&
1922          !(result = environmentVars.OnCompare(b.environmentVars)) &&
1923          !(result = prepDirectives.OnCompare(b.prepDirectives)) &&
1924          !(result = excludeLibs.OnCompare(b.excludeLibs)) &&
1925          !(result = cxxFlags.OnCompare(b.cxxFlags)) &&
1926          !(result = eCcompilerFlags.OnCompare(b.eCcompilerFlags)) &&
1927          !(result = compilerFlags.OnCompare(b.compilerFlags)) &&
1928          !(result = linkerFlags.OnCompare(b.linkerFlags)));
1929       return result;
1930    }
1931
1932 public:
1933    CompilerConfig Copy()
1934    {
1935       CompilerConfig copy
1936       {
1937          name,
1938          readOnly,
1939          type,
1940          targetPlatform,
1941          numJobs,
1942          makeCommand,
1943          ecpCommand,
1944          eccCommand,
1945          ecsCommand,
1946          earCommand,
1947          cppCommand,
1948          ccCommand,
1949          cxxCommand,
1950          arCommand,
1951          ldCommand,
1952          objectFileExt,
1953          staticLibFileExt,
1954          sharedLibFileExt,
1955          executableFileExt,
1956          executableLauncher,
1957          ccacheEnabled,
1958          distccEnabled,
1959          false,
1960          distccHosts,
1961          gnuToolchainPrefix,
1962          sysroot,
1963          resourcesDotEar,
1964          noStripTarget
1965       };
1966       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
1967       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
1968       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
1969       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
1970       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
1971       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
1972       for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
1973       for(s : cxxFlags) copy.cxxFlags.Add(CopyString(s));
1974       for(s : eCcompilerFlags) copy.eCcompilerFlags.Add(CopyString(s));
1975       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
1976
1977       incref copy;
1978       return copy;
1979    }
1980
1981    CompilerConfig ::read(const char * path)
1982    {
1983       CompilerConfig d = null;
1984       readConfigFile(path, class(CompilerConfig), &d);
1985       return d;
1986    }
1987
1988    void write(IDESettingsContainer settingsContainer)
1989    {
1990       char dir[MAX_LOCATION];
1991       char path[MAX_LOCATION];
1992       const char * settingsFilePath = settingsContainer.settingsFilePath;
1993       settingsContainer.getConfigFilePath(path, _class, dir, name);
1994       if(FileExists(settingsFilePath) && !FileExists(dir))
1995       {
1996          MakeDir(dir);
1997          if(!FileExists(dir))
1998             PrintLn($"Error creating compiler configs directory at ", dir, " location.");
1999       }
2000       writeConfigFile(path, _class, this);
2001    }
2002 }
2003
2004 class CompilerConfigs : List<CompilerConfig>
2005 {
2006    CompilerConfig GetCompilerConfig(const String compilerName)
2007    {
2008       const char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
2009       CompilerConfig compilerConfig = null;
2010       for(compiler : this)
2011       {
2012          if(!strcmp(compiler.name, name))
2013          {
2014             compilerConfig = compiler;
2015             break;
2016          }
2017       }
2018       if(!compilerConfig && count)
2019          compilerConfig = this[0];
2020       if(compilerConfig)
2021       {
2022          incref compilerConfig;
2023          if(compilerConfig._refCount == 1)
2024             incref compilerConfig;
2025       }
2026       return compilerConfig;
2027    }
2028
2029    void ensureDefaults()
2030    {
2031       // Ensure we have a default compiler
2032       CompilerConfig defaultCompiler = GetCompilerConfig(defaultCompilerName);
2033       if(!defaultCompiler)
2034       {
2035          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
2036          Insert(null, defaultCompiler);
2037          defaultCompiler = null;
2038       }
2039       delete defaultCompiler;
2040
2041       for(ccfg : this)
2042       {
2043          if(!ccfg.ecpCommand || !ccfg.ecpCommand[0])
2044             ccfg.ecpCommand = ecpDefaultCommand;
2045          if(!ccfg.eccCommand || !ccfg.eccCommand[0])
2046             ccfg.eccCommand = eccDefaultCommand;
2047          if(!ccfg.ecsCommand || !ccfg.ecsCommand[0])
2048             ccfg.ecsCommand = ecsDefaultCommand;
2049          if(!ccfg.earCommand || !ccfg.earCommand[0])
2050             ccfg.earCommand = earDefaultCommand;
2051          if(!ccfg.cppCommand || !ccfg.cppCommand[0])
2052             ccfg.cppCommand = cppDefaultCommand;
2053          if(!ccfg.ccCommand || !ccfg.ccCommand[0])
2054             ccfg.ccCommand = ccDefaultCommand;
2055          if(!ccfg.cxxCommand || !ccfg.cxxCommand[0])
2056             ccfg.cxxCommand = cxxDefaultCommand;
2057          /*if(!ccfg.ldCommand || !ccfg.ldCommand[0])
2058             ccfg.ldCommand = ldDefaultCommand;*/
2059          if(!ccfg.arCommand || !ccfg.arCommand[0])
2060             ccfg.arCommand = arDefaultCommand;
2061          if(!ccfg.objectFileExt || !ccfg.objectFileExt[0])
2062             ccfg.objectFileExt = objectDefaultFileExt;
2063          /*if(!ccfg.staticLibFileExt || !ccfg.staticLibFileExt[0])
2064             ccfg.staticLibFileExt = staticLibDefaultFileExt;*/
2065          /*if(!ccfg.sharedLibFileExt || !ccfg.sharedLibFileExt[0])
2066             ccfg.sharedLibFileExt = sharedLibDefaultFileExt;*/
2067          /*if(!ccfg.executableFileExt || !ccfg.executableFileExt[0])
2068             ccfg.executableFileExt = outputDefaultFileExt;*/
2069          if(!ccfg._refCount) incref ccfg;
2070       }
2071    }
2072
2073    AVLTree<String> getWriteRequiredList(CompilerConfigs oldConfigs)
2074    {
2075       AVLTree<String> list { };
2076       for(ccfg : this)
2077       {
2078          bool found = false;
2079          for(occfg : oldConfigs; !strcmp(ccfg.name, occfg.name))
2080          {
2081             found = true;
2082             if(ccfg.OnCompare(occfg))
2083                list.Add(CopyString(ccfg.name));
2084             break;
2085          }
2086          if(!found)
2087             list.Add(CopyString(ccfg.name));
2088       }
2089       return list;
2090    }
2091
2092    bool read(IDESettingsContainer settingsContainer)
2093    {
2094       if(settingsContainer.settingsFilePath)
2095       {
2096          char dir[MAX_LOCATION];
2097          char path[MAX_LOCATION];
2098          Class _class = class(CompilerConfig);
2099          settingsContainer.getConfigFilePath(path, _class, dir, null);
2100          if(dir[0])
2101          {
2102             AVLTree<const String> addedConfigs { };
2103             Map<String, CompilerConfig> compilerConfigsByName = getCompilerConfigsByName(dir);
2104             MapIterator<const String, CompilerConfig> it { map = compilerConfigsByName };
2105             Free();
2106             settingsContainer.compilerConfigs = this; // Merge IDEConfigHolder / IDESettingsContainer?
2107             if(it.Index("Default", false))
2108             {
2109                CompilerConfig ccfg = it.data;
2110                Add(ccfg.Copy());
2111                addedConfigs.Add(ccfg.name);
2112             }
2113             for(ccfg : compilerConfigsByName)
2114             {
2115                if(!addedConfigs.Find(ccfg.name))
2116                {
2117                   Add(ccfg.Copy());
2118                   addedConfigs.Add(ccfg.name);
2119                }
2120             }
2121             delete addedConfigs;
2122             ensureDefaults();
2123             compilerConfigsByName.Free();
2124             delete compilerConfigsByName;
2125             settingsContainer.onLoadCompilerConfigs();
2126             return true;
2127          }
2128       }
2129       return false;
2130    }
2131
2132    void write(IDESettingsContainer settingsContainer, AVLTree<String> cfgsToWrite)
2133    {
2134       char dir[MAX_LOCATION];
2135       char path[MAX_LOCATION];
2136       Map<String, String> paths;
2137       settingsContainer.getConfigFilePath(path, class(CompilerConfig), dir, null);
2138       paths = getCompilerConfigFilePathsByName(dir);
2139       {
2140          MapIterator<String, String> it { map = paths };
2141          for(c : this)
2142          {
2143             CompilerConfig ccfg = c;
2144             if(!cfgsToWrite || cfgsToWrite.Find(ccfg.name))
2145                ccfg.write(settingsContainer);
2146             if(it.Index(ccfg.name, false))
2147             {
2148                delete it.data;
2149                it.Remove();
2150             }
2151          }
2152       }
2153       for(p : paths)
2154       {
2155          const char * path = p;
2156          DeleteFile(path);
2157       }
2158       paths.Free();
2159       delete paths;
2160    }
2161 }
2162
2163 #if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
2164 struct LanguageOption
2165 {
2166    const String name;
2167    const String bitmap;
2168    const String code;
2169    BitmapResource res;
2170
2171    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
2172    {
2173       return name;
2174    }
2175
2176    void OnDisplay(Surface surface, int x, int y, int width, void * data, Alignment alignment, DataDisplayFlags flags)
2177    {
2178       Bitmap icon = res ? res.bitmap : null;
2179       int w = 8 + 16;
2180       if(icon)
2181          surface.Blit(icon, x + (16 - icon.width) / 2,y+2,0,0, icon.width, icon.height);
2182       class::OnDisplay(surface, x + w, y, width - w, null, alignment, flags);
2183    }
2184 };
2185
2186 Array<LanguageOption> languages
2187 { [
2188    { "English",            ":countryCode/gb.png", "" },
2189    { "汉语",                ":countryCode/cn.png", "zh_CN" },
2190    { "Español",            ":countryCode/es.png", "es" },
2191    { "Português (Brazil)", ":countryCode/br.png", "pt_BR" },
2192    { "Русский (43%)",      ":countryCode/ru.png", "ru" },
2193    { "Nederlandse (13%)",  ":countryCode/nl.png", "nl" },
2194    { "Tiếng Việt (12%)",   ":countryCode/vn.png", "vi" },
2195    { "मराठी (10%)",          ":countryCode/in.png", "mr" },
2196    { "Hebrew (8%)",        ":countryCode/il.png", "he" },
2197    { "Magyar (8%)",        ":countryCode/hu.png", "hu" }
2198 ] };
2199
2200 const String GetLanguageString()
2201 {
2202    char * dot, * colon;
2203    static char lang[256];
2204    const String language = getenv("ECERE_LANGUAGE");
2205    if(!language) language = getenv("LANGUAGE");
2206    if(!language) language = getenv("LC_ALL");
2207    if(!language) language = getenv("LC_MESSAGES");
2208    if(!language) language = getenv("LANG");
2209    if(!language) language = "";
2210    if(language && (colon = strchr(language, ':')))
2211    {
2212       if(lang != language)
2213          strncpy(lang, language, sizeof(lang));
2214       lang[sizeof(lang)-1] = 0;
2215       lang[colon - language] = 0;
2216       language = lang;
2217    }
2218    if(language && (dot = strchr(language, '.')))
2219    {
2220       if(lang != language)
2221          strncpy(lang, language, sizeof(lang));
2222       lang[sizeof(lang)-1] = 0;
2223       lang[dot - language] = 0;
2224       language = lang;
2225    }
2226    return language;
2227 }
2228
2229 void setEcereLanguageInWinRegEnvironment(const char * languageCode)
2230 {
2231 #ifdef __WIN32__
2232    HKEY key = null;
2233    uint16 wLanguage[256];
2234    DWORD status;
2235    wLanguage[0] = 0;
2236
2237    RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
2238    if(key)
2239    {
2240       UTF8toUTF16Buffer(languageCode, wLanguage, sizeof(wLanguage) / sizeof(uint16));
2241       RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
2242       RegCloseKey(key);
2243    }
2244 #endif
2245 }
2246
2247 bool LanguageRestart(const char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
2248 {
2249    bool restart = true;
2250    String command = null;
2251    int arg0Len = (int)strlen(app.argv[0]);
2252    int len = arg0Len;
2253    int j;
2254    char ch;
2255
2256    if(ide)
2257    {
2258       Window w;
2259
2260       if(projectView)
2261       {
2262          Window w;
2263          for(w = ide.firstChild; w; w = w.next)
2264          {
2265             if(w.isActiveClient && w.isDocument)
2266             {
2267                if(!w.CloseConfirmation(true))
2268                {
2269                   restart = false;
2270                   break;
2271                }
2272             }
2273          }
2274          if(restart)
2275          {
2276             if(!projectView.CloseConfirmation(true))
2277                restart = false;
2278             if(projectView.fileName)
2279             {
2280                const char * name = projectView.fileName;
2281                if(name)
2282                {
2283                   for(j = 0; (ch = name[j]); j++)
2284                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
2285                }
2286             }
2287
2288             command = new char[len + 1];
2289
2290             strcpy(command, app.argv[0]);
2291             len = arg0Len;
2292             if(projectView.fileName)
2293             {
2294                strcat(command, " ");
2295                len++;
2296                ReplaceSpaces(command + len, projectView.fileName);
2297             }
2298          }
2299          if(restart)
2300          {
2301             for(w = ide.firstChild; w; w = w.next)
2302                if(w.isActiveClient && w.isDocument)
2303                   w.modifiedDocument = false;
2304             projectView.modifiedDocument = false;
2305          }
2306       }
2307       else
2308       {
2309          for(w = ide.firstChild; w; w = w.next)
2310          {
2311             if(w.isActiveClient && w.isDocument)
2312             {
2313                if(!w.CloseConfirmation(true))
2314                {
2315                   restart = false;
2316                   break;
2317                }
2318                if(w.fileName)
2319                {
2320                   const char * name = w.fileName;
2321                   len++;
2322                   for(j = 0; (ch = name[j]); j++)
2323                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
2324                }
2325             }
2326          }
2327
2328          if(restart)
2329          {
2330             command = new char[len + 1];
2331             strcpy(command, app.argv[0]);
2332             len = arg0Len;
2333
2334             for(w = ide.firstChild; w; w = w.next)
2335             {
2336                if(w.isActiveClient && w.isDocument)
2337                {
2338                   const char * name = w.fileName;
2339                   if(name)
2340                   {
2341                      strcat(command, " ");
2342                      len++;
2343                      ReplaceSpaces(command + len, name);
2344                      len = (int)strlen(command);
2345                   }
2346                }
2347             }
2348          }
2349          if(restart)
2350          {
2351             for(w = ide.firstChild; w; w = w.next)
2352                if(w.isActiveClient && w.isDocument)
2353                   w.modifiedDocument = false;
2354          }
2355       }
2356       if(restart)
2357       {
2358          settings.language = code;
2359          settingsContainer.Save();
2360
2361          setEcereLanguageInWinRegEnvironment(code);
2362
2363          if(eClass_IsDerived(app._class, class(GuiApplication)))
2364          {
2365             GuiApplication guiApp = (GuiApplication)app;
2366             guiApp.desktop.Destroy(0);
2367          }
2368       }
2369    }
2370    else
2371    {
2372       int i;
2373       for(i = 1; i < app.argc; i++)
2374       {
2375          const char * arg = app.argv[i];
2376          len++;
2377          for(j = 0; (ch = arg[j]); j++)
2378             len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
2379       }
2380
2381       command = new char[len + 1];
2382       strcpy(command, app.argv[0]);
2383       len = arg0Len;
2384       for(i = 1; i < app.argc; i++)
2385       {
2386          strcat(command, " ");
2387          len++;
2388          ReplaceSpaces(command + len, app.argv[i]);
2389          len = (int)strlen(command);
2390       }
2391    }
2392
2393    if(restart)
2394    {
2395       SetEnvironment("ECERE_LANGUAGE", code);
2396       if(wait)
2397          ExecuteWait(command);
2398       else
2399          Execute(command);
2400    }
2401    delete command;
2402    return restart;
2403 }
2404 #endif