ide/CodeEditor: Customizable color scheme support
[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       if(data.activeColorScheme)
718       {
719          colorScheme = null;
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          data.compilerConfigs.ensureDefaults();
736          data.compilerConfigs.write(this, null);
737          data.compilerConfigs.Free();
738
739          data.recentFiles.write(this);
740          data.recentFiles.Free();
741
742          data.recentProjects.write(this);
743          data.recentProjects.Free();
744
745          Save();
746       }
747       return result;
748    }
749
750    SettingsIOResult Save()
751    {
752       SettingsIOResult result;
753       IDESettings data;
754       useNewConfigurationFiles = true;
755       data = (IDESettings)this.data;
756       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
757          data.ManagePortablePaths(moduleLocation, false);
758       data.ForcePathSeparatorStyle(true);
759       if(oldConfig)
760          settingsFilePath = null;
761       result = GlobalSettings::Save();
762       if(result != success)
763          PrintLn("Error saving IDE settings");
764       else
765          oldConfig = false;
766       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
767          data.ManagePortablePaths(moduleLocation, true);
768
769       CloseAndMonitor();
770       FileGetSize(settingsFilePath, &settingsFileSize);
771
772       return result;
773    }
774 }
775
776 static Map<String, String> getCompilerConfigFilePathsByName(const char * path)
777 {
778    Map<String, String> map { };
779    FileListing fl { path, extensions = "econ" };
780    while(fl.Find())
781    {
782       if(fl.stats.attribs.isFile)
783       {
784          char name[MAX_FILENAME];
785          char * path = CopyString(fl.path);
786          MakeSlashPath(path);
787          GetLastDirectory(path, name);
788          StripExtension(name);
789          map[name] = path;
790       }
791    }
792    return map;
793 }
794
795 static Map<String, CompilerConfig> getCompilerConfigsByName(const char * path)
796 {
797    Map<String, CompilerConfig> map { };
798    FileListing fl { path, extensions = "econ" };
799    while(fl.Find())
800    {
801       if(fl.stats.attribs.isFile)
802       {
803          char name[MAX_FILENAME];
804          char * path = CopyString(fl.path);
805          MakeSlashPath(path);
806          GetLastDirectory(path, name);
807          StripExtension(name);
808          {
809             CompilerConfig ccfg = CompilerConfig::read(path);
810             if(ccfg)
811                map[name] = ccfg;
812          }
813          delete path;
814       }
815    }
816    return map;
817 }
818
819 static SettingsIOResult writeConfigFile(const char * path, Class dataType, void * data)
820 {
821    SettingsIOResult result = error;
822    SafeFile sf = SafeFile::open(path, write);
823    if(sf)
824    {
825       WriteECONObject(sf.file, dataType, data, 0);
826       sf.sync();
827       delete sf;
828       result = success;
829    }
830    else
831       PrintLn($"error: could not safely open file for writing configuration: ", path);
832    return result;
833 }
834
835 static SettingsIOResult readConfigFile(const char * path, Class dataType, void ** data)
836 {
837    SettingsIOResult result = error;
838    SafeFile sf;
839    if(!FileExists(path))
840       result = fileNotFound;
841    else if((sf = SafeFile::open(path, read)))
842    {
843       JSONResult jsonResult;
844       {
845          ECONParser parser { f = sf.file };
846          sf.file.Seek(0, start);
847          jsonResult = parser.GetObject(dataType, data);
848          if(jsonResult != success)
849             delete *data;
850          delete parser;
851       }
852       if(jsonResult == success)
853          result = success;
854       else
855       {
856          result = fileNotCompatibleWithDriver;
857          PrintLn($"error: could not parse configuration file: ", path);
858       }
859       delete sf;
860    }
861    return result;
862 }
863
864 class SafeFile
865 {
866    File file;
867    FileOpenMode mode;
868    char path[MAX_LOCATION];
869    char tmp[MAX_LOCATION];
870
871    SafeFile ::open(const char * path, FileOpenMode mode)
872    {
873       SafeFile result = null;
874       if(mode == write || mode == read)
875       {
876          SafeFile sf { mode = mode };
877          int c;
878          bool locked = false;
879          FileLock lockType = mode == write ? exclusive : shared;
880
881          strcpy(sf.path, path);
882          strcpy(sf.tmp, path);
883          strcat(sf.tmp, ".tmp");
884          if(mode == write && FileExists(sf.tmp).isFile)
885             DeleteFile(sf.tmp);
886
887          if(mode == write)
888          {
889             sf.file = FileOpen(sf.tmp, readWrite);
890             if(!sf.file)
891             {
892                sf.file = FileOpen(sf.tmp, writeRead);
893                if(sf.file)
894                {
895                   delete sf.file;
896                   sf.file = FileOpen(sf.tmp, readWrite);
897                }
898             }
899          }
900          else
901             sf.file = FileOpen(path, mode);
902          if(sf.file)
903          {
904             for(c = 0; c < 10 && !(locked = sf.file.Lock(lockType, 0, 0, false)); c++) Sleep(0.01);
905             if(locked)
906             {
907                sf.file.Truncate(0);
908                sf.file.Seek(0, start);
909                result = sf;
910             }
911             else if(mode == write)
912                PrintLn($"warning: SafeFile::open: unable to obtain exclusive lock on temporary file for writing: ", sf.tmp);
913             else
914                PrintLn($"warning: SafeFile::open: unable to obtain shared lock on file for reading: ", path);
915          }
916          else if(mode == write)
917             PrintLn($"warning: SafeFile::open: unable to open temporary file for writing: ", sf.tmp);
918          else
919             PrintLn($"warning: SafeFile::open: unable to open file for reading: ", path);
920
921          if(!result)
922             delete sf;
923       }
924       else
925          PrintLn($"warning: SafeFile::open: does not yet support FileOpenMode::", mode);
926       return result;
927    }
928
929    void sync()
930    {
931       if(file && mode == write)
932       {
933          int c;
934          File f = FileOpen(path, readWrite);
935          if(!f)
936          {
937             f = FileOpen(path, writeRead);
938             if(f)
939             {
940                delete f;
941                f = FileOpen(path, readWrite);
942             }
943          }
944          if(f)
945          {
946             bool locked = true;
947             for(c = 0; c < 10 && !(locked = f.Lock(exclusive, 0,0, false)); c++) Sleep(0.01);
948
949             if(locked)
950             {
951                f.Unlock(0,0, false);
952                delete f;
953                file.Unlock(0,0, false);
954                delete file;
955
956                for(c = 0; c < 10; c++)
957                {
958                   if(MoveFileEx(tmp, path, { true, true }))
959                      break;
960                   else
961                      Sleep(0.01);
962                }
963             }
964             else
965             {
966                delete f;
967                PrintLn($"warning: SafeFile::sync: failed to lock file for ", mode);
968             }
969          }
970       }
971    }
972
973
974    ~SafeFile()
975    {
976       if(file)
977       {
978          file.Unlock(0,0, false);
979          delete file;
980       }
981    }
982 }
983
984 class RecentFilesData
985 {
986 public:
987    RecentFiles recentFiles;
988 }
989
990 class RecentWorkspacesData
991 {
992 public:
993    RecentWorkspaces recentWorkspaces;
994 }
995
996 class IDESettings : GlobalSettingsData
997 {
998 public:
999    property CompilerConfigs compilerConfigs
1000    {
1001       set { /*if(settingsContainer.oldConfig)*/ { if(compilerConfigs) compilerConfigs.Free(); delete compilerConfigs; if(value) compilerConfigs = value; } }
1002       get { return compilerConfigs; }
1003       isset { return false; }
1004    }
1005    property RecentFiles recentFiles
1006    {
1007       set { if(recentFiles) recentFiles.Free(); delete recentFiles; if(value) recentFiles = value; }
1008       get { return recentFiles; }
1009       isset { return false; }
1010    }
1011    property RecentWorkspaces recentProjects
1012    {
1013       set { if(recentProjects) recentProjects.Free(); delete recentProjects; if(value) recentProjects = value; }
1014       get { return recentProjects; }
1015       isset { return false; }
1016    }
1017    property const char * docDir
1018    {
1019       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
1020       get { return docDir ? docDir : ""; }
1021       isset { return docDir && docDir[0]; }
1022    }
1023    property const char * ideFileDialogLocation
1024    {
1025       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
1026       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
1027       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
1028    }
1029    property const char * ideProjectFileDialogLocation
1030    {
1031       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
1032       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
1033       isset { return ideProjectFileDialogLocation && ideProjectFileDialogLocation[0]; }
1034    }
1035    bool useFreeCaret;
1036    bool showLineNumbers;
1037    bool caretFollowsScrolling;
1038    char * displayDriver;
1039
1040    // TODO: Classify settings
1041    //EditorSettings editor { };
1042
1043    property const char * projectDefaultTargetDir
1044    {
1045       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
1046       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
1047       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
1048    }
1049    property const char * projectDefaultIntermediateObjDir
1050    {
1051       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
1052       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
1053       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
1054    }
1055
1056    property const char * compilerConfigsDir
1057    {
1058       set { delete compilerConfigsDir; if(value && value[0]) compilerConfigsDir = CopyString(value); }
1059       get { return compilerConfigsDir ? compilerConfigsDir : ""; }
1060       isset { return compilerConfigsDir && compilerConfigsDir[0]; }
1061    }
1062
1063    property const char * defaultCompiler
1064    {
1065       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
1066       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
1067       isset { return defaultCompiler && defaultCompiler[0]; }
1068    }
1069
1070    property const String language
1071    {
1072       set
1073       {
1074          delete language;
1075          language = CopyString(value);
1076       }
1077       get { return language; }
1078       isset { return language != null; }
1079    }
1080
1081    property const String codeEditorFont
1082    {
1083       set
1084       {
1085          delete codeEditorFont;
1086          codeEditorFont = CopyString(value);
1087       }
1088       get { return codeEditorFont; }
1089    }
1090
1091    float codeEditorFontSize;
1092    bool showFixedPitchFontsOnly;
1093
1094    property Array<IDEColorScheme> colorSchemes
1095    {
1096       set
1097       {
1098          if(colorSchemes && colorSchemes._refCount < 2)
1099             colorSchemes.Free();
1100          delete colorSchemes;
1101          colorSchemes = value;
1102          incref colorSchemes;
1103       }
1104       get { return colorSchemes; }
1105    }
1106
1107    property const String activeColorScheme
1108    {
1109       set
1110       {
1111          delete activeColorScheme;
1112          activeColorScheme = CopyString(value);
1113       }
1114       get { return activeColorScheme; }
1115    }
1116
1117 private:
1118    CompilerConfigs compilerConfigs { };
1119    char * docDir;
1120    char * ideFileDialogLocation;
1121    char * ideProjectFileDialogLocation;
1122    char * projectDefaultTargetDir;
1123    char * projectDefaultIntermediateObjDir;
1124    char * compilerConfigsDir;
1125    char * defaultCompiler;
1126    String language;
1127    RecentFiles recentFiles { };
1128    RecentWorkspaces recentProjects { };
1129
1130    Array<IDEColorScheme> colorSchemes;
1131
1132    String codeEditorFont;
1133
1134    String activeColorScheme;
1135
1136    showFixedPitchFontsOnly = true;
1137    codeEditorFontSize = 12;
1138    codeEditorFont = CopyString("Courier New");
1139
1140    ~IDESettings()
1141    {
1142       compilerConfigs.Free();
1143       delete compilerConfigs;
1144       if(recentProjects) { recentFiles.Free(); delete recentFiles; }
1145       if(recentProjects) { recentProjects.Free(); delete recentProjects; }
1146       delete docDir;
1147
1148       delete projectDefaultTargetDir;
1149       delete projectDefaultIntermediateObjDir;
1150       delete compilerConfigsDir;
1151       delete defaultCompiler;
1152       delete language;
1153
1154       delete ideFileDialogLocation;
1155       delete ideProjectFileDialogLocation;
1156       delete displayDriver;
1157
1158       delete codeEditorFont;
1159
1160       colorSchemes.Free();
1161       delete activeColorScheme;
1162    }
1163
1164    void ForcePathSeparatorStyle(bool unixStyle)
1165    {
1166       char from, to;
1167       if(unixStyle)
1168          from = '\\', to = '/';
1169       else
1170          from = '/', to = '\\';
1171       if(compilerConfigs && compilerConfigs.count)
1172       {
1173          int i;
1174          for(config : compilerConfigs)
1175          {
1176             if(config.includeDirs && config.includeDirs.count)
1177             {
1178                for(i = 0; i < config.includeDirs.count; i++)
1179                {
1180                   if(config.includeDirs[i] && config.includeDirs[i][0])
1181                      ChangeCh(config.includeDirs[i], from, to);
1182                }
1183             }
1184             if(config.libraryDirs && config.libraryDirs.count)
1185             {
1186                for(i = 0; i < config.libraryDirs.count; i++)
1187                {
1188                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
1189                      ChangeCh(config.libraryDirs[i], from, to);
1190                }
1191             }
1192             if(config.executableDirs && config.executableDirs.count)
1193             {
1194                for(i = 0; i < config.executableDirs.count; i++)
1195                {
1196                   if(config.executableDirs[i] && config.executableDirs[i][0])
1197                      ChangeCh(config.executableDirs[i], from, to);
1198                }
1199             }
1200          }
1201       }
1202       recentFiles.changeChar(from, to);
1203       recentProjects.changeChar(from, to);
1204       if(docDir && docDir[0])
1205          ChangeCh(docDir, from, to);
1206       if(ideFileDialogLocation && ideFileDialogLocation[0])
1207          ChangeCh(ideFileDialogLocation, from, to);
1208       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
1209          ChangeCh(ideProjectFileDialogLocation, from, to);
1210
1211       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
1212          ChangeCh(projectDefaultTargetDir, from, to);
1213       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
1214          ChangeCh(projectDefaultIntermediateObjDir, from, to);
1215
1216       if(compilerConfigsDir && compilerConfigsDir[0])
1217          ChangeCh(compilerConfigsDir, from, to);
1218    }
1219
1220    void ManagePortablePaths(char * location, bool makeAbsolute)
1221    {
1222       int c;
1223       if(compilerConfigs && compilerConfigs.count)
1224       {
1225          for(config : compilerConfigs)
1226          {
1227             DirTypes t;
1228             for(t = 0; t < DirTypes::enumSize; t++)
1229             {
1230                Array<String> dirs = null;
1231                if(t == executables) dirs = config.executableDirs;
1232                else if(t == includes) dirs = config.includeDirs;
1233                else if(t == libraries) dirs = config.libraryDirs;
1234                if(dirs && dirs.count)
1235                {
1236                   for(c = 0; c < dirs.count; c++)
1237                   {
1238                      if(dirs[c] && dirs[c][0])
1239                         dirs[c] = UpdatePortablePath(dirs[c], location, makeAbsolute);
1240                   }
1241                }
1242             }
1243          }
1244       }
1245       if(recentFiles && recentFiles.count)
1246       {
1247          for(c = 0; c < recentFiles.count; c++)
1248          {
1249             if(recentFiles[c] && recentFiles[c][0])
1250                recentFiles[c] = UpdatePortablePath(recentFiles[c], location, makeAbsolute);
1251          }
1252       }
1253       if(recentProjects && recentProjects.count)
1254       {
1255          for(c = 0; c < recentProjects.count; c++)
1256          {
1257             if(recentProjects[c] && recentProjects[c][0])
1258                recentProjects[c] = UpdatePortablePath(recentProjects[c], location, makeAbsolute);
1259          }
1260       }
1261       if(docDir && docDir[0])
1262          docDir = UpdatePortablePath(docDir, location, makeAbsolute);
1263       if(ideFileDialogLocation && ideFileDialogLocation[0])
1264          ideFileDialogLocation = UpdatePortablePath(ideFileDialogLocation, location, makeAbsolute);
1265       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
1266          ideProjectFileDialogLocation = UpdatePortablePath(ideProjectFileDialogLocation, location, makeAbsolute);
1267
1268       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
1269          projectDefaultTargetDir = UpdatePortablePath(projectDefaultTargetDir, location, makeAbsolute);
1270       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
1271          projectDefaultIntermediateObjDir = UpdatePortablePath(projectDefaultIntermediateObjDir, location, makeAbsolute);
1272
1273       if(compilerConfigsDir && compilerConfigsDir[0])
1274          compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
1275    }
1276
1277    char * UpdatePortablePath(char * path, const char * location, bool makeAbsolute)
1278    {
1279       char * output;
1280       if(makeAbsolute)
1281       {
1282          char p[MAX_LOCATION];
1283          strcpy(p, location);
1284          PathCatSlash(p, path);
1285          delete path;
1286          output = CopyString(p);
1287       }
1288       else
1289       {
1290          PathRelationship rel = eString_PathRelated(path, location, null);
1291          if(rel == subPath || rel == identical)
1292          {
1293             char p[MAX_LOCATION];
1294             MakePathRelative(path, location, p);
1295             if(!*p) strcpy(p, "./");
1296             else ChangeCh(p, '\\', '/');
1297             delete path;
1298             output = CopyString(p);
1299          }
1300          else
1301             output = path;
1302       }
1303       return output;
1304    }
1305 }
1306
1307 class RecentFiles : RecentPaths
1308 {
1309    void read(IDESettingsContainer settingsContainer)
1310    {
1311       char path[MAX_LOCATION];
1312       RecentFilesData d = null;
1313       Class _class = class(RecentFilesData);
1314       settingsContainer.getConfigFilePath(path, _class, null, null);
1315       readConfigFile(path, _class, &d);
1316       if(d && d.recentFiles && d.recentFiles.count)
1317       {
1318          Free();
1319          Copy((void *)d.recentFiles);
1320          settingsContainer.recentFiles = this; // Merge IDEConfigHolder / IDESettingsContainer?
1321       }
1322       delete d;
1323       settingsContainer.recentFilesMonitor.fileName = path;
1324       settingsContainer.recentFilesMonitor.StartMonitoring();
1325       settingsContainer.onLoadRecentFiles();
1326    }
1327
1328    void write(IDESettingsContainer settingsContainer)
1329    {
1330       char path[MAX_LOCATION];
1331       RecentFilesData d { };
1332       Class _class = class(RecentFilesData);
1333       settingsContainer.getConfigFilePath(path, _class, null, null);
1334       d.recentFiles = this;
1335       writeConfigFile(path, _class, d);
1336       d.recentFiles = null;
1337       delete d;
1338    }
1339 }
1340
1341 class RecentWorkspaces : RecentPaths
1342 {
1343    void read(IDESettingsContainer settingsContainer)
1344    {
1345       char path[MAX_LOCATION];
1346       RecentWorkspacesData d = null;
1347       Class _class = class(RecentWorkspacesData);
1348       settingsContainer.getConfigFilePath(path, _class, null, null);
1349       readConfigFile(path, _class, &d);
1350       if(d && d.recentWorkspaces && d.recentWorkspaces.count)
1351       {
1352          Free();
1353          Copy((void *)d.recentWorkspaces);
1354          settingsContainer.recentProjects = this; // Merge IDEConfigHolder / IDESettingsContainer?
1355       }
1356       delete d;
1357       settingsContainer.recentProjectsMonitor.fileName = path;
1358       settingsContainer.recentProjectsMonitor.StartMonitoring();
1359       settingsContainer.onLoadRecentProjects();
1360    }
1361
1362    void write(IDESettingsContainer settingsContainer)
1363    {
1364       char path[MAX_LOCATION];
1365       RecentWorkspacesData d { };
1366       Class _class = class(RecentWorkspacesData);
1367       settingsContainer.getConfigFilePath(path, _class, null, null);
1368       d.recentWorkspaces = this;
1369       writeConfigFile(path, _class, d);
1370       d.recentWorkspaces = null;
1371       delete d;
1372    }
1373 }
1374
1375 class RecentPaths : Array<String>
1376 {
1377    IteratorPointer Add(T value)
1378    {
1379       int c;
1380       char * filePath = (char *)value;
1381       ChangeCh(filePath, '\\', '/');
1382       for(c = 0; c < count; c++)
1383       {
1384          if(this[c] && !fstrcmp(this[c], filePath))
1385          {
1386             Delete((void *)&this[c]);
1387             c--;
1388          }
1389       }
1390       return Array::Add((T)filePath);
1391    }
1392
1393    IteratorPointer addRecent(const String value)
1394    {
1395       int c;
1396       char * filePath = CopyString((char *)value);
1397       IteratorPointer ip;
1398       ChangeCh(filePath, '\\', '/');
1399       for(c = 0; c < count; c++)
1400       {
1401          if(this[c] && !fstrcmp(this[c], filePath))
1402          {
1403             Delete((void *)&this[c]);
1404             c--;
1405          }
1406       }
1407       while(count >= MaxRecent)
1408          Delete(GetLast());
1409       ip = Insert(null, filePath);
1410       return ip;
1411    }
1412
1413    void changeChar(char from, char to)
1414    {
1415       if(this && count)
1416       {
1417          int c;
1418          for(c = 0; c < count; c++)
1419          {
1420             if(this[c] && this[c][0])
1421                ChangeCh(this[c], from, to);
1422          }
1423       }
1424    }
1425 }
1426
1427 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
1428 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
1429 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
1430 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
1431 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
1432 // TODO: i18n with Array
1433 static Array<const String> compilerTypeLongNames
1434 { [
1435    $"GNU Compiler Collection (GCC) / GNU Make",
1436    $"Tiny C Compiler / GNU Make",
1437    $"Portable C Compiler / GNU Make",
1438    $"Microsoft Visual Studio 2005 (8.0) Compiler",
1439    $"Microsoft Visual Studio 2008 (9.0) Compiler",
1440    $"Microsoft Visual Studio 2010 (10.0) Compiler"
1441 ] };
1442 const CompilerType firstCompilerType = gcc;
1443 const CompilerType lastCompilerType = vs10;
1444 public enum CompilerType
1445 {
1446    gcc, tcc, pcc, vs8, vs9, vs10;
1447
1448    property bool isVC
1449    {
1450       get { return this == vs8 || this == vs9 || this == vs10; }
1451    }
1452
1453    property const char *
1454    {
1455       get { return OnGetString(null, null, null); }
1456       set
1457       {
1458          if(value)
1459          {
1460             CompilerType c;
1461             for(c = firstCompilerType; c <= lastCompilerType; c++)
1462                if(!strcmpi(value, compilerTypeNames[c]))
1463                   return c;
1464          }
1465          return gcc;
1466       }
1467    };
1468
1469    property const char * longName { get { return OnGetString(null, (void*)1, null); } };
1470    property const char * versionString { get { return OnGetString(null, (void*)2, null); } };
1471    property const char * yearString { get { return OnGetString(null, (void*)3, null); } };
1472    property const char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
1473    property const char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
1474
1475    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
1476    {
1477       if(this >= firstCompilerType && this <= lastCompilerType)
1478       {
1479          if(tempString)
1480             strcpy(tempString, compilerTypeNames[this]);
1481          if(fieldData == null)
1482             return compilerTypeNames[this];
1483          else if(fieldData == (void*)1)
1484             return compilerTypeLongNames[this];
1485          else if(fieldData == (void*)2)
1486             return compilerTypeVersionString[this];
1487          else if(fieldData == (void*)3)
1488             return compilerTypeYearString[this];
1489          else if(fieldData == (void*)4)
1490             return compilerTypeProjectFileExtension[this];
1491          else if(fieldData == (void*)5)
1492             return compilerTypeSolutionFileVersionString[this];
1493       }
1494       return null;
1495    }
1496 };
1497
1498 class CompilerConfig
1499 {
1500    class_no_expansion;
1501
1502    numJobs = 1;
1503 public:
1504    property const char * name
1505    {
1506       set { delete name; if(value) name = CopyString(value); }
1507       get { return name; }
1508    }
1509    bool readOnly;
1510    CompilerType type;
1511    Platform targetPlatform;
1512    int numJobs;
1513    property const char * makeCommand
1514    {
1515       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
1516       get { return makeCommand; }
1517       isset { return makeCommand && makeCommand[0]; }
1518    }
1519    property const char * ecpCommand
1520    {
1521       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
1522       get { return ecpCommand; }
1523       isset { return ecpCommand && ecpCommand[0]; }
1524    }
1525    property const char * eccCommand
1526    {
1527       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
1528       get { return eccCommand; }
1529       isset { return eccCommand && eccCommand[0]; }
1530    }
1531    property const char * ecsCommand
1532    {
1533       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
1534       get { return ecsCommand; }
1535       isset { return ecsCommand && ecsCommand[0]; }
1536    }
1537    property const char * earCommand
1538    {
1539       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
1540       get { return earCommand; }
1541       isset { return earCommand && earCommand[0]; }
1542    }
1543    property const char * cppCommand
1544    {
1545       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
1546       get { return cppCommand; }
1547       isset { return cppCommand && cppCommand[0]; }
1548    }
1549    property const char * ccCommand
1550    {
1551       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
1552       get { return ccCommand; }
1553       isset { return ccCommand && ccCommand[0]; }
1554    }
1555    property const char * cxxCommand
1556    {
1557       set { delete cxxCommand; if(value && value[0]) cxxCommand = CopyString(value); }
1558       get { return cxxCommand; }
1559       isset { return cxxCommand && cxxCommand[0]; }
1560    }
1561    property const char * arCommand
1562    {
1563       set { delete arCommand; if(value && value[0]) arCommand = CopyString(value); }
1564       get { return arCommand; }
1565       isset { return arCommand && arCommand[0]; }
1566    }
1567    property const char * ldCommand
1568    {
1569       set { delete ldCommand; if(value && value[0]) ldCommand = CopyString(value); }
1570       get { return ldCommand; }
1571       isset { return ldCommand && ldCommand[0]; }
1572    }
1573    property const char * objectFileExt
1574    {
1575       set { delete objectFileExt; if(value && value[0]) objectFileExt = CopyString(value); }
1576       get { return objectFileExt && objectFileExt[0] ? objectFileExt : objectDefaultFileExt ; }
1577       isset { return objectFileExt && objectFileExt[0] && strcmp(objectFileExt, objectDefaultFileExt); }
1578    }
1579    property const char * staticLibFileExt
1580    {
1581       set { delete staticLibFileExt; if(value && value[0]) staticLibFileExt = CopyString(value); }
1582       get { return staticLibFileExt; }
1583       isset { return staticLibFileExt && staticLibFileExt[0]; }
1584    }
1585    property const char * sharedLibFileExt
1586    {
1587       set { delete sharedLibFileExt; if(value && value[0]) sharedLibFileExt = CopyString(value); }
1588       get { return sharedLibFileExt; }
1589       isset { return sharedLibFileExt && sharedLibFileExt[0]; }
1590    }
1591    property const char * executableFileExt
1592    {
1593       set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
1594       get { return executableFileExt; }
1595       isset { return executableFileExt && executableFileExt[0]; }
1596    }
1597    property const char * executableLauncher
1598    {
1599       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
1600       get { return executableLauncher; }
1601       isset { return executableLauncher && executableLauncher[0]; }
1602    }
1603    // TODO: implement CompilerConfig::windresCommand
1604    bool ccacheEnabled;
1605    bool distccEnabled;
1606    // deprecated
1607    property bool supportsBitDepth { set { } get { return true; } isset { return false; } }
1608
1609    property const char * distccHosts
1610    {
1611       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
1612       get { return distccHosts; }
1613       isset { return distccHosts && distccHosts[0]; }
1614    }
1615    property const char * gnuToolchainPrefix
1616    {
1617       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
1618       get { return gnuToolchainPrefix; }
1619       isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
1620    }
1621    property const char * sysroot
1622    {
1623       set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
1624       get { return sysroot; }
1625       isset { return sysroot && sysroot[0]; }
1626    }
1627    bool resourcesDotEar;
1628    bool noStripTarget;
1629    property Array<String> includeDirs
1630    {
1631       set
1632       {
1633          includeDirs.Free();
1634          if(value)
1635          {
1636             delete includeDirs;
1637             includeDirs = value;
1638          }
1639       }
1640       get { return includeDirs; }
1641       isset { return includeDirs.count != 0; }
1642    }
1643    property Array<String> libraryDirs
1644    {
1645       set
1646       {
1647          libraryDirs.Free();
1648          if(value)
1649          {
1650             delete libraryDirs;
1651             libraryDirs = value;
1652          }
1653       }
1654       get { return libraryDirs; }
1655       isset { return libraryDirs.count != 0; }
1656    }
1657    property Array<String> executableDirs
1658    {
1659       set
1660       {
1661          executableDirs.Free();
1662          if(value)
1663          {
1664             delete executableDirs;
1665             executableDirs = value;
1666          }
1667       }
1668       get { return executableDirs; }
1669       isset { return executableDirs.count != 0; }
1670    }
1671    property Array<NamedString> environmentVars
1672    {
1673       set
1674       {
1675          environmentVars.Free();
1676          if(value)
1677          {
1678             delete environmentVars;
1679             environmentVars = value;
1680          }
1681       }
1682       get { return environmentVars; }
1683       isset { return environmentVars.count != 0; }
1684    }
1685    property Array<String> prepDirectives
1686    {
1687       set
1688       {
1689          prepDirectives.Free();
1690          if(value)
1691          {
1692             delete prepDirectives;
1693             prepDirectives = value;
1694          }
1695       }
1696       get { return prepDirectives; }
1697       isset { return prepDirectives.count != 0; }
1698    }
1699    property Array<String> excludeLibs
1700    {
1701       set
1702       {
1703          excludeLibs.Free();
1704          if(value)
1705          {
1706             delete excludeLibs;
1707             excludeLibs = value;
1708          }
1709       }
1710       get { return excludeLibs; }
1711       isset { return excludeLibs.count != 0; }
1712    }
1713    property Array<String> eCcompilerFlags
1714    {
1715       set
1716       {
1717          eCcompilerFlags.Free();
1718          if(value)
1719          {
1720             delete eCcompilerFlags;
1721             eCcompilerFlags = value;
1722          }
1723       }
1724       get { return eCcompilerFlags; }
1725       isset { return eCcompilerFlags.count != 0; }
1726    }
1727    property Array<String> compilerFlags
1728    {
1729       set
1730       {
1731          compilerFlags.Free();
1732          if(value)
1733          {
1734             delete compilerFlags;
1735             compilerFlags = value;
1736          }
1737       }
1738       get { return compilerFlags; }
1739       isset { return compilerFlags.count != 0; }
1740    }
1741    property Array<String> cxxFlags
1742    {
1743       set
1744       {
1745          cxxFlags.Free();
1746          if(value)
1747          {
1748             delete cxxFlags;
1749             cxxFlags = value;
1750          }
1751       }
1752       get { return cxxFlags; }
1753       isset { return cxxFlags.count != 0; }
1754    }
1755    property Array<String> linkerFlags
1756    {
1757       set
1758       {
1759          linkerFlags.Free();
1760          if(value)
1761          {
1762             delete linkerFlags;
1763             linkerFlags = value;
1764          }
1765       }
1766       get { return linkerFlags; }
1767       isset { return linkerFlags.count != 0; }
1768    }
1769    // json backward compatibility
1770    property const char * gccPrefix
1771    {
1772       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
1773       get { return gnuToolchainPrefix; }
1774       isset { return false; }
1775    }
1776    property const char * execPrefixCommand
1777    {
1778       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
1779       get { return executableLauncher; }
1780       isset { return false; }
1781    }
1782    property const char * outputFileExt
1783    {
1784       set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
1785       get { return executableFileExt; }
1786       isset { return false; }
1787    }
1788    // utility
1789    property bool hasDocumentOutput
1790    {
1791       get
1792       {
1793          bool result = executableFileExt && executableFileExt[0] &&
1794                (!strcmpi(executableFileExt, "htm") || !strcmpi(executableFileExt, "html"));
1795          return result;
1796       }
1797       isset { return false; }
1798    }
1799 private:
1800    Array<String> includeDirs { };
1801    Array<String> libraryDirs { };
1802    Array<String> executableDirs { };
1803    // TODO: Can JSON parse and serialize maps?
1804    //EnvironmentVariables { };
1805    Array<NamedString> environmentVars { };
1806    Array<String> prepDirectives { };
1807    Array<String> excludeLibs { };
1808    Array<String> eCcompilerFlags { };
1809    Array<String> compilerFlags { };
1810    Array<String> cxxFlags { };
1811    Array<String> linkerFlags { };
1812    char * name;
1813    char * makeCommand;
1814    char * ecpCommand;
1815    char * eccCommand;
1816    char * ecsCommand;
1817    char * earCommand;
1818    char * cppCommand;
1819    char * ccCommand;
1820    char * cxxCommand;
1821    char * ldCommand;
1822    char * arCommand;
1823    char * objectFileExt;
1824    char * staticLibFileExt;
1825    char * sharedLibFileExt;
1826    char * executableFileExt;
1827    char * executableLauncher;
1828    char * distccHosts;
1829    char * gnuToolchainPrefix;
1830    char * sysroot;
1831    /*union
1832    {
1833       struct { Array<String> includes, libraries, executables; };
1834       Array<String> dirs[DirTypes];
1835    }*/
1836
1837    ~CompilerConfig()
1838    {
1839       delete name;
1840       delete ecpCommand;
1841       delete eccCommand;
1842       delete ecsCommand;
1843       delete earCommand;
1844       delete cppCommand;
1845       delete ccCommand;
1846       delete cxxCommand;
1847       delete ldCommand;
1848       delete arCommand;
1849       delete objectFileExt;
1850       delete staticLibFileExt;
1851       delete sharedLibFileExt;
1852       delete executableFileExt;
1853       delete makeCommand;
1854       delete executableLauncher;
1855       delete distccHosts;
1856       delete gnuToolchainPrefix;
1857       delete sysroot;
1858       if(environmentVars) environmentVars.Free();
1859       if(includeDirs) { includeDirs.Free(); }
1860       if(libraryDirs) { libraryDirs.Free(); }
1861       if(executableDirs) { executableDirs.Free(); }
1862       if(prepDirectives) { prepDirectives.Free(); }
1863       if(excludeLibs) { excludeLibs.Free(); }
1864       if(compilerFlags) { compilerFlags.Free(); }
1865       if(cxxFlags) { cxxFlags.Free(); }
1866       if(eCcompilerFlags) { eCcompilerFlags.Free(); }
1867       if(linkerFlags) { linkerFlags.Free(); }
1868    }
1869
1870    int OnCompare(CompilerConfig b)
1871    {
1872       int result;
1873       if(
1874          !(result = type.OnCompare(b.type)) &&
1875          !(result = targetPlatform.OnCompare(b.targetPlatform)) &&
1876          !(result = numJobs.OnCompare(b.numJobs)) &&
1877          !(result = ccacheEnabled.OnCompare(b.ccacheEnabled)) &&
1878          !(result = distccEnabled.OnCompare(b.distccEnabled)) &&
1879          !(result = resourcesDotEar.OnCompare(b.resourcesDotEar)) &&
1880          !(result = noStripTarget.OnCompare(b.noStripTarget))
1881          );
1882
1883       if(!result &&
1884          !(result = name.OnCompare(b.name)) &&
1885          !(result = ecpCommand.OnCompare(b.ecpCommand)) &&
1886          !(result = eccCommand.OnCompare(b.eccCommand)) &&
1887          !(result = ecsCommand.OnCompare(b.ecsCommand)) &&
1888          !(result = earCommand.OnCompare(b.earCommand)) &&
1889          !(result = cppCommand.OnCompare(b.cppCommand)) &&
1890          !(result = ccCommand.OnCompare(b.ccCommand)) &&
1891          !(result = cxxCommand.OnCompare(b.cxxCommand)) &&
1892          !(result = ldCommand.OnCompare(b.ldCommand)) &&
1893          !(result = arCommand.OnCompare(b.arCommand)) &&
1894          !(result = objectFileExt.OnCompare(b.objectFileExt)) &&
1895          !(result = outputFileExt.OnCompare(b.outputFileExt)) &&
1896          !(result = makeCommand.OnCompare(b.makeCommand)) &&
1897          !(result = executableLauncher.OnCompare(b.executableLauncher)) &&
1898          !(result = distccHosts.OnCompare(b.distccHosts)) &&
1899          !(result = gnuToolchainPrefix.OnCompare(b.gnuToolchainPrefix)) &&
1900          !(result = sysroot.OnCompare(b.sysroot)));
1901
1902       if(!result &&
1903          !(result = includeDirs.OnCompare(b.includeDirs)) &&
1904          !(result = libraryDirs.OnCompare(b.libraryDirs)) &&
1905          !(result = executableDirs.OnCompare(b.executableDirs)) &&
1906          !(result = environmentVars.OnCompare(b.environmentVars)) &&
1907          !(result = prepDirectives.OnCompare(b.prepDirectives)) &&
1908          !(result = excludeLibs.OnCompare(b.excludeLibs)) &&
1909          !(result = cxxFlags.OnCompare(b.cxxFlags)) &&
1910          !(result = eCcompilerFlags.OnCompare(b.eCcompilerFlags)) &&
1911          !(result = compilerFlags.OnCompare(b.compilerFlags)) &&
1912          !(result = linkerFlags.OnCompare(b.linkerFlags)));
1913       return result;
1914    }
1915
1916 public:
1917    CompilerConfig Copy()
1918    {
1919       CompilerConfig copy
1920       {
1921          name,
1922          readOnly,
1923          type,
1924          targetPlatform,
1925          numJobs,
1926          makeCommand,
1927          ecpCommand,
1928          eccCommand,
1929          ecsCommand,
1930          earCommand,
1931          cppCommand,
1932          ccCommand,
1933          cxxCommand,
1934          arCommand,
1935          ldCommand,
1936          objectFileExt,
1937          staticLibFileExt,
1938          sharedLibFileExt,
1939          executableFileExt,
1940          executableLauncher,
1941          ccacheEnabled,
1942          distccEnabled,
1943          false,
1944          distccHosts,
1945          gnuToolchainPrefix,
1946          sysroot,
1947          resourcesDotEar,
1948          noStripTarget
1949       };
1950       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
1951       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
1952       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
1953       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
1954       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
1955       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
1956       for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
1957       for(s : cxxFlags) copy.cxxFlags.Add(CopyString(s));
1958       for(s : eCcompilerFlags) copy.eCcompilerFlags.Add(CopyString(s));
1959       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
1960
1961       incref copy;
1962       return copy;
1963    }
1964
1965    CompilerConfig ::read(const char * path)
1966    {
1967       CompilerConfig d = null;
1968       readConfigFile(path, class(CompilerConfig), &d);
1969       return d;
1970    }
1971
1972    void write(IDESettingsContainer settingsContainer)
1973    {
1974       char dir[MAX_LOCATION];
1975       char path[MAX_LOCATION];
1976       const char * settingsFilePath = settingsContainer.settingsFilePath;
1977       settingsContainer.getConfigFilePath(path, _class, dir, name);
1978       if(FileExists(settingsFilePath) && !FileExists(dir))
1979       {
1980          MakeDir(dir);
1981          if(!FileExists(dir))
1982             PrintLn($"Error creating compiler configs directory at ", dir, " location.");
1983       }
1984       writeConfigFile(path, _class, this);
1985    }
1986 }
1987
1988 class CompilerConfigs : List<CompilerConfig>
1989 {
1990    CompilerConfig GetCompilerConfig(const String compilerName)
1991    {
1992       const char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
1993       CompilerConfig compilerConfig = null;
1994       for(compiler : this)
1995       {
1996          if(!strcmp(compiler.name, name))
1997          {
1998             compilerConfig = compiler;
1999             break;
2000          }
2001       }
2002       if(!compilerConfig && count)
2003          compilerConfig = this[0];
2004       if(compilerConfig)
2005       {
2006          incref compilerConfig;
2007          if(compilerConfig._refCount == 1)
2008             incref compilerConfig;
2009       }
2010       return compilerConfig;
2011    }
2012
2013    void ensureDefaults()
2014    {
2015       // Ensure we have a default compiler
2016       CompilerConfig defaultCompiler = GetCompilerConfig(defaultCompilerName);
2017       if(!defaultCompiler)
2018       {
2019          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
2020          Insert(null, defaultCompiler);
2021          defaultCompiler = null;
2022       }
2023       delete defaultCompiler;
2024
2025       for(ccfg : this)
2026       {
2027          if(!ccfg.ecpCommand || !ccfg.ecpCommand[0])
2028             ccfg.ecpCommand = ecpDefaultCommand;
2029          if(!ccfg.eccCommand || !ccfg.eccCommand[0])
2030             ccfg.eccCommand = eccDefaultCommand;
2031          if(!ccfg.ecsCommand || !ccfg.ecsCommand[0])
2032             ccfg.ecsCommand = ecsDefaultCommand;
2033          if(!ccfg.earCommand || !ccfg.earCommand[0])
2034             ccfg.earCommand = earDefaultCommand;
2035          if(!ccfg.cppCommand || !ccfg.cppCommand[0])
2036             ccfg.cppCommand = cppDefaultCommand;
2037          if(!ccfg.ccCommand || !ccfg.ccCommand[0])
2038             ccfg.ccCommand = ccDefaultCommand;
2039          if(!ccfg.cxxCommand || !ccfg.cxxCommand[0])
2040             ccfg.cxxCommand = cxxDefaultCommand;
2041          /*if(!ccfg.ldCommand || !ccfg.ldCommand[0])
2042             ccfg.ldCommand = ldDefaultCommand;*/
2043          if(!ccfg.arCommand || !ccfg.arCommand[0])
2044             ccfg.arCommand = arDefaultCommand;
2045          if(!ccfg.objectFileExt || !ccfg.objectFileExt[0])
2046             ccfg.objectFileExt = objectDefaultFileExt;
2047          /*if(!ccfg.staticLibFileExt || !ccfg.staticLibFileExt[0])
2048             ccfg.staticLibFileExt = staticLibDefaultFileExt;*/
2049          /*if(!ccfg.sharedLibFileExt || !ccfg.sharedLibFileExt[0])
2050             ccfg.sharedLibFileExt = sharedLibDefaultFileExt;*/
2051          /*if(!ccfg.executableFileExt || !ccfg.executableFileExt[0])
2052             ccfg.executableFileExt = outputDefaultFileExt;*/
2053          if(!ccfg._refCount) incref ccfg;
2054       }
2055    }
2056
2057    AVLTree<String> getWriteRequiredList(CompilerConfigs oldConfigs)
2058    {
2059       AVLTree<String> list { };
2060       for(ccfg : this)
2061       {
2062          bool found = false;
2063          for(occfg : oldConfigs; !strcmp(ccfg.name, occfg.name))
2064          {
2065             found = true;
2066             if(ccfg.OnCompare(occfg))
2067                list.Add(CopyString(ccfg.name));
2068             break;
2069          }
2070          if(!found)
2071             list.Add(CopyString(ccfg.name));
2072       }
2073       return list;
2074    }
2075
2076    bool read(IDESettingsContainer settingsContainer)
2077    {
2078       if(settingsContainer.settingsFilePath)
2079       {
2080          char dir[MAX_LOCATION];
2081          char path[MAX_LOCATION];
2082          Class _class = class(CompilerConfig);
2083          settingsContainer.getConfigFilePath(path, _class, dir, null);
2084          if(dir[0])
2085          {
2086             AVLTree<const String> addedConfigs { };
2087             Map<String, CompilerConfig> compilerConfigsByName = getCompilerConfigsByName(dir);
2088             MapIterator<const String, CompilerConfig> it { map = compilerConfigsByName };
2089             Free();
2090             settingsContainer.compilerConfigs = this; // Merge IDEConfigHolder / IDESettingsContainer?
2091             if(it.Index("Default", false))
2092             {
2093                CompilerConfig ccfg = it.data;
2094                Add(ccfg.Copy());
2095                addedConfigs.Add(ccfg.name);
2096             }
2097             for(ccfg : compilerConfigsByName)
2098             {
2099                if(!addedConfigs.Find(ccfg.name))
2100                {
2101                   Add(ccfg.Copy());
2102                   addedConfigs.Add(ccfg.name);
2103                }
2104             }
2105             delete addedConfigs;
2106             ensureDefaults();
2107             compilerConfigsByName.Free();
2108             delete compilerConfigsByName;
2109             settingsContainer.onLoadCompilerConfigs();
2110             return true;
2111          }
2112       }
2113       return false;
2114    }
2115
2116    void write(IDESettingsContainer settingsContainer, AVLTree<String> cfgsToWrite)
2117    {
2118       char dir[MAX_LOCATION];
2119       char path[MAX_LOCATION];
2120       Map<String, String> paths;
2121       settingsContainer.getConfigFilePath(path, class(CompilerConfig), dir, null);
2122       paths = getCompilerConfigFilePathsByName(dir);
2123       {
2124          MapIterator<String, String> it { map = paths };
2125          for(c : this)
2126          {
2127             CompilerConfig ccfg = c;
2128             if(!cfgsToWrite || cfgsToWrite.Find(ccfg.name))
2129                ccfg.write(settingsContainer);
2130             if(it.Index(ccfg.name, false))
2131             {
2132                delete it.data;
2133                it.Remove();
2134             }
2135          }
2136       }
2137       for(p : paths)
2138       {
2139          const char * path = p;
2140          DeleteFile(path);
2141       }
2142       paths.Free();
2143       delete paths;
2144    }
2145 }
2146
2147 #if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
2148 struct LanguageOption
2149 {
2150    const String name;
2151    const String bitmap;
2152    const String code;
2153    BitmapResource res;
2154
2155    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
2156    {
2157       return name;
2158    }
2159
2160    void OnDisplay(Surface surface, int x, int y, int width, void * data, Alignment alignment, DataDisplayFlags flags)
2161    {
2162       Bitmap icon = res ? res.bitmap : null;
2163       int w = 8 + 16;
2164       if(icon)
2165          surface.Blit(icon, x + (16 - icon.width) / 2,y+2,0,0, icon.width, icon.height);
2166       class::OnDisplay(surface, x + w, y, width - w, null, alignment, flags);
2167    }
2168 };
2169
2170 Array<LanguageOption> languages
2171 { [
2172    { "English",            ":countryCode/gb.png", "" },
2173    { "汉语",                ":countryCode/cn.png", "zh_CN" },
2174    { "Español",            ":countryCode/es.png", "es" },
2175    { "Português (Brazil)", ":countryCode/br.png", "pt_BR" },
2176    { "Русский (43%)",      ":countryCode/ru.png", "ru" },
2177    { "Nederlandse (13%)",  ":countryCode/nl.png", "nl" },
2178    { "Tiếng Việt (12%)",   ":countryCode/vn.png", "vi" },
2179    { "मराठी (10%)",          ":countryCode/in.png", "mr" },
2180    { "Hebrew (8%)",        ":countryCode/il.png", "he" },
2181    { "Magyar (8%)",        ":countryCode/hu.png", "hu" }
2182 ] };
2183
2184 const String GetLanguageString()
2185 {
2186    char * dot, * colon;
2187    static char lang[256];
2188    const String language = getenv("ECERE_LANGUAGE");
2189    if(!language) language = getenv("LANGUAGE");
2190    if(!language) language = getenv("LC_ALL");
2191    if(!language) language = getenv("LC_MESSAGES");
2192    if(!language) language = getenv("LANG");
2193    if(!language) language = "";
2194    if(language && (colon = strchr(language, ':')))
2195    {
2196       if(lang != language)
2197          strncpy(lang, language, sizeof(lang));
2198       lang[sizeof(lang)-1] = 0;
2199       lang[colon - language] = 0;
2200       language = lang;
2201    }
2202    if(language && (dot = strchr(language, '.')))
2203    {
2204       if(lang != language)
2205          strncpy(lang, language, sizeof(lang));
2206       lang[sizeof(lang)-1] = 0;
2207       lang[dot - language] = 0;
2208       language = lang;
2209    }
2210    return language;
2211 }
2212
2213 void setEcereLanguageInWinRegEnvironment(const char * languageCode)
2214 {
2215 #ifdef __WIN32__
2216    HKEY key = null;
2217    uint16 wLanguage[256];
2218    DWORD status;
2219    wLanguage[0] = 0;
2220
2221    RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
2222    if(key)
2223    {
2224       UTF8toUTF16Buffer(languageCode, wLanguage, sizeof(wLanguage) / sizeof(uint16));
2225       RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
2226       RegCloseKey(key);
2227    }
2228 #endif
2229 }
2230
2231 bool LanguageRestart(const char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
2232 {
2233    bool restart = true;
2234    String command = null;
2235    int arg0Len = (int)strlen(app.argv[0]);
2236    int len = arg0Len;
2237    int j;
2238    char ch;
2239
2240    if(ide)
2241    {
2242       Window w;
2243
2244       if(projectView)
2245       {
2246          Window w;
2247          for(w = ide.firstChild; w; w = w.next)
2248          {
2249             if(w.isActiveClient && w.isDocument)
2250             {
2251                if(!w.CloseConfirmation(true))
2252                {
2253                   restart = false;
2254                   break;
2255                }
2256             }
2257          }
2258          if(restart)
2259          {
2260             if(!projectView.CloseConfirmation(true))
2261                restart = false;
2262             if(projectView.fileName)
2263             {
2264                const char * name = projectView.fileName;
2265                if(name)
2266                {
2267                   for(j = 0; (ch = name[j]); j++)
2268                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
2269                }
2270             }
2271
2272             command = new char[len + 1];
2273
2274             strcpy(command, app.argv[0]);
2275             len = arg0Len;
2276             if(projectView.fileName)
2277             {
2278                strcat(command, " ");
2279                len++;
2280                ReplaceSpaces(command + len, projectView.fileName);
2281             }
2282          }
2283          if(restart)
2284          {
2285             for(w = ide.firstChild; w; w = w.next)
2286                if(w.isActiveClient && w.isDocument)
2287                   w.modifiedDocument = false;
2288             projectView.modifiedDocument = false;
2289          }
2290       }
2291       else
2292       {
2293          for(w = ide.firstChild; w; w = w.next)
2294          {
2295             if(w.isActiveClient && w.isDocument)
2296             {
2297                if(!w.CloseConfirmation(true))
2298                {
2299                   restart = false;
2300                   break;
2301                }
2302                if(w.fileName)
2303                {
2304                   const char * name = w.fileName;
2305                   len++;
2306                   for(j = 0; (ch = name[j]); j++)
2307                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
2308                }
2309             }
2310          }
2311
2312          if(restart)
2313          {
2314             command = new char[len + 1];
2315             strcpy(command, app.argv[0]);
2316             len = arg0Len;
2317
2318             for(w = ide.firstChild; w; w = w.next)
2319             {
2320                if(w.isActiveClient && w.isDocument)
2321                {
2322                   const char * name = w.fileName;
2323                   if(name)
2324                   {
2325                      strcat(command, " ");
2326                      len++;
2327                      ReplaceSpaces(command + len, name);
2328                      len = (int)strlen(command);
2329                   }
2330                }
2331             }
2332          }
2333          if(restart)
2334          {
2335             for(w = ide.firstChild; w; w = w.next)
2336                if(w.isActiveClient && w.isDocument)
2337                   w.modifiedDocument = false;
2338          }
2339       }
2340       if(restart)
2341       {
2342          settings.language = code;
2343          settingsContainer.Save();
2344
2345          setEcereLanguageInWinRegEnvironment(code);
2346
2347          if(eClass_IsDerived(app._class, class(GuiApplication)))
2348          {
2349             GuiApplication guiApp = (GuiApplication)app;
2350             guiApp.desktop.Destroy(0);
2351          }
2352       }
2353    }
2354    else
2355    {
2356       int i;
2357       for(i = 1; i < app.argc; i++)
2358       {
2359          const char * arg = app.argv[i];
2360          len++;
2361          for(j = 0; (ch = arg[j]); j++)
2362             len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
2363       }
2364
2365       command = new char[len + 1];
2366       strcpy(command, app.argv[0]);
2367       len = arg0Len;
2368       for(i = 1; i < app.argc; i++)
2369       {
2370          strcat(command, " ");
2371          len++;
2372          ReplaceSpaces(command + len, app.argv[i]);
2373          len = (int)strlen(command);
2374       }
2375    }
2376
2377    if(restart)
2378    {
2379       SetEnvironment("ECERE_LANGUAGE", code);
2380       if(wait)
2381          ExecuteWait(command);
2382       else
2383          Execute(command);
2384    }
2385    delete command;
2386    return restart;
2387 }
2388 #endif