i18n: Update
[sdk] / ide / src / IDESettings.ec
index c030d02..d542dab 100644 (file)
@@ -1,7 +1,7 @@
 #ifdef ECERE_STATIC
-import static "ecere"
+public import static "ecere"
 #else
-import "ecere"
+public import "ecere"
 #endif
 
 import "StringsBox"
@@ -14,21 +14,88 @@ enum DirTypes { includes, libraries, executables };
 
 define defaultCompilerName = "Default";
 
-define defaultObjDirExpression = "obj/$(CONFIG).$(PLATFORM)";
+define defaultObjDirExpression = "obj/$(CONFIG).$(PLATFORM)$(COMPILER_SUFFIX)$(DEBUG_SUFFIX)";
 
-char * settingsDirectoryNames[DirTypes] = 
+char * settingsDirectoryNames[DirTypes] =
 {
    "Include Files",
    "Library Files",
    "Executable Files"
 };
 
+// This function cannot accept same pointer for source and output
+// todo: rename ReplaceSpaces to EscapeSpaceAndSpecialChars or something
+void ReplaceSpaces(char * output, char * source)
+{
+   int c, dc;
+   char ch, pch = 0;
+
+   for(c = 0, dc = 0; (ch = source[c]); c++, dc++)
+   {
+      if(ch == ' ') output[dc++] = '\\';
+      if(ch == '\"') output[dc++] = '\\';
+      if(ch == '&') output[dc++] = '\\';
+      if(pch != '$')
+      {
+         if(ch == '(' || ch == ')') output[dc++] = '\\';
+         pch = ch;
+      }
+      else if(ch == ')')
+         pch = 0;
+      output[dc] = ch;
+   }
+   output[dc] = '\0';
+}
+
+
 enum GlobalSettingsChange { none, editorSettings, projectOptions, compilerSettings };
 
