瀏覽代碼

Bug 1711: Improve incremental search in file panels

https://winscp.net/tracker/1711

Source commit: 2fcb59e4ef3cf5ad46f6d9d5a9aa0a30e5ec1c86
Martin Prikryl 7 年之前
父節點
當前提交
9479d15991

+ 1 - 0
source/components/UnixDirView.h

@@ -106,6 +106,7 @@ __published:
   __property TDDDragFileName OnDDDragFileName = { read = FOnDDDragFileName,
     write = FOnDDDragFileName};
   __property OnBusy;
+  __property OnChangeFocus;
   __property bool ShowInaccesibleDirectories  =
     { read=FShowInaccesibleDirectories, write=SetShowInaccesibleDirectories,
       default=true  };

+ 14 - 0
source/core/Common.cpp

@@ -864,6 +864,20 @@ int __fastcall CompareLogicalText(
   }
 }
 //---------------------------------------------------------------------------
+bool ContainsTextSemiCaseSensitive(const UnicodeString & Text, const UnicodeString & SubText)
+{
+  bool Result;
+  if (AnsiLowerCase(SubText) == SubText)
+  {
+    Result = ContainsText(Text, SubText);
+  }
+  else
+  {
+    Result = ContainsStr(Text, SubText);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 bool __fastcall IsReservedName(UnicodeString FileName)
 {
   int P = FileName.Pos(L".");

+ 1 - 0
source/core/Common.h

@@ -96,6 +96,7 @@ bool __fastcall SamePaths(const UnicodeString & Path1, const UnicodeString & Pat
 bool __fastcall IsPathToSameFile(const UnicodeString & Path1, const UnicodeString & Path2);
 int __fastcall CompareLogicalText(
   const UnicodeString & S1, const UnicodeString & S2, bool NaturalOrderNumericalSorting);
+bool ContainsTextSemiCaseSensitive(const UnicodeString & Text, const UnicodeString & SubText);
 bool __fastcall IsReservedName(UnicodeString FileName);
 UnicodeString __fastcall ApiPath(UnicodeString Path);
 UnicodeString __fastcall DisplayableStr(const RawByteString & Str);

+ 227 - 1
source/forms/CustomScpExplorer.cpp

@@ -226,6 +226,8 @@ __fastcall TCustomScpExplorerForm::TCustomScpExplorerForm(TComponent* Owner):
   FOnFeedSynchronizeError = NULL;
   FNeedSession = false;
   FDoNotIdleCurrentTerminal = 0;
+  FIncrementalSearching = 0;
+  FIncrementalSearchHaveNext = false;
 
   FEditorManager = new TEditorManager();
   FEditorManager->OnFileChange = ExecutedFileChanged;
@@ -8189,7 +8191,11 @@ UnicodeString __fastcall TCustomScpExplorerForm::FileStatusBarText(
 {
   UnicodeString Result;
 
-  if ((Side == osRemote) && (Terminal == NULL))
+  if (!FIncrementalSearch.IsEmpty() && (Side == GetSide(osCurrent)))
+  {
+    Result = FormatIncrementalSearchStatus(FIncrementalSearch, FIncrementalSearchHaveNext);
+  }
+  else if ((Side == osRemote) && (Terminal == NULL))
   {
    // noop
   }
@@ -8702,6 +8708,7 @@ void __fastcall TCustomScpExplorerForm::BeforeAction()
   {
     RemoteDirView->ItemFocused->CancelEdit();
   }
+  ResetIncrementalSearch();
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::PostComponentHide(Byte Component)
@@ -8813,6 +8820,10 @@ void __fastcall TCustomScpExplorerForm::Dispatch(void * Message)
       WMWinIniChange(*M);
       break;
 
+    case CM_DIALOGKEY:
+      CMDialogKey(*reinterpret_cast<TWMKeyDown *>(M));
+      break;
+
     default:
       TForm::Dispatch(Message);
       break;
@@ -9318,6 +9329,7 @@ void __fastcall TCustomScpExplorerForm::RemotePathComboBoxCancel(TObject * Sende
 void __fastcall TCustomScpExplorerForm::DirViewEditing(
   TObject * Sender, TListItem * Item, bool & /*AllowEdit*/)
 {
+  ResetIncrementalSearch();
   TCustomDirView * DirView = dynamic_cast<TCustomDirView *>(Sender);
   DebugAssert(DirView != NULL);
   if (!WinConfiguration->RenameWholeName && !DirView->ItemIsDirectory(Item))
@@ -10238,3 +10250,217 @@ void __fastcall TCustomScpExplorerForm::DirViewGetItemColor(
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::DirViewKeyDown(TObject * Sender, WORD & Key, TShiftState)
+{
+  TCustomDirView * DirView = dynamic_cast<TCustomDirView *>(Sender);
+  if (!DirView->IsEditing())
+  {
+    // Handled here, instead of RemoteDirViewKeyPress (as on Login dialog),
+    // as VK_BACK is handled by TCustomDirView.KeyDown and we can swallow it here,
+    // while searching
+    if (Key == VK_BACK)
+    {
+      if (!FIncrementalSearch.IsEmpty())
+      {
+        if (FIncrementalSearch.Length() == 1)
+        {
+          ResetIncrementalSearch();
+        }
+        else
+        {
+          UnicodeString NewText = FIncrementalSearch.SubString(1, FIncrementalSearch.Length() - 1);
+          IncrementalSearch(NewText, false, false);
+        }
+        Key = 0;
+      }
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::DirViewKeyPress(TObject * Sender, wchar_t & Key)
+{
+  TCustomDirView * DirView = dynamic_cast<TCustomDirView *>(Sender);
+  if (!DirView->IsEditing())
+  {
+    // Filter control sequences.
+    // When not searching yet, prefer use of the space for toggling file selection
+    // (so we cannot incrementally search for a string starting with a space).
+    if ((Key > VK_SPACE) ||
+        ((Key == VK_SPACE) && (GetKeyState(VK_CONTROL) >= 0) && !FIncrementalSearch.IsEmpty()))
+    {
+      IncrementalSearch(FIncrementalSearch + Key, false, false);
+      Key = 0;
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::ResetIncrementalSearch()
+{
+  if (!FIncrementalSearch.IsEmpty())
+  {
+    FIncrementalSearch = L"";
+    DirView(osCurrent)->UpdateStatusBar();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::IncrementalSearch(const UnicodeString & Text, bool SkipCurrent, bool Reverse)
+{
+  TListItem * Item = SearchFile(Text, SkipCurrent, Reverse);
+
+  if (Item == NULL)
+  {
+    MessageBeep(MB_ICONHAND);
+  }
+  else
+  {
+    TCustomDirView * ADirView = DirView(osCurrent);
+    {
+      TAutoNestingCounter Guard(FIncrementalSearching);
+      ADirView->ItemFocused = Item;
+    }
+    FIncrementalSearch = Text;
+    Item->MakeVisible(false);
+
+    TListItem * NextItem = SearchFile(Text, true, Reverse);
+    FIncrementalSearchHaveNext = (NextItem != NULL) && (NextItem != Item);
+
+    ADirView->UpdateStatusBar();
+  }
+}
+//---------------------------------------------------------------------------
+TListItem * __fastcall TCustomScpExplorerForm::GetNextFile(TListItem * Item, bool Reverse)
+{
+  int Index = Item->Index;
+  if (!Reverse)
+  {
+    Index++;
+    if (Index >= Item->Owner->Count)
+    {
+      Index = 0;
+    }
+  }
+  else
+  {
+    Index--;
+    if (Index < 0)
+    {
+      Index = Item->Owner->Count - 1;
+    }
+  }
+  return Item->Owner->Item[Index];
+}
+//---------------------------------------------------------------------------
+TListItem * __fastcall TCustomScpExplorerForm::SearchFile(const UnicodeString & Text, bool SkipCurrent, bool Reverse)
+{
+  TCustomDirView * ADirView = DirView(osCurrent);
+  if (ADirView->Items->Count == 0)
+  {
+    return NULL;
+  }
+  else
+  {
+    TListItem * CurrentItem = (ADirView->ItemFocused != NULL) ? ADirView->ItemFocused : ADirView->Items->Item[0];
+    TListItem * Item = CurrentItem;
+    if (SkipCurrent)
+    {
+      Item = DebugNotNull(GetNextFile(Item, Reverse));
+    }
+
+    while (true)
+    {
+      bool Matches = false;
+
+      switch (WinConfiguration->PanelSearch)
+      {
+        case isNameStartOnly:
+          Matches = ContainsTextSemiCaseSensitive(Item->Caption.SubString(1, Text.Length()), Text);
+          break;
+        case isName:
+          Matches = ContainsTextSemiCaseSensitive(Item->Caption, Text);
+          break;
+        case isAll:
+          int ColCount = (ADirView->ViewStyle == vsReport) ? ADirView->ColProperties->Count : 1;
+          int Index = 0;
+          while ((Index < ColCount) && !Matches)
+          {
+            bool Visible = (ADirView->ViewStyle == vsReport) ? ADirView->ColProperties->Visible[Index] : true;
+            if (Visible)
+            {
+              Matches = ContainsTextSemiCaseSensitive(ADirView->GetColumnText(Item, Index), Text);
+            }
+            Index++;
+          }
+          break;
+      }
+
+      if (Matches)
+      {
+        return Item;
+      }
+
+      Item = GetNextFile(Item, Reverse);
+
+      if (Item == CurrentItem)
+      {
+        return NULL;
+      }
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::DirViewExit(TObject *)
+{
+  ResetIncrementalSearch();
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::CMDialogKey(TWMKeyDown & Message)
+{
+  if (Message.CharCode == VK_TAB)
+  {
+    if (!FIncrementalSearch.IsEmpty())
+    {
+      TShiftState Shift = KeyDataToShiftState(Message.KeyData);
+      bool Reverse = Shift.Contains(ssShift);
+      IncrementalSearch(FIncrementalSearch, true, Reverse);
+      Message.Result = 1;
+      return;
+    }
+  }
+  else if (Message.CharCode == VK_ESCAPE)
+  {
+    if (!FIncrementalSearch.IsEmpty())
+    {
+      ResetIncrementalSearch();
+      Message.Result = 1;
+      return;
+    }
+  }
+  TForm::Dispatch(&Message);
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::Deactivate()
+{
+  TForm::Deactivate();
+  // E.g. when switching to an internal editor window
+  ResetIncrementalSearch();
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::ApplicationEventsDeactivate(TObject *)
+{
+  // When switching to another application
+  ResetIncrementalSearch();
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::ApplicationEventsModalBegin(TObject *)
+{
+  ResetIncrementalSearch();
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::DirViewChangeFocus(TObject *, TListItem *)
+{
+  if (FIncrementalSearching <= 0)
+  {
+    ResetIncrementalSearch();
+  }
+}
+//---------------------------------------------------------------------------

+ 6 - 0
source/forms/CustomScpExplorer.dfm

@@ -80,11 +80,15 @@ object CustomScpExplorerForm: TCustomScpExplorerForm
       OnColumnRightClick = DirViewColumnRightClick
       OnEditing = DirViewEditing
       OnEnter = RemoteDirViewEnter
+      OnExit = DirViewExit
+      OnKeyDown = DirViewKeyDown
+      OnKeyPress = DirViewKeyPress
       NortonLike = nlOff
       UnixColProperties.ExtWidth = 20
       UnixColProperties.TypeVisible = False
       OnDDDragFileName = RemoteFileControlDDDragFileName
       OnBusy = DirViewBusy
+      OnChangeFocus = DirViewChangeFocus
       OnSelectItem = DirViewSelectItem
       OnLoaded = DirViewLoaded
       OnExecFile = DirViewExecFile
@@ -317,7 +321,9 @@ object CustomScpExplorerForm: TCustomScpExplorerForm
     end
   end
   object ApplicationEvents: TApplicationEvents
+    OnDeactivate = ApplicationEventsDeactivate
     OnMinimize = ApplicationMinimize
+    OnModalBegin = ApplicationEventsModalBegin
     OnRestore = ApplicationRestore
     Left = 88
     Top = 200

+ 15 - 0
source/forms/CustomScpExplorer.h

@@ -193,6 +193,12 @@ __published:
   void __fastcall SessionsPageControlCloseButtonClick(TPageControl *Sender, int Index);
   void __fastcall DirViewGetItemColor(
     TObject * Sender, UnicodeString FileName, bool Directory, __int64 Size, TDateTime Modification, TColor & Color);
+  void __fastcall DirViewExit(TObject *Sender);
+  void __fastcall DirViewKeyDown(TObject *Sender, WORD &Key, TShiftState Shift);
+  void __fastcall DirViewKeyPress(TObject *Sender, System::WideChar &Key);
+  void __fastcall ApplicationEventsDeactivate(TObject *Sender);
+  void __fastcall ApplicationEventsModalBegin(TObject *Sender);
+  void __fastcall DirViewChangeFocus(TObject *Sender, TListItem *Item);
 
 private:
   TTerminal * FTerminal;
@@ -345,6 +351,9 @@ protected:
   int FDoNotIdleCurrentTerminal;
   UnicodeString FFakeFileDropTarget;
   TFileColorData::TList FFileColors;
+  UnicodeString FIncrementalSearch;
+  int FIncrementalSearching;
+  bool FIncrementalSearchHaveNext;
 
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
     TTransferType Type, bool Temp, TStrings * FileList,
@@ -654,6 +663,12 @@ protected:
   TColor __fastcall PanelColor();
   TColor __fastcall DisabledPanelColor();
   void __fastcall WMWinIniChange(TMessage & Message);
+  void __fastcall ResetIncrementalSearch();
+  void __fastcall IncrementalSearch(const UnicodeString & Text, bool SkipCurrent, bool Reverse);
+  TListItem * __fastcall GetNextFile(TListItem * Item, bool Reverse);
+  TListItem * __fastcall SearchFile(const UnicodeString & Text, bool SkipCurrent, bool Reverse);
+  void __fastcall CMDialogKey(TWMKeyDown & Message);
+  DYNAMIC void __fastcall Deactivate();
 
 public:
   virtual __fastcall ~TCustomScpExplorerForm();

+ 11 - 28
source/forms/Login.cpp

@@ -71,7 +71,7 @@ __fastcall TLoginDialog::TLoginDialog(TComponent* AOwner)
   FForceNewSite = false;
   FLoading = false;
   FSortEnablePending = false;
-  FSiteSearch = ssSiteName;
+  FSiteSearch = isName;
   FLinkedForm = NULL;
   FPrevPos = TPoint(std::numeric_limits<LONG>::min(), std::numeric_limits<LONG>::min());
 
@@ -654,9 +654,7 @@ void __fastcall TLoginDialog::UpdateControls()
 
     if (!FSitesIncrementalSearch.IsEmpty())
     {
-      SitesIncrementalSearchLabel->Caption =
-        L" " + FMTLOAD(LOGIN_SITES_INC_SEARCH, (FSitesIncrementalSearch)) +
-        (FSitesIncrementalSearchHaveNext ? L" " + LoadStr(LOGIN_SITES_NEXT_SEARCH) : UnicodeString());
+      SitesIncrementalSearchLabel->Caption = FormatIncrementalSearchStatus(FSitesIncrementalSearch, FSitesIncrementalSearchHaveNext);
     }
 
     EnableControl(ManageButton, !FEditing);
@@ -1208,15 +1206,15 @@ void __fastcall TLoginDialog::ActionListUpdate(TBasicAction * BasicAction,
   }
   else if (Action == SearchSiteNameStartOnlyAction)
   {
-    Action->Checked = (FSiteSearch == ssSiteNameStartOnly);
+    Action->Checked = (FSiteSearch == isNameStartOnly);
   }
   else if (Action == SearchSiteNameAction)
   {
-    Action->Checked = (FSiteSearch == ssSiteName);
+    Action->Checked = (FSiteSearch == isName);
   }
   else if (Action == SearchSiteAction)
   {
-    Action->Checked = (FSiteSearch == ssSite);
+    Action->Checked = (FSiteSearch == isAll);
   }
   else if (Action == CheckForUpdatesAction)
   {
@@ -2638,21 +2636,6 @@ TTreeNode * __fastcall TLoginDialog::GetNextNode(TTreeNode * Node, bool Reverse)
   return Node;
 }
 //---------------------------------------------------------------------------
-static bool __fastcall ContainsTextSemiCaseSensitive(
-  const UnicodeString & Text, const UnicodeString & SubText)
-{
-  bool Result;
-  if (AnsiLowerCase(SubText) == SubText)
-  {
-    Result = ContainsText(Text, SubText);
-  }
-  else
-  {
-    Result = ContainsStr(Text, SubText);
-  }
-  return Result;
-}
-//---------------------------------------------------------------------------
 TTreeNode * __fastcall TLoginDialog::SearchSite(const UnicodeString & Text,
   bool AllowExpanding, bool SkipCurrent, bool Reverse)
 {
@@ -2691,13 +2674,13 @@ TTreeNode * __fastcall TLoginDialog::SearchSite(const UnicodeString & Text,
 
         switch (FSiteSearch)
         {
-          case ssSiteNameStartOnly:
+          case isNameStartOnly:
             Matches = ContainsTextSemiCaseSensitive(Node->Text.SubString(1, Text.Length()), Text);
             break;
-          case ssSiteName:
+          case isName:
             Matches = ContainsTextSemiCaseSensitive(Node->Text, Text);
             break;
-          case ssSite:
+          case isAll:
             Matches = ContainsTextSemiCaseSensitive(Node->Text, Text);
             if (!Matches && IsSiteNode(Node))
             {
@@ -3065,17 +3048,17 @@ void __fastcall TLoginDialog::CopyParamRuleActionExecute(TObject * /*Sender*/)
 //---------------------------------------------------------------------------
 void __fastcall TLoginDialog::SearchSiteNameStartOnlyActionExecute(TObject * /*Sender*/)
 {
-  FSiteSearch = ssSiteNameStartOnly;
+  FSiteSearch = isNameStartOnly;
 }
 //---------------------------------------------------------------------------
 void __fastcall TLoginDialog::SearchSiteNameActionExecute(TObject * /*Sender*/)
 {
-  FSiteSearch = ssSiteName;
+  FSiteSearch = isName;
 }
 //---------------------------------------------------------------------------
 void __fastcall TLoginDialog::SearchSiteActionExecute(TObject * /*Sender*/)
 {
-  FSiteSearch = ssSite;
+  FSiteSearch = isAll;
 }
 //---------------------------------------------------------------------------
 void __fastcall TLoginDialog::ChangeScale(int M, int D)

+ 1 - 1
source/forms/Login.h

@@ -306,7 +306,7 @@ private:
   bool FLoading;
   bool FSortEnablePending;
   std::unique_ptr<TImageList> FButtonImageList;
-  TSiteSearch FSiteSearch;
+  TIncrementalSearch FSiteSearch;
   TForm * FLinkedForm;
   TPoint FPrevPos;
   int FSiteButtonsPadding;

+ 30 - 0
source/forms/Preferences.cpp

@@ -500,6 +500,21 @@ void __fastcall TPreferencesDialog::LoadConfiguration()
         DebugFail();
     }
 
+    switch (WinConfiguration->PanelSearch)
+    {
+      case isNameStartOnly:
+        PanelSearchCombo->ItemIndex = 0;
+        break;
+      case isName:
+        PanelSearchCombo->ItemIndex = 1;
+        break;
+      case isAll:
+        PanelSearchCombo->ItemIndex = 2;
+        break;
+      default:
+        DebugFail();
+    }
+
     bool CustomPanelFont = !WinConfiguration->PanelFont.FontName.IsEmpty();
     PanelFontCheck->Checked = CustomPanelFont;
     if (CustomPanelFont)
@@ -859,6 +874,21 @@ void __fastcall TPreferencesDialog::SaveConfiguration()
         DebugFail();
     }
 
+    switch (PanelSearchCombo->ItemIndex)
+    {
+      case 0:
+        WinConfiguration->PanelSearch = isNameStartOnly;
+        break;
+      case 1:
+        WinConfiguration->PanelSearch = isName;
+        break;
+      case 2:
+        WinConfiguration->PanelSearch = isAll;
+        break;
+      default:
+        DebugFail();
+    }
+
     TFontConfiguration PanelFontConfiguration;
     if (PanelFontCheck->Checked)
     {

+ 28 - 4
source/forms/Preferences.dfm

@@ -632,13 +632,13 @@ object PreferencesDialog: TPreferencesDialog
           Left = 8
           Top = 8
           Width = 389
-          Height = 195
+          Height = 222
           Anchors = [akLeft, akTop, akRight]
           Caption = 'Common'
           TabOrder = 0
           DesignSize = (
             389
-            195)
+            222)
           object Label1: TLabel
             Left = 16
             Top = 167
@@ -648,6 +648,15 @@ object PreferencesDialog: TPreferencesDialog
             FocusControl = FormatSizeBytesCombo
             OnClick = ControlChange
           end
+          object Label2: TLabel
+            Left = 16
+            Top = 194
+            Width = 96
+            Height = 13
+            Caption = '&Incremental search:'
+            FocusControl = FormatSizeBytesCombo
+            OnClick = ControlChange
+          end
           object ShowHiddenFilesCheck: TCheckBox
             Left = 16
             Top = 21
@@ -723,10 +732,25 @@ object PreferencesDialog: TPreferencesDialog
             TabOrder = 5
             OnClick = ControlChange
           end
+          object PanelSearchCombo: TComboBox
+            Left = 216
+            Top = 191
+            Width = 156
+            Height = 21
+            Style = csDropDownList
+            Anchors = [akTop, akRight]
+            MaxLength = 1
+            TabOrder = 7
+            OnChange = ControlChange
+            Items.Strings = (
+              'Beginning of name only'
+              'Any part of name'
+              'All columns')
+          end
         end
         object DoubleClickGroup: TGroupBox
           Left = 8
-          Top = 209
+          Top = 236
           Width = 389
           Height = 74
           Anchors = [akLeft, akTop, akRight]
@@ -771,7 +795,7 @@ object PreferencesDialog: TPreferencesDialog
         end
         object PanelFontGroup: TGroupBox
           Left = 8
-          Top = 289
+          Top = 316
           Width = 389
           Height = 82
           Anchors = [akLeft, akRight, akBottom]

+ 2 - 0
source/forms/Preferences.h

@@ -333,6 +333,8 @@ __published:
   TGroupBox *ThemeGroup;
   TLabel *Label7;
   TComboBox *ThemeCombo;
+  TComboBox *PanelSearchCombo;
+  TLabel *Label2;
   void __fastcall FormShow(TObject *Sender);
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall EditorFontButtonClick(TObject *Sender);

+ 2 - 0
source/forms/ScpCommander.cpp

@@ -1830,6 +1830,7 @@ void __fastcall TScpCommanderForm::BeforeAction()
 void __fastcall TScpCommanderForm::RemoteDirViewPathChange(TCustomDirView * /*Sender*/)
 {
   UpdateRemotePathComboBox(false);
+  ResetIncrementalSearch();
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::UpdateImages()
@@ -1912,6 +1913,7 @@ void __fastcall TScpCommanderForm::LocalPathComboUpdate()
 void __fastcall TScpCommanderForm::LocalDirViewPathChange(TCustomDirView * /*Sender*/)
 {
   LocalPathComboUpdate();
+  ResetIncrementalSearch();
   if (IsUncPath(LocalDirView->Path))
   {
     Configuration->Usage->Inc(L"BrowsedUncPath");

+ 4 - 0
source/forms/ScpCommander.dfm

@@ -1437,6 +1437,9 @@ inherited ScpCommanderForm: TScpCommanderForm
       OnColumnRightClick = DirViewColumnRightClick
       OnEditing = DirViewEditing
       OnEnter = LocalDirViewEnter
+      OnExit = DirViewExit
+      OnKeyDown = DirViewKeyDown
+      OnKeyPress = DirViewKeyPress
       DirColProperties.ExtVisible = False
       PathLabel = LocalPathLabel
       OnUpdateStatusBar = LocalDirViewUpdateStatusBar
@@ -1459,6 +1462,7 @@ inherited ScpCommanderForm: TScpCommanderForm
       OnHistoryGo = DirViewHistoryGo
       OnPathChange = LocalDirViewPathChange
       OnBusy = DirViewBusy
+      OnChangeFocus = DirViewChangeFocus
     end
     object LocalTopDock: TTBXDock
       Left = 0

+ 1 - 0
source/forms/ScpExplorer.cpp

@@ -358,6 +358,7 @@ void __fastcall TScpExplorerForm::RemoteDirViewPathChange(
   TCustomDirView * /*Sender*/)
 {
   UpdateRemotePathComboBox(false);
+  ResetIncrementalSearch();
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpExplorerForm::ToolbarItemResize(TTBXCustomDropDownItem * Item, int Width)

+ 53 - 16
source/packages/filemng/CustomDirView.pas

@@ -72,6 +72,7 @@ type
   TDirViewGetItemColorEvent = procedure(Sender: TObject; FileName: string; Directory: Boolean; Size: Int64; Modification: TDateTime; var Color: TColor) of object;
   TDirViewUpdateStatusBarEvent = procedure(Sender: TObject; const FileInfo: TStatusFileInfo) of object;
   TDirViewBusy = procedure(Sender: TObject; Busy: Integer; var State: Boolean) of object;
+  TDirViewChangeFocusEvent = procedure(Sender: TObject; Item: TListItem) of object;
   TBusyOperation = reference to procedure;
 
 type
@@ -170,6 +171,7 @@ type
     FStatusFileInfo: TStatusFileInfo;
     FDoubleBufferedScrollingWorkaround: Boolean;
     FOnBusy: TDirViewBusy;
+    FOnChangeFocus: TDirViewChangeFocusEvent;
 
     procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
     procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
@@ -305,7 +307,6 @@ type
     function TargetHasDropHandler(Item: TListItem; Effect: Integer): Boolean; virtual;
     procedure UpdatePathLabel; dynamic;
     procedure UpdatePathLabelCaption; dynamic;
-    procedure UpdateStatusBar; dynamic;
     function FileNameMatchesMasks(FileName: string; Directory: Boolean; Size: Int64; Modification: TDateTime; Masks: string; AllowImplicitMatches: Boolean): Boolean;
     function EnableDragOnClick: Boolean; override;
     procedure SetMask(Value: string); virtual;
@@ -330,6 +331,7 @@ type
     procedure Load(DoFocusSomething: Boolean); virtual;
     procedure NeedImageLists(Recreate: Boolean);
     procedure FreeImageLists;
+    procedure DoUpdateStatusBar(Force: Boolean = False);
     procedure DoCustomDrawItem(Item: TListItem; Stage: TCustomDrawStage);
     property ImageList16: TImageList read FImageList16;
     property ImageList32: TImageList read FImageList32;
@@ -361,6 +363,7 @@ type
       ExistingOnly: Boolean; Criterias: TCompareCriterias): TStrings;
     procedure CompareFiles(DirView: TCustomDirView; ExistingOnly: Boolean;
       Criterias: TCompareCriterias); virtual;
+    function GetColumnText(ListItem: TListItem; Index: Integer): string;
     procedure SaveSelection;
     procedure RestoreSelection;
     procedure DiscardSavedSelection;
@@ -374,6 +377,7 @@ type
     procedure ClearState;
     procedure DisplayContextMenu(Where: TPoint); virtual; abstract;
     procedure DisplayContextMenuInSitu;
+    procedure UpdateStatusBar;
 
     property AddParentDir: Boolean read FAddParentDir write SetAddParentDir default False;
     property DimmHiddenFiles: Boolean read FDimmHiddenFiles write SetDimmHiddenFiles default True;
@@ -479,6 +483,8 @@ type
     property ShowHiddenFiles: Boolean read FShowHiddenFiles write SetShowHiddenFiles default True;
     property OnUpdateStatusBar: TDirViewUpdateStatusBarEvent read FOnUpdateStatusBar write FOnUpdateStatusBar;
     property OnBusy: TDirViewBusy read FOnBusy write FOnBusy;
+    property OnChangeFocus: TDirViewChangeFocusEvent read FOnChangeFocus write FOnChangeFocus;
+
     {Watch current directory for filename changes (create, rename, delete files)}
     property WatchForChanges: Boolean read FWatchForChanges write SetWatchForChanges default False;
   end;
@@ -886,7 +892,7 @@ begin
   finally
     FFilesSelSize := 0;
     FFilesSize := 0;
-    UpdateStatusBar;
+    DoUpdateStatusBar;
   end;
 end;
 
@@ -942,14 +948,21 @@ begin
       with PNMListView(Message.NMHdr)^ do
         if (uChanged = LVIF_STATE) and Valid and (not FClearingItems) then
         begin
-          if ((uOldState and (LVIS_SELECTED or LVIS_FOCUSED)) <>
-              (uNewState and (LVIS_SELECTED or LVIS_FOCUSED))) then
-                UpdateStatusBarPending := True;
-          if ((uOldState and LVIS_SELECTED) <> (uNewState and LVIS_SELECTED)) then
+          if ((uOldState and (LVIS_SELECTED or LVIS_FOCUSED)) <> (uNewState and (LVIS_SELECTED or LVIS_FOCUSED))) then
           begin
-            FileSize := ItemFileSize(Items[iItem]);
-            if (uOldState and LVIS_SELECTED) <> 0 then Dec(FFilesSelSize, FileSize)
-              else Inc(FFilesSelSize, FileSize);
+            Item := Items[iItem];
+            UpdateStatusBarPending := True;
+            if (uOldState and LVIS_SELECTED) <> (uNewState and LVIS_SELECTED) then
+            begin
+              FileSize := ItemFileSize(Item);
+              if (uOldState and LVIS_SELECTED) <> 0 then Dec(FFilesSelSize, FileSize)
+                else Inc(FFilesSelSize, FileSize);
+            end;
+            if (uOldState and LVIS_FOCUSED) <> (uNewState and LVIS_FOCUSED) then
+            begin
+              if Assigned(OnChangeFocus) then
+                OnChangeFocus(Self, Item);
+            end;
           end;
         end;
     LVN_ENDLABELEDIT:
@@ -1016,7 +1029,7 @@ begin
       except
       end;
 
-  if UpdateStatusBarPending then UpdateStatusBar;
+  if UpdateStatusBarPending then DoUpdateStatusBar;
 end;
 
 function TCustomDirView.FileNameMatchesMasks(FileName: string;
@@ -1433,8 +1446,13 @@ begin
     if ((Key = VK_BACK) or ((Key = VK_PRIOR) and (ssCtrl in Shift))) and
        (not IsRoot) then
     begin
-      Key := 0;
-      DoExecuteParentDirectory;
+      inherited;
+      // If not handled in TCustomScpExplorerForm::DirViewKeyDown
+      if Key <> 0 then
+      begin
+        Key := 0;
+        DoExecuteParentDirectory;
+      end;
     end
       else
     if ((Key = VK_UP) and (ssAlt in Shift)) and
@@ -1572,7 +1590,7 @@ begin
   end;
 end; { UpdatePathLabel }
 
-procedure TCustomDirView.UpdateStatusBar;
+procedure TCustomDirView.DoUpdateStatusBar(Force: Boolean);
 var
   StatusFileInfo: TStatusFileInfo;
 begin
@@ -1588,7 +1606,7 @@ begin
       FilteredCount := Self.FilteredCount;
     end;
 
-    if not CompareMem(@StatusFileInfo, @FStatusFileInfo, SizeOf(StatusFileInfo)) then
+    if Force or (not CompareMem(@StatusFileInfo, @FStatusFileInfo, SizeOf(StatusFileInfo))) then
     begin
       FStatusFileInfo := StatusFileInfo;
       OnUpdateStatusBar(Self, FStatusFileInfo);
@@ -1596,6 +1614,11 @@ begin
   end;
 end; { UpdateStatusBar }
 
+procedure TCustomDirView.UpdateStatusBar;
+begin
+  DoUpdateStatusBar(True);
+end;
+
 procedure TCustomDirView.WMContextMenu(var Message: TWMContextMenu);
 var
   Point: TPoint;
@@ -1945,7 +1968,7 @@ begin
         end;
 
         UpdatePathLabel;
-        UpdateStatusBar;
+        DoUpdateStatusBar;
       end;
     end;
   end;
@@ -2445,7 +2468,7 @@ procedure TCustomDirView.EndSelectionUpdate;
 begin
   inherited;
   if FUpdatingSelection = 0 then
-    UpdateStatusBar;
+    DoUpdateStatusBar;
 end; { EndUpdatingSelection }
 
 procedure TCustomDirView.ExecuteCurrentFile;
@@ -2995,6 +3018,20 @@ begin
   ProcessChangedFiles(DirView, nil, True, ExistingOnly, Criterias);
 end;
 
+function TCustomDirView.GetColumnText(ListItem: TListItem; Index: Integer): string;
+var
+  DispInfo: TLVItem;
+begin
+  FillChar(DispInfo, SizeOf(DispInfo), 0);
+  DispInfo.Mask := LVIF_TEXT;
+  DispInfo.iSubItem := Index;
+  DispInfo.cchTextMax := 260;
+  SetLength(Result, DispInfo.cchTextMax);
+  DispInfo.pszText := PChar(Result);
+  GetDisplayInfo(ListItem, DispInfo);
+  SetLength(Result, StrLen(PChar(Result)));
+end;
+
 procedure TCustomDirView.FocusSomething;
 begin
   if FSavedSelection then FPendingFocusSomething := True

+ 3 - 2
source/packages/filemng/DirView.pas

@@ -384,6 +384,7 @@ type
     property OnHistoryGo;
     property OnPathChange;
     property OnBusy;
+    property OnChangeFocus;
 
     property ColumnClick;
     property MultiSelect;
@@ -1668,7 +1669,7 @@ begin
             if FocusedIsVisible and Assigned(ItemFocused) then
               ItemFocused.MakeVisible(False);
 
-            UpdateStatusBar;
+            DoUpdateStatusBar;
 
             Screen.Cursor := SaveCursor;
           end;
@@ -2198,7 +2199,7 @@ begin
       if Updating then
         Items.EndUpdate;
       if Updated then
-        UpdateStatusBar;
+        DoUpdateStatusBar;
       FileList.Free;
     end;
   end;

+ 1 - 1
source/packages/my/ListViewColProperties.pas

@@ -61,7 +61,6 @@ type
     procedure CreateProperties(ACount: Integer);
 
     property Columns: TListColumns read GetColumns stored False;
-    property Count: Integer read GetCount stored False;
   public
     constructor Create(ListView: TCustomListView; ColCount: Integer);
     destructor Destroy; override;
@@ -70,6 +69,7 @@ type
     procedure ListViewWndCreated;
     procedure ListViewWndDestroying;
     procedure ListViewWndDestroyed;
+    property Count: Integer read GetCount stored False;
     property Alignments[Index: Integer]: TAlignment read GetAlignments write SetAlignments;
     property Captions[Index: Integer]: string read GetCaptions write SetCaptions;
     property Widths[Index: Integer]: Integer read GetWidths write SetWidths;

+ 6 - 2
source/packages/my/NortonLikeListView.pas

@@ -572,8 +572,12 @@ begin
   begin
     if (GetKeyState(VK_CONTROL) >= 0) then
     begin
-      if Assigned(ItemFocused) then
-        ItemFocused.Selected := not ItemFocused.Selected;
+      // If not handled in TCustomScpExplorerForm::DirViewKeyPress
+      if not DoKeyPress(Message) then
+      begin
+        if Assigned(ItemFocused) then
+          ItemFocused.Selected := not ItemFocused.Selected;
+      end;
     end
       else inherited;
   end

+ 2 - 2
source/resource/TextsWin.h

@@ -507,8 +507,8 @@
 #define EDITOR_LOAD_ERROR       1896
 #define EDITOR_ENCODING_REVERTED 1897
 #define IMPORT_CONF_TITLE       1898
-#define LOGIN_SITES_INC_SEARCH  1899
-#define LOGIN_SITES_NEXT_SEARCH 1900
+#define INC_SEARCH              1899
+#define INC_NEXT_SEARCH         1900
 #define PROGRESS_UPLOAD         1901
 #define PROGRESS_DOWNLOAD       1902
 #define PROGRESS_IN_QUEUE       1903

+ 2 - 2
source/resource/TextsWin1.rc

@@ -512,8 +512,8 @@ BEGIN
         EDITOR_LOAD_ERROR, "Error loading file '%s'."
         EDITOR_ENCODING_REVERTED, "Falling back to '%s' encoding."
         IMPORT_CONF_TITLE, "Select file to import configuration from"
-        LOGIN_SITES_INC_SEARCH, "Search: %s"
-        LOGIN_SITES_NEXT_SEARCH, "(press Tab for next)"
+        INC_SEARCH, "Search: %s"
+        INC_NEXT_SEARCH, "(press Tab for next)"
         PROGRESS_UPLOAD, "Uploading"
         PROGRESS_DOWNLOAD, "Downloading"
         PROGRESS_IN_QUEUE, "%d in Queue"

+ 1 - 1
source/windows/CustomWinConfiguration.cpp

@@ -116,7 +116,7 @@ void __fastcall TCustomWinConfiguration::Default()
   FFindFile.ListParams = L"3;1|125,1;181,1;80,1;122,1;@" + SaveDefaultPixelsPerInch() + L"|0;1;2;3";
   FConsoleWin.WindowSize = FormatDefaultWindowSize(570, 430);
   FLoginDialog.WindowSize = FormatDefaultWindowSize(640, 430);
-  FLoginDialog.SiteSearch = ssSiteName;
+  FLoginDialog.SiteSearch = isName;
   FConfirmExitOnCompletion = true;
   FSynchronizeSummary = true;
   FSessionColors = L"";

+ 2 - 2
source/windows/CustomWinConfiguration.h

@@ -32,11 +32,11 @@ struct TConsoleWinConfiguration
     { return C(WindowSize) 0; };
 };
 //---------------------------------------------------------------------------
-enum TSiteSearch { ssSiteNameStartOnly, ssSiteName, ssSite };
+enum TIncrementalSearch { isNameStartOnly, isName, isAll };
 //---------------------------------------------------------------------------
 struct TLoginDialogConfiguration : public TConsoleWinConfiguration
 {
-  TSiteSearch SiteSearch;
+  TIncrementalSearch SiteSearch;
   bool __fastcall operator !=(TLoginDialogConfiguration & rhc)
     { return (TConsoleWinConfiguration::operator !=(rhc)) || C(SiteSearch) 0; };
 };

+ 9 - 0
source/windows/GUITools.cpp

@@ -9,6 +9,7 @@
 #include "GUITools.h"
 #include "WinConfiguration.h"
 #include <TextsCore.h>
+#include <TextsWin.h>
 #include <CoreMain.h>
 #include <SessionData.h>
 #include <WinInterface.h>
@@ -963,6 +964,14 @@ void __fastcall HideComponentsPanel(TForm * Form)
   }
 }
 //---------------------------------------------------------------------------
+UnicodeString FormatIncrementalSearchStatus(const UnicodeString & Text, bool HaveNext)
+{
+  UnicodeString Result =
+    L" " + FMTLOAD(INC_SEARCH, (Text)) +
+    (HaveNext ? L" " + LoadStr(INC_NEXT_SEARCH) : UnicodeString());
+  return Result;
+}
+//---------------------------------------------------------------------------
 class TCustomDocHandler : public TComponent, public ::IDocHostUIHandler
 {
 public:

+ 1 - 0
source/windows/GUITools.h

@@ -42,6 +42,7 @@ void __fastcall LoadDialogImage(TImage * Image, const UnicodeString & ImageName)
 int __fastcall DialogImageSize(TForm * Form);
 int __fastcall NormalizePixelsPerInch(int PixelsPerInch);
 void __fastcall HideComponentsPanel(TForm * Form);
+UnicodeString FormatIncrementalSearchStatus(const UnicodeString & Text, bool HaveNext);
 namespace Webbrowserex
 {
   class TWebBrowserEx;

+ 7 - 0
source/windows/WinConfiguration.cpp

@@ -546,6 +546,7 @@ void __fastcall TWinConfiguration::Default()
   FSelectMask = AnyMask;
   FShowHiddenFiles = false;
   FFormatSizeBytes = fbKilobytes;
+  FPanelSearch = isNameStartOnly;
   FShowInaccesibleDirectories = true;
   FConfirmTransferring = true;
   FConfirmDeleting = true;
@@ -954,6 +955,7 @@ THierarchicalStorage * TWinConfiguration::CreateScpStorage(bool & SessionList)
     KEY(String,   SelectMask); \
     KEY(Bool,     ShowHiddenFiles); \
     KEY(Integer,  FormatSizeBytes); \
+    KEY(Integer,  PanelSearch); \
     KEY(Bool,     ShowInaccesibleDirectories); \
     KEY(Bool,     ConfirmTransferring); \
     KEY(Bool,     ConfirmDeleting); \
@@ -1976,6 +1978,11 @@ void __fastcall TWinConfiguration::SetFormatSizeBytes(TFormatBytesStyle value)
   SET_CONFIG_PROPERTY(FormatSizeBytes);
 }
 //---------------------------------------------------------------------------
+void __fastcall TWinConfiguration::SetPanelSearch(TIncrementalSearch value)
+{
+  SET_CONFIG_PROPERTY(PanelSearch);
+}
+//---------------------------------------------------------------------------
 void __fastcall TWinConfiguration::SetShowInaccesibleDirectories(bool value)
 {
   SET_CONFIG_PROPERTY(ShowInaccesibleDirectories);

+ 3 - 0
source/windows/WinConfiguration.h

@@ -360,6 +360,7 @@ private:
   UnicodeString FSelectMask;
   bool FShowHiddenFiles;
   TFormatBytesStyle FFormatSizeBytes;
+  TIncrementalSearch FPanelSearch;
   bool FShowInaccesibleDirectories;
   bool FConfirmTransferring;
   bool FConfirmDeleting;
@@ -473,6 +474,7 @@ private:
   void __fastcall SetSelectDirectories(bool value);
   void __fastcall SetShowHiddenFiles(bool value);
   void __fastcall SetFormatSizeBytes(TFormatBytesStyle value);
+  void __fastcall SetPanelSearch(TIncrementalSearch value);
   void __fastcall SetShowInaccesibleDirectories(bool value);
   void __fastcall SetConfirmTransferring(bool value);
   void __fastcall SetConfirmDeleting(bool value);
@@ -650,6 +652,7 @@ public:
   __property UnicodeString SelectMask = { read = FSelectMask, write = FSelectMask };
   __property bool ShowHiddenFiles = { read = FShowHiddenFiles, write = SetShowHiddenFiles };
   __property TFormatBytesStyle FormatSizeBytes = { read = FFormatSizeBytes, write = SetFormatSizeBytes };
+  __property TIncrementalSearch PanelSearch = { read = FPanelSearch, write = SetPanelSearch };
   __property bool ShowInaccesibleDirectories = { read = FShowInaccesibleDirectories, write = SetShowInaccesibleDirectories };
   __property TEditorConfiguration Editor = { read = FEditor, write = SetEditor };
   __property TQueueViewConfiguration QueueView = { read = FQueueView, write = SetQueueView };