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