epj2make, ide: makefile generation, global settings dialog: add CXXFLAGS to compiler...
[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> cxxFlags
1397    {
1398       set
1399       {
1400          cxxFlags.Free();
1401          if(value)
1402          {
1403             delete cxxFlags;
1404             cxxFlags = value;
1405          }
1406       }
1407       get { return cxxFlags; }
1408       isset { return cxxFlags.count != 0; }
1409    }
1410    property Array<String> linkerFlags
1411    {
1412       set
1413       {
1414          linkerFlags.Free();
1415          if(value)
1416          {
1417             delete linkerFlags;
1418             linkerFlags = value;
1419          }
1420       }
1421       get { return linkerFlags; }
1422       isset { return linkerFlags.count != 0; }
1423    }
1424    // json backward compatibility
1425    property const char * gccPrefix
1426    {
1427       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
1428       get { return gnuToolchainPrefix; }
1429       isset { return false; }
1430    }
1431    property const char * execPrefixCommand
1432    {
1433       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
1434       get { return executableLauncher; }
1435       isset { return false; }
1436    }
1437 private:
1438    Array<String> includeDirs { };
1439    Array<String> libraryDirs { };
1440    Array<String> executableDirs { };
1441    // TODO: Can JSON parse and serialize maps?
1442    //EnvironmentVariables { };
1443    Array<NamedString> environmentVars { };
1444    Array<String> prepDirectives { };
1445    Array<String> excludeLibs { };
1446    Array<String> eCcompilerFlags { };
1447    Array<String> compilerFlags { };
1448    Array<String> cxxFlags { };
1449    Array<String> linkerFlags { };
1450    char * name;
1451    char * makeCommand;
1452    char * ecpCommand;
1453    char * eccCommand;
1454    char * ecsCommand;
1455    char * earCommand;
1456    char * cppCommand;
1457    char * ccCommand;
1458    char * cxxCommand;
1459    char * ldCommand;
1460    char * arCommand;
1461    char * objectFileExt;
1462    char * outputFileExt;
1463    char * executableLauncher;
1464    char * distccHosts;
1465    char * gnuToolchainPrefix;
1466    char * sysroot;
1467    /*union
1468    {
1469       struct { Array<String> includes, libraries, executables; };
1470       Array<String> dirs[DirTypes];
1471    }*/
1472
1473    ~CompilerConfig()
1474    {
1475       delete name;
1476       delete ecpCommand;
1477       delete eccCommand;
1478       delete ecsCommand;
1479       delete earCommand;
1480       delete cppCommand;
1481       delete ccCommand;
1482       delete cxxCommand;
1483       delete ldCommand;
1484       delete arCommand;
1485       delete objectFileExt;
1486       delete outputFileExt;
1487       delete makeCommand;
1488       delete executableLauncher;
1489       delete distccHosts;
1490       delete gnuToolchainPrefix;
1491       delete sysroot;
1492       if(environmentVars) environmentVars.Free();
1493       if(includeDirs) { includeDirs.Free(); }
1494       if(libraryDirs) { libraryDirs.Free(); }
1495       if(executableDirs) { executableDirs.Free(); }
1496       if(prepDirectives) { prepDirectives.Free(); }
1497       if(excludeLibs) { excludeLibs.Free(); }
1498       if(compilerFlags) { compilerFlags.Free(); }
1499       if(cxxFlags) { cxxFlags.Free(); }
1500       if(eCcompilerFlags) { eCcompilerFlags.Free(); }
1501       if(linkerFlags) { linkerFlags.Free(); }
1502    }
1503
1504    int OnCompare(CompilerConfig b)
1505    {
1506       int result;
1507       if(!(result = name.OnCompare(b.name)) &&
1508          !(result = ecpCommand.OnCompare(b.ecpCommand)) &&
1509          !(result = eccCommand.OnCompare(b.eccCommand)) &&
1510          !(result = ecsCommand.OnCompare(b.ecsCommand)) &&
1511          !(result = earCommand.OnCompare(b.earCommand)) &&
1512          !(result = cppCommand.OnCompare(b.cppCommand)) &&
1513          !(result = ccCommand.OnCompare(b.ccCommand)) &&
1514          !(result = cxxCommand.OnCompare(b.cxxCommand)) &&
1515          !(result = ldCommand.OnCompare(b.ldCommand)) &&
1516          !(result = arCommand.OnCompare(b.arCommand)) &&
1517          !(result = objectFileExt.OnCompare(b.objectFileExt)) &&
1518          !(result = outputFileExt.OnCompare(b.outputFileExt)) &&
1519          !(result = makeCommand.OnCompare(b.makeCommand)) &&
1520          !(result = executableLauncher.OnCompare(b.executableLauncher)) &&
1521          !(result = distccHosts.OnCompare(b.distccHosts)) &&
1522          !(result = gnuToolchainPrefix.OnCompare(b.gnuToolchainPrefix)) &&
1523          !(result = sysroot.OnCompare(b.sysroot)))
1524          ;
1525
1526       if(!result &&
1527          !(result = includeDirs.OnCompare(b.includeDirs)) &&
1528          !(result = libraryDirs.OnCompare(b.libraryDirs)) &&
1529          !(result = executableDirs.OnCompare(b.executableDirs)) &&
1530          !(result = environmentVars.OnCompare(b.environmentVars)) &&
1531          !(result = prepDirectives.OnCompare(b.prepDirectives)) &&
1532          !(result = excludeLibs.OnCompare(b.excludeLibs)) &&
1533          !(result = cxxFlags.OnCompare(b.cxxFlags)) &&
1534          !(result = eCcompilerFlags.OnCompare(b.eCcompilerFlags)) &&
1535          !(result = compilerFlags.OnCompare(b.compilerFlags)) &&
1536          !(result = linkerFlags.OnCompare(b.linkerFlags)))
1537          ;
1538
1539       return result;
1540    }
1541
1542 public:
1543    CompilerConfig Copy()
1544    {
1545       CompilerConfig copy
1546       {
1547          name,
1548          readOnly,
1549          type,
1550          targetPlatform,
1551          numJobs,
1552          makeCommand,
1553          ecpCommand,
1554          eccCommand,
1555          ecsCommand,
1556          earCommand,
1557          cppCommand,
1558          ccCommand,
1559          cxxCommand,
1560          arCommand,
1561          ldCommand,
1562          objectFileExt,
1563          outputFileExt,
1564          executableLauncher,
1565          ccacheEnabled,
1566          distccEnabled,
1567          false,
1568          distccHosts,
1569          gnuToolchainPrefix,
1570          sysroot,
1571          resourcesDotEar
1572       };
1573       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
1574       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
1575       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
1576       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
1577       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
1578       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
1579       for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
1580       for(s : cxxFlags) copy.cxxFlags.Add(CopyString(s));
1581       for(s : eCcompilerFlags) copy.eCcompilerFlags.Add(CopyString(s));
1582       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
1583
1584       incref copy;
1585       return copy;
1586    }
1587
1588    CompilerConfig ::read(const char * path)
1589    {
1590       CompilerConfig d = null;
1591       readConfigFile(path, class(CompilerConfig), &d);
1592       return d;
1593    }
1594
1595    void write()
1596    {
1597       char dir[MAX_LOCATION];
1598       char path[MAX_LOCATION];
1599       const char * settingsFilePath = settingsContainer.settingsFilePath;
1600       getConfigFilePath(path, _class, dir, name);
1601       if(FileExists(settingsFilePath) && !FileExists(dir))
1602       {
1603          MakeDir(dir);
1604          if(!FileExists(dir))
1605             PrintLn($"Error creating compiler configs directory at ", dir, " location.");
1606       }
1607       writeConfigFile(path, _class, this);
1608    }
1609 }
1610
1611 class CompilerConfigs : List<CompilerConfig>
1612 {
1613    void ::fix()
1614    {
1615       IDESettings s = (IDESettings)settingsContainer.data;
1616       // Ensure we have a default compiler
1617       CompilerConfig defaultCompiler = null;
1618       defaultCompiler = s.GetCompilerConfig(defaultCompilerName);
1619       if(!defaultCompiler)
1620       {
1621          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
1622          s.compilerConfigs.Insert(null, defaultCompiler);
1623          defaultCompiler = null;
1624       }
1625       delete defaultCompiler;
1626
1627       if(s.compilerConfigs)
1628       {
1629          for(ccfg : s.compilerConfigs)
1630          {
1631             if(!ccfg.ecpCommand || !ccfg.ecpCommand[0])
1632                ccfg.ecpCommand = ecpDefaultCommand;
1633             if(!ccfg.eccCommand || !ccfg.eccCommand[0])
1634                ccfg.eccCommand = eccDefaultCommand;
1635             if(!ccfg.ecsCommand || !ccfg.ecsCommand[0])
1636                ccfg.ecsCommand = ecsDefaultCommand;
1637             if(!ccfg.earCommand || !ccfg.earCommand[0])
1638                ccfg.earCommand = earDefaultCommand;
1639             if(!ccfg.cppCommand || !ccfg.cppCommand[0])
1640                ccfg.cppCommand = cppDefaultCommand;
1641             if(!ccfg.ccCommand || !ccfg.ccCommand[0])
1642                ccfg.ccCommand = ccDefaultCommand;
1643             if(!ccfg.cxxCommand || !ccfg.cxxCommand[0])
1644                ccfg.cxxCommand = cxxDefaultCommand;
1645             /*if(!ccfg.ldCommand || !ccfg.ldCommand[0])
1646                ccfg.ldCommand = ldDefaultCommand;*/
1647             if(!ccfg.arCommand || !ccfg.arCommand[0])
1648                ccfg.arCommand = arDefaultCommand;
1649             if(!ccfg.objectFileExt || !ccfg.objectFileExt[0])
1650                ccfg.objectFileExt = objectDefaultFileExt;
1651             /*if(!ccfg.outputFileExt || !ccfg.outputFileExt[0])
1652                ccfg.outputFileExt = outputDefaultFileExt;*/
1653             if(!ccfg._refCount) incref ccfg;
1654          }
1655       }
1656    }
1657
1658    AVLTree<String> getWriteRequiredList(CompilerConfigs oldConfigs)
1659    {
1660       AVLTree<String> list { };
1661       for(ccfg : this)
1662       {
1663          for(occfg : oldConfigs)
1664          {
1665             if(!strcmp(ccfg.name, occfg.name))
1666             {
1667                if(ccfg.OnCompare(occfg))
1668                   list.Add(CopyString(ccfg.name));
1669                break;
1670             }
1671          }
1672       }
1673       return list;
1674    }
1675
1676    void ::read()
1677    {
1678       if(settingsContainer.settingsFilePath)
1679       {
1680          char dir[MAX_LOCATION];
1681          char path[MAX_LOCATION];
1682          Class _class = class(CompilerConfig);
1683          getConfigFilePath(path, _class, dir, null);
1684          if(dir[0])
1685          {
1686             CompilerConfigs ccfgs { };
1687             AVLTree<const String> addedConfigs { };
1688             IDESettings s = (IDESettings)settingsContainer.data;
1689             Map<String, CompilerConfig> compilerConfigsByName = getCompilerConfigsByName(dir);
1690             MapIterator<const String, CompilerConfig> it { map = compilerConfigsByName };
1691             if(it.Index("Default", false))
1692             {
1693                CompilerConfig ccfg = it.data;
1694                ccfgs.Add(ccfg.Copy());
1695                addedConfigs.Add(ccfg.name);
1696             }
1697             for(ccfg : compilerConfigsByName)
1698             {
1699                if(!addedConfigs.Find(ccfg.name))
1700                {
1701                   ccfgs.Add(ccfg.Copy());
1702                   addedConfigs.Add(ccfg.name);
1703                }
1704             }
1705             for(ccfg : s.compilerConfigs)
1706             {
1707                if(!addedConfigs.Find(ccfg.name))
1708                   ccfgs.Add(ccfg.Copy());
1709             }
1710             delete addedConfigs;
1711             s.property::compilerConfigs = ccfgs;
1712             fix();
1713             compilerConfigsByName.Free();
1714             delete compilerConfigsByName;
1715 #if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
1716             ide.UpdateCompilerConfigs(true);
1717 #endif
1718          }
1719       }
1720    }
1721
1722    void ::write(AVLTree<String> cfgsToWrite)
1723    {
1724       char dir[MAX_LOCATION];
1725       char path[MAX_LOCATION];
1726       Map<String, String> paths;
1727       IDESettings s = (IDESettings)settingsContainer.data;
1728       getConfigFilePath(path, class(CompilerConfig), dir, null);
1729       paths = getCompilerConfigFilePathsByName(dir);
1730       {
1731          MapIterator<String, String> it { map = paths };
1732          for(c : s.compilerConfigs)
1733          {
1734             CompilerConfig ccfg = c;
1735             if(!cfgsToWrite || cfgsToWrite.Find(ccfg.name))
1736                ccfg.write();
1737             if(it.Index(ccfg.name, false))
1738             {
1739                delete it.data;
1740                it.Remove();
1741             }
1742          }
1743       }
1744       for(p : paths)
1745       {
1746          const char * path = p;
1747          DeleteFile(path);
1748       }
1749       paths.Free();
1750       delete paths;
1751    }
1752 }
1753
1754 #if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
1755 struct LanguageOption
1756 {
1757    const String name;
1758    const String bitmap;
1759    const String code;
1760    BitmapResource res;
1761
1762    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
1763    {
1764       return name;
1765    }
1766
1767    void OnDisplay(Surface surface, int x, int y, int width, void * data, Alignment alignment, DataDisplayFlags flags)
1768    {
1769       Bitmap icon = res ? res.bitmap : null;
1770       int w = 8 + 16;
1771       if(icon)
1772          surface.Blit(icon, x + (16 - icon.width) / 2,y+2,0,0, icon.width, icon.height);
1773       class::OnDisplay(surface, x + w, y, width - w, null, alignment, flags);
1774    }
1775 };
1776
1777 Array<LanguageOption> languages
1778 { [
1779    { "English",            ":countryCode/gb.png", "" },
1780    { "汉语",                ":countryCode/cn.png", "zh_CN" },
1781    { "Español",            ":countryCode/es.png", "es" },
1782    { "Português (Brazil)", ":countryCode/br.png", "pt_BR" },
1783    { "Русский (43%)",      ":countryCode/ru.png", "ru" },
1784    { "Nederlandse (13%)",  ":countryCode/nl.png", "nl" },
1785    { "Tiếng Việt (12%)",   ":countryCode/vn.png", "vi" },
1786    { "मराठी (10%)",          ":countryCode/in.png", "mr" },
1787    { "Hebrew (8%)",        ":countryCode/il.png", "he" },
1788    { "Magyar (8%)",        ":countryCode/hu.png", "hu" }
1789 ] };
1790
1791 const String GetLanguageString()
1792 {
1793    char * dot, * colon;
1794    static char lang[256];
1795    const String language = getenv("ECERE_LANGUAGE");
1796    if(!language) language = getenv("LANGUAGE");
1797    if(!language) language = getenv("LC_ALL");
1798    if(!language) language = getenv("LC_MESSAGES");
1799    if(!language) language = getenv("LANG");
1800    if(!language) language = "";
1801    if(language && (colon = strchr(language, ':')))
1802    {
1803       if(lang != language)
1804          strncpy(lang, language, sizeof(lang));
1805       lang[sizeof(lang)-1] = 0;
1806       lang[colon - language] = 0;
1807       language = lang;
1808    }
1809    if(language && (dot = strchr(language, '.')))
1810    {
1811       if(lang != language)
1812          strncpy(lang, language, sizeof(lang));
1813       lang[sizeof(lang)-1] = 0;
1814       lang[dot - language] = 0;
1815       language = lang;
1816    }
1817    return language;
1818 }
1819
1820 bool LanguageRestart(const char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
1821 {
1822    bool restart = true;
1823    String command = null;
1824    int arg0Len = (int)strlen(app.argv[0]);
1825    int len = arg0Len;
1826    int j;
1827    char ch;
1828
1829    if(ide)
1830    {
1831       Window w;
1832
1833       if(projectView)
1834       {
1835          Window w;
1836          for(w = ide.firstChild; w; w = w.next)
1837          {
1838             if(w.isActiveClient && w.isDocument)
1839             {
1840                if(!w.CloseConfirmation(true))
1841                {
1842                   restart = false;
1843                   break;
1844                }
1845             }
1846          }
1847          if(restart)
1848          {
1849             if(!projectView.CloseConfirmation(true))
1850                restart = false;
1851             if(projectView.fileName)
1852             {
1853                const char * name = projectView.fileName;
1854                if(name)
1855                {
1856                   for(j = 0; (ch = name[j]); j++)
1857                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1858                }
1859             }
1860
1861             command = new char[len + 1];
1862
1863             strcpy(command, app.argv[0]);
1864             len = arg0Len;
1865             if(projectView.fileName)
1866             {
1867                strcat(command, " ");
1868                len++;
1869                ReplaceSpaces(command + len, projectView.fileName);
1870             }
1871          }
1872          if(restart)
1873          {
1874             for(w = ide.firstChild; w; w = w.next)
1875                if(w.isActiveClient && w.isDocument)
1876                   w.modifiedDocument = false;
1877             projectView.modifiedDocument = false;
1878          }
1879       }
1880       else
1881       {
1882          for(w = ide.firstChild; w; w = w.next)
1883          {
1884             if(w.isActiveClient && w.isDocument)
1885             {
1886                if(!w.CloseConfirmation(true))
1887                {
1888                   restart = false;
1889                   break;
1890                }
1891                if(w.fileName)
1892                {
1893                   const char * name = w.fileName;
1894                   len++;
1895                   for(j = 0; (ch = name[j]); j++)
1896                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1897                }
1898             }
1899          }
1900
1901          if(restart)
1902          {
1903             command = new char[len + 1];
1904             strcpy(command, app.argv[0]);
1905             len = arg0Len;
1906
1907             for(w = ide.firstChild; w; w = w.next)
1908             {
1909                if(w.isActiveClient && w.isDocument)
1910                {
1911                   const char * name = w.fileName;
1912                   if(name)
1913                   {
1914                      strcat(command, " ");
1915                      len++;
1916                      ReplaceSpaces(command + len, name);
1917                      len = (int)strlen(command);
1918                   }
1919                }
1920             }
1921          }
1922          if(restart)
1923          {
1924             for(w = ide.firstChild; w; w = w.next)
1925                if(w.isActiveClient && w.isDocument)
1926                   w.modifiedDocument = false;
1927          }
1928       }
1929       if(restart)
1930       {
1931          settings.language = code;
1932          settingsContainer.Save();
1933
1934          setEcereLanguageInWinRegEnvironment(code);
1935
1936          if(eClass_IsDerived(app._class, class(GuiApplication)))
1937          {
1938             GuiApplication guiApp = (GuiApplication)app;
1939             guiApp.desktop.Destroy(0);
1940          }
1941       }
1942    }
1943    else
1944    {
1945       int i;
1946       for(i = 1; i < app.argc; i++)
1947       {
1948          const char * arg = app.argv[i];
1949          len++;
1950          for(j = 0; (ch = arg[j]); j++)
1951             len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1952       }
1953
1954       command = new char[len + 1];
1955       strcpy(command, app.argv[0]);
1956       len = arg0Len;
1957       for(i = 1; i < app.argc; i++)
1958       {
1959          strcat(command, " ");
1960          len++;
1961          ReplaceSpaces(command + len, app.argv[i]);
1962          len = (int)strlen(command);
1963       }
1964    }
1965
1966    if(restart)
1967    {
1968       SetEnvironment("ECERE_LANGUAGE", code);
1969       if(wait)
1970          ExecuteWait(command);
1971       else
1972          Execute(command);
1973    }
1974    delete command;
1975    return restart;
1976 }
1977 #endif