+enum PathRelationship { unrelated, identical, siblings, subPath, parentPath, insuficientInput, pathEmpty, toEmpty, pathNull, toNull, bothEmpty, bothNull };
+PathRelationship eString_PathRelated(char * path, char * to, char * pathDiff)
+{
+   PathRelationship result;
+   if(pathDiff) *pathDiff = '\0';
+   if(path && *path && to && *to)
+   {
+      char toPart[MAX_FILENAME], toRest[MAX_LOCATION];
+      char pathPart[MAX_FILENAME], pathRest[MAX_LOCATION];
+      strcpy(toRest, to);
+      strcpy(pathRest, path);
+      for(; toRest[0] && pathRest[0];)
+      {
+         SplitDirectory(toRest, toPart, toRest);
+         SplitDirectory(pathRest, pathPart, pathRest);
+         if(!fstrcmp(pathPart, toPart)) result = siblings;
+         else break;
+      }
+      if(result == siblings)
+      {
+         if(!*toRest && !*pathRest) result = identical;
+         else if(!*pathRest) result = parentPath;
+         else result = subPath;
+         if(pathDiff && result != identical) strcpy(pathDiff, *pathRest == '\0' ? toRest : pathRest);
+      }
+      else result = unrelated;
+   }
+   else
+   {
+      if(path && to)
+      {
+         if(!*path && !*to) result = bothEmpty;
+         else if(!*path) result = pathEmpty;
+         else result = toEmpty;
+      }
+      else if(!path && !to) result = bothNull;
+      else if(!path) result = pathNull;
+      else result = toNull;
+   }
+   return result;
+}
+
 char * CopyValidateMakefilePath(char * path)
 {
-   const int map[]  =    { 0,           1,           2,             3,             4,           0,            1,                  7         };
-   const char * vars[] = { "$(MODULE)", "$(CONFIG)", "$(PLATFORM)", "$(COMPILER)", "$(TARGET)", "$(PROJECT)", "$(CONFIGURATION)", (char *)0 };
+   const int map[]  =    {           0,           1,             2,             3,           4,                    5,                 6,            0,                   1,                    2,        7 };
+   const char * vars[] = { "$(MODULE)", "$(CONFIG)", "$(PLATFORM)", "$(COMPILER)", "$(TARGET)", "$(COMPILER_SUFFIX)", "$(DEBUG_SUFFIX)", "$(PROJECT)",  "$(CONFIGURATION)", "$(TARGET_PLATFORM)",(char *)0 };
 
    char * copy = null;
    if(path)
@@ -42,7 +109,7 @@ char * CopyValidateMakefilePath(char * path)
          char * tmp = copy;
          char * start = tmp;
          Array<char *> parts { };
-         
+
          for(c=0; c<len; c++)
          {
             if(tmp[c] == '$')
@@ -83,6 +150,50 @@ char * CopyValidateMakefilePath(char * path)
    return copy;
 }
 
+void ValidPathBufCopy(char *output, char *input)
+{
+#ifdef __WIN32__
+   bool volumePath = false;
+#endif
+   strcpy(output, input);
+   TrimLSpaces(output, output);
+   TrimRSpaces(output, output);
+   MakeSystemPath(output);
+#ifdef __WIN32__
+   if(output[0] && output[1] == ':')
+   {
+      output[1] = '_';
+      volumePath = true;
+   }
+#endif
+   {
+      char * chars = "*|:\",<>?";
+      char ch, * s = output, * o = output;
+      while((ch = *s++)) { if(!strchr(chars, ch)) *o++ = ch; }
+      *o = '\0';
+   }
+#ifdef __WIN32__
+   if(volumePath && output[0])
+      output[1] = ':';
+#endif
+}
+
+void RemoveTrailingPathSeparator(char *path)
+{
+   int len;
+   len = strlen(path);
+   if(len>1 && path[len-1] == DIR_SEP)
+      path[--len] = '\0';
+}
+
+void BasicValidatePathBoxPath(PathBox pathBox)
+{
+   char path[MAX_LOCATION];
+   ValidPathBufCopy(path, pathBox.path);
+   RemoveTrailingPathSeparator(path);
+   pathBox.path = path;
+}
+
 CompilerConfig MakeDefaultCompiler(char * name, bool readOnly)
 {
    CompilerConfig defaultCompiler
@@ -98,7 +209,8 @@ CompilerConfig MakeDefaultCompiler(char * name, bool readOnly)
       ecsDefaultCommand,
       earDefaultCommand,
       cppDefaultCommand,
-      ccDefaultCommand
+      ccDefaultCommand,
+      cxxDefaultCommand
    };
    incref defaultCompiler;
    return defaultCompiler;
@@ -114,12 +226,19 @@ class IDESettingsContainer : GlobalSettings
 
    virtual void OnLoad(GlobalSettingsData data);
 
+   char moduleLocation[MAX_LOCATION];
+
 private:
+   FileSize settingsFileSize;
+
    IDESettingsContainer()
    {
       char path[MAX_LOCATION];
       char * start;
-      LocateModule(null, path);
+      LocateModule(null, moduleLocation);
+      strcpy(path, moduleLocation);
+      StripLastDirectory(moduleLocation, moduleLocation);
+      ChangeCh(moduleLocation, '\\', '/');
       // PortableApps.com directory structure
       if((start = strstr(path, "\\App\\EcereSDK\\bin\\ide.exe")))
       {
@@ -136,7 +255,7 @@ private:
          PathCat(defaultConfigFilePath, "App");
          PathCat(defaultConfigFilePath, "DefaultData");
          PathCat(defaultConfigFilePath, "ecereIDE.ini");
-         
+
          if(FileExists(defaultConfigFilePath))
          {
             if(!FileExists(configFilePath))
@@ -148,9 +267,9 @@ private:
             }
             PathCat(path, "Data");
             // the forced settings location will only be
-            // used if the running ide's path matches 
+            // used if the running ide's path matches
             // the PortableApps.com directory structure
-            // and the default ini file is found in 
+            // and the default ini file is found in
             // the DefaultData directory
             settingsLocation = path;
             portable = true;
@@ -160,20 +279,39 @@ private:
 
    void OnAskReloadSettings()
    {
-      /*if(MessageBox { type = YesNo, master = this, 
-            text = "Global Settings Modified Externally", 
+      /*if(MessageBox { type = YesNo, master = this,
+            text = "Global Settings Modified Externally",
             contents = "The global settings were modified by another instance.\n"
             "Would you like to reload them?" }.Modal() == Yes)*/
       {
-         Load();
+         double o, n;
+         FileSize newSettingsFileSize;
+         FileGetSize(settingsFilePath, &newSettingsFileSize);
+         o = settingsFileSize;
+         n = newSettingsFileSize;
+         if(o - n < 2048)
+            Load();
+         else
+         {
+            GuiApplication app = ((GuiApplication)__thisModule.application);
+            Window w;
+            for(w = app.desktop.firstChild; w && (!w.created || !w.visible); w = w.next);
+            MessageBox { master = w, type = ok,
+                  text = "Global Settings Modified Externally",
+                  contents = "The global settings were modified by another process and a drastic shrinking of the settings file was detected.\n"
+                  "The new settings will not be loaded to prevent loss of your ide settings.\n"
+                  "Please check your settings file and make sure to save this IDE's global settings if your settings file has been compromised."
+                  }.Create();
+            //Save();
+         }
       }
    }
 
-   void Load()
+   SettingsIOResult Load()
    {
       SettingsIOResult result = GlobalSettings::Load();
       IDESettings data = (IDESettings)this.data;
-      //if(result == success)
+      CompilerConfig defaultCompiler = null;
       if(!data)
       {
          this.data = IDESettings { };
@@ -185,7 +323,7 @@ private:
             bool loaded;
             OldIDESettings oldSettings { };
             Close();
-            loaded = oldSettings.Load();
+            loaded = oldSettings.Load() == success;
             oldSettings.Close();
             if(loaded)
             {
@@ -203,11 +341,10 @@ private:
                data.useFreeCaret = oldSettings.useFreeCaret;
                data.showLineNumbers = oldSettings.showLineNumbers;
                data.caretFollowsScrolling = oldSettings.caretFollowsScrolling;
-               delete data.displayDriver;
-               data.displayDriver = oldSettings.displayDriver;
+               delete data.displayDriver; data.displayDriver = CopyString(oldSettings.displayDriver);
                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
-                        
+
                Save();
                result = success;
             }
@@ -215,55 +352,60 @@ private:
          }
          if(result == fileNotFound || !data)
          {
-            CompilerConfig defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
-            // We incref the compilers below, so reset refCount to 0
-            defaultCompiler._refCount = 0;
             data = (IDESettings)this.data;
-
-            data.compilerConfigs.Add(defaultCompiler);
-            data.useFreeCaret = true;
+            data.useFreeCaret = false; //true;
             data.showLineNumbers = true;
-            data.caretFollowsScrolling = true;
+            data.caretFollowsScrolling = false; //true;
          }
       }
+      // Ensure we have a default compiler
+      defaultCompiler = data.GetCompilerConfig(defaultCompilerName);
+      if(!defaultCompiler)
+      {
+         defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
+         data.compilerConfigs.Add(defaultCompiler);
+      }
+
+      // We incref the compilers below, so reset refCount to 0
+      defaultCompiler._refCount = 0;
+
       CloseAndMonitor();
+      FileGetSize(settingsFilePath, &settingsFileSize);
       if(data.compilerConfigs)
       {
          for(c : data.compilerConfigs)
          {
             CompilerConfig compiler = c;
+            char * cxxCommand = compiler.cxxCommand;
+            if(!cxxCommand || !cxxCommand[0])
+               compiler.cxxCommand = cxxDefaultCommand;
             incref compiler;
          }
       }
-      if(portable)
-      {
-         char location[MAX_LOCATION];
-         LocateModule(null, location);
-         StripLastDirectory(location, location);
-         if(location[0] && FileExists(location).isDirectory)
-         {
-            if(data.portableLocation && data.portableLocation[0] && strcmp(data.portableLocation, location))
-            {
-               data.UpdatePortablePaths(data.portableLocation, location);
-               data.portableLocation = location;
-               Save();
-            }
-         }
-      }
+      if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
+         data.ManagePortablePaths(moduleLocation, true);
       data.ForcePathSeparatorStyle(true);
       OnLoad(data);
+      return result;
    }
 
-   void Save()
+   SettingsIOResult Save()
    {
+      SettingsIOResult result;
+
       IDESettings data = (IDESettings)this.data;
       Platform runtimePlatform = GetRuntimePlatform();
-      data.ForcePathSeparatorStyle(runtimePlatform != win32);
-      if(!GlobalSettings::Save())
+      if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
+         data.ManagePortablePaths(moduleLocation, false);
+      data.ForcePathSeparatorStyle(true);
+      result = GlobalSettings::Save();
+      if(result != success)
          PrintLn("Error saving IDE settings");
-      if(runtimePlatform == win32)
-         data.ForcePathSeparatorStyle(true);
+      if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
+         data.ManagePortablePaths(moduleLocation, true);
       CloseAndMonitor();
+      FileGetSize(settingsFilePath, &settingsFileSize);
+      return result;
    }
 }
 
@@ -295,7 +437,7 @@ public:
    bool showLineNumbers;
    bool caretFollowsScrolling;
    char * displayDriver;
-   
+
    // TODO: Classify settings
    //EditorSettings editor { };
 
@@ -312,11 +454,11 @@ public:
       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
    }
 
