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