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