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