ide/settings: Fixed color scheme issues
[sdk] / ide / src / IDESettings.ec
index e3e518b..b0feab9 100644 (file)
@@ -4,6 +4,171 @@ public import static "ecere"
 public import "ecere"
 #endif
 
+// *** Color Schemes ***
+class IDEColorScheme
+{
+   String name;
+   SyntaxColorScheme syntaxColors;
+public:
+
+   property const String name
+   {
+      set { delete name; name = CopyString(value); }
+      get { return name; }
+   }
+
+   Color selectionColor;
+   Color selectionText;
+   Color viewsBackground;
+   Color viewsText;
+   Color outputBackground;
+   Color outputText;
+   Color projectViewBackground;
+   Color projectViewText;
+   Color codeEditorBG;
+   Color codeEditorFG;
+   Color marginColor;
+   Color selectedMarginColor;
+   Color lineNumbersColor;
+   Color sheetSelectionColor;
+   Color sheetSelectionText;
+
+   property SyntaxColorScheme syntaxColors
+   {
+      set { delete syntaxColors; syntaxColors = value; if(value) incref value; }
+      get { return syntaxColors; }
+   }
+
+   ~IDEColorScheme()
+   {
+      delete syntaxColors;
+      delete name;
+   }
+}
+
+IDEColorScheme colorScheme;
+
+// Default Color Schemes
+IDEColorScheme darkColorScheme
+{
+   name = "Classic Dark";
+   selectionColor = lightYellow;
+   selectionText = Color { 30, 40, 50 };
+   viewsBackground = Color { 30, 40, 50 };
+   viewsText = lightGray;
+   outputBackground = black;
+   outputText = lime;
+   sheetSelectionColor = Color { 170, 220, 255 };
+   sheetSelectionText = black;
+   projectViewBackground = Color { 30, 40, 50 };
+   projectViewText = lightGray;
+   codeEditorBG = black;
+   codeEditorFG = ivory;
+   marginColor = Color {24, 24, 24};
+   selectedMarginColor = Color {64, 64, 64};
+   lineNumbersColor = Color {160, 160, 160};
+   syntaxColors =
+   {
+      keywordColors = [ skyBlue, skyBlue ];
+      commentColor = Color { 125, 125, 125 };
+      charLiteralColor = Color { 245, 50, 245 };
+      stringLiteralColor = Color { 245, 50, 245 };
+      preprocessorColor = { 120, 220, 140 };
+      numberColor = Color {   0, 192, 192 };
+   };
+};
+
+IDEColorScheme lightColorScheme
+{
+   name = "Classic Light";
+   selectionColor = Color { 10, 36, 106 };
+   selectionText = white;
+   viewsBackground = white;
+   viewsText = black;
+   outputBackground = white;
+   outputText = black;
+   sheetSelectionColor = Color { 170, 220, 255 };
+   sheetSelectionText = black;
+   projectViewBackground = white;
+   projectViewText = black;
+   codeEditorBG = white;
+   codeEditorFG = black;
+   marginColor = Color {230, 230, 230};
+   selectedMarginColor = Color {200, 200, 200};
+   lineNumbersColor = Color {60, 60, 60};
+   syntaxColors =
+   {
+      keywordColors = [ blue, blue ];
+      commentColor = dimGray;
+      charLiteralColor = crimson;
+      stringLiteralColor = crimson;
+      preprocessorColor = green;
+      numberColor = teal;
+   };
+};
+
+IDEColorScheme greenColorScheme
+{
+   name = "Green",
+   selectionColor = 0x00FFFFE0,
+   selectionText = 0x001E2832,
+   viewsBackground = 0x001E2832,
+   viewsText = 0x00D3D3D3,
+   outputBackground = 0x00000000,
+   outputText = 0x0000FF00,
+   projectViewBackground = 0x001E2832,
+   projectViewText = 0x00D3D3D3,
+   codeEditorBG = 0x00000000,
+   codeEditorFG = 0x00FFFFF0,
+   marginColor = 0x001100,
+   selectedMarginColor = 0x002200,
+   lineNumbersColor = 0x00FF00,
+   sheetSelectionColor = 0x00AADCFF,
+   sheetSelectionText = 0x00000000,
+   syntaxColors = {
+      commentColor = 0x008c00,
+      charLiteralColor = 0x89de00,
+      stringLiteralColor = 0x89de00,
+      preprocessorColor = 0x0078DC8C,
+      numberColor = { 8, 237, 141 },
+      keywordColors = [
+         0x00e09d,
+         0x00e09d
+      ]
+   }
+};
+
+IDEColorScheme grayColorScheme
+{
+   name = "Gray & Orange",
+   selectionColor = 0x00FFFFE0,
+   selectionText = 0x001E2832,
+   viewsBackground = 0x001E2832,
+   viewsText = 0x00D3D3D3,
+   outputBackground = 0x00000000,
+   outputText = 0x0000FF00,
+   projectViewBackground = 0x001E2832,
+   projectViewText = 0x00D3D3D3,
+   codeEditorBG = 0x00202020,
+   codeEditorFG = 0x00E2E2E5,
+   marginColor = 0x00303030,
+   selectedMarginColor = 0x00404040,
+   lineNumbersColor = 0x00939395,
+   sheetSelectionColor = 0x00AADCFF,
+   sheetSelectionText = 0x00000000,
+   syntaxColors = {
+      commentColor = 0x005F5F5F,
+      charLiteralColor = 0x0089DE00,
+      stringLiteralColor = 0x00707070,
+      preprocessorColor = 0x00FAF4C6,
+      numberColor = 0x00FF9800,
+      keywordColors = [
+         0x00FF9800,
+         0x00FF9800
+      ]
+   }
+};
+
 define ecpDefaultCommand = "ecp";
 define eccDefaultCommand = "ecc";
 define ecsDefaultCommand = "ecs";
