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