1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00
Files
winscp/source/packages/filemng/ShellDialogs.pas
Martin Prikryl a2119a7eac Types and casts safe in respect to pointer size for forward 64-bit compatibility
Also GetWindowLongPtr and GWLP_USERDATA

https://winscp.net/tracker/618

Source commit: c85fdf15e50ce8a6f35b1c4d2a0125a0b9c4f1f0
2026-01-22 14:51:13 +01:00

522 lines
17 KiB
ObjectPascal

Unit ShellDialogs;
{==================================================================
Unit ShellDialogs / Version 1.0 / 06.1999
==================================================================
Description:
============
ShellDisplayContextMenu displays the shell's contextmenu for a
file or directory or for multiple files.
ShellExecuteContextCommand performs a contextmenu-action for a
file or directory or for multiple files.
Author:
=======
(c) Ingo Eckel 1999
Sodener Weg 38
65812 Bad Soden
Germany
Credits:
========
This unit is partly based on the work of
Gerald Nunn (GXExplorer)
and
Brad Stowers (ItemProp)
==================================================================}
{------------------------------------------------------------------
You must pass fully qualified path names to all of these functions.
If you are calling for a subdirectory (i.e. no filename), it is your
responsibility to insure that subdirectories contain !NO! trailing
backslash. Root-directories must be passed as 'C:\'.
ShellDisplayContextMenu displays the right click menu for the given file or
directory and processes the item selected, if any. Parent is the window
handle for the owning window of any error messages that may need to be
displayed by the system, MyForm.Handle is generally fine. Pos is the X, Y
position to display the menu at given in screen (absolute) coordinates.
------------------------------------------------------------------}
Interface
Uses Windows, ShlObj, SysUtils, Classes, Messages, Menus,
PIDL;
{Commands for ShellExecuteContextCommand:}
Const shcProperties = 'properties';
shcCut = 'cut';
shcCopy = 'copy';
shcPaste = 'paste';
shcDelete = 'delete';
shcLink = 'link';
shcrename = 'rename';
shcDefault = '';
Type PPIDLArray = ^TPIDLArray;
TPIDLArray = Array [0..0] of PItemIDList;
{Display the shell's contextmenu to a file or directory.
Requires the iShellFolder-interface to the parent directory and the
PIDLs of the files or directories:}
Procedure ShellDisplayContextMenu(Handle: THandle;
P: TPoint;
ShellFolder: IShellFolder;
PIDLCount: Integer;
Var PIDL: PItemIDList;
AllowRename : Boolean;
Var Verb: String;
PerformPaste : Boolean = True); Overload;
{Display the shell's contextmenu to single file or directory.
Requires the full qualified name of the file or directory:}
Procedure ShellDisplayContextMenu(Handle: THandle;
P: TPoint;
FileName : String;
AllowRename : Boolean;
Var Verb: String;
PerformPaste : Boolean = True); Overload;
{Display the shell's contextmenu to muliple files or directories.
Requires the full qualified name of the parent directory and the
filenames of the files or directories as TStringList:}
Procedure ShellDisplayContextMenu(Handle: THandle;
P: TPoint;
Path : String;
Files : TStringList;
Var Verb: String;
PerformPaste : Boolean = True); Overload;
{Performs a contextmenu-command (properties, copy, cut, paste) for files or directories.
Requires the iShellFolder-interface to the parent directory and the
PIDLs of the files or directories:}
Function ShellExecuteContextCommand(Handle: THandle;
Command: String;
ShellFolder: IShellFolder;
PIDLCount: Integer;
Var PIDL: PItemIDList): Boolean; Overload;
{Performs a contextmenu-command (properties, copy, cut, paste) for a file or directory.
Requires the full qualified name of the file or directory:}
Function ShellExecuteContextCommand(Handle: THandle;
Command: String;
FileName : String): Boolean; Overload;
{Performs a contextmenu-command (properties, copy, cut, paste) for
multiple files or directories.
Requires the full qualified name of the parent directory and the
filenames of the files or directories as TStringList:}
Function ShellExecuteContextCommand(Handle: THandle;
Command : String;
Path : String;
Files : TStringList) : Boolean; Overload;
Var CustomContextMenu : TPopupMenu;
{------------------------------------------------------------------}
Implementation
{------------------------------------------------------------------}
{$R-}
function MenuCallbackProc(Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LResult; stdcall; export;
var CM2: IContextMenu2;
begin
case Msg of
WM_CREATE:
begin
CM2 := IContextMenu2(PCreateStruct(lParam).lpCreateParams);
SetWindowLongPtr(Wnd, GWLP_USERDATA, NativeInt(CM2));
Result := DefWindowProc(Wnd, Msg, wParam, lParam);
end;
// these are the biggies -- the messages that IContextMenu2::HandlMenuMsg is
// supposed to handle.
WM_DRAWITEM,
WM_MEASUREITEM,
WM_INITMENUPOPUP:
begin
begin
CM2 := IContextMenu2(GetWindowLongPtr(Wnd, GWLP_USERDATA));
Assert(CM2 <> NIL, 'NIL Context Menu!');
CM2.HandleMenuMsg(Msg, wParam, lParam);
end;
if Msg = WM_INITMENUPOPUP then
Result := 0
else
Result := 1;
end;
else
Result := DefWindowProc(Wnd, Msg, wParam, lParam);
end;
end; {MenuCallBackProc}
Procedure ShellDisplayContextMenu(Handle: THandle;
P: TPoint;
ShellFolder: IShellFolder;
PIDLCount: Integer;
Var PIDL: PItemIDList;
AllowRename : Boolean;
Var Verb: String;
PerformPaste : Boolean = True);
Const CallBackClassName = 'ShellDialogsCallBack';
CallBackProcessor = 'ShellDialogsCallBackProcessor';
MaxStdEntries = 1000;
Var PopupMenu : HMenu;
Cmd : Cardinal;
ICM : TCMInvokeCommandInfo;
ContextMenu : IContextMenu;
ContextMenu2 : IContextMenu2;
Flags : UINT;
AWndClass : TWndClass;
CallbackWnd : HWnd;
MenuHandle : HWnd;
i : Integer;
Begin
Verb := EmptyStr;
CallBackWnd := 0;
If AllowRename then
Flags := CMF_EXPLORE Or CMF_CANRENAME
Else
Flags := CMF_EXPLORE;
PopupMenu := CreatePopupMenu;
Try
If Succeeded(ShellFolder.GetUIObjectOf(Handle, PIDLCount, PIDL, IID_IContextMenu, NIL, Pointer(ContextMenu))) Then
Begin
ContextMenu._AddRef;
If Succeeded(ContextMenu.QueryInterface(IID_IContextMenu2, ContextMenu2)) Then
Begin
ContextMenu2._AddRef;
Try
ContextMenu2.QueryContextMenu(PopupMenu, 0, 1, MaxStdEntries, Flags);
Except
Exit;
End;
FillChar(AWndClass, SizeOf(AWndClass), #0);
AWndClass.lpszClassName := CallBackClassName;
AWndClass.Style := CS_PARENTDC;
AWndClass.lpfnWndProc := @MenuCallbackProc;
AWndClass.hInstance := HInstance;
Windows.RegisterClass(AWndClass);
CallbackWnd := CreateWindow(CallBackClassName, CallBackProcessor, WS_POPUPWINDOW, 0, 0, 0, 0, 0, 0, HInstance, Pointer(ContextMenu2));
End
Else
Try
ContextMenu.QueryContextMenu(PopupMenu, 0, 1, $7FFF, Flags);
Except
Exit;
End;
If CallbackWnd = 0 then
MenuHandle := Handle
else
MenuHandle := CallbackWnd;
IF Assigned(CustomContextMenu) And (CustomContextMenu.Items.Count > 0) Then
Begin
AppendMenu(PopupMenu, MF_SEPARATOR, 0, NIL);
For i := 0 To CustomContextMenu.Items.Count -1 Do
With CustomContextMenu.Items[i] Do
IF Visible And Assigned(OnClick) Then
Begin
Flags := MF_STRING;
IF Checked Then
Flags := Flags Or MF_CHECKED;
AppendMenu(PopUpMenu, Flags, MaxStdEntries + i + 1, PChar(Caption));
End;
End;
Cmd := Cardinal(TrackPopupMenuEx(PopupMenu,TPM_LEFTALIGN Or TPM_RIGHTBUTTON Or TPM_RETURNCMD, P.X, P.Y, MenuHandle, NIL));
IF Cmd > MaxStdEntries Then
Begin
IF Assigned(CustomContextMenu.Items[Cmd - MaxStdEntries - 1].OnClick) Then
CustomContextMenu.Items[Cmd - MaxStdEntries - 1].OnClick(NIL);
End
Else
if Cmd > 0 then
Begin
SetLength(Verb, 255);
If Assigned(ContextMenu2) then
ContextMenu2.GetCommandString(Cardinal(MakeIntResource(Cmd-1)), GCS_VERB, NIL,PAnsiChar(PChar(Verb)), Length(Verb))
Else
ContextMenu.GetCommandString(Cardinal(MakeIntResource(Cmd-1)),GCS_VERB, NIL, PAnsiChar(PChar(Verb)), Length(Verb));
SetLength(Verb, strlen(PChar(Verb)));
Verb := LowerCase(Verb);
If (Verb <> shcRename) And (PerformPaste Or (Verb <> shcPaste)) Then
Begin
FillChar(ICM,SizeOf(TCMInvokeCommandInfo),#0);
ICM.cbSize := Sizeof(TCMInvokeCommandInfo);
ICM.hwnd := Handle;
ICM.lpVerb := LPCSTR(MakeIntResource(Cmd-1));
ICM.nShow := SW_SHOWNORMAL;
Try
If Assigned(ContextMenu2) Then
ContextMenu2.InvokeCommand(ICM)
Else
ContextMenu.InvokeCommand(ICM);
Except
// eat any dammned shell exceptions!
Exit;
End;
End;
End;
End;
Finally
DestroyMenu(PopupMenu);
If CallbackWnd <> 0 Then
DestroyWindow(CallbackWnd);
IF Assigned(ContextMenu2) Then
ContextMenu2._Release;
IF Assigned(ContextMenu) Then
ContextMenu._Release;
End;
End; {ShellDisplayContextMenu (PIDL) }
Procedure ShellDisplayContextMenu(Handle: THandle;
P: TPoint;
FileName : String;
AllowRename : Boolean;
Var Verb: String;
PerformPaste : Boolean = True);
Var ShellFolder : iShellFolder;
DirPIDL : PItemIDList;
DirPIDLFQ : PItemIDList;
ParentPIDL : PItemIDList;
Begin
DirPIDL := NIL;
DirPIDLFQ := NIL;
ParentPIDL := NIL;
DirPidlFQ := PIDL_GetFromPath(PChar(FileName));
IF Assigned(DirPIDLFQ) Then
Begin
PIDL_GetRelative(DirPIDLFQ, ParentPIDL, DirPIDL);
Try
If PIDL_GetFileFolder(ParentPIDL, ShellFolder) Then
ShellDisplayContextMenu(Handle, P, ShellFolder, 1, DirPIDL, AllowRename, Verb, PerformPaste);
Finally
PIDL_Free(DirPIDL);
PIDL_Free(DirPIDLFQ);
PIDL_Free(ParentPIDL);
End;
End;
End; {ShellDisplayContextMenu (Filename) }
Procedure ShellDisplayContextMenu(Handle: THandle;
P: TPoint;
Path : String;
Files : TStringList;
Var Verb: String;
PerformPaste : Boolean = True);
Var ShellFolder : iShellFolder;
PathPIDL : PItemIDList;
PIDLArray : PPIDLArray;
Index : Integer;
i : Integer;
Begin
IF Files.Count = 0 Then
Exit;
Index := 0;
GetMem(PIDLArray, SizeOf(PItemIDList) * Files.Count);
FillChar(PIDLArray^, Sizeof(PItemIDList) * Files.Count, #0);
Try
PathPIDL := PIDL_GetFromPath(PChar(Path));
IF Assigned(PathPIDL) Then
Begin
Try
If PIDL_GetFileFolder(PathPIDL, ShellFolder) Then
Begin
For i := 0 To Files.Count - 1 Do
Begin
PIDLArray^[i] := PIDL_GetFromParentFolder(ShellFolder, PChar(Files[i]));
IF Assigned(PIDLArray^[i]) Then
INC(Index);
End;
IF Index > 0 Then
Begin
Try
ShellDisplayContextMenu(Handle, P, ShellFolder, Index, PIDLArray^[0], False, Verb, PerformPaste);
Finally
For i := 0 To Index - 1 Do
PIDL_Free(PIDLArray[i]);
End;
End;
End;
Finally
PIDL_Free(PathPIDL);
End;
End;
Finally
FreeMem(PIDLArray);
End;
End; {ShellDisplayContextMenu (TStringList) }
Function ShellExecuteContextCommand(Handle: THandle; Command: String; ShellFolder: IShellFolder; PIDLCount: Integer; Var PIDL: PItemIDList): Boolean;
Var ICM : TCMInvokeCommandInfoEx;
ContextMenu : IContextMenu;
ContextMenu2: IContextMenu2;
Popup : HMenu;
MenuCmd : Cardinal;
HRes : HResult;
Begin
Result := False;
IF Succeeded(ShellFolder.GetUIObjectOf(Handle, PIDLCount, PIDL, IID_IContextMenu, NIL, Pointer(ContextMenu))) Then
Begin
ContextMenu.QueryInterface(IID_IContextMenu2, ContextMenu2);
FillChar(ICM,SizeOf(TCMInvokeCommandInfo), #0);
ICM.hwnd := Handle;
ICM.cbSize := SizeOf(TCMInvokeCommandInfo);
ICM.nShow := SW_SHOWNORMAL;
IF Command <> shcDefault Then
begin
ICM.fMask := CMIC_MASK_UNICODE;
ICM.lpVerb := PAnsiChar(AnsiString(Command));
ICM.lpVerbW := PChar(Command);
end
Else
Begin
{Locate the menuitem for the default action:}
Popup := CreatePopupMenu;
Try
Try
IF Assigned(ContextMenu2) Then
Hres := ContextMenu2.QueryContextMenu(Popup, 0, 1, $7FFF, CMF_DEFAULTONLY)
Else
Hres := ContextMenu.QueryContextMenu(Popup, 0, 1, $7FFF, CMF_DEFAULTONLY);
Except
Exit;
End;
If Succeeded(HRes) Then
Begin
MenuCmd := GetMenuDefaultItem(Popup, 0, 0);
If MenuCmd <> $FFFFFFFF then
ICM.lpVerb := LPCSTR(MakeIntResource(MenuCmd-1))
Else
ICM.lpVerb := NIL;
end;
finally
DestroyMenu(Popup);
end;
End;
Try
If Assigned(ContextMenu2) then
Result := Succeeded(ContextMenu2.InvokeCommand(PCMInvokeCommandInfo(@ICM)^))
Else
Result := Succeeded(ContextMenu.InvokeCommand(PCMInvokeCommandInfo(@ICM)^));
Except
// eat any dammned shell exceptions.
End;
End;
End; {ShellExecuteContextCommand (PIDL)}
Function ShellExecuteContextCommand(Handle: THandle; Command: String; FileName : String): Boolean;
Var ShellFolder : iShellFolder;
DirPIDL : PItemIDList;
DirPIDLFQ : PItemIDList;
ParentPIDL : PItemIDList;
Begin
DirPIDL := NIL;
DirPIDLFQ := NIL;
ParentPIDL := NIL;
Result := False;
DirPidlFQ := PIDL_GetFromPath(PChar(FileName));
IF Assigned(DirPIDLFQ) Then
Begin
PIDL_GetRelative(DirPIDLFQ, ParentPIDL, DirPIDL);
Try
If PIDL_GetFileFolder(ParentPIDL, ShellFolder) Then
Result := ShellExecuteContextCommand(Handle, Command, ShellFolder, 1, DirPIDL);
Finally
PIDL_Free(DirPIDL);
PIDL_Free(DirPIDLFQ);
PIDL_Free(ParentPIDL);
End;
End;
End; {ShellExecuteContextCommand (FileName)}
Function ShellExecuteContextCommand(Handle: THandle;
Command : String;
Path : String;
Files : TStringList) : Boolean;
Var ShellFolder : iShellFolder;
PathPIDL : PItemIDList;
PIDLArray : PPIDLArray;
Index : Integer;
i : Integer;
Begin
Result := False;
IF Files.Count = 0 Then
Exit;
Index := 0;
GetMem(PIDLArray, SizeOf(PItemIDList) * Files.Count);
FillChar(PIDLArray^, Sizeof(PItemIDList) * Files.Count, #0);
Try
PathPIDL := PIDL_GetFromPath(PChar(Path));
IF Assigned(PathPIDL) Then
Begin
Try
If PIDL_GetFileFolder(PathPIDL, ShellFolder) Then
Begin
For i := 0 To Files.Count - 1 Do
Begin
PIDLArray^[i] := PIDL_GetFromParentFolder(ShellFolder, PChar(Files[i]));
IF Assigned(PIDLArray^[i]) Then
INC(Index);
End;
IF Index > 0 Then
Begin
Try
Result := ShellExecuteContextCommand(Handle, Command, ShellFolder, Index, PIDLArray^[0]);
Finally
For i := 0 To Index - 1 Do
PIDL_Free(PIDLArray^[i]);
End;
End;
End;
Finally
PIDL_Free(PathPIDL);
End;
End;
Finally
FreeMem(PIDLArray);
End;
End; {ShellExecuteContextCommand (TStringList) }
End.