-   property char * portableLocation
+   property char * compilerConfigsDir
    {
-      set { delete portableLocation; if(value && value[0]) portableLocation = CopyString(value); }
-      get { return portableLocation ? portableLocation : ""; }
-      isset { return portableLocation && portableLocation[0]; }
+      set { delete compilerConfigsDir; if(value && value[0]) compilerConfigsDir = CopyString(value); }
+      get { return compilerConfigsDir ? compilerConfigsDir : ""; }
+      isset { return compilerConfigsDir && compilerConfigsDir[0]; }
    }
 
    property char * defaultCompiler
@@ -326,14 +468,26 @@ public:
       isset { return defaultCompiler && defaultCompiler[0]; }
    }
 
+   property String language
+   {
+      set
+      {
+         delete language;
+         language = CopyString(value);
+      }
+      get { return language; }
+      isset { return language != null; }
+   }
+
 private:
    char * docDir;
    char * ideFileDialogLocation;
    char * ideProjectFileDialogLocation;
    char * projectDefaultTargetDir;
    char * projectDefaultIntermediateObjDir;
-   char * portableLocation;
+   char * compilerConfigsDir;
    char * defaultCompiler;
+   String language;
 
    CompilerConfig GetCompilerConfig(String compilerName)
    {
@@ -363,10 +517,10 @@ private:
       recentProjects.Free();
       delete recentProjects;
       delete docDir;
-   
+
       delete projectDefaultTargetDir;
       delete projectDefaultIntermediateObjDir;
-      delete portableLocation;
+      delete compilerConfigsDir;
       delete defaultCompiler;
 
       delete ideFileDialogLocation;
@@ -442,32 +596,30 @@ private:
       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
          ChangeCh(projectDefaultIntermediateObjDir, from, to);
 
-      if(portableLocation && portableLocation[0])
-         ChangeCh(portableLocation, from, to);
+      if(compilerConfigsDir && compilerConfigsDir[0])
+         ChangeCh(compilerConfigsDir, from, to);
    }
 
