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