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