63978911bfba8ab4d3e3098d96e26b2470b162c3
[sdk] / ide / src / IDESettings.ec
1 #ifdef ECERE_STATIC
2 public import static "ecere"
3 #else
4 public 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 // This function cannot accept same pointer for source and output
27 // todo: rename ReplaceSpaces to EscapeSpaceAndSpecialChars or something
28 void ReplaceSpaces(char * output, char * source)
29 {
30    int c, dc;
31    char ch, pch = 0;
32
33    for(c = 0, dc = 0; (ch = source[c]); c++, dc++)
34    {
35       if(ch == ' ') output[dc++] = '\\';
36       if(ch == '\"') output[dc++] = '\\';
37       if(ch == '&') output[dc++] = '\\';
38       if(pch != '$')
39       {
40          if(ch == '(' || ch == ')') output[dc++] = '\\';
41          pch = ch;
42       }
43       else if(ch == ')')
44          pch = 0;
45       output[dc] = ch;
46    }
47    output[dc] = '\0';
48 }
49
50
51 enum GlobalSettingsChange { none, editorSettings, projectOptions, compilerSettings };
52
53 enum PathRelationship { unrelated, identical, siblings, subPath, parentPath, insuficientInput, pathEmpty, toEmpty, pathNull, toNull, bothEmpty, bothNull };
54 PathRelationship eString_PathRelated(char * path, char * to, char * pathDiff)
55 {
56    PathRelationship result;
57    if(pathDiff) *pathDiff = '\0';
58    if(path && *path && to && *to)
59    {
60       char toPart[MAX_FILENAME], toRest[MAX_LOCATION];
61       char pathPart[MAX_FILENAME], pathRest[MAX_LOCATION];
62       strcpy(toRest, to);
63       strcpy(pathRest, path);
64       for(; toRest[0] && pathRest[0];)
65       {
66          SplitDirectory(toRest, toPart, toRest);
67          SplitDirectory(pathRest, pathPart, pathRest);
68          if(!fstrcmp(pathPart, toPart)) result = siblings;
69          else break;
70       }
71       if(result == siblings)
72       {
73          if(!*toRest && !*pathRest) result = identical;
74          else if(!*pathRest) result = parentPath;
75          else result = subPath;
76          if(pathDiff && result != identical) strcpy(pathDiff, *pathRest == '\0' ? toRest : pathRest);
77       }
78       else result = unrelated;
79    }
80    else
81    {
82       if(path && to)
83       {
84          if(!*path && !*to) result = bothEmpty;
85          else if(!*path) result = pathEmpty;
86          else result = toEmpty;
87       }
88       else if(!path && !to) result = bothNull;
89       else if(!path) result = pathNull;
90       else result = toNull;
91    }
92    return result;
93 }
94
95 char * CopyValidateMakefilePath(char * path)
96 {
97    const int map[]  =    {           0,           1,             2,             3,           4,                    5,                 6,            0,                   1,                    2,        7 };
98    const char * vars[] = { "$(MODULE)", "$(CONFIG)", "$(PLATFORM)", "$(COMPILER)", "$(TARGET)", "$(COMPILER_SUFFIX)", "$(DEBUG_SUFFIX)", "$(PROJECT)",  "$(CONFIGURATION)", "$(TARGET_PLATFORM)",(char *)0 };
99
100    char * copy = null;
101    if(path)
102    {
103       int len;
104       len = strlen(path);
105       copy = CopyString(path);
106       if(len)
107       {
108          int c;
109          char * tmp = copy;
110          char * start = tmp;
111          Array<char *> parts { };
112
113          for(c=0; c<len; c++)
114          {
115             if(tmp[c] == '$')
116             {
117                int v;
118                for(v=0; vars[v]; v++)
119                {
120                   if(SearchString(&tmp[c], 0, vars[v], false, false) == &tmp[c])
121                   {
122                      tmp[c] = '\0';
123                      parts.Add(start);
124                      parts.Add(vars[map[v]]);
125                      c += strlen(vars[v]);
126                      start = &tmp[c];
127                      c--;
128                      break;
129                   }
130                }
131             }
132          }
133          if(start[0])
134             parts.Add(start);
135
136          if(parts.count)
137          {
138             /*int c, */len = 0;
139             for(c=0; c<parts.count; c++) len += strlen(parts[c]);
140             copy = new char[++len];
141             copy[0] = '\0';
142             for(c=0; c<parts.count; c++) strcat(copy, parts[c]);
143          }
144          else
145             copy = null;
146          delete parts;
147          delete tmp;
148       }
149    }
150    return copy;
151 }
152
153 void ValidPathBufCopy(char *output, char *input)
154 {
155 #ifdef __WIN32__
156    bool volumePath = false;
157 #endif
158    strcpy(output, input);
159    TrimLSpaces(output, output);
160    TrimRSpaces(output, output);
161    MakeSystemPath(output);
162 #ifdef __WIN32__
163    if(output[0] && output[1] == ':')
164    {
165       output[1] = '_';
166       volumePath = true;
167    }
168 #endif
169    {
170       char * chars = "*|:\",<>?";
171       char ch, * s = output, * o = output;
172       while((ch = *s++)) { if(!strchr(chars, ch)) *o++ = ch; }
173       *o = '\0';
174    }
175 #ifdef __WIN32__
176    if(volumePath && output[0])
177       output[1] = ':';
178 #endif
179 }
180
181 void RemoveTrailingPathSeparator(char *path)
182 {
183    int len;
184    len = strlen(path);
185    if(len>1 && path[len-1] == DIR_SEP)
186       path[--len] = '\0';
187 }
188
189 void BasicValidatePathBoxPath(PathBox pathBox)
190 {
191    char path[MAX_LOCATION];
192    ValidPathBufCopy(path, pathBox.path);
193    RemoveTrailingPathSeparator(path);
194    pathBox.path = path;
195 }
196
197 CompilerConfig MakeDefaultCompiler(char * name, bool readOnly)
198 {
199    CompilerConfig defaultCompiler
200    {
201       name,
202       readOnly,
203       gcc,
204       GetRuntimePlatform(),
205       1,
206       makeDefaultCommand,
207       ecpDefaultCommand,
208       eccDefaultCommand,
209       ecsDefaultCommand,
210       earDefaultCommand,
211       cppDefaultCommand,
212       ccDefaultCommand,
213       cxxDefaultCommand
214    };
215    incref defaultCompiler;
216    return defaultCompiler;
217 }
218
219 class IDESettingsContainer : GlobalSettings
220 {
221 #ifdef SETTINGS_TEST
222    settingsName = "ecereIDESettingsTest";
223 #else
224    settingsName = "ecereIDE";
225 #endif
226
227    virtual void OnLoad(GlobalSettingsData data);
228
229    char moduleLocation[MAX_LOCATION];
230
231 private:
232    FileSize settingsFileSize;
233
234    IDESettingsContainer()
235    {
236       char path[MAX_LOCATION];
237       char * start;
238       LocateModule(null, moduleLocation);
239       strcpy(path, moduleLocation);
240       StripLastDirectory(moduleLocation, moduleLocation);
241       ChangeCh(moduleLocation, '\\', '/');
242       // PortableApps.com directory structure
243       if((start = strstr(path, "\\App\\EcereSDK\\bin\\ide.exe")))
244       {
245          char configFilePath[MAX_LOCATION];
246          char defaultConfigFilePath[MAX_LOCATION];
247
248          start[0] = '\0';
249
250          strcpy(configFilePath, path);
251          PathCat(configFilePath, "Data");
252          PathCat(configFilePath, "ecereIDE.ini");
253
254          strcpy(defaultConfigFilePath, path);
255          PathCat(defaultConfigFilePath, "App");
256          PathCat(defaultConfigFilePath, "DefaultData");
257          PathCat(defaultConfigFilePath, "ecereIDE.ini");
258
259          if(FileExists(defaultConfigFilePath))
260          {
261             if(!FileExists(configFilePath))
262             {
263                File f = FileOpen(defaultConfigFilePath, read);
264                f.CopyTo(configFilePath);
265                f.Flush();
266                delete f;
267             }
268             PathCat(path, "Data");
269             // the forced settings location will only be
270             // used if the running ide's path matches
271             // the PortableApps.com directory structure
272             // and the default ini file is found in
273             // the DefaultData directory
274             settingsLocation = path;
275             portable = true;
276          }
277       }
278    }
279
280    void OnAskReloadSettings()
281    {
282       /*if(MessageBox { type = YesNo, master = this,
283             text = "Global Settings Modified Externally",
284             contents = "The global settings were modified by another instance.\n"
285             "Would you like to reload them?" }.Modal() == Yes)*/
286       {
287          double o, n;
288          FileSize newSettingsFileSize;
289          FileGetSize(settingsFilePath, &newSettingsFileSize);
290          o = settingsFileSize;
291          n = newSettingsFileSize;
292          if(o - n < 2048)
293             Load();
294          else
295          {
296             GuiApplication app = ((GuiApplication)__thisModule.application);
297             Window w;
298             for(w = app.desktop.firstChild; w && (!w.created || !w.visible); w = w.next);
299             MessageBox { master = w, type = ok,
300                   text = "Global Settings Modified Externally",
301                   contents = "The global settings were modified by another process and a drastic shrinking of the settings file was detected.\n"
302                   "The new settings will not be loaded to prevent loss of your ide settings.\n"
303                   "Please check your settings file and make sure to save this IDE's global settings if your settings file has been compromised."
304                   }.Create();
305             //Save();
306          }
307       }
308    }
309
310    SettingsIOResult Load()
311    {
312       SettingsIOResult result = GlobalSettings::Load();
313       IDESettings data = (IDESettings)this.data;
314       CompilerConfig defaultCompiler = null;
315       if(!data)
316       {
317          this.data = IDESettings { };
318          if(dataOwner)
319             *dataOwner = this.data;
320
321          if(result == fileNotCompatibleWithDriver)
322          {
323             bool loaded;
324             OldIDESettings oldSettings { };
325             Close();
326             loaded = oldSettings.Load() == success;
327             oldSettings.Close();
328             if(loaded)
329             {
330                data = (IDESettings)this.data;
331
332                for(c : oldSettings.compilerConfigs)
333                   data.compilerConfigs.Add(c.Copy());
334
335                for(s : oldSettings.recentFiles) data.recentFiles.Add(CopyString(s));
336                for(s : oldSettings.recentProjects) data.recentProjects.Add(CopyString(s));
337
338                data.docDir = oldSettings.docDir;
339                data.ideFileDialogLocation = oldSettings.ideFileDialogLocation;
340                data.ideProjectFileDialogLocation = oldSettings.ideProjectFileDialogLocation;
341                data.useFreeCaret = oldSettings.useFreeCaret;
342                data.showLineNumbers = oldSettings.showLineNumbers;
343                data.caretFollowsScrolling = oldSettings.caretFollowsScrolling;
344                delete data.displayDriver; data.displayDriver = CopyString(oldSettings.displayDriver);
345                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
346                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
347
348                Save();
349                result = success;
350             }
351             delete oldSettings;
352          }
353          if(result == fileNotFound || !data)
354          {
355             data = (IDESettings)this.data;
356             data.useFreeCaret = false; //true;
357             data.showLineNumbers = true;
358             data.caretFollowsScrolling = false; //true;
359          }
360       }
361       // Ensure we have a default compiler
362       defaultCompiler = data.GetCompilerConfig(defaultCompilerName);
363       if(!defaultCompiler)
364       {
365          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
366          data.compilerConfigs.Add(defaultCompiler);
367       }
368
369       // We incref the compilers below, so reset refCount to 0
370       defaultCompiler._refCount = 0;
371
372       CloseAndMonitor();
373       FileGetSize(settingsFilePath, &settingsFileSize);
374       if(data.compilerConfigs)
375       {
376          for(c : data.compilerConfigs)
377          {
378             CompilerConfig compiler = c;
379             char * cxxCommand = compiler.cxxCommand;
380             if(!cxxCommand || !cxxCommand[0])
381                compiler.cxxCommand = cxxDefaultCommand;
382             incref compiler;
383          }
384       }
385       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
386          data.ManagePortablePaths(moduleLocation, true);
387       data.ForcePathSeparatorStyle(true);
388       OnLoad(data);
389       return result;
390    }
391
392    SettingsIOResult Save()
393    {
394       SettingsIOResult result;
395
396       IDESettings data = (IDESettings)this.data;
397       Platform runtimePlatform = GetRuntimePlatform();
398       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
399          data.ManagePortablePaths(moduleLocation, false);
400       data.ForcePathSeparatorStyle(true);
401       result = GlobalSettings::Save();
402       if(result != success)
403          PrintLn("Error saving IDE settings");
404       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
405          data.ManagePortablePaths(moduleLocation, true);
406       CloseAndMonitor();
407       FileGetSize(settingsFilePath, &settingsFileSize);
408       return result;
409    }
410 }
411
412 class IDESettings : GlobalSettingsData
413 {
414 public:
415    List<CompilerConfig> compilerConfigs { };
416    Array<String> recentFiles { };
417    Array<String> recentProjects { };
418    property char * docDir
419    {
420       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
421       get { return docDir ? docDir : ""; }
422       isset { return docDir && docDir[0]; }
423    }
424    property char * ideFileDialogLocation
425    {
426       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
427       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
428       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
429    }
430    property char * ideProjectFileDialogLocation
431    {
432       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
433       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
434       isset { return ideProjectFileDialogLocation && ideProjectFileDialogLocation[0]; }
435    }
436    bool useFreeCaret;
437    bool showLineNumbers;
438    bool caretFollowsScrolling;
439    char * displayDriver;
440
441    // TODO: Classify settings
442    //EditorSettings editor { };
443
444    property char * projectDefaultTargetDir
445    {
446       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
447       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
448       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
449    }
450    property char * projectDefaultIntermediateObjDir
451    {
452       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
453       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
454       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
455    }
456
457    property char * compilerConfigsDir
458    {
459       set { delete compilerConfigsDir; if(value && value[0]) compilerConfigsDir = CopyString(value); }
460       get { return compilerConfigsDir ? compilerConfigsDir : ""; }
461       isset { return compilerConfigsDir && compilerConfigsDir[0]; }
462    }
463
464    property char * defaultCompiler
465    {
466       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
467       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
468       isset { return defaultCompiler && defaultCompiler[0]; }
469    }
470
471    property String language
472    {
473       set
474       {
475          delete language;
476          language = CopyString(value);
477       }
478       get { return language; }
479       isset { return language != null; }
480    }
481
482 private:
483    char * docDir;
484    char * ideFileDialogLocation;
485    char * ideProjectFileDialogLocation;
486    char * projectDefaultTargetDir;
487    char * projectDefaultIntermediateObjDir;
488    char * compilerConfigsDir;
489    char * defaultCompiler;
490    String language;
491
492    CompilerConfig GetCompilerConfig(String compilerName)
493    {
494       char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
495       CompilerConfig compilerConfig = null;
496       for(compiler : compilerConfigs)
497       {
498          if(!strcmp(compiler.name, name))
499          {
500             compilerConfig = compiler;
501             break;
502          }
503       }
504       if(!compilerConfig && compilerConfigs.count)
505          compilerConfig = compilerConfigs.firstIterator.data;
506       if(compilerConfig)
507          incref compilerConfig;
508       return compilerConfig;
509    }
510
511    ~IDESettings()
512    {
513       compilerConfigs.Free();
514       delete compilerConfigs;
515       recentFiles.Free();
516       delete recentFiles;
517       recentProjects.Free();
518       delete recentProjects;
519       delete docDir;
520
521       delete projectDefaultTargetDir;
522       delete projectDefaultIntermediateObjDir;
523       delete compilerConfigsDir;
524       delete defaultCompiler;
525
526       delete ideFileDialogLocation;
527       delete ideProjectFileDialogLocation;
528       delete displayDriver;
529    }
530
531    void ForcePathSeparatorStyle(bool unixStyle)
532    {
533       char from, to;
534       if(unixStyle)
535          from = '\\', to = '/';
536       else
537          from = '/', to = '\\';
538       if(compilerConfigs && compilerConfigs.count)
539       {
540          int i;
541          for(config : compilerConfigs)
542          {
543             if(config.includeDirs && config.includeDirs.count)
544             {
545                for(i = 0; i < config.includeDirs.count; i++)
546                {
547                   if(config.includeDirs[i] && config.includeDirs[i][0])
548                      ChangeCh(config.includeDirs[i], from, to);
549                }
550             }
551             if(config.libraryDirs && config.libraryDirs.count)
552             {
553                for(i = 0; i < config.libraryDirs.count; i++)
554                {
555                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
556                      ChangeCh(config.libraryDirs[i], from, to);
557                }
558             }
559             if(config.executableDirs && config.executableDirs.count)
560             {
561                for(i = 0; i < config.executableDirs.count; i++)
562                {
563                   if(config.executableDirs[i] && config.executableDirs[i][0])
564                      ChangeCh(config.executableDirs[i], from, to);
565                }
566             }
567          }
568       }
569       if(recentFiles && recentFiles.count)
570       {
571          int c;
572          for(c = 0; c < recentFiles.count; c++)
573          {
574             if(recentFiles[c] && recentFiles[c][0])
575                ChangeCh(recentFiles[c], from, to);
576          }
577       }
578       if(recentProjects && recentProjects.count)
579       {
580          int c;
581          for(c = 0; c < recentProjects.count; c++)
582          {
583             if(recentProjects[c] && recentProjects[c][0])
584                ChangeCh(recentProjects[c], from, to);
585          }
586       }
587       if(docDir && docDir[0])
588          ChangeCh(docDir, from, to);
589       if(ideFileDialogLocation && ideFileDialogLocation[0])
590          ChangeCh(ideFileDialogLocation, from, to);
591       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
592          ChangeCh(ideProjectFileDialogLocation, from, to);
593
594       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
595          ChangeCh(projectDefaultTargetDir, from, to);
596       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
597          ChangeCh(projectDefaultIntermediateObjDir, from, to);
598
599       if(compilerConfigsDir && compilerConfigsDir[0])
600          ChangeCh(compilerConfigsDir, from, to);
601    }
602
603    void ManagePortablePaths(char * location, bool makeAbsolute)
604    {
605       int c;
606       if(compilerConfigs && compilerConfigs.count)
607       {
608          for(config : compilerConfigs)
609          {
610             DirTypes t;
611             for(t = 0; t < DirTypes::enumSize; t++)
612             {
613                Array<String> dirs = null;
614                if(t == executables) dirs = config.executableDirs;
615                else if(t == includes) dirs = config.includeDirs;
616                else if(t == libraries) dirs = config.libraryDirs;
617                if(dirs && dirs.count)
618                {
619                   for(c = 0; c < dirs.count; c++)
620                   {
621                      if(dirs[c] && dirs[c][0])
622                         dirs[c] = UpdatePortablePath(dirs[c], location, makeAbsolute);
623                   }
624                }
625             }
626          }
627       }
628       if(recentFiles && recentFiles.count)
629       {
630          for(c = 0; c < recentFiles.count; c++)
631          {
632             if(recentFiles[c] && recentFiles[c][0])
633                recentFiles[c] = UpdatePortablePath(recentFiles[c], location, makeAbsolute);
634          }
635       }
636       if(recentProjects && recentProjects.count)
637       {
638          for(c = 0; c < recentProjects.count; c++)
639          {
640             if(recentProjects[c] && recentProjects[c][0])
641                recentProjects[c] = UpdatePortablePath(recentProjects[c], location, makeAbsolute);
642          }
643       }
644       if(docDir && docDir[0])
645          docDir = UpdatePortablePath(docDir, location, makeAbsolute);
646       if(ideFileDialogLocation && ideFileDialogLocation[0])
647          ideFileDialogLocation = UpdatePortablePath(ideFileDialogLocation, location, makeAbsolute);
648       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
649          ideProjectFileDialogLocation = UpdatePortablePath(ideProjectFileDialogLocation, location, makeAbsolute);
650
651       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
652          projectDefaultTargetDir = UpdatePortablePath(projectDefaultTargetDir, location, makeAbsolute);
653       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
654          projectDefaultIntermediateObjDir = UpdatePortablePath(projectDefaultIntermediateObjDir, location, makeAbsolute);
655
656       if(compilerConfigsDir && compilerConfigsDir[0])
657          compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
658    }
659
660    char * UpdatePortablePath(char * path, char * location, bool makeAbsolute)
661    {
662       char * output;
663       if(makeAbsolute)
664       {
665          char p[MAX_LOCATION];
666          strcpy(p, location);
667          PathCatSlash(p, path);
668          delete path;
669          output = CopyString(p);
670       }
671       else
672       {
673          PathRelationship rel = eString_PathRelated(path, location, null);
674          if(rel == subPath || rel == identical)
675          {
676             char p[MAX_LOCATION];
677             MakePathRelative(path, location, p);
678             if(!*p) strcpy(p, "./");
679             else ChangeCh(p, '\\', '/');
680             delete path;
681             output = CopyString(p);
682          }
683          else
684             output = path;
685       }
686       return output;
687    }
688
689    void AddRecentFile(char * fileName)
690    {
691       int c;
692       char * filePath = CopyString(fileName);
693       ChangeCh(filePath, '\\', '/');
694       for(c = 0; c<recentFiles.count; c++)
695       {
696          if(recentFiles[c] && !fstrcmp(recentFiles[c], filePath))
697          {
698             recentFiles.Delete((void *)&recentFiles[c]);
699             c--;
700          }
701       }
702       while(recentFiles.count >= MaxRecent)
703          recentFiles.Delete(recentFiles.GetLast());
704       recentFiles.Insert(null, filePath);
705       //UpdateRecentMenus(owner);
706    }
707
708    void AddRecentProject(char * projectName)
709    {
710       int c;
711       char * filePath = CopyString(projectName);
712       ChangeCh(filePath, '\\', '/');
713       for(c = 0; c<recentProjects.count; c++)
714       {
715          if(recentProjects[c] && !fstrcmp(recentProjects[c], filePath))
716          {
717             recentProjects.Delete((void *)&recentProjects[c]);
718             c--;
719          }
720       }
721       while(recentProjects.count >= MaxRecent)
722          recentProjects.Delete(recentProjects.GetLast());
723       recentProjects.Insert(null, filePath);
724       //UpdateRecentMenus(owner);
725    }
726 }
727
728 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
729 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
730 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
731 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
732 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
733 // TODO: i18n with Array
734 static Array<String> compilerTypeLongNames
735 { [
736    $"GNU Compiler Collection (GCC) / GNU Make",
737    $"Tiny C Compiler / GNU Make",
738    $"Portable C Compiler / GNU Make",
739    $"Microsoft Visual Studio 2005 (8.0) Compiler",
740    $"Microsoft Visual Studio 2008 (9.0) Compiler",
741    $"Microsoft Visual Studio 2010 (10.0) Compiler"
742 ] };
743 const CompilerType firstCompilerType = gcc;
744 const CompilerType lastCompilerType = vs10;
745 public enum CompilerType
746 {
747    gcc, tcc, pcc, vs8, vs9, vs10;
748
749    property bool isVC
750    {
751       get { return this == vs8 || this == vs9 || this == vs10; }
752    }
753
754    property char *
755    {
756       get { return OnGetString(null, null, null); }
757       set
758       {
759          if(value)
760          {
761             Platform c;
762             for(c = firstCompilerType; c <= lastCompilerType; c++)
763                if(!strcmpi(value, compilerTypeNames[c]))
764                   return c;
765          }
766          return gcc;
767       }
768    };
769
770    property char * longName { get { return OnGetString(null, (void*)1, null); } };
771    property char * versionString { get { return OnGetString(null, (void*)2, null); } };
772    property char * yearString { get { return OnGetString(null, (void*)3, null); } };
773    property char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
774    property char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
775
776    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
777    {
778       if(this >= firstCompilerType && this <= lastCompilerType)
779       {
780          if(tempString)
781             strcpy(tempString, compilerTypeNames[this]);
782          if(fieldData == null)
783             return compilerTypeNames[this];
784          else if(fieldData == (void*)1)
785             return compilerTypeLongNames[this];
786          else if(fieldData == (void*)2)
787             return compilerTypeVersionString[this];
788          else if(fieldData == (void*)3)
789             return compilerTypeYearString[this];
790          else if(fieldData == (void*)4)
791             return compilerTypeProjectFileExtension[this];
792          else if(fieldData == (void*)5)
793             return compilerTypeSolutionFileVersionString[this];
794       }
795       return null;
796    }
797 };
798
799 class CompilerConfig
800 {
801    class_no_expansion;
802
803    numJobs = 1;
804 public:
805    property char * name
806    {
807       set
808       {
809          delete name;
810          if(value)
811             name = CopyString(value);
812       }
813       get { return name; }
814    }
815    bool readOnly;
816    CompilerType type;
817    Platform targetPlatform;
818    int numJobs;
819    property char * makeCommand
820    {
821       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
822       get { return makeCommand; }
823       isset { return makeCommand && makeCommand[0]; }
824    }
825    property char * ecpCommand
826    {
827       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
828       get { return ecpCommand; }
829       isset { return ecpCommand && ecpCommand[0]; }
830    }
831    property char * eccCommand
832    {
833       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
834       get { return eccCommand; }
835       isset { return eccCommand && eccCommand[0]; }
836    }
837    property char * ecsCommand
838    {
839       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
840       get { return ecsCommand; }
841       isset { return ecsCommand && ecsCommand[0]; }
842    }
843    property char * earCommand
844    {
845       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
846       get { return earCommand; }
847       isset { return earCommand && earCommand[0]; }
848    }
849    property char * cppCommand
850    {
851       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
852       get { return cppCommand; }
853       isset { return cppCommand && cppCommand[0]; }
854    }
855    property char * ccCommand
856    {
857       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
858       get { return ccCommand; }
859       isset { return ccCommand && ccCommand[0]; }
860    }
861    property char * cxxCommand
862    {
863       set { delete cxxCommand; if(value && value[0]) cxxCommand = CopyString(value); }
864       get { return cxxCommand; }
865       isset { return cxxCommand && cxxCommand[0]; }
866    }
867    property char * execPrefixCommand // <-- old name for json ide settings file compatibility
868    {
869       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
870       get { return executableLauncher; }
871       isset { return executableLauncher && executableLauncher[0]; }
872    }
873    // TODO: implement CompilerConfig::windresCommand
874    bool ccacheEnabled;
875    bool distccEnabled;
876    // deprecated
877    property bool supportsBitDepth { set { } get { return true; } isset { return false; } }
878
879    property char * distccHosts
880    {
881       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
882       get { return distccHosts; }
883       isset { return distccHosts && distccHosts[0]; }
884    }
885    property char * gccPrefix // <-- old name for json ide settings file compatibility
886    {
887       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
888       get { return gnuToolchainPrefix; }
889       isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
890    }
891    property char * sysroot
892    {
893       set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
894       get { return sysroot; }
895       isset { return sysroot && sysroot[0]; }
896    }
897    property Array<String> includeDirs
898    {
899       set
900       {
901          includeDirs.Free();
902          if(value)
903          {
904             delete includeDirs;
905             includeDirs = value;
906          }
907       }
908       get { return includeDirs; }
909       isset { return includeDirs.count != 0; }
910    }
911    property Array<String> libraryDirs
912    {
913       set
914       {
915          libraryDirs.Free();
916          if(value)
917          {
918             delete libraryDirs;
919             libraryDirs = value;
920          }
921       }
922       get { return libraryDirs; }
923       isset { return libraryDirs.count != 0; }
924    }
925    property Array<String> executableDirs
926    {
927       set
928       {
929          executableDirs.Free();
930          if(value)
931          {
932             delete executableDirs;
933             executableDirs = value;
934          }
935       }
936       get { return executableDirs; }
937       isset { return executableDirs.count != 0; }
938    }
939    property Array<NamedString> environmentVars
940    {
941       set
942       {
943          environmentVars.Free();
944          if(value)
945          {
946             delete environmentVars;
947             environmentVars = value;
948          }
949       }
950       get { return environmentVars; }
951       isset { return environmentVars.count != 0; }
952    }
953    property Array<String> prepDirectives
954    {
955       set
956       {
957          prepDirectives.Free();
958          if(value)
959          {
960             delete prepDirectives;
961             prepDirectives = value;
962          }
963       }
964       get { return prepDirectives; }
965       isset { return prepDirectives.count != 0; }
966    }
967    property Array<String> excludeLibs
968    {
969       set
970       {
971          excludeLibs.Free();
972          if(value)
973          {
974             delete excludeLibs;
975             excludeLibs = value;
976          }
977       }
978       get { return excludeLibs; }
979       isset { return excludeLibs.count != 0; }
980    }
981    property Array<String> eCcompilerFlags
982    {
983       set
984       {
985          eCcompilerFlags.Free();
986          if(value)
987          {
988             delete eCcompilerFlags;
989             eCcompilerFlags = value;
990          }
991       }
992       get { return eCcompilerFlags; }
993       isset { return eCcompilerFlags.count != 0; }
994    }
995    property Array<String> compilerFlags
996    {
997       set
998       {
999          compilerFlags.Free();
1000          if(value)
1001          {
1002             delete compilerFlags;
1003             compilerFlags = value;
1004          }
1005       }
1006       get { return compilerFlags; }
1007       isset { return compilerFlags.count != 0; }
1008    }
1009    property Array<String> linkerFlags
1010    {
1011       set
1012       {
1013          linkerFlags.Free();
1014          if(value)
1015          {
1016             delete linkerFlags;
1017             linkerFlags = value;
1018          }
1019       }
1020       get { return linkerFlags; }
1021       isset { return linkerFlags.count != 0; }
1022    }
1023 private:
1024    Array<String> includeDirs { };
1025    Array<String> libraryDirs { };
1026    Array<String> executableDirs { };
1027    // TODO: Can JSON parse and serialize maps?
1028    //EnvironmentVariables { };
1029    Array<NamedString> environmentVars { };
1030    Array<String> prepDirectives { };
1031    Array<String> excludeLibs { };
1032    Array<String> eCcompilerFlags { };
1033    Array<String> compilerFlags { };
1034    Array<String> linkerFlags { };
1035    char * name;
1036    char * makeCommand;
1037    char * ecpCommand;
1038    char * eccCommand;
1039    char * ecsCommand;
1040    char * earCommand;
1041    char * cppCommand;
1042    char * ccCommand;
1043    char * cxxCommand;
1044    char * executableLauncher;
1045    char * distccHosts;
1046    char * gnuToolchainPrefix;
1047    char * sysroot;
1048    /*union
1049    {
1050       struct { Array<String> includes, libraries, executables; };
1051       Array<String> dirs[DirTypes];
1052    }*/
1053
1054    ~CompilerConfig()
1055    {
1056       delete name;
1057       delete ecpCommand;
1058       delete eccCommand;
1059       delete ecsCommand;
1060       delete earCommand;
1061       delete cppCommand;
1062       delete ccCommand;
1063       delete cxxCommand;
1064       delete makeCommand;
1065       delete executableLauncher;
1066       delete distccHosts;
1067       delete gnuToolchainPrefix;
1068       delete sysroot;
1069       if(environmentVars) environmentVars.Free();
1070       if(includeDirs) { includeDirs.Free(); }
1071       if(libraryDirs) { libraryDirs.Free(); }
1072       if(executableDirs) { executableDirs.Free(); }
1073       if(prepDirectives) { prepDirectives.Free(); }
1074       if(excludeLibs) { excludeLibs.Free(); }
1075       if(compilerFlags) { compilerFlags.Free(); }
1076       if(eCcompilerFlags) { eCcompilerFlags.Free(); }
1077       if(linkerFlags) { linkerFlags.Free(); }
1078    }
1079    CompilerConfig Copy()
1080    {
1081       CompilerConfig copy
1082       {
1083          name,
1084          readOnly,
1085          type,
1086          targetPlatform,
1087          numJobs,
1088          makeCommand,
1089          ecpCommand,
1090          eccCommand,
1091          ecsCommand,
1092          earCommand,
1093          cppCommand,
1094          ccCommand,
1095          cxxCommand,
1096          executableLauncher,
1097          ccacheEnabled,
1098          distccEnabled,
1099          false,
1100          distccHosts,
1101          gnuToolchainPrefix,
1102          sysroot
1103       };
1104       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
1105       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
1106       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
1107       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
1108       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
1109       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
1110       for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
1111       for(s : eCcompilerFlags) copy.eCcompilerFlags.Add(CopyString(s));
1112       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
1113
1114       incref copy;
1115       return copy;
1116    }
1117 }
1118
1119 struct LanguageOption
1120 {
1121    String name;
1122    String bitmap;
1123    String code;
1124    BitmapResource res;
1125
1126    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
1127    {
1128       return name;
1129    }
1130
1131    void OnDisplay(Surface surface, int x, int y, int width, void * data, Alignment alignment, DataDisplayFlags flags)
1132    {
1133       Bitmap icon = res ? res.bitmap : null;
1134       int w = 8 + 16;
1135       if(icon)
1136          surface.Blit(icon, x + (16 - icon.width) / 2,y+2,0,0, icon.width, icon.height);
1137       class::OnDisplay(surface, x + w, y, width - w, null, alignment, flags);
1138    }
1139 };
1140
1141 Array<LanguageOption> languages
1142 { [
1143    { "English",            ":countryCode/gb.png", "" },
1144    { "汉语",                ":countryCode/cn.png", "zh_CN" },
1145    { "Español",            ":countryCode/es.png", "es_ES" },
1146    { "Русский (43%)",      ":countryCode/ru.png", "ru_RU" },
1147    { "Português (28%)",    ":countryCode/pt.png", "pt_BR" },
1148    { "Nederlandse (13%)",  ":countryCode/nl.png", "nl_NL" },
1149    { "Tiếng Việt (12%)",   ":countryCode/vn.png", "vi_VI" },
1150    { "मराठी (10%)",          ":countryCode/in.png", "mr_MR" },
1151    { "Hebrew (8%)",        ":countryCode/il.png", "he_HE" },
1152    { "Magyar (8%)",        ":countryCode/hu.png", "hu_HU" }
1153 ] };
1154
1155 String GetLanguageString()
1156 {
1157    String language = getenv("LANGUAGE");
1158    if(!language) language = getenv("LC_ALL");
1159    if(!language) language = getenv("LC_MESSAGES");
1160    if(!language) language = getenv("LANG");
1161    if(!language) language = "";
1162    return language;
1163 }
1164
1165 bool LanguageRestart(char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
1166 {
1167    bool restart = true;
1168    String command = null;
1169    int arg0Len = strlen(app.argv[0]);
1170    int len = arg0Len;
1171    int j;
1172    char ch;
1173
1174    if(ide)
1175    {
1176       Window w;
1177
1178       if(projectView)
1179       {
1180          Window w;
1181          for(w = ide.firstChild; w; w = w.next)
1182          {
1183             if(w.isActiveClient && w.isDocument)
1184             {
1185                if(!w.CloseConfirmation(true))
1186                {
1187                   restart = false;
1188                   break;
1189                }
1190             }
1191          }
1192          if(restart)
1193          {
1194             if(!projectView.CloseConfirmation(true))
1195                restart = false;
1196             if(projectView.fileName)
1197             {
1198                char * name = projectView.fileName;
1199                if(name)
1200                {
1201                   for(j = 0; (ch = name[j]); j++)
1202                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1203                }
1204             }
1205
1206             command = new char[len + 1];
1207
1208             strcpy(command, app.argv[0]);
1209             len = arg0Len;
1210             if(projectView.fileName)
1211             {
1212                strcat(command, " ");
1213                len++;
1214                ReplaceSpaces(command + len, projectView.fileName);
1215             }
1216          }
1217          if(restart)
1218          {
1219             for(w = ide.firstChild; w; w = w.next)
1220                if(w.isActiveClient && w.isDocument)
1221                   w.modifiedDocument = false;
1222             projectView.modifiedDocument = false;
1223          }
1224       }
1225       else
1226       {
1227          for(w = ide.firstChild; w; w = w.next)
1228          {
1229             if(w.isActiveClient && w.isDocument)
1230             {
1231                if(!w.CloseConfirmation(true))
1232                {
1233                   restart = false;
1234                   break;
1235                }
1236                if(w.fileName)
1237                {
1238                   char * name = w.fileName;
1239                   len++;
1240                   for(j = 0; (ch = name[j]); j++)
1241                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1242                }
1243             }
1244          }
1245
1246          if(restart)
1247          {
1248             command = new char[len + 1];
1249             strcpy(command, app.argv[0]);
1250             len = arg0Len;
1251
1252             for(w = ide.firstChild; w; w = w.next)
1253             {
1254                if(w.isActiveClient && w.isDocument)
1255                {
1256                   char * name = w.fileName;
1257                   if(name)
1258                   {
1259                      strcat(command, " ");
1260                      len++;
1261                      ReplaceSpaces(command + len, name);
1262                      len = strlen(command);
1263                   }
1264                }
1265             }
1266          }
1267          if(restart)
1268          {
1269             for(w = ide.firstChild; w; w = w.next)
1270                if(w.isActiveClient && w.isDocument)
1271                   w.modifiedDocument = false;
1272          }
1273       }
1274       if(restart)
1275       {
1276          settings.language = code;
1277          settingsContainer.Save();
1278          if(eClass_IsDerived(app._class, class(GuiApplication)))
1279          {
1280             GuiApplication guiApp = (GuiApplication)app;
1281             guiApp.desktop.Destroy(0);
1282          }
1283       }
1284    }
1285    else
1286    {
1287       int i;
1288       for(i = 1; i < app.argc; i++)
1289       {
1290          char * arg = app.argv[i];
1291          len++;
1292          for(j = 0; (ch = arg[j]); j++)
1293             len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1294       }
1295
1296       command = new char[len + 1];
1297       strcpy(command, app.argv[0]);
1298       len = arg0Len;
1299       for(i = 1; i < app.argc; i++)
1300       {
1301          strcat(command, " ");
1302          len++;
1303          ReplaceSpaces(command + len, app.argv[i]);
1304          len = strlen(command);
1305       }
1306    }
1307
1308    if(restart)
1309    {
1310       SetEnvironment("LANGUAGE", code);
1311       if(wait)
1312          ExecuteWait(command);
1313       else
1314          Execute(command);
1315    }
1316    delete command;
1317    return restart;
1318 }