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