ide/IDESettings: Fixed issues with fresh install
[sdk] / ide / src / IDESettings.ec
index b83f2a5..ab38ac5 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 };
@@ -250,9 +410,14 @@ CompilerConfig MakeDefaultCompiler(const char * name, bool readOnly)
 define settingsDir = ".ecereIDE-SettingsTest";
 define ideSettingsName = "ecereIDE-SettingsTest";
 #else
+#ifdef __WIN32__
+define settingsDir = "EcereIDE";
+define ideSettingsName = "EcereIDE";
+#else
 define settingsDir = ".ecereIDE";
 define ideSettingsName = "ecereIDE";
 #endif
+#endif
 
 class IDEConfigHolder
 {
@@ -297,20 +462,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;
@@ -334,7 +508,7 @@ class IDESettingsContainer : GlobalSettings
             int c;
             bool locked;
             for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
-            RecentFiles::read();
+            recentFiles.read(this);
             f.Unlock(0,0,true);
             delete f;
          }
@@ -356,7 +530,8 @@ class IDESettingsContainer : GlobalSettings
             int c;
             bool locked;
             for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++) ecere::sys::Sleep(0.01);
-            RecentWorkspaces::read();
+            if(!recentProjects) recentProjects = { };
+            recentProjects.read(this);
             f.Unlock(0,0,true);
             delete f;
          }
@@ -364,6 +539,30 @@ class IDESettingsContainer : GlobalSettings
       }
    };
 
+   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;
@@ -377,7 +576,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];
@@ -422,9 +621,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;
@@ -439,7 +639,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();
-         }
+         }*/
       }
    }
 
@@ -451,11 +651,33 @@ private:
       result = GlobalSettings::Load();
       data = (IDESettings)this.data;
       oldConfig = false;
-      if(result == fileNotFound)
+      if(result == fileNotFound || (settingsFilePath && isGlobalPath))
       {
-         oldConfig = true;
+         bool retryNewConfig = settingsFilePath && isGlobalPath;
+
+         if(retryNewConfig)
+         {
+            // Need to load the data outside of main config file first
+            data.compilerConfigs.read(this);
+            data.recentFiles.read(this);
+            data.recentProjects.read(this);
+         }
+         oldConfig = true; // WARNING: This is being used with two meanings: Old format and loaded from system-wide settings
          useNewConfigurationFiles = false;
-         result = GlobalSettings::Load();
+         if(retryNewConfig)
+         {
+            settingsFilePath = null;
+            CloseAndMonitor();
+         }
+
+         GlobalSettings::Load();
+
+         if(result == fileNotFound && retryNewConfig)
+         {
+            oldConfig = false;
+            useNewConfigurationFiles = true;
+            result = GlobalSettings::Load();
+         }
       }
       data = (IDESettings)this.data;
       if(!data)
@@ -511,25 +733,46 @@ private:
          data.ManagePortablePaths(moduleLocation, true);
       data.ForcePathSeparatorStyle(true);
 
+      if(!data.colorSchemes || !data.colorSchemes.count)
+      {
+         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;
+      }
+
       // Import from previous ecereIDE settings
       if(oldConfig)
       {
+         // Save first so that settingsFilePath get set up
+         Save();
+
          data.compilerConfigs.ensureDefaults();
-         data.compilerConfigs.write(null);
+         data.compilerConfigs.write(this, null);
          data.compilerConfigs.Free();
 
-         data.recentFiles.write();
+         data.recentFiles.write(this);
          data.recentFiles.Free();
 
-         data.recentProjects.write();
+         data.recentProjects.write(this);
          data.recentProjects.Free();
-
-         Save();
       }
-
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-      globalSettingsDialog.ideSettings = data;
-#endif
       return result;
    }
 
@@ -602,38 +845,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);
-         strcat(path, ".econ");
-      }
-   }
-   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;
    }
@@ -648,7 +867,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;
       {
@@ -678,81 +897,141 @@ 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)
          {
-            delete sf.file;
-            PrintLn($"warning: safeWriteFileOpen: unable to obtain shared lock on file for reading: ", path);
+            f = FileOpen(path, writeRead);
+            if(f)
+            {
+               delete f;
+               f = FileOpen(path, readWrite);
+            }
+         }
+         if(f)
+         {
+            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
 {
 public:
    RecentFiles recentFiles;
+
+   ~RecentFilesData()
+   {
+      if(recentFiles) recentFiles.Free();
+      delete recentFiles;
+   }
 }
 
 class RecentWorkspacesData
 {
 public:
    RecentWorkspaces recentWorkspaces;
+
+   ~RecentWorkspacesData()
+   {
+      if(recentWorkspaces) recentWorkspaces.Free();
+      delete recentWorkspaces;
+   }
 }
 
 class IDESettings : GlobalSettingsData
@@ -760,7 +1039,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; }
    }
