瀏覽代碼

Extensions are configurable

Source commit: 5bad7facd077eb698fab75e7ca9eedd77f0e9475
Martin Prikryl 9 年之前
父節點
當前提交
9923907f07

+ 116 - 23
source/forms/Custom.cpp

@@ -30,7 +30,9 @@ __fastcall TCustomDialog::TCustomDialog(UnicodeString AHelpKeyword)
 {
   UseSystemSettings(this);
 
+  FControlPadding = ScaleByTextHeight(this, 8);
   FPos = ScaleByTextHeight(this, 8);
+  FPrePos = FPos;
   FHorizontalMargin = ScaleByTextHeight(this, 8);
   FIndent = FHorizontalMargin;
 
@@ -75,11 +77,16 @@ void __fastcall TCustomDialog::Change(TObject * /*Sender*/)
   Changed();
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomDialog::HelpButtonClick(TObject * /*Sender*/)
+void __fastcall TCustomDialog::DoHelp()
 {
   FormHelp(this);
 }
 //---------------------------------------------------------------------------
+void __fastcall TCustomDialog::HelpButtonClick(TObject * /*Sender*/)
+{
+  DoHelp();
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomDialog::DoShow()
 {
   OKButton->TabOrder = FCount;
@@ -113,6 +120,18 @@ void __fastcall TCustomDialog::AddImage(const UnicodeString & ImageName)
   FIndent += Image->Width + ScaleByTextHeight(this, 12);
 }
 //---------------------------------------------------------------------------
+int __fastcall TCustomDialog::GetMaxControlWidth(TControl * Control)
+{
+  return ClientWidth - Control->Left - FHorizontalMargin;
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomDialog::AdjustHeight(TControl * Control)
+{
+  FPos = Control->Top + Control->Height + FControlPadding;
+  ClientHeight = ClientHeight + (FPos - FPrePos);
+  FPrePos = FPos;
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomDialog::AddWinControl(TWinControl * Control)
 {
   Control->TabOrder = FCount;
@@ -134,21 +153,41 @@ TLabel * __fastcall TCustomDialog::CreateLabel(UnicodeString Label)
   return Result;
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomDialog::AddEditLikeControl(TWinControl * Edit, TLabel * Label)
+void __fastcall TCustomDialog::AddEditLikeControl(TWinControl * Edit, TLabel * Label, bool OneLine)
 {
   int PrePos = FPos;
-  Label->Parent = this;
-  Label->Left = FIndent;
-  Label->Top = FPos;
-  FPos += Label->Height + ScaleByTextHeight(this, 4);
 
   Edit->Parent = this;
-  Edit->Left = FIndent;
-  Edit->Top = FPos;
-  Edit->Width = ClientWidth - Edit->Left - FHorizontalMargin;
   // this updates Height property to real value
   Edit->HandleNeeded();
-  FPos += Edit->Height + ScaleByTextHeight(this, 8);
+
+  Label->Parent = this;
+  Label->Left = FIndent;
+
+  if (OneLine)
+  {
+    DebugAssert(Edit->Height > Label->Height);
+    Label->Top = FPos + ((Edit->Height - Label->Height) / 2);
+  }
+  else
+  {
+    Label->Top = FPos;
+
+    FPos += Label->Height + ScaleByTextHeight(this, 4);
+  }
+
+  Edit->Top = FPos;
+  if (OneLine)
+  {
+    Edit->Left = ClientWidth - FHorizontalMargin - Edit->Width;
+  }
+  else
+  {
+    Edit->Left = FIndent;
+    Edit->Width = GetMaxControlWidth(Edit);
+  }
+
+  AdjustHeight(Edit);
 
   if (Label->FocusControl == NULL)
   {
@@ -159,14 +198,12 @@ void __fastcall TCustomDialog::AddEditLikeControl(TWinControl * Edit, TLabel * L
     DebugAssert(Label->FocusControl == Edit);
   }
 
-  ClientHeight = ClientHeight + (FPos - PrePos);
-
   AddWinControl(Edit);
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomDialog::AddEdit(TCustomEdit * Edit, TLabel * Label)
 {
-  AddEditLikeControl(Edit, Label);
+  AddEditLikeControl(Edit, Label, false);
 
   TEdit * PublicEdit = reinterpret_cast<TEdit *>(Edit);
   if (PublicEdit->OnChange == NULL)
@@ -175,9 +212,29 @@ void __fastcall TCustomDialog::AddEdit(TCustomEdit * Edit, TLabel * Label)
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomDialog::AddComboBox(TCustomCombo * Combo, TLabel * Label)
+void __fastcall TCustomDialog::AddComboBox(TCustomCombo * Combo, TLabel * Label, TStrings * Items, bool OneLine)
 {
-  AddEditLikeControl(Combo, Label);
+  AddEditLikeControl(Combo, Label, OneLine);
+
+  if (Items != NULL)
+  {
+    Combo->Items = Items;
+  }
+
+  if (OneLine)
+  {
+    int Width = 0;
+    for (int Index = 0; Index < Combo->Items->Count; Index++)
+    {
+      Width = Max(Width, Combo->Canvas->TextWidth(Combo->Items->Strings[Index]));
+    }
+
+    Width += ScaleByTextHeight(Combo, 4 + 16 + 14);
+    Width = Max(Width, HelpButton->Width);
+
+    Combo->Width = Width;
+    Combo->Left = ClientWidth - FHorizontalMargin - Width;
+  }
 
   TComboBox * PublicCombo = reinterpret_cast<TComboBox *>(Combo);
   if (PublicCombo->OnChange == NULL)
@@ -186,20 +243,23 @@ void __fastcall TCustomDialog::AddComboBox(TCustomCombo * Combo, TLabel * Label)
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomDialog::AddButtonControl(TButtonControl * Control)
+void __fastcall TCustomDialog::ScaleButtonControl(TButtonControl * Control)
 {
-  int PrePos = FPos;
-  Control->Parent = this;
-  Control->Left = FIndent + ScaleByTextHeight(this, 6);
-  Control->Top = FPos;
-  Control->Width = ClientWidth - Control->Left - FHorizontalMargin;
   // this updates Height property to real value
   Control->HandleNeeded();
   // buttons do not scale with text on their own
   Control->Height = ScaleByTextHeight(Control, Control->Height);
-  FPos += Control->Height + ScaleByTextHeight(this, 8);
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomDialog::AddButtonControl(TButtonControl * Control)
+{
+  Control->Parent = this;
+  Control->Left = FIndent + ScaleByTextHeight(this, 6);
+  Control->Top = FPos;
+  Control->Width = GetMaxControlWidth(Control);
+  ScaleButtonControl(Control);
 
-  ClientHeight = ClientHeight + (FPos - PrePos);
+  AdjustHeight(Control);
 
   AddWinControl(Control);
 
@@ -210,6 +270,39 @@ void __fastcall TCustomDialog::AddButtonControl(TButtonControl * Control)
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TCustomDialog::AddText(TLabel * Label)
+{
+  Label->Parent = this;
+
+  Label->WordWrap = true;
+  Label->Left = FIndent;
+  Label->Width = GetMaxControlWidth(Label);
+  Label->Top = FPos;
+  Label->ShowAccelChar = false;
+
+  TRect TextRect;
+  SetRect(&TextRect, 0, 0, Label->Width, 0);
+  DrawText(Label->Canvas->Handle, Label->Caption.c_str(), Label->Caption.Length() + 1, &TextRect,
+    DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK | DT_NOPREFIX |
+    Label->DrawTextBiDiModeFlagsReadingOnly());
+  Label->Height = TextRect.Height();
+
+  AdjustHeight(Label);
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomDialog::AddText(TStaticText * Label)
+{
+  Label->Parent = this;
+
+  Label->Left = FIndent;
+  Label->Width = GetMaxControlWidth(Label);
+  Label->Top = FPos;
+  Label->ShowAccelChar = false;
+
+  AdjustHeight(Label);
+  AddWinControl(Label);
+}
+//---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class TSaveSessionDialog : public TCustomDialog
 {

+ 12 - 3
source/forms/Custom.h

@@ -18,13 +18,16 @@ __published:
 
 private:
   int FPos;
+  int FPrePos;
   int FIndent;
   int FHorizontalMargin;
+  int FControlPadding;
   short FCount;
 
   void __fastcall Change(TObject * Sender);
-  void __fastcall AddWinControl(TWinControl * Control);
   void __fastcall Changed();
+  int __fastcall GetMaxControlWidth(TControl * Control);
+  void __fastcall AdjustHeight(TControl * Control);
 
 protected:
   DYNAMIC void __fastcall DoShow();
@@ -32,17 +35,23 @@ protected:
 
   virtual void __fastcall DoChange(bool & CanSubmit);
   virtual void __fastcall DoValidate();
+  virtual void __fastcall DoHelp();
 
 public:
   __fastcall TCustomDialog(UnicodeString HelpKeyword);
 
   TLabel * __fastcall CreateLabel(UnicodeString Label);
   TCheckBox * __fastcall CreateAndAddCheckBox(const UnicodeString & Caption);
-  void __fastcall AddEditLikeControl(TWinControl * Edit, TLabel * Label);
+  void __fastcall AddEditLikeControl(TWinControl * Edit, TLabel * Label, bool OneLine = false);
   void __fastcall AddEdit(TCustomEdit * Edit, TLabel * Label);
-  void __fastcall AddComboBox(TCustomCombo * Combo, TLabel * Label);
+  void __fastcall AddComboBox(TCustomCombo * Combo, TLabel * Label, TStrings * Items = NULL, bool OneLine = false);
   void __fastcall AddButtonControl(TButtonControl * Control);
   void __fastcall AddImage(const UnicodeString & ImageName);
+  void __fastcall AddWinControl(TWinControl * Control);
+  void __fastcall AddText(TLabel * Label);
+  void __fastcall AddText(TStaticText * Label);
+
+  void __fastcall ScaleButtonControl(TButtonControl * Control);
 
   bool __fastcall Execute();
 };

+ 5 - 2
source/forms/CustomScpExplorer.cpp

@@ -1638,6 +1638,9 @@ int __fastcall TCustomScpExplorerForm::CustomCommandState(
 void __fastcall TCustomScpExplorerForm::CustomCommand(TStrings * FileList,
   const TCustomCommandType & ACommand, TStrings * ALocalFileList)
 {
+
+  UnicodeString CommandCommand = ACommand.GetCommandWithExpandedOptions(WinConfiguration->CustomCommandOptions);
+
   if (FLAGCLEAR(ACommand.Params, ccLocal))
   {
     if (EnsureCommandSessionFallback(fcShellAnyCommand))
@@ -1647,7 +1650,7 @@ void __fastcall TCustomScpExplorerForm::CustomCommand(TStrings * FileList,
       TWinInteractiveCustomCommand InteractiveCustomCommand(
         &RemoteCustomCommand, ACommand.Name);
 
-      UnicodeString Command = InteractiveCustomCommand.Complete(ACommand.Command, false);
+      UnicodeString Command = InteractiveCustomCommand.Complete(CommandCommand, false);
 
       Configuration->Usage->Inc(L"RemoteCustomCommandRuns2");
 
@@ -1699,7 +1702,7 @@ void __fastcall TCustomScpExplorerForm::CustomCommand(TStrings * FileList,
     TWinInteractiveCustomCommand InteractiveCustomCommand(
       &LocalCustomCommand, ACommand.Name);
 
-    UnicodeString Command = InteractiveCustomCommand.Complete(ACommand.Command, false);
+    UnicodeString Command = InteractiveCustomCommand.Complete(CommandCommand, false);
 
     bool FileListCommand = LocalCustomCommand.IsFileListCommand(Command);
     bool LocalFileCommand = LocalCustomCommand.HasLocalFileName(Command);

+ 400 - 5
source/forms/Preferences.cpp

@@ -7,6 +7,7 @@
 #include <math.h>
 
 #include "Preferences.h"
+#include "Custom.h"
 
 #include <CoreMain.h>
 #include <Terminal.h>
@@ -76,6 +77,7 @@ __fastcall TPreferencesDialog::TPreferencesDialog(
   FAutomaticUpdatesPossible = IsInstalled();
   FCustomCommandsHintItem = NULL;
   FAddedExtensions.reset(CreateSortedStringList());
+  FCustomCommandOptions.reset(new TStringList());
   UseSystemSettings(this);
 
   FCustomCommandsScrollOnDragOver = new TListViewScrollOnDragOver(CustomCommandsView, true);
@@ -370,6 +372,7 @@ void __fastcall TPreferencesDialog::LoadConfiguration()
     FCustomCommandList->Assign(WinConfiguration->CustomCommandList);
     FExtensionList->Assign(WinConfiguration->ExtensionList);
     UpdateCustomCommandsView();
+    FCustomCommandOptions->Assign(WinConfiguration->CustomCommandOptions);
 
     PuttyPathEdit->Text = GUIConfiguration->PuttyPath;
     PuttyPasswordCheck2->Checked = GUIConfiguration->PuttyPassword;
@@ -731,6 +734,7 @@ void __fastcall TPreferencesDialog::SaveConfiguration()
 
     WinConfiguration->CustomCommandList = FCustomCommandList;
     WinConfiguration->ExtensionList = FExtensionList;
+    WinConfiguration->CustomCommandOptions = FCustomCommandOptions.get();
 
     GUIConfiguration->PuttyPath = PuttyPathEdit->Text;
     GUIConfiguration->PuttyPassword = PuttyPasswordCheck2->Checked;
@@ -1126,7 +1130,11 @@ void __fastcall TPreferencesDialog::UpdateControls()
     int CommandIndex = GetCommandIndex(CustomCommandsView->ItemIndex);
     bool CommandSelected = (CustomCommandsView->Selected != NULL);
     bool CustomCommandSelected = CommandSelected && (CommandList == FCustomCommandList);
+    bool ExtensionSelected = CommandSelected && (CommandList == FExtensionList);
     EnableControl(EditCommandButton, CustomCommandSelected);
+    EditCommandButton->Visible = !ExtensionSelected;
+    EnableControl(ConfigureCommandButton, ExtensionSelected && (CommandList->Commands[CommandIndex]->OptionsCount > 0));
+    ConfigureCommandButton->Visible = ExtensionSelected;
     EnableControl(RemoveCommandButton, CommandSelected);
     EnableControl(UpCommandButton, CommandSelected && (CommandIndex > 0));
     EnableControl(DownCommandButton, CommandSelected && (CommandIndex < CommandList->Count - 1));
@@ -1410,6 +1418,9 @@ void __fastcall TPreferencesDialog::UpdateCustomCommandsView()
   CustomCommandsView->Items->Count = FCustomCommandList->Count + FExtensionList->Count;
   AutoSizeListColumnsWidth(CustomCommandsView, 1);
   CustomCommandsView->Invalidate();
+  // particularly after command is edited/configured, make sure the hint is updated,
+  // even if we manage to display a hint for the same command as before the change
+  FCustomCommandsHintItem = NULL;
 }
 //---------------------------------------------------------------------------
 void __fastcall TPreferencesDialog::CustomCommandsViewKeyDown(
@@ -1433,6 +1444,10 @@ void __fastcall TPreferencesDialog::CustomCommandsViewDblClick(
   {
     AddEditCommand(true);
   }
+  else if (ConfigureCommandButton->Enabled)
+  {
+    ConfigureCommand();
+  }
 }
 //---------------------------------------------------------------------------
 static int __fastcall AddCommandToList(TCustomCommandList * List, int Index, TCustomCommandType * Command)
@@ -2430,13 +2445,10 @@ void __fastcall TPreferencesDialog::AddExtension()
       if (IsUrl)
       {
         UnicodeString Url = Path;
+        Url = SecureUrl(Url);
         bool WinSCPURL = IsWinSCPUrl(Url);
         if (WinSCPURL)
         {
-          if (IsHttpUrl(Url))
-          {
-            Url = ChangeUrlProtocol(Url, HttpsProtocol);
-          }
           Url = CampaignUrl(ProgramUrl(Url));
         }
 
@@ -2630,7 +2642,7 @@ void __fastcall TPreferencesDialog::CustomCommandsViewMouseMove(TObject * /*Send
       {
         Hint += L"\n" + Command->Description;
       }
-      Hint += L"\n" + Command->Command;
+      Hint += L"\n" + Command->GetCommandWithExpandedOptions(FCustomCommandOptions.get());
       if (List == FExtensionList)
       {
         Hint += L"\n" + Command->FileName;
@@ -2651,3 +2663,386 @@ void __fastcall TPreferencesDialog::BackgroundConfirmationsLinkClick(TObject * /
   QueueNoConfirmationCheck->Perform(WM_CHANGEUISTATE, MAKEWPARAM(UIS_CLEAR, UISF_HIDEFOCUS), 0);
 }
 //---------------------------------------------------------------------------
+class TCustomCommandOptionsDialog : public TCustomDialog
+{
+public:
+  __fastcall TCustomCommandOptionsDialog(const TCustomCommandType * Command, TStrings * CustomCommandOptions);
+
+  bool __fastcall Execute();
+
+protected:
+  virtual void __fastcall DoHelp();
+
+private:
+  const TCustomCommandType * FCommand;
+  TStrings * FCustomCommandOptions;
+  std::vector<TControl *> FControls;
+  std::vector<std::vector<UnicodeString> > FValues;
+
+  UnicodeString __fastcall HistoryKey(const TCustomCommandType::TOption & Option);
+  THistoryComboBox * __fastcall CreateHistoryComboBox(const TCustomCommandType::TOption & Option, const UnicodeString & Value);
+  void __fastcall BrowseButtonClick(TObject * Sender);
+  void __fastcall LinkLabelClick(TObject * Sender);
+  UnicodeString __fastcall SaveHistoryComboBoxValue(TControl * Control, const TCustomCommandType::TOption & Option);
+  void __fastcall AddOptionComboBox(
+    TComboBox * ComboBox, const UnicodeString & Value, const TCustomCommandType::TOption & Option,
+    std::vector<UnicodeString> & Values);
+  UnicodeString __fastcall GetComboBoxValue(TControl * Control, const UnicodeString & Default);
+};
+//---------------------------------------------------------------------------
+__fastcall TCustomCommandOptionsDialog::TCustomCommandOptionsDialog(
+    const TCustomCommandType * Command, TStrings * CustomCommandOptions) :
+  TCustomDialog(HELP_EXTENSION_OPTIONS)
+{
+  FCommand = Command;
+  FCustomCommandOptions = CustomCommandOptions;
+  Caption = FMTLOAD(EXTENSION_OPTIONS_CAPTION, (StripHotkey(FCommand->Name)));
+  Width = ScaleByTextHeight(this, 400);
+
+  for (int Index = 0; Index < FCommand->OptionsCount; Index++)
+  {
+    const TCustomCommandType::TOption & Option = FCommand->GetOption(Index);
+
+    UnicodeString OptionKey = FCommand->GetOptionKey(Option);
+    UnicodeString Value;
+    if (FCustomCommandOptions->IndexOfName(OptionKey) >= 0)
+    {
+      Value = FCustomCommandOptions->Values[OptionKey];
+    }
+    else
+    {
+      Value = Option.Default;
+    }
+
+    TControl * Control = NULL;
+    std::vector<UnicodeString> Values;
+    if (Option.Kind == TCustomCommandType::okUnknown)
+    {
+      Control = NULL;
+    }
+    else if (Option.Kind == TCustomCommandType::okLabel)
+    {
+      TLabel * Label = CreateLabel(Option.Caption);
+      AddText(Label);
+      Control = Label;
+    }
+    else if (Option.Kind == TCustomCommandType::okLink)
+    {
+      TStaticText * Label = new TStaticText(this);
+      Label->Caption = Option.Caption;
+      if (IsHttpOrHttpsUrl(Label->Caption))
+      {
+        Label->Caption = SecureUrl(Label->Caption);
+        LinkLabel(Label);
+        Label->TabStop = true;
+      }
+      else if (!Option.Default.IsEmpty() && IsHttpOrHttpsUrl(Option.Default))
+      {
+        Label->OnClick = LinkLabelClick;
+        LinkLabel(Label);
+        Label->TabStop = true;
+      }
+      else
+      {
+        // keep it plain text, as we have no URL
+      }
+      AddText(Label);
+      Control = Label;
+    }
+    else if (Option.Kind == TCustomCommandType::okTextBox)
+    {
+      Control = CreateHistoryComboBox(Option, Value);
+    }
+    else if (Option.Kind == TCustomCommandType::okFile)
+    {
+      THistoryComboBox * ComboBox = CreateHistoryComboBox(Option, Value);
+      TButton * Button = new TButton(this);
+      Button->Parent = this;
+      Button->Width = HelpButton->Width;
+      Button->Left = HelpButton->Left;
+      ComboBox->Width = HelpButton->Left - ComboBox->Left - ScaleByTextHeight(this, 6);
+      Button->Top = ComboBox->Top - ScaleByTextHeight(this, 2);
+      Button->Tag = Index;
+      Button->Caption = LoadStr(EXTENSION_OPTIONS_BROWSE);
+      Button->OnClick = BrowseButtonClick;
+      ScaleButtonControl(Button);
+      AddWinControl(Button);
+      Control = ComboBox;
+    }
+    else if (Option.Kind == TCustomCommandType::okDropDownList)
+    {
+      TComboBox * ComboBox = new TComboBox(this);
+      ComboBox->Style = csDropDownList;
+
+      AddOptionComboBox(ComboBox, Value, Option, Values);
+
+      Control = ComboBox;
+    }
+    else if (Option.Kind == TCustomCommandType::okComboBox)
+    {
+      TComboBox * ComboBox = new TComboBox(this);
+      ComboBox->Style = csDropDown;
+
+      AddOptionComboBox(ComboBox, Value, Option, Values);
+      if (ComboBox->ItemIndex < 0)
+      {
+        ComboBox->Text = Value;
+      }
+
+      Control = ComboBox;
+    }
+    else if (Option.Kind == TCustomCommandType::okCheckBox)
+    {
+      TCheckBox * CheckBox = CreateAndAddCheckBox(Option.Caption);
+
+      CheckBox->Checked =
+        (Option.Params.size() >= 1) &&
+        (Value == Option.Params[0]);
+
+      Control = CheckBox;
+    }
+    else
+    {
+      DebugFail();
+    }
+
+    if (Control != NULL)
+    {
+      Control->Tag = Index;
+    }
+    FControls.push_back(Control);
+    FValues.push_back(Values);
+  }
+
+  DebugAssert(FCommand->OptionsCount == static_cast<int>(FControls.size()));
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomCommandOptionsDialog::AddOptionComboBox(
+  TComboBox * ComboBox, const UnicodeString & Value, const TCustomCommandType::TOption & Option, std::vector<UnicodeString> & Values)
+{
+  std::unique_ptr<TStringList> Items(new TStringList());
+  int ItemIndex = -1;
+
+  TCustomCommandType::TOption::TParams::const_iterator ParamI = Option.Params.begin();
+  while (ParamI != Option.Params.end())
+  {
+    UnicodeString Item = (*ParamI);
+    int P = Item.Pos(L"=");
+    UnicodeString ParamValue;
+    if (P > 0)
+    {
+      ParamValue = Item.SubString(1, P - 1);
+      Item.Delete(1, P);
+    }
+    else
+    {
+      ParamValue = Item;
+    }
+    Items->Add(Item);
+    if (Value == ParamValue)
+    {
+      ItemIndex = Items->Count - 1;
+    }
+    Values.push_back(ParamValue);
+    ParamI++;
+  }
+
+  AddComboBox(ComboBox, CreateLabel(Option.Caption), Items.get(), true);
+
+  ComboBox->ItemIndex = ItemIndex;
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomCommandOptionsDialog::LinkLabelClick(TObject * Sender)
+{
+  TStaticText * Label = DebugNotNull(dynamic_cast<TStaticText *>(Sender));
+  const TCustomCommandType::TOption & Option = FCommand->GetOption(Label->Tag);
+  OpenBrowser(SecureUrl(Option.Default));
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomCommandOptionsDialog::BrowseButtonClick(TObject * Sender)
+{
+  TButton * Button = DebugNotNull(dynamic_cast<TButton *>(Sender));
+  int Index = Button->Tag;
+  THistoryComboBox * ComboBox = dynamic_cast<THistoryComboBox *>(FControls[Index]);
+
+  std::unique_ptr<TOpenDialog> OpenDialog(new TOpenDialog(Application));
+  UnicodeString Caption = FCommand->GetOption(Index).Caption;
+  Caption = StripHotkey(Caption);
+  if (!Caption.IsEmpty() && (Caption[Caption.Length()] == L':'))
+  {
+    Caption.SetLength(Caption.Length() - 1);
+  }
+  OpenDialog->Title = FMTLOAD(EXTENSION_OPTIONS_BROWSE_TITLE, (Caption));
+  UnicodeString ExpandedValue = ExpandEnvironmentVariables(ComboBox->Text);
+  OpenDialog->FileName = ExpandedValue;
+  UnicodeString InitialDir = ExtractFilePath(ExpandedValue);
+  if (!InitialDir.IsEmpty())
+  {
+    OpenDialog->InitialDir = InitialDir;
+  }
+
+  if (OpenDialog->Execute())
+  {
+    if (OpenDialog->FileName != ExpandedValue)
+    {
+      ComboBox->Text = OpenDialog->FileName;
+    }
+  }
+}
+//---------------------------------------------------------------------------
+THistoryComboBox * __fastcall TCustomCommandOptionsDialog::CreateHistoryComboBox(
+  const TCustomCommandType::TOption & Option, const UnicodeString & Value)
+{
+  THistoryComboBox * ComboBox = new THistoryComboBox(this);
+  ComboBox->AutoComplete = false;
+  AddComboBox(ComboBox, CreateLabel(Option.Caption));
+  ComboBox->Items = CustomWinConfiguration->History[HistoryKey(Option)];
+  ComboBox->Text = Value;
+  return ComboBox;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TCustomCommandOptionsDialog::HistoryKey(const TCustomCommandType::TOption & Option)
+{
+  UnicodeString Result = FCommand->GetOptionKey(Option);
+  for (int Index = 1; Index < Result.Length(); Index++)
+  {
+    if (!IsLetter(Result[Index]) && !IsDigit(Result[Index]))
+    {
+      Result[Index] = L'_';
+    }
+  }
+  return L"CustomCommandOption_" + Result;
+}
+//---------------------------------------------------------------------------
+bool __fastcall TCustomCommandOptionsDialog::Execute()
+{
+  bool Result = TCustomDialog::Execute();
+
+  if (Result)
+  {
+    DebugAssert(FCommand->OptionsCount == static_cast<int>(FControls.size()));
+
+    for (int Index = 0; Index < FCommand->OptionsCount; Index++)
+    {
+      const TCustomCommandType::TOption & Option = FCommand->GetOption(Index);
+      if ((Option.Kind != TCustomCommandType::okUnknown) &&
+          Option.IsControl)
+      {
+        UnicodeString OptionKey = FCommand->GetOptionKey(Option);
+
+        TControl * Control = FControls[Index];
+
+        UnicodeString Value;
+        if (Option.Kind == TCustomCommandType::okTextBox)
+        {
+          Value = SaveHistoryComboBoxValue(Control, Option);
+        }
+        else if (Option.Kind == TCustomCommandType::okFile)
+        {
+          Value = SaveHistoryComboBoxValue(Control, Option);
+        }
+        else if (Option.Kind == TCustomCommandType::okDropDownList)
+        {
+          Value = GetComboBoxValue(Control, Option.Default);
+        }
+        else if (Option.Kind == TCustomCommandType::okComboBox)
+        {
+          TComboBox * ComboBox = DebugNotNull(dynamic_cast<TComboBox *>(Control));
+          Value = GetComboBoxValue(Control, ComboBox->Text);
+        }
+        else if (Option.Kind == TCustomCommandType::okCheckBox)
+        {
+          TCheckBox * CheckBox = DebugNotNull(dynamic_cast<TCheckBox *>(Control));
+          int Index = (CheckBox->Checked ? 0 : 1);
+          Value = (Index < static_cast<int>(Option.Params.size())) ? Option.Params[Index] : UnicodeString();
+        }
+        else
+        {
+          DebugFail();
+        }
+
+        // The default value setter deletes the "name" when the value is empty.
+        // It would cause us to fall back to the default value, but we want to remember the empty value.
+        if (Value.IsEmpty())
+        {
+          int Index = FCustomCommandOptions->IndexOfName(OptionKey);
+          if (Index < 0)
+          {
+            Index = FCustomCommandOptions->Add(L"");
+          }
+          UnicodeString Line = OptionKey + FCustomCommandOptions->NameValueSeparator;
+          FCustomCommandOptions->Strings[Index] = Line;
+        }
+        else
+        {
+          FCustomCommandOptions->Values[OptionKey] = Value;
+        }
+      }
+    }
+  }
+
+  return Result;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TCustomCommandOptionsDialog::GetComboBoxValue(
+  TControl * Control, const UnicodeString & Default)
+{
+  TComboBox * ComboBox = DebugNotNull(dynamic_cast<TComboBox *>(Control));
+  UnicodeString Result;
+  if (ComboBox->ItemIndex < 0)
+  {
+    Result = Default;
+  }
+  else
+  {
+    Result = FValues[Control->Tag][ComboBox->ItemIndex];
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TCustomCommandOptionsDialog::SaveHistoryComboBoxValue(
+  TControl * Control, const TCustomCommandType::TOption & Option)
+{
+  THistoryComboBox * ComboBox = DebugNotNull(dynamic_cast<THistoryComboBox *>(Control));
+  ComboBox->SaveToHistory();
+  CustomWinConfiguration->History[HistoryKey(Option)] = ComboBox->Items;
+  return ComboBox->Text;
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomCommandOptionsDialog::DoHelp()
+{
+  UnicodeString HelpPage;
+  if (!FCommand->OptionsPage.IsEmpty())
+  {
+    HelpPage = FCommand->OptionsPage;
+  }
+  else
+  {
+    HelpPage = FCommand->HomePage;
+  }
+
+  if (!HelpPage.IsEmpty())
+  {
+    OpenBrowser(HelpPage);
+  }
+  else
+  {
+    TCustomDialog::DoHelp();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::ConfigureCommandButtonClick(TObject * /*Sender*/)
+{
+  ConfigureCommand();
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::ConfigureCommand()
+{
+  int Index = CustomCommandsView->ItemIndex;
+  const TCustomCommandType * Command = GetCommandList(Index)->Commands[GetCommandIndex(Index)];
+
+  std::unique_ptr<TCustomCommandOptionsDialog> Dialog(new TCustomCommandOptionsDialog(Command, FCustomCommandOptions.get()));
+  Dialog->Execute();
+  UpdateCustomCommandsView();
+}
+//---------------------------------------------------------------------------

+ 13 - 3
source/forms/Preferences.dfm

@@ -1230,7 +1230,7 @@ object PreferencesDialog: TPreferencesDialog
             Height = 25
             Anchors = [akLeft, akBottom]
             Caption = '&Remove'
-            TabOrder = 3
+            TabOrder = 4
             OnClick = RemoveCommandButtonClick
           end
           object UpCommandButton: TButton
@@ -1240,7 +1240,7 @@ object PreferencesDialog: TPreferencesDialog
             Height = 25
             Anchors = [akRight, akBottom]
             Caption = '&Up'
-            TabOrder = 4
+            TabOrder = 5
             OnClick = UpDownCommandButtonClick
           end
           object DownCommandButton: TButton
@@ -1250,7 +1250,7 @@ object PreferencesDialog: TPreferencesDialog
             Height = 25
             Anchors = [akRight, akBottom]
             Caption = '&Down'
-            TabOrder = 5
+            TabOrder = 6
             OnClick = UpDownCommandButtonClick
           end
           object EditCommandButton: TButton
@@ -1263,6 +1263,16 @@ object PreferencesDialog: TPreferencesDialog
             TabOrder = 2
             OnClick = EditCommandButtonClick
           end
+          object ConfigureCommandButton: TButton
+            Left = 112
+            Top = 307
+            Width = 83
+            Height = 25
+            Anchors = [akLeft, akBottom]
+            Caption = '&Configure...'
+            TabOrder = 3
+            OnClick = ConfigureCommandButtonClick
+          end
         end
       end
       object DragDropSheet: TTabSheet

+ 4 - 0
source/forms/Preferences.h

@@ -313,6 +313,7 @@ __published:
   TMenuItem *AddCustomCommandMenuItem;
   TMenuItem *AddExtensionMenuItem;
   TStaticText *BackgroundConfirmationsLink;
+  TButton *ConfigureCommandButton;
   void __fastcall FormShow(TObject *Sender);
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall EditorFontButtonClick(TObject *Sender);
@@ -400,6 +401,7 @@ __published:
   void __fastcall AddCommandButtonDropDownClick(TObject *Sender);
   void __fastcall CustomCommandsViewMouseMove(TObject *Sender, TShiftState Shift, int X, int Y);
   void __fastcall BackgroundConfirmationsLinkClick(TObject *Sender);
+  void __fastcall ConfigureCommandButtonClick(TObject *Sender);
 
 private:
   TPreferencesMode FPreferencesMode;
@@ -428,6 +430,7 @@ private:
   TWndMethod FOrigCustomCommandsViewWindowProc;
   int FCustomCommandsHintItem;
   std::unique_ptr<TStrings> FAddedExtensions;
+  std::unique_ptr<TStringList> FCustomCommandOptions;
   void __fastcall CMDialogKey(TWMKeyDown & Message);
   void __fastcall WMHelp(TWMHelp & Message);
   UnicodeString __fastcall TabSample(UnicodeString Values);
@@ -469,6 +472,7 @@ protected:
   void __fastcall EditorBackgroundColorChange(TColor Color);
   void __fastcall AddEditCommand(bool Edit);
   void __fastcall AddExtension();
+  void __fastcall ConfigureCommand();
 };
 //----------------------------------------------------------------------------
 #endif

+ 1 - 0
source/resource/HelpWin.h

@@ -57,5 +57,6 @@
 #define HELP_KEY_TYPE_UNSUPPORTED    "ui_puttygen#other_formats"
 #define HELP_TIPS                    "ui_tips"
 #define HELP_DEBUGGING               "debugging"
+#define HELP_EXTENSION_OPTIONS       "ui_pref_commands"
 
 #endif // TextsWin

+ 3 - 0
source/resource/TextsWin.h

@@ -580,6 +580,9 @@
 #define ADD_EXTENSION_CAPTION   1973
 #define ADD_EXTENSION_PROMPT    1974
 #define EXTENSION_INSTALLED     1975
+#define EXTENSION_OPTIONS_CAPTION 1976
+#define EXTENSION_OPTIONS_BROWSE 1977
+#define EXTENSION_OPTIONS_BROWSE_TITLE 1978
 
 // 2xxx is reserved for TextsFileZilla.h
 

+ 3 - 0
source/resource/TextsWin1.rc

@@ -583,6 +583,9 @@ BEGIN
         ADD_EXTENSION_CAPTION, "Add Extension"
         ADD_EXTENSION_PROMPT, "Enter URL or path to the &extension:"
         EXTENSION_INSTALLED, "Extension was installed. Please restart WinSCP."
+        EXTENSION_OPTIONS_CAPTION, "Options for extension '%s'"
+        EXTENSION_OPTIONS_BROWSE, "Browse..."
+        EXTENSION_OPTIONS_BROWSE_TITLE, "Select '%s'"
 
         WIN_VARIABLE_STRINGS, "WIN_VARIABLE"
         WINSCP_COPYRIGHT, "Copyright © 2000-2016 Martin Prikryl"

+ 10 - 0
source/windows/Tools.cpp

@@ -540,6 +540,16 @@ bool __fastcall IsWinSCPUrl(const UnicodeString & Url)
     StartsText(HttpHomePageUrl, Url);
 }
 //---------------------------------------------------------------------------
+UnicodeString __fastcall SecureUrl(const UnicodeString & Url)
+{
+  UnicodeString Result = Url;
+  if (IsWinSCPUrl(Url) && IsHttpUrl(Url))
+  {
+    Result = ChangeUrlProtocol(Result, HttpsProtocol);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 void __fastcall OpenBrowser(UnicodeString URL)
 {
   if (IsWinSCPUrl(URL))

+ 1 - 0
source/windows/Tools.h

@@ -39,6 +39,7 @@ void __fastcall ValidateMaskEdit(TComboBox * Edit);
 void __fastcall ValidateMaskEdit(TEdit * Edit);
 void __fastcall ValidateMaskEdit(TMemo * Edit, bool Directory);
 bool __fastcall IsWinSCPUrl(const UnicodeString & Url);
+UnicodeString __fastcall SecureUrl(const UnicodeString & Url);
 void __fastcall OpenBrowser(UnicodeString URL);
 void __fastcall ShowHelp(const UnicodeString & HelpKeyword);
 bool __fastcall IsFormatInClipboard(unsigned int Format);

+ 213 - 3
source/windows/WinConfiguration.cpp

@@ -443,6 +443,8 @@ __fastcall TWinConfiguration::TWinConfiguration(): TCustomWinConfiguration()
   FMasterPasswordSession = 0;
   FMasterPasswordSessionAsked = false;
   FSystemIconFont.reset(new TFont());
+  FCustomCommandOptions.reset(new TStringList());
+  FCustomCommandOptionsModified = false;
   UpdateSystemIconFont();
   Default();
 
@@ -471,6 +473,7 @@ __fastcall TWinConfiguration::~TWinConfiguration()
 void __fastcall TWinConfiguration::Default()
 {
   FCustomCommandsDefaults = true;
+  FCustomCommandOptionsModified = false;
 
   TCustomWinConfiguration::Default();
 
@@ -1104,6 +1107,15 @@ void __fastcall TWinConfiguration::SaveData(THierarchicalStorage * Storage, bool
     FCustomCommandList->Save(Storage);
   }
 
+  if ((All || FCustomCommandOptionsModified) &&
+      Storage->OpenSubKey(L"CustomCommandOptions", true))
+  {
+    Storage->ClearValues();
+    Storage->WriteValues(FCustomCommandOptions.get(), true);
+    Storage->CloseSubKey();
+    FCustomCommandOptionsModified = false;
+  }
+
   if ((All || FEditorList->Modified) &&
       Storage->OpenSubKey(L"Interface\\Editor", true, true))
   try
@@ -1335,6 +1347,13 @@ void __fastcall TWinConfiguration::LoadData(THierarchicalStorage * Storage)
   }
   FCustomCommandList->Reset();
 
+  if (Storage->OpenSubKey(L"CustomCommandOptions", false))
+  {
+    Storage->ReadValues(FCustomCommandOptions.get(), true);
+    Storage->CloseSubKey();
+    FCustomCommandOptionsModified = false;
+  }
+
   if (Storage->OpenSubKey(L"Interface\\Editor", false, true))
   try
   {
@@ -2427,6 +2446,20 @@ void __fastcall TWinConfiguration::SetEditorList(const TEditorList * value)
   }
 }
 //---------------------------------------------------------------------------
+TStrings * __fastcall TWinConfiguration::GetCustomCommandOptions()
+{
+  return FCustomCommandOptions.get();
+}
+//---------------------------------------------------------------------------
+void __fastcall TWinConfiguration::SetCustomCommandOptions(TStrings * value)
+{
+  if (!FCustomCommandOptions->Equals(value))
+  {
+    FCustomCommandOptions->Assign(value);
+    FCustomCommandOptionsModified = true;
+  }
+}
+//---------------------------------------------------------------------------
 TStringList * __fastcall TWinConfiguration::LoadJumpList(
   THierarchicalStorage * Storage, UnicodeString Name)
 {
@@ -2634,7 +2667,10 @@ __fastcall TCustomCommandType::TCustomCommandType(const TCustomCommandType & Oth
   FShortCut(Other.FShortCut),
   FId(Other.FId),
   FFileName(Other.FFileName),
-  FDescription(Other.FDescription)
+  FDescription(Other.FDescription),
+  FHomePage(Other.FHomePage),
+  FOptionsPage(Other.FOptionsPage),
+  FOptions(Other.FOptions)
 {
 }
 //---------------------------------------------------------------------------
@@ -2647,6 +2683,9 @@ TCustomCommandType & TCustomCommandType::operator=(const TCustomCommandType & Ot
   FId = Other.FId;
   FFileName = Other.FFileName;
   FDescription = Other.FDescription;
+  FHomePage = Other.FHomePage;
+  FOptionsPage = Other.FOptionsPage;
+  FOptions = Other.FOptions;
   return *this;
 }
 //---------------------------------------------------------------------------
@@ -2659,7 +2698,10 @@ bool __fastcall TCustomCommandType::Equals(const TCustomCommandType * Other) con
     (FShortCut == Other->FShortCut) &&
     (FId == Other->FId) &&
     (FFileName == Other->FFileName) &&
-    (FDescription == Other->FDescription);
+    (FDescription == Other->FDescription) &&
+    (FHomePage == Other->FHomePage) &&
+    (FOptionsPage == Other->FOptionsPage) &&
+    (FOptions == Other->FOptions);
 }
 //---------------------------------------------------------------------------
 const UnicodeString ExtensionNameDirective(L"name");
@@ -2750,6 +2792,7 @@ void __fastcall TCustomCommandType::LoadExtension(TStrings * Lines)
 {
   Params = ccLocal;
   bool AnythingFound = false;
+  std::set<UnicodeString> OptionIds;
 
   for (int Index = 0; Index < Lines->Count; Index++)
   {
@@ -2879,6 +2922,23 @@ void __fastcall TCustomCommandType::LoadExtension(TStrings * Lines)
               throw Exception(MainInstructions(FMTLOAD(EXTENSION_DIRECTIVE_ERROR, (Value, Directive))));
             }
           }
+          else if (Key == L"option")
+          {
+            TOption Option;
+            if (!ParseOption(Value, Option) ||
+                (Option.IsControl && (OptionIds.find(Option.Id.LowerCase()) != OptionIds.end())))
+            {
+              throw Exception(MainInstructions(FMTLOAD(EXTENSION_DIRECTIVE_ERROR, (Value, Directive))));
+            }
+            else
+            {
+              FOptions.push_back(Option);
+              if (!Option.IsControl)
+              {
+                OptionIds.insert(Option.Id.LowerCase());
+              }
+            }
+          }
           else if (Key == L"description")
           {
             Description = Value;
@@ -2893,7 +2953,11 @@ void __fastcall TCustomCommandType::LoadExtension(TStrings * Lines)
           }
           else if (Key == L"homepage")
           {
-            // noop
+            HomePage = Value;
+          }
+          else if (Key == L"optionspage")
+          {
+            OptionsPage = Value;
           }
           else if (Key == L"source")
           {
@@ -2929,6 +2993,152 @@ void __fastcall TCustomCommandType::LoadExtension(TStrings * Lines)
   }
 }
 //---------------------------------------------------------------------------
+bool __fastcall TCustomCommandType::ParseOption(const UnicodeString & Value, TOption & Option)
+{
+  UnicodeString Buf = Value;
+  UnicodeString KindName;
+  bool Result =
+    CutToken(Buf, Option.Id) &&
+    CutToken(Buf, KindName) &&
+    CutToken(Buf, Option.Caption);
+    (CutToken(Buf, Option.Default) || !Option.IsControl);
+
+  if (Result)
+  {
+    KindName = KindName.LowerCase();
+    if (KindName == L"label")
+    {
+      Option.Kind = okLabel;
+      Result = !Option.IsControl;
+    }
+    else if (KindName == L"link")
+    {
+      Option.Kind = okLink;
+      Result = !Option.IsControl;
+    }
+    else if (KindName == L"textbox")
+    {
+      Option.Kind = okTextBox;
+      Result = Option.IsControl;
+    }
+    else if (KindName == L"file")
+    {
+      Option.Kind = okFile;
+      Result = Option.IsControl;
+    }
+    else if (KindName == L"dropdownlist")
+    {
+      Option.Kind = okDropDownList;
+      Result = Option.IsControl;
+    }
+    else if (KindName == L"combobox")
+    {
+      Option.Kind = okComboBox;
+      Result = Option.IsControl;
+    }
+    else if (KindName == L"checkbox")
+    {
+      Option.Kind = okCheckBox;
+      Result = Option.IsControl;
+    }
+    else
+    {
+      Option.Kind = okUnknown;
+    }
+
+    UnicodeString Param;
+    while (CutToken(Buf, Param))
+    {
+      Option.Params.push_back(Param);
+    }
+  }
+
+  return Result;
+}
+//---------------------------------------------------------------------------
+int __fastcall TCustomCommandType::GetOptionsCount() const
+{
+  return FOptions.size();
+}
+//---------------------------------------------------------------------------
+const TCustomCommandType::TOption & __fastcall TCustomCommandType::GetOption(int Index) const
+{
+  return FOptions[Index];
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TCustomCommandType::GetOptionKey(const TCustomCommandType::TOption & Option) const
+{
+  return Id + L"\\" + Option.Id;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TCustomCommandType::GetCommandWithExpandedOptions(TStrings * CustomCommandOptions) const
+{
+  UnicodeString Result = Command;
+  for (int Index = 0; Index < OptionsCount; Index++)
+  {
+    const TCustomCommandType::TOption & Option = GetOption(Index);
+    if (Option.IsControl)
+    {
+      UnicodeString OptionKey = GetOptionKey(Option);
+      UnicodeString OptionValue;
+      if (CustomCommandOptions->IndexOfName(OptionKey) >= 0)
+      {
+        OptionValue = CustomCommandOptions->Values[OptionKey];
+      }
+      else
+      {
+        OptionValue = Option.Default;
+      }
+      UnicodeString OptionCommand = GetOptionCommand(Option, OptionValue);
+      Result = ReplaceText(Result, FORMAT(L"%%%s%%", (Option.Id)), OptionCommand);
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TCustomCommandType::GetOptionCommand(const TOption & Option, const UnicodeString & Value) const
+{
+  UnicodeString Result = Value;
+
+  switch (Option.Kind)
+  {
+    case okUnknown:
+    case okTextBox:
+    case okDropDownList:
+    case okComboBox:
+    case okCheckBox:
+      // noop
+      break;
+
+    case okFile:
+      Result = ExpandEnvironmentVariables(Result);
+      break;
+
+    case okLabel:
+    case okLink:
+    default:
+      DebugFail();
+  }
+
+  return Result;
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+bool __fastcall TCustomCommandType::TOption::GetIsControl() const
+{
+  return (Id != L"-");
+}
+//---------------------------------------------------------------------------
+bool TCustomCommandType::TOption::operator==(const TCustomCommandType::TOption & Other) const
+{
+  return
+    (Id == Other.Id) &&
+    (Kind == Other.Kind) &&
+    (Caption == Other.Caption) &&
+    (Default == Other.Default) &&
+    (Params == Other.Params);
+}
+//---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TCustomCommandList::TCustomCommandList()
 {

+ 41 - 0
source/windows/WinConfiguration.h

@@ -434,6 +434,8 @@ private:
   int FMasterPasswordSession;
   bool FMasterPasswordSessionAsked;
   std::unique_ptr<TFont> FSystemIconFont;
+  std::unique_ptr<TStringList> FCustomCommandOptions;
+  bool FCustomCommandOptionsModified;
   int FLastMachineInstallations;
   __property int LastMachineInstallations = { read = FLastMachineInstallations, write = FLastMachineInstallations };
   int FMachineInstallations;
@@ -528,6 +530,8 @@ private:
   void __fastcall SetHonorDrivePolicy(bool value);
   bool __fastcall GetIsBeta();
   TFont * __fastcall GetSystemIconFont();
+  TStrings * __fastcall GetCustomCommandOptions();
+  void __fastcall SetCustomCommandOptions(TStrings * value);
 
   bool __fastcall GetDDExtInstalled();
   void __fastcall AddVersionToHistory();
@@ -698,6 +702,7 @@ public:
   __property bool HonorDrivePolicy = { read = GetHonorDrivePolicy, write = SetHonorDrivePolicy };
   __property TMasterPasswordPromptEvent OnMasterPasswordPrompt = { read = FOnMasterPasswordPrompt, write = FOnMasterPasswordPrompt };
   __property TFont * SystemIconFont = { read = GetSystemIconFont };
+  __property TStrings * CustomCommandOptions = { read = GetCustomCommandOptions, write = SetCustomCommandOptions };
 };
 //---------------------------------------------------------------------------
 class TCustomCommandType
@@ -706,6 +711,27 @@ public:
   __fastcall TCustomCommandType();
   __fastcall TCustomCommandType(const TCustomCommandType & Other);
 
+  enum TOptionKind { okUnknown, okLabel, okLink, okTextBox, okFile, okDropDownList, okComboBox, okCheckBox };
+
+  class TOption
+  {
+  public:
+    __fastcall TOption() {}
+
+    UnicodeString Id;
+    TOptionKind Kind;
+    UnicodeString Caption;
+    UnicodeString Default;
+    typedef std::vector<UnicodeString> TParams;
+    TParams Params;
+
+    bool operator==(const TOption & Other) const;
+    __property bool IsControl = { read = GetIsControl };
+
+  private:
+    bool __fastcall GetIsControl() const;
+  };
+
   TCustomCommandType & operator=(const TCustomCommandType & Other);
   bool __fastcall Equals(const TCustomCommandType * Other) const;
 
@@ -720,6 +746,18 @@ public:
   __property UnicodeString Id = { read = FId, write = FId };
   __property UnicodeString FileName = { read = FFileName, write = FFileName };
   __property UnicodeString Description = { read = FDescription, write = FDescription };
+  __property UnicodeString HomePage = { read = FHomePage, write = FHomePage };
+  __property UnicodeString OptionsPage = { read = FOptionsPage, write = FOptionsPage };
+
+  __property int OptionsCount = { read = GetOptionsCount };
+  const TOption & __fastcall GetOption(int Index) const;
+  UnicodeString __fastcall GetOptionKey(const TOption & Option) const;
+  UnicodeString __fastcall GetCommandWithExpandedOptions(TStrings * CustomCommandOptions) const;
+
+protected:
+  bool __fastcall ParseOption(const UnicodeString & Value, TOption & Option);
+  int __fastcall GetOptionsCount() const;
+  UnicodeString __fastcall GetOptionCommand(const TOption & Option, const UnicodeString & Value) const;
 
 private:
   UnicodeString FName;
@@ -729,6 +767,9 @@ private:
   UnicodeString FId;
   UnicodeString FFileName;
   UnicodeString FDescription;
+  UnicodeString FHomePage;
+  UnicodeString FOptionsPage;
+  std::vector<TOption> FOptions;
 };
 //---------------------------------------------------------------------------
 class TCustomCommandList