-   bool UpdatePortablePaths(char * oldPath, char * newPath)
+   void ManagePortablePaths(char * location, bool makeAbsolute)
    {
-      int oldLen = strlen(oldPath);
-      int newLen = strlen(newPath);
+      int c;
       if(compilerConfigs && compilerConfigs.count)
       {
          for(config : compilerConfigs)
          {
-            DirTypes c;
-            for(c = 0; c < DirTypes::enumSize; c++)
+            DirTypes t;
+            for(t = 0; t < DirTypes::enumSize; t++)
             {
-               Array<String> dirs;
-               if(c == executables) dirs = config.executableDirs;
-               else if(c == includes) dirs = config.includeDirs;
-               else if(c == libraries) dirs = config.libraryDirs;
+               Array<String> dirs = null;
+               if(t == executables) dirs = config.executableDirs;
+               else if(t == includes) dirs = config.includeDirs;
+               else if(t == libraries) dirs = config.libraryDirs;
                if(dirs && dirs.count)
                {
-                  int i;
-                  for(i = 0; i < dirs.count; i++)
+                  for(c = 0; c < dirs.count; c++)
                   {
-                     if(dirs[i] && dirs[i][0])
-                        dirs[i] = ReplaceInCopyString(dirs[i], oldPath, oldLen, newPath, newLen);
+                     if(dirs[c] && dirs[c][0])
+                        dirs[c] = UpdatePortablePath(dirs[c], location, makeAbsolute);
                   }
                }
             }
@@ -475,55 +627,61 @@ private:
       }
       if(recentFiles && recentFiles.count)
       {
-         int c;
          for(c = 0; c < recentFiles.count; c++)
          {
             if(recentFiles[c] && recentFiles[c][0])
-               recentFiles[c] = ReplaceInCopyString(recentFiles[c], oldPath, oldLen, newPath, newLen);
+               recentFiles[c] = UpdatePortablePath(recentFiles[c], location, makeAbsolute);
          }
       }
       if(recentProjects && recentProjects.count)
       {
-         int c;
          for(c = 0; c < recentProjects.count; c++)
          {
             if(recentProjects[c] && recentProjects[c][0])
-               recentProjects[c] = ReplaceInCopyString(recentProjects[c], oldPath, oldLen, newPath, newLen);
+               recentProjects[c] = UpdatePortablePath(recentProjects[c], location, makeAbsolute);
          }
       }
       if(docDir && docDir[0])
-         docDir = ReplaceInCopyString(docDir, oldPath, oldLen, newPath, newLen);
+         docDir = UpdatePortablePath(docDir, location, makeAbsolute);
       if(ideFileDialogLocation && ideFileDialogLocation[0])
-         ideFileDialogLocation = ReplaceInCopyString(ideFileDialogLocation, oldPath, oldLen, newPath, newLen);
+         ideFileDialogLocation = UpdatePortablePath(ideFileDialogLocation, location, makeAbsolute);
       if(ideProjectFileDialogLocation && ideProjectFileDialogLocation[0])
-         ideProjectFileDialogLocation = ReplaceInCopyString(ideProjectFileDialogLocation, oldPath, oldLen, newPath, newLen);
+         ideProjectFileDialogLocation = UpdatePortablePath(ideProjectFileDialogLocation, location, makeAbsolute);
 
       if(projectDefaultTargetDir && projectDefaultTargetDir[0])
-         projectDefaultTargetDir = ReplaceInCopyString(projectDefaultTargetDir, oldPath, oldLen, newPath, newLen);
+         projectDefaultTargetDir = UpdatePortablePath(projectDefaultTargetDir, location, makeAbsolute);
       if(projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0])
-         projectDefaultIntermediateObjDir = ReplaceInCopyString(projectDefaultIntermediateObjDir, oldPath, oldLen, newPath, newLen);
+         projectDefaultIntermediateObjDir = UpdatePortablePath(projectDefaultIntermediateObjDir, location, makeAbsolute);
+
+      if(compilerConfigsDir && compilerConfigsDir[0])
+         compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
    }
 
