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