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