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