-   char * ReplaceInCopyString(char * string, char * find, int lenFind, char * replace, int lenReplace)
+   char * UpdatePortablePath(char * path, char * location, bool makeAbsolute)
    {
-      char * output = string;
-      char * start;
-      if(/*string && string[0] && */(start = strstr(string, find)))
+      char * output;
+      if(makeAbsolute)
       {
-         if(lenFind == lenReplace)
-            strncpy(start, replace, lenReplace);
-         else
+         char p[MAX_LOCATION];
+         strcpy(p, location);
+         PathCatSlash(p, path);
+         delete path;
+         output = CopyString(p);
+      }
+      else
+      {
+         PathRelationship rel = eString_PathRelated(path, location, null);
+         if(rel == subPath || rel == identical)
          {
-            int newLen = strlen(string) + lenReplace - lenFind + 1;
-            char * newString = new char[newLen];
-            start[0] = '\0';
-            strcpy(newString, string);
-            start += lenFind;
-            strcat(newString, replace);
-            strcat(newString, start);
-            delete string;
-            return newString;
+            char p[MAX_LOCATION];
+            MakePathRelative(path, location, p);
+            if(!*p) strcpy(p, "./");
+            else ChangeCh(p, '\\', '/');
+            delete path;
+            output = CopyString(p);
          }
+         else
+            output = path;
       }
       return output;
    }
