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