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