@@ -20,21 +185,16 @@ import "StringsBox"
 
 import "OldIDESettings"
 
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-import "ide"
-import "process"
+#ifdef __WIN32__
+#define WIN32_LEAN_AND_MEAN
+#define Sleep _Sleep
+#include <windows.h>
+#undef MoveFileEx
+#undef Sleep
+#undef MessageBox
+#undef GetObject
 #endif
 
-IDEConfigHolder ideConfig { };
-
-IDESettings ideSettings;
-
-IDESettingsContainer settingsContainer
-{
-   dataOwner = &ideSettings;
-   dataClass = class(IDESettings);
-};
-
 define MaxRecent = 9;
 
 enum DirTypes { includes, libraries, executables };
@@ -260,6 +420,29 @@ class IDEConfigHolder
    RecentFiles recentFiles { };
    RecentWorkspaces recentWorkspaces { };
 
+   property CompilerConfigs compilers
+   {
+      set { compilers.Free(); delete compilers; compilers = value; }
+      get { return compilers; }
+   }
+   property RecentFiles recentFiles
+   {
+      set { recentFiles.Free(); delete recentFiles; recentFiles = value; }
+      get { return recentFiles; }
+   }
+   property RecentWorkspaces recentProjects
+   {
+      set { recentWorkspaces.Free(); delete recentWorkspaces; recentWorkspaces = value; }
+      get { return recentWorkspaces; }
+   }
+
+   ~IDEConfigHolder()
+   {
+      compilers.Free();
+      recentFiles.Free();
+      recentWorkspaces.Free();
+   }
+
    void forcePathSeparatorStyle(bool unixStyle)
    {
       char from, to;
@@ -274,20 +457,29 @@ class IDEConfigHolder
 
 class IDESettingsContainer : GlobalSettings
 {
+   virtual void onLoadCompilerConfigs();
+   virtual void onLoadRecentFiles();
+   virtual void onLoadRecentProjects();
+   virtual void onLoad();
+
+   CompilerConfigs compilerConfigs;
+   RecentFiles recentFiles;
+   RecentWorkspaces recentProjects;
+
    property bool useNewConfigurationFiles
    {
       set
       {
          if(value)
          {
-            settingsContainer.driver = "ECON";
+            driver = "ECON";
             settingsName = "config";
             settingsExtension = "econ";
             settingsDirectory = settingsDir;
          }
          else
          {
-            settingsContainer.driver = "JSON";
+            driver = "JSON";
             settingsName = ideSettingsName;
             settingsExtension = null;
             settingsDirectory = null;
@@ -297,6 +489,74 @@ class IDESettingsContainer : GlobalSettings
 
    char moduleLocation[MAX_LOCATION];
 
+   FileMonitor recentFilesMonitor
+   {
+      this, fileChange = { modified = true };
+
+      bool OnFileNotify(FileChange action, const char * param)
+      {
+         File f;
+         recentFilesMonitor.StopMonitoring();
+         f = FileOpen(recentFilesMonitor.fileName, read);
+         if(f)
+         {
+            int c;
+            bool locked;
+            for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
+            recentFiles.read(this);
+            f.Unlock(0,0,true);
+            delete f;
+         }
+         return true;
+      }
+   };
+
+   FileMonitor recentProjectsMonitor
+   {
+      this, fileChange = { modified = true };
+
+      bool OnFileNotify(FileChange action, const char * param)
+      {
+         File f;
+         recentProjectsMonitor.StopMonitoring();
+         f = FileOpen(recentProjectsMonitor.fileName, read);
+         if(f)
+         {
+            int c;
+            bool locked;
+            for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
+            recentProjects.read(this);
+            f.Unlock(0,0,true);
+            delete f;
+         }
+         return true;
+      }
+   };
+
+   static void getConfigFilePath(char * path, Class _class, char * dir, const char * configName)
+   {
+      if(dir) *dir = 0;
+      strcpy(path, settingsFilePath);
+      StripLastDirectory(path, path);
+      if(oldConfig)
+         PathCatSlash(path, settingsDir);
+      if(_class == class(CompilerConfig))
+      {
+         PathCatSlash(path, "compilerConfigs");
+         if(dir)
+            strcpy(dir, path);
+         if(configName)
+         {
+            PathCatSlash(path, configName);
+            strcat(path, ".econ");
+         }
+      }
+      else if(_class == class(RecentFilesData))
+         PathCatSlash(path, "recentFiles.econ");
+      else if(_class == class(RecentWorkspacesData))
+         PathCatSlash(path, "recentWorkspaces.econ");
+   }
+
 private:
    bool oldConfig;
    FileSize settingsFileSize;
@@ -310,7 +570,7 @@ private:
       StripLastDirectory(moduleLocation, moduleLocation);
       ChangeCh(moduleLocation, '\\', '/');
       // PortableApps.com directory structure
-      if((start = strstr(path, "\\App\\EcereSDK\\bin\\ide.exe")))
+      if((start = strstr(path, "\\App\\EcereSDK\\bin\\ecere-ide.exe")))
       {
          char configFilePath[MAX_LOCATION];
          char defaultConfigFilePath[MAX_LOCATION];
@@ -355,9 +615,10 @@ private:
 
       if(OpenAndLock(&newSettingsFileSize))
       {
-         if((double)settingsFileSize - (double)newSettingsFileSize < 2048)
+         //if((double)settingsFileSize - (double)newSettingsFileSize < 2048)
             Load();
-         else
+            onLoad();
+         /*else
          {
             GuiApplication app = ((GuiApplication)__thisModule.application);
             Window w;
@@ -372,7 +633,7 @@ private:
                   "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();
-         }
+         }*/
       }
    }
 
@@ -438,51 +699,51 @@ private:
          }
       }
 
-      if(oldConfig)
-      {
-         if(data.compilerConfigs && data.compilerConfigs.count)
-         {
-            for(c : data.compilerConfigs)
-               ideConfig.compilers.Add(c);
-            data.compilerConfigs.RemoveAll();
-         }
-         if(data.recentFiles && data.recentFiles.count)
-         {
-            for(s : data.recentFiles)
-               ideConfig.recentFiles.Add(s);
-            data.recentFiles.RemoveAll();
-         }
-         if(data.recentProjects && data.recentProjects.count)
-         {
-            for(s : data.recentProjects)
-               ideConfig.recentWorkspaces.Add(s);
-            data.recentProjects.RemoveAll();
-         }
-         ideConfig.forcePathSeparatorStyle(true);
-      }
-
       CloseAndMonitor();
       FileGetSize(settingsFilePath, &settingsFileSize);
-      if(oldConfig)
-         CompilerConfigs::fix();
       if(portable && moduleLocation[0] && FileExists(moduleLocation).isDirectory)
          data.ManagePortablePaths(moduleLocation, true);
       data.ForcePathSeparatorStyle(true);
 
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-      globalSettingsDialog.ideSettings = data;
-      if(oldConfig)
+      if(!data.colorSchemes || !data.colorSchemes.count)
       {
-         ide.updateRecentMenus();
-         ide.UpdateCompilerConfigs(true);
+         if(!data.colorSchemes) data.colorSchemes = { };
+
+         data.colorSchemes.Add(darkColorScheme);   incref darkColorScheme;
+         data.colorSchemes.Add(lightColorScheme);  incref lightColorScheme;
+         data.colorSchemes.Add(greenColorScheme);  incref greenColorScheme;
+         data.colorSchemes.Add(grayColorScheme);   incref grayColorScheme;
+      }
+      colorScheme = null;
+      if(data.activeColorScheme)
+      {
+         for(cs : data.colorSchemes; cs.name && !strcmp(cs.name, data.activeColorScheme))
+         {
+            colorScheme = cs;
+            break;
+         }
+      }
+      if(!colorScheme)
+      {
+         colorScheme = data.colorSchemes[0];
+         data.activeColorScheme = colorScheme.name;
       }
-#endif
 
+      // Import from previous ecereIDE settings
       if(oldConfig)
       {
-         useNewConfigurationFiles = true;
+         // Save first so that settingsFilePath get set up
          Save();
-         CompilerConfigs::write(null);
+
+         data.compilerConfigs.ensureDefaults();
+         data.compilerConfigs.write(this, null);
+         data.compilerConfigs.Free();
+
+         data.recentFiles.write(this);
+         data.recentFiles.Free();
+
+         data.recentProjects.write(this);
+         data.recentProjects.Free();
       }
       return result;
    }
@@ -525,10 +786,7 @@ static Map<String, String> getCompilerConfigFilePathsByName(const char * path)
          char * path = CopyString(fl.path);
          MakeSlashPath(path);
          GetLastDirectory(path, name);
-         {
-            char * s = strstr(name, ".");
-            if(s) *s = 0;
-         }
+         StripExtension(name);
          map[name] = path;
       }
    }
