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