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