ide: tweak compiler config support for specifying output file extensions.
[sdk] / ide / src / IDESettings.ec
index 3c102a7..53191c2 100644 (file)
@@ -1,9 +1,28 @@
+#if defined(__WIN32__)
+#define MessageBox _MessageBox
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#undef MessageBox
+#endif
+
 #ifdef ECERE_STATIC
-import static "ecere"
+public import static "ecere"
 #else
-import "ecere"
+public import "ecere"
 #endif
 
+define ecpDefaultCommand = "ecp";
+define eccDefaultCommand = "ecc";
+define ecsDefaultCommand = "ecs";
+define earDefaultCommand = "ear";
+define cppDefaultCommand = "gcc"; // As per #624 we decided to default to "gcc"...
+define ccDefaultCommand = "gcc";
+define cxxDefaultCommand = "g++";
+//define ldDefaultCommand = "gcc";
+define arDefaultCommand = "ar";
+define objectDefaultFileExt = "o";
+define outputDefaultFileExt = "";
+
 import "StringsBox"
 
 import "OldIDESettings"
@@ -16,17 +35,42 @@ define defaultCompilerName = "Default";
 
 define defaultObjDirExpression = "obj/$(CONFIG).$(PLATFORM)$(COMPILER_SUFFIX)$(DEBUG_SUFFIX)";
 
-char * settingsDirectoryNames[DirTypes] = 
+const 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, const 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 eString_PathRelated(const char * path, const char * to, char * pathDiff)
 {
    PathRelationship result;
    if(pathDiff) *pathDiff = '\0';
@@ -67,7 +111,7 @@ PathRelationship eString_PathRelated(char * path, char * to, char * pathDiff)
    return result;
 }
 
-char * CopyValidateMakefilePath(char * path)
+char * CopyValidateMakefilePath(const char * path)
 {
    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 };