@@ -573,15 +731,15 @@ static const char * compilerTypeSolutionFileVersionString[CompilerType] = { "",
 static const char * compilerTypeYearString[CompilerType] = { "", "", "", "2005", "2008", "2010" };
 static const char * compilerTypeProjectFileExtension[CompilerType] = { "", "", "", "vcproj", "vcproj", "vcxproj" };
 // TODO: i18n with Array
-static const char * compilerTypeLongNames[CompilerType] =
-{
-   "GNU Compiler Collection (GCC) / GNU Make",
-   "Tiny C Compiler / GNU Make",
-   "Portable C Compiler / GNU Make",
-   "Microsoft Visual Studio 2005 (8.0) Compiler",
-   "Microsoft Visual Studio 2008 (9.0) Compiler",
-   "Microsoft Visual Studio 2010 (10.0) Compiler"
-};
+static Array<String> compilerTypeLongNames
+{ [
+   $"GNU Compiler Collection (GCC) / GNU Make",
+   $"Tiny C Compiler / GNU Make",
+   $"Portable C Compiler / GNU Make",
+   $"Microsoft Visual Studio 2005 (8.0) Compiler",
+   $"Microsoft Visual Studio 2008 (9.0) Compiler",
+   $"Microsoft Visual Studio 2010 (10.0) Compiler"
+};
 const CompilerType firstCompilerType = gcc;
 const CompilerType lastCompilerType = vs10;
 public enum CompilerType
@@ -597,7 +755,7 @@ public enum CompilerType
    {
       get { return OnGetString(null, null, null); }
       set
-      {  
+      {
          if(value)
          {
             Platform c;
@@ -700,20 +858,42 @@ public:
       get { return ccCommand; }
       isset { return ccCommand && ccCommand[0]; }
    }
-   property char * execPrefixCommand
+   property char * cxxCommand
    {
-      set { delete execPrefixCommand; if(value && value[0]) execPrefixCommand = CopyString(value); }
-      get { return execPrefixCommand; }
-      isset { return execPrefixCommand && execPrefixCommand[0]; }
+      set { delete cxxCommand; if(value && value[0]) cxxCommand = CopyString(value); }
+      get { return cxxCommand; }
+      isset { return cxxCommand && cxxCommand[0]; }
    }
+   property char * execPrefixCommand // <-- old name for json ide settings file compatibility
+   {
+      set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
+      get { return executableLauncher; }
+      isset { return executableLauncher && executableLauncher[0]; }
+   }
+   // TODO: implement CompilerConfig::windresCommand
    bool ccacheEnabled;
    bool distccEnabled;
+   // deprecated
+   property bool supportsBitDepth { set { } get { return true; } isset { return false; } }
+
    property char * distccHosts
    {
       set { delete distccHosts; if(value && value[0]) distccHosts = CopyString(value); }
       get { return distccHosts; }
       isset { return distccHosts && distccHosts[0]; }
    }
+   property char * gccPrefix // <-- old name for json ide settings file compatibility
+   {
+      set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
+      get { return gnuToolchainPrefix; }
+      isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
+   }
+   property char * sysroot
+   {
+      set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
+      get { return sysroot; }
+      isset { return sysroot && sysroot[0]; }
+   }
    property Array<String> includeDirs
    {
       set
@@ -770,6 +950,76 @@ public:
       get { return environmentVars; }
       isset { return environmentVars.count != 0; }
    }
+   property Array<String> prepDirectives
+   {
+      set
+      {
+         prepDirectives.Free();
+         if(value)
+         {
+            delete prepDirectives;
+            prepDirectives = value;
+         }
+      }
+      get { return prepDirectives; }
+      isset { return prepDirectives.count != 0; }
+   }
+   property Array<String> excludeLibs
+   {
+      set
+      {
+         excludeLibs.Free();
+         if(value)
+         {
+            delete excludeLibs;
+            excludeLibs = value;
+         }
+      }
+      get { return excludeLibs; }
+      isset { return excludeLibs.count != 0; }
+   }
+   property Array<String> eCcompilerFlags
+   {
+      set
+      {
+         eCcompilerFlags.Free();
+         if(value)
+         {
+            delete eCcompilerFlags;
+            eCcompilerFlags = value;
+         }
+      }
+      get { return eCcompilerFlags; }
+      isset { return eCcompilerFlags.count != 0; }
+   }
+   property Array<String> compilerFlags
+   {
+      set
+      {
+         compilerFlags.Free();
+         if(value)
+         {
+            delete compilerFlags;
+            compilerFlags = value;
+         }
+      }
+      get { return compilerFlags; }
+      isset { return compilerFlags.count != 0; }
+   }
+   property Array<String> linkerFlags
+   {
+      set
+      {
+         linkerFlags.Free();
+         if(value)
+         {
+            delete linkerFlags;
+            linkerFlags = value;
+         }
+      }
+      get { return linkerFlags; }
+      isset { return linkerFlags.count != 0; }
+   }
 private:
    Array<String> includeDirs { };
    Array<String> libraryDirs { };
@@ -777,6 +1027,11 @@ private:
    // TODO: Can JSON parse and serialize maps?
    //EnvironmentVariables { };
    Array<NamedString> environmentVars { };
+   Array<String> prepDirectives { };
+   Array<String> excludeLibs { };
+   Array<String> eCcompilerFlags { };
+   Array<String> compilerFlags { };
+   Array<String> linkerFlags { };
    char * name;
    char * makeCommand;
    char * ecpCommand;
@@ -785,8 +1040,11 @@ private:
    char * earCommand;
    char * cppCommand;
    char * ccCommand;
-   char * execPrefixCommand;
+   char * cxxCommand;
+   char * executableLauncher;
    char * distccHosts;
+   char * gnuToolchainPrefix;
+   char * sysroot;
    /*union
    {
       struct { Array<String> includes, libraries, executables; };
@@ -802,13 +1060,21 @@ private:
       delete earCommand;
       delete cppCommand;
       delete ccCommand;
+      delete cxxCommand;
       delete makeCommand;
-      delete execPrefixCommand;
+      delete executableLauncher;
       delete distccHosts;
+      delete gnuToolchainPrefix;
+      delete sysroot;
       if(environmentVars) environmentVars.Free();
       if(includeDirs) { includeDirs.Free(); }
       if(libraryDirs) { libraryDirs.Free(); }
       if(executableDirs) { executableDirs.Free(); }
+      if(prepDirectives) { prepDirectives.Free(); }
+      if(excludeLibs) { excludeLibs.Free(); }
+      if(compilerFlags) { compilerFlags.Free(); }
+      if(eCcompilerFlags) { eCcompilerFlags.Free(); }
+      if(linkerFlags) { linkerFlags.Free(); }
    }
    CompilerConfig Copy()
    {
@@ -826,17 +1092,227 @@ private:
          earCommand,
          cppCommand,
          ccCommand,
-         execPrefixCommand,
+         cxxCommand,
+         executableLauncher,
          ccacheEnabled,
          distccEnabled,
-         distccHosts
+         false,
+         distccHosts,
+         gnuToolchainPrefix,
+         sysroot
       };
       for(s : includeDirs) copy.includeDirs.Add(CopyString(s));
       for(s : libraryDirs) copy.libraryDirs.Add(CopyString(s));
       for(s : executableDirs) copy.executableDirs.Add(CopyString(s));
       for(ns : environmentVars) copy.environmentVars.Add(NamedString { name = ns.name, string = ns.string });
+      for(s : prepDirectives) copy.prepDirectives.Add(CopyString(s));
+      for(s : excludeLibs) copy.excludeLibs.Add(CopyString(s));
+      for(s : compilerFlags) copy.compilerFlags.Add(CopyString(s));
+      for(s : eCcompilerFlags) copy.eCcompilerFlags.Add(CopyString(s));
+      for(s : linkerFlags) copy.linkerFlags.Add(CopyString(s));
 
       incref copy;
       return copy;
    }
 }
+
+struct LanguageOption
+{
+   String name;
+   String bitmap;
+   String code;
+   BitmapResource res;
+
+   char * OnGetString(char * tempString, void * fieldData, bool * needClass)
+   {
+      return name;
+   }
+
+   void OnDisplay(Surface surface, int x, int y, int width, void * data, Alignment alignment, DataDisplayFlags flags)
+   {
+      Bitmap icon = res ? res.bitmap : null;
+      int w = 8 + 16;
+      if(icon)
+         surface.Blit(icon, x + (16 - icon.width) / 2,y+2,0,0, icon.width, icon.height);
+      class::OnDisplay(surface, x + w, y, width - w, null, alignment, flags);
+   }
+};
+
+Array<LanguageOption> languages
+{ [
+   { "English",            ":countryCode/gb.png", "" },
+   { "汉语",                ":countryCode/cn.png", "zh_CN" },
+   { "Español",            ":countryCode/es.png", "es" },
+   { "Русский (43%)",      ":countryCode/ru.png", "ru" },
+   { "Português (28%)",    ":countryCode/pt.png", "pt_BR" },
+   { "Nederlandse (13%)",  ":countryCode/nl.png", "nl" },
+   { "Tiếng Việt (12%)",   ":countryCode/vn.png", "vi" },
+   { "मराठी (10%)",          ":countryCode/in.png", "mr" },
+   { "Hebrew (8%)",        ":countryCode/il.png", "he" },
+   { "Magyar (8%)",        ":countryCode/hu.png", "hu" }
+] };
+
+String GetLanguageString()
+{
+   String language = getenv("LANGUAGE");
+   if(!language) language = getenv("LC_ALL");
+   if(!language) language = getenv("LC_MESSAGES");
+   if(!language) language = getenv("LANG");
+   if(!language) language = "";
+   return language;
+}
+
+bool LanguageRestart(char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
+{
+   bool restart = true;
+   String command = null;
+   int arg0Len = strlen(app.argv[0]);
+   int len = arg0Len;
+   int j;
+   char ch;
+
+   if(ide)
+   {
+      Window w;
+
+      if(projectView)
+      {
+         Window w;
+         for(w = ide.firstChild; w; w = w.next)
+         {
+            if(w.isActiveClient && w.isDocument)
+            {
+               if(!w.CloseConfirmation(true))
+               {
+                  restart = false;
+                  break;
+               }
+            }
+         }
+         if(restart)
+         {
+            if(!projectView.CloseConfirmation(true))
+               restart = false;
+            if(projectView.fileName)
+            {
+               char * name = projectView.fileName;
+               if(name)
+               {
+                  for(j = 0; (ch = name[j]); j++)
+                     len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
+               }
+            }
+
+            command = new char[len + 1];
+
+            strcpy(command, app.argv[0]);
+            len = arg0Len;
+            if(projectView.fileName)
+            {
+               strcat(command, " ");
+               len++;
+               ReplaceSpaces(command + len, projectView.fileName);
+            }
+         }
+         if(restart)
+         {
+            for(w = ide.firstChild; w; w = w.next)
+               if(w.isActiveClient && w.isDocument)
+                  w.modifiedDocument = false;
+            projectView.modifiedDocument = false;
+         }
+      }
+      else
+      {
+         for(w = ide.firstChild; w; w = w.next)
+         {
+            if(w.isActiveClient && w.isDocument)
+            {
+               if(!w.CloseConfirmation(true))
+               {
+                  restart = false;
+                  break;
+               }
+               if(w.fileName)
+               {
+                  char * name = w.fileName;
+                  len++;
+                  for(j = 0; (ch = name[j]); j++)
+                     len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
+               }
+            }
+         }
+
+         if(restart)
+         {
+            command = new char[len + 1];
+            strcpy(command, app.argv[0]);
+            len = arg0Len;
+
+            for(w = ide.firstChild; w; w = w.next)
+            {
+               if(w.isActiveClient && w.isDocument)
+               {
+                  char * name = w.fileName;
+                  if(name)
+                  {
+                     strcat(command, " ");
+                     len++;
+                     ReplaceSpaces(command + len, name);
+                     len = strlen(command);
+                  }
+               }
+            }
+         }
+         if(restart)
+         {
+            for(w = ide.firstChild; w; w = w.next)
+               if(w.isActiveClient && w.isDocument)
+                  w.modifiedDocument = false;
+         }
+      }
+      if(restart)
+      {
+         settings.language = code;
+         settingsContainer.Save();
+         if(eClass_IsDerived(app._class, class(GuiApplication)))
+         {
+            GuiApplication guiApp = (GuiApplication)app;
+            guiApp.desktop.Destroy(0);
+         }
+      }
+   }
+   else
+   {
+      int i;
+      for(i = 1; i < app.argc; i++)
+      {
+         char * arg = app.argv[i];
+         len++;
+         for(j = 0; (ch = arg[j]); j++)
+            len += (ch == ' ' || ch == '\"' || ch == '&' || ch == '$' || ch == '(' || ch == ')') ? 2 : 1;
+      }
+
+      command = new char[len + 1];
+      strcpy(command, app.argv[0]);
+      len = arg0Len;
+      for(i = 1; i < app.argc; i++)
+      {
+         strcat(command, " ");
+         len++;
+         ReplaceSpaces(command + len, app.argv[i]);
+         len = strlen(command);
+      }
+   }
+
+   if(restart)
+   {
+      SetEnvironment("LANGUAGE", code);
+      if(wait)
+         ExecuteWait(command);
+      else
+         Execute(command);
+   }
+   delete command;
+   return restart;
+}