Bläddra i källkod

Configurable path to INI file configuration storage

Source commit: b4ebec9b69b7dc55cc11bc1d6ddd15a1909a13b9
Martin Prikryl 9 år sedan
förälder
incheckning
4f77be14e8

+ 73 - 0
source/core/Common.cpp

@@ -442,6 +442,79 @@ UnicodeString __fastcall GetShellFolderPath(int CSIdl)
   return Result;
 }
 //---------------------------------------------------------------------------
+static UnicodeString __fastcall GetWineHomeFolder()
+{
+  UnicodeString Result;
+
+  UnicodeString WineHostHome = GetEnvironmentVariable(L"WINE_HOST_HOME");
+  if (!WineHostHome.IsEmpty())
+  {
+    Result = L"Z:" + FromUnixPath(WineHostHome);
+  }
+  else
+  {
+    // Should we use WinAPI GetUserName() instead?
+    UnicodeString UserName = GetEnvironmentVariable(L"USERNAME");
+    if (!UserName.IsEmpty())
+    {
+      Result = L"Z:\\home\\" + UserName;
+    }
+  }
+
+  if (!DirectoryExists(Result))
+  {
+    Result = L"";
+  }
+
+  return Result;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall GetPersonalFolder()
+{
+  UnicodeString Result = GetShellFolderPath(CSIDL_PERSONAL);
+
+  if (IsWine())
+  {
+    UnicodeString WineHome = GetWineHomeFolder();
+
+    if (!WineHome.IsEmpty())
+    {
+      // if at least home exists, use it
+      Result = WineHome;
+
+      // but try to go deeper to "Documents"
+      UnicodeString WineDocuments =
+        IncludeTrailingBackslash(WineHome) + L"Documents";
+      if (DirectoryExists(WineDocuments))
+      {
+        Result = WineDocuments;
+      }
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall GetDesktopFolder()
+{
+  UnicodeString Result = GetShellFolderPath(CSIDL_DESKTOPDIRECTORY);
+
+  if (IsWine())
+  {
+    UnicodeString WineHome = GetWineHomeFolder();
+
+    if (!WineHome.IsEmpty())
+    {
+      UnicodeString WineDesktop =
+        IncludeTrailingBackslash(WineHome) + L"Desktop";
+      if (DirectoryExists(WineHome))
+      {
+        Result = WineDesktop;
+      }
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 // Particularly needed when using file name selected by TFilenameEdit,
 // as it wraps a path to double-quotes, when there is a space in the path.
 UnicodeString __fastcall StripPathQuotes(const UnicodeString Path)

+ 2 - 0
source/core/Common.h

@@ -64,6 +64,8 @@ UnicodeString RemoveEmptyLines(const UnicodeString & S);
 bool IsNumber(const UnicodeString Str);
 UnicodeString __fastcall SystemTemporaryDirectory();
 UnicodeString __fastcall GetShellFolderPath(int CSIdl);
+UnicodeString __fastcall GetPersonalFolder();
+UnicodeString __fastcall GetDesktopFolder();
 UnicodeString __fastcall StripPathQuotes(const UnicodeString Path);
 UnicodeString __fastcall AddQuotes(UnicodeString Str);
 UnicodeString __fastcall AddPathQuotes(UnicodeString Path);

+ 117 - 50
source/core/Configuration.cpp

@@ -124,6 +124,7 @@ void __fastcall TConfiguration::Default()
   FActionsLogFileName = L"%TEMP%\\!S.xml";
   FPermanentActionsLogFileName = FActionsLogFileName;
   FProgramIniPathWrittable = -1;
+  FCustomIniFileStorageName = LoadCustomIniFileStorageName();
 
   Changed();
 }
@@ -139,6 +140,7 @@ __fastcall TConfiguration::~TConfiguration()
 void __fastcall TConfiguration::UpdateStaticUsage()
 {
   Usage->Set(L"ConfigurationIniFile", (Storage == stIniFile));
+  Usage->Set(L"ConfigurationIniFileCustom", !CustomIniFileStorageName.IsEmpty());
   Usage->Set("Unofficial", IsUnofficial);
 
   // this is called from here, because we are guarded from calling into
@@ -293,6 +295,25 @@ void __fastcall TConfiguration::DoSave(bool All, bool Explicit)
   {
     CleanupIniFile();
   }
+
+  SaveCustomIniFileStorageName();
+
+}
+//---------------------------------------------------------------------------
+void __fastcall TConfiguration::SaveCustomIniFileStorageName()
+{
+  // Particularly, not to create an empty "Override" key, unless the custom INI file is ever set
+  if (CustomIniFileStorageName != LoadCustomIniFileStorageName())
+  {
+    std::unique_ptr<TRegistryStorage> RegistryStorage(new TRegistryStorage(GetRegistryStorageOverrideKey()));
+    RegistryStorage->AccessMode = smReadWrite;
+    RegistryStorage->Explicit = true;
+    if (RegistryStorage->OpenRootKey(true))
+    {
+      RegistryStorage->WriteString(L"IniFile", CustomIniFileStorageName);
+      RegistryStorage->CloseSubKey();
+    }
+  }
 }
 //---------------------------------------------------------------------------
 void __fastcall TConfiguration::Export(const UnicodeString & FileName)
@@ -405,6 +426,24 @@ void __fastcall TConfiguration::LoadFrom(THierarchicalStorage * Storage)
   }
 }
 //---------------------------------------------------------------------------
+UnicodeString __fastcall TConfiguration::GetRegistryStorageOverrideKey()
+{
+  return GetRegistryStorageKey() + L" Override";
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TConfiguration::LoadCustomIniFileStorageName()
+{
+  UnicodeString Result;
+  std::unique_ptr<TRegistryStorage> RegistryStorage(new TRegistryStorage(GetRegistryStorageOverrideKey()));
+  if (RegistryStorage->OpenRootKey(false))
+  {
+    Result = RegistryStorage->ReadString(L"IniFile", L"");
+    RegistryStorage->CloseSubKey();
+  }
+  RegistryStorage.reset(NULL);
+  return Result;
+}
+//---------------------------------------------------------------------------
 void __fastcall TConfiguration::Load(THierarchicalStorage * Storage)
 {
   TGuard Guard(FCriticalSection);
@@ -1023,6 +1062,14 @@ void __fastcall TConfiguration::SetIniFileStorageName(UnicodeString value)
   FStorage = stIniFile;
 }
 //---------------------------------------------------------------------------
+UnicodeString __fastcall TConfiguration::GetDefaultIniFileExportPath()
+{
+  UnicodeString PersonalDirectory = GetPersonalFolder();
+  UnicodeString FileName = IncludeTrailingBackslash(PersonalDirectory) +
+    ExtractFileName(ExpandEnvironmentVariables(IniFileStorageName));
+  return FileName;
+}
+//---------------------------------------------------------------------------
 UnicodeString __fastcall TConfiguration::GetIniFileStorageNameForReading()
 {
   return GetIniFileStorageName(true);
@@ -1033,73 +1080,82 @@ UnicodeString __fastcall TConfiguration::GetIniFileStorageNameForReadingWriting(
   return GetIniFileStorageName(false);
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall TConfiguration::GetIniFileStorageName(bool ReadingOnly)
+UnicodeString __fastcall TConfiguration::GetAutomaticIniFileStorageName(bool ReadingOnly)
 {
-  if (FIniFileStorageName.IsEmpty())
-  {
-    UnicodeString ProgramPath = ParamStr(0);
+  UnicodeString ProgramPath = ParamStr(0);
 
-    UnicodeString ProgramIniPath = ChangeFileExt(ProgramPath, L".ini");
+  UnicodeString ProgramIniPath = ChangeFileExt(ProgramPath, L".ini");
 
-    UnicodeString IniPath;
-    if (FileExists(ApiPath(ProgramIniPath)))
+  UnicodeString IniPath;
+  if (FileExists(ApiPath(ProgramIniPath)))
+  {
+    IniPath = ProgramIniPath;
+  }
+  else
+  {
+    UnicodeString AppDataIniPath =
+      IncludeTrailingBackslash(GetShellFolderPath(CSIDL_APPDATA)) +
+      ExtractFileName(ProgramIniPath);
+    if (FileExists(ApiPath(AppDataIniPath)))
     {
-      IniPath = ProgramIniPath;
+      IniPath = AppDataIniPath;
     }
     else
     {
-      UnicodeString AppDataIniPath =
-        IncludeTrailingBackslash(GetShellFolderPath(CSIDL_APPDATA)) +
-        ExtractFileName(ProgramIniPath);
-      if (FileExists(ApiPath(AppDataIniPath)))
+      // avoid expensive test if we are interested in existing files only
+      if (!ReadingOnly && (FProgramIniPathWrittable < 0))
       {
-        IniPath = AppDataIniPath;
+        UnicodeString ProgramDir = ExtractFilePath(ProgramPath);
+        FProgramIniPathWrittable = IsDirectoryWriteable(ProgramDir) ? 1 : 0;
       }
-      else
-      {
-        // avoid expensive test if we are interested in existing files only
-        if (!ReadingOnly && (FProgramIniPathWrittable < 0))
-        {
-          UnicodeString ProgramDir = ExtractFilePath(ProgramPath);
-          FProgramIniPathWrittable = IsDirectoryWriteable(ProgramDir) ? 1 : 0;
-        }
 
-        // does not really matter what we return when < 0
-        IniPath = (FProgramIniPathWrittable == 0) ? AppDataIniPath : ProgramIniPath;
-      }
+      // does not really matter what we return when < 0
+      IniPath = (FProgramIniPathWrittable == 0) ? AppDataIniPath : ProgramIniPath;
     }
+  }
 
-    // BACKWARD COMPATIBILITY with 4.x
-    if (FVirtualIniFileStorageName.IsEmpty() &&
-        TPath::IsDriveRooted(IniPath))
+  // BACKWARD COMPATIBILITY with 4.x
+  if (FVirtualIniFileStorageName.IsEmpty() &&
+      TPath::IsDriveRooted(IniPath))
+  {
+    UnicodeString LocalAppDataPath = GetShellFolderPath(CSIDL_LOCAL_APPDATA);
+    // virtual store for non-system drives have a different virtual store,
+    // do not bother about them
+    if (TPath::IsDriveRooted(LocalAppDataPath) &&
+        SameText(ExtractFileDrive(IniPath), ExtractFileDrive(LocalAppDataPath)))
     {
-      UnicodeString LocalAppDataPath = GetShellFolderPath(CSIDL_LOCAL_APPDATA);
-      // virtual store for non-system drives have a different virtual store,
-      // do not bother about them
-      if (TPath::IsDriveRooted(LocalAppDataPath) &&
-          SameText(ExtractFileDrive(IniPath), ExtractFileDrive(LocalAppDataPath)))
-      {
-        FVirtualIniFileStorageName =
-          IncludeTrailingBackslash(LocalAppDataPath) +
-          L"VirtualStore\\" +
-          IniPath.SubString(4, IniPath.Length() - 3);
-      }
+      FVirtualIniFileStorageName =
+        IncludeTrailingBackslash(LocalAppDataPath) +
+        L"VirtualStore\\" +
+        IniPath.SubString(4, IniPath.Length() - 3);
     }
+  }
 
-    if (!FVirtualIniFileStorageName.IsEmpty() &&
-        FileExists(ApiPath(FVirtualIniFileStorageName)))
-    {
-      return FVirtualIniFileStorageName;
-    }
-    else
-    {
-      return IniPath;
-    }
+  if (!FVirtualIniFileStorageName.IsEmpty() &&
+      FileExists(ApiPath(FVirtualIniFileStorageName)))
+  {
+    return FVirtualIniFileStorageName;
   }
   else
+  {
+    return IniPath;
+  }
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TConfiguration::GetIniFileStorageName(bool ReadingOnly)
+{
+  if (!FIniFileStorageName.IsEmpty())
   {
     return FIniFileStorageName;
   }
+  else if (!FCustomIniFileStorageName.IsEmpty())
+  {
+    return FCustomIniFileStorageName;
+  }
+  else
+  {
+    return GetAutomaticIniFileStorageName(ReadingOnly);
+  }
 }
 //---------------------------------------------------------------------------
 void __fastcall TConfiguration::SetOptionsStorage(TStrings * value)
@@ -1142,11 +1198,13 @@ UnicodeString __fastcall TConfiguration::GetRootKeyStr()
   return RootKeyToStr(HKEY_CURRENT_USER);
 }
 //---------------------------------------------------------------------------
-void __fastcall TConfiguration::SetStorage(TStorage value)
+void __fastcall TConfiguration::MoveStorage(TStorage AStorage, const UnicodeString & ACustomIniFileStorageName)
 {
-  if (FStorage != value)
+  if ((FStorage != AStorage) ||
+      !IsPathToSameFile(FCustomIniFileStorageName, ACustomIniFileStorageName))
   {
     TStorage StorageBak = FStorage;
+    UnicodeString CustomIniFileStorageNameBak = FCustomIniFileStorageName;
     try
     {
       THierarchicalStorage * SourceStorage = NULL;
@@ -1157,7 +1215,8 @@ void __fastcall TConfiguration::SetStorage(TStorage value)
         SourceStorage = CreateConfigStorage();
         SourceStorage->AccessMode = smRead;
 
-        FStorage = value;
+        FStorage = AStorage;
+        FCustomIniFileStorageName = ACustomIniFileStorageName;
 
         TargetStorage = CreateConfigStorage();
         TargetStorage->AccessMode = smReadWrite;
@@ -1185,11 +1244,19 @@ void __fastcall TConfiguration::SetStorage(TStorage value)
       // - When removing INI file fails, when switching to registry
       //   (possible, when the INI file is in Program Files folder)
       FStorage = StorageBak;
+      FCustomIniFileStorageName = CustomIniFileStorageNameBak;
       throw;
     }
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TConfiguration::ScheduleCustomIniFileStorageUse(const UnicodeString & ACustomIniFileStorageName)
+{
+  FStorage = stIniFile;
+  FCustomIniFileStorageName = ACustomIniFileStorageName;
+  SaveCustomIniFileStorageName();
+}
+//---------------------------------------------------------------------------
 void __fastcall TConfiguration::Saved()
 {
   // nothing

+ 10 - 2
source/core/Configuration.h

@@ -56,6 +56,7 @@ private:
   int FSessionReopenBackground;
   int FSessionReopenTimeout;
   int FSessionReopenAutoStall;
+  UnicodeString FCustomIniFileStorageName;
   UnicodeString FIniFileStorageName;
   UnicodeString FVirtualIniFileStorageName;
   std::unique_ptr<TStrings> FOptionsStorage;
@@ -116,7 +117,6 @@ private:
   UnicodeString __fastcall GetActionsLogFileName();
   UnicodeString __fastcall GetDefaultLogFileName();
   UnicodeString __fastcall GetTimeFormat();
-  void __fastcall SetStorage(TStorage value);
   UnicodeString __fastcall GetRegistryStorageKey();
   UnicodeString __fastcall GetIniFileStorageNameForReadingWriting();
   UnicodeString __fastcall GetIniFileStorageNameForReading();
@@ -171,6 +171,9 @@ protected:
   void __fastcall SetAutoReadDirectoryAfterOp(bool value);
   virtual bool __fastcall GetRememberPassword();
   UnicodeString __fastcall GetReleaseType();
+  UnicodeString __fastcall LoadCustomIniFileStorageName();
+  void __fastcall SaveCustomIniFileStorageName();
+  UnicodeString __fastcall GetRegistryStorageOverrideKey();
 
   virtual UnicodeString __fastcall ModuleFileName();
 
@@ -198,8 +201,12 @@ public:
   void __fastcall Load(THierarchicalStorage * Storage);
   void __fastcall Save();
   void __fastcall SaveExplicit();
+  void __fastcall MoveStorage(TStorage AStorage, const UnicodeString & ACustomIniFileStorageName);
+  void __fastcall ScheduleCustomIniFileStorageUse(const UnicodeString & ACustomIniFileStorageName);
   void __fastcall SetNulStorage();
   void __fastcall SetDefaultStorage();
+  UnicodeString __fastcall GetAutomaticIniFileStorageName(bool ReadingOnly);
+  UnicodeString __fastcall GetDefaultIniFileExportPath();
   void __fastcall Export(const UnicodeString & FileName);
   void __fastcall Import(const UnicodeString & FileName);
   void __fastcall CleanupConfiguration();
@@ -289,8 +296,9 @@ public:
   __property int ParallelDurationThreshold = { read = FParallelDurationThreshold, write = SetParallelDurationThreshold };
 
   __property UnicodeString TimeFormat = { read = GetTimeFormat };
-  __property TStorage Storage  = { read=GetStorage, write=SetStorage };
+  __property TStorage Storage  = { read=GetStorage };
   __property UnicodeString RegistryStorageKey  = { read=GetRegistryStorageKey };
+  __property UnicodeString CustomIniFileStorageName  = { read=FCustomIniFileStorageName };
   __property UnicodeString IniFileStorageName  = { read=GetIniFileStorageNameForReadingWriting, write=SetIniFileStorageName };
   __property UnicodeString IniFileStorageNameForReading  = { read=GetIniFileStorageNameForReading };
   __property TStrings * OptionsStorage = { read = GetOptionsStorage, write = SetOptionsStorage };

+ 2 - 10
source/forms/Login.cpp

@@ -2472,17 +2472,9 @@ void __fastcall TLoginDialog::PortNumberEditChange(TObject * Sender)
   DataChange(Sender);
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall TLoginDialog::ImportExportIniFilePath()
-{
-  UnicodeString PersonalDirectory = GetPersonalFolder();
-  UnicodeString FileName = IncludeTrailingBackslash(PersonalDirectory) +
-    ExtractFileName(ExpandEnvironmentVariables(Configuration->IniFileStorageName));
-  return FileName;
-}
-//---------------------------------------------------------------------------
 void __fastcall TLoginDialog::ExportActionExecute(TObject * /*Sender*/)
 {
-  UnicodeString FileName = ImportExportIniFilePath();
+  UnicodeString FileName = Configuration->GetDefaultIniFileExportPath();
   if (SaveDialog(LoadStr(EXPORT_CONF_TITLE), LoadStr(EXPORT_CONF_FILTER), L"ini", FileName))
   {
     Configuration->Export(FileName);
@@ -2498,7 +2490,7 @@ void __fastcall TLoginDialog::ImportActionExecute(TObject * /*Sender*/)
     OpenDialog->Title = LoadStr(IMPORT_CONF_TITLE);
     OpenDialog->Filter = LoadStr(EXPORT_CONF_FILTER);
     OpenDialog->DefaultExt = L"ini";
-    OpenDialog->FileName = ImportExportIniFilePath();
+    OpenDialog->FileName = Configuration->GetDefaultIniFileExportPath();
 
     if (OpenDialog->Execute())
     {

+ 0 - 1
source/forms/Login.h

@@ -356,7 +356,6 @@ private:
   void __fastcall SetNewSiteNodeLabel();
   inline TSessionData * __fastcall GetNodeSession(TTreeNode * Node);
   void __fastcall ExecuteTool(const UnicodeString & Name);
-  UnicodeString __fastcall ImportExportIniFilePath();
   void __fastcall ReloadSessions(const UnicodeString & SelectSite);
   void __fastcall ResetSitesIncrementalSearch();
   bool __fastcall SitesIncrementalSearch(const UnicodeString & Text,

+ 116 - 18
source/forms/Preferences.cpp

@@ -29,8 +29,9 @@
 #pragma link "CopyParams"
 #pragma link "UpDownEdit"
 #pragma link "ComboEdit"
-#ifndef NO_RESOURCES
 #pragma link "HistoryComboBox"
+#pragma link "PathLabel"
+#ifndef NO_RESOURCES
 #pragma resource "*.dfm"
 #endif
 //---------------------------------------------------------------------
@@ -122,6 +123,8 @@ __fastcall TPreferencesDialog::TPreferencesDialog(
   LinkLabel(UpdatesLink);
   LinkAppLabel(BackgroundConfirmationsLink);
 
+  AutomaticIniFileStorageLabel->Caption = ExpandEnvironmentVariables(Configuration->GetAutomaticIniFileStorageName(false));
+
   HideComponentsPanel(this);
 }
 //---------------------------------------------------------------------------
@@ -332,7 +335,18 @@ void __fastcall TPreferencesDialog::LoadConfiguration()
 
     // select none when stNul
     RegistryStorageButton->Checked = (Configuration->Storage == stRegistry);
-    IniFileStorageButton2->Checked = (Configuration->Storage == stIniFile);
+    AutomaticIniFileStorageButton->Checked = (Configuration->Storage == stIniFile) && Configuration->CustomIniFileStorageName.IsEmpty();
+    CustomIniFileStorageButton->Checked = (Configuration->Storage == stIniFile) && !Configuration->CustomIniFileStorageName.IsEmpty();
+    CustomIniFileStorageEdit->Text = Configuration->CustomIniFileStorageName;
+    if (Configuration->CustomIniFileStorageName.IsEmpty())
+    {
+      CustomIniFileStorageEdit->Text = Configuration->GetDefaultIniFileExportPath();
+    }
+    else
+    {
+      CustomIniFileStorageEdit->Text = Configuration->CustomIniFileStorageName;
+    }
+    FCustomIniFileStorageName = GetCustomIniFileStorageName();
 
     RandomSeedFileEdit->Text = Configuration->RandomSeedFile;
 
@@ -902,24 +916,36 @@ void __fastcall TPreferencesDialog::SaveConfiguration()
       }
       GUIConfiguration->Locale = Locale;
     }
-
-    // This possibly fails, make it last, so that the other settings are preserved.
-    // Do nothing when no option is selected (i.e. storage is stNul).
-    if (RegistryStorageButton->Checked)
-    {
-      Configuration->Storage = stRegistry;
-    }
-    else if (IniFileStorageButton2->Checked)
-    {
-      Configuration->Storage = stIniFile;
-    }
-
     #undef BOOLPROP
   }
   __finally
   {
     Configuration->EndUpdate();
   }
+
+  bool MoveStorage = true;
+  TStorage Storage;
+  if (RegistryStorageButton->Checked)
+  {
+    Storage = stRegistry;
+  }
+  else if (AutomaticIniFileStorageButton->Checked)
+  {
+    Storage = stIniFile;
+  }
+  else if (CustomIniFileStorageButton->Checked)
+  {
+    Storage = stIniFile;
+  }
+  else
+  {
+    MoveStorage = false;
+  }
+
+  if (MoveStorage)
+  {
+    Configuration->MoveStorage(Storage, GetCustomIniFileStorageName());
+  }
 }
 //---------------------------------------------------------------------------
 TUpdatesConfiguration __fastcall TPreferencesDialog::SaveUpdates()
@@ -1188,10 +1214,10 @@ void __fastcall TPreferencesDialog::UpdateControls()
     // allow only when some of the known storages is selected,
     // and particularly do not allow switching storage, when we start with stNul,
     // as that would destroy the stored configuration
-    EnableControl(StorageGroup, RegistryStorageButton->Checked || IniFileStorageButton2->Checked);
-    IniFileStorageButton2->Caption =
-      AnsiReplaceStr(IniFileStorageButton2->Caption, L"(winscp.ini)",
-        FORMAT(L"(%s)", (ExpandEnvironmentVariables(Configuration->IniFileStorageName))));
+    EnableControl(StorageGroup,
+      RegistryStorageButton->Checked || AutomaticIniFileStorageButton->Checked || CustomIniFileStorageButton->Checked);
+    AutomaticIniFileStorageLabel->UpdateStatus();
+    EnableControl(CustomIniFileStorageEdit, CustomIniFileStorageButton->Checked);
 
     EditorFontLabel->WordWrap = EditorWordWrapCheck->Checked;
     bool EditorSelected = (EditorListView3->Selected != NULL);
@@ -2783,3 +2809,75 @@ void __fastcall TPreferencesDialog::PuttyPathEditExit(TObject * /*Sender*/)
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::AutomaticIniFileStorageLabelGetStatus(TCustomPathLabel * /*Sender*/, bool & Active)
+{
+  Active = AutomaticIniFileStorageButton->Checked;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TPreferencesDialog::GetCustomIniFileStorageName()
+{
+  UnicodeString Result;
+  if (CustomIniFileStorageButton->Checked)
+  {
+    Result = CustomIniFileStorageEdit->Text;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::CustomIniFileStorageChanged()
+{
+  UnicodeString CustomIniFileStorageName = GetCustomIniFileStorageName();
+  if (!CustomIniFileStorageName.IsEmpty() &&
+      !IsPathToSameFile(CustomIniFileStorageName, FCustomIniFileStorageName) &&
+      FileExists(CustomIniFileStorageName))
+  {
+    UnicodeString Message = FORMAT(LoadStrPart(CUSTOM_INI_FILE_OVERWRITE, 1), (CustomIniFileStorageName));
+    TMessageParams Params;
+    TQueryButtonAlias Aliases[2];
+    Aliases[0].Button = qaYes;
+    Aliases[0].Alias = LoadStrPart(CUSTOM_INI_FILE_OVERWRITE, 2);
+    Aliases[1].Button = qaNo;
+    Aliases[1].Alias = LoadStrPart(CUSTOM_INI_FILE_OVERWRITE, 3);
+    Params.Aliases = Aliases;
+    Params.AliasesCount = 2;
+    unsigned int Result = MessageDialog(Message, qtConfirmation, qaYes | qaNo | qaCancel, HELP_MOVE_CONFIGURATION, &Params);
+    if (Result == qaYes)
+    {
+      // noop
+    }
+    else if (Result == qaNo)
+    {
+      Configuration->ScheduleCustomIniFileStorageUse(GetCustomIniFileStorageName());
+      ExecuteNewInstance(L"");
+      TerminateApplication();
+    }
+    else
+    {
+      Abort();
+    }
+  }
+  FCustomIniFileStorageName = CustomIniFileStorageName;
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::CustomIniFileStorageEditExit(TObject * /*Sender*/)
+{
+  CustomIniFileStorageChanged();
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::CustomIniFileStorageEditAfterDialog(TObject * Sender, UnicodeString & Name, bool & Action)
+{
+  PathEditAfterDialog(Sender, Name, Action);
+  if (Action)
+  {
+    CustomIniFileStorageEdit->Text = Name;
+    CustomIniFileStorageChanged();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::CustomIniFileStorageButtonClick(TObject * /*Sender*/)
+{
+  UpdateControls();
+  // Focus to force validation
+  CustomIniFileStorageEdit->SetFocus();
+}
+//---------------------------------------------------------------------------

+ 122 - 79
source/forms/Preferences.dfm

@@ -6,7 +6,7 @@ object PreferencesDialog: TPreferencesDialog
   BorderIcons = [biSystemMenu, biMinimize, biMaximize, biHelp]
   BorderStyle = bsDialog
   Caption = 'Preferences'
-  ClientHeight = 489
+  ClientHeight = 495
   ClientWidth = 545
   Color = clBtnFace
   ParentFont = True
@@ -16,12 +16,12 @@ object PreferencesDialog: TPreferencesDialog
   OnShow = FormShow
   DesignSize = (
     545
-    489)
+    495)
   PixelsPerInch = 96
   TextHeight = 13
   object OKButton: TButton
     Left = 282
-    Top = 408
+    Top = 414
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -32,7 +32,7 @@ object PreferencesDialog: TPreferencesDialog
   end
   object CloseButton: TButton
     Left = 370
-    Top = 408
+    Top = 414
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -45,7 +45,7 @@ object PreferencesDialog: TPreferencesDialog
     Left = 0
     Top = 0
     Width = 545
-    Height = 402
+    Height = 408
     Align = alTop
     Anchors = [akLeft, akTop, akRight, akBottom]
     BevelOuter = bvNone
@@ -54,7 +54,7 @@ object PreferencesDialog: TPreferencesDialog
       Left = 132
       Top = 0
       Width = 413
-      Height = 402
+      Height = 408
       ActivePage = PreferencesSheet
       Align = alClient
       MultiLine = True
@@ -71,7 +71,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object CommonPreferencesGroup: TGroupBox
           Left = 8
           Top = 8
@@ -259,7 +259,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object LoggingGroup: TGroupBox
           Left = 8
           Top = 8
@@ -490,7 +490,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object InterfaceChangeLabel: TLabel
           Left = 8
           Top = 216
@@ -502,13 +502,13 @@ object PreferencesDialog: TPreferencesDialog
           Left = 8
           Top = 8
           Width = 389
-          Height = 202
+          Height = 208
           Anchors = [akLeft, akTop, akRight, akBottom]
           Caption = 'User Interface'
           TabOrder = 0
           DesignSize = (
             389
-            202)
+            208)
           object CommanderDescriptionLabel2: TLabel
             Left = 132
             Top = 20
@@ -584,7 +584,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object PanelsCommonGroup: TGroupBox
           Left = 8
           Top = 8
@@ -718,7 +718,7 @@ object PreferencesDialog: TPreferencesDialog
         end
         object PanelFontGroup: TGroupBox
           Left = 8
-          Top = 264
+          Top = 270
           Width = 389
           Height = 82
           Anchors = [akLeft, akRight, akBottom]
@@ -769,7 +769,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object Label3: TLabel
           Left = 8
           Top = 8
@@ -919,7 +919,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object Label4: TLabel
           Left = 8
           Top = 8
@@ -962,23 +962,23 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object EditorPreferenceGroup: TGroupBox
           Left = 8
           Top = 8
           Width = 389
-          Height = 374
+          Height = 380
           Anchors = [akLeft, akTop, akRight, akBottom]
           Caption = 'Editor preference'
           TabOrder = 0
           DesignSize = (
             389
-            374)
+            380)
           object EditorListView3: TListView
             Left = 16
             Top = 24
             Width = 356
-            Height = 277
+            Height = 283
             Anchors = [akLeft, akTop, akRight, akBottom]
             Columns = <
               item
@@ -1014,7 +1014,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object AddEditorButton: TButton
             Left = 16
-            Top = 307
+            Top = 313
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1024,7 +1024,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object EditEditorButton: TButton
             Left = 112
-            Top = 307
+            Top = 313
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1034,7 +1034,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object UpEditorButton: TButton
             Left = 290
-            Top = 307
+            Top = 313
             Width = 83
             Height = 25
             Anchors = [akRight, akBottom]
@@ -1044,7 +1044,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object DownEditorButton: TButton
             Left = 290
-            Top = 338
+            Top = 344
             Width = 83
             Height = 25
             Anchors = [akRight, akBottom]
@@ -1054,7 +1054,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object RemoveEditorButton: TButton
             Left = 16
-            Top = 338
+            Top = 344
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1073,7 +1073,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object ShellIconsGroup: TGroupBox
           Left = 8
           Top = 8
@@ -1161,23 +1161,23 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object CustomCommandsGroup: TGroupBox
           Left = 8
           Top = 8
           Width = 389
-          Height = 374
+          Height = 380
           Anchors = [akLeft, akTop, akRight, akBottom]
           Caption = 'Custom commands'
           TabOrder = 0
           DesignSize = (
             389
-            374)
+            380)
           object CustomCommandsView: TListView
             Left = 16
             Top = 24
             Width = 356
-            Height = 277
+            Height = 283
             Anchors = [akLeft, akTop, akRight, akBottom]
             Columns = <
               item
@@ -1216,7 +1216,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object AddCommandButton: TButton
             Left = 16
-            Top = 307
+            Top = 313
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1228,7 +1228,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object RemoveCommandButton: TButton
             Left = 16
-            Top = 338
+            Top = 344
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1238,7 +1238,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object UpCommandButton: TButton
             Left = 290
-            Top = 307
+            Top = 313
             Width = 83
             Height = 25
             Anchors = [akRight, akBottom]
@@ -1248,7 +1248,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object DownCommandButton: TButton
             Left = 290
-            Top = 338
+            Top = 344
             Width = 83
             Height = 25
             Anchors = [akRight, akBottom]
@@ -1258,7 +1258,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object EditCommandButton: TButton
             Left = 112
-            Top = 307
+            Top = 313
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1268,7 +1268,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object ConfigureCommandButton: TButton
             Left = 112
-            Top = 307
+            Top = 313
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -1287,7 +1287,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object DragDropDownloadsGroup: TGroupBox
           Left = 8
           Top = 8
@@ -1399,7 +1399,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object QueueGroup: TGroupBox
           Left = 8
           Top = 8
@@ -1554,18 +1554,32 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object StorageGroup: TGroupBox
           Left = 8
           Top = 8
           Width = 389
-          Height = 72
+          Height = 96
           Anchors = [akLeft, akTop, akRight]
           Caption = 'Configuration storage'
           TabOrder = 0
           DesignSize = (
             389
-            72)
+            96)
+          object AutomaticIniFileStorageLabel: TPathLabel
+            Left = 167
+            Top = 46
+            Width = 209
+            Height = 13
+            ActiveTextColor = clWindowText
+            IndentHorizontal = 0
+            IndentVertical = 0
+            InactiveTextColor = clGrayText
+            OnGetStatus = AutomaticIniFileStorageLabelGetStatus
+            Align = alNone
+            Anchors = [akLeft, akTop, akRight]
+            AutoSize = False
+          end
           object RegistryStorageButton: TRadioButton
             Left = 16
             Top = 21
@@ -1576,20 +1590,49 @@ object PreferencesDialog: TPreferencesDialog
             TabOrder = 0
             OnClick = ControlChange
           end
-          object IniFileStorageButton2: TRadioButton
+          object AutomaticIniFileStorageButton: TRadioButton
             Left = 16
             Top = 45
-            Width = 360
+            Width = 148
             Height = 17
             Anchors = [akLeft, akTop, akRight]
-            Caption = '&INI file (winscp.ini)'
+            Caption = '&Automatic INI file'
             TabOrder = 1
             OnClick = ControlChange
           end
+          object CustomIniFileStorageButton: TRadioButton
+            Left = 16
+            Top = 68
+            Width = 148
+            Height = 17
+            Anchors = [akLeft, akTop, akRight]
+            Caption = 'Custo&m INI file:'
+            TabOrder = 2
+            OnClick = CustomIniFileStorageButtonClick
+          end
+          object CustomIniFileStorageEdit: TFilenameEdit
+            Left = 168
+            Top = 66
+            Width = 208
+            Height = 21
+            AcceptFiles = True
+            OnBeforeDialog = PathEditBeforeDialog
+            OnAfterDialog = CustomIniFileStorageEditAfterDialog
+            DialogKind = dkSave
+            DefaultExt = 'ini'
+            Filter = 'INI files (*.ini)|*.ini|All files (*.*)|*.*'
+            DialogOptions = [ofHideReadOnly, ofPathMustExist]
+            ClickKey = 16397
+            Anchors = [akLeft, akTop, akRight]
+            TabOrder = 3
+            Text = 'CustomIniFileStorageEdit'
+            OnChange = ControlChange
+            OnExit = CustomIniFileStorageEditExit
+          end
         end
         object TemporaryDirectoryGrouo: TGroupBox
           Left = 8
-          Top = 88
+          Top = 112
           Width = 389
           Height = 223
           Anchors = [akLeft, akTop, akRight]
@@ -1695,7 +1738,7 @@ object PreferencesDialog: TPreferencesDialog
         end
         object OtherStorageGroup: TGroupBox
           Left = 8
-          Top = 318
+          Top = 342
           Width = 389
           Height = 53
           Anchors = [akLeft, akTop, akRight]
@@ -1742,7 +1785,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object ResumeBox: TGroupBox
           Left = 8
           Top = 8
@@ -1960,7 +2003,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object UpdatesGroup2: TGroupBox
           Left = 8
           Top = 8
@@ -2173,21 +2216,21 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object CopyParamListGroup: TGroupBox
           Left = 8
           Top = 8
           Width = 389
-          Height = 374
+          Height = 380
           Anchors = [akLeft, akTop, akRight, akBottom]
           Caption = 'Transfer settings presets'
           TabOrder = 0
           DesignSize = (
             389
-            374)
+            380)
           object CopyParamLabel: TLabel
             Left = 18
-            Top = 222
+            Top = 228
             Width = 354
             Height = 53
             Anchors = [akLeft, akRight, akBottom]
@@ -2200,7 +2243,7 @@ object PreferencesDialog: TPreferencesDialog
             Left = 16
             Top = 24
             Width = 356
-            Height = 192
+            Height = 198
             Anchors = [akLeft, akTop, akRight, akBottom]
             Columns = <
               item
@@ -2233,7 +2276,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object AddCopyParamButton: TButton
             Left = 16
-            Top = 281
+            Top = 287
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -2243,7 +2286,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object RemoveCopyParamButton: TButton
             Left = 16
-            Top = 313
+            Top = 319
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -2253,7 +2296,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object UpCopyParamButton: TButton
             Left = 289
-            Top = 281
+            Top = 287
             Width = 83
             Height = 25
             Anchors = [akRight, akBottom]
@@ -2263,7 +2306,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object DownCopyParamButton: TButton
             Left = 289
-            Top = 313
+            Top = 319
             Width = 83
             Height = 25
             Anchors = [akRight, akBottom]
@@ -2273,7 +2316,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object EditCopyParamButton: TButton
             Left = 112
-            Top = 281
+            Top = 287
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -2283,7 +2326,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object DuplicateCopyParamButton: TButton
             Left = 112
-            Top = 313
+            Top = 319
             Width = 83
             Height = 25
             Anchors = [akLeft, akBottom]
@@ -2293,7 +2336,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object CopyParamAutoSelectNoticeCheck: TCheckBox
             Left = 18
-            Top = 344
+            Top = 350
             Width = 354
             Height = 17
             Anchors = [akLeft, akRight, akBottom]
@@ -2312,7 +2355,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object PathInCaptionGroup: TGroupBox
           Left = 8
           Top = 131
@@ -2464,7 +2507,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object MasterPasswordGroup: TGroupBox
           Left = 8
           Top = 8
@@ -2528,7 +2571,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object ExternalAppsGroup: TGroupBox
           Left = 8
           Top = 8
@@ -2636,7 +2679,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object ExternalIpAddressGroupBox: TGroupBox
           Left = 8
           Top = 8
@@ -2709,7 +2752,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object PanelsRemoteDirectoryGroup: TGroupBox
           Left = 8
           Top = 8
@@ -2782,7 +2825,7 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object LocalPanelGroup: TGroupBox
           Left = 8
           Top = 8
@@ -2833,21 +2876,21 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object LanguagesGroup: TGroupBox
           Left = 8
           Top = 8
           Width = 389
-          Height = 374
+          Height = 380
           Anchors = [akLeft, akTop, akRight, akBottom]
           Caption = 'Languages'
           TabOrder = 0
           DesignSize = (
             389
-            374)
+            380)
           object LanguageChangeLabel: TLabel
             Left = 16
-            Top = 343
+            Top = 349
             Width = 177
             Height = 13
             Anchors = [akLeft, akBottom]
@@ -2857,7 +2900,7 @@ object PreferencesDialog: TPreferencesDialog
             Left = 16
             Top = 24
             Width = 356
-            Height = 308
+            Height = 314
             Anchors = [akLeft, akTop, akRight, akBottom]
             Columns = <
               item
@@ -2876,7 +2919,7 @@ object PreferencesDialog: TPreferencesDialog
           end
           object LanguagesGetMoreButton: TButton
             Left = 273
-            Top = 338
+            Top = 344
             Width = 100
             Height = 25
             Anchors = [akRight, akBottom]
@@ -2894,10 +2937,10 @@ object PreferencesDialog: TPreferencesDialog
         TabVisible = False
         DesignSize = (
           405
-          392)
+          398)
         object InternalEditorGroup: TGroupBox
           Left = 8
-          Top = 8
+          Top = 14
           Width = 389
           Height = 146
           Anchors = [akLeft, akRight, akBottom]
@@ -2960,7 +3003,7 @@ object PreferencesDialog: TPreferencesDialog
         end
         object FontGroup: TGroupBox
           Left = 8
-          Top = 160
+          Top = 166
           Width = 389
           Height = 118
           Anchors = [akLeft, akRight, akBottom]
@@ -3019,18 +3062,18 @@ object PreferencesDialog: TPreferencesDialog
       Left = 0
       Top = 0
       Width = 132
-      Height = 402
+      Height = 408
       Align = alLeft
       BevelOuter = bvNone
       TabOrder = 0
       DesignSize = (
         132
-        402)
+        408)
       object NavigationTree: TTreeView
         Left = 8
         Top = 9
         Width = 116
-        Height = 392
+        Height = 398
         Anchors = [akLeft, akTop, akRight, akBottom]
         DoubleBuffered = True
         HideSelection = False
@@ -3088,7 +3131,7 @@ object PreferencesDialog: TPreferencesDialog
   end
   object HelpButton: TButton
     Left = 458
-    Top = 408
+    Top = 414
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -3098,7 +3141,7 @@ object PreferencesDialog: TPreferencesDialog
   end
   object ComponentsPanel: TPanel
     Left = 0
-    Top = 439
+    Top = 445
     Width = 545
     Height = 50
     Align = alBottom

+ 12 - 1
source/forms/Preferences.h

@@ -20,6 +20,7 @@
 #include <Vcl.Imaging.pngimage.hpp>
 #include <Vcl.Menus.hpp>
 #include <WinConfiguration.h>
+#include "PathLabel.hpp"
 //----------------------------------------------------------------------------
 class TCustomCommandList;
 class TEditorList;
@@ -105,7 +106,7 @@ __published:
   TTabSheet *StorageSheet;
   TGroupBox *StorageGroup;
   TRadioButton *RegistryStorageButton;
-  TRadioButton *IniFileStorageButton2;
+  TRadioButton *AutomaticIniFileStorageButton;
   TGroupBox *NotificationsGroup;
   TCheckBox *BeepOnFinishCheck;
   TUpDownEdit *BeepOnFinishAfterEdit;
@@ -315,6 +316,9 @@ __published:
   TCheckBox *LogMaxSizeCountCheck;
   TUpDownEdit *LogMaxSizeCountEdit;
   TLabel *LogMaxSizeCountFilesLabel;
+  TRadioButton *CustomIniFileStorageButton;
+  TFilenameEdit *CustomIniFileStorageEdit;
+  TPathLabel *AutomaticIniFileStorageLabel;
   void __fastcall FormShow(TObject *Sender);
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall EditorFontButtonClick(TObject *Sender);
@@ -406,6 +410,10 @@ __published:
   void __fastcall LanguagesViewCustomDrawItem(TCustomListView * Sender, TListItem * Item, TCustomDrawState State, bool & DefaultDraw);
   void __fastcall LogMaxSizeComboExit(TObject *Sender);
   void __fastcall PuttyPathEditExit(TObject *Sender);
+  void __fastcall AutomaticIniFileStorageLabelGetStatus(TCustomPathLabel *Sender, bool &Active);
+  void __fastcall CustomIniFileStorageEditExit(TObject *Sender);
+  void __fastcall CustomIniFileStorageEditAfterDialog(TObject *Sender, UnicodeString &Name, bool &Action);
+  void __fastcall CustomIniFileStorageButtonClick(TObject *Sender);
 
 private:
   TPreferencesMode FPreferencesMode;
@@ -435,6 +443,7 @@ private:
   int FCustomCommandsHintItem;
   std::unique_ptr<TStrings> FAddedExtensions;
   std::unique_ptr<TStringList> FCustomCommandOptions;
+  UnicodeString FCustomIniFileStorageName;
   void __fastcall CMDialogKey(TWMKeyDown & Message);
   void __fastcall WMHelp(TWMHelp & Message);
   UnicodeString __fastcall TabSample(UnicodeString Values);
@@ -479,6 +488,8 @@ protected:
   void __fastcall AddEditCommand(bool Edit);
   void __fastcall AddExtension();
   void __fastcall ConfigureCommand();
+  void __fastcall CustomIniFileStorageChanged();
+  UnicodeString __fastcall GetCustomIniFileStorageName();
 };
 //----------------------------------------------------------------------------
 #endif

+ 1 - 0
source/resource/HelpWin.h

@@ -50,6 +50,7 @@
 #define HELP_EDITOR_AUTO_CONFIG      "ui_pref_editor#auto"
 #define HELP_IMPORT_SESSIONS         "ui_import"
 #define HELP_IMPORT_CONFIGURATION    "config"
+#define HELP_MOVE_CONFIGURATION      HELP_IMPORT_CONFIGURATION
 #define HELP_DELETE_FILE             "task_delete"
 #define HELP_COLOR                   "task_connections#session_color"
 #define HELP_DRAGEXT_TARGET_NOT_INSTALLED "dragext"

+ 1 - 0
source/resource/TextsWin.h

@@ -156,6 +156,7 @@
 #define CLOSE_BUTTON            1366
 #define EXTENSION_UNTRUSTED     1367
 #define EXTENSION_NOT_LATEST    1368
+#define CUSTOM_INI_FILE_OVERWRITE 1369
 
 #define WIN_INFORMATION_STRINGS 1400
 #define COMPARE_NO_DIFFERENCES  1402

+ 1 - 0
source/resource/TextsWin1.rc

@@ -158,6 +158,7 @@ BEGIN
         CLOSE_BUTTON, "Close"
         EXTENSION_UNTRUSTED, "The extension does not come from a trusted source. Are you sure you want to install it?"
         EXTENSION_NOT_LATEST, "**Using the last compatible and trusted version of the extension.**\n\nThe latest version of the extension is either not reviewed yet or is not compatible with this version of WinSCP."
+        CUSTOM_INI_FILE_OVERWRITE, "**Do you want to overwrite an existing INI file '%s'?**\n\nChoose 'Overwrite' to overwrite the selected INI file with the current configuration.\n\nChoose 'Use' to restart WinSCP with a configuration loaded from the selected INI file. Your current configuration will be preserved and you can revert to it, if needed.|&Overwrite|&Use"
 
         WIN_INFORMATION_STRINGS, "WIN_INFORMATION"
         COMPARE_NO_DIFFERENCES, "No differences found."

+ 0 - 75
source/windows/GUITools.cpp

@@ -292,81 +292,6 @@ bool __fastcall SpecialFolderLocation(int PathID, UnicodeString & Path)
   return false;
 }
 //---------------------------------------------------------------------------
-static UnicodeString __fastcall GetWineHomeFolder()
-{
-  UnicodeString Result;
-
-  UnicodeString WineHostHome = GetEnvironmentVariable(L"WINE_HOST_HOME");
-  if (!WineHostHome.IsEmpty())
-  {
-    Result = L"Z:" + FromUnixPath(WineHostHome);
-  }
-  else
-  {
-    // Should we use WinAPI GetUserName() instead?
-    UnicodeString UserName = GetEnvironmentVariable(L"USERNAME");
-    if (!UserName.IsEmpty())
-    {
-      Result = L"Z:\\home\\" + UserName;
-    }
-  }
-
-  if (!DirectoryExists(Result))
-  {
-    Result = L"";
-  }
-
-  return Result;
-}
-//---------------------------------------------------------------------------
-UnicodeString __fastcall GetPersonalFolder()
-{
-  UnicodeString Result;
-  ::SpecialFolderLocation(CSIDL_PERSONAL, Result);
-
-  if (IsWine())
-  {
-    UnicodeString WineHome = GetWineHomeFolder();
-
-    if (!WineHome.IsEmpty())
-    {
-      // if at least home exists, use it
-      Result = WineHome;
-
-      // but try to go deeper to "Documents"
-      UnicodeString WineDocuments =
-        IncludeTrailingBackslash(WineHome) + L"Documents";
-      if (DirectoryExists(WineDocuments))
-      {
-        Result = WineDocuments;
-      }
-    }
-  }
-  return Result;
-}
-//---------------------------------------------------------------------------
-UnicodeString __fastcall GetDesktopFolder()
-{
-  UnicodeString Result;
-  ::SpecialFolderLocation(CSIDL_DESKTOPDIRECTORY, Result);
-
-  if (IsWine())
-  {
-    UnicodeString WineHome = GetWineHomeFolder();
-
-    if (!WineHome.IsEmpty())
-    {
-      UnicodeString WineDesktop =
-        IncludeTrailingBackslash(WineHome) + L"Desktop";
-      if (DirectoryExists(WineHome))
-      {
-        Result = WineDesktop;
-      }
-    }
-  }
-  return Result;
-}
-//---------------------------------------------------------------------------
 UnicodeString __fastcall UniqTempDir(const UnicodeString BaseDir, const UnicodeString Identity,
   bool Mask)
 {

+ 0 - 11
source/windows/GUITools.h

@@ -2,15 +2,6 @@
 #ifndef GUIToolsH
 #define GUIToolsH
 //---------------------------------------------------------------------------
-// from shlobj.h
-#define CSIDL_DESKTOP                   0x0000        // <desktop>
-#define CSIDL_SENDTO                    0x0009        // <user name>\SendTo
-#define CSIDL_DESKTOPDIRECTORY          0x0010        // <user name>\Desktop
-#define CSIDL_COMMON_DESKTOPDIRECTORY   0x0019        // All Users\Desktop
-#define CSIDL_APPDATA                   0x001a        // <user name>\Application Data
-#define CSIDL_PROGRAM_FILES             0x0026        // C:\Program Files
-#define CSIDL_PERSONAL                  0x0005        // My Documents
-//---------------------------------------------------------------------------
 #include <FileMasks.H>
 #include <Tbx.hpp>
 //---------------------------------------------------------------------------
@@ -30,8 +21,6 @@ bool __fastcall CopyCommandToClipboard(const UnicodeString & Command);
 void __fastcall OpenSessionInPutty(const UnicodeString PuttyPath,
   TSessionData * SessionData);
 bool __fastcall SpecialFolderLocation(int PathID, UnicodeString & Path);
-UnicodeString __fastcall GetPersonalFolder();
-UnicodeString __fastcall GetDesktopFolder();
 UnicodeString __fastcall UniqTempDir(const UnicodeString BaseDir,
   const UnicodeString Identity, bool Mask = false);
 bool __fastcall DeleteDirectory(const UnicodeString DirName);