318 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
| {
 | |
|    Double Commander Components
 | |
|    -------------------------------------------------------------------------
 | |
|    Path edit class with auto complete feature
 | |
| 
 | |
|    Copyright (C) 2012-2014  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
 | |
|    in a file called COPYING along with this program; if not, write to
 | |
|    the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
 | |
|    02139, USA.
 | |
| }
 | |
| 
 | |
| unit KASPathEdit;
 | |
| 
 | |
| {$mode delphi}
 | |
| 
 | |
| interface
 | |
| 
 | |
| uses
 | |
|   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls,
 | |
|   ShellCtrls, LCLType;
 | |
| 
 | |
| type
 | |
| 
 | |
|   { TKASPathEdit }
 | |
| 
 | |
|   TKASPathEdit = class(TEdit)
 | |
|   private
 | |
|     FPanel: THintWindow;
 | |
|     FListBox: TListBox;
 | |
|     FKeyDown: Word;
 | |
|     FAutoComplete: Boolean;
 | |
|     FObjectTypes: TObjectTypes;
 | |
|     FFileSortType: TFileSortType;
 | |
|   private
 | |
|     procedure AutoComplete(const Path: UTF8String);
 | |
|     procedure SetObjectTypes(const AValue: TObjectTypes);
 | |
|     procedure FormChangeBoundsEvent(Sender: TObject);
 | |
|     procedure ListBoxClick(Sender: TObject);
 | |
|     procedure ListBoxMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
 | |
|   private
 | |
|     procedure ShowListBox;
 | |
|     procedure HideListBox;
 | |
|   protected
 | |
| {$IF DEFINED(LCLWIN32)}
 | |
|     procedure CreateWnd; override;
 | |
| {$ENDIF}
 | |
|     procedure DoExit; override;
 | |
|     procedure KeyDown(var Key: Word; Shift: TShiftState); override;
 | |
|     procedure KeyUpAfterInterface(var Key: Word; Shift: TShiftState); override;
 | |
|   public
 | |
|     constructor Create(AOwner: TComponent); override;
 | |
|   published
 | |
|     property ObjectTypes: TObjectTypes read FObjectTypes write SetObjectTypes;
 | |
|     property FileSortType: TFileSortType read FFileSortType write FFileSortType;
 | |
|   end;
 | |
| 
 | |
| procedure Register;
 | |
| 
 | |
| implementation
 | |
| 
 | |
| uses
 | |
|   LCLProc, Math
 | |
|   {$IF DEFINED(LCLWIN32)}
 | |
|   , ComObj
 | |
|   {$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 }
 | |
| 
 | |
| procedure TKASPathEdit.AutoComplete(const Path: UTF8String);
 | |
| var
 | |
|   I: LongWord;
 | |
|   BasePath: UTF8String;
 | |
| begin
 | |
|   FListBox.Clear;
 | |
|   if Pos(PathDelim, Path) > 0 then
 | |
|   begin
 | |
|     BasePath:= ExtractFilePath(Path);
 | |
|     TCustomShellTreeView.GetFilesInDir(
 | |
|                                        BasePath,
 | |
|                                        ExtractFileName(Path) + '*',
 | |
|                                        FObjectTypes,
 | |
|                                        FListBox.Items,
 | |
|                                        FFileSortType
 | |
|                                        );
 | |
|     if (FListBox.Items.Count > 0) then
 | |
|     begin
 | |
|       ShowListBox;
 | |
|       // Make absolute file name
 | |
|       for I:= 0 to FListBox.Items.Count - 1 do
 | |
|       FListBox.Items[I]:= BasePath + FListBox.Items[I];
 | |
|       // Calculate ListBox height
 | |
|       with FListBox.ItemRect(0) do
 | |
|       I:= Bottom - Top; // TListBox.ItemHeight sometimes don't work under GTK2
 | |
|       with FListBox do
 | |
|       begin
 | |
|         if Items.Count = 1 then
 | |
|           FPanel.ClientHeight:= Self.Height
 | |
|         else
 | |
|           FPanel.ClientHeight:= I * IfThen(Items.Count > 10, 11, Items.Count + 1);
 | |
|       end;
 | |
|     end;
 | |
|   end;
 | |
|   if (FListBox.Items.Count = 0) then HideListBox;
 | |
| 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
 | |
|     Text:= FListBox.Items[FListBox.ItemIndex];
 | |
|     SelStart:= UTF8Length(Text);
 | |
|     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;
 | |
| 
 | |
| procedure TKASPathEdit.ShowListBox;
 | |
| begin
 | |
|   if (FPanel = nil) then
 | |
|   begin
 | |
|     FPanel:= THintWindow.Create(Self);
 | |
|     FPanel.Color:= clForm;
 | |
|     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 (FPanel <> nil) 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}
 | |
| 
 | |
| procedure TKASPathEdit.DoExit;
 | |
| begin
 | |
|   HideListBox;
 | |
|   inherited DoExit;
 | |
| end;
 | |
| 
 | |
| procedure TKASPathEdit.KeyDown(var Key: Word; Shift: TShiftState);
 | |
| begin
 | |
|   FKeyDown:= Key;
 | |
|   case Key of
 | |
|     VK_ESCAPE,
 | |
|     VK_RETURN,
 | |
|     VK_SELECT:
 | |
|       begin
 | |
|         HideListBox;
 | |
|       end;
 | |
|     VK_UP:
 | |
|       if Assigned(FPanel) then
 | |
|       begin
 | |
|         Key:= 0;
 | |
|         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
 | |
|           Text:= FListBox.Items[FListBox.ItemIndex]
 | |
|         else
 | |
|           Text:= ExtractFilePath(Text);
 | |
|         SelStart:= UTF8Length(Text);
 | |
|       end;
 | |
|     VK_DOWN:
 | |
|       if Assigned(FPanel) then
 | |
|       begin
 | |
|         Key:= 0;
 | |
|         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
 | |
|           Text:= FListBox.Items[FListBox.ItemIndex]
 | |
|         else
 | |
|           Text:= ExtractFilePath(Text);
 | |
|         SelStart:= UTF8Length(Text);
 | |
|       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 (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;
 | |
|   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.ClickOnSelChange:= False;
 | |
|   FListBox.OnClick:= ListBoxClick;
 | |
|   FListBox.OnMouseMove:= ListBoxMouseMove;
 | |
| 
 | |
|   FAutoComplete:= True;
 | |
|   FFileSortType:= fstFoldersFirst;
 | |
|   FObjectTypes:= [otNonFolders, otFolders];
 | |
| end;
 | |
| 
 | |
| end.
 |