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