DragDropPIDL.pas 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  1. unit DragDropPIDL;
  2. // -----------------------------------------------------------------------------
  3. // Project: Drag and Drop Component Suite
  4. // Module: DragDropPIDL
  5. // Description: Implements Dragging & Dropping of PIDLs (files and folders).
  6. // Version: 4.0
  7. // Date: 18-MAY-2001
  8. // Target: Win32, Delphi 5-6
  9. // Authors: Anders Melander, anders@melander.dk, http://www.melander.dk
  10. // Copyright © 1997-2001 Angus Johnson & Anders Melander
  11. // -----------------------------------------------------------------------------
  12. interface
  13. uses
  14. DragDrop,
  15. DropTarget,
  16. DropSource,
  17. DragDropFormats,
  18. DragDropFile,
  19. Windows,
  20. ActiveX,
  21. Classes,
  22. ShlObj;
  23. {$include DragDrop.inc}
  24. type
  25. ////////////////////////////////////////////////////////////////////////////////
  26. //
  27. // TPIDLClipboardFormat
  28. //
  29. ////////////////////////////////////////////////////////////////////////////////
  30. // Supports the 'Shell IDList Array' format.
  31. ////////////////////////////////////////////////////////////////////////////////
  32. TPIDLClipboardFormat = class(TCustomSimpleClipboardFormat)
  33. private
  34. FPIDLs: TStrings; // Used internally to store PIDLs. We use strings to simplify cleanup.
  35. FFilenames: TStrings;
  36. protected
  37. function ReadData(Value: pointer; Size: integer): boolean; override;
  38. function WriteData(Value: pointer; Size: integer): boolean; override;
  39. function GetSize: integer; override;
  40. public
  41. constructor Create; override;
  42. destructor Destroy; override;
  43. function GetClipboardFormat: TClipFormat; override;
  44. procedure Clear; override;
  45. function HasData: boolean; override;
  46. property PIDLs: TStrings read FPIDLs;
  47. property Filenames: TStrings read FFilenames;
  48. end;
  49. type
  50. ////////////////////////////////////////////////////////////////////////////////
  51. //
  52. // TPIDLDataFormat
  53. //
  54. ////////////////////////////////////////////////////////////////////////////////
  55. TPIDLDataFormat = class(TCustomDataFormat)
  56. private
  57. FPIDLs : TStrings;
  58. FFilenames : TStrings;
  59. protected
  60. public
  61. constructor Create(AOwner: TDragDropComponent); override;
  62. destructor Destroy; override;
  63. function Assign(Source: TClipboardFormat): boolean; override;
  64. function AssignTo(Dest: TClipboardFormat): boolean; override;
  65. procedure Clear; override;
  66. function HasData: boolean; override;
  67. function NeedsData: boolean; override;
  68. property PIDLs: TStrings read FPIDLs;
  69. property Filenames: TStrings read FFilenames;
  70. end;
  71. type
  72. ////////////////////////////////////////////////////////////////////////////////
  73. //
  74. // TDropPIDLTarget
  75. //
  76. ////////////////////////////////////////////////////////////////////////////////
  77. TDropPIDLTarget = class(TCustomDropMultiTarget)
  78. private
  79. FPIDLDataFormat : TPIDLDataFormat;
  80. FFileMapDataFormat : TFileMapDataFormat;
  81. function GetFilenames: TStrings;
  82. protected
  83. function GetPIDLs: TStrings;
  84. function GetPIDLCount: integer;
  85. function GetMappedNames: TStrings;
  86. property PIDLs: TStrings read GetPIDLs;
  87. function DoGetPIDL(Index: integer): pItemIdList;
  88. function GetPreferredDropEffect: LongInt; override;
  89. public
  90. constructor Create(AOwner: TComponent); override;
  91. destructor Destroy; Override;
  92. // Note: It is the callers responsibility to cleanup
  93. // the returned PIDLs from the following 3 methods:
  94. // - GetFolderPidl
  95. // - GetRelativeFilePidl
  96. // - GetAbsoluteFilePidl
  97. // Use the CoTaskMemFree procedure to free the PIDLs.
  98. function GetFolderPIDL: pItemIdList;
  99. function GetRelativeFilePIDL(Index: integer): pItemIdList;
  100. function GetAbsoluteFilePIDL(Index: integer): pItemIdList;
  101. property PIDLCount: integer read GetPIDLCount; // Includes folder pidl in count
  102. // If you just want the filenames (not PIDLs) then use ...
  103. property Filenames: TStrings read GetFilenames;
  104. // MappedNames is only needed if files need to be renamed after a drag or
  105. // e.g. dragging from 'Recycle Bin'.
  106. property MappedNames: TStrings read GetMappedNames;
  107. end;
  108. ////////////////////////////////////////////////////////////////////////////////
  109. //
  110. // TDropPIDLSource
  111. //
  112. ////////////////////////////////////////////////////////////////////////////////
  113. TDropPIDLSource = class(TCustomDropMultiSource)
  114. private
  115. FPIDLDataFormat : TPIDLDataFormat;
  116. FFileMapDataFormat : TFileMapDataFormat;
  117. protected
  118. function GetMappedNames: TStrings;
  119. public
  120. constructor Create(AOwner: TComponent); override;
  121. destructor Destroy; override;
  122. procedure CopyFolderPIDLToList(pidl: PItemIDList);
  123. procedure CopyFilePIDLToList(pidl: PItemIDList);
  124. property MappedNames: TStrings read GetMappedNames;
  125. end;
  126. ////////////////////////////////////////////////////////////////////////////////
  127. //
  128. // PIDL utility functions
  129. //
  130. ////////////////////////////////////////////////////////////////////////////////
  131. //: GetPIDLsFromData extracts a PIDL list from a memory block and stores the
  132. // PIDLs in a string list.
  133. function GetPIDLsFromData(Data: pointer; Size: integer; PIDLs: TStrings): boolean;
  134. //: GetPIDLsFromHGlobal extracts a PIDL list from a global memory block and
  135. // stores the PIDLs in a string list.
  136. function GetPIDLsFromHGlobal(const HGlob: HGlobal; PIDLs: TStrings): boolean;
  137. //: GetPIDLsFromFilenames converts a list of files to PIDLs and stores the
  138. // PIDLs in a string list. All the PIDLs are relative to a common root.
  139. function GetPIDLsFromFilenames(const Files: TStrings; PIDLs: TStrings): boolean;
  140. //: GetRootFolderPIDL finds the PIDL of the folder which is the parent of a list
  141. // of files. The PIDl is returned as a string. If the files do not share a
  142. // common root, an empty string is returnde.
  143. function GetRootFolderPIDL(const Files: TStrings): string;
  144. //: GetFullPIDLFromPath converts a path (filename and path) to a folder/filename
  145. // PIDL pair.
  146. function GetFullPIDLFromPath(Path: string): pItemIDList;
  147. //: GetFullPathFromPIDL converts a folder/filename PIDL pair to a full path.
  148. function GetFullPathFromPIDL(PIDL: pItemIDList): string;
  149. //: PIDLToString converts a single PIDL to a string.
  150. function PIDLToString(pidl: PItemIDList): string;
  151. //: StringToPIDL converts a PIDL string to a PIDL.
  152. function StringToPIDL(const PIDL: string): PItemIDList;
  153. //: JoinPIDLStrings merges two PIDL strings into one.
  154. function JoinPIDLStrings(pidl1, pidl2: string): string;
  155. //: ConvertFilesToShellIDList converts a list of files to a PIDL list. The
  156. // files are relative to the folder specified by the Path parameter. The PIDLs
  157. // are returned as a global memory handle.
  158. function ConvertFilesToShellIDList(Path: string; Files: TStrings): HGlobal;
  159. //: GetSizeOfPIDL calculates the size of a PIDL list.
  160. function GetSizeOfPIDL(PIDL: pItemIDList): integer;
  161. //: CopyPIDL makes a copy of a PIDL.
  162. // It is the callers responsibility to free the returned PIDL.
  163. function CopyPIDL(PIDL: pItemIDList): pItemIDList;
  164. {$ifndef BCB}
  165. // Undocumented PIDL utility functions...
  166. // From http://www.geocities.com/SiliconValley/4942/
  167. function ILCombine(pidl1,pidl2:PItemIDList): PItemIDList; stdcall;
  168. function ILFindLastID(pidl: PItemIDList): PItemIDList; stdcall;
  169. function ILClone(pidl: PItemIDList): PItemIDList; stdcall;
  170. function ILRemoveLastID(pidl: PItemIDList): LongBool; stdcall;
  171. function ILIsEqual(pidl1,pidl2: PItemIDList): LongBool; stdcall;
  172. procedure ILFree(Buffer: PItemIDList); stdcall;
  173. // Undocumented IMalloc utility functions...
  174. function SHAlloc(BufferSize: ULONG): Pointer; stdcall;
  175. procedure SHFree(Buffer: Pointer); stdcall;
  176. {$endif}
  177. ////////////////////////////////////////////////////////////////////////////////
  178. //
  179. // PIDL/IShellFolder utility functions
  180. //
  181. ////////////////////////////////////////////////////////////////////////////////
  182. //: GetShellFolderOfPath retrieves an IShellFolder interface which can be used
  183. // to manage the specified folder.
  184. function GetShellFolderOfPath(FolderPath: string): IShellFolder;
  185. //: GetPIDLDisplayName retrieves the display name of the specified PIDL,
  186. // relative to the specified folder.
  187. function GetPIDLDisplayName(Folder: IShellFolder; PIDL: PItemIdList): string;
  188. //: GetSubPIDL retrieves the PIDL of the specified file or folder to a PIDL.
  189. // The PIDL is relative to the folder specified by the Folder parameter.
  190. function GetSubPIDL(Folder: IShellFolder; Sub: string): pItemIDList;
  191. ////////////////////////////////////////////////////////////////////////////////
  192. //
  193. // Component registration
  194. //
  195. ////////////////////////////////////////////////////////////////////////////////
  196. procedure Register;
  197. implementation
  198. uses
  199. ShellAPI,
  200. SysUtils;
  201. resourcestring
  202. sNoFolderPIDL = 'Folder PIDL must be added first';
  203. ////////////////////////////////////////////////////////////////////////////////
  204. //
  205. // Component registration
  206. //
  207. ////////////////////////////////////////////////////////////////////////////////
  208. procedure Register;
  209. begin
  210. RegisterComponents(DragDropComponentPalettePage, [TDropPIDLTarget,
  211. TDropPIDLSource]);
  212. end;
  213. ////////////////////////////////////////////////////////////////////////////////
  214. //
  215. // PIDL utility functions
  216. //
  217. ////////////////////////////////////////////////////////////////////////////////
  218. function GetPIDLsFromData(Data: pointer; Size: integer; PIDLs: TStrings): boolean;
  219. var
  220. i : integer;
  221. pOffset : ^UINT;
  222. PIDL : PItemIDList;
  223. begin
  224. PIDLs.Clear;
  225. Result := (Data <> nil) and
  226. (Size >= integer(PIDA(Data)^.cidl) * (SizeOf(UINT)+SizeOf(PItemIDList)) + SizeOf(UINT));
  227. if (not Result) then
  228. exit;
  229. pOffset := @(PIDA(Data)^.aoffset[0]);
  230. i := PIDA(Data)^.cidl; // Note: Count doesn't include folder PIDL
  231. while (i >= 0) do
  232. begin
  233. PIDL := PItemIDList(UINT(Data)+ pOffset^);
  234. PIDLs.Add(PIDLToString(PIDL));
  235. inc(pOffset);
  236. dec(i);
  237. end;
  238. Result := (PIDLs.Count > 1);
  239. end;
  240. function GetPIDLsFromHGlobal(const HGlob: HGlobal; PIDLs: TStrings): boolean;
  241. var
  242. pCIDA : PIDA;
  243. begin
  244. pCIDA := PIDA(GlobalLock(HGlob));
  245. try
  246. Result := GetPIDLsFromData(pCIDA, GlobalSize(HGlob), PIDLs);
  247. finally
  248. GlobalUnlock(HGlob);
  249. end;
  250. end;
  251. resourcestring
  252. sBadDesktop = 'Failed to get interface to Desktop';
  253. sBadFilename = 'Invalid filename: %s';
  254. (*
  255. ** Find the folder which is the parent of all the files in a list.
  256. *)
  257. function GetRootFolderPIDL(const Files: TStrings): string;
  258. var
  259. DeskTopFolder: IShellFolder;
  260. WidePath: WideString;
  261. PIDL: pItemIDList;
  262. PIDLs: TStrings;
  263. s: string;
  264. PIDL1, PIDL2: pItemIDList;
  265. Size, MaxSize: integer;
  266. i: integer;
  267. begin
  268. Result := '';
  269. if (Files.Count = 0) then
  270. exit;
  271. if (SHGetDesktopFolder(DeskTopFolder) <> NOERROR) then
  272. raise Exception.Create(sBadDesktop);
  273. PIDLs := TStringList.Create;
  274. try
  275. // First convert all paths to PIDLs.
  276. for i := 0 to Files.Count-1 do
  277. begin
  278. WidePath := ExtractFilePath(Files[i]);
  279. if (DesktopFolder.ParseDisplayName(0, nil, PWideChar(WidePath), PULONG(nil)^,
  280. PIDL, PULONG(nil)^) <> NOERROR) then
  281. raise Exception.Create(sBadFilename);
  282. try
  283. PIDLs.Add(PIDLToString(PIDL));
  284. finally
  285. coTaskMemFree(PIDL);
  286. end;
  287. end;
  288. Result := PIDLs[0];
  289. MaxSize := Length(Result)-SizeOf(Word);
  290. PIDL := pItemIDList(PChar(Result));
  291. for i := 1 to PIDLs.Count-1 do
  292. begin
  293. s := PIDLs[1];
  294. PIDL1 := PIDL;
  295. PIDL2 := pItemIDList(PChar(s));
  296. Size := 0;
  297. while (Size < MaxSize) and (PIDL1^.mkid.cb <> 0) and (PIDL1^.mkid.cb = PIDL2^.mkid.cb) and (CompareMem(PIDL1, PIDL2, PIDL1^.mkid.cb)) do
  298. begin
  299. inc(Size, PIDL1^.mkid.cb);
  300. inc(integer(PIDL2), PIDL1^.mkid.cb);
  301. inc(integer(PIDL1), PIDL1^.mkid.cb);
  302. end;
  303. if (Size <> MaxSize) then
  304. begin
  305. MaxSize := Size;
  306. SetLength(Result, Size+SizeOf(Word));
  307. PIDL1^.mkid.cb := 0;
  308. end;
  309. if (Size = 0) then
  310. break;
  311. end;
  312. finally
  313. PIDLs.Free;
  314. end;
  315. end;
  316. function GetPIDLsFromFilenames(const Files: TStrings; PIDLs: TStrings): boolean;
  317. var
  318. RootPIDL: string;
  319. i: integer;
  320. PIDL: pItemIdList;
  321. FilePIDL: string;
  322. begin
  323. Result := False;
  324. PIDLs.Clear;
  325. if (Files.Count = 0) then
  326. exit;
  327. // Get the PIDL of the root folder...
  328. // All the file PIDLs will be relative to this PIDL
  329. RootPIDL := GetRootFolderPIDL(Files);
  330. if (RootPIDL = '') then
  331. exit;
  332. Result := True;
  333. PIDLS.Add(RootPIDL);
  334. // Add the file PIDLs (all relative to the root)...
  335. for i := 0 to Files.Count-1 do
  336. begin
  337. PIDL := GetFullPIDLFromPath(Files[i]);
  338. if (PIDL = nil) then
  339. begin
  340. Result := False;
  341. PIDLs.Clear;
  342. break;
  343. end;
  344. try
  345. FilePIDL := PIDLToString(PIDL);
  346. finally
  347. coTaskMemFree(PIDL);
  348. end;
  349. // Remove the root PIDL from the file PIDL making it relative to the root.
  350. PIDLS.Add(copy(FilePIDL, Length(RootPIDL)-SizeOf(Word)+1,
  351. Length(FilePIDL)-(Length(RootPIDL)-SizeOf(Word))));
  352. end;
  353. end;
  354. function GetSizeOfPIDL(PIDL: pItemIDList): integer;
  355. var
  356. Size: integer;
  357. begin
  358. if (PIDL <> nil) then
  359. begin
  360. Result := SizeOf(PIDL^.mkid.cb);
  361. repeat
  362. Size := PIDL^.mkid.cb;
  363. inc(Result, Size);
  364. inc(integer(PIDL), Size);
  365. until (Size = 0);
  366. end else
  367. Result := 0;
  368. end;
  369. function CopyPIDL(PIDL: pItemIDList): pItemIDList;
  370. var
  371. Size: integer;
  372. begin
  373. Size := GetSizeOfPIDL(PIDL);
  374. if (Size > 0) then
  375. begin
  376. Result := ShellMalloc.Alloc(Size);
  377. if (Result <> nil) then
  378. Move(PIDL^, Result^, Size);
  379. end else
  380. Result := nil;
  381. end;
  382. function GetFullPIDLFromPath(Path: string): pItemIDList;
  383. var
  384. DeskTopFolder : IShellFolder;
  385. WidePath : WideString;
  386. begin
  387. WidePath := Path;
  388. if (SHGetDesktopFolder(DeskTopFolder) = NOERROR) then
  389. begin
  390. if (DesktopFolder.ParseDisplayName(0, nil, PWideChar(WidePath), PULONG(nil)^,
  391. Result, PULONG(nil)^) <> NOERROR) then
  392. Result := nil;
  393. end else
  394. Result := nil;
  395. end;
  396. function GetFullPathFromPIDL(PIDL: pItemIDList): string;
  397. var
  398. Path: array[0..MAX_PATH] of char;
  399. begin
  400. if SHGetPathFromIDList(PIDL, Path) then
  401. Result := Path
  402. else
  403. Result := '';
  404. end;
  405. // See "Clipboard Formats for Shell Data Transfers" in Ole.hlp...
  406. // (Needed to drag links (shortcuts).)
  407. type
  408. POffsets = ^TOffsets;
  409. TOffsets = array[0..$FFFF] of UINT;
  410. function ConvertFilesToShellIDList(Path: string; Files: TStrings): HGlobal;
  411. var
  412. shf: IShellFolder;
  413. PathPidl, pidl: pItemIDList;
  414. Ida: PIDA;
  415. pOffset: POffsets;
  416. ptrByte: ^Byte;
  417. i, PathPidlSize, IdaSize, PreviousPidlSize: integer;
  418. begin
  419. Result := 0;
  420. shf := GetShellFolderOfPath(path);
  421. if shf = nil then
  422. exit;
  423. // Calculate size of IDA structure ...
  424. // cidl: UINT ; Directory pidl
  425. // offset: UINT ; all file pidl offsets
  426. IdaSize := (Files.Count + 2) * SizeOf(UINT);
  427. PathPidl := GetFullPIDLFromPath(path);
  428. if PathPidl = nil then
  429. exit;
  430. try
  431. PathPidlSize := GetSizeOfPidl(PathPidl);
  432. //Add to IdaSize space for ALL pidls...
  433. IdaSize := IdaSize + PathPidlSize;
  434. for i := 0 to Files.Count-1 do
  435. begin
  436. pidl := GetSubPidl(shf, files[i]);
  437. try
  438. IdaSize := IdaSize + GetSizeOfPidl(Pidl);
  439. finally
  440. ShellMalloc.Free(pidl);
  441. end;
  442. end;
  443. //Allocate memory...
  444. Result := GlobalAlloc(GMEM_SHARE or GMEM_ZEROINIT, IdaSize);
  445. if (Result = 0) then
  446. exit;
  447. try
  448. Ida := GlobalLock(Result);
  449. try
  450. FillChar(Ida^, IdaSize, 0);
  451. //Fill in offset and pidl data...
  452. Ida^.cidl := Files.Count; //cidl = file count
  453. pOffset := POffsets(@(Ida^.aoffset));
  454. pOffset^[0] := (Files.Count+2) * sizeof(UINT); //offset of Path pidl
  455. ptrByte := pointer(Ida);
  456. inc(ptrByte, pOffset^[0]); //ptrByte now points to Path pidl
  457. Move(PathPidl^, ptrByte^, PathPidlSize); //copy path pidl
  458. PreviousPidlSize := PathPidlSize;
  459. for i := 1 to Files.Count do
  460. begin
  461. pidl := GetSubPidl(shf,files[i-1]);
  462. try
  463. pOffset^[i] := pOffset^[i-1] + UINT(PreviousPidlSize); //offset of pidl
  464. PreviousPidlSize := GetSizeOfPidl(Pidl);
  465. ptrByte := pointer(Ida);
  466. inc(ptrByte, pOffset^[i]); //ptrByte now points to current file pidl
  467. Move(Pidl^, ptrByte^, PreviousPidlSize); //copy file pidl
  468. //PreviousPidlSize = current pidl size here
  469. finally
  470. ShellMalloc.Free(pidl);
  471. end;
  472. end;
  473. finally
  474. GlobalUnLock(Result);
  475. end;
  476. except
  477. GlobalFree(Result);
  478. raise;
  479. end;
  480. finally
  481. ShellMalloc.Free(PathPidl);
  482. end;
  483. end;
  484. function PIDLToString(pidl: PItemIDList): String;
  485. var
  486. PidlLength : integer;
  487. begin
  488. PidlLength := GetSizeOfPidl(pidl);
  489. SetLength(Result, PidlLength);
  490. Move(pidl^, PChar(Result)^, PidlLength);
  491. end;
  492. function StringToPIDL(const PIDL: string): PItemIDList;
  493. begin
  494. Result := ShellMalloc.Alloc(Length(PIDL));
  495. if (Result <> nil) then
  496. Move(PChar(PIDL)^, Result^, Length(PIDL));
  497. end;
  498. function JoinPIDLStrings(pidl1, pidl2: string): String;
  499. var
  500. PidlLength : integer;
  501. begin
  502. if Length(pidl1) <= 2 then
  503. PidlLength := 0
  504. else
  505. PidlLength := Length(pidl1)-2;
  506. SetLength(Result, PidlLength + Length(pidl2));
  507. if PidlLength > 0 then
  508. Move(PChar(pidl1)^, PChar(Result)^, PidlLength);
  509. Move(PChar(pidl2)^, Result[PidlLength+1], Length(pidl2));
  510. end;
  511. {$ifndef BCB}
  512. // BCB appearantly doesn't support ordinal DLL imports. Strange!
  513. function ILCombine(pidl1,pidl2:PItemIDList): PItemIDList; stdcall;
  514. external shell32 index 25;
  515. function ILFindLastID(pidl: PItemIDList): PItemIDList; stdcall;
  516. external shell32 index 16;
  517. function ILClone(pidl: PItemIDList): PItemIDList; stdcall;
  518. external shell32 index 18;
  519. function ILRemoveLastID(pidl: PItemIDList): LongBool; stdcall;
  520. external shell32 index 17;
  521. function ILIsEqual(pidl1,pidl2: PItemIDList): LongBool; stdcall;
  522. external shell32 index 21;
  523. procedure ILFree(Buffer: PItemIDList); stdcall;
  524. external shell32 index 155;
  525. function SHAlloc(BufferSize: ULONG): Pointer; stdcall;
  526. external shell32 index 196;
  527. procedure SHFree(Buffer: Pointer); stdcall;
  528. external shell32 index 195;
  529. {$endif}
  530. ////////////////////////////////////////////////////////////////////////////////
  531. //
  532. // PIDL/IShellFolder utility functions
  533. //
  534. ////////////////////////////////////////////////////////////////////////////////
  535. function GetShellFolderOfPath(FolderPath: string): IShellFolder;
  536. var
  537. DeskTopFolder: IShellFolder;
  538. PathPidl: pItemIDList;
  539. WidePath: WideString;
  540. pdwAttributes: ULONG;
  541. begin
  542. Result := nil;
  543. WidePath := FolderPath;
  544. pdwAttributes := SFGAO_FOLDER;
  545. if (SHGetDesktopFolder(DeskTopFolder) <> NOERROR) then
  546. exit;
  547. if (DesktopFolder.ParseDisplayName(0, nil, PWideChar(WidePath), PULONG(nil)^,
  548. PathPidl, pdwAttributes) = NOERROR) then
  549. try
  550. if (pdwAttributes and SFGAO_FOLDER <> 0) then
  551. DesktopFolder.BindToObject(PathPidl, nil, IID_IShellFolder,
  552. // Note: For Delphi 4 and prior, the ppvOut parameter must be a pointer.
  553. pointer(Result));
  554. finally
  555. ShellMalloc.Free(PathPidl);
  556. end;
  557. end;
  558. function GetSubPIDL(Folder: IShellFolder; Sub: string): pItemIDList;
  559. var
  560. WidePath: WideString;
  561. begin
  562. WidePath := Sub;
  563. Folder.ParseDisplayName(0, nil, PWideChar(WidePath), PULONG(nil)^, Result,
  564. PULONG(nil)^);
  565. end;
  566. function GetPIDLDisplayName(Folder: IShellFolder; PIDL: PItemIdList): string;
  567. var
  568. StrRet: TStrRet;
  569. begin
  570. Result := '';
  571. Folder.GetDisplayNameOf(PIDL, 0, StrRet);
  572. case StrRet.uType of
  573. STRRET_WSTR: Result := WideCharToString(StrRet.pOleStr);
  574. STRRET_OFFSET: Result := PChar(UINT(PIDL)+StrRet.uOffset);
  575. STRRET_CSTR: Result := StrRet.cStr;
  576. end;
  577. end;
  578. ////////////////////////////////////////////////////////////////////////////////
  579. //
  580. // TPIDLsToFilenamesStrings
  581. //
  582. ////////////////////////////////////////////////////////////////////////////////
  583. // Used internally to convert PIDLs to filenames on-demand.
  584. ////////////////////////////////////////////////////////////////////////////////
  585. type
  586. TPIDLsToFilenamesStrings = class(TStrings)
  587. private
  588. FPIDLs: TStrings;
  589. protected
  590. function Get(Index: Integer): string; override;
  591. function GetCount: Integer; override;
  592. procedure Put(Index: Integer; const S: string); override;
  593. procedure PutObject(Index: Integer; AObject: TObject); override;
  594. public
  595. constructor Create(APIDLs: TStrings);
  596. procedure Clear; override;
  597. procedure Delete(Index: Integer); override;
  598. procedure Insert(Index: Integer; const S: string); override;
  599. procedure Assign(Source: TPersistent); override;
  600. end;
  601. constructor TPIDLsToFilenamesStrings.Create(APIDLs: TStrings);
  602. begin
  603. inherited Create;
  604. FPIDLs := APIDLs;
  605. end;
  606. function TPIDLsToFilenamesStrings.Get(Index: Integer): string;
  607. var
  608. PIDL: string;
  609. Path: array [0..MAX_PATH] of char;
  610. begin
  611. if (Index < 0) or (Index > FPIDLs.Count-2) then
  612. raise Exception.create('Filename index out of range');
  613. PIDL := JoinPIDLStrings(FPIDLs[0], FPIDLs[Index+1]);
  614. if SHGetPathFromIDList(PItemIDList(PChar(PIDL)), Path) then
  615. Result := Path
  616. else
  617. Result := '';
  618. end;
  619. function TPIDLsToFilenamesStrings.GetCount: Integer;
  620. begin
  621. if FPIDLs.Count < 2 then
  622. Result := 0
  623. else
  624. Result := FPIDLs.Count-1;
  625. end;
  626. procedure TPIDLsToFilenamesStrings.Assign(Source: TPersistent);
  627. begin
  628. if Source is TStrings then
  629. begin
  630. BeginUpdate;
  631. try
  632. GetPIDLsFromFilenames(TStrings(Source), FPIDLs);
  633. finally
  634. EndUpdate;
  635. end;
  636. end else
  637. inherited Assign(Source);
  638. end;
  639. // Inherited abstract methods which do not need implementation...
  640. procedure TPIDLsToFilenamesStrings.Put(Index: Integer; const S: string);
  641. begin
  642. end;
  643. procedure TPIDLsToFilenamesStrings.PutObject(Index: Integer; AObject: TObject);
  644. begin
  645. end;
  646. procedure TPIDLsToFilenamesStrings.Clear;
  647. begin
  648. end;
  649. procedure TPIDLsToFilenamesStrings.Delete(Index: Integer);
  650. begin
  651. end;
  652. procedure TPIDLsToFilenamesStrings.Insert(Index: Integer; const S: string);
  653. begin
  654. end;
  655. ////////////////////////////////////////////////////////////////////////////////
  656. //
  657. // TPIDLClipboardFormat
  658. //
  659. ////////////////////////////////////////////////////////////////////////////////
  660. constructor TPIDLClipboardFormat.Create;
  661. begin
  662. inherited Create;
  663. FPIDLs := TStringList.Create;
  664. FFilenames := TPIDLsToFilenamesStrings.Create(FPIDLs);
  665. end;
  666. destructor TPIDLClipboardFormat.Destroy;
  667. begin
  668. FFilenames.Free;
  669. FPIDLs.Free;
  670. inherited Destroy;
  671. end;
  672. var
  673. CF_IDLIST: TClipFormat = 0;
  674. function TPIDLClipboardFormat.GetClipboardFormat: TClipFormat;
  675. begin
  676. if (CF_IDLIST = 0) then
  677. CF_IDLIST := RegisterClipboardFormat(CFSTR_SHELLIDLIST);
  678. Result := CF_IDLIST;
  679. end;
  680. procedure TPIDLClipboardFormat.Clear;
  681. begin
  682. FPIDLs.Clear;
  683. end;
  684. function TPIDLClipboardFormat.HasData: boolean;
  685. begin
  686. Result := (FPIDLs.Count > 0);
  687. end;
  688. function TPIDLClipboardFormat.GetSize: integer;
  689. var
  690. i : integer;
  691. begin
  692. Result := (FPIDLs.Count+1) * SizeOf(UINT);
  693. for i := 0 to FPIDLs.Count-1 do
  694. inc(Result, Length(FPIDLs[i]));
  695. end;
  696. function TPIDLClipboardFormat.ReadData(Value: pointer;
  697. Size: integer): boolean;
  698. begin
  699. Result := GetPIDLsFromData(Value, Size, FPIDLs);
  700. end;
  701. function TPIDLClipboardFormat.WriteData(Value: pointer;
  702. Size: integer): boolean;
  703. var
  704. i : integer;
  705. pCIDA : PIDA;
  706. Offset : integer;
  707. pOffset : ^UINT;
  708. PIDL : PItemIDList;
  709. begin
  710. pCIDA := PIDA(Value);
  711. pCIDA^.cidl := FPIDLs.Count-1; // Don't count folder PIDL
  712. pOffset := @(pCIDA^.aoffset[0]); // Points to aoffset[0]
  713. Offset := (FPIDLs.Count+1)*SizeOf(UINT); // Size of CIDA structure
  714. PIDL := PItemIDList(integer(pCIDA) + Offset); // PIDLs are stored after CIDA structure.
  715. for i := 0 to FPIDLs.Count-1 do
  716. begin
  717. pOffset^ := Offset; // Store relative offset of PIDL into aoffset[i]
  718. // Copy the PIDL
  719. Move(PChar(FPIDLs[i])^, PIDL^, length(FPIDLs[i]));
  720. // Move on to next PIDL
  721. inc(Offset, length(FPIDLs[i]));
  722. inc(pOffset);
  723. inc(integer(PIDL), length(FPIDLs[i]));
  724. end;
  725. Result := True;
  726. end;
  727. ////////////////////////////////////////////////////////////////////////////////
  728. //
  729. // TPIDLDataFormat
  730. //
  731. ////////////////////////////////////////////////////////////////////////////////
  732. constructor TPIDLDataFormat.Create(AOwner: TDragDropComponent);
  733. begin
  734. inherited Create(AOwner);
  735. FPIDLs := TStringList.Create;
  736. TStringList(FPIDLs).OnChanging := DoOnChanging;
  737. FFilenames := TPIDLsToFilenamesStrings.Create(FPIDLs);
  738. end;
  739. destructor TPIDLDataFormat.Destroy;
  740. begin
  741. FFilenames.Free;
  742. FPIDLs.Free;
  743. inherited Destroy;
  744. end;
  745. function TPIDLDataFormat.Assign(Source: TClipboardFormat): boolean;
  746. begin
  747. Result := True;
  748. if (Source is TPIDLClipboardFormat) then
  749. FPIDLs.Assign(TPIDLClipboardFormat(Source).PIDLs)
  750. else if (Source is TFileClipboardFormat) then
  751. Result := GetPIDLsFromFilenames(TFileClipboardFormat(Source).Files, FPIDLs)
  752. else
  753. Result := inherited Assign(Source);
  754. end;
  755. function TPIDLDataFormat.AssignTo(Dest: TClipboardFormat): boolean;
  756. begin
  757. Result := True;
  758. if (Dest is TPIDLClipboardFormat) then
  759. TPIDLClipboardFormat(Dest).PIDLs.Assign(FPIDLs)
  760. else if (Dest is TFileClipboardFormat) then
  761. TFileClipboardFormat(Dest).Files.Assign(Filenames)
  762. else
  763. Result := inherited Assign(Dest);
  764. end;
  765. procedure TPIDLDataFormat.Clear;
  766. begin
  767. FPIDLs.Clear;
  768. end;
  769. function TPIDLDataFormat.HasData: boolean;
  770. begin
  771. Result := (FPIDLs.Count > 0);
  772. end;
  773. function TPIDLDataFormat.NeedsData: boolean;
  774. begin
  775. Result := (FPIDLs.Count = 0);
  776. end;
  777. ////////////////////////////////////////////////////////////////////////////////
  778. //
  779. // TDropPIDLTarget
  780. //
  781. ////////////////////////////////////////////////////////////////////////////////
  782. constructor TDropPIDLTarget.Create(AOwner: TComponent);
  783. begin
  784. inherited Create(AOwner);
  785. FPIDLDataFormat := TPIDLDataFormat.Create(Self);
  786. FFileMapDataFormat := TFileMapDataFormat.Create(Self);
  787. end;
  788. destructor TDropPIDLTarget.Destroy;
  789. begin
  790. FPIDLDataFormat.Free;
  791. FFileMapDataFormat.Free;
  792. inherited Destroy;
  793. end;
  794. function TDropPIDLTarget.GetPIDLs: TStrings;
  795. begin
  796. Result := FPIDLDataFormat.PIDLs;
  797. end;
  798. function TDropPIDLTarget.DoGetPIDL(Index: integer): pItemIdList;
  799. var
  800. PIDL : string;
  801. begin
  802. PIDL := PIDLs[Index];
  803. Result := ShellMalloc.Alloc(Length(PIDL));
  804. if (Result <> nil) then
  805. Move(PChar(PIDL)^, Result^, Length(PIDL));
  806. end;
  807. function TDropPIDLTarget.GetFolderPidl: pItemIdList;
  808. begin
  809. Result := DoGetPIDL(0);
  810. end;
  811. function TDropPIDLTarget.GetRelativeFilePidl(Index: integer): pItemIdList;
  812. begin
  813. Result := nil;
  814. if (index < 1) then
  815. exit;
  816. Result := DoGetPIDL(Index);
  817. end;
  818. function TDropPIDLTarget.GetAbsoluteFilePidl(Index: integer): pItemIdList;
  819. var
  820. PIDL : string;
  821. begin
  822. Result := nil;
  823. if (index < 1) then
  824. exit;
  825. PIDL := JoinPIDLStrings(PIDLs[0], PIDLs[Index]);
  826. Result := ShellMalloc.Alloc(Length(PIDL));
  827. if (Result <> nil) then
  828. Move(PChar(PIDL)^, Result^, Length(PIDL));
  829. end;
  830. function TDropPIDLTarget.GetPIDLCount: integer;
  831. begin
  832. // Note: Includes folder PIDL in count!
  833. Result := FPIDLDataFormat.PIDLs.Count;
  834. end;
  835. function TDropPIDLTarget.GetFilenames: TStrings;
  836. begin
  837. Result := FPIDLDataFormat.Filenames;
  838. end;
  839. function TDropPIDLTarget.GetMappedNames: TStrings;
  840. begin
  841. Result := FFileMapDataFormat.FileMaps;
  842. end;
  843. function TDropPIDLTarget.GetPreferredDropEffect: LongInt;
  844. begin
  845. Result := inherited GetPreferredDropEffect;
  846. if (Result = DROPEFFECT_NONE) then
  847. Result := DROPEFFECT_COPY;
  848. end;
  849. ////////////////////////////////////////////////////////////////////////////////
  850. //
  851. // TDropPIDLSource
  852. //
  853. ////////////////////////////////////////////////////////////////////////////////
  854. constructor TDropPIDLSource.Create(AOwner: TComponent);
  855. begin
  856. inherited Create(AOwner);
  857. FPIDLDataFormat := TPIDLDataFormat.Create(Self);
  858. FFileMapDataFormat := TFileMapDataFormat.Create(Self);
  859. end;
  860. destructor TDropPIDLSource.Destroy;
  861. begin
  862. FPIDLDataFormat.Free;
  863. FFileMapDataFormat.Free;
  864. inherited Destroy;
  865. end;
  866. procedure TDropPIDLSource.CopyFolderPIDLToList(pidl: PItemIDList);
  867. begin
  868. //Note: Once the PIDL has been copied into the list it can be 'freed'.
  869. FPIDLDataFormat.Clear;
  870. FFileMapDataFormat.Clear;
  871. FPIDLDataFormat.PIDLs.Add(PIDLToString(pidl));
  872. end;
  873. procedure TDropPIDLSource.CopyFilePIDLToList(pidl: PItemIDList);
  874. begin
  875. // Note: Once the PIDL has been copied into the list it can be 'freed'.
  876. // Make sure that folder pidl has been added.
  877. if (FPIDLDataFormat.PIDLs.Count < 1) then
  878. raise Exception.Create(sNoFolderPIDL);
  879. FPIDLDataFormat.PIDLs.Add(PIDLToString(pidl));
  880. end;
  881. function TDropPIDLSource.GetMappedNames: TStrings;
  882. begin
  883. Result := FFileMapDataFormat.FileMaps;
  884. end;
  885. ////////////////////////////////////////////////////////////////////////////////
  886. //
  887. // Initialization/Finalization
  888. //
  889. ////////////////////////////////////////////////////////////////////////////////
  890. initialization
  891. // Data format registration
  892. TPIDLDataFormat.RegisterDataFormat;
  893. // Clipboard format registration
  894. TPIDLDataFormat.RegisterCompatibleFormat(TPIDLClipboardFormat, 0, csSourceTarget, [ddRead]);
  895. TPIDLDataFormat.RegisterCompatibleFormat(TFileClipboardFormat, 1, csSourceTarget, [ddRead]);
  896. finalization
  897. TPIDLDataFormat.UnregisterDataFormat;
  898. end.