{ DRAGDROP.PAS -- simple realization of OLE drag and drop. Author: Jim Mischel Last modification date: 30/05/97 Add some changes for compatibility with FPC/Lazarus Copyright (C) 2009 Alexander Koblov (Alexx2000@mail.ru) Some inspiration for drag-and-drop using CF_FILEGROUPDESCRIPTORW and CFU_FILECONTENTS: -http://msdn.microsoft.com/en-us/library/windows/desktop/bb776904%28v=vs.85%29.aspx#filecontents -http://www.unitoops.com/uoole/examples/outlooktest.htm } unit uOleDragDrop; {$mode delphi}{$H+} interface uses DCBasicTypes, Windows, ActiveX, Classes, Controls, uDragDropEx; type { IEnumFormatEtc } TEnumFormatEtc = class(TInterfacedObject, IEnumFormatEtc) private FIndex: Integer; public constructor Create(Index: Integer = 0); function Next(celt: LongWord; out elt: FormatEtc; pceltFetched: pULong): HResult; stdcall; function Skip(celt: LongWord): HResult; stdcall; function Reset: HResult; stdcall; function Clone(out enum: IEnumFormatEtc): HResult; stdcall; end; { TDragDropInfo } TDragDropInfo = class(TObject) private FFileList: TStringList; FPreferredWinDropEffect: DWORD; function CreateHDrop(bUnicode: Boolean): HGlobal; function CreateFileNames(bUnicode: Boolean): HGlobal; function CreateURIs(bUnicode: Boolean): HGlobal; function CreateShellIdListArray: HGlobal; function MakeHGlobal(ptr: Pointer; Size: LongWord): HGlobal; public constructor Create(PreferredWinDropEffect: DWORD); destructor Destroy; override; procedure Add(const s: string); function MakeDataInFormat(const formatEtc: TFormatEtc): HGlobal; function CreatePreferredDropEffect(WinDropEffect: DWORD): HGlobal; property Files: TStringList Read FFileList; end; TDragDropTargetWindows = class; // forward declaration { TFileDropTarget знает, как принимать сброшенные файлы } TFileDropTarget = class(TInterfacedObject, IDropTarget) private FHandle: HWND; FReleased: Boolean; FDragDropTarget: TDragDropTargetWindows; public constructor Create(DragDropTarget: TDragDropTargetWindows); {en Unregisters drag&drop target and releases the object (it is destroyed). This is the function that should be called to cleanup the object instead of Free. Do not use the object after calling it. } procedure FinalRelease; function DragEnter(const {%H-}dataObj: IDataObject; grfKeyState: LongWord; pt: TPoint; var dwEffect: LongWord): HResult; stdcall; function DragOver(grfKeyState: LongWord; pt: TPoint; var dwEffect: LongWord): HResult; stdcall; function DragLeave: HResult; stdcall; function Drop(const dataObj: IDataObject; grfKeyState: LongWord; pt: TPoint; var dwEffect: LongWord): HResult; stdcall; {en Retrieves the filenames from the HDROP format as a list of UTF-8 strings. @returns(List of filenames or nil in case of an error.) } class function GetDropFilenames(hDropData: HDROP): TStringList; {en Retrieves the filenames from the CFU_FILEGROUPDESCRIPTORW/CFU_FILEGROUPDESCRIPTOR format as a list of UTF-8 strings. @returns(List of filenames or nil in case of an error.) } function GetDropFileGroupFilenames(const dataObj: IDataObject; var Medium: TSTGMedium; Format: TFormatETC): TStringList; function SaveCfuContentToFile(const dataObj:IDataObject; Index:Integer; WantedFilename:String; WantedCreationTime, WantedModificationTime, WantedLastAccessTime:DCBasicTypes.TFileTime):boolean; {en Retrieves the text from the CF_UNICODETEXT/CF_TEXT format, will store this in a single file return filename as a list of a single UTF-8 string. @returns(List of filenames or nil in case of an error.) } function GetDropTextCreatedFilenames(var Medium: TSTGMedium; Format: TFormatETC): TStringList; end; { TFileDropSource - источник для перетаскивания файлов } TFileDropSource = class(TInterfacedObject, IDropSource) constructor Create; {$IF FPC_FULLVERSION < 020601} function QueryContinueDrag(fEscapePressed: BOOL; grfKeyState: longint): HResult; stdcall; {$ELSE} function QueryContinueDrag(fEscapePressed: BOOL; grfKeyState: DWORD): HResult; stdcall; {$ENDIF} {$IF FPC_FULLVERSION < 020601} function GiveFeedback(dwEffect: longint): HResult; stdcall; {$ELSE} function GiveFeedback(dwEffect: DWORD): HResult; stdcall; {$ENDIF} end; { THDropDataObject - объект данных с информацией о перетаскиваемых файлах } THDropDataObject = class(TInterfacedObject, IDataObject) private FDropInfo: TDragDropInfo; public constructor Create(PreferredWinDropEffect: DWORD); destructor Destroy; override; procedure Add(const s: string); { из IDataObject } function GetData(const formatetcIn: TFormatEtc; out medium: TStgMedium): HResult; stdcall; function GetDataHere(const formatetc: TFormatEtc; out medium: TStgMedium): HResult; stdcall; function QueryGetData(const formatetc: TFormatEtc): HResult; stdcall; function GetCanonicalFormatEtc(const formatetc: TFormatEtc; out formatetcOut: TFormatEtc): HResult; stdcall; function SetData(const formatetc: TFormatEtc; const medium: TStgMedium; fRelease: BOOL): HResult; stdcall; function EnumFormatEtc(dwDirection: LongWord; out enumFormatEtc: IEnumFormatEtc): HResult; stdcall; function DAdvise(const formatetc: TFormatEtc; advf: LongWord; const advSink: IAdviseSink; out dwConnection: LongWord): HResult; stdcall; function DUnadvise(dwConnection: LongWord): HResult; stdcall; function EnumDAdvise(out enumAdvise: IEnumStatData): HResult; stdcall; end; TDragDropSourceWindows = class(TDragDropSource) public function RegisterEvents(DragBeginEvent : uDragDropEx.TDragBeginEvent; RequestDataEvent: uDragDropEx.TRequestDataEvent;// not handled in Windows DragEndEvent : uDragDropEx.TDragEndEvent): Boolean; override; function DoDragDrop(const FileNamesList: TStringList; MouseButton: TMouseButton; ScreenStartPoint: TPoint ): Boolean; override; end; TDragDropTargetWindows = class(TDragDropTarget) public constructor Create(Control: TWinControl); override; destructor Destroy; override; function RegisterEvents(DragEnterEvent: uDragDropEx.TDragEnterEvent; DragOverEvent : uDragDropEx.TDragOverEvent; DropEvent : uDragDropEx.TDropEvent; DragLeaveEvent: uDragDropEx.TDragLeaveEvent): Boolean; override; procedure UnregisterEvents; override; private FDragDropTarget: TFileDropTarget; end; function GetEffectByKeyState(grfKeyState: LongWord) : Integer; { These functions convert Windows-specific effect value to { TDropEffect values and vice-versa. } function WinEffectToDropEffect(dwEffect: LongWord): TDropEffect; function DropEffectToWinEffect(DropEffect: TDropEffect): LongWord; { Query DROPFILES structure for [BOOL fWide] parameter } function DragQueryWide( hGlobalDropInfo: HDROP ): boolean; implementation uses //Lazarus, Free-Pascal, etc. LazUTF8, SysUtils, ShellAPI, ShlObj, LCLIntf, ComObj, DCDateTimeUtils, Forms, DCConvertEncoding, //DC uOSUtils, fOptionsDragDrop, uShowMsg, UGlobs, DCStrUtils, DCOSUtils, uClipboard, uLng, uDebug; var // Supported formats by the source. DataFormats: TList = nil; // of TFormatEtc procedure InitDataFormats; procedure AddFormat(FormatId: Word); var FormatEtc: PFormatEtc; begin if FormatId > 0 then begin New(FormatEtc); if Assigned(FormatEtc) then begin DataFormats.Add(FormatEtc); with FormatEtc^ do begin CfFormat := FormatId; Ptd := nil; dwAspect := DVASPECT_CONTENT; lindex := -1; tymed := TYMED_HGLOBAL; end; end; end; end; begin DataFormats := TList.Create; AddFormat(CF_HDROP); AddFormat(CFU_PREFERRED_DROPEFFECT); AddFormat(CFU_FILENAME); AddFormat(CFU_FILENAMEW); // URIs disabled for now. This implementation does not work correct. // See bug http://doublecmd.sourceforge.net/mantisbt/view.php?id=692 { AddFormat(CFU_UNIFORM_RESOURCE_LOCATOR); AddFormat(CFU_UNIFORM_RESOURCE_LOCATORW); } AddFormat(CFU_SHELL_IDLIST_ARRAY); end; procedure DestroyDataFormats; var i : Integer; begin if Assigned(DataFormats) then begin for i := 0 to DataFormats.Count - 1 do if Assigned(DataFormats.Items[i]) then Dispose(PFormatEtc(DataFormats.Items[i])); FreeAndNil(DataFormats); end; end; { TEnumFormatEtc.Create } constructor TEnumFormatEtc.Create(Index: Integer); begin inherited Create; FIndex := Index; end; { TEnumFormatEtc.Next извлекает заданное количество структур TFormatEtc в передаваемый массив elt. Извлекается celt элементов, начиная с текущей позиции в списке. } function TEnumFormatEtc.Next(celt: LongWord; out elt: FormatEtc; pceltFetched: pULong): HResult; var i: Integer; eltout: PFormatEtc; begin // Support returning only 1 format at a time. if celt > 1 then celt := 1; eltout := @elt; i := 0; while (i < celt) and (FIndex < DataFormats.Count) do begin (eltout + i)^ := PFormatEtc(DataFormats.Items[FIndex])^; Inc(FIndex); Inc(i); end; if (pceltFetched <> nil) then pceltFetched^ := i; if (I = celt) then Result := S_OK else Result := S_FALSE; end; { TEnumFormatEtc.Skip пропускает celt элементов списка, устанавливая текущую позицию на (CurrentPointer + celt) или на конец списка в случае переполнения. } function TEnumFormatEtc.Skip(celt: LongWord): HResult; begin if (celt <= DataFormats.Count - FIndex) then begin FIndex := FIndex + celt; Result := S_OK; end else begin FIndex := DataFormats.Count; Result := S_FALSE; end; end; { TEnumFormatEtc.Reset устанавливает указатель текущей позиции на начало списка } function TEnumFormatEtc.Reset: HResult; begin FIndex := 0; Result := S_OK; end; { TEnumFormatEtc.Clone копирует список структур } function TEnumFormatEtc.Clone(out enum: IEnumFormatEtc): HResult; begin enum := TEnumFormatEtc.Create(FIndex); Result := S_OK; end; { TDragDropInfo.Create } constructor TDragDropInfo.Create(PreferredWinDropEffect: DWORD); begin inherited Create; FFileList := TStringList.Create; FPreferredWinDropEffect := PreferredWinDropEffect; end; { TDragDropInfo.Destroy } destructor TDragDropInfo.Destroy; begin FFileList.Free; inherited Destroy; end; { TDragDropInfo.Add } procedure TDragDropInfo.Add(const s: string); begin Files.Add(s); end; { TDragDropInfo.MakeDataInFormat } function TDragDropInfo.MakeDataInFormat(const formatEtc: TFormatEtc): HGlobal; begin Result := 0; if (formatEtc.tymed = DWORD(-1)) or // Transport medium not specified. (Boolean(formatEtc.tymed and TYMED_HGLOBAL)) // Support only HGLOBAL medium. then begin if formatEtc.CfFormat = CF_HDROP then begin Result := CreateHDrop(Win32Platform = VER_PLATFORM_WIN32_NT) end else if formatEtc.CfFormat = CFU_PREFERRED_DROPEFFECT then begin Result := CreatePreferredDropEffect(FPreferredWinDropEffect); end else if (formatEtc.CfFormat = CFU_FILENAME) then begin Result := CreateFileNames(False); end else if (formatEtc.CfFormat = CFU_FILENAMEW) then begin Result := CreateFileNames(True); end // URIs disabled for now. This implementation does not work correct. // See bug http://doublecmd.sourceforge.net/mantisbt/view.php?id=692 { else if (formatEtc.CfFormat = CFU_UNIFORM_RESOURCE_LOCATOR) then begin Result := CreateURIs(False); end else if (formatEtc.CfFormat = CFU_UNIFORM_RESOURCE_LOCATORW) then begin Result := CreateURIs(True); end } else if (formatEtc.CfFormat = CFU_SHELL_IDLIST_ARRAY) then begin Result := CreateShellIdListArray; end; end; end; { TDragDropInfo.CreateFileNames } function TDragDropInfo.CreateFileNames(bUnicode: Boolean): HGlobal; var FileList: AnsiString; wsFileList: WideString; begin if Files.Count = 0 then Exit; if bUnicode then begin wsFileList := UTF8Decode(Self.Files[0]) + #0; Result := MakeHGlobal(PWideChar(wsFileList), Length(wsFileList) * SizeOf(WideChar)); end else begin FileList := CeUtf8ToAnsi(Self.Files[0]) + #0; Result := MakeHGlobal(PAnsiChar(FileList), Length(FileList) * SizeOf(AnsiChar)); end; end; { TDragDropInfo.CreateURIs } function TDragDropInfo.CreateURIs(bUnicode: Boolean): HGlobal; var UriList: AnsiString; wsUriList: WideString; I: Integer; begin wsUriList := ''; for I := 0 to Self.Files.Count - 1 do begin if I > 0 then wsUriList := wsUriList + LineEnding; wsUriList := wsUriList + fileScheme + '//' { don't put hostname } + UTF8Decode(URIEncode(StringReplace(Files[I], '\', '/', [rfReplaceAll] ))); end; wsUriList := wsUriList + #0; if bUnicode then Result := MakeHGlobal(PWideChar(wsUriList), Length(wsUriList) * SizeOf(WideChar)) else begin // Wide to Ansi UriList := CeUtf8ToAnsi(UTF16ToUTF8(wsUriList)); Result := MakeHGlobal(PAnsiChar(UriList), Length(UriList) * SizeOf(AnsiChar)); end; end; { TDragDropInfo.CreateShellIdListArray } function TDragDropInfo.CreateShellIdListArray: HGlobal; var pidl: LPITEMIDLIST; pidlSize: Integer; pIdA: LPIDA = nil; // ShellIdListArray structure ShellDesktop: IShellFolder = nil; CurPosition: UINT; dwTotalSizeToAllocate: DWORD; I: Integer; function GetPidlFromPath(ShellFolder: IShellFolder; Path: WideString): LPITEMIDLIST; var chEaten: ULONG = 0; dwAttributes: ULONG = 0; begin if ShellFolder.ParseDisplayName(0, nil, PWideChar(Path), chEaten, Result, dwAttributes) <> S_OK then begin Result := nil; end; end; function GetPidlSize(Pidl: LPITEMIDLIST): Integer; var pidlTmp: LPITEMIDLIST; begin Result := 0; pidlTmp := pidl; while pidlTmp^.mkid.cb <> 0 do begin Result := Result + pidlTmp^.mkid.cb; pidlTmp := LPITEMIDLIST(LPBYTE(pidlTmp) + PtrInt(pidlTmp^.mkid.cb)); // Next Item. end; Inc(Result, SizeOf(BYTE) * 2); // PIDL ends with two zeros. end; begin Result := 0; // Get Desktop shell interface. if SHGetDesktopFolder(ShellDesktop) = S_OK then begin // Get Desktop PIDL, which will be the root PIDL for the files' PIDLs. if SHGetSpecialFolderLocation(0, CSIDL_DESKTOP, pidl) = S_OK then begin pidlSize := GetPidlSize(pidl); // How much memory to allocate for the whole structure. // We don't know how much memory each PIDL takes yet // (estimate using desktop pidl size). dwTotalSizeToAllocate := SizeOf(_IDA.cidl) + SizeOf(UINT) * (Files.Count + 1) // PIDLs' offsets + pidlSize * (Files.Count + 1); // PIDLs pIda := AllocMem(dwTotalSizeToAllocate); // Number of files PIDLs (without root). pIdA^.cidl := Files.Count; // Calculate offset for the first pidl (root). CurPosition := SizeOf(_IDA.cidl) + SizeOf(UINT) * (Files.Count + 1); // Write first PIDL. pIdA^.aoffset[0] := CurPosition; CopyMemory(LPBYTE(pIda) + PtrInt(CurPosition), pidl, pidlSize); Inc(CurPosition, pidlSize); CoTaskMemFree(pidl); for I := 0 to Self.Files.Count - 1 do begin // Get PIDL for each file (if Desktop is the root, then // absolute paths are acceptable). pidl := GetPidlFromPath(ShellDesktop, UTF8Decode(Files[i])); if pidl <> nil then begin pidlSize := GetPidlSize(pidl); // If not enough memory then reallocate. if dwTotalSizeToAllocate < CurPosition + pidlSize then begin // Estimate using current PIDL's size. Inc(dwTotalSizeToAllocate, (Files.Count - i) * pidlSize); pIdA := ReAllocMem(pIda, dwTotalSizeToAllocate); if not Assigned(pIda) then Break; end; // Write PIDL. {$R-} pIdA^.aoffset[i + 1] := CurPosition; {$R+} CopyMemory(LPBYTE(pIdA) + PtrInt(CurPosition), pidl, pidlSize); Inc(CurPosition, pidlSize); CoTaskMemFree(pidl); end; end; if Assigned(pIda) then begin // Current position it at the end of the structure. Result := MakeHGlobal(pIdA, CurPosition); Freemem(pIda); end; end; // SHGetSpecialFolderLocation ShellDesktop := nil; end; // SHGetDesktopFolder end; { TDragDropInfo.CreatePreferredDropEffect } function TDragDropInfo.CreatePreferredDropEffect(WinDropEffect: DWORD) : HGlobal; begin Result := MakeHGlobal(@WinDropEffect, SizeOf(WinDropEffect)); end; { TDragDropInfo.MakeHGlobal } function TDragDropInfo.MakeHGlobal(ptr: Pointer; Size: LongWord): HGlobal; var DataPointer : Pointer; DataHandle : HGLOBAL; begin Result := 0; if Assigned(ptr) then begin DataHandle := GlobalAlloc(GMEM_MOVEABLE or GMEM_ZEROINIT, Size); if (DataHandle <> 0) then begin DataPointer := GlobalLock(DataHandle); if Assigned(DataPointer) then begin CopyMemory(DataPointer, ptr, Size); GlobalUnlock(DataHandle); Result := DataHandle; end else begin GlobalFree(DataHandle); end; end; end; end; { TDragDropInfo.CreateHDrop } function TDragDropInfo.CreateHDrop(bUnicode: Boolean): HGlobal; var RequiredSize: Integer; I: Integer; hGlobalDropInfo: HGlobal; DropFiles: PDropFiles; FileList: AnsiString = ''; wsFileList: WideString = ''; begin { Построим структуру TDropFiles в памяти, выделенной через GlobalAlloc. Область памяти сделаем глобальной и совместной, поскольку она, вероятно, будет передаваться другому процессу. Bring the filenames in a form, separated by #0 and ending with a double #0#0 } if bUnicode then begin for I := 0 to Self.Files.Count - 1 do wsFileList := wsFileList + UTF8Decode(Self.Files[I]) + #0; wsFileList := wsFileList + #0; { Определяем необходимый размер структуры } RequiredSize := SizeOf(TDropFiles) + Length(wsFileList) * SizeOf(WChar); end else begin for I := 0 to Self.Files.Count - 1 do FileList := FileList + CeUtf8ToAnsi(Self.Files[I]) + #0; FileList := FileList + #0; { Определяем необходимый размер структуры } RequiredSize := SizeOf(TDropFiles) + Length(FileList) * SizeOf(AnsiChar); end; hGlobalDropInfo := GlobalAlloc(GMEM_MOVEABLE or GMEM_ZEROINIT, RequiredSize); if (hGlobalDropInfo <> 0) then begin { Заблокируем область памяти, чтобы к ней можно было обратиться } DropFiles := GlobalLock(hGlobalDropInfo); { Заполним поля структуры DropFiles pFiles -- смещение от начала структуры до первого байта массива с именами файлов. } DropFiles.pFiles := SizeOf(TDropFiles); if Windows.GetCursorPos(@DropFiles.pt) = False then begin DropFiles.pt.x := 0; DropFiles.pt.y := 0; end; DropFiles.fNC := True; // Pass cursor coordinates as screen coords DropFiles.fWide := bUnicode; { Копируем имена файлов в буфер. Буфер начинается со смещения DropFiles + DropFiles.pFiles, то есть после последнего поля структуры. The pointer should be aligned nicely, because the TDropFiles record is not packed. } DropFiles := Pointer(DropFiles) + DropFiles.pFiles; if bUnicode then CopyMemory(DropFiles, PWideChar(wsFileList), Length(wsFileList) * SizeOf(WChar)) else CopyMemory(DropFiles, PAnsiChar(FileList), Length(FileList) * SizeOf(AnsiChar)); { Снимаем блокировку } GlobalUnlock(hGlobalDropInfo); end; Result := hGlobalDropInfo; end; { TFileDropTarget.Create } constructor TFileDropTarget.Create(DragDropTarget: TDragDropTargetWindows); begin inherited Create; // Here RefCount is 1 - as set in TInterfacedObject.NewInstance, // but it's decremented back in TInterfacedObject.AfterConstruction // (when this constructor finishes). So we must manually again increase it. _AddRef; FReleased := False; FDragDropTarget := DragDropTarget; // Increases RefCount. ActiveX.CoLockObjectExternal(Self, True, False); // Increases RefCount. if ActiveX.RegisterDragDrop(DragDropTarget.GetControl.Handle, Self) = S_OK then FHandle := DragDropTarget.GetControl.Handle else FHandle := 0; end; { TFileDropTarget.FinalRelease } procedure TFileDropTarget.FinalRelease; begin if not FReleased then begin FReleased := True; // Decreases reference count. ActiveX.CoLockObjectExternal(Self, False, True); // Check if window was not already destroyed. if (FHandle <> 0) and (IsWindow(FHandle)) then begin // Decreases reference count. ActiveX.RevokeDragDrop(FHandle); FHandle := 0; end else _Release; // Cannot revoke - just release reference. _Release; // For _AddRef in Create. end; end; function TFileDropTarget.DragEnter(const dataObj: IDataObject; grfKeyState: LongWord; pt: TPoint; var dwEffect: LongWord): HResult; stdcall; var DropEffect: TDropEffect; begin // dwEffect parameter states which effects are allowed by the source. dwEffect := dwEffect and GetEffectByKeyState(grfKeyState); if Assigned(FDragDropTarget.GetDragEnterEvent) then begin DropEffect := WinEffectToDropEffect(dwEffect); if FDragDropTarget.GetDragEnterEvent()(DropEffect, pt) = True then begin dwEffect := DropEffectToWinEffect(DropEffect); Result := S_OK end else Result := S_FALSE; end else Result := S_OK; end; { TFileDropTarget.DragOver } function TFileDropTarget.DragOver(grfKeyState: LongWord; pt: TPoint; var dwEffect: LongWord): HResult; stdcall; var DropEffect: TDropEffect; begin // dwEffect parameter states which effects are allowed by the source. dwEffect := dwEffect and GetEffectByKeyState(grfKeyState); if Assigned(FDragDropTarget.GetDragOverEvent) then begin DropEffect := WinEffectToDropEffect(dwEffect); if FDragDropTarget.GetDragOverEvent()(DropEffect, pt) = True then begin dwEffect := DropEffectToWinEffect(DropEffect); Result := S_OK end else Result := S_FALSE; end else Result := S_OK; end; { TFileDropTarget.DragLeave } function TFileDropTarget.DragLeave: HResult; stdcall; begin if Assigned(FDragDropTarget.GetDragLeaveEvent) then begin if FDragDropTarget.GetDragLeaveEvent() = True then Result := S_OK else Result := S_FALSE; end else Result := S_OK; end; { Обработка сброшенных данных. } { TFileDropTarget.Drop } function TFileDropTarget.Drop(const dataObj: IDataObject; grfKeyState: LongWord; pt: TPoint; var dwEffect: LongWord): HResult; stdcall; var Medium: TSTGMedium; CyclingThroughFormat, ChosenFormat: TFormatETC; i: Integer; DropInfo: TDragDropInfo; FileNames, DragTextModeOfferedList: TStringList; SelectedFormatName:String; DropEffect: TDropEffect; Enum: IEnumFormatEtc; DragAndDropSupportedFormatList:TStringList; UnusedInteger : integer; begin DragAndDropSupportedFormatList:=TStringList.Create; try FileNames:=nil; UnusedInteger:=0; dataObj._AddRef; { Получаем данные. Структура TFormatETC сообщает dataObj.GetData, как получить данные и в каком формате они должны храниться (эта информация содержится в структуре TSTGMedium). } //1. Let's build as quick list of the supported formats of what we've just been dropped. // We scan through all because sometimes the best one is not the first compatible one. OleCheck(DataObj.EnumFormatEtc(DATADIR_GET, Enum)); while Enum.Next(1, CyclingThroughFormat, nil) = S_OK do DragAndDropSupportedFormatList.Add(IntToStr(CyclingThroughFormat.CfFormat)); //2. Let's determine our best guess. // The order for this will be: // 1st) CF_HDROP (for legacy purpose, since DC was using it first). // 2nd) CFU_FILEGROUPDESCRIPTORW + CFU_FILECONTENTS (Outlook 2010 / Windows Live Mail, etc.) // 3rd) CFU_FILEGROUPDESCRIPTOR + CFU_FILECONTENTS (Outlook 2010 / Windows Live Mail, etc.) // 4th) We'll see if user would like to create a new text file from possible selected text dropped on the panel // CF_UNICODETEXT (Notepad++ / Wordpad / Firefox) // CF_TEXT (Notepad / Wordpad / Firefox) // CFU_HTML (Firefox) // Rich Text (Wordpad / Microsoft Word) ChosenFormat.CfFormat:=0; if DragAndDropSupportedFormatList.IndexOf(IntToStr(CF_HDROP))<>-1 then ChosenFormat.CfFormat:=CF_HDROP; if (ChosenFormat.CfFormat=0) AND (DragAndDropSupportedFormatList.IndexOf(IntToStr(CFU_FILEGROUPDESCRIPTORW))<>-1) AND (DragAndDropSupportedFormatList.IndexOf(IntToStr(CFU_FILECONTENTS))<>-1) then ChosenFormat.CfFormat:=CFU_FILEGROUPDESCRIPTORW; if (ChosenFormat.CfFormat=0) AND (DragAndDropSupportedFormatList.IndexOf(IntToStr(CFU_FILEGROUPDESCRIPTOR))<>-1) AND (DragAndDropSupportedFormatList.IndexOf(IntToStr(CFU_FILECONTENTS))<>-1) then ChosenFormat.CfFormat:=CFU_FILEGROUPDESCRIPTOR; // If we have no chosen format yet, let's attempt for text ones... if ChosenFormat.CfFormat=0 then begin DragTextModeOfferedList:=TStringList.Create; try if (DragAndDropSupportedFormatList.IndexOf(IntToStr(CFU_RICHTEXT))<>-1) then DragTextModeOfferedList.Add(gDragAndDropDesiredTextFormat[DropTextRichText_Index].Name); if (DragAndDropSupportedFormatList.IndexOf(IntToStr(CFU_HTML))<>-1) then DragTextModeOfferedList.Add(gDragAndDropDesiredTextFormat[DropTextHtml_Index].Name); if (DragAndDropSupportedFormatList.IndexOf(IntToStr(CF_UNICODETEXT))<>-1) then DragTextModeOfferedList.Add(gDragAndDropDesiredTextFormat[DropTextUnicode_Index].Name); if (DragAndDropSupportedFormatList.IndexOf(IntToStr(CF_TEXT))<>-1) then DragTextModeOfferedList.Add(gDragAndDropDesiredTextFormat[DropTextSimpleText_Index].Name); SortThisListAccordingToDragAndDropDesiredFormat(DragTextModeOfferedList); if DragTextModeOfferedList.Count>0 then SelectedFormatName:=DragTextModeOfferedList.Strings[0] else SelectedFormatName:=''; if (DragTextModeOfferedList.Count>1) AND (gDragAndDropAskFormatEachTime) then if not ShowInputListBox(rsCaptionForTextFormatToImport,rsMsgForTextFormatToImport,DragTextModeOfferedList,SelectedFormatName,UnusedInteger) then SelectedFormatName:=''; if SelectedFormatName<>'' then begin if SelectedFormatName=gDragAndDropDesiredTextFormat[DropTextRichText_Index].Name then ChosenFormat.CfFormat:=CFU_RICHTEXT; if SelectedFormatName=gDragAndDropDesiredTextFormat[DropTextHtml_Index].Name then ChosenFormat.CfFormat:=CFU_HTML; if SelectedFormatName=gDragAndDropDesiredTextFormat[DropTextUnicode_Index].Name then ChosenFormat.CfFormat:=CF_UNICODETEXT; if SelectedFormatName=gDragAndDropDesiredTextFormat[DropTextSimpleText_Index].Name then ChosenFormat.CfFormat:=CF_TEXT; end; finally DragTextModeOfferedList.Free; end; end; //3. According to our best guess, let's store to "FileNames" list, the list of files we got (...or that we'll create!) if ChosenFormat.CfFormat<>0 then begin ChosenFormat.ptd := nil; ChosenFormat.dwAspect := DVASPECT_CONTENT; ChosenFormat.lindex := -1; ChosenFormat.tymed := TYMED_HGLOBAL; { Заносим данные в структуру Medium } Result:=dataObj.GetData(ChosenFormat, Medium); { Если все прошло успешно, далее действуем, как при операции файлового перетаскивания FMDD. } if Result = S_OK then begin if Medium.Tymed=TYMED_HGLOBAL then begin case ChosenFormat.CfFormat of CF_HDROP: FileNames := GetDropFilenames(Medium.hGlobal); CF_UNICODETEXT, CF_TEXT: FileNames := GetDropTextCreatedFilenames(Medium, ChosenFormat); else begin if ChosenFormat.CfFormat=CFU_FILEGROUPDESCRIPTORW then FileNames := GetDropFileGroupFilenames(dataObj, Medium, ChosenFormat); if ChosenFormat.CfFormat=CFU_FILEGROUPDESCRIPTOR then FileNames := GetDropFileGroupFilenames(dataObj, Medium, ChosenFormat); if ChosenFormat.CfFormat=CFU_HTML then FileNames := GetDropTextCreatedFilenames(Medium, ChosenFormat); if ChosenFormat.CfFormat=CFU_RICHTEXT then FileNames := GetDropTextCreatedFilenames(Medium, ChosenFormat); end; end; end; end; end; //4. If we have some filenames in our list, continue to process the actual "Drop" of files if (Result = S_OK) then begin { Создаем объект TDragDropInfo } DropInfo := TDragDropInfo.Create(dwEffect); if Assigned(FileNames) then begin for i := 0 to FileNames.Count - 1 do DropInfo.Add(FileNames[i]); FreeAndNil(FileNames); end; { Если указан обработчик, вызываем его } if (Assigned(FDragDropTarget.GetDropEvent)) then begin // Set default effect by examining keyboard keys, taking into // consideration effects allowed by the source (dwEffect parameter). dwEffect := dwEffect and GetEffectByKeyState(grfKeyState); DropEffect := WinEffectToDropEffect(dwEffect); FDragDropTarget.GetDropEvent()(DropInfo.Files, DropEffect, pt); dwEffect := DropEffectToWinEffect(DropEffect); end; DropInfo.Free; if (Medium.PUnkForRelease = nil) then // Drop target must release the medium allocated by GetData. // This does the same as DragFinish(Medium.hGlobal) in this case, // but can support other media. ReleaseStgMedium(@Medium) else // Drop source is responsible for releasing medium via this object. IUnknown(Medium.PUnkForRelease)._Release; end; dataObj._Release; finally DragAndDropSupportedFormatList.Free; end; end; { TFileDropTarget.GetDropFilenames } class function TFileDropTarget.GetDropFilenames(hDropData: HDROP): TStringList; var NumFiles: Integer; i: Integer; wszFilename: PWideChar; FileName: WideString; RequiredSize: Cardinal; begin Result := nil; if hDropData <> 0 then begin Result := TStringList.Create; try NumFiles := DragQueryFileW(hDropData, $FFFFFFFF, nil, 0); for i := 0 to NumFiles - 1 do begin RequiredSize := DragQueryFileW(hDropData, i, nil, 0) + 1; // + 1 = terminating zero wszFilename := GetMem(RequiredSize * SizeOf(WideChar)); if Assigned(wszFilename) then try if DragQueryFileW(hDropData, i, wszFilename, RequiredSize) > 0 then begin FileName := wszFilename; // Windows inserts '?' character where Wide->Ansi conversion // of a character was not possible, in which case filename is invalid. // This may happen if a non-Unicode application was the source. if Pos('?', FileName) = 0 then Result.Add(UTF16ToUTF8(FileName)) else raise Exception.Create(rsMsgInvalidFilename + ': ' + LineEnding + UTF16ToUTF8(FileName)); end; finally FreeMem(wszFilename); end; end; except FreeAndNil(Result); raise; end; end; end; { TFileDropTarget.SaveCfuContentToFile } function TFileDropTarget.SaveCfuContentToFile(const dataObj:IDataObject; Index:Integer; WantedFilename:String; WantedCreationTime, WantedModificationTime, WantedLastAccessTime:DCBasicTypes.TFileTime):boolean; const TEMPFILENAME='CfuContentFile.bin'; var Format : TFORMATETC; Medium : TSTGMedium; Ifile, iStg : IStorage; tIID : PGuid; hFile: THandle; pvStrm: IStream; dwSize: LongInt; AnyPointer: PAnsiChar; InnerFilename: String; StgDocFile: WideString; msStream: TMemoryStream; i64Size, i64Move: {$IF FPC_FULLVERSION < 030002}Int64{$ELSE}QWord{$ENDIF}; begin result:=FALSE; InnerFilename:= ExtractFilepath(WantedFilename) + TEMPFILENAME; Format.cfFormat := CFU_FILECONTENTS; Format.dwAspect := DVASPECT_CONTENT; Format.lindex := Index; Format.ptd := nil; Format.TYMED := TYMED_ISTREAM OR TYMED_ISTORAGE or TYMED_HGLOBAL; if dataObj.GetData(Format, Medium) = S_OK then begin if Medium.TYMED = TYMED_ISTORAGE then begin iStg := IStorage(Medium.pstg); StgDocFile := UTF8Decode(InnerFilename); StgCreateDocfile(PWideChar(StgDocFile), STGM_CREATE Or STGM_READWRITE Or STGM_SHARE_EXCLUSIVE, 0, iFile); tIID:=nil; iStg.CopyTo(0, tIID, nil, iFile); iFile.Commit(0); iFile := nil; iStg := nil; end else if Medium.Tymed = TYMED_HGLOBAL then begin AnyPointer := GlobalLock(Medium.HGLOBAL); try hFile := mbFileCreate(InnerFilename); if hFile <> feInvalidHandle then begin FileWrite(hFile, AnyPointer^, GlobalSize(Medium.HGLOBAL)); FileClose(hFile); end; finally GlobalUnlock(Medium.HGLOBAL); end; if Medium.PUnkForRelease = nil then GlobalFree(Medium.HGLOBAL); end else begin pvStrm:=IStream(Medium.pstm); // Figure out how large the data is if (pvStrm.Seek(0, STREAM_SEEK_END, i64Size) = S_OK) then begin // Seek back to start of stream pvStrm.Seek(0, STREAM_SEEK_SET, i64Move); // Create memory stream to convert to msStream:=TMemoryStream.Create; // Allocate size msStream.Size:=i64Size; // Read from the IStream into the memory for the TMemoryStream if pvStrm.Read(msStream.Memory, i64Size, @dwSize)=S_OK then msStream.Size:=dwSize else msStream.Size:=0; // Release interface pvStrm:=nil; msStream.Position:=0; msStream.SaveToFile(UTF8ToSys(InnerFilename)); msStream.Free; end; end; end; if mbFileExists(InnerFilename) then begin mbRenameFile(InnerFilename,WantedFilename); if mbFileExists(WantedFilename) then result:=mbFileSetTime(WantedFilename, WantedModificationTime, WantedCreationTime, WantedLastAccessTime); end; end; { TFileDropTarget.GetDropFileGroupFilenames } function TFileDropTarget.GetDropFileGroupFilenames(const dataObj: IDataObject; var Medium: TSTGMedium; Format: TFormatETC): TStringList; var AnyPointer: Pointer; DC_FileGroupeDescriptorW: FILEGROUPDESCRIPTORW; DC_FileGroupeDescriptor: FILEGROUPDESCRIPTOR; DC_FileDescriptorW: FILEDESCRIPTORW; DC_FileDescriptor: FILEDESCRIPTOR; NumberOfFiles, CopyNumber, IndexFile: integer; ActualFilename, DroppedTextFilename: String; SuffixStr: string; WantedCreationTime, WantedModificationTime, WantedLastAccessTime : DCBasicTypes.TFileTime; begin Result := nil; AnyPointer := GlobalLock(Medium.HGLOBAL); try // Copy the structure if Format.CfFormat=CFU_FILEGROUPDESCRIPTORW then begin MoveMemory(@DC_FileGroupeDescriptorW, AnyPointer, SizeOf(FILEGROUPDESCRIPTORW)); NumberOfFiles:=DC_FileGroupeDescriptorW.cItems; end else begin MoveMemory(@DC_FileGroupeDescriptor, AnyPointer, SizeOf(FILEGROUPDESCRIPTOR)); NumberOfFiles:=DC_FileGroupeDescriptor.cItems; end; // Return the number of messages if NumberOfFiles>0 then begin if Format.CfFormat=CFU_FILEGROUPDESCRIPTORW then AnyPointer:=AnyPointer+SizeOf(FILEGROUPDESCRIPTORW.cItems) else AnyPointer:=AnyPointer+SizeOf(FILEGROUPDESCRIPTOR.cItems); result:=TStringList.Create; for IndexFile:=0 to pred(NumberOfFiles) do begin if Format.CfFormat=CFU_FILEGROUPDESCRIPTORW then begin MoveMemory(@DC_FileDescriptorW, AnyPointer, SizeOf(FILEDESCRIPTORW)); AnyPointer:=AnyPointer+SizeOf(FILEDESCRIPTORW); ActualFilename:=UTF16ToUTF8(UnicodeString(DC_FileDescriptorW.cFileName)); WantedCreationTime:=DCBasicTypes.TFileTime(DC_FileDescriptorW.ftCreationTime); WantedModificationTime:=DCBasicTypes.TFileTime(DC_FileDescriptorW.ftLastWriteTime); WantedLastAccessTime:=DCBasicTypes.TFileTime(DC_FileDescriptorW.ftLastAccessTime); end else begin MoveMemory(@DC_FileDescriptor, AnyPointer, SizeOf(FILEDESCRIPTOR)); AnyPointer:=AnyPointer+SizeOf(FILEDESCRIPTOR); ActualFilename:=CeSysToUTF8(AnsiString(DC_FileDescriptor.cFileName)); WantedCreationTime:=DCBasicTypes.TFileTime(DC_FileDescriptor.ftCreationTime); WantedModificationTime:=DCBasicTypes.TFileTime(DC_FileDescriptor.ftLastWriteTime); WantedLastAccessTime:=DCBasicTypes.TFileTime(DC_FileDescriptor.ftLastAccessTime); end; DroppedTextFilename := GetTempFolderDeletableAtTheEnd+ActualFilename; if result.IndexOf(DroppedTextFilename) <> -1 then begin CopyNumber := 2; repeat case gTypeOfDuplicatedRename of drLikeWindows7: SuffixStr:=' ('+IntToStr(CopyNumber)+')'; drLikeTC: SuffixStr:='('+IntToStr(CopyNumber)+')'; end; case gTypeOfDuplicatedRename of drLegacyWithCopy: DroppedTextFilename := GetTempFolderDeletableAtTheEnd+SysUtils.Format(rsCopyNameTemplate, [CopyNumber, ActualFilename]); drLikeWindows7, drLikeTC: DroppedTextFilename := GetTempFolderDeletableAtTheEnd+RemoveFileExt(ActualFilename) + SuffixStr + ExtractFileExt(ActualFilename); end; Inc(CopyNumber); until result.IndexOf(DroppedTextFilename) = -1; end; if SaveCfuContentToFile(dataObj, IndexFile, DroppedTextFilename, WantedCreationTime, WantedModificationTime, WantedLastAccessTime) then result.Add(DroppedTextFilename); end; end; finally // Release the pointer GlobalUnlock(Medium.HGLOBAL); end; end; { TFileDropTarget.GetDropTextCreatedFilenames } function TFileDropTarget.GetDropTextCreatedFilenames(var Medium: TSTGMedium; Format: TFormatETC): TStringList; var FlagKeepGoing:boolean; AnyPointer: Pointer; UnicodeCharPointer: PUnicodeChar; hFile: THandle; DroppedTextFilename: String; MyUnicodeString: UnicodeString; procedure SetDefaultFilename; begin DroppedTextFilename:=GetDateTimeInStrEZSortable(now)+rsDefaultSuffixDroppedText+'.txt'; if Format.CfFormat=CFU_RICHTEXT then DroppedTextFilename:=GetDateTimeInStrEZSortable(now)+rsDefaultSuffixDroppedTextRichtextFilename+'.rtf'; if Format.CfFormat=CFU_HTML then DroppedTextFilename:=GetDateTimeInStrEZSortable(now)+rsDefaultSuffixDroppedTextHTMLFilename+'.html'; if (Format.CfFormat=CF_UNICODETEXT) AND not gDragAndDropSaveUnicodeTextInUFT8 then DroppedTextFilename:=GetDateTimeInStrEZSortable(now)+rsDefaultSuffixDroppedTextUnicodeUTF16Filename+'.txt'; if (Format.CfFormat=CF_UNICODETEXT) AND gDragAndDropSaveUnicodeTextInUFT8 then DroppedTextFilename:=GetDateTimeInStrEZSortable(now)+rsDefaultSuffixDroppedTextUnicodeUTF8Filename+'.txt'; if Format.CfFormat=CF_TEXT then DroppedTextFilename:=GetDateTimeInStrEZSortable(now)+rsDefaultSuffixDroppedTextSimpleFilename+'.txt'; end; begin result:=nil; FlagKeepGoing:=TRUE; SetDefaultFilename; if not gDragAndDropTextAutoFilename then FlagKeepGoing:=ShowInputQuery(rsCaptionForAskingFilename, rsMsgPromptAskingFilename, DroppedTextFilename); if FlagKeepGoing then begin if DroppedTextFilename='' then SetDefaultFilename; //Just minimal idot-proof... DroppedTextFilename:=GetTempFolderDeletableAtTheEnd+DroppedTextFilename; AnyPointer := GlobalLock(Medium.hGlobal); try hFile:= mbFileCreate(DroppedTextFilename); try case Format.CfFormat of CF_TEXT: begin FileWrite(hFile, PAnsiChar(AnyPointer)^, UTF8Length(PAnsiChar(AnyPointer))); end; CF_UNICODETEXT: begin if gDragAndDropSaveUnicodeTextInUFT8 then begin UnicodeCharPointer:=AnyPointer; MyUnicodeString:=''; while UnicodeCharPointer^<>#$0000 do begin MyUnicodeString:=MyUnicodeString+UnicodeCharPointer^; inc(UnicodeCharPointer); end; FileWrite(hFile, PChar(#$EF+#$BB+#$BF)[0], 3); //Adding Byte Order Mask for UTF8. FileWrite(hFile, UTF16toUTF8(MyUnicodeString)[1], Length(UTF16toUTF8(MyUnicodeString))); end else begin FileWrite(hFile, PChar(#$FF+#$FE)[0], 2); //Adding Byte Order Mask for UTF16, Little-Endian first. FileWrite(hFile, PUnicodeChar(AnyPointer)^, Length(PUnicodeChar(AnyPointer))*2); end; end; else begin if Format.CfFormat=CFU_HTML then FileWrite(hFile, PAnsiChar(AnyPointer)^, UTF8Length(PAnsiChar(AnyPointer))); if Format.CfFormat=CFU_RICHTEXT then FileWrite(hFile, PAnsiChar(AnyPointer)^, UTF8Length(PAnsiChar(AnyPointer))); end; end; finally FileClose(hFile); end; result:=TStringList.Create; result.Add(DroppedTextFilename); finally GlobalUnlock(Medium.hGlobal); end; end; end; { TFileDropSource.Create } constructor TFileDropSource.Create; begin inherited Create; _AddRef; end; { TFileDropSource.QueryContinueDrag } {$IF FPC_FULLVERSION < 020601} function TFileDropSource.QueryContinueDrag(fEscapePressed: BOOL; grfKeyState: longint): HResult; {$ELSE} function TFileDropSource.QueryContinueDrag(fEscapePressed: BOOL; grfKeyState: DWORD): HResult; {$ENDIF} var Point:TPoint; begin if (fEscapePressed) then begin Result := DRAGDROP_S_CANCEL; // Set flag to notify that dragging was canceled by the user. uDragDropEx.TransformDragging := False; end else if ((grfKeyState and (MK_LBUTTON or MK_MBUTTON or MK_RBUTTON)) = 0) then begin Result := DRAGDROP_S_DROP; end else begin if uDragDropEx.AllowTransformToInternal then begin GetCursorPos(Point); // Call LCL function, not the Windows one. // LCL version will return 0 if mouse is over a window belonging to another process. if LCLIntf.WindowFromPoint(Point) <> 0 then begin // Mouse cursor has been moved back into the application window. // Cancel external dragging. Result := DRAGDROP_S_CANCEL; // Set flag to notify that dragging has not finished, // but rather it is to be transformed into internal dragging. uDragDropEx.TransformDragging := True; end else Result := S_OK; // Continue dragging end else Result := S_OK; // Continue dragging end; end; {$IF FPC_FULLVERSION < 020601} function TFileDropSource.GiveFeedback(dwEffect: longint): HResult; {$ELSE} function TFileDropSource.GiveFeedback(dwEffect: DWORD): HResult; {$ENDIF} begin case LongWord(dwEffect) of DROPEFFECT_NONE, DROPEFFECT_COPY, DROPEFFECT_MOVE, DROPEFFECT_LINK, DROPEFFECT_SCROLL: Result := DRAGDROP_S_USEDEFAULTCURSORS; else Result := S_OK; end; end; { THDropDataObject.Create } constructor THDropDataObject.Create(PreferredWinDropEffect: DWORD); begin inherited Create; _AddRef; FDropInfo := TDragDropInfo.Create(PreferredWinDropEffect); end; { THDropDataObject.Destroy } destructor THDropDataObject.Destroy; begin if (FDropInfo <> nil) then FDropInfo.Free; inherited Destroy; end; { THDropDataObject.Add } procedure THDropDataObject.Add(const s: string); begin FDropInfo.Add(s); end; { THDropDataObject.GetData } function THDropDataObject.GetData(const formatetcIn: TFormatEtc; out medium: TStgMedium): HResult; begin Result := DV_E_FORMATETC; { Необходимо обнулить все поля medium на случай ошибки } medium.tymed := 0; medium.hGlobal := 0; medium.PUnkForRelease := nil; { Если формат поддерживается, создаем и возвращаем данные } if (QueryGetData(formatetcIn) = S_OK) then begin if (FDropInfo <> nil) then begin { Create data in specified format. } { The hGlobal will be released by the caller of GetData. } medium.hGlobal := FDropInfo.MakeDataInFormat(formatetcIn); if medium.hGlobal <> 0 then begin medium.tymed := TYMED_HGLOBAL; Result := S_OK; end; end; end; end; { THDropDataObject.GetDataHere } function THDropDataObject.GetDataHere(const formatetc: TFormatEtc; out medium: TStgMedium): HResult; begin Result := DV_E_FORMATETC; { К сожалению, не поддерживается } end; { THDropDataObject.QueryGetData } function THDropDataObject.QueryGetData(const formatetc: TFormatEtc): HResult; var i:Integer; begin with formatetc do if dwAspect = DVASPECT_CONTENT then begin Result := DV_E_FORMATETC; // begin with 'format not supported' // See if the queried format is supported. for i := 0 to DataFormats.Count - 1 do begin if Assigned(DataFormats[i]) then begin if cfFormat = PFormatEtc(DataFormats[i])^.CfFormat then begin // Format found, see if transport medium is supported. if (tymed = DWORD(-1)) or (Boolean(tymed and PFormatEtc(DataFormats[i])^.tymed)) then begin Result := S_OK; end else Result := DV_E_TYMED; // transport medium not supported Exit; // exit if format found (regardless of transport medium) end end end end else Result := DV_E_DVASPECT; // aspect not supported end; { THDropDataObject.GetCanonicalFormatEtc } function THDropDataObject.GetCanonicalFormatEtc(const formatetc: TFormatEtc; out formatetcOut: TFormatEtc): HResult; begin formatetcOut.ptd := nil; Result := E_NOTIMPL; end; { THDropDataObject.SetData } function THDropDataObject.SetData(const formatetc: TFormatEtc; const medium: TStgMedium; fRelease: BOOL): HResult; begin Result := E_NOTIMPL; end; { THDropDataObject.EnumFormatEtc возвращает список поддерживаемых форматов} function THDropDataObject.EnumFormatEtc(dwDirection: LongWord; out enumFormatEtc: IEnumFormatEtc): HResult; begin { Поддерживается только Get. Задать содержимое данных нельзя } if dwDirection = DATADIR_GET then begin enumFormatEtc := TEnumFormatEtc.Create; Result := S_OK; end else begin enumFormatEtc := nil; Result := E_NOTIMPL; end; end; { THDropDataObject.DAdviseDAdvise не поддерживаются} function THDropDataObject.DAdvise(const formatetc: TFormatEtc; advf: LongWord; const advSink: IAdviseSink; out dwConnection: LongWord): HResult; begin Result := OLE_E_ADVISENOTSUPPORTED; end; { THDropDataObject.DUnadvise } function THDropDataObject.DUnadvise(dwConnection: LongWord): HResult; begin Result := OLE_E_ADVISENOTSUPPORTED; end; { THDropDataObject.EnumDAdvise } function THDropDataObject.EnumDAdvise(out enumAdvise: IEnumStatData): HResult; begin Result := OLE_E_ADVISENOTSUPPORTED; end; function GetEffectByKeyState(grfKeyState: LongWord): Integer; begin Result := DROPEFFECT_COPY; { default effect } if (grfKeyState and MK_CONTROL) > 0 then begin if (grfKeyState and MK_SHIFT) > 0 then Result := DROPEFFECT_LINK else Result := DROPEFFECT_COPY; end else if (grfKeyState and MK_SHIFT) > 0 then Result := DROPEFFECT_MOVE; end; function WinEffectToDropEffect(dwEffect: LongWord): TDropEffect; begin case dwEffect of DROPEFFECT_COPY: Result := DropCopyEffect; DROPEFFECT_MOVE: Result := DropMoveEffect; DROPEFFECT_LINK: Result := DropLinkEffect; else Result := DropNoEffect; end; end; function DropEffectToWinEffect(DropEffect: TDropEffect): LongWord; begin case DropEffect of DropCopyEffect: Result := DROPEFFECT_COPY; DropMoveEffect: Result := DROPEFFECT_MOVE; DropLinkEffect: Result := DROPEFFECT_LINK; else Result := DROPEFFECT_NONE; end; end; function DragQueryWide( hGlobalDropInfo: HDROP ): boolean; var DropFiles: PDropFiles; begin DropFiles := GlobalLock( hGlobalDropInfo ); Result := DropFiles^.fWide; GlobalUnlock( hGlobalDropInfo ); end; { ---------------------------------------------------------} { TDragDropSourceWindows } function TDragDropSourceWindows.RegisterEvents( DragBeginEvent : uDragDropEx.TDragBeginEvent; RequestDataEvent: uDragDropEx.TRequestDataEvent; // not Handled in Windows DragEndEvent : uDragDropEx.TDragEndEvent): Boolean; begin inherited; // RequestDataEvent is not handled, because the system has control of all data transfer. Result := True; // confirm that events are registered end; function TDragDropSourceWindows.DoDragDrop(const FileNamesList: TStringList; MouseButton: TMouseButton; ScreenStartPoint: TPoint): Boolean; var DropSource: TFileDropSource; DropData: THDropDataObject; Rslt: HRESULT; dwEffect: LongWord; I: Integer; begin // Simulate drag-begin event. if Assigned(GetDragBeginEvent) then begin Result := GetDragBeginEvent()(); if Result = False then Exit; end; // Create source-object DropSource:= TFileDropSource.Create; // and data object DropData:= THDropDataObject.Create(DROPEFFECT_COPY { default effect } ); for I:= 0 to FileNamesList.Count - 1 do DropData.Add (FileNamesList[i]); // Start OLE Drag&Drop Rslt:= ActiveX.DoDragDrop(DropData, DropSource, DROPEFFECT_MOVE or DROPEFFECT_COPY or DROPEFFECT_LINK, // Allowed effects @dwEffect); case Rslt of DRAGDROP_S_DROP: begin FLastStatus := DragDropSuccessful; Result := True; end; DRAGDROP_S_CANCEL: begin FLastStatus := DragDropAborted; Result := False; end; else begin MessageBox(0, PAnsiChar(SysErrorMessage(Rslt)), nil, MB_OK or MB_ICONERROR); FLastStatus := DragDropError; Result := False; end; end; // Simulate drag-end event. This must be called here, // after DoDragDrop returns from the system. if Assigned(GetDragEndEvent) then begin if Result = True then Result := GetDragEndEvent()() else GetDragEndEvent()() end; // Release created objects. DropSource._Release; DropData._Release; end; { ---------------------------------------------------------} { TDragDropTargetWindows } constructor TDragDropTargetWindows.Create(Control: TWinControl); begin FDragDropTarget := nil; inherited Create(Control); end; destructor TDragDropTargetWindows.Destroy; begin inherited Destroy; if Assigned(FDragDropTarget) then begin FDragDropTarget.FinalRelease; FDragDropTarget := nil; end; end; function TDragDropTargetWindows.RegisterEvents( DragEnterEvent: uDragDropEx.TDragEnterEvent; DragOverEvent : uDragDropEx.TDragOverEvent; DropEvent : uDragDropEx.TDropEvent; DragLeaveEvent: uDragDropEx.TDragLeaveEvent): Boolean; begin // Unregister if registered before. UnregisterEvents; inherited; // Call inherited Register now. GetControl.HandleNeeded; // force creation of the handle if GetControl.HandleAllocated = True then begin FDragDropTarget := TFileDropTarget.Create(Self); Result := True; end; end; procedure TDragDropTargetWindows.UnregisterEvents; begin inherited; if Assigned(FDragDropTarget) then begin FDragDropTarget.FinalRelease; // Releasing will unregister events FDragDropTarget := nil; end; end; initialization OleInitialize(nil); InitDataFormats; finalization OleUninitialize; DestroyDataFormats; end.