ide; implemented a way to detect drastic reduction if global settings file size.
[sdk] / ide / src / IDESettings.ec
1 #ifdef ECERE_STATIC
2 import static "ecere"
3 #else
4 import "ecere"
5 #endif
6
7 import "StringsBox"
8
9 import "OldIDESettings"
10
11 define MaxRecent = 9;
12
13 enum DirTypes { includes, libraries, executables };
14
15 define defaultCompilerName = "Default";
16
17 define defaultObjDirExpression = "obj/$(CONFIG).$(PLATFORM)$(COMPILER_SUFFIX)$(DEBUG_SUFFIX)";
18
19 char * settingsDirectoryNames[DirTypes] = 
20 {
21    "Include Files",
22    "Library Files",
23    "Executable Files"
24 };
25
26 enum GlobalSettingsChange { none, editorSettings, projectOptions, compilerSettings };
27
28 enum PathRelationship { unrelated, identical, siblings, subPath, parentPath, insuficientInput, pathEmpty, toEmpty, pathNull, toNull, bothEmpty, bothNull };
29 PathRelationship eString_PathRelated(char * path, char * to, char * pathDiff)
30 {
31    PathRelationship result;
32    if(pathDiff) *pathDiff = '\0';
33    if(path && *path && to && *to)
34    {
35       char toPart[MAX_FILENAME], toRest[MAX_LOCATION];
36       char pathPart[MAX_FILENAME], pathRest[MAX_LOCATION];
37       strcpy(toRest, to);
38       strcpy(pathRest, path);
39       for(; toRest[0] && pathRest[0];)
40       {
41          SplitDirectory(toRest, toPart, toRest);
42          SplitDirectory(pathRest, pathPart, pathRest);
43          if(!fstrcmp(pathPart, toPart)) result = siblings;
44          else break;
45       }
46       if(result == siblings)
47       {
48          if(!*toRest && !*pathRest) result = identical;
49          else if(!*pathRest) result = parentPath;
50          else result = subPath;
51          if(pathDiff && result != identical) strcpy(pathDiff, *pathRest == '\0' ? toRest : pathRest);
52       }
53       else result = unrelated;
54    }
55    else
56    {
57       if(path && to)
58       {
59          if(!*path && !*to) result = bothEmpty;
60          else if(!*path) result = pathEmpty;
61          else result = toEmpty;
62       }
63       else if(!path && !to) result = bothNull;
64       else if(!path) result = pathNull;
65       else result = toNull;
66    }
67    return result;
68 }
69
70 char * CopyValidateMakefilePath(char * path)
71 {
72    const int map[]  =    {           0,           1,             2,             3,           4,                    5,                 6,            0,                   1,                    2,        7 };
73    const char * vars[] = { "$(MODULE)", "$(CONFIG)", "$(PLATFORM)", "$(COMPILER)", "$(TARGET)", "$(COMPILER_SUFFIX)", "$(DEBUG_SUFFIX)", "$(PROJECT)",  "$(CONFIGURATION)", "$(TARGET_PLATFORM)",(char *)0 };
74
75    char * copy = null;
76    if(path)
77    {
78       int len;
79       len = strlen(path);
80       copy = CopyString(path);
81       if(len)
82       {
83          int c;
84          char * tmp = copy;
85          char * start = tmp;
86          Array<char *> parts { };
87          
88          for(c=0; c<len; c++)
89          {
90             if(tmp[c] == '$')
91             {
92                int v;
93                for(v=0; vars[v]; v++)
94                {
95                   if(SearchString(&tmp[c], 0, vars[v], false, false) == &tmp[c])
96                   {
97                      tmp[c] = '\0';
98                      parts.Add(start);
99                      parts.Add(vars[map[v]]);
100                      c += strlen(vars[v]);
101                      start = &tmp[c];
102                      c--;
103                      break;
104                   }
105                }
106             }
107          }
108          if(start[0])
109             parts.Add(start);
110
111          if(parts.count)
112          {
113             /*int c, */len = 0;
114             for(c=0; c<parts.count; c++) len += strlen(parts[c]);
115             copy = new char[++len];
116             copy[0] = '\0';
117             for(c=0; c<parts.count; c++) strcat(copy, parts[c]);
118          }
119          else
120             copy = null;
121          delete parts;
122          delete tmp;
123       }
124    }
125    return copy;
126 }
127
128 CompilerConfig MakeDefaultCompiler(char * name, bool readOnly)
129 {
130    CompilerConfig defaultCompiler
131    {
132       name,
133       readOnly,
134       gcc,
135       GetRuntimePlatform(),
136       1,
137       makeDefaultCommand,
138       ecpDefaultCommand,
139       eccDefaultCommand,
140       ecsDefaultCommand,
141       earDefaultCommand,
142       cppDefaultCommand,
143       ccDefaultCommand,
144       cxxDefaultCommand
145    };
146    incref defaultCompiler;
147    return defaultCompiler;
148 }
149
150 class IDESettingsContainer : GlobalSettings
151 {
152 #ifdef SETTINGS_TEST
153    settingsName = "ecereIDESettingsTest";
154 #else
155    settingsName = "ecereIDE";
156 #endif
157
158    virtual void OnLoad(GlobalSettingsData data);
159
160    char moduleLocation[MAX_LOCATION];
161
162 private:
163    FileSize settingsFileSize;
164
165    IDESettingsContainer()
166    {
167       char path[MAX_LOCATION];
168       char * start;
169       LocateModule(null, moduleLocation);
170       strcpy(path, moduleLocation);
171       StripLastDirectory(moduleLocation, moduleLocation);
172       ChangeCh(moduleLocation, '\\', '/');
173       // PortableApps.com directory structure
174       if((start = strstr(path, "\\App\\EcereSDK\\bin\\ide.exe")))
175       {
176          char configFilePath[MAX_LOCATION];
177          char defaultConfigFilePath[MAX_LOCATION];
178
179          start[0] = '\0';
180
181          strcpy(configFilePath, path);
182          PathCat(configFilePath, "Data");
183          PathCat(configFilePath, "ecereIDE.ini");
184
185          strcpy(defaultConfigFilePath, path);
186          PathCat(defaultConfigFilePath, "App");
187          PathCat(defaultConfigFilePath, "DefaultData");
188          PathCat(defaultConfigFilePath, "ecereIDE.ini");
189          
190          if(FileExists(defaultConfigFilePath))
191          {
192             if(!FileExists(configFilePath))
193             {
194                File f = FileOpen(defaultConfigFilePath, read);
195                f.CopyTo(configFilePath);
196                f.Flush();
197                delete f;
198             }
199             PathCat(path, "Data");
200             // the forced settings location will only be
201             // used if the running ide's path matches 
202             // the PortableApps.com directory structure
203             // and the default ini file is found in 
204             // the DefaultData directory
205             settingsLocation = path;
206             portable = true;
207          }
208       }
209    }
210
211    void OnAskReloadSettings()
212    {
213       /*if(MessageBox { type = YesNo, master = this,
214             text = "Global Settings Modified Externally",
215             contents = "The global settings were modified by another instance.\n"
216             "Would you like to reload them?" }.Modal() == Yes)*/
217       {
218          double o, n;
219          FileSize newSettingsFileSize;
220          FileGetSize(settingsFilePath, &newSettingsFileSize);
221          o = settingsFileSize;
222          n = newSettingsFileSize;
223          if(o - n < 2048)
224             Load();
225          else
226          {
227             GuiApplication app = ((GuiApplication)__thisModule.application);
228             Window w;
229             for(w = app.desktop.firstChild; w && (!w.created || !w.visible); w = w.next);
230             MessageBox { master = w, type = ok,
231                   text = "Global Settings Modified Externally",
232                   contents = "The global settings were modified by another process and a drastic shrinking of the settings file was detected.\n"
233                   "The new settings will not be loaded to prevent loss of your ide settings.\n"
234                   "Please check your settings file and make sure to save this IDE's global settings if your settings file has been compromised."
235                   }.Create();
236             //Save();
237          }
238       }
239    }
240
241    SettingsIOResult Load()
242    {
243       SettingsIOResult result = GlobalSettings::Load();
244       IDESettings data = (IDESettings)this.data;
245       CompilerConfig defaultCompiler = null;
246       if(!data)
247       {
248          this.data = IDESettings { };
249          if(dataOwner)
250             *dataOwner = this.data;
251
252          if(result == fileNotCompatibleWithDriver)
253          {
254             bool loaded;
255             OldIDESettings oldSettings { };
256             Close();
257             loaded = oldSettings.Load() == success;
258             oldSettings.Close();
259             if(loaded)
260             {
261                data = (IDESettings)this.data;
262
263                for(c : oldSettings.compilerConfigs)
264                   data.compilerConfigs.Add(c.Copy());
265
266                for(s : oldSettings.recentFiles) data.recentFiles.Add(CopyString(s));
267                for(s : oldSettings.recentProjects) data.recentProjects.Add(CopyString(s));
268
269                data.docDir = oldSettings.docDir;
270                data.ideFileDialogLocation = oldSettings.ideFileDialogLocation;
271                data.ideProjectFileDialogLocation = oldSettings.ideProjectFileDialogLocation;
272                data.useFreeCaret = oldSettings.useFreeCaret;
273                data.showLineNumbers = oldSettings.showLineNumbers;
274                data.caretFollowsScrolling = oldSettings.caretFollowsScrolling;
275                delete data.displayDriver; data.displayDriver = CopyString(oldSettings.displayDriver);
276                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
277                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
278                         
279                Save();
280                result = success;
281             }
282             delete oldSettings;
283          }
284          if(result == fileNotFound || !data)
285          {
286             data = (IDESettings)this.data;
287             data.useFreeCaret = true;
288             data.showLineNumbers = true;
289             data.caretFollowsScrolling = true;
290          }
291       }
292       // Ensure we have a default compiler
293       defaultCompiler = data.GetCompilerConfig(defaultCompilerName);
294       if(!defaultCompiler)
295       {
296          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
297          data.compilerConfigs.Add(defaultCompiler);
298       }
299
300       // We incref the compilers below, so reset refCount to 0
301       defaultCompiler._refCount = 0;
302
303       CloseAndMonitor();
304       FileGetSize(settingsFilePath, &settingsFileSize);
305       if(data.compilerConfigs)
306       {
307          for(c : data.compilerConfigs)
308          {
309             CompilerConfig compiler = c;
310             char * cxxCommand = compiler.cxxCommand;
311             if(!cxxCommand || !cxxCommand[0])
312                compiler.cxxCommand = cxxDefaultCommand;
313             incref compiler;
314          }
315       }
316       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
317          data.ManagePortablePaths(moduleLocation, true);
318       data.ForcePathSeparatorStyle(true);
319       OnLoad(data);
320       return result;
321    }
322
323    SettingsIOResult Save()
324    {
325       SettingsIOResult result;
326
327       IDESettings data = (IDESettings)this.data;
328       Platform runtimePlatform = GetRuntimePlatform();
329       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
330          data.ManagePortablePaths(moduleLocation, false);
331       data.ForcePathSeparatorStyle(true);
332       result = GlobalSettings::Save();
333       if(result != success)
334          PrintLn("Error saving IDE settings");
335       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
336          data.ManagePortablePaths(moduleLocation, true);
337       CloseAndMonitor();
338       FileGetSize(settingsFilePath, &settingsFileSize);
339       return result;
340    }
341 }
342
343 class IDESettings : GlobalSettingsData
344 {
345 public:
346    List<CompilerConfig> compilerConfigs { };
347    Array<String> recentFiles { };
348    Array<String> recentProjects { };
349    property char * docDir
350    {
351       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
352       get { return docDir ? docDir : ""; }
353       isset { return docDir && docDir[0]; }
354    }
355    property char * ideFileDialogLocation
356    {
357       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
358       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
359       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
360    }
361    property char * ideProjectFileDialogLocation
362    {
363       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
364       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
365       isset { return ideProjectFileDialogLocation && ideProjectFileDialogLocation[0]; }
366    }
367    bool useFreeCaret;
368    bool showLineNumbers;
369    bool caretFollowsScrolling;
370    char * displayDriver;
371    
372    // TODO: Classify settings
373    //EditorSettings editor { };
374
375    property char * projectDefaultTargetDir
376    {
377       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
378       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
379       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
380    }
381    property char * projectDefaultIntermediateObjDir
382    {
383       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
384       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
385       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
386    }
387
388    property char * compilerConfigsDir
389    {
390       set { delete compilerConfigsDir; if(value && value[0]) compilerConfigsDir = CopyString(value); }
391       get { return compilerConfigsDir ? compilerConfigsDir : ""; }
392       isset { return compilerConfigsDir && compilerConfigsDir[0]; }
393    }
394
395    property char * defaultCompiler
396    {
397       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
398       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
399       isset { return defaultCompiler && defaultCompiler[0]; }
400    }
401
402 private:
403    char * docDir;
404    char * ideFileDialogLocation;
405    char * ideProjectFileDialogLocation;
406    char * projectDefaultTargetDir;
407    char * projectDefaultIntermediateObjDir;
408    char * compilerConfigsDir;
409    char * defaultCompiler;
410
411    CompilerConfig GetCompilerConfig(String compilerName)
412    {
413       char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
414       CompilerConfig compilerConfig = null;
415       for(compiler : compilerConfigs)
416       {
417          if(!strcmp(compiler.name, name))
418          {
419             compilerConfig = compiler;
420             break;
421          }
422       }
423       if(!compilerConfig && compilerConfigs.count)
424          compilerConfig = compilerConfigs.firstIterator.data;
425       if(compilerConfig)
426          incref compilerConfig;
427       return compilerConfig;
428    }
429
430    ~IDESettings()
431    {
432       compilerConfigs.Free();
433       delete compilerConfigs;
434       recentFiles.Free();
435       delete recentFiles;
436       recentProjects.Free();
437       delete recentProjects;
438       delete docDir;
439    
440       delete projectDefaultTargetDir;
441       delete projectDefaultIntermediateObjDir;
442       delete compilerConfigsDir;
443       delete defaultCompiler;
444
445       delete ideFileDialogLocation;
446       delete ideProjectFileDialogLocation;
447       delete displayDriver;
448    }
449
450    void ForcePathSeparatorStyle(bool unixStyle)
451    {
452       char from, to;
453       if(unixStyle)
454          from = '\\', to = '/';
455       else
456          from = '/', to = '\\';
457       if(compilerConfigs && compilerConfigs.count)
458       {
459          int i;
460          for(config : compilerConfigs)
461          {
462             if(config.includeDirs && config.includeDirs.count)
463             {
464                for(i = 0; i < config.includeDirs.count; i++)
465                {
466                   if(config.includeDirs[i] && config.includeDirs[i][0])
467                      ChangeCh(config.includeDirs[i], from, to);
468                }
469             }
470             if(config.libraryDirs && config.libraryDirs.count)
471             {
472                for(i = 0; i < config.libraryDirs.count; i++)
473                {
474                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
475                      ChangeCh(config.libraryDirs[i], from, to);
476                }
477             }
478             if(config.executableDirs && config.executableDirs.count)
479             {
480                for(i = 0; i < config.executableDirs.count; i++)
481                {
482                   if(config.executableDirs[i] && config.executableDirs[i][0])
483                      ChangeCh(config.executableDirs[i], from, to);
484                }
485             }
486          }
487       }
488       if(recentFiles && recentFiles.count)
489       {
490          int c;
491          for(c = 0; c < recentFiles.count; c++)
492          {
493             if(recentFiles[c] && recentFiles[c][0])
494                ChangeCh(recentFiles[c], from, to);
495          }
496       }
497       if(recentProjects && recentProjects.count)
498       {
499          int c;
500          for(c = 0; c < recentProjects.count; c++)
501          {
502             if(recentProjects[c] && recentProjects[c][0])
503                ChangeCh(recentProjects[c], from, to);
504          }
505       }
506       if(docDir && docDir[0])
507          ChangeCh(docDir, from, to);
508       if(ideFileDialogLocation && ideFileDialogLocation[0])
509          ChangeCh(ideFileDialogLocation, from, to);
510       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
511          ChangeCh(ideProjectFileDialogLocation, from, to);
512
513       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
514          ChangeCh(projectDefaultTargetDir, from, to);
515       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
516          ChangeCh(projectDefaultIntermediateObjDir, from, to);
517
518       if(compilerConfigsDir && compilerConfigsDir[0])
519          ChangeCh(compilerConfigsDir, from, to);
520    }
521
522    void ManagePortablePaths(char * location, bool makeAbsolute)
523    {
524       int c;
525       if(compilerConfigs && compilerConfigs.count)
526       {
527          for(config : compilerConfigs)
528          {
529             DirTypes t;
530             for(t = 0; t < DirTypes::enumSize; t++)
531             {
532                Array<String> dirs = null;
533                if(t == executables) dirs = config.executableDirs;
534                else if(t == includes) dirs = config.includeDirs;
535                else if(t == libraries) dirs = config.libraryDirs;
536                if(dirs && dirs.count)
537                {
538                   for(c = 0; c < dirs.count; c++)
539                   {
540                      if(dirs[c] && dirs[c][0])
541                         dirs[c] = UpdatePortablePath(dirs[c], location, makeAbsolute);
542                   }
543                }
544             }
545          }
546       }
547       if(recentFiles && recentFiles.count)
548       {
549          for(c = 0; c < recentFiles.count; c++)
550          {
551             if(recentFiles[c] && recentFiles[c][0])
552                recentFiles[c] = UpdatePortablePath(recentFiles[c], location, makeAbsolute);
553          }
554       }
555       if(recentProjects && recentProjects.count)
556       {
557          for(c = 0; c < recentProjects.count; c++)
558          {
559             if(recentProjects[c] && recentProjects[c][0])
560                recentProjects[c] = UpdatePortablePath(recentProjects[c], location, makeAbsolute);
561          }
562       }
563       if(docDir && docDir[0])
564          docDir = UpdatePortablePath(docDir, location, makeAbsolute);
565       if(ideFileDialogLocation && ideFileDialogLocation[0])
566          ideFileDialogLocation = UpdatePortablePath(ideFileDialogLocation, location, makeAbsolute);
567       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
568          ideProjectFileDialogLocation = UpdatePortablePath(ideProjectFileDialogLocation, location, makeAbsolute);
569
570       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
571          projectDefaultTargetDir = UpdatePortablePath(projectDefaultTargetDir, location, makeAbsolute);
572       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
573          projectDefaultIntermediateObjDir = UpdatePortablePath(projectDefaultIntermediateObjDir, location, makeAbsolute);
574
575       if(compilerConfigsDir && compilerConfigsDir[0])
576          compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
577    }
578
579    char * UpdatePortablePath(char * path, char * location, bool makeAbsolute)
580    {
581       char * output;
582       if(makeAbsolute)
583       {
584          char p[MAX_LOCATION];
585          strcpy(p, location);
586          PathCatSlash(p, path);
587          delete path;
588          output = CopyString(p);
589       }
590       else
591       {
592          PathRelationship rel = eString_PathRelated(path, location, null);
593          if(rel == subPath || rel == identical)
594          {
595             char p[MAX_LOCATION];
596             MakePathRelative(path, location, p);
597             if(!*p) strcpy(p, "./");
598             else ChangeCh(p, '\\', '/');
599             delete path;
600             output = CopyString(p);
601          }
602          else
603             output = path;
604       }
605       return output;
606    }
607
608    void AddRecentFile(char * fileName)
609    {
610       int c;
611       char * filePath = CopyString(fileName);
612       ChangeCh(filePath, '\\', '/');
613       for(c = 0; c<recentFiles.count; c++)
614       {
615          if(recentFiles[c] && !fstrcmp(recentFiles[c], filePath))
616          {
617             recentFiles.Delete((void *)&recentFiles[c]);
618             c--;
619          }
620       }
621       while(recentFiles.count >= MaxRecent)
622          recentFiles.Delete(recentFiles.GetLast());
623       recentFiles.Insert(null, filePath);
624       //UpdateRecentMenus(owner);
625    }
626
627    void AddRecentProject(char * projectName)
628    {
629       int c;
630       char * filePath = CopyString(projectName);
631       ChangeCh(filePath, '\\', '/');
632       for(c = 0; c<recentProjects.count; c++)
633       {
634          if(recentProjects[c] && !fstrcmp(recentProjects[c], filePath))
635          {
636             recentProjects.Delete((void *)&recentProjects[c]);
637             c--;
638          }
639       }
640       while(recentProjects.count >= MaxRecent)
641          recentProjects.Delete(recentProjects.GetLast());
642       recentProjects.Insert(null, filePath);
643       //UpdateRecentMenus(owner);
644    }
645 }
646
647 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
648 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
649 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
650 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
651 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
652 // TODO: i18n with Array
653 static Array<String> compilerTypeLongNames
654 { [
655    $"GNU Compiler Collection (GCC) / GNU Make",
656    $"Tiny C Compiler / GNU Make",
657    $"Portable C Compiler / GNU Make",
658    $"Microsoft Visual Studio 2005 (8.0) Compiler",
659    $"Microsoft Visual Studio 2008 (9.0) Compiler",
660    $"Microsoft Visual Studio 2010 (10.0) Compiler"
661 ] };
662 const CompilerType firstCompilerType = gcc;
663 const CompilerType lastCompilerType = vs10;
664 public enum CompilerType
665 {
666    gcc, tcc, pcc, vs8, vs9, vs10;
667
668    property bool isVC
669    {
670       get { return this == vs8 || this == vs9 || this == vs10; }
671    }
672
673    property char *
674    {
675       get { return OnGetString(null, null, null); }
676       set
677       {  
678          if(value)
679          {
680             Platform c;
681             for(c = firstCompilerType; c <= lastCompilerType; c++)
682                if(!strcmpi(value, compilerTypeNames[c]))
683                   return c;
684          }
685          return gcc;
686       }
687    };
688
689    property char * longName { get { return OnGetString(null, (void*)1, null); } };
690    property char * versionString { get { return OnGetString(null, (void*)2, null); } };
691    property char * yearString { get { return OnGetString(null, (void*)3, null); } };
692    property char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
693    property char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
694
695    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
696    {
697       if(this >= firstCompilerType && this <= lastCompilerType)
698       {
699          if(tempString)
700             strcpy(tempString, compilerTypeNames[this]);
701          if(fieldData == null)
702             return compilerTypeNames[this];
703          else if(fieldData == (void*)1)
704             return compilerTypeLongNames[this];
705          else if(fieldData == (void*)2)
706             return compilerTypeVersionString[this];
707          else if(fieldData == (void*)3)
708             return compilerTypeYearString[this];
709          else if(fieldData == (void*)4)
710             return compilerTypeProjectFileExtension[this];
711          else if(fieldData == (void*)5)
712             return compilerTypeSolutionFileVersionString[this];
713       }
714       return null;
715    }
716 };
717
718 class CompilerConfig
719 {
720    class_no_expansion;
721
722    numJobs = 1;
723 public:
724    property char * name
725    {
726       set
727       {
728          delete name;
729          if(value)
730             name = CopyString(value);
731       }
732       get { return name; }
733    }
734    bool readOnly;
735    CompilerType type;
736    Platform targetPlatform;
737    int numJobs;
738    property char * makeCommand
739    {
740       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
741       get { return makeCommand; }
742       isset { return makeCommand && makeCommand[0]; }
743    }
744    property char * ecpCommand
745    {
746       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
747       get { return ecpCommand; }
748       isset { return ecpCommand && ecpCommand[0]; }
749    }
750    property char * eccCommand
751    {
752       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
753       get { return eccCommand; }
754       isset { return eccCommand && eccCommand[0]; }
755    }
756    property char * ecsCommand
757    {
758       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
759       get { return ecsCommand; }
760       isset { return ecsCommand && ecsCommand[0]; }
761    }
762    property char * earCommand
763    {
764       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
765       get { return earCommand; }
766       isset { return earCommand && earCommand[0]; }
767    }
768    property char * cppCommand
769    {
770       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
771       get { return cppCommand; }
772       isset { return cppCommand && cppCommand[0]; }
773    }
774    property char * ccCommand
775    {
776       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
777       get { return ccCommand; }
778       isset { return ccCommand && ccCommand[0]; }
779    }
780    property char * cxxCommand
781    {
782       set { delete cxxCommand; if(value && value[0]) cxxCommand = CopyString(value); }
783       get { return cxxCommand; }
784       isset { return cxxCommand && cxxCommand[0]; }
785    }
786    property char * execPrefixCommand // <-- old name for json ide settings file compatibility
787    {
788       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
789       get { return executableLauncher; }
790       isset { return executableLauncher && executableLauncher[0]; }
791    }
792    // TODO: implement CompilerConfig::windresCommand
793    bool ccacheEnabled;
794    bool distccEnabled;
795    // deprecated
796    property bool supportsBitDepth { set { } get { return true; } isset { return false; } }
797
798    property char * distccHosts
799    {
800       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
801       get { return distccHosts; }
802       isset { return distccHosts && distccHosts[0]; }
803    }
804    property char * gccPrefix // <-- old name for json ide settings file compatibility
805    {
806       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
807       get { return gnuToolchainPrefix; }
808       isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
809    }
810    property char * sysroot
811    {
812       set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
813       get { return sysroot; }
814       isset { return sysroot && sysroot[0]; }
815    }
816    property Array<String> includeDirs
817    {
818       set
819       {
820          includeDirs.Free();
821          if(value)
822          {
823             delete includeDirs;
824             includeDirs = value;
825          }
826       }
827       get { return includeDirs; }
828       isset { return includeDirs.count != 0; }
829    }
830    property Array<String> libraryDirs
831    {
832       set
833       {
834          libraryDirs.Free();
835          if(value)
836          {
837             delete libraryDirs;
838             libraryDirs = value;
839          }
840       }
841       get { return libraryDirs; }
842       isset { return libraryDirs.count != 0; }
843    }
844    property Array<String> executableDirs
845    {
846       set
847       {
848          executableDirs.Free();
849          if(value)
850          {
851             delete executableDirs;
852             executableDirs = value;
853          }
854       }
855       get { return executableDirs; }
856       isset { return executableDirs.count != 0; }
857    }
858    property Array<NamedString> environmentVars
859    {
860       set
861       {
862          environmentVars.Free();
863          if(value)
864          {
865             delete environmentVars;
866             environmentVars = value;
867          }
868       }
869       get { return environmentVars; }
870       isset { return environmentVars.count != 0; }
871    }
872    property Array<String> prepDirectives
873    {
874       set
875       {
876          prepDirectives.Free();
877          if(value)
878          {
879             delete prepDirectives;
880             prepDirectives = value;
881          }
882       }
883       get { return prepDirectives; }
884       isset { return prepDirectives.count != 0; }
885    }
886    property Array<String> excludeLibs
887    {
888       set
889       {
890          excludeLibs.Free();
891          if(value)
892          {
893             delete excludeLibs;
894             excludeLibs = value;
895          }
896       }
897       get { return excludeLibs; }
898       isset { return excludeLibs.count != 0; }
899    }
900    property Array<String> compilerFlags
901    {
902       set
903       {
904          compilerFlags.Free();
905          if(value)
906          {
907             delete compilerFlags;
908             compilerFlags = value;
909          }
910       }
911       get { return compilerFlags; }
912       isset { return compilerFlags.count != 0; }
913    }
914    property Array<String> linkerFlags
915    {
916       set
917       {
918          linkerFlags.Free();
919          if(value)
920          {
921             delete linkerFlags;
922             linkerFlags = value;
923          }
924       }
925       get { return linkerFlags; }
926       isset { return linkerFlags.count != 0; }
927    }
928 private:
929    Array<String> includeDirs { };
930    Array<String> libraryDirs { };
931    Array<String> executableDirs { };
932    // TODO: Can JSON parse and serialize maps?
933    //EnvironmentVariables { };
934    Array<NamedString> environmentVars { };
935    Array<String> prepDirectives { };
936    Array<String> excludeLibs { };
937    Array<String> compilerFlags { };
938    Array<String> linkerFlags { };
939    char * name;
940    char * makeCommand;
941    char * ecpCommand;
942    char * eccCommand;
943    char * ecsCommand;
944    char * earCommand;
945    char * cppCommand;
946    char * ccCommand;
947    char * cxxCommand;
948    char * executableLauncher;
949    char * distccHosts;
950    char * gnuToolchainPrefix;
951    char * sysroot;
952    /*union
953    {
954       struct { Array<String> includes, libraries, executables; };
955       Array<String> dirs[DirTypes];
956    }*/
957
958    ~CompilerConfig()
959    {
960       delete name;
961       delete ecpCommand;
962       delete eccCommand;
963       delete ecsCommand;
964       delete earCommand;
965       delete cppCommand;
966       delete ccCommand;
967       delete cxxCommand;
968       delete makeCommand;
969       delete executableLauncher;
970       delete distccHosts;
971       delete gnuToolchainPrefix;
972       delete sysroot;
973       if(environmentVars) environmentVars.Free();
974       if(includeDirs) { includeDirs.Free(); }
975       if(libraryDirs) { libraryDirs.Free(); }
976       if(executableDirs) { executableDirs.Free(); }
977       if(prepDirectives) { prepDirectives.Free(); }
978       if(excludeLibs) { excludeLibs.Free(); }
979       if(compilerFlags) { compilerFlags.Free(); }
980       if(linkerFlags) { linkerFlags.Free(); }
981    }
982    CompilerConfig Copy()
983    {
984       CompilerConfig copy
985       {
986          name,
987          readOnly,
988          type,
989          targetPlatform,
990          numJobs,
991          makeCommand,
992          ecpCommand,
993          eccCommand,
994          ecsCommand,
995          earCommand,
996          cppCommand,
997          ccCommand,
998          cxxCommand,
999          executableLauncher,
1000          ccacheEnabled,
1001          distccEnabled,
1002          false,
1003          distccHosts,
1004          gnuToolchainPrefix,
1005          sysroot
1006       };
1007       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
1008       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
1009       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
1010       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
1011       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
1012       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
1013       for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
1014       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
1015
1016       incref copy;
1017       return copy;
1018    }
1019 }