mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-21 09:58:13 +00:00
476 lines
12 KiB
ObjectPascal
476 lines
12 KiB
ObjectPascal
{
|
|
Double Commander Components
|
|
-------------------------------------------------------------------------
|
|
Path edit class with auto complete feature
|
|
|
|
Copyright (C) 2012-2022 Alexander Koblov (alexx2000@mail.ru)
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
}
|
|
|
|
unit KASPathEdit;
|
|
|
|
{$mode delphi}
|
|
{$interfaces corba}
|
|
{$IF DEFINED(LCLCOCOA)}
|
|
{$modeswitch objectivec1}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls,
|
|
ShellCtrls, LCLType, LCLVersion
|
|
{$IF DEFINED(LCLCOCOA)}
|
|
, CocoaAll, CocoaWindows
|
|
{$ENDIF}
|
|
;
|
|
|
|
type
|
|
|
|
{ TKASPathEditGetFilesFunc }
|
|
|
|
TKASPathEditGetFilesFunc = Procedure (
|
|
const path: String;
|
|
const types: TObjectTypes;
|
|
const sort: TFileSortType;
|
|
files: TStringList );
|
|
|
|
{ IKASPathEditMate }
|
|
|
|
IKASPathEditMate = interface
|
|
function getFilesAtPath(
|
|
const path: String;
|
|
const types: TObjectTypes;
|
|
const sort: TFileSortType ): TStringList;
|
|
end;
|
|
|
|
{ TKASPathEdit }
|
|
|
|
TKASPathEdit = class(TEdit)
|
|
private
|
|
FMate: IKASPathEditMate;
|
|
FKeyDown: Word;
|
|
FBasePath: String;
|
|
FListBox: TListBox;
|
|
FPanel: THintWindow;
|
|
FAutoComplete: Boolean;
|
|
FGetFilesFunc: TKASPathEditGetFilesFunc;
|
|
FStringList: TStringList;
|
|
FObjectTypes: TObjectTypes;
|
|
FFileSortType: TFileSortType;
|
|
private
|
|
procedure setTextAndSelect( newText:String );
|
|
procedure handleSpecialKeys( var Key: Word );
|
|
procedure handleUpKey;
|
|
procedure handleDownKey;
|
|
procedure AutoComplete(const Path: String);
|
|
procedure SetObjectTypes(const AValue: TObjectTypes);
|
|
procedure FormChangeBoundsEvent(Sender: TObject);
|
|
procedure ListBoxClick(Sender: TObject);
|
|
procedure ListBoxMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
|
|
private
|
|
function isShowingListBox(): Boolean; inline;
|
|
procedure ShowListBox;
|
|
procedure HideListBox;
|
|
protected
|
|
{$IF DEFINED(LCLWIN32)}
|
|
procedure CreateWnd; override;
|
|
{$ENDIF}
|
|
{$IF DEFINED(LCLCOCOA)}
|
|
procedure TextChanged; override;
|
|
{$ENDIF}
|
|
procedure DoExit; override;
|
|
procedure VisibleChanged; override;
|
|
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
|
|
procedure KeyUpAfterInterface(var Key: Word; Shift: TShiftState); override;
|
|
public
|
|
onKeyESCAPE: TNotifyEvent;
|
|
onKeyRETURN: TNotifyEvent;
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
property GetFilesFunc: TKASPathEditGetFilesFunc read FGetFilesFunc write FGetFilesFunc;
|
|
property Mate: IKASPathEditMate read FMate write FMate;
|
|
published
|
|
property ObjectTypes: TObjectTypes read FObjectTypes write SetObjectTypes;
|
|
property FileSortType: TFileSortType read FFileSortType write FFileSortType;
|
|
end;
|
|
|
|
procedure Register;
|
|
|
|
implementation
|
|
|
|
uses
|
|
LazUTF8, Math, LazFileUtils, Masks
|
|
{$IF DEFINED(LCLWIN32)}
|
|
, ComObj
|
|
{$ENDIF}
|
|
{$IF DEFINED(MSWINDOWS)}
|
|
, Windows
|
|
{$ENDIF}
|
|
;
|
|
|
|
{$IF DEFINED(LCLWIN32)}
|
|
|
|
const
|
|
SHACF_AUTOAPPEND_FORCE_ON = $40000000;
|
|
SHACF_AUTOSUGGEST_FORCE_ON = $10000000;
|
|
SHACF_FILESYS_ONLY = $00000010;
|
|
SHACF_FILESYS_DIRS = $00000020;
|
|
|
|
function SHAutoComplete(hwndEdit: HWND; dwFlags: DWORD): HRESULT; stdcall; external 'shlwapi.dll';
|
|
|
|
function SHAutoCompleteX(hwndEdit: HWND; ObjectTypes: TObjectTypes): Boolean;
|
|
var
|
|
dwFlags: DWORD;
|
|
begin
|
|
if (ObjectTypes = []) then Exit(False);
|
|
dwFlags := SHACF_AUTOAPPEND_FORCE_ON or SHACF_AUTOSUGGEST_FORCE_ON;
|
|
if (otNonFolders in ObjectTypes) then
|
|
dwFlags := dwFlags or SHACF_FILESYS_ONLY
|
|
else if (otFolders in ObjectTypes) then
|
|
dwFlags := dwFlags or SHACF_FILESYS_DIRS;
|
|
Result:= (SHAutoComplete(hwndEdit, dwFlags) = 0);
|
|
end;
|
|
|
|
{$ENDIF}
|
|
|
|
procedure Register;
|
|
begin
|
|
RegisterComponents('KASComponents', [TKASPathEdit]);
|
|
end;
|
|
|
|
{ TKASPathEdit }
|
|
|
|
function TKASPathEdit.isShowingListBox(): Boolean;
|
|
begin
|
|
Result:= FPanel<>nil;
|
|
end;
|
|
|
|
procedure TKASPathEdit.AutoComplete(const Path: String);
|
|
{$IF LCL_FULLVERSION < 4990000}
|
|
const
|
|
AFlags: array[Boolean] of TMaskOptions = (
|
|
[moDisableSets], [moDisableSets, moCaseSensitive]
|
|
);
|
|
{$ENDIF}
|
|
var
|
|
I: Integer;
|
|
AMask: TMask;
|
|
BasePath: String;
|
|
begin
|
|
FListBox.Clear;
|
|
if Pos(PathDelim, Path) = 0 then
|
|
HideListBox
|
|
else begin
|
|
BasePath:= ExtractFilePath(Path);
|
|
if CompareFilenames(FBasePath, BasePath) <> 0 then
|
|
begin
|
|
FreeAndNil(FStringList);
|
|
FBasePath:= BasePath;
|
|
if Assigned(FMate) then
|
|
FStringList:= FMate.getFilesAtPath(BasePath, FObjectTypes, FFileSortType);
|
|
end;
|
|
if (FStringList=nil) or (FStringList.Count<=0) then
|
|
Exit;
|
|
FListBox.Items.BeginUpdate;
|
|
try
|
|
// Check mask and make absolute file name
|
|
AMask:= TMask.Create(ExtractFileName(Path) + '*',
|
|
{$IF LCL_FULLVERSION < 4990000}
|
|
AFlags[FileNameCaseSensitive]
|
|
{$ELSE}
|
|
FileNameCaseSensitive
|
|
{$ENDIF}
|
|
);
|
|
for I:= 0 to FStringList.Count - 1 do
|
|
begin
|
|
if AMask.Matches(FStringList[I]) then
|
|
FListBox.Items.Add(BasePath + FStringList[I]);
|
|
end;
|
|
AMask.Free;
|
|
finally
|
|
FListBox.Items.EndUpdate;
|
|
end;
|
|
if FListBox.Items.Count = 0 then HideListBox;
|
|
if FListBox.Items.Count > 0 then
|
|
begin
|
|
ShowListBox;
|
|
// Calculate ListBox height
|
|
with FListBox.ItemRect(0) do
|
|
I:= Bottom - Top; // TListBox.ItemHeight sometimes don't work under GTK2
|
|
with FListBox do
|
|
begin
|
|
{$IF NOT DEFINED(LCLCOCOA)}
|
|
if Items.Count = 1 then
|
|
FPanel.ClientHeight:= Self.Height
|
|
else
|
|
FPanel.ClientHeight:= I * IfThen(Items.Count > 10, 11, Items.Count + 1);
|
|
{$ELSE}
|
|
FPanel.ClientHeight:= I * IfThen(Items.Count > 10, 11, Items.Count + 1) + trunc(i/2);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TKASPathEdit.SetObjectTypes(const AValue: TObjectTypes);
|
|
begin
|
|
if FObjectTypes = AValue then Exit;
|
|
FObjectTypes:= AValue;
|
|
{$IF DEFINED(LCLWIN32)}
|
|
if HandleAllocated then RecreateWnd(Self);
|
|
if FAutoComplete then
|
|
{$ENDIF}
|
|
FAutoComplete:= (FObjectTypes <> []);
|
|
end;
|
|
|
|
procedure TKASPathEdit.FormChangeBoundsEvent(Sender: TObject);
|
|
begin
|
|
HideListBox;
|
|
end;
|
|
|
|
procedure TKASPathEdit.ListBoxClick(Sender: TObject);
|
|
begin
|
|
if FListBox.ItemIndex >= 0 then
|
|
begin
|
|
setTextAndSelect( FListBox.Items[FListBox.ItemIndex] );
|
|
HideListBox;
|
|
SetFocus;
|
|
end;
|
|
end;
|
|
|
|
procedure TKASPathEdit.ListBoxMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
FListBox.ItemIndex:= FListBox.ItemAtPos(Classes.Point(X, Y), True);
|
|
end;
|
|
|
|
{$IF DEFINED(LCLCOCOA)}
|
|
procedure cocoaNeedMouseEvent( hintWindow: THintWindow );
|
|
var
|
|
cnt: TCocoaWindowContent;
|
|
begin
|
|
cnt:= TCocoaWindowContent( hintWindow.Handle );
|
|
cnt.window.setIgnoresMouseEvents( false );
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure TKASPathEdit.ShowListBox;
|
|
begin
|
|
if not isShowingListBox() then
|
|
begin
|
|
FPanel:= THintWindow.Create(Self);
|
|
{$IF DEFINED(LCLCOCOA)}
|
|
cocoaNeedMouseEvent(FPanel);
|
|
{$ENDIF}
|
|
FPanel.Color:= clDefault;
|
|
FListBox.Parent:= FPanel;
|
|
|
|
with Parent.ClientToScreen(CLasses.Point(Left, Top)) do
|
|
begin
|
|
FPanel.Left:= X;
|
|
FPanel.Top:= Y + Height;
|
|
end;
|
|
|
|
FPanel.Width:= Width;
|
|
FPanel.Visible:= True;
|
|
|
|
Application.AddOnDeactivateHandler(FormChangeBoundsEvent, True);
|
|
GetParentForm(Self).AddHandlerOnChangeBounds(FormChangeBoundsEvent, True);
|
|
end;
|
|
end;
|
|
|
|
procedure TKASPathEdit.HideListBox;
|
|
begin
|
|
if isShowingListBox() then
|
|
begin
|
|
FPanel.Visible:= False;
|
|
FListBox.Parent:= nil;
|
|
FreeAndNil(FPanel);
|
|
Application.RemoveOnDeactivateHandler(FormChangeBoundsEvent);
|
|
GetParentForm(Self).RemoveHandlerOnChangeBounds(FormChangeBoundsEvent);
|
|
end;
|
|
end;
|
|
|
|
{$IF DEFINED(LCLWIN32)}
|
|
|
|
procedure TKASPathEdit.CreateWnd;
|
|
begin
|
|
inherited CreateWnd;
|
|
FAutoComplete:= not SHAutoCompleteX(Handle, FObjectTypes);
|
|
end;
|
|
|
|
{$ENDIF}
|
|
|
|
|
|
{$IF DEFINED(LCLCOCOA)}
|
|
procedure TKASPathEdit.TextChanged;
|
|
begin
|
|
Inherited;
|
|
if not Modified then
|
|
Exit;
|
|
if FAutoComplete then
|
|
AutoComplete(Text);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
|
|
procedure TKASPathEdit.setTextAndSelect( newText:String );
|
|
var
|
|
start: Integer;
|
|
begin
|
|
if Pos(Text,newText) > 0 then
|
|
start:= UTF8Length(Text)
|
|
else
|
|
start:= UTF8Length(ExtractFilePath(Text));
|
|
Text:= newText;
|
|
SelStart:= start;
|
|
SelLength:= UTF8Length(Text)-SelStart;
|
|
end;
|
|
|
|
procedure TKASPathEdit.DoExit;
|
|
begin
|
|
HideListBox;
|
|
inherited DoExit;
|
|
end;
|
|
|
|
procedure TKASPathEdit.VisibleChanged;
|
|
begin
|
|
FBasePath:= EmptyStr;
|
|
inherited VisibleChanged;
|
|
end;
|
|
|
|
procedure TKASPathEdit.handleSpecialKeys( var Key: Word );
|
|
begin
|
|
if isShowingListBox() then begin
|
|
HideListBox;
|
|
Key:= 0;
|
|
end else begin
|
|
if Key=VK_ESCAPE then begin
|
|
if Assigned(onKeyESCAPE) then begin
|
|
onKeyESCAPE( self );
|
|
Key:= 0;
|
|
end;
|
|
end else begin
|
|
if Assigned(onKeyRETURN) then begin
|
|
onKeyRETURN( self );
|
|
Key:= 0;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TKASPathEdit.handleUpKey;
|
|
begin
|
|
if FListBox.ItemIndex = -1 then
|
|
FListBox.ItemIndex:= FListBox.Items.Count - 1
|
|
else if FListBox.ItemIndex - 1 < 0 then
|
|
FListBox.ItemIndex:= - 1
|
|
else
|
|
FListBox.ItemIndex:= FListBox.ItemIndex - 1;
|
|
|
|
if FListBox.ItemIndex >= 0 then
|
|
setTextAndSelect( FListBox.Items[FListBox.ItemIndex] )
|
|
else
|
|
setTextAndSelect( ExtractFilePath(Text) );
|
|
end;
|
|
|
|
procedure TKASPathEdit.handleDownKey;
|
|
begin
|
|
if FListBox.ItemIndex + 1 >= FListBox.Items.Count then
|
|
FListBox.ItemIndex:= -1
|
|
else if FListBox.ItemIndex = -1 then
|
|
FListBox.ItemIndex:= IfThen(FListBox.Items.Count > 0, 0, -1)
|
|
else
|
|
FListBox.ItemIndex:= FListBox.ItemIndex + 1;
|
|
|
|
if FListBox.ItemIndex >= 0 then
|
|
setTextAndSelect( FListBox.Items[FListBox.ItemIndex] )
|
|
else
|
|
setTextAndSelect( ExtractFilePath(Text) );
|
|
end;
|
|
|
|
procedure TKASPathEdit.KeyDown(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
FKeyDown:= Key;
|
|
case Key of
|
|
VK_ESCAPE,
|
|
VK_RETURN,
|
|
VK_SELECT:
|
|
handleSpecialKeys( Key );
|
|
VK_UP:
|
|
if isShowingListBox() then
|
|
begin
|
|
Key:= 0;
|
|
handleUpKey();
|
|
end;
|
|
VK_DOWN:
|
|
if isShowingListBox() then
|
|
begin
|
|
Key:= 0;
|
|
handleDownKey();
|
|
end;
|
|
end;
|
|
|
|
inherited KeyDown(Key, Shift);
|
|
{$IFDEF LCLGTK2}
|
|
// Workaround for GTK2 - up and down arrows moving through controls.
|
|
if Key in [VK_UP, VK_DOWN] then Key:= 0;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TKASPathEdit.KeyUpAfterInterface(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
{$IF not DEFINED(LCLCOCOA)}
|
|
if (FKeyDown = Key) and FAutoComplete and not (Key in [VK_ESCAPE, VK_RETURN, VK_SELECT, VK_UP, VK_DOWN]) then
|
|
begin
|
|
if Modified then
|
|
begin
|
|
Modified:= False;
|
|
AutoComplete(Text);
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
inherited KeyUpAfterInterface(Key, Shift);
|
|
{$IF DEFINED(LCLWIN32)}
|
|
// Windows auto-completer eats the TAB so LCL doesn't get it and doesn't move to next control.
|
|
if not FAutoComplete and (Key = VK_TAB) then
|
|
GetParentForm(Self).SelectNext(Self, True, True);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
constructor TKASPathEdit.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
|
|
FListBox:= TListBox.Create(Self);
|
|
FListBox.TabStop:= False;
|
|
FListBox.Align:= alClient;
|
|
FListBox.ParentFont:= False;
|
|
FListBox.ClickOnSelChange:= False;
|
|
FListBox.OnClick:= ListBoxClick;
|
|
FListBox.OnMouseMove:= ListBoxMouseMove;
|
|
|
|
FAutoComplete:= True;
|
|
FFileSortType:= fstFoldersFirst;
|
|
FObjectTypes:= [otNonFolders, otFolders];
|
|
end;
|
|
|
|
destructor TKASPathEdit.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
FStringList.Free;
|
|
end;
|
|
|
|
end.
|