@@ -547,10 +805,7 @@ static Map<String, CompilerConfig> getCompilerConfigsByName(const char * path)
          char * path = CopyString(fl.path);
          MakeSlashPath(path);
          GetLastDirectory(path, name);
-         {
-            char * s = strstr(name, ".");
-            if(s) *s = 0;
-         }
+         StripExtension(name);
          {
             CompilerConfig ccfg = CompilerConfig::read(path);
             if(ccfg)
@@ -562,38 +817,14 @@ static Map<String, CompilerConfig> getCompilerConfigsByName(const char * path)
    return map;
 }
 
-static void getConfigFilePath(char * path, Class _class, char * dir, const char * configName)
-{
-   if(dir) *dir = 0;
-   strcpy(path, settingsContainer.settingsFilePath);
-   StripLastDirectory(path, path);
-   if(settingsContainer.oldConfig)
-      PathCatSlash(path, settingsDir);
-   if(_class == class(CompilerConfig))
-   {
-      PathCatSlash(path, "compilerConfigs");
-      if(dir)
-         strcpy(dir, path);
-      if(configName)
-      {
-         PathCatSlash(path, configName);
-         ChangeExtension(path, "econ", path);
-      }
-   }
-   else if(_class == class(RecentFilesData))
-      PathCatSlash(path, "recentFiles.econ");
-   else if(_class == class(RecentWorkspacesData))
-      PathCatSlash(path, "recentWorkspaces.econ");
-}
-
 static SettingsIOResult writeConfigFile(const char * path, Class dataType, void * data)
 {
    SettingsIOResult result = error;
-   SafeFile sf;
-   sf = safeWriteFileOpen(path, write);
-   if(sf.file)
+   SafeFile sf = SafeFile::open(path, write);
+   if(sf)
    {
       WriteECONObject(sf.file, dataType, data, 0);
+      sf.sync();
       delete sf;
       result = success;
    }
@@ -608,7 +839,7 @@ static SettingsIOResult readConfigFile(const char * path, Class dataType, void *
    SafeFile sf;
    if(!FileExists(path))
       result = fileNotFound;
-   else if((sf = safeWriteFileOpen(path, read)))
+   else if((sf = SafeFile::open(path, read)))
    {
       JSONResult jsonResult;
       {
@@ -638,69 +869,117 @@ class SafeFile
    char path[MAX_LOCATION];
    char tmp[MAX_LOCATION];
 
-   ~SafeFile()
+   SafeFile ::open(const char * path, FileOpenMode mode)
    {
-      delete file;
-      // how to make this atomic ?
-      if(mode == write)
+      SafeFile result = null;
+      if(mode == write || mode == read)
       {
+         SafeFile sf { mode = mode };
          int c;
-         for(c = 0; c < 10; c++)
+         bool locked = false;
+         FileLock lockType = mode == write ? exclusive : shared;
+
+         strcpy(sf.path, path);
+         strcpy(sf.tmp, path);
+         strcat(sf.tmp, ".tmp");
+         if(mode == write && FileExists(sf.tmp).isFile)
+            DeleteFile(sf.tmp);
+
+         if(mode == write)
          {
-            if(FileExists(path).isFile)
-               DeleteFile(path);
-            else
+            sf.file = FileOpen(sf.tmp, readWrite);
+            if(!sf.file)
             {
-               MoveFile(tmp, path);
-               if(FileExists(path).isFile)
-                  break;
+               sf.file = FileOpen(sf.tmp, writeRead);
+               if(sf.file)
+               {
+                  delete sf.file;
+                  sf.file = FileOpen(sf.tmp, readWrite);
+               }
             }
          }
-      }
-   }
-}
-
-SafeFile safeWriteFileOpen(const char * path, FileOpenMode mode)
-{
-   SafeFile sf { mode = mode };
-   int c;
-   bool locked = false;
-   strcpy(sf.path, path);
-   strcpy(sf.tmp, path);
-   strcat(sf.tmp, ".tmp");
-   if(mode == write)
-   {
-      if(FileExists(sf.tmp).isFile)
-         DeleteFile(sf.tmp);
-      sf.file = FileOpen(sf.tmp, write);
-      if(sf.file)
-      {
-         for(c = 0; c < 10 && !(locked = sf.file.Lock(exclusive, 0, 0, false)); c++);
-         if(!locked)
+         else
+            sf.file = FileOpen(path, mode);
+         if(sf.file)
          {
-            delete sf.file;
-            PrintLn($"warning: safeWriteFileOpen: unable to obtain exclusive lock on temporary file for writing: ", sf.tmp);
+            for(c = 0; c < 10 && !(locked = sf.file.Lock(lockType, 0, 0, false)); c++) Sleep(0.01);
+            if(locked)
+            {
+               sf.file.Truncate(0);
+               sf.file.Seek(0, start);
+               result = sf;
+            }
+            else if(mode == write)
+               PrintLn($"warning: SafeFile::open: unable to obtain exclusive lock on temporary file for writing: ", sf.tmp);
+            else
+               PrintLn($"warning: SafeFile::open: unable to obtain shared lock on file for reading: ", path);
          }
+         else if(mode == write)
+            PrintLn($"warning: SafeFile::open: unable to open temporary file for writing: ", sf.tmp);
+         else
+            PrintLn($"warning: SafeFile::open: unable to open file for reading: ", path);
+
+         if(!result)
+            delete sf;
       }
       else
-         PrintLn($"warning: safeWriteFileOpen: unable to open temporary file for writing: ", sf.tmp);
+         PrintLn($"warning: SafeFile::open: does not yet support FileOpenMode::", mode);
+      return result;
    }
-   else if(mode == read)
+
+   void sync()
    {
-      sf.file = FileOpen(path, read);
-      if(sf.file)
+      if(file && mode == write)
       {
-         for(c = 0; c < 10 && !(locked = sf.file.Lock(shared, 0, 0, false)); c++) Sleep(0.01);
-         if(!locked)
+         int c;
+         File f = FileOpen(path, readWrite);
+         if(!f)
+         {
+            f = FileOpen(path, writeRead);
+            if(f)
+            {
+               delete f;
+               f = FileOpen(path, readWrite);
+            }
+         }
+         if(f)
          {
-            delete sf.file;
-            PrintLn($"warning: safeWriteFileOpen: unable to obtain shared lock on file for reading: ", path);
+            bool locked = true;
+            for(c = 0; c < 10 && !(locked = f.Lock(exclusive, 0,0, false)); c++) Sleep(0.01);
+
+            if(locked)
+            {
+               f.Unlock(0,0, false);
+               delete f;
+               file.Unlock(0,0, false);
+               delete file;
+
+               for(c = 0; c < 10; c++)
+               {
+                  if(MoveFileEx(tmp, path, { true, true }))
+                     break;
+                  else
+                     Sleep(0.01);
+               }
+            }
+            else
+            {
+               delete f;
+               PrintLn($"warning: SafeFile::sync: failed to lock file for ", mode);
+            }
          }
       }
    }
-   else
-      PrintLn($"warning: safeWriteFileOpen: does not yet support FileOpenMode::", mode);
-   return sf;
+
+
+   ~SafeFile()
+   {
+      if(file)
+      {
+         file.Unlock(0,0, false);
+         delete file;
+      }
+   }
 }
 
 class RecentFilesData
@@ -720,7 +999,7 @@ class IDESettings : GlobalSettingsData
 public:
    property CompilerConfigs compilerConfigs
    {
-      set { if(settingsContainer.oldConfig) { if(compilerConfigs) compilerConfigs.Free(); delete compilerConfigs; if(value) compilerConfigs = value; } }
+      set { /*if(settingsContainer.oldConfig)*/ { if(compilerConfigs) compilerConfigs.Free(); delete compilerConfigs; if(value) compilerConfigs = value; } }
       get { return compilerConfigs; }
       isset { return false; }
    }
@@ -800,6 +1079,43 @@ public:
       isset { return language != null; }
    }
 
+   property const String codeEditorFont
+   {
+      set
+      {
+         delete codeEditorFont;
+         codeEditorFont = CopyString(value);
+      }
+      get { return codeEditorFont; }
+   }
+
+   float codeEditorFontSize;
+   bool showFixedPitchFontsOnly;
+
+   property Array<IDEColorScheme> colorSchemes
+   {
+      set
+      {
+         if(colorSchemes && colorSchemes._refCount < 2)
+            colorSchemes.Free();
+         delete colorSchemes;
+         colorSchemes = value;
+         if(value)
+            incref colorSchemes;
+      }
+      get { return colorSchemes; }
+   }
+
+   property const String activeColorScheme
+   {
+      set
+      {
+         delete activeColorScheme;
+         activeColorScheme = CopyString(value);
+      }
+      get { return activeColorScheme; }
+   }
+
 private:
    CompilerConfigs compilerConfigs { };
    char * docDir;
@@ -813,24 +1129,15 @@ private:
    RecentFiles recentFiles { };
    RecentWorkspaces recentProjects { };
 
-   CompilerConfig GetCompilerConfig(const String compilerName)
-   {
-      const char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
-      CompilerConfig compilerConfig = null;
-      for(compiler : compilerConfigs)
-      {
-         if(!strcmp(compiler.name, name))
-         {
-            compilerConfig = compiler;
-            break;
-         }
-      }
-      if(!compilerConfig && compilerConfigs.count)
-         compilerConfig = compilerConfigs.firstIterator.data;
-      if(compilerConfig)
-         incref compilerConfig;
-      return compilerConfig;
-   }
+   Array<IDEColorScheme> colorSchemes;
+
+   String codeEditorFont;
+
+   String activeColorScheme;
+
+   showFixedPitchFontsOnly = true;
+   codeEditorFontSize = 12;
+   codeEditorFont = CopyString("Courier New");
 
    ~IDESettings()
    {
@@ -849,6 +1156,11 @@ private:
       delete ideFileDialogLocation;
       delete ideProjectFileDialogLocation;
       delete displayDriver;
+
+      delete codeEditorFont;
+
+      colorSchemes.Free();
+      delete activeColorScheme;
    }
 
    void ForcePathSeparatorStyle(bool unixStyle)
@@ -996,38 +1308,32 @@ private:
 
 class RecentFiles : RecentPaths
 {
-   void onAdd()
-   {
-      write();
-   }
-
-   void ::read()
+   void read(IDESettingsContainer settingsContainer)
    {
       char path[MAX_LOCATION];
       RecentFilesData d = null;
       Class _class = class(RecentFilesData);
-      getConfigFilePath(path, _class, null, null);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
       readConfigFile(path, _class, &d);
       if(d && d.recentFiles && d.recentFiles.count)
       {
-         IDESettings s = (IDESettings)settingsContainer.data;
-         s.property::recentFiles = d.recentFiles;
-         d.recentFiles = null;
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-         ide.updateRecentFilesMenu();
-#endif
+         Free();
+         Copy((void *)d.recentFiles);
+         settingsContainer.recentFiles = this; // Merge IDEConfigHolder / IDESettingsContainer?
       }
       delete d;
+      settingsContainer.recentFilesMonitor.fileName = path;
+      settingsContainer.recentFilesMonitor.StartMonitoring();
+      settingsContainer.onLoadRecentFiles();
    }
 
-   void ::write()
+   void write(IDESettingsContainer settingsContainer)
    {
       char path[MAX_LOCATION];
-      IDESettings s = (IDESettings)settingsContainer.data;
       RecentFilesData d { };
       Class _class = class(RecentFilesData);
-      getConfigFilePath(path, _class, null, null);
-      d.recentFiles = s.recentFiles;
+      settingsContainer.getConfigFilePath(path, _class, null, null);
+      d.recentFiles = this;
       writeConfigFile(path, _class, d);
       d.recentFiles = null;
       delete d;
@@ -1036,38 +1342,32 @@ class RecentFiles : RecentPaths
 
 class RecentWorkspaces : RecentPaths
 {
-   void onAdd()
-   {
-      write();
-   }
-
-   void ::read()
+   void read(IDESettingsContainer settingsContainer)
    {
       char path[MAX_LOCATION];
       RecentWorkspacesData d = null;
       Class _class = class(RecentWorkspacesData);
-      getConfigFilePath(path, _class, null, null);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
       readConfigFile(path, _class, &d);
       if(d && d.recentWorkspaces && d.recentWorkspaces.count)
       {
-         IDESettings s = (IDESettings)settingsContainer.data;
-         s.property::recentProjects = d.recentWorkspaces;
-         d.recentWorkspaces = null;
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-         ide.updateRecentProjectsMenu();
-#endif
+         Free();
+         Copy((void *)d.recentWorkspaces);
+         settingsContainer.recentProjects = this; // Merge IDEConfigHolder / IDESettingsContainer?
       }
       delete d;
+      settingsContainer.recentProjectsMonitor.fileName = path;
+      settingsContainer.recentProjectsMonitor.StartMonitoring();
+      settingsContainer.onLoadRecentProjects();
    }
 
-   void ::write()
+   void write(IDESettingsContainer settingsContainer)
    {
       char path[MAX_LOCATION];
-      IDESettings s = (IDESettings)settingsContainer.data;
       RecentWorkspacesData d { };
       Class _class = class(RecentWorkspacesData);
-      getConfigFilePath(path, _class, null, null);
-      d.recentWorkspaces = s.recentProjects;
+      settingsContainer.getConfigFilePath(path, _class, null, null);
+      d.recentWorkspaces = this;
       writeConfigFile(path, _class, d);
       d.recentWorkspaces = null;
       delete d;
@@ -1076,8 +1376,6 @@ class RecentWorkspaces : RecentPaths
 
 class RecentPaths : Array<String>
 {
-   virtual void onAdd();
-
    IteratorPointer Add(T value)
    {
       int c;
@@ -1094,10 +1392,10 @@ class RecentPaths : Array<String>
       return Array::Add((T)filePath);
    }
 
-   IteratorPointer addRecent(T value)
+   IteratorPointer addRecent(const String value)
    {
       int c;
-      char * filePath = (char *)value;
+      char * filePath = CopyString((char *)value);
       IteratorPointer ip;
       ChangeCh(filePath, '\\', '/');
       for(c = 0; c < count; c++)
@@ -1111,7 +1409,6 @@ class RecentPaths : Array<String>
       while(count >= MaxRecent)
          Delete(GetLast());
       ip = Insert(null, filePath);
-      onAdd();
       return ip;
    }
 
@@ -1575,7 +1872,18 @@ private:
    int OnCompare(CompilerConfig b)
    {
       int result;
-      if(!(result = name.OnCompare(b.name)) &&
+      if(
+         !(result = type.OnCompare(b.type)) &&
+         !(result = targetPlatform.OnCompare(b.targetPlatform)) &&
+         !(result = numJobs.OnCompare(b.numJobs)) &&
+         !(result = ccacheEnabled.OnCompare(b.ccacheEnabled)) &&
+         !(result = distccEnabled.OnCompare(b.distccEnabled)) &&
+         !(result = resourcesDotEar.OnCompare(b.resourcesDotEar)) &&
+         !(result = noStripTarget.OnCompare(b.noStripTarget))
+         );
+
+      if(!result &&
+         !(result = name.OnCompare(b.name)) &&
          !(result = ecpCommand.OnCompare(b.ecpCommand)) &&
          !(result = eccCommand.OnCompare(b.eccCommand)) &&
          !(result = ecsCommand.OnCompare(b.ecsCommand)) &&
@@ -1591,8 +1899,7 @@ private:
          !(result = executableLauncher.OnCompare(b.executableLauncher)) &&
          !(result = distccHosts.OnCompare(b.distccHosts)) &&
          !(result = gnuToolchainPrefix.OnCompare(b.gnuToolchainPrefix)) &&
-         !(result = sysroot.OnCompare(b.sysroot)))
-         ;
+         !(result = sysroot.OnCompare(b.sysroot)));
 
       if(!result &&
          !(result = includeDirs.OnCompare(b.includeDirs)) &&
@@ -1604,9 +1911,7 @@ private:
          !(result = cxxFlags.OnCompare(b.cxxFlags)) &&
          !(result = eCcompilerFlags.OnCompare(b.eCcompilerFlags)) &&
          !(result = compilerFlags.OnCompare(b.compilerFlags)) &&
-         !(result = linkerFlags.OnCompare(b.linkerFlags)))
-         ;
-
+         !(result = linkerFlags.OnCompare(b.linkerFlags)));
       return result;
    }
 
@@ -1666,12 +1971,12 @@ public:
       return d;
    }
 
-   void write()
+   void write(IDESettingsContainer settingsContainer)
    {
       char dir[MAX_LOCATION];
       char path[MAX_LOCATION];
       const char * settingsFilePath = settingsContainer.settingsFilePath;
-      getConfigFilePath(path, _class, dir, name);
+      settingsContainer.getConfigFilePath(path, _class, dir, name);
       if(FileExists(settingsFilePath) && !FileExists(dir))
       {
          MakeDir(dir);
@@ -1684,52 +1989,70 @@ public:
 
 class CompilerConfigs : List<CompilerConfig>
 {
-   void ::fix()
+   CompilerConfig GetCompilerConfig(const String compilerName)
+   {
+      const char * name = compilerName && compilerName[0] ? compilerName : defaultCompilerName;
+      CompilerConfig compilerConfig = null;
+      for(compiler : this)
+      {
+         if(!strcmp(compiler.name, name))
+         {
+            compilerConfig = compiler;
+            break;
+         }
+      }
+      if(!compilerConfig && count)
+         compilerConfig = this[0];
+      if(compilerConfig)
+      {
+         incref compilerConfig;
+         if(compilerConfig._refCount == 1)
+            incref compilerConfig;
+      }
+      return compilerConfig;
+   }
+
+   void ensureDefaults()
    {
-      IDESettings s = (IDESettings)settingsContainer.data;
       // Ensure we have a default compiler
-      CompilerConfig defaultCompiler = null;
-      defaultCompiler = s.GetCompilerConfig(defaultCompilerName);
+      CompilerConfig defaultCompiler = GetCompilerConfig(defaultCompilerName);
       if(!defaultCompiler)
       {
          defaultCompiler = MakeDefaultCompiler(defaultCompilerName, true);
-         s.compilerConfigs.Insert(null, defaultCompiler);
+         Insert(null, defaultCompiler);
          defaultCompiler = null;
       }
       delete defaultCompiler;
 
-      if(s.compilerConfigs)
+      for(ccfg : this)
       {
-         for(ccfg : s.compilerConfigs)
-         {
-            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;*/
-            if(!ccfg._refCount) incref ccfg;
-         }
+         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;*/
+         if(!ccfg._refCount) incref ccfg;
       }
    }
 
@@ -1738,80 +2061,74 @@ class CompilerConfigs : List<CompilerConfig>
       AVLTree<String> list { };
       for(ccfg : this)
       {
-         for(occfg : oldConfigs)
+         bool found = false;
+         for(occfg : oldConfigs; !strcmp(ccfg.name, occfg.name))
          {
-            if(!strcmp(ccfg.name, occfg.name))
-            {
-               if(ccfg.OnCompare(occfg))
-                  list.Add(CopyString(ccfg.name));
-               break;
-            }
+            found = true;
+            if(ccfg.OnCompare(occfg))
+               list.Add(CopyString(ccfg.name));
+            break;
          }
+         if(!found)
+            list.Add(CopyString(ccfg.name));
       }
       return list;
    }
 
-   void ::read()
+   bool read(IDESettingsContainer settingsContainer)
    {
       if(settingsContainer.settingsFilePath)
       {
          char dir[MAX_LOCATION];
          char path[MAX_LOCATION];
          Class _class = class(CompilerConfig);
-         getConfigFilePath(path, _class, dir, null);
+         settingsContainer.getConfigFilePath(path, _class, dir, null);
          if(dir[0])
          {
-            CompilerConfigs ccfgs { };
             AVLTree<const String> addedConfigs { };
-            IDESettings s = (IDESettings)settingsContainer.data;
             Map<String, CompilerConfig> compilerConfigsByName = getCompilerConfigsByName(dir);
             MapIterator<const String, CompilerConfig> it { map = compilerConfigsByName };
+            Free();
+            settingsContainer.compilerConfigs = this; // Merge IDEConfigHolder / IDESettingsContainer?
             if(it.Index("Default", false))
             {
                CompilerConfig ccfg = it.data;
-               ccfgs.Add(ccfg.Copy());
+               Add(ccfg.Copy());
                addedConfigs.Add(ccfg.name);
             }
             for(ccfg : compilerConfigsByName)
             {
                if(!addedConfigs.Find(ccfg.name))
                {
-                  ccfgs.Add(ccfg.Copy());
+                  Add(ccfg.Copy());
                   addedConfigs.Add(ccfg.name);
                }
             }
-            for(ccfg : s.compilerConfigs)
-            {
-               if(!addedConfigs.Find(ccfg.name))
-                  ccfgs.Add(ccfg.Copy());
-            }
             delete addedConfigs;
-            s.property::compilerConfigs = ccfgs;
-            fix();
+            ensureDefaults();
             compilerConfigsByName.Free();
             delete compilerConfigsByName;
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-            ide.UpdateCompilerConfigs(true);
-#endif
+            settingsContainer.onLoadCompilerConfigs();
+            return true;
          }
       }
+      return false;
    }
 
-   void ::write(AVLTree<String> cfgsToWrite)
+   void write(IDESettingsContainer settingsContainer, AVLTree<String> cfgsToWrite)
    {
       char dir[MAX_LOCATION];
       char path[MAX_LOCATION];
       Map<String, String> paths;
-      IDESettings s = (IDESettings)settingsContainer.data;
-      getConfigFilePath(path, class(CompilerConfig), dir, null);
+      settingsContainer.getConfigFilePath(path, class(CompilerConfig), dir, null);
       paths = getCompilerConfigFilePathsByName(dir);
       {
          MapIterator<String, String> it { map = paths };
-         for(c : s.compilerConfigs)
+         for(c : this)
          {
             CompilerConfig ccfg = c;
             if(!cfgsToWrite || cfgsToWrite.Find(ccfg.name))
-               ccfg.write();
+               ccfg.write(settingsContainer);
             if(it.Index(ccfg.name, false))
             {
                delete it.data;
@@ -1895,6 +2212,24 @@ const String GetLanguageString()
    return language;
 }
 
+void setEcereLanguageInWinRegEnvironment(const char * languageCode)
+{
+#ifdef __WIN32__
+   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(languageCode, wLanguage, sizeof(wLanguage) / sizeof(uint16));
+      RegSetValueExW(key, L"ECERE_LANGUAGE", 0, REG_EXPAND_SZ, (byte *)wLanguage, (uint)(wcslen(wLanguage)+1) * 2);
+      RegCloseKey(key);
+   }
+#endif
+}
+
 bool LanguageRestart(const char * code, Application app, IDESettings settings, IDESettingsContainer settingsContainer, Window ide, Window projectView, bool wait)
 {
    bool restart = true;