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