@@ -840,6 +1119,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;
@@ -853,6 +1169,16 @@ private:
    RecentFiles recentFiles { };
    RecentWorkspaces recentProjects { };
 
+   Array<IDEColorScheme> colorSchemes;
+
+   String codeEditorFont;
+
+   String activeColorScheme;
+
+   showFixedPitchFontsOnly = true;
+   codeEditorFontSize = 12;
+   codeEditorFont = CopyString("Courier New");
+
    ~IDESettings()
    {
       compilerConfigs.Free();
@@ -870,6 +1196,11 @@ private:
       delete ideFileDialogLocation;
       delete ideProjectFileDialogLocation;
       delete displayDriver;
+
+      delete codeEditorFont;
+
+      colorSchemes.Free();
+      delete activeColorScheme;
    }
 
    void ForcePathSeparatorStyle(bool unixStyle)
@@ -1017,37 +1348,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)
       {
-         ideConfig.recentFiles = d.recentFiles;
-         d.recentFiles = null;
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-         ide.updateRecentFilesMenu();
-#endif
+         Free();
+         Copy((void *)d.recentFiles);
+         d.recentFiles.RemoveAll();
+         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];
       RecentFilesData d { };
       Class _class = class(RecentFilesData);
-      getConfigFilePath(path, _class, null, null);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
       d.recentFiles = this;
       writeConfigFile(path, _class, d);
       d.recentFiles = null;
@@ -1057,37 +1383,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)
       {
-         ideConfig.recentProjects = d.recentWorkspaces;
-         d.recentWorkspaces = null;
-#if !defined(ECERE_DOCUMENTOR) && !defined(ECERE_EPJ2MAKE)
-         ide.updateRecentProjectsMenu();
-#endif
+         Free();
+         Copy((void *)d.recentWorkspaces);
+         d.recentWorkspaces.RemoveAll();
+         settingsContainer.recentProjects = this; // Merge IDEConfigHolder / IDESettingsContainer?
       }
+      delete d;
       settingsContainer.recentProjectsMonitor.fileName = path;
       settingsContainer.recentProjectsMonitor.StartMonitoring();
-      delete d;
+      settingsContainer.onLoadRecentProjects();
    }
 
-   void write()
+   void write(IDESettingsContainer settingsContainer)
    {
       char path[MAX_LOCATION];
       RecentWorkspacesData d { };
       Class _class = class(RecentWorkspacesData);
-      getConfigFilePath(path, _class, null, null);
+      settingsContainer.getConfigFilePath(path, _class, null, null);
       d.recentWorkspaces = this;
       writeConfigFile(path, _class, d);
       d.recentWorkspaces = null;
@@ -1097,8 +1418,6 @@ class RecentWorkspaces : RecentPaths
 
 class RecentPaths : Array<String>
 {
-   virtual void onAdd();
-
    IteratorPointer Add(T value)
    {
       int c;
@@ -1132,7 +1451,6 @@ class RecentPaths : Array<String>
       while(count >= MaxRecent)
          Delete(GetLast());
       ip = Insert(null, filePath);
-      onAdd();
       return ip;
    }
 
@@ -1599,7 +1917,12 @@ private:
       if(
          !(result = type.OnCompare(b.type)) &&
          !(result = targetPlatform.OnCompare(b.targetPlatform)) &&
-         !(result = numJobs.OnCompare(b.numJobs)));
+         !(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)) &&
@@ -1690,12 +2013,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);
@@ -1794,19 +2117,21 @@ class CompilerConfigs : List<CompilerConfig>
       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])
          {
             AVLTree<const String> addedConfigs { };
             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;
@@ -1821,30 +2146,23 @@ class CompilerConfigs : List<CompilerConfig>
                   addedConfigs.Add(ccfg.name);
                }
             }
-            /*
-            for(ccfg : this)
-            {
-               if(!addedConfigs.Find(ccfg.name))
-                  Add(ccfg.Copy());
-            }
-            */
             delete addedConfigs;
             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;
-      getConfigFilePath(path, class(CompilerConfig), dir, null);
+      settingsContainer.getConfigFilePath(path, class(CompilerConfig), dir, null);
       paths = getCompilerConfigFilePathsByName(dir);
       {
          MapIterator<String, String> it { map = paths };
@@ -1852,7 +2170,7 @@ class CompilerConfigs : List<CompilerConfig>
          {
             CompilerConfig ccfg = c;
             if(!cfgsToWrite || cfgsToWrite.Find(ccfg.name))
-               ccfg.write();
+               ccfg.write(settingsContainer);
             if(it.Index(ccfg.name, false))
             {
                delete it.data;
@@ -1936,6 +2254,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;