@@ -76,15 +120,15 @@ char * CopyValidateMakefilePath(char * path)
    if(path)
    {
       int len;
-      len = strlen(path);
+      len = (int)strlen(path);
       copy = CopyString(path);
       if(len)
       {
          int c;
          char * tmp = copy;
          char * start = tmp;
-         Array<char *> parts { };
-         
+         Array<const char *> parts { };
+
          for(c=0; c<len; c++)
          {
             if(tmp[c] == '$')
@@ -125,14 +169,58 @@ char * CopyValidateMakefilePath(char * path)
    return copy;
 }
 
-CompilerConfig MakeDefaultCompiler(char * name, bool readOnly)
+void ValidPathBufCopy(char *output, const 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
+   {
+      const 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 = (int)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(const char * name, bool readOnly)
 {
    CompilerConfig defaultCompiler
    {
       name,
       readOnly,
       gcc,
-      GetRuntimePlatform(),
+      __runtimePlatform,
       1,
       makeDefaultCommand,
       ecpDefaultCommand,
@@ -141,20 +229,24 @@ CompilerConfig MakeDefaultCompiler(char * name, bool readOnly)
       earDefaultCommand,
       cppDefaultCommand,
       ccDefaultCommand,
-      cxxDefaultCommand
+      cxxDefaultCommand,
+      arDefaultCommand
+      //ldDefaultCommand
    };
    incref defaultCompiler;
    return defaultCompiler;
 }
 
-class IDESettingsContainer : GlobalSettings
-{
 #ifdef SETTINGS_TEST
-   settingsName = "ecereIDESettingsTest";
+define settingsName = "ecereIDE-SettingsTest";
 #else
-   settingsName = "ecereIDE";
+define ideSettingsName = "ecereIDE";
 #endif
 
+class IDESettingsContainer : GlobalSettings
+{
+   settingsName = ideSettingsName;
+
    virtual void OnLoad(GlobalSettingsData data);
 
    char moduleLocation[MAX_LOCATION];
@@ -180,13 +272,15 @@ private:
 
          strcpy(configFilePath, path);
          PathCat(configFilePath, "Data");
-         PathCat(configFilePath, "ecereIDE.ini");
+         PathCat(configFilePath, ideSettingsName);
+         ChangeExtension(configFilePath, "ini", configFilePath);
 
          strcpy(defaultConfigFilePath, path);
          PathCat(defaultConfigFilePath, "App");
          PathCat(defaultConfigFilePath, "DefaultData");
-         PathCat(defaultConfigFilePath, "ecereIDE.ini");
-         
+         PathCat(defaultConfigFilePath, ideSettingsName);
+         ChangeExtension(defaultConfigFilePath, "ini", defaultConfigFilePath);
+
          if(FileExists(defaultConfigFilePath))
          {
             if(!FileExists(configFilePath))
@@ -198,9 +292,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;
@@ -210,30 +304,26 @@ private:
 
    void OnAskReloadSettings()
    {
-      /*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)*/
-      {
-         double o, n;
-         FileSize newSettingsFileSize;
-         FileGetSize(settingsFilePath, &newSettingsFileSize);
-         o = settingsFileSize;
-         n = newSettingsFileSize;
-         if(o - n < 2048)
+      FileSize newSettingsFileSize;
+
+      if(OpenAndLock(&newSettingsFileSize))
+      {
+         if((double)settingsFileSize - (double)newSettingsFileSize < 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,
+
+            CloseAndMonitor();
+
+            MessageBox { master = w, type = ok, isModal = true,
                   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();
          }
       }
    }
@@ -275,7 +365,7 @@ private:
                delete data.displayDriver; data.displayDriver = CopyString(oldSettings.displayDriver);
                data.projectDefaultTargetDir = oldSettings.projectDefaultTargetDir;
                data.projectDefaultIntermediateObjDir = oldSettings.projectDefaultIntermediateObjDir;
-                        
+
                Save();
                result = success;
             }
@@ -284,9 +374,9 @@ private:
          if(result == fileNotFound || !data)
          {
             data = (IDESettings)this.data;
-            data.useFreeCaret = true;
+            data.useFreeCaret = false; //true;
             data.showLineNumbers = true;
-            data.caretFollowsScrolling = true;
+            data.caretFollowsScrolling = false; //true;
          }
       }
       // Ensure we have a default compiler
@@ -304,13 +394,35 @@ private:
       FileGetSize(settingsFilePath, &settingsFileSize);
       if(data.compilerConfigs)
       {
-         for(c : data.compilerConfigs)
+         for(ccfg : data.compilerConfigs)
          {
-            CompilerConfig compiler = c;
-            char * cxxCommand = compiler.cxxCommand;
-            if(!cxxCommand || !cxxCommand[0])
-               compiler.cxxCommand = cxxDefaultCommand;
-            incref compiler;
+            if(!ccfg.ecpCommand || !ccfg.ecpCommand[0])
+               ccfg.ecpCommand = ecpDefaultCommand;
+            if(!ccfg.eccCommand || !ccfg.eccCommand[0])
+               ccfg.eccCommand = eccDefaultCommand;
+            if(!ccfg.ecsCommand || !ccfg.ecsCommand[0])
+               ccfg.ecsCommand = ecsDefaultCommand;
+            if(!ccfg.earCommand || !ccfg.earCommand[0])
+               ccfg.earCommand = earDefaultCommand;
+            if(!ccfg.cppCommand || !ccfg.cppCommand[0])
+               ccfg.cppCommand = cppDefaultCommand;
+            if(!ccfg.ccCommand || !ccfg.ccCommand[0])
+               ccfg.ccCommand = ccDefaultCommand;
+            if(!ccfg.cxxCommand || !ccfg.cxxCommand[0])
+               ccfg.cxxCommand = cxxDefaultCommand;
+            /*if(!ccfg.ldCommand || !ccfg.ldCommand[0])
+               ccfg.ldCommand = ldDefaultCommand;*/
+            if(!ccfg.arCommand || !ccfg.arCommand[0])
+               ccfg.arCommand = arDefaultCommand;
+            if(!ccfg.objectFileExt || !ccfg.objectFileExt[0])
+               ccfg.objectFileExt = objectDefaultFileExt;
+            /*if(!ccfg.staticLibFileExt || !ccfg.staticLibFileExt[0])
+               ccfg.staticLibFileExt = staticLibDefaultFileExt;*/
+            /*if(!ccfg.sharedLibFileExt || !ccfg.sharedLibFileExt[0])
+               ccfg.sharedLibFileExt = sharedLibDefaultFileExt;*/
+            /*if(!ccfg.executableFileExt || !ccfg.executableFileExt[0])
+               ccfg.executableFileExt = outputDefaultFileExt;*/
+            incref ccfg;
          }
       }
       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
@@ -325,7 +437,6 @@ private:
       SettingsIOResult result;
 
       IDESettings data = (IDESettings)this.data;
-      Platform runtimePlatform = GetRuntimePlatform();
       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
          data.ManagePortablePaths(moduleLocation, false);
       data.ForcePathSeparatorStyle(true);
@@ -346,19 +457,19 @@ public:
    List<CompilerConfig> compilerConfigs { };
    Array<String> recentFiles { };
    Array<String> recentProjects { };
-   property char * docDir
+   property const char * docDir
    {
       set { delete docDir; if(value && value[0]) docDir = CopyString(value); }
       get { return docDir ? docDir : ""; }
       isset { return docDir && docDir[0]; }
    }
-   property char * ideFileDialogLocation
+   property const char * ideFileDialogLocation
    {
       set { delete ideFileDialogLocation; if(value && value[0]) ideFileDialogLocation = CopyString(value); }
       get { return ideFileDialogLocation ? ideFileDialogLocation : ""; }
       isset { return ideFileDialogLocation && ideFileDialogLocation[0]; }
    }
-   property char * ideProjectFileDialogLocation
+   property const char * ideProjectFileDialogLocation
    {
       set { delete ideProjectFileDialogLocation; if(value && value[0]) ideProjectFileDialogLocation = CopyString(value); }
       get { return ideProjectFileDialogLocation ? ideProjectFileDialogLocation : ""; }
@@ -368,37 +479,63 @@ public:
    bool showLineNumbers;
    bool caretFollowsScrolling;
    char * displayDriver;
-   
+
    // TODO: Classify settings
    //EditorSettings editor { };
 
-   property char * projectDefaultTargetDir
+   property const char * projectDefaultTargetDir
    {
       set { delete projectDefaultTargetDir; if(value && value[0]) projectDefaultTargetDir = CopyValidateMakefilePath(value); }
       get { return projectDefaultTargetDir ? projectDefaultTargetDir : ""; }
       isset { return projectDefaultTargetDir && projectDefaultTargetDir[0]; }
    }
-   property char * projectDefaultIntermediateObjDir
+   property const char * projectDefaultIntermediateObjDir
    {
       set { delete projectDefaultIntermediateObjDir; if(value && value[0]) projectDefaultIntermediateObjDir = CopyValidateMakefilePath(value); }
       get { return projectDefaultIntermediateObjDir ? projectDefaultIntermediateObjDir : ""; }
       isset { return projectDefaultIntermediateObjDir && projectDefaultIntermediateObjDir[0]; }
    }
 
-   property char * compilerConfigsDir
+   property const char * compilerConfigsDir
    {
       set { delete compilerConfigsDir; if(value && value[0]) compilerConfigsDir = CopyString(value); }
       get { return compilerConfigsDir ? compilerConfigsDir : ""; }
       isset { return compilerConfigsDir && compilerConfigsDir[0]; }
    }
 
-   property char * defaultCompiler
+   property const char * defaultCompiler
    {
       set { delete defaultCompiler; if(value && value[0]) defaultCompiler = CopyString(value); }
       get { return defaultCompiler && defaultCompiler[0] ? defaultCompiler : defaultCompilerName; }
       isset { return defaultCompiler && defaultCompiler[0]; }
    }
 
+   property const String language
+   {
+      set
+      {
+         delete language;
+         language = CopyString(value);
+      }
+      get { return language; }
+      isset { return language != null; }
+   }
+
+   property Array<NamedString> findInFileFileFilters
+   {
+      set
+      {
+         findInFileFileFilters.Free();
+         if(value)
+         {
+            delete findInFileFileFilters;
+            findInFileFileFilters = value;
+         }
+      }
+      get { return findInFileFileFilters; }
+      isset { return findInFileFileFilters.count != 0; }
+   }
+
 private:
    char * docDir;
    char * ideFileDialogLocation;
@@ -407,10 +544,12 @@ private:
    char * projectDefaultIntermediateObjDir;
    char * compilerConfigsDir;
    char * defaultCompiler;
+   String language;
+   Array<NamedString> findInFileFileFilters { };
 
-   CompilerConfig GetCompilerConfig(String compilerName)
+   CompilerConfig GetCompilerConfig(const String compilerName)
    {
-      char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
+      const char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
       CompilerConfig compilerConfig = null;
       for(compiler : compilerConfigs)
       {
@@ -436,15 +575,18 @@ private:
       recentProjects.Free();
       delete recentProjects;
       delete docDir;
-   
+
       delete projectDefaultTargetDir;
       delete projectDefaultIntermediateObjDir;
       delete compilerConfigsDir;
       delete defaultCompiler;
+      delete language;
 
       delete ideFileDialogLocation;
       delete ideProjectFileDialogLocation;
       delete displayDriver;
+
+      if(findInFileFileFilters) findInFileFileFilters.Free();
    }
 
    void ForcePathSeparatorStyle(bool unixStyle)
@@ -576,7 +718,7 @@ private:
          compilerConfigsDir = UpdatePortablePath(compilerConfigsDir, location, makeAbsolute);
    }
 
-   char * UpdatePortablePath(char * path, char * location, bool makeAbsolute)
+   char * UpdatePortablePath(char * path, const char * location, bool makeAbsolute)
    {
       char * output;
       if(makeAbsolute)
@@ -605,7 +747,7 @@ private:
       return output;
    }
 
-   void AddRecentFile(char * fileName)
+   void AddRecentFile(const char * fileName)
    {
       int c;
       char * filePath = CopyString(fileName);
@@ -624,7 +766,7 @@ private:
       //UpdateRecentMenus(owner);
    }
 
-   void AddRecentProject(char * projectName)
+   void AddRecentProject(const char * projectName)
    {
       int c;
       char * filePath = CopyString(projectName);
@@ -650,7 +792,7 @@ 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 Array<String> compilerTypeLongNames
+static Array<const String> compilerTypeLongNames
 { [
    $"GNU Compiler Collection (GCC) / GNU Make",
    $"Tiny C Compiler / GNU Make",
@@ -670,14 +812,14 @@ public enum CompilerType
       get { return this == vs8 || this == vs9 || this == vs10; }
    }
 
-   property char *
+   property const char *
    {
       get { return OnGetString(null, null, null); }
       set
-      {  
+      {
          if(value)
          {
-            Platform c;
+            CompilerType c;
             for(c = firstCompilerType; c <= lastCompilerType; c++)
                if(!strcmpi(value, compilerTypeNames[c]))
                   return c;
@@ -686,13 +828,13 @@ public enum CompilerType
       }
    };
 
-   property char * longName { get { return OnGetString(null, (void*)1, null); } };
-   property char * versionString { get { return OnGetString(null, (void*)2, null); } };
-   property char * yearString { get { return OnGetString(null, (void*)3, null); } };
-   property char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
-   property char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
+   property const char * longName { get { return OnGetString(null, (void*)1, null); } };
+   property const char * versionString { get { return OnGetString(null, (void*)2, null); } };
+   property const char * yearString { get { return OnGetString(null, (void*)3, null); } };
+   property const char * projectFileExtension { get { return OnGetString(null, (void*)4, null); } };
+   property const char * solutionFileVersionString { get { return OnGetString(null, (void*)5, null); } };
 
-   char * OnGetString(char * tempString, void * fieldData, bool * needClass)
+   const char * OnGetString(char * tempString, void * fieldData, bool * needClass)
    {
       if(this >= firstCompilerType && this <= lastCompilerType)
       {
@@ -721,7 +863,7 @@ class CompilerConfig
 
    numJobs = 1;
 public:
-   property char * name
+   property const char * name
    {
       set
       {
@@ -735,55 +877,92 @@ public:
    CompilerType type;
    Platform targetPlatform;
    int numJobs;
-   property char * makeCommand
+   property const char * makeCommand
    {
       set { delete makeCommand; if(value && value[0]) makeCommand = CopyString(value); }
       get { return makeCommand; }
       isset { return makeCommand && makeCommand[0]; }
    }
-   property char * ecpCommand
+   property const char * ecpCommand
    {
       set { delete ecpCommand; if(value && value[0]) ecpCommand = CopyString(value); }
       get { return ecpCommand; }
       isset { return ecpCommand && ecpCommand[0]; }
    }
-   property char * eccCommand
+   property const char * eccCommand
    {
       set { delete eccCommand; if(value && value[0]) eccCommand = CopyString(value); }
       get { return eccCommand; }
       isset { return eccCommand && eccCommand[0]; }
    }
-   property char * ecsCommand
+   property const char * ecsCommand
    {
       set { delete ecsCommand; if(value && value[0]) ecsCommand = CopyString(value); }
       get { return ecsCommand; }
       isset { return ecsCommand && ecsCommand[0]; }
    }
-   property char * earCommand
+   property const char * earCommand
    {
       set { delete earCommand; if(value && value[0]) earCommand = CopyString(value); }
       get { return earCommand; }
       isset { return earCommand && earCommand[0]; }
    }
-   property char * cppCommand
+   property const char * cppCommand
    {
       set { delete cppCommand; if(value && value[0]) cppCommand = CopyString(value); }
       get { return cppCommand; }
       isset { return cppCommand && cppCommand[0]; }
    }
-   property char * ccCommand
+   property const char * ccCommand
    {
       set { delete ccCommand; if(value && value[0]) ccCommand = CopyString(value); }
       get { return ccCommand; }
       isset { return ccCommand && ccCommand[0]; }
    }
-   property char * cxxCommand
+   property const char * cxxCommand
    {
       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
+   property const char * arCommand
+   {
+      set { delete arCommand; if(value && value[0]) arCommand = CopyString(value); }
+      get { return arCommand; }
+      isset { return arCommand && arCommand[0]; }
+   }
+   property const char * ldCommand
+   {
+      set { delete ldCommand; if(value && value[0]) ldCommand = CopyString(value); }
+      get { return ldCommand; }
+      isset { return ldCommand && ldCommand[0]; }
+   }
+   bool noStripTarget;
+   property const char * objectFileExt
+   {
+      set { delete objectFileExt; if(value && value[0]) objectFileExt = CopyString(value); }
+      get { return objectFileExt && objectFileExt[0] ? objectFileExt : objectDefaultFileExt ; }
+      isset { return objectFileExt && objectFileExt[0] && strcmp(objectFileExt, objectDefaultFileExt); }
+   }
+   property const char * staticLibFileExt
+   {
+      set { delete staticLibFileExt; if(value && value[0]) staticLibFileExt = CopyString(value); }
+      get { return staticLibFileExt; }
+      isset { return staticLibFileExt && staticLibFileExt[0]; }
+   }
+   property const char * sharedLibFileExt
+   {
+      set { delete sharedLibFileExt; if(value && value[0]) sharedLibFileExt = CopyString(value); }
+      get { return sharedLibFileExt; }
+      isset { return sharedLibFileExt && sharedLibFileExt[0]; }
+   }
+   property const char * executableFileExt
+   {
+      set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
+      get { return executableFileExt; }
+      isset { return executableFileExt && executableFileExt[0]; }
+   }
+   property const char * executableLauncher
    {
       set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
       get { return executableLauncher; }
@@ -795,19 +974,19 @@ public:
    // deprecated
    property bool supportsBitDepth { set { } get { return true; } isset { return false; } }
 
-   property char * distccHosts
+   property const 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
+   property const char * gnuToolchainPrefix
    {
       set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
       get { return gnuToolchainPrefix; }
       isset { return gnuToolchainPrefix && gnuToolchainPrefix[0]; }
    }
-   property char * sysroot
+   property const char * sysroot
    {
       set { delete sysroot; if(value && value[0]) sysroot = CopyString(value); }
       get { return sysroot; }
@@ -897,6 +1076,20 @@ public:
       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
@@ -925,6 +1118,36 @@ public:
       get { return linkerFlags; }
       isset { return linkerFlags.count != 0; }
    }
+   // json backward compatibility
+   property const char * gccPrefix
+   {
+      set { delete gnuToolchainPrefix; if(value && value[0]) gnuToolchainPrefix = CopyString(value); }
+      get { return gnuToolchainPrefix; }
+      isset { return false; }
+   }
+   property const char * execPrefixCommand
+   {
+      set { delete executableLauncher; if(value && value[0]) executableLauncher = CopyString(value); }
+      get { return executableLauncher; }
+      isset { return false; }
+   }
+   property const char * outputFileExt
+   {
+      set { delete executableFileExt; if(value && value[0]) executableFileExt = CopyString(value); }
+      get { return executableFileExt; }
+      isset { return false; }
+   }
+   // utility
+   property bool hasDocumentOutput
+   {
+      get
+      {
+         bool result = executableFileExt && executableFileExt[0] &&
+               (!strcmpi(executableFileExt, "htm") || !strcmpi(executableFileExt, "html"));
+         return result;
+      }
+      isset { return false; }
+   }
 private:
    Array<String> includeDirs { };
    Array<String> libraryDirs { };
@@ -934,6 +1157,7 @@ private:
    Array<NamedString> environmentVars { };
    Array<String> prepDirectives { };
    Array<String> excludeLibs { };
+   Array<String> eCcompilerFlags { };
    Array<String> compilerFlags { };
    Array<String> linkerFlags { };
    char * name;
@@ -945,6 +1169,12 @@ private:
    char * cppCommand;
    char * ccCommand;
    char * cxxCommand;
+   char * ldCommand;
+   char * arCommand;
+   char * objectFileExt;
+   char * staticLibFileExt;
+   char * sharedLibFileExt;
+   char * executableFileExt;
    char * executableLauncher;
    char * distccHosts;
    char * gnuToolchainPrefix;
@@ -965,6 +1195,12 @@ private:
       delete cppCommand;
       delete ccCommand;
       delete cxxCommand;
+      delete ldCommand;
+      delete arCommand;
+      delete objectFileExt;
+      delete staticLibFileExt;
+      delete sharedLibFileExt;
+      delete executableFileExt;
       delete makeCommand;
       delete executableLauncher;
       delete distccHosts;
@@ -977,8 +1213,11 @@ private:
       if(prepDirectives) { prepDirectives.Free(); }
       if(excludeLibs) { excludeLibs.Free(); }
       if(compilerFlags) { compilerFlags.Free(); }
+      if(eCcompilerFlags) { eCcompilerFlags.Free(); }
       if(linkerFlags) { linkerFlags.Free(); }
    }
+
+public:
    CompilerConfig Copy()
    {
       CompilerConfig copy
@@ -996,6 +1235,13 @@ private:
          cppCommand,
          ccCommand,
          cxxCommand,
+         arCommand,
+         ldCommand,
+         noStripTarget,
+         objectFileExt,
+         staticLibFileExt,
+         sharedLibFileExt,
+         executableFileExt,
          executableLauncher,
          ccacheEnabled,
          distccEnabled,
@@ -1011,9 +1257,249 @@ private:
       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
+{
+   const String name;
+   const String bitmap;
+   const String code;
+   BitmapResource res;
+
+   const 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" },
+   { "Português (Brazil)", ":countryCode/br.png", "pt_BR" },
+   { "Русский (43%)",      ":countryCode/ru.png", "ru" },
+   { "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" }
+] };
+
+const String GetLanguageString()
+{
+   char * dot, * colon;
+   static char lang[256];
+   const String language = getenv("ECERE_LANGUAGE");
+   if(!language) language = getenv("LANGUAGE");
+   if(!language) language = getenv("LC_ALL");
+   if(!language) language = getenv("LC_MESSAGES");
+   if(!language) language = getenv("LANG");
+   if(!language) language = "";
+   if(language && (colon = strchr(language, ':')))
+   {
+      if(lang != language)
+         strncpy(lang, language, sizeof(lang));
+      lang[sizeof(lang)-1] = 0;
+      lang[colon - language] = 0;
+      language = lang;
+   }
+   if(language && (dot = strchr(language, '.')))
+   {
+      if(lang != language)
+         strncpy(lang, language, sizeof(lang));
+      lang[sizeof(lang)-1] = 0;
+      lang[dot - language] = 0;
+      language = lang;
+   }
+   return language;
+}
+
+bool LanguageRestart(const char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
+{
+   bool restart = true;
+   String command = null;
+   int arg0Len = (int)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)
+            {
+               const 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)
+               {
+                  const 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)
+               {
+                  const char * name = w.fileName;
+                  if(name)
+                  {
+                     strcat(command, " ");
+                     len++;
+                     ReplaceSpaces(command + len, name);
+                     len = (int)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 defined(__WIN32__)
+         // Set LANGUAGE environment variable
+         {
+            HKEY key = null;
+            uint16 wLanguage[256];
+            DWORD status;
+            wLanguage[0] = 0;
+
+            RegCreateKeyEx(HKEY_CURRENT_USER, "Environment", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, null, &key, &status);
+            if(key)
+            {
+               UTF8toUTF16Buffer(code, wLanguage, sizeof(wLanguage) / sizeof(uint16));
+               RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
+               RegCloseKey(key);
+            }
+         }
+#endif
+
+         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++)
+      {
+         const 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 = (int)strlen(command);
+      }
+   }
+
+   if(restart)
+   {
+      SetEnvironment("ECERE_LANGUAGE", code);
+      if(wait)
+         ExecuteWait(command);
+      else
+         Execute(command);
+   }
+   delete command;
+   return restart;
+}