i18n: Checking ECERE_LANGUAGE first; Setting environment variable from IDE and installer
[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       /*if(MessageBox { type = YesNo, master = this,
290             text = "Global Settings Modified Externally",
291             contents = "The global settings were modified by another instance.\n"
292             "Would you like to reload them?" }.Modal() == Yes)*/
293       {
294          double o, n;
295          FileSize newSettingsFileSize;
296          FileGetSize(settingsFilePath, &newSettingsFileSize);
297          o = settingsFileSize;
298          n = newSettingsFileSize;
299          if(o - n < 2048)
300             Load();
301          else
302          {
303             GuiApplication app = ((GuiApplication)__thisModule.application);
304             Window w;
305             for(w = app.desktop.firstChild; w && (!w.created || !w.visible); w = w.next);
306             MessageBox { master = w, type = ok,
307                   text = "Global Settings Modified Externally",
308                   contents = "The global settings were modified by another process and a drastic shrinking of the settings file was detected.\n"
309                   "The new settings will not be loaded to prevent loss of your ide settings.\n"
310                   "Please check your settings file and make sure to save this IDE's global settings if your settings file has been compromised."
311                   }.Create();
312             //Save();
313          }
314       }
315    }
316
317    SettingsIOResult Load()
318    {
319       SettingsIOResult result = GlobalSettings::Load();
320       IDESettings data = (IDESettings)this.data;
321       CompilerConfig defaultCompiler = null;
322       if(!data)
323       {
324          this.data = IDESettings { };
325          if(dataOwner)
326             *dataOwner = this.data;
327
328          if(result == fileNotCompatibleWithDriver)
329          {
330             bool loaded;
331             OldIDESettings oldSettings { };
332             Close();
333             loaded = oldSettings.Load() == success;
334             oldSettings.Close();
335             if(loaded)
336             {
337                data = (IDESettings)this.data;
338
339                for(c : oldSettings.compilerConfigs)
340                   data.compilerConfigs.Add(c.Copy());
341
342                for(s : oldSettings.recentFiles) data.recentFiles.Add(CopyString(s));
343                for(s : oldSettings.recentProjects) data.recentProjects.Add(CopyString(s));
344
345                data.docDir = oldSettings.docDir;
346                data.ideFileDialogLocation = oldSettings.ideFileDialogLocation;
347                data.ideProjectFileDialogLocation = oldSettings.ideProjectFileDialogLocation;
348                data.useFreeCaret = oldSettings.useFreeCaret;
349                data.showLineNumbers = oldSettings.showLineNumbers;
350                data.caretFollowsScrolling = oldSettings.caretFollowsScrolling;
351                delete data.displayDriver; data.displayDriver = CopyString(oldSettings.displayDriver);
352                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
353                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
354
355                Save();
356                result = success;
357             }
358             delete oldSettings;
359          }
360          if(result == fileNotFound || !data)
361          {
362             data = (IDESettings)this.data;
363             data.useFreeCaret = false; //true;
364             data.showLineNumbers = true;
365             data.caretFollowsScrolling = false; //true;
366          }
367       }
368       // Ensure we have a default compiler
369       defaultCompiler = data.GetCompilerConfig(defaultCompilerName);
370       if(!defaultCompiler)
371       {
372          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
373          data.compilerConfigs.Add(defaultCompiler);
374       }
375
376       // We incref the compilers below, so reset refCount to 0
377       defaultCompiler._refCount = 0;
378
379       CloseAndMonitor();
380       FileGetSize(settingsFilePath, &settingsFileSize);
381       if(data.compilerConfigs)
382       {
383          for(c : data.compilerConfigs)
384          {
385             CompilerConfig compiler = c;
386             char * cxxCommand = compiler.cxxCommand;
387             if(!cxxCommand || !cxxCommand[0])
388                compiler.cxxCommand = cxxDefaultCommand;
389             incref compiler;
390          }
391       }
392       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
393          data.ManagePortablePaths(moduleLocation, true);
394       data.ForcePathSeparatorStyle(true);
395       OnLoad(data);
396       return result;
397    }
398
399    SettingsIOResult Save()
400    {
401       SettingsIOResult result;
402
403       IDESettings data = (IDESettings)this.data;
404       Platform runtimePlatform = GetRuntimePlatform();
405       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
406          data.ManagePortablePaths(moduleLocation, false);
407       data.ForcePathSeparatorStyle(true);
408       result = GlobalSettings::Save();
409       if(result != success)
410          PrintLn("Error saving IDE settings");
411       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
412          data.ManagePortablePaths(moduleLocation, true);
413       CloseAndMonitor();
414       FileGetSize(settingsFilePath, &settingsFileSize);
415       return result;
416    }
417 }
418
419 class IDESettings : GlobalSettingsData
420 {
421 public:
422    List<CompilerConfig> compilerConfigs { };
423    Array<String> recentFiles { };
424    Array<String> recentProjects { };
425    property char * docDir
426    {
427       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
428       get { return docDir ? docDir : ""; }
429       isset { return docDir && docDir[0]; }
430    }
431    property char * ideFileDialogLocation
432    {
433       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
434       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
435       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
436    }
437    property char * ideProjectFileDialogLocation
438    {
439       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
440       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
441       isset { return ideProjectFileDialogLocation && ideProjectFileDialogLocation[0]; }
442    }
443    bool useFreeCaret;
444    bool showLineNumbers;
445    bool caretFollowsScrolling;
446    char * displayDriver;
447
448    // TODO: Classify settings
449    //EditorSettings editor { };
450
451    property char * projectDefaultTargetDir
452    {
453       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
454       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
455       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
456    }
457    property char * projectDefaultIntermediateObjDir
458    {
459       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
460       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
461       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
462    }
463
464    property char * compilerConfigsDir
465    {
466       set { delete compilerConfigsDir; if(value && value[0]) compilerConfigsDir = CopyString(value); }
467       get { return compilerConfigsDir ? compilerConfigsDir : ""; }
468       isset { return compilerConfigsDir && compilerConfigsDir[0]; }
469    }
470
471    property char * defaultCompiler
472    {
473       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
474       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
475       isset { return defaultCompiler && defaultCompiler[0]; }
476    }
477
478    property String language
479    {
480       set
481       {
482          delete language;
483          language = CopyString(value);
484       }
485       get { return language; }
486       isset { return language != null; }
487    }
488
489 private:
490    char * docDir;
491    char * ideFileDialogLocation;
492    char * ideProjectFileDialogLocation;
493    char * projectDefaultTargetDir;
494    char * projectDefaultIntermediateObjDir;
495    char * compilerConfigsDir;
496    char * defaultCompiler;
497    String language;
498
499    CompilerConfig GetCompilerConfig(String compilerName)
500    {
501       char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
502       CompilerConfig compilerConfig = null;
503       for(compiler : compilerConfigs)
504       {
505          if(!strcmp(compiler.name, name))
506          {
507             compilerConfig = compiler;
508             break;
509          }
510       }
511       if(!compilerConfig && compilerConfigs.count)
512          compilerConfig = compilerConfigs.firstIterator.data;
513       if(compilerConfig)
514          incref compilerConfig;
515       return compilerConfig;
516    }
517
518    ~IDESettings()
519    {
520       compilerConfigs.Free();
521       delete compilerConfigs;
522       recentFiles.Free();
523       delete recentFiles;
524       recentProjects.Free();
525       delete recentProjects;
526       delete docDir;
527
528       delete projectDefaultTargetDir;
529       delete projectDefaultIntermediateObjDir;
530       delete compilerConfigsDir;
531       delete defaultCompiler;
532
533       delete ideFileDialogLocation;
534       delete ideProjectFileDialogLocation;
535       delete displayDriver;
536    }
537
538    void ForcePathSeparatorStyle(bool unixStyle)
539    {
540       char from, to;
541       if(unixStyle)
542          from = '\\', to = '/';
543       else
544          from = '/', to = '\\';
545       if(compilerConfigs && compilerConfigs.count)
546       {
547          int i;
548          for(config : compilerConfigs)
549          {
550             if(config.includeDirs && config.includeDirs.count)
551             {
552                for(i = 0; i < config.includeDirs.count; i++)
553                {
554                   if(config.includeDirs[i] && config.includeDirs[i][0])
555                      ChangeCh(config.includeDirs[i], from, to);
556                }
557             }
558             if(config.libraryDirs && config.libraryDirs.count)
559             {
560                for(i = 0; i < config.libraryDirs.count; i++)
561                {
562                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
563                      ChangeCh(config.libraryDirs[i], from, to);
564                }
565             }
566             if(config.executableDirs && config.executableDirs.count)
567             {
568                for(i = 0; i < config.executableDirs.count; i++)
569                {
570                   if(config.executableDirs[i] && config.executableDirs[i][0])
571                      ChangeCh(config.executableDirs[i], from, to);
572                }
573             }
574          }
575       }
576       if(recentFiles && recentFiles.count)
577       {
578          int c;
579          for(c = 0; c < recentFiles.count; c++)
580          {
581             if(recentFiles[c] && recentFiles[c][0])
582                ChangeCh(recentFiles[c], from, to);
583          }
584       }
585       if(recentProjects && recentProjects.count)
586       {
587          int c;
588          for(c = 0; c < recentProjects.count; c++)
589          {
590             if(recentProjects[c] && recentProjects[c][0])
591                ChangeCh(recentProjects[c], from, to);
592          }
593       }
594       if(docDir && docDir[0])
595          ChangeCh(docDir, from, to);
596       if(ideFileDialogLocation && ideFileDialogLocation[0])
597          ChangeCh(ideFileDialogLocation, from, to);
598       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
599          ChangeCh(ideProjectFileDialogLocation, from, to);
600
601       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
602          ChangeCh(projectDefaultTargetDir, from, to);
603       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
604          ChangeCh(projectDefaultIntermediateObjDir, from, to);
605
606       if(compilerConfigsDir && compilerConfigsDir[0])
607          ChangeCh(compilerConfigsDir, from, to);
608    }
609
610    void ManagePortablePaths(char * location, bool makeAbsolute)
611    {
612       int c;
613       if(compilerConfigs && compilerConfigs.count)
614       {
615          for(config : compilerConfigs)
616          {
617             DirTypes t;
618             for(t = 0; t < DirTypes::enumSize; t++)
619             {
620                Array<String> dirs = null;
621                if(t == executables) dirs = config.executableDirs;
622                else if(t == includes) dirs = config.includeDirs;
623                else if(t == libraries) dirs = config.libraryDirs;
624                if(dirs && dirs.count)
625                {
626                   for(c = 0; c < dirs.count; c++)
627                   {
628                      if(dirs[c] && dirs[c][0])
629                         dirs[c] = UpdatePortablePath(dirs[c], location, makeAbsolute);
630                   }
631                }
632             }
633          }
634       }
635       if(recentFiles && recentFiles.count)
636       {
637          for(c = 0; c < recentFiles.count; c++)
638          {
639             if(recentFiles[c] && recentFiles[c][0])
640                recentFiles[c] = UpdatePortablePath(recentFiles[c], location, makeAbsolute);
641          }
642       }
643       if(recentProjects && recentProjects.count)
644       {
645          for(c = 0; c < recentProjects.count; c++)
646          {
647             if(recentProjects[c] && recentProjects[c][0])
648                recentProjects[c] = UpdatePortablePath(recentProjects[c], location, makeAbsolute);
649          }
650       }
651       if(docDir && docDir[0])
652          docDir = UpdatePortablePath(docDir, location, makeAbsolute);
653       if(ideFileDialogLocation && ideFileDialogLocation[0])
654          ideFileDialogLocation = UpdatePortablePath(ideFileDialogLocation, location, makeAbsolute);
655       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
656          ideProjectFileDialogLocation = UpdatePortablePath(ideProjectFileDialogLocation, location, makeAbsolute);
657
658       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
659          projectDefaultTargetDir = UpdatePortablePath(projectDefaultTargetDir, location, makeAbsolute);
660       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
661          projectDefaultIntermediateObjDir = UpdatePortablePath(projectDefaultIntermediateObjDir, location, makeAbsolute);
662
663       if(compilerConfigsDir && compilerConfigsDir[0])
664          compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
665    }
666
667    char * UpdatePortablePath(char * path, char * location, bool makeAbsolute)
668    {
669       char * output;
670       if(makeAbsolute)
671       {
672          char p[MAX_LOCATION];
673          strcpy(p, location);
674          PathCatSlash(p, path);
675          delete path;
676          output = CopyString(p);
677       }
678       else
679       {
680          PathRelationship rel = eString_PathRelated(path, location, null);
681          if(rel == subPath || rel == identical)
682          {
683             char p[MAX_LOCATION];
684             MakePathRelative(path, location, p);
685             if(!*p) strcpy(p, "./");
686             else ChangeCh(p, '\\', '/');
687             delete path;
688             output = CopyString(p);
689          }
690          else
691             output = path;
692       }
693       return output;
694    }
695
696    void AddRecentFile(char * fileName)
697    {
698       int c;
699       char * filePath = CopyString(fileName);
700       ChangeCh(filePath, '\\', '/');
701       for(c = 0; c<recentFiles.count; c++)
702       {
703          if(recentFiles[c] && !fstrcmp(recentFiles[c], filePath))
704          {
705             recentFiles.Delete((void *)&recentFiles[c]);
706             c--;
707          }
708       }
709       while(recentFiles.count >= MaxRecent)
710          recentFiles.Delete(recentFiles.GetLast());
711       recentFiles.Insert(null, filePath);
712       //UpdateRecentMenus(owner);
713    }
714
715    void AddRecentProject(char * projectName)
716    {
717       int c;
718       char * filePath = CopyString(projectName);
719       ChangeCh(filePath, '\\', '/');
720       for(c = 0; c<recentProjects.count; c++)
721       {
722          if(recentProjects[c] && !fstrcmp(recentProjects[c], filePath))
723          {
724             recentProjects.Delete((void *)&recentProjects[c]);
725             c--;
726          }
727       }
728       while(recentProjects.count >= MaxRecent)
729          recentProjects.Delete(recentProjects.GetLast());
730       recentProjects.Insert(null, filePath);
731       //UpdateRecentMenus(owner);
732    }
733 }
734
735 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
736 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
737 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
738 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
739 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
740 // TODO: i18n with Array
741 static Array<String> compilerTypeLongNames
742 { [
743    $"GNU Compiler Collection (GCC) / GNU Make",
744    $"Tiny C Compiler / GNU Make",
745    $"Portable C Compiler / GNU Make",
746    $"Microsoft Visual Studio 2005 (8.0) Compiler",
747    $"Microsoft Visual Studio 2008 (9.0) Compiler",
748    $"Microsoft Visual Studio 2010 (10.0) Compiler"
749 ] };
750 const CompilerType firstCompilerType = gcc;
751 const CompilerType lastCompilerType = vs10;
752 public enum CompilerType
753 {
754    gcc, tcc, pcc, vs8, vs9, vs10;
755
756    property bool isVC
757    {
758       get { return this == vs8 || this == vs9 || this == vs10; }
759    }
760
761    property char *
762    {
763       get { return OnGetString(null, null, null); }
764       set
765       {
766          if(value)
767          {
768             Platform c;
769             for(c = firstCompilerType; c <= lastCompilerType; c++)
770                if(!strcmpi(value, compilerTypeNames[c]))
771                   return c;
772          }
773          return gcc;
774       }
775    };
776
777    property char * longName { get { return OnGetString(null, (void*)1, null); } };
778    property char * versionString { get { return OnGetString(null, (void*)2, null); } };
779    property char * yearString { get { return OnGetString(null, (void*)3, null); } };
780    property char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
781    property char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
782
783    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
784    {
785       if(this >= firstCompilerType && this <= lastCompilerType)
786       {
787          if(tempString)
788             strcpy(tempString, compilerTypeNames[this]);
789          if(fieldData == null)
790             return compilerTypeNames[this];
791          else if(fieldData == (void*)1)
792             return compilerTypeLongNames[this];
793          else if(fieldData == (void*)2)
794             return compilerTypeVersionString[this];
795          else if(fieldData == (void*)3)
796             return compilerTypeYearString[this];
797          else if(fieldData == (void*)4)
798             return compilerTypeProjectFileExtension[this];
799          else if(fieldData == (void*)5)
800             return compilerTypeSolutionFileVersionString[this];
801       }
802       return null;
803    }
804 };
805
806 class CompilerConfig
807 {
808    class_no_expansion;
809
810    numJobs = 1;
811 public:
812    property char * name
813    {
814       set
815       {
816          delete name;
817          if(value)
818             name = CopyString(value);
819       }
820       get { return name; }
821    }
822    bool readOnly;
823    CompilerType type;
824    Platform targetPlatform;
825    int numJobs;
826    property char * makeCommand
827    {
828       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
829       get { return makeCommand; }
830       isset { return makeCommand && makeCommand[0]; }
831    }
832    property char * ecpCommand
833    {
834       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
835       get { return ecpCommand; }
836       isset { return ecpCommand && ecpCommand[0]; }
837    }
838    property char * eccCommand
839    {
840       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
841       get { return eccCommand; }
842       isset { return eccCommand && eccCommand[0]; }
843    }
844    property char * ecsCommand
845    {
846       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
847       get { return ecsCommand; }
848       isset { return ecsCommand && ecsCommand[0]; }
849    }
850    property char * earCommand
851    {
852       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
853       get { return earCommand; }
854       isset { return earCommand && earCommand[0]; }
855    }
856    property char * cppCommand
857    {
858       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
859       get { return cppCommand; }
860       isset { return cppCommand && cppCommand[0]; }
861    }
862    property char * ccCommand
863    {
864       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
865       get { return ccCommand; }
866       isset { return ccCommand && ccCommand[0]; }
867    }
868    property char * cxxCommand
869    {
870       set { delete cxxCommand; if(value && value[0]) cxxCommand = CopyString(value); }
871       get { return cxxCommand; }
872       isset { return cxxCommand && cxxCommand[0]; }
873    }
874    property char * execPrefixCommand // <-- old name for json ide settings file compatibility
875    {
876       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
877       get { return executableLauncher; }
878       isset { return executableLauncher && executableLauncher[0]; }
879    }
880    // TODO: implement CompilerConfig::windresCommand
881    bool ccacheEnabled;
882    bool distccEnabled;
883    // deprecated
884    property bool supportsBitDepth { set { } get { return true; } isset { return false; } }
885
886    property char * distccHosts
887    {
888       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
889       get { return distccHosts; }
890       isset { return distccHosts && distccHosts[0]; }
891    }
892    property char * gccPrefix // <-- old name for json ide settings file compatibility
893    {
894       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
895       get { return gnuToolchainPrefix; }
896       isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
897    }
898    property char * sysroot
899    {
900       set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
901       get { return sysroot; }
902       isset { return sysroot && sysroot[0]; }
903    }
904    property Array<String> includeDirs
905    {
906       set
907       {
908          includeDirs.Free();
909          if(value)
910          {
911             delete includeDirs;
912             includeDirs = value;
913          }
914       }
915       get { return includeDirs; }
916       isset { return includeDirs.count != 0; }
917    }
918    property Array<String> libraryDirs
919    {
920       set
921       {
922          libraryDirs.Free();
923          if(value)
924          {
925             delete libraryDirs;
926             libraryDirs = value;
927          }
928       }
929       get { return libraryDirs; }
930       isset { return libraryDirs.count != 0; }
931    }
932    property Array<String> executableDirs
933    {
934       set
935       {
936          executableDirs.Free();
937          if(value)
938          {
939             delete executableDirs;
940             executableDirs = value;
941          }
942       }
943       get { return executableDirs; }
944       isset { return executableDirs.count != 0; }
945    }
946    property Array<NamedString> environmentVars
947    {
948       set
949       {
950          environmentVars.Free();
951          if(value)
952          {
953             delete environmentVars;
954             environmentVars = value;
955          }
956       }
957       get { return environmentVars; }
958       isset { return environmentVars.count != 0; }
959    }
960    property Array<String> prepDirectives
961    {
962       set
963       {
964          prepDirectives.Free();
965          if(value)
966          {
967             delete prepDirectives;
968             prepDirectives = value;
969          }
970       }
971       get { return prepDirectives; }
972       isset { return prepDirectives.count != 0; }
973    }
974    property Array<String> excludeLibs
975    {
976       set
977       {
978          excludeLibs.Free();
979          if(value)
980          {
981             delete excludeLibs;
982             excludeLibs = value;
983          }
984       }
985       get { return excludeLibs; }
986       isset { return excludeLibs.count != 0; }
987    }
988    property Array<String> eCcompilerFlags
989    {
990       set
991       {
992          eCcompilerFlags.Free();
993          if(value)
994          {
995             delete eCcompilerFlags;
996             eCcompilerFlags = value;
997          }
998       }
999       get { return eCcompilerFlags; }
1000       isset { return eCcompilerFlags.count != 0; }
1001    }
1002    property Array<String> compilerFlags
1003    {
1004       set
1005       {
1006          compilerFlags.Free();
1007          if(value)
1008          {
1009             delete compilerFlags;
1010             compilerFlags = value;
1011          }
1012       }
1013       get { return compilerFlags; }
1014       isset { return compilerFlags.count != 0; }
1015    }
1016    property Array<String> linkerFlags
1017    {
1018       set
1019       {
1020          linkerFlags.Free();
1021          if(value)
1022          {
1023             delete linkerFlags;
1024             linkerFlags = value;
1025          }
1026       }
1027       get { return linkerFlags; }
1028       isset { return linkerFlags.count != 0; }
1029    }
1030 private:
1031    Array<String> includeDirs { };
1032    Array<String> libraryDirs { };
1033    Array<String> executableDirs { };
1034    // TODO: Can JSON parse and serialize maps?
1035    //EnvironmentVariables { };
1036    Array<NamedString> environmentVars { };
1037    Array<String> prepDirectives { };
1038    Array<String> excludeLibs { };
1039    Array<String> eCcompilerFlags { };
1040    Array<String> compilerFlags { };
1041    Array<String> linkerFlags { };
1042    char * name;
1043    char * makeCommand;
1044    char * ecpCommand;
1045    char * eccCommand;
1046    char * ecsCommand;
1047    char * earCommand;
1048    char * cppCommand;
1049    char * ccCommand;
1050    char * cxxCommand;
1051    char * executableLauncher;
1052    char * distccHosts;
1053    char * gnuToolchainPrefix;
1054    char * sysroot;
1055    /*union
1056    {
1057       struct { Array<String> includes, libraries, executables; };
1058       Array<String> dirs[DirTypes];
1059    }*/
1060
1061    ~CompilerConfig()
1062    {
1063       delete name;
1064       delete ecpCommand;
1065       delete eccCommand;
1066       delete ecsCommand;
1067       delete earCommand;
1068       delete cppCommand;
1069       delete ccCommand;
1070       delete cxxCommand;
1071       delete makeCommand;
1072       delete executableLauncher;
1073       delete distccHosts;
1074       delete gnuToolchainPrefix;
1075       delete sysroot;
1076       if(environmentVars) environmentVars.Free();
1077       if(includeDirs) { includeDirs.Free(); }
1078       if(libraryDirs) { libraryDirs.Free(); }
1079       if(executableDirs) { executableDirs.Free(); }
1080       if(prepDirectives) { prepDirectives.Free(); }
1081       if(excludeLibs) { excludeLibs.Free(); }
1082       if(compilerFlags) { compilerFlags.Free(); }
1083       if(eCcompilerFlags) { eCcompilerFlags.Free(); }
1084       if(linkerFlags) { linkerFlags.Free(); }
1085    }
1086    CompilerConfig Copy()
1087    {
1088       CompilerConfig copy
1089       {
1090          name,
1091          readOnly,
1092          type,
1093          targetPlatform,
1094          numJobs,
1095          makeCommand,
1096          ecpCommand,
1097          eccCommand,
1098          ecsCommand,
1099          earCommand,
1100          cppCommand,
1101          ccCommand,
1102          cxxCommand,
1103          executableLauncher,
1104          ccacheEnabled,
1105          distccEnabled,
1106          false,
1107          distccHosts,
1108          gnuToolchainPrefix,
1109          sysroot
1110       };
1111       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
1112       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
1113       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
1114       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
1115       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
1116       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
1117       for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
1118       for(s : eCcompilerFlags) copy.eCcompilerFlags.Add(CopyString(s));
1119       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
1120
1121       incref copy;
1122       return copy;
1123    }
1124 }
1125
1126 struct LanguageOption
1127 {
1128    String name;
1129    String bitmap;
1130    String code;
1131    BitmapResource res;
1132
1133    char * OnGetString(char * tempString, void * fieldData, bool * needClass)
1134    {
1135       return name;
1136    }
1137
1138    void OnDisplay(Surface surface, int x, int y, int width, void * data, Alignment alignment, DataDisplayFlags flags)
1139    {
1140       Bitmap icon = res ? res.bitmap : null;
1141       int w = 8 + 16;
1142       if(icon)
1143          surface.Blit(icon, x + (16 - icon.width) / 2,y+2,0,0, icon.width, icon.height);
1144       class::OnDisplay(surface, x + w, y, width - w, null, alignment, flags);
1145    }
1146 };
1147
1148 Array<LanguageOption> languages
1149 { [
1150    { "English",            ":countryCode/gb.png", "" },
1151    { "汉语",                ":countryCode/cn.png", "zh_CN" },
1152    { "Español",            ":countryCode/es.png", "es" },
1153    { "Português (Brazil)", ":countryCode/br.png", "pt_BR" },
1154    { "Русский (43%)",      ":countryCode/ru.png", "ru" },
1155    { "Nederlandse (13%)",  ":countryCode/nl.png", "nl" },
1156    { "Tiếng Việt (12%)",   ":countryCode/vn.png", "vi" },
1157    { "मराठी (10%)",          ":countryCode/in.png", "mr" },
1158    { "Hebrew (8%)",        ":countryCode/il.png", "he" },
1159    { "Magyar (8%)",        ":countryCode/hu.png", "hu" }
1160 ] };
1161
1162 String GetLanguageString()
1163 {
1164    String language = getenv("ECERE_LANGUAGE");
1165    if(!language) language = getenv("LANGUAGE");
1166    if(!language) language = getenv("LC_ALL");
1167    if(!language) language = getenv("LC_MESSAGES");
1168    if(!language) language = getenv("LANG");
1169    if(!language) language = "";
1170    return language;
1171 }
1172
1173 bool LanguageRestart(char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
1174 {
1175    bool restart = true;
1176    String command = null;
1177    int arg0Len = (int)strlen(app.argv[0]);
1178    int len = arg0Len;
1179    int j;
1180    char ch;
1181
1182    if(ide)
1183    {
1184       Window w;
1185
1186       if(projectView)
1187       {
1188          Window w;
1189          for(w = ide.firstChild; w; w = w.next)
1190          {
1191             if(w.isActiveClient && w.isDocument)
1192             {
1193                if(!w.CloseConfirmation(true))
1194                {
1195                   restart = false;
1196                   break;
1197                }
1198             }
1199          }
1200          if(restart)
1201          {
1202             if(!projectView.CloseConfirmation(true))
1203                restart = false;
1204             if(projectView.fileName)
1205             {
1206                char * name = projectView.fileName;
1207                if(name)
1208                {
1209                   for(j = 0; (ch = name[j]); j++)
1210                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1211                }
1212             }
1213
1214             command = new char[len + 1];
1215
1216             strcpy(command, app.argv[0]);
1217             len = arg0Len;
1218             if(projectView.fileName)
1219             {
1220                strcat(command, " ");
1221                len++;
1222                ReplaceSpaces(command + len, projectView.fileName);
1223             }
1224          }
1225          if(restart)
1226          {
1227             for(w = ide.firstChild; w; w = w.next)
1228                if(w.isActiveClient && w.isDocument)
1229                   w.modifiedDocument = false;
1230             projectView.modifiedDocument = false;
1231          }
1232       }
1233       else
1234       {
1235          for(w = ide.firstChild; w; w = w.next)
1236          {
1237             if(w.isActiveClient && w.isDocument)
1238             {
1239                if(!w.CloseConfirmation(true))
1240                {
1241                   restart = false;
1242                   break;
1243                }
1244                if(w.fileName)
1245                {
1246                   char * name = w.fileName;
1247                   len++;
1248                   for(j = 0; (ch = name[j]); j++)
1249                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1250                }
1251             }
1252          }
1253
1254          if(restart)
1255          {
1256             command = new char[len + 1];
1257             strcpy(command, app.argv[0]);
1258             len = arg0Len;
1259
1260             for(w = ide.firstChild; w; w = w.next)
1261             {
1262                if(w.isActiveClient && w.isDocument)
1263                {
1264                   char * name = w.fileName;
1265                   if(name)
1266                   {
1267                      strcat(command, " ");
1268                      len++;
1269                      ReplaceSpaces(command + len, name);
1270                      len = (int)strlen(command);
1271                   }
1272                }
1273             }
1274          }
1275          if(restart)
1276          {
1277             for(w = ide.firstChild; w; w = w.next)
1278                if(w.isActiveClient && w.isDocument)
1279                   w.modifiedDocument = false;
1280          }
1281       }
1282       if(restart)
1283       {
1284          settings.language = code;
1285          settingsContainer.Save();
1286
1287 #if defined(__WIN32__)
1288          // Set LANGUAGE environment variable
1289          {
1290             HKEY key = null;
1291             uint16 wLanguage[256];
1292             HRESULT status;
1293             wLanguage[0] = 0;
1294
1295             RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1296             if(key)
1297             {
1298                UTF8toUTF16Buffer(code, wLanguage, sizeof(wLanguage) / sizeof(uint16));
1299                RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
1300                RegCloseKey(key);
1301             }
1302          }
1303 #endif
1304
1305          if(eClass_IsDerived(app._class, class(GuiApplication)))
1306          {
1307             GuiApplication guiApp = (GuiApplication)app;
1308             guiApp.desktop.Destroy(0);
1309          }
1310       }
1311    }
1312    else
1313    {
1314       int i;
1315       for(i = 1; i < app.argc; i++)
1316       {
1317          char * arg = app.argv[i];
1318          len++;
1319          for(j = 0; (ch = arg[j]); j++)
1320             len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1321       }
1322
1323       command = new char[len + 1];
1324       strcpy(command, app.argv[0]);
1325       len = arg0Len;
1326       for(i = 1; i < app.argc; i++)
1327       {
1328          strcat(command, " ");
1329          len++;
1330          ReplaceSpaces(command + len, app.argv[i]);
1331          len = (int)strlen(command);
1332       }
1333    }
1334
1335    if(restart)
1336    {
1337       SetEnvironment("ECERE_LANGUAGE", code);
1338       if(wait)
1339          ExecuteWait(command);
1340       else
1341          Execute(command);
1342    }
1343    delete command;
1344    return restart;
1345 }