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