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