ide: add recent files to workspace for the recent files menu when a workspace is...
[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(s);
358                for(s : oldSettings.recentProjects) data.recentProjects.Add(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    RecentPaths recentFiles { };
456    RecentPaths 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       if(recentProjects) { recentFiles.Free(); delete recentFiles; }
555       if(recentProjects) { recentProjects.Free(); delete recentProjects; }
556       delete docDir;
557
558       delete projectDefaultTargetDir;
559       delete projectDefaultIntermediateObjDir;
560       delete compilerConfigsDir;
561       delete defaultCompiler;
562       delete language;
563
564       delete ideFileDialogLocation;
565       delete ideProjectFileDialogLocation;
566       delete displayDriver;
567    }
568
569    void ForcePathSeparatorStyle(bool unixStyle)
570    {
571       char from, to;
572       if(unixStyle)
573          from = '\\', to = '/';
574       else
575          from = '/', to = '\\';
576       if(compilerConfigs && compilerConfigs.count)
577       {
578          int i;
579          for(config : compilerConfigs)
580          {
581             if(config.includeDirs && config.includeDirs.count)
582             {
583                for(i = 0; i < config.includeDirs.count; i++)
584                {
585                   if(config.includeDirs[i] && config.includeDirs[i][0])
586                      ChangeCh(config.includeDirs[i], from, to);
587                }
588             }
589             if(config.libraryDirs && config.libraryDirs.count)
590             {
591                for(i = 0; i < config.libraryDirs.count; i++)
592                {
593                   if(config.libraryDirs[i] && config.libraryDirs[i][0])
594                      ChangeCh(config.libraryDirs[i], from, to);
595                }
596             }
597             if(config.executableDirs && config.executableDirs.count)
598             {
599                for(i = 0; i < config.executableDirs.count; i++)
600                {
601                   if(config.executableDirs[i] && config.executableDirs[i][0])
602                      ChangeCh(config.executableDirs[i], from, to);
603                }
604             }
605          }
606       }
607       recentFiles.changeChar(from, to);
608       recentProjects.changeChar(from, to);
609       if(docDir && docDir[0])
610          ChangeCh(docDir, from, to);
611       if(ideFileDialogLocation && ideFileDialogLocation[0])
612          ChangeCh(ideFileDialogLocation, from, to);
613       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
614          ChangeCh(ideProjectFileDialogLocation, from, to);
615
616       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
617          ChangeCh(projectDefaultTargetDir, from, to);
618       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
619          ChangeCh(projectDefaultIntermediateObjDir, from, to);
620
621       if(compilerConfigsDir && compilerConfigsDir[0])
622          ChangeCh(compilerConfigsDir, from, to);
623    }
624
625    void ManagePortablePaths(char * location, bool makeAbsolute)
626    {
627       int c;
628       if(compilerConfigs && compilerConfigs.count)
629       {
630          for(config : compilerConfigs)
631          {
632             DirTypes t;
633             for(t = 0; t < DirTypes::enumSize; t++)
634             {
635                Array<String> dirs = null;
636                if(t == executables) dirs = config.executableDirs;
637                else if(t == includes) dirs = config.includeDirs;
638                else if(t == libraries) dirs = config.libraryDirs;
639                if(dirs && dirs.count)
640                {
641                   for(c = 0; c < dirs.count; c++)
642                   {
643                      if(dirs[c] && dirs[c][0])
644                         dirs[c] = UpdatePortablePath(dirs[c], location, makeAbsolute);
645                   }
646                }
647             }
648          }
649       }
650       if(recentFiles && recentFiles.count)
651       {
652          for(c = 0; c < recentFiles.count; c++)
653          {
654             if(recentFiles[c] && recentFiles[c][0])
655                recentFiles[c] = UpdatePortablePath(recentFiles[c], location, makeAbsolute);
656          }
657       }
658       if(recentProjects && recentProjects.count)
659       {
660          for(c = 0; c < recentProjects.count; c++)
661          {
662             if(recentProjects[c] && recentProjects[c][0])
663                recentProjects[c] = UpdatePortablePath(recentProjects[c], location, makeAbsolute);
664          }
665       }
666       if(docDir && docDir[0])
667          docDir = UpdatePortablePath(docDir, location, makeAbsolute);
668       if(ideFileDialogLocation && ideFileDialogLocation[0])
669          ideFileDialogLocation = UpdatePortablePath(ideFileDialogLocation, location, makeAbsolute);
670       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
671          ideProjectFileDialogLocation = UpdatePortablePath(ideProjectFileDialogLocation, location, makeAbsolute);
672
673       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
674          projectDefaultTargetDir = UpdatePortablePath(projectDefaultTargetDir, location, makeAbsolute);
675       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
676          projectDefaultIntermediateObjDir = UpdatePortablePath(projectDefaultIntermediateObjDir, location, makeAbsolute);
677
678       if(compilerConfigsDir && compilerConfigsDir[0])
679          compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
680    }
681
682    char * UpdatePortablePath(char * path, const char * location, bool makeAbsolute)
683    {
684       char * output;
685       if(makeAbsolute)
686       {
687          char p[MAX_LOCATION];
688          strcpy(p, location);
689          PathCatSlash(p, path);
690          delete path;
691          output = CopyString(p);
692       }
693       else
694       {
695          PathRelationship rel = eString_PathRelated(path, location, null);
696          if(rel == subPath || rel == identical)
697          {
698             char p[MAX_LOCATION];
699             MakePathRelative(path, location, p);
700             if(!*p) strcpy(p, "./");
701             else ChangeCh(p, '\\', '/');
702             delete path;
703             output = CopyString(p);
704          }
705          else
706             output = path;
707       }
708       return output;
709    }
710 }
711
712 class RecentPaths : Array<String>
713 {
714    IteratorPointer Add(T value)
715    {
716       int c;
717       char * filePath = (char *)value;
718       ChangeCh(filePath, '\\', '/');
719       for(c = 0; c < count; c++)
720       {
721          if(this[c] && !fstrcmp(this[c], filePath))
722          {
723             Delete((void *)&this[c]);
724             c--;
725          }
726       }
727       return Array::Add((T)filePath);
728    }
729
730    IteratorPointer addRecent(T value)
731    {
732       int c;
733       char * filePath = (char *)value;
734       ChangeCh(filePath, '\\', '/');
735       for(c = 0; c < count; c++)
736       {
737          if(this[c] && !fstrcmp(this[c], filePath))
738          {
739             Delete((void *)&this[c]);
740             c--;
741          }
742       }
743       while(count >= MaxRecent)
744          Delete(GetLast());
745       return Insert(null, filePath);
746    }
747
748    void changeChar(char from, char to)
749    {
750       if(this && count)
751       {
752          int c;
753          for(c = 0; c < count; c++)
754          {
755             if(this[c] && this[c][0])
756                ChangeCh(this[c], from, to);
757          }
758       }
759    }
760 }
761
762 static const char * compilerTypeNames[CompilerType] = { "GCC", "TCC", "PCC", "VS8", "VS9", "VS10" };
763 static const char * compilerTypeVersionString[CompilerType] = { "", "", "", "8.00", "9.00", "10.00" };
764 static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "", "", "", "9.00", "10.00", "11.00" };
765 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
766 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
767 // TODO: i18n with Array
768 static Array<const String> compilerTypeLongNames
769 { [
770    $"GNU Compiler Collection (GCC) / GNU Make",
771    $"Tiny C Compiler / GNU Make",
772    $"Portable C Compiler / GNU Make",
773    $"Microsoft Visual Studio 2005 (8.0) Compiler",
774    $"Microsoft Visual Studio 2008 (9.0) Compiler",
775    $"Microsoft Visual Studio 2010 (10.0) Compiler"
776 ] };
777 const CompilerType firstCompilerType = gcc;
778 const CompilerType lastCompilerType = vs10;
779 public enum CompilerType
780 {
781    gcc, tcc, pcc, vs8, vs9, vs10;
782
783    property bool isVC
784    {
785       get { return this == vs8 || this == vs9 || this == vs10; }
786    }
787
788    property const char *
789    {
790       get { return OnGetString(null, null, null); }
791       set
792       {
793          if(value)
794          {
795             CompilerType c;
796             for(c = firstCompilerType; c <= lastCompilerType; c++)
797                if(!strcmpi(value, compilerTypeNames[c]))
798                   return c;
799          }
800          return gcc;
801       }
802    };
803
804    property const char * longName { get { return OnGetString(null, (void*)1, null); } };
805    property const char * versionString { get { return OnGetString(null, (void*)2, null); } };
806    property const char * yearString { get { return OnGetString(null, (void*)3, null); } };
807    property const char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
808    property const char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
809
810    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
811    {
812       if(this >= firstCompilerType && this <= lastCompilerType)
813       {
814          if(tempString)
815             strcpy(tempString, compilerTypeNames[this]);
816          if(fieldData == null)
817             return compilerTypeNames[this];
818          else if(fieldData == (void*)1)
819             return compilerTypeLongNames[this];
820          else if(fieldData == (void*)2)
821             return compilerTypeVersionString[this];
822          else if(fieldData == (void*)3)
823             return compilerTypeYearString[this];
824          else if(fieldData == (void*)4)
825             return compilerTypeProjectFileExtension[this];
826          else if(fieldData == (void*)5)
827             return compilerTypeSolutionFileVersionString[this];
828       }
829       return null;
830    }
831 };
832
833 class CompilerConfig
834 {
835    class_no_expansion;
836
837    numJobs = 1;
838 public:
839    property const char * name
840    {
841       set
842       {
843          delete name;
844          if(value)
845             name = CopyString(value);
846       }
847       get { return name; }
848    }
849    bool readOnly;
850    CompilerType type;
851    Platform targetPlatform;
852    int numJobs;
853    property const char * makeCommand
854    {
855       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
856       get { return makeCommand; }
857       isset { return makeCommand && makeCommand[0]; }
858    }
859    property const char * ecpCommand
860    {
861       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
862       get { return ecpCommand; }
863       isset { return ecpCommand && ecpCommand[0]; }
864    }
865    property const char * eccCommand
866    {
867       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
868       get { return eccCommand; }
869       isset { return eccCommand && eccCommand[0]; }
870    }
871    property const char * ecsCommand
872    {
873       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
874       get { return ecsCommand; }
875       isset { return ecsCommand && ecsCommand[0]; }
876    }
877    property const char * earCommand
878    {
879       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
880       get { return earCommand; }
881       isset { return earCommand && earCommand[0]; }
882    }
883    property const char * cppCommand
884    {
885       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
886       get { return cppCommand; }
887       isset { return cppCommand && cppCommand[0]; }
888    }
889    property const char * ccCommand
890    {
891       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
892       get { return ccCommand; }
893       isset { return ccCommand && ccCommand[0]; }
894    }
895    property const char * cxxCommand
896    {
897       set { delete cxxCommand; if(value && value[0]) cxxCommand = CopyString(value); }
898       get { return cxxCommand; }
899       isset { return cxxCommand && cxxCommand[0]; }
900    }
901    property const char * arCommand
902    {
903       set { delete arCommand; if(value && value[0]) arCommand = CopyString(value); }
904       get { return arCommand; }
905       isset { return arCommand && arCommand[0]; }
906    }
907    property const char * ldCommand
908    {
909       set { delete ldCommand; if(value && value[0]) ldCommand = CopyString(value); }
910       get { return ldCommand; }
911       isset { return ldCommand && ldCommand[0]; }
912    }
913    property const char * objectFileExt
914    {
915       set { delete objectFileExt; if(value && value[0]) objectFileExt = CopyString(value); }
916       get { return objectFileExt && objectFileExt[0] ? objectFileExt : objectDefaultFileExt ; }
917       isset { return objectFileExt && objectFileExt[0] && strcmp(objectFileExt, objectDefaultFileExt); }
918    }
919    property const char * outputFileExt
920    {
921       set { delete outputFileExt; if(value && value[0]) outputFileExt = CopyString(value); }
922       get { return outputFileExt; }
923       isset { return outputFileExt && outputFileExt[0]; }
924    }
925    property const char * executableLauncher
926    {
927       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
928       get { return executableLauncher; }
929       isset { return executableLauncher && executableLauncher[0]; }
930    }
931    // TODO: implement CompilerConfig::windresCommand
932    bool ccacheEnabled;
933    bool distccEnabled;
934    // deprecated
935    property bool supportsBitDepth { set { } get { return true; } isset { return false; } }
936
937    property const char * distccHosts
938    {
939       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
940       get { return distccHosts; }
941       isset { return distccHosts && distccHosts[0]; }
942    }
943    property const char * gnuToolchainPrefix
944    {
945       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
946       get { return gnuToolchainPrefix; }
947       isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
948    }
949    property const char * sysroot
950    {
951       set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
952       get { return sysroot; }
953       isset { return sysroot && sysroot[0]; }
954    }
955    bool resourcesDotEar;
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          resourcesDotEar
1190       };
1191       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
1192       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
1193       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
1194       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
1195       for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
1196       for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
1197       for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
1198       for(s : eCcompilerFlags) copy.eCcompilerFlags.Add(CopyString(s));
1199       for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
1200
1201       incref copy;
1202       return copy;
1203    }
1204 }
1205
1206 struct LanguageOption
1207 {
1208    const String name;
1209    const String bitmap;
1210    const String code;
1211    BitmapResource res;
1212
1213    const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
1214    {
1215       return name;
1216    }
1217
1218    void OnDisplay(Surface surface, int x, int y, int width, void * data, Alignment alignment, DataDisplayFlags flags)
1219    {
1220       Bitmap icon = res ? res.bitmap : null;
1221       int w = 8 + 16;
1222       if(icon)
1223          surface.Blit(icon, x + (16 - icon.width) / 2,y+2,0,0, icon.width, icon.height);
1224       class::OnDisplay(surface, x + w, y, width - w, null, alignment, flags);
1225    }
1226 };
1227
1228 Array<LanguageOption> languages
1229 { [
1230    { "English",            ":countryCode/gb.png", "" },
1231    { "汉语",                ":countryCode/cn.png", "zh_CN" },
1232    { "Español",            ":countryCode/es.png", "es" },
1233    { "Português (Brazil)", ":countryCode/br.png", "pt_BR" },
1234    { "Русский (43%)",      ":countryCode/ru.png", "ru" },
1235    { "Nederlandse (13%)",  ":countryCode/nl.png", "nl" },
1236    { "Tiếng Việt (12%)",   ":countryCode/vn.png", "vi" },
1237    { "मराठी (10%)",          ":countryCode/in.png", "mr" },
1238    { "Hebrew (8%)",        ":countryCode/il.png", "he" },
1239    { "Magyar (8%)",        ":countryCode/hu.png", "hu" }
1240 ] };
1241
1242 const String GetLanguageString()
1243 {
1244    char * dot, * colon;
1245    static char lang[256];
1246    const String language = getenv("ECERE_LANGUAGE");
1247    if(!language) language = getenv("LANGUAGE");
1248    if(!language) language = getenv("LC_ALL");
1249    if(!language) language = getenv("LC_MESSAGES");
1250    if(!language) language = getenv("LANG");
1251    if(!language) language = "";
1252    if(language && (colon = strchr(language, ':')))
1253    {
1254       if(lang != language)
1255          strncpy(lang, language, sizeof(lang));
1256       lang[sizeof(lang)-1] = 0;
1257       lang[colon - language] = 0;
1258       language = lang;
1259    }
1260    if(language && (dot = strchr(language, '.')))
1261    {
1262       if(lang != language)
1263          strncpy(lang, language, sizeof(lang));
1264       lang[sizeof(lang)-1] = 0;
1265       lang[dot - language] = 0;
1266       language = lang;
1267    }
1268    return language;
1269 }
1270
1271 bool LanguageRestart(const char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
1272 {
1273    bool restart = true;
1274    String command = null;
1275    int arg0Len = (int)strlen(app.argv[0]);
1276    int len = arg0Len;
1277    int j;
1278    char ch;
1279
1280    if(ide)
1281    {
1282       Window w;
1283
1284       if(projectView)
1285       {
1286          Window w;
1287          for(w = ide.firstChild; w; w = w.next)
1288          {
1289             if(w.isActiveClient && w.isDocument)
1290             {
1291                if(!w.CloseConfirmation(true))
1292                {
1293                   restart = false;
1294                   break;
1295                }
1296             }
1297          }
1298          if(restart)
1299          {
1300             if(!projectView.CloseConfirmation(true))
1301                restart = false;
1302             if(projectView.fileName)
1303             {
1304                const char * name = projectView.fileName;
1305                if(name)
1306                {
1307                   for(j = 0; (ch = name[j]); j++)
1308                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1309                }
1310             }
1311
1312             command = new char[len + 1];
1313
1314             strcpy(command, app.argv[0]);
1315             len = arg0Len;
1316             if(projectView.fileName)
1317             {
1318                strcat(command, " ");
1319                len++;
1320                ReplaceSpaces(command + len, projectView.fileName);
1321             }
1322          }
1323          if(restart)
1324          {
1325             for(w = ide.firstChild; w; w = w.next)
1326                if(w.isActiveClient && w.isDocument)
1327                   w.modifiedDocument = false;
1328             projectView.modifiedDocument = false;
1329          }
1330       }
1331       else
1332       {
1333          for(w = ide.firstChild; w; w = w.next)
1334          {
1335             if(w.isActiveClient && w.isDocument)
1336             {
1337                if(!w.CloseConfirmation(true))
1338                {
1339                   restart = false;
1340                   break;
1341                }
1342                if(w.fileName)
1343                {
1344                   const char * name = w.fileName;
1345                   len++;
1346                   for(j = 0; (ch = name[j]); j++)
1347                      len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1348                }
1349             }
1350          }
1351
1352          if(restart)
1353          {
1354             command = new char[len + 1];
1355             strcpy(command, app.argv[0]);
1356             len = arg0Len;
1357
1358             for(w = ide.firstChild; w; w = w.next)
1359             {
1360                if(w.isActiveClient && w.isDocument)
1361                {
1362                   const char * name = w.fileName;
1363                   if(name)
1364                   {
1365                      strcat(command, " ");
1366                      len++;
1367                      ReplaceSpaces(command + len, name);
1368                      len = (int)strlen(command);
1369                   }
1370                }
1371             }
1372          }
1373          if(restart)
1374          {
1375             for(w = ide.firstChild; w; w = w.next)
1376                if(w.isActiveClient && w.isDocument)
1377                   w.modifiedDocument = false;
1378          }
1379       }
1380       if(restart)
1381       {
1382          settings.language = code;
1383          settingsContainer.Save();
1384
1385 #if defined(__WIN32__)
1386          // Set LANGUAGE environment variable
1387          {
1388             HKEY key = null;
1389             uint16 wLanguage[256];
1390             DWORD status;
1391             wLanguage[0] = 0;
1392
1393             RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
1394             if(key)
1395             {
1396                UTF8toUTF16Buffer(code, wLanguage, sizeof(wLanguage) / sizeof(uint16));
1397                RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
1398                RegCloseKey(key);
1399             }
1400          }
1401 #endif
1402
1403          if(eClass_IsDerived(app._class, class(GuiApplication)))
1404          {
1405             GuiApplication guiApp = (GuiApplication)app;
1406             guiApp.desktop.Destroy(0);
1407          }
1408       }
1409    }
1410    else
1411    {
1412       int i;
1413       for(i = 1; i < app.argc; i++)
1414       {
1415          const char * arg = app.argv[i];
1416          len++;
1417          for(j = 0; (ch = arg[j]); j++)
1418             len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
1419       }
1420
1421       command = new char[len + 1];
1422       strcpy(command, app.argv[0]);
1423       len = arg0Len;
1424       for(i = 1; i < app.argc; i++)
1425       {
1426          strcat(command, " ");
1427          len++;
1428          ReplaceSpaces(command + len, app.argv[i]);
1429          len = (int)strlen(command);
1430       }
1431    }
1432
1433    if(restart)
1434    {
1435       SetEnvironment("ECERE_LANGUAGE", code);
1436       if(wait)
1437          ExecuteWait(command);
1438       else
1439          Execute(command);
1440    }
1441    delete command;
1442    return restart;
1443 }