unit MPDataObject;

// Version 1.7.0
//
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
//
// Alternatively, you may redistribute this library, use and/or modify it under the terms of the
// GNU Lesser General Public License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any later version.
// You may obtain a copy of the LGPL at http://www.gnu.org/copyleft/.
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
// specific language governing rights and limitations under the License.
//
// The initial developer of this code is Jim Kueneman <jimdk@mindspring.com>
// Special thanks to the following in no particular order for their help/support/code
//    Danijel Malik, Robert Lee, Werner Lehmann, Alexey Torgashin, Milan Vandrovec
//
//----------------------------------------------------------------------------

interface

{.$DEFINE GX_DEBUG}

{$I Compilers.inc}
{$I Options.inc}
{$I ..\Include\Debug.inc}
{$I ..\Include\Addins.inc}

uses
  Windows,
  Messages,
  SysUtils,
  Classes,
  Graphics,
  Controls,
  Forms,
  ActiveX,
  ShlObj,
  ShellAPI,
  {$IFDEF GX_DEBUG}
  DbugIntf,
  {$ENDIF}
  MPShellTypes,
  MPCommonUtilities,
  MPCommonObjects,
  {$IFDEF TNTSUPPORT}
  TntStdCtrls,
  TntClasses,
  TntWideStrings,
  {$ENDIF}
  AxCtrls;

const
  // Standard Shell Formats
  CFSTR_LOGICALPERFORMEDDROPEFFECT = 'Logical Performed DropEffect';
  CFSTR_PREFERREDDROPEFFECT = 'Preferred DropEffect';
  CFSTR_PERFORMEDDROPEFFECT = 'Performed DropEffect';
  CFSTR_PASTESUCCEEDED = 'Paste Succeeded';
  CFSTR_INDRAGLOOP = 'InShellDragLoop';
  CFSTR_SHELLIDLISTOFFSET = 'Shell Object Offsets';
  SIZE_SHELLDRAGLOOPDATA = 4;

type
  PPerformedDropEffect = ^TPerformedDropEffect;
  TPerformedDropEffect = (
    effectNone,    // No Operation (DROPEFFECT_NONE)
    effectCopy,    // Operation was a copy (DROPEFFECT_COPY)
    effectMove,    // Operation was a move (DROPEFFECT_MOVE)
    effectLink     // Operation was a link (DROPEFFECT_LINK)
  );

type
  TFormatEtcArray = array of TFormatEtc;
  TDataObjectInfo = record
    FormatEtc: TFormatEtc;
    StgMedium: TStgMedium;
    OwnedByDataObject: Boolean
  end;
  TDataObjectInfoArray = array of TDataObjectInfo;

type
  // This is just a WAG for the size.  I can't imagine it ever needing this
  // many formats
  TeltArray = array[0..255] of TFormatEtc;

  {$IFNDEF COMPILER_6_UP}
  PCardinal = ^Cardinal;
  {$ENDIF}
  
//------------------------------------------------------------------------------
// TCommonEnumFormatEtc :
//       Implements the IEnumFormatEtc interface for the IDataObject
// implementation.  This interface is called by a potential droptarget to see it
// the IDataObject contains data that the target knows how to handle and would
// like a shot at accepting it.
//-------------------------------------------------------------------------------

  TCommonEnumFormatEtc = class(TInterfacedObject, IEnumFormatEtc)
  private
    FInternalIndex: integer;
    FFormats: TFormatEtcArray;
  protected
    { IEnumFormatEtc }
    function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; stdcall;
    function Skip(celt: Longint): HResult; stdcall;
    function Reset: HResult; stdcall;
    function Clone(out Enum: IEnumFormatEtc): HResult; stdcall;

    property InternalIndex: integer read FInternalIndex write FInternalIndex;
  public
    constructor Create;
    destructor Destroy; override;

    procedure SetFormatLength(Size: Integer);
    property Formats: TFormatEtcArray read FFormats write FFormats;
  end;

//-------------------------------------------------------------------------------}
// TCommonDataObject :                                                                 }
//       Implements the IDataObject interface.  This interface is called by a    }
// potential droptarget to see it the IDataObject contains data that the target  }
// knows how to handle and would like a shot at accepting it.                    }
//-------------------------------------------------------------------------------}

  ICommonDataObject = interface(IDataObject)
    ['{F8B3EE47-C6C1-4FE3-9D94-757AA35DC038}']
    function AssignDragImage(Image: TBitmap; HotSpot: TPoint; TransparentColor: TColor): Boolean;
    function SaveGlobalBlock(Format: TClipFormat; MemoryBlock: Pointer; MemoryBlockSize: integer): Boolean;
    function LoadGlobalBlock(Format: TClipFormat; var MemoryBlock: Pointer): Boolean;
  end;

  TGetDataEvent = procedure(Sender: TObject; const FormatEtcIn: TFormatEtc; var Medium: TStgMedium; var Handled: Boolean) of object;
  TQueryGetDataEvent = procedure(Sender: TObject; const FormatEtcIn: TFormatEtc; var FormatAvailable: Boolean; var Handled: Boolean) of object;
         
  TCommonDataObject = class(TObject, IUnknown, IDataObject, ICommonDataObject, ICommonExtractObj)
  private
    FRefCount: Integer;
  protected
    FAdviseHolder: IDataAdviseHolder;
    FFormats: TDataObjectInfoArray;
    FOnGetData: TGetDataEvent;
    FOnQueryGetData: TQueryGetDataEvent;  // Reference to an OLE supplied implementation for advising.

    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;

    function CanonicalIUnknown(TestUnknown: IUnknown): IUnknown;
    function DAdvise(const formatetc: TFormatEtc; advf: Longint; const advSink: IAdviseSink; out dwConnection: Longint): HResult; virtual; stdcall;
    function DUnadvise(dwConnection: Longint): HResult; virtual; stdcall;
    function EnumDAdvise(out enumAdvise: IEnumStatData): HResult;virtual; stdcall;
    function EnumFormatEtc(dwDirection: Longint; out enumFormatEtc: IEnumFormatEtc): HResult;virtual; stdcall;
    function EqualFormatEtc(FormatEtc1, FormatEtc2: TFormatEtc): Boolean;
    function FindFormatEtc(TestFormatEtc: TFormatEtc): integer;
    function GetCanonicalFormatEtc(const formatetc: TFormatEtc; out formatetcOut: TFormatEtc): HResult;virtual; stdcall;
    function GetData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium): HResult;virtual; stdcall;
    function GetDataHere(const formatetc: TFormatEtc; out medium: TStgMedium): HResult;virtual; stdcall;
    function HGlobalClone(HGlobal: THandle): THandle;
    function QueryGetData(const formatetc: TFormatEtc): HResult;virtual; stdcall;
    function SetData(const formatetc: TFormatEtc; var medium: TStgMedium; fRelease: BOOL): HResult;virtual; stdcall;
    procedure DoGetCustomFormats(var Formats: TFormatEtcArray); virtual;
    procedure DoOnGetData(const FormatEtcIn: TFormatEtc; var Medium: TStgMedium; var Handled: Boolean); virtual;
    procedure DoOnQueryGetData(const FormatEtcIn: TFormatEtc; var FormatAvailable: Boolean; var Handled: Boolean); virtual;
    function RetrieveOwnedStgMedium(Format: TFormatEtc; var StgMedium: TStgMedium): HRESULT;
    function StgMediumIncRef(const InStgMedium: TStgMedium; var OutStgMedium: TStgMedium; CopyInMedium: Boolean): HRESULT;

    function GetObj: TObject;


    property AdviseHolder: IDataAdviseHolder read FAdviseHolder;
    property Formats: TDataObjectInfoArray read FFormats write FFormats;
    property Obj: TObject read GetObj;
    property RefCount: Integer read FRefCount;
  public
    constructor Create;
    destructor Destroy; override;
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    class function NewInstance: TObject; override;
    function AssignDragImage(Image: TBitmap; HotSpot: TPoint; TransparentColor: TColor): Boolean;
    function GetUserData(Format: TFormatEtc; var StgMedium: TStgMedium): Boolean; virtual;
    function LoadGlobalBlock(Format: TClipFormat; var MemoryBlock: Pointer): Boolean;
    function SaveGlobalBlock(Format: TClipFormat; MemoryBlock: Pointer; MemoryBlockSize: integer): Boolean;

    property OnGetData: TGetDataEvent read FOnGetData write FOnGetData;
    property OnQueryGetData: TQueryGetDataEvent read FOnQueryGetData write FOnQueryGetData;
  end;

  TCommonClipboardFormat = class
  public
    function GetFormatEtc: TFormatEtc; virtual;
    function LoadFromClipboard: Boolean; virtual;
    function LoadFromDataObject(DataObject: IDataObject): Boolean; virtual; abstract;
    function SaveToClipboard: Boolean; virtual;
    function SaveToDataObject(DataObject: IDataObject): Boolean; virtual; abstract;
  end;

  TCommonStreamClipFormat = class(TCommonClipboardFormat)
  public
    function GetFormatEtc: TFormatEtc; override;
    function LoadFromClipboard: Boolean; override;
    function LoadFromDataObject(DataObject: IDataObject; CoolStream: TCommonStream): Boolean; reintroduce;
    function SaveToClipboard: Boolean; override;
    function SaveToDataObject(DataObject: IDataObject; CoolStream: TCommonStream): Boolean; reintroduce;
  end;

// Simpifies dealing with the CFSTR_FILEGROUPDESCRIPTOR format
  TDescriptorAArray = array of TFileDescriptorA;
  TDescriptorWArray = array of TFileDescriptorW;

  TFileGroupDescriptorA = class(TCommonClipboardFormat)
  private
    FStream: TStream;
    function GetDescriptorCount: Integer;
    function GetFileDescriptorA(Index: Integer): TFileDescriptorA;
    procedure SetFileDescriptor(Index: Integer;
      const Value: TFileDescriptorA);
  protected
    FFileDescriptors: TDescriptorAArray;
    property Stream: TStream read FStream write FStream;
  public
    procedure AddFileDescriptor(FileDescriptor: TFileDescriptorA);
    procedure DeleteFileDescriptor(Index: integer);
    function GetFormatEtc: TFormatEtc; override;
    function FillDescriptor(FileName: string): TFileDescriptorA;
    function GetFileStream(const DataObject: IDataObject; FileIndex: Integer): TStream;
    procedure LoadFileGroupDestriptor(FileGroupDiscriptor: PFileGroupDescriptorA);
    function LoadFromClipboard: Boolean; override;
    function LoadFromDataObject(DataObject: IDataObject): Boolean; override;
    function SaveToClipboard: Boolean; override;
    function SaveToDataObject(DataObject: IDataObject): Boolean; override;

    property DescriptorCount: Integer read GetDescriptorCount;
    property FileDescriptor[Index: Integer]: TFileDescriptorA read GetFileDescriptorA write SetFileDescriptor;
  end;

  TFileGroupDescriptorW = class(TCommonClipboardFormat)
  private
    FStream: TStream;
    function GetDescriptorCount: Integer;
    function GetFileDescriptorW(Index: Integer): TFileDescriptorW;
    procedure SetFileDescriptor(Index: Integer;
      const Value: TFileDescriptorW);
  protected
    FFileDescriptors: TDescriptorWArray;
    property Stream: TStream read FStream write FStream;
  public
    procedure AddFileDescriptor(FileDescriptor: TFileDescriptorW);
    procedure DeleteFileDescriptor(Index: integer);
    function FillDescriptor(FileName: WideString): TFileDescriptorW;
    function GetFileStream(const DataObject: IDataObject; FileIndex: Integer): TStream;
    function GetFormatEtc: TFormatEtc; override;
    procedure LoadFileGroupDestriptor(FileGroupDiscriptor: PFileGroupDescriptorW);
    function LoadFromClipboard: Boolean; override;
    function LoadFromDataObject(DataObject: IDataObject): Boolean; override;
    function SaveToClipboard: Boolean; override;
    function SaveToDataObject(DataObject: IDataObject): Boolean; override;

    property DescriptorCount: Integer read GetDescriptorCount;
    property FileDescriptor[Index: Integer]: TFileDescriptorW read GetFileDescriptorW write SetFileDescriptor;
  end;

  // Simpifies dealing with the CF_HDROP format
  TCommonHDrop = class(TCommonClipboardFormat)
  private
    procedure SetDropFiles(const Value: PDropFiles);
    function GetHDropStruct: THandle;
  protected
    FDropFiles: PDropFiles;
    FStructureSize: integer;
    FFileCount: integer;

    procedure AllocStructure(Size: integer);
    function CalculateDropFileStructureSizeA(Value: PDropFiles): integer;
    function CalculateDropFileStructureSizeW(Value: PDropFiles): integer;
    function FileCountA: Integer;
    function FileCountW: Integer;
    function FileNameA(Index: integer): string;
    function FileNameW(Index: integer): WideString;
    procedure FreeStructure; // Frees memory allocated
  public
    destructor Destroy; override;
    function AssignFromClipboard: Boolean;
    function LoadFromClipboard: Boolean; override;
    function LoadFromDataObject(DataObject: IDataObject): Boolean; override;
    function FileCount: integer;
    function FileName(Index: integer): WideString;
    function GetFormatEtc: TFormatEtc; override;
    procedure AssignFilesA(FileList: TStringList);
    {$IFDEF TNTSUPPORT}
    procedure AssignFilesW(FileList: TWideStrings);
    procedure FileNamesW(FileList: TWideStrings);
    {$ENDIF}
    procedure FileNamesA(FileList: TStrings);

    property HDropStruct: THandle read GetHDropStruct;
    function SaveToClipboard: Boolean; override;
    function SaveToDataObject(DataObject: IDataObject): Boolean; override;
    property StructureSize: integer read FStructureSize;
    property DropFiles: PDropFiles read FDropFiles write SetDropFiles;
  end;

  // Simpifies dealing with the CFSTR_SHELLIDLIST format
type
  TCommonShellIDList = class(TCommonClipboardFormat)
  private
    FCIDA: PIDA;
    function GetCIDASize: integer;
    function InternalChildPIDL(Index: integer): PItemIDList;
    function InternalParentPIDL: PItemIDList;
    procedure SetCIDA(const Value: PIDA);
  public
    function AbsolutePIDL(Index: integer): PItemIDList;
    procedure AbsolutePIDLs(APIDLList: TCommonPIDLList);
    procedure AssignPIDLs(APIDLList: TCommonPIDLList);
    destructor Destroy; override;
    function GetFormatEtc: TFormatEtc; override;
    function LoadFromClipboard: Boolean; override;
    function LoadFromDataObject(DataObject: IDataObject): Boolean; override;
    function ParentPIDL: PItemIDList;
    function PIDLCount: integer;
    function RelativePIDL(Index: integer): PItemIDList;
    procedure RelativePIDLs(APIDLList: TCommonPIDLList);
    function SaveToClipboard: Boolean; override;
    function SaveToDataObject(DataObject: IDataObject): Boolean; override;

    property CIDA: PIDA read FCIDA write SetCIDA;
    property CIDASize: integer read GetCIDASize;
  end;

  // Simpilies dealing with the CFSTR_LOGICALPERFORMEDDROPEFFECT format
  TCommonLogicalPerformedDropEffect = class(TCommonClipboardFormat)
  private
    FAction: TPerformedDropEffect;
  public
    function GetFormatEtc: TFormatEtc; override;
    function LoadFromDataObject(DataObject: IDataObject): Boolean; override;
    function SaveToDataObject(DataObject: IDataObject): Boolean; override;

    property Action: TPerformedDropEffect read FAction write FAction;
  end;

  // Simpilies dealing with the CFSTR_PerferredDropEffect format
  TCommonPreferredDropEffect = class(TCommonLogicalPerformedDropEffect)
  public
    function GetFormatEtc: TFormatEtc; override;
  end;

  TCommonInShellDragLoop = class(TCommonClipboardFormat)
  private
    FData: Cardinal;
  public
    function GetFormatEtc: TFormatEtc; override;
    function LoadFromDataObject(DataObject: IDataObject): Boolean; override;
    function SaveToDataObject(DataObject: IDataObject): Boolean; override;
    property Data: Cardinal read FData write FData;
  end;

  function FillFormatEtc(cfFormat: Word; ptd: PDVTargetDevice = nil;
    dwAspect: Longint = DVASPECT_CONTENT; lindex: Longint = -1; tymed: Longint = TYMED_HGLOBAL): TFormatEtc;
  function DataObjectSupportsShell(const DataObj: IDataObject): Boolean;
  function DataObjectContainsPIDL(APIDL: PItemIDList; const DataObj: IDataObject): Boolean;

  function HDropFormat: TFormatEtc;
  function ShellIDListFormat: TFormatEtc;
  function FileDescriptorAFormat: TFormatEtc;
  function FileDescriptorWFormat: TFormatEtc;

var
  CF_SHELLIDLIST,
  CF_PERFORMEDDROPEFFECT,
  CF_PASTESUCCEEDED,
  CF_INDRAGLOOP,
  CF_SHELLIDLISTOFFSET,
  CF_LOGICALPERFORMEDDROPEFFECT,
  CF_PREFERREDDROPEFFECT,
  CF_FILECONTENTS,
  CF_FILEDESCRIPTORA,
  CF_FILEDESCRIPTORW: TClipFormat;

implementation

var
  PIDLMgr: TCommonPIDLManager;
  ShellILIsEqual: function(PIDL1: PItemIDList; PIDL2: PItemIDList): LongBool; stdcall;
  ShellILIsEqualChecked: Boolean = False;


function LoadShellILIsEqual: Boolean;
begin
  if not ShellILIsEqualChecked then
  begin
    ShellILIsEqual := GetProcAddress(GetModuleHandle(PChar(Shell32)), PChar(21));
    ShellILIsEqualChecked := True;
  end;
  Result := Assigned(ShellILIsEqual)
end;

function DataObjectContainsPIDL(APIDL: PItemIDList; const DataObj: IDataObject): Boolean;
var
  ShellIDList: TCommonShellIDList;
  i: Integer;
  P: PItemIDList;
begin
  Result := False;
  ;
  if Assigned(DataObj) and Assigned(APIDL) and LoadShellILIsEqual then
  begin
    if Succeeded(DataObj.QueryGetData(ShellIDListFormat)) then
    begin
      ShellIDList := TCommonShellIDList.Create;
      try
        if ShellIDList.LoadFromDataObject(DataObj) then
        begin
          i := 0;
          while not Result and (i < ShellIDList.PIDLCount) do
          begin
            P := ShellIDList.AbsolutePIDL(i);
            Result := ShellILIsEqual(P, APIDL);
            PIDLMgr.FreePIDL(P);
            Inc(i)
          end
        end
      finally
        ShellIDList.Free
      end
    end
  end
end;

function DataObjectSupportsShell(const DataObj: IDataObject): Boolean;
begin
  Result := False;
  if Assigned(DataObj) then
  begin
    Result := Succeeded(DataObj.QueryGetData(HDropFormat)) or
      Succeeded(DataObj.QueryGetData(ShellIDListFormat)) or
      Succeeded(DataObj.QueryGetData(FileDescriptorAFormat)) or
      Succeeded(DataObj.QueryGetData(FileDescriptorWFormat))
  end
end;

function HDropFormat: TFormatEtc;
begin
  Result.cfFormat := CF_HDROP; // This guy is always registered for all applications
  Result.ptd := nil;
  Result.dwAspect := DVASPECT_CONTENT;
  Result.lindex := -1;
  Result.tymed := TYMED_HGLOBAL
end;

function ShellIDListFormat: TFormatEtc;
begin
  Result.cfFormat := CF_SHELLIDLIST;
  Result.ptd := nil;
  Result.dwAspect := DVASPECT_CONTENT;
  Result.lindex := -1;
  Result.tymed := TYMED_HGLOBAL;
end;

function FileDescriptorAFormat: TFormatEtc; 
begin 
  Result.cfFormat := CF_FILEDESCRIPTORA; 
  Result.ptd := nil; 
  Result.dwAspect := DVASPECT_CONTENT; 
  Result.lindex := -1; 
  Result.tymed := TYMED_HGLOBAL 
end; 

function FileDescriptorWFormat: TFormatEtc; 
begin 
  Result.cfFormat := CF_FILEDESCRIPTORW; 
  Result.ptd := nil; 
  Result.dwAspect := DVASPECT_CONTENT; 
  Result.lindex := -1; 
  Result.tymed := TYMED_HGLOBAL 
end;

function FillFormatEtc(cfFormat: Word; ptd: PDVTargetDevice = nil;
  dwAspect: Longint = DVASPECT_CONTENT; lindex: Longint = -1; tymed: Longint = TYMED_HGLOBAL): TFormatEtc;
begin
  Result.cfFormat := cfFormat;
  Result.ptd := ptd;
  Result.dwAspect := dwAspect;
  Result.lindex := lindex;
  Result.tymed := tymed
end;

{ TCommonClipboardFormat }

function TCommonClipboardFormat.GetFormatEtc: TFormatEtc;
begin
  FillChar(Result, SizeOf(Result), #0);
end;

function TCommonClipboardFormat.LoadFromClipboard: Boolean;
begin
  Result := False;
end;

function TCommonClipboardFormat.SaveToClipboard: Boolean;
begin
  Result := False;
end;

{ TLogicalPerformedDropEffect }

function TCommonLogicalPerformedDropEffect.GetFormatEtc: TFormatEtc;
begin
  Result.cfFormat := CF_LOGICALPERFORMEDDROPEFFECT;
  Result.ptd := nil;
  Result.dwAspect := DVASPECT_CONTENT;
  Result.lindex := -1;
  Result.tymed := TYMED_HGLOBAL
end;

function TCommonLogicalPerformedDropEffect.LoadFromDataObject(DataObject: IDataObject): Boolean;
var
  Ptr: PPerformedDropEffect;
  StgMedium: TStgMedium;
begin
  Result := False;
  FillChar(StgMedium, SizeOf(StgMedium), #0);

  if Succeeded(DataObject.GetData(GetFormatEtc, StgMedium)) then
  try
    Ptr := GlobalLock(StgMedium.hGlobal);
    try
      if Assigned(Ptr) then
      begin
        FAction := Ptr^;
        Result := True;
      end;
    finally
      GlobalUnLock(StgMedium.hGlobal);
    end
  finally
    ReleaseStgMedium(StgMedium)
  end
end;

function TCommonLogicalPerformedDropEffect.SaveToDataObject(DataObject: IDataObject): Boolean;
var
  Ptr: PPerformedDropEffect;
  StgMedium: TStgMedium;
begin
  FillChar(StgMedium, SizeOf(StgMedium), #0);

  StgMedium.hGlobal := GlobalAlloc(GPTR, SizeOf(FAction));
  Ptr := GlobalLock(StgMedium.hGlobal);
  try
    Ptr^ := FAction;
    StgMedium.tymed := TYMED_HGLOBAL;
    Result := Succeeded(DataObject.SetData(GetFormatEtc, StgMedium, True))
  finally
    GlobalUnLock(StgMedium.hGlobal);
  end
end;

{ THDrop }

procedure TCommonHDrop.AllocStructure(Size: integer);
begin
  FreeStructure;
  GetMem(FDropFiles, Size);
  FStructureSize := Size;
  FillChar(FDropFiles^, Size, #0);
end;

procedure TCommonHDrop.AssignFilesA(FileList: TStringList);
var
  i: Integer;
  Size: integer;
  Path: PChar;
begin
  if Assigned(FileList) then
  begin
    FreeStructure;
    Size := 0;
    for i := 0 to FileList.Count - 1 do
      Inc(Size, Length(FileList[i]) + SizeOf(Char)); // add spot for the null
    Inc(Size, SizeOf(TDropFiles));
    Inc(Size, SizeOf(Char)); // room for the terminating null
    AllocStructure(Size);
    DropFiles.pFiles := SizeOf(TDropFiles);
    DropFiles.pt.x := 0;
    DropFiles.pt.y := 0;
    DropFiles.fNC := False;
    DropFiles.fWide := False;  // Don't support wide char let NT convert it
    Path := PChar(FDropFiles) + FDropFiles.pFiles;
    for i := 0 to FileList.Count - 1 do
    begin
      MoveMemory(Path, Pointer(FileList[i]), Length(FileList[i]));
      Inc(Path, Length(FileList[i]) + 1); // skip over the single null #0
    end
  end
end;

function TCommonHDrop.AssignFromClipboard: Boolean;
var
  Handle: THandle;
  Ptr: PDropFiles;
begin
  Result := False;
  Handle := 0;
  OpenClipboard(Application.Handle);
  try
    Handle := GetClipboardData(CF_HDROP);
    if Handle <> 0 then
    begin
      Ptr := GlobalLock(Handle);
      if Assigned(Ptr) then
      begin
        DropFiles := Ptr;
        Result := True;
      end;
    end;
  finally
    CloseClipboard;
    GlobalUnLock(Handle);
  end;
end;

function TCommonHDrop.CalculateDropFileStructureSizeA(
  Value: PDropFiles): integer;
var
  Head: PChar;
  Len: integer;
begin
  if Assigned(Value) then
  begin
    Result := Value^.pFiles;
    Head := PChar( Value) + Value^.pFiles;
    Len := lstrlen(Head);
    while Len > 0 do
    begin
      Result := Result + Len + 1;
      Head := Head + Len + 1;
       Len := lstrlen(Head);
    end;
    Inc(Result, 1); // Add second null
  end else
    Result := 0
end;

function TCommonHDrop.CalculateDropFileStructureSizeW(
  Value: PDropFiles): integer;
var
  Head: PChar;
  Len: integer;
begin
  if Assigned(Value) then
  begin
    Result := Value^.pFiles;
    Head := PChar( Value) + Value^.pFiles;
    Len := 2 * (lstrlenW(PWideChar( Head)));
    while Len > 0 do
    begin
      Result := Result + Len + 2;
      Head := Head + Len + 2;
       Len := 2 * (lstrlenW(PWideChar( Head)));
    end;
    Inc(Result, 2); // Add second null
  end else
    Result := 0
end;

destructor TCommonHDrop.Destroy;
begin
  FreeStructure;
  inherited;
end;

function TCommonHDrop.FileCount: integer;
begin
  if Assigned(DropFiles) then
  begin
    if FFileCount = 0 then
    begin
      if DropFiles.fWide then
        Result := FileCountW
      else
        Result := FileCountA;
       FFileCount := Result;
    end;
  end else
   FFileCount := 0;
  Result := FFileCount
end;

function TCommonHDrop.FileCountA: Integer;
var
  Head: PChar;
  Len: integer;
begin
  Result := 0;
  if Assigned(DropFiles) then
  begin
    Head := PChar( DropFiles) + DropFiles^.pFiles;
    Len := lstrlen(Head);
    while Len > 0 do
    begin
      Head := Head + Len + 1;
      Inc(Result);
      Len := lstrlen(Head);
    end
  end
end;

function TCommonHDrop.FileCountW: Integer;
var
  Head: PChar;
  Len: integer;
begin
  Result := 0;
  if Assigned(DropFiles) then
  begin
    Head := PChar( DropFiles) + DropFiles^.pFiles;
    Len := 2 * (lstrlenW(PWideChar( Head)));
    while Len > 0 do
    begin
      Head := Head + Len + 2;
      Inc(Result);
      Len := 2 * (lstrlenW(PWideChar( Head)));
    end
  end;
end;

function TCommonHDrop.FileName(Index: integer): WideString;
begin
  if Assigned(DropFiles) then
  begin
    if DropFiles.fWide then
      Result := FileNameW(Index)
    else
      Result := FileNameA(Index)
  end
end;

function TCommonHDrop.FileNameA(Index: integer): string;
var
  Head: PChar;
  PathNameCount: integer;
  Done: Boolean;
  Len: integer;
begin
  PathNameCount := 0;
  Done := False;
  if Assigned(DropFiles) then
  begin
    Head := PChar( DropFiles) + DropFiles^.pFiles;
    Len := lstrlen(Head);
    while (not Done) and (PathNameCount < FileCount) do
    begin
      if PathNameCount = Index then
      begin
        SetLength(Result, Len + 1);
        CopyMemory(@Result[1], Head, Len + 1); // Include the NULL
        Done := True;
      end;
      Head := Head + Len + 1;
      Inc(PathNameCount);
      Len := lstrlen(Head);
    end
  end
end;

{$IFDEF TNTSUPPORT}
procedure TCommonHDrop.AssignFilesW(FileList: TWideStrings);
var
  i: Integer;
  Size: integer;
  Path: PChar;
  ByteSize: Integer;
begin
  if Assigned(FileList) then
  begin
    FreeStructure;
    Size := 0;
    if UnicodeStringLists then
      ByteSize := 2
    else
      ByteSize := 1;
    for i := 0 to FileList.Count - 1 do
      Inc(Size, (Length(FileList[i])+1)*(SizeOf(Char)*ByteSize)); // add spot for the null
    Inc(Size, SizeOf(TDropFiles));
    Inc(Size, SizeOf(Char)*2); // room for the terminating null
    AllocStructure(Size);
    DropFiles.pFiles := SizeOf(TDropFiles);
    DropFiles.pt.x := 0;
    DropFiles.pt.y := 0;
    DropFiles.fNC := False;
    DropFiles.fWide := UnicodeStringLists;
    Path := PChar(FDropFiles) + FDropFiles.pFiles;
    for i := 0 to FileList.Count - 1 do
    begin
      MoveMemory(Path, Pointer(FileList[i]), Length(FileList[i])*ByteSize);
      Inc(Path, (Length(FileList[i]) + 1)*ByteSize); // skip over the single null #0
    end
  end
end;

procedure TCommonHDrop.FileNamesW(FileList: TWideStrings);
var
  i: integer;
begin
  if Assigned(FileList) then
  begin
    for i := 0 to FileCount - 1 do
      FileList.Add(FileNameW(i));
  end;
end;
{$ENDIF}

procedure TCommonHDrop.FileNamesA(FileList: TStrings);
var
  i: integer;
begin
  if Assigned(FileList) then
  begin
    for i := 0 to FileCount - 1 do
      FileList.Add(FileName(i));
  end;
end;

function TCommonHDrop.FileNameW(Index: integer): WideString;
var
  Head: PChar;
  PathNameCount: integer;
  Done: Boolean;
  Len: integer;
begin
  PathNameCount := 0;
  Done := False;
  if Assigned(DropFiles) then
  begin
    Head := PChar( DropFiles) + DropFiles^.pFiles;
    Len := 2 * (lstrlenW(PWideChar( Head)));
    while (not Done) and (PathNameCount < FileCount) do
    begin
      if PathNameCount = Index then
      begin
        SetLength(Result, (Len + 1) div 2);
        CopyMemory(@Result[1], Head, Len + 2); // Include the NULL
        Done := True;
      end;
      Head := Head + Len + 2;
      Inc(PathNameCount);
      Len := 2 * (lstrlenW(PWideChar( Head)));
    end
  end
end;

procedure TCommonHDrop.FreeStructure;
begin
  FFileCount := 0;
  if Assigned(FDropFiles) then
    FreeMem(FDropFiles, FStructureSize);
  FDropFiles := nil;
  FStructureSize := 0;
end;

function TCommonHDrop.GetFormatEtc: TFormatEtc;
begin
  Result.cfFormat := CF_HDROP; // This guy is always registered for all applications
  Result.ptd := nil;
  Result.dwAspect := DVASPECT_CONTENT;
  Result.lindex := -1;
  Result.tymed := TYMED_HGLOBAL
end;

function TCommonHDrop.GetHDropStruct: THandle;
var
  Files: PDropFiles;
begin
  Result := GlobalAlloc(GHND, StructureSize);
  Files := GlobalLock(Result);
  try
    MoveMemory(Files, FDropFiles, StructureSize);
  finally
    GlobalUnlock(Result)
  end;
end;

function TCommonHDrop.LoadFromClipboard: Boolean;
var
  Handle: THandle;
  Ptr: PDropFiles;
begin
  Result := False;
  Handle := 0;
  OpenClipboard(Application.Handle);
  try
    Handle := GetClipboardData(CF_HDROP);
    if Handle <> 0 then
    begin
      Ptr := GlobalLock(Handle);
      if Assigned(Ptr) then
      begin
        DropFiles := Ptr;
        Result := True;
      end;
    end;
  finally
    CloseClipboard;
    GlobalUnLock(Handle);
  end;
end;

function TCommonHDrop.LoadFromDataObject(DataObject: IDataObject): Boolean;
var
  Medium: TStgMedium;
  Files: PDropFiles;
begin
  Result := False;
  if Assigned(DataObject) then
  begin
    if Succeeded(DataObject.GetData(GetFormatEtc, Medium)) then
    try
      Files := GlobalLock(Medium.hGlobal);
      try
        DropFiles := Files
      finally
        GlobalUnlock(Medium.hGlobal)
      end
    finally
      ReleaseStgMedium(Medium)
    end;
    Result := Assigned(DropFiles)
  end
end;

function TCommonHDrop.SaveToClipboard: Boolean;
begin
  Result := False;
  OpenClipboard(Application.Handle);
  try
    SetClipboardData(CF_HDROP, HDropStruct)
  finally
    CloseClipboard;
  end;
end;

function TCommonHDrop.SaveToDataObject(DataObject: IDataObject): Boolean;
var
  Medium: TStgMedium;
begin
  Result := False;
  FillChar(Medium, SizeOf(Medium), #0);
  Medium.tymed := TYMED_HGLOBAL;
  Medium.hGlobal := HDropStruct;
  // Give the block to the DataObject
  if Succeeded(DataObject.SetData(GetFormatEtc, Medium, True)) then
    Result := True
  else
    GlobalFree(Medium.hGlobal)
end;

procedure TCommonHDrop.SetDropFiles(const Value: PDropFiles);
begin
  FreeStructure;
  if Assigned(Value) then
  begin
    if Value.fWide then
      FStructureSize := CalculateDropFileStructureSizeW(Value)
    else
      FStructureSize := CalculateDropFileStructureSizeA(Value);
    AllocStructure(StructureSize);
    CopyMemory(FDropFiles, Value, StructureSize);
  end;
end;

{ TPreferredDropEffect }

function TCommonPreferredDropEffect.GetFormatEtc: TFormatEtc;
begin
  Result.cfFormat := CF_PREFERREDDROPEFFECT;
  Result.ptd := nil;
  Result.dwAspect := DVASPECT_CONTENT;
  Result.lindex := -1;
  Result.tymed := TYMED_HGLOBAL
end;

{ TCoolStreamClipFormat }

function TCommonStreamClipFormat.GetFormatEtc: TFormatEtc;
begin
  Result.cfFormat := 0;
  Result.ptd := nil;
  Result.dwAspect := DVASPECT_CONTENT;
  Result.lindex := -1;
  Result.tymed := TYMED_HGLOBAL
end;

function TCommonStreamClipFormat.LoadFromClipboard: Boolean;
begin
  Result := False;
  Assert(True=False, 'TCoolStream.LoadFromClipboard not Implemented');
end;

function TCommonStreamClipFormat.LoadFromDataObject(DataObject: IDataObject;
  CoolStream: TCommonStream): Boolean;
var
  Medium: TStgMedium;
  PMem, PSize: Pointer;
  Size: Int64;
begin
  Result := False;
  if Succeeded(DataObject.GetData(GetFormatEtc, Medium)) then
  begin
    PMem := GlobalLock(Medium.hGlobal);
    try
      PSize := @Size;
      // Get the size of the Stream
      MoveMemory(PMem, PSize, SizeOf(Int64));

      CoolStream.Seek(0, soFromBeginning);
      CoolStream.SetSize(Size);

      Inc(PChar( PMem), SizeOf(Int64));

      // Copy the data to the stream
      MoveMemory(PMem, CoolStream.Memory, Size);
    finally
      GlobalUnlock(Medium.hGlobal);
      ReleaseStgMedium(Medium);
    end
  end
end;

function TCommonStreamClipFormat.SaveToClipboard: Boolean;
begin
  Result := False;
  Assert(True=False, 'TCoolStream.SaveToClipboard not Implemented');
end;

function TCommonStreamClipFormat.SaveToDataObject(DataObject: IDataObject;
  CoolStream: TCommonStream): Boolean;
var
  Medium: TStgMedium;
  Mem: HGlobal;
  PMem: Pointer;
  Size: Int64;
  PSize: Pointer;
begin
  Mem := GlobalAlloc(GMEM_SHARE  or GMEM_MOVEABLE, CoolStream.Size + SizeOf(Int64));
  PMem := GlobalLock(Mem);
  try
    FillChar(Medium, SizeOf(Medium), #0);
    
    PSize := @Size;
    
    // Write the size of the Stream to the block
    Size := CoolStream.Size;
    MoveMemory(PMem, PSize, SizeOf(Int64));

    // Copy the Stream to a global memory block
    MoveMemory(PMem, CoolStream.Memory, CoolStream.Size);

    // Transfer the data with a global memory block
    Medium.tymed := TYMED_HGLOBAL;
    Medium.hGlobal := Mem;

    // Give it to the DataObject so it may destroy it when the clients are done
    Result := Succeeded(DataObject.SetData(GetFormatEtc, Medium, True))
  finally
    GlobalUnlock(Mem)
  end
end;

{ TShellIDList }

function TCommonShellIDList.AbsolutePIDL(Index: integer): PItemIDList;
{ Appends the single ItemID with the Parent folder to create an Absolute PIDL }
begin
  if Assigned(FCIDA) then
  begin
    Result := PIDLMgr.AppendPIDL(InternalParentPIDL, InternalChildPIDL(Index));
  end else
    Result := nil
end;

procedure TCommonShellIDList.AbsolutePIDLs(APIDLList: TCommonPIDLList);
var
  i: integer;
begin
  if Assigned(APIDLList) and Assigned(FCIDA) then
  begin
    for i := 0 to PIDLCount - 1 do
      APIDLList.Add( PIDLMgr.AppendPIDL(InternalParentPIDL, InternalChildPIDL(i)))
  end;
end;

procedure TCommonShellIDList.AssignPIDLs(APIDLList: TCommonPIDLList);
{ PIDLs[0] must be the Absolute Parent PIDL and the rest single ItemID children }
var
  Count: Integer;
  i: Integer;
  Head: Pointer;
  PIDLLength: Integer;
begin
  Count := 0;
  if Assigned(APIDLList) then
  begin
    { Free previously assigned CIDA }
    if Assigned(FCIDA) then
      FreeMem(FCIDA, CIDASize);
    FCIDA := nil;
    Inc(Count, SizeOf(FCIDA.cidl));
    Inc(Count, SizeOf(FCIDA.aoffset) * (APIDLList.Count));
    for i := 0 to APIDLList.Count - 1 do
      Inc(Count, PIDLMgr.PIDLSize( APIDLList[i]));
    GetMem(FCIDA, Count);
    Head := FCIDA;
    { Head points to the position of the first PIDL }
    Inc(PChar(Head),  SizeOf(FCIDA.cidl) + (SizeOf(FCIDA.aoffset) * APIDLList.Count));
    { Don't count the absolute parent PIDL }
    FCIDA.cidl := APIDLList.Count - 1;
    for i := 0 to APIDLList.Count - 1 do
    begin
      { Set up the array index to point to the actual PIDL data }
      FCIDA.aoffset[i] := LongWord(Head-PChar( CIDA));
      PIDLLength := PIDLMgr.PIDLSize(APIDLList[i]);
      Move(APIDLList[i]^, Head^, PIDLLength);
      Inc(PChar(Head), PIDLLength);
    end;
  end;
end;

destructor TCommonShellIDList.Destroy;
begin
  { Free previously assigned CIDA }
  if Assigned(FCIDA) then
    FreeMem(FCIDA, CIDASize);
  inherited;
end;

function TCommonShellIDList.GetCIDASize: integer;
var
  Count: integer;
  i: integer;
begin
  Count := 0;
  if Assigned(FCIDA) then
  begin
    Inc(Count, SizeOf( FCIDA.cidl));
    Inc(Count, SizeOf( FCIDA.aoffset) * (PIDLCount + 1)); // Does't count [0]
    Inc(Count, PIDLMgr.PIDLSize(InternalParentPIDL));
    for i := 0 to PIDLCount - 1 do
      Inc(Count, PIDLMgr.PIDLSize(InternalChildPIDL(i)));
  end;
  Result := Count;
end;

function TCommonShellIDList.GetFormatEtc: TFormatEtc;
begin
  Result.cfFormat := CF_SHELLIDLIST;
  Result.ptd := nil;
  Result.dwAspect := DVASPECT_CONTENT;
  Result.lindex := -1;
  Result.tymed := TYMED_HGLOBAL;
end;

function TCommonShellIDList.InternalChildPIDL(Index: integer): PItemIDList;
{ Remember PIDLCount does not count index [0] where the Absolute Parent is     }
begin
  if Assigned(FCIDA) and (Index > -1) and (Index < PIDLCount) then
    Result := PItemIDList( PChar(FCIDA) + PDWORD(PChar(@FCIDA^.aoffset)+sizeof(FCIDA^.aoffset[0])*(1+Index))^)
  else
    Result := nil
end;

function TCommonShellIDList.InternalParentPIDL: PItemIDList;
{ Remember PIDLCount does not count index [0] where the Absolute Parent is     }
begin
  if Assigned(FCIDA) then
      Result :=  PItemIDList( PChar(FCIDA) + FCIDA^.aoffset[0])
  else
    Result := nil
end;

function TCommonShellIDList.LoadFromClipboard: Boolean;
var
  Handle: THandle;
  Ptr: PIDA;
begin
  Result := True;
  Handle := 0;
  if Result then
  begin
    try
      try
        Handle := GetClipboardData(CF_SHELLIDLIST);
        if Handle <> 0 then
        begin
          Ptr := GlobalLock(Handle);
          if Assigned(Ptr) then
          begin
            CIDA := Ptr;
            Result := True;
          end;
        end;
      except
        Result := False;
        raise;
      end;
    finally
      GlobalUnLock(Handle);
    end;
  end
end;

function TCommonShellIDList.LoadFromDataObject(DataObject: IDataObject): Boolean;
var
  Ptr: PIDA;
  StgMedium: TStgMedium;
begin
  Result := False;
  FillChar(StgMedium, SizeOf(StgMedium), #0);
  if Succeeded(DataObject.GetData(GetFormatEtc, StgMedium)) then
  try
    Ptr := GlobalLock(StgMedium.hGlobal);
    try
      if Assigned(Ptr) then
      begin
        CIDA := Ptr;
        Result := True;
      end;
    finally
      GlobalUnLock(StgMedium.hGlobal);
    end;
  finally
    ReleaseStgMedium(StgMedium)
  end;
end;

function TCommonShellIDList.ParentPIDL: PItemIDList;
begin
  Result := PIDLMgr.CopyPIDL( InternalParentPIDL)
end;

function TCommonShellIDList.PIDLCount: integer;
{ indexing is a bit weird.  Index 0 is the Absolute Parent PIDL but it is not }
{ counted in the first byte of the structure.                                 }
begin
  if Assigned(FCIDA) then
    Result := FCIDA^.cidl
  else
    Result := 0
end;

function TCommonShellIDList.RelativePIDL(Index: integer): PItemIDList;
{ Retrieves the single ItemID child by index                                    }
begin
  Result := PIDLMgr.CopyPIDL( InternalChildPIDL(Index))
end;

procedure TCommonShellIDList.RelativePIDLs(APIDLList: TCommonPIDLList);
{ Loads APIDLList with PIDL's stored in the CIDA. ReturnCopy flags if the       }
{ contents will be the origionals or copies created by the PIDLMgr.             }
var
  i: integer;
begin
  if Assigned(APIDLList) and Assigned(FCIDA) then
  begin
    APIDLList.CopyAdd( InternalParentPIDL);
    for i := 0 to PIDLCount - 1 do
      APIDLList.CopyAdd( InternalChildPIDL(i))
  end;
end;

function TCommonShellIDList.SaveToClipboard: Boolean;
var
  DataObject: IDataObject;
begin
  Result := False;
  DataObject := TCommonDataObject.Create;
  if SaveToDataObject(DataObject) then
    Result := Succeeded(OleSetClipboard(DataObject))
end;

function TCommonShellIDList.SaveToDataObject(DataObject: IDataObject): Boolean;
var
  StgMedium: TStgMedium;
  Ptr: PIDA;
begin
  FillChar(StgMedium, SizeOf(StgMedium), #0);

  StgMedium.hGlobal := GlobalAlloc(GPTR, GetCIDASize);
  Ptr := GlobalLock(StgMedium.hGlobal);
  try
    StgMedium.tymed := TYMED_HGLOBAL;
    CopyMemory(Ptr, CIDA, GetCIDASize);
    Result := Succeeded(DataObject.SetData(GetFormatEtc, StgMedium, True))
  finally
    GlobalUnLock(StgMedium.hGlobal);
  end;
end;

procedure TCommonShellIDList.SetCIDA(const Value: PIDA);
var
  TempSize: integer;
begin
  { Free previously assigned CIDA }
  if Assigned(FCIDA) then
  begin
    FreeMem(FCIDA, CIDASize);
    FCIDA := nil;
  end;
  if Value <> nil then
  begin
    { Temporally assign the passed PIDA to the object }
    FCIDA := Value;
    { Get the size of the passed PIDA }
    TempSize := CIDASize;
    { Get memory to make a copy of the passed PIDA }
    GetMem(FCIDA, TempSize);
    { Copy the passed PIDA }
    Move(Value^, FCIDA^, TempSize);
  end;
end;

{ TCommonEnumFormatEtc }

function TCommonEnumFormatEtc.Clone(out Enum: IEnumFormatEtc): HResult;
// Creates a exact copy of the current object.
var
  EnumFormatEtc: TCommonEnumFormatEtc;
begin
  Result := S_OK;   // Think positive
  EnumFormatEtc := TCommonEnumFormatEtc.Create;      // Does not increase COM reference
  if Assigned(EnumFormatEtc) then
  begin
    SetLength(EnumFormatEtc.FFormats, Length(Formats));
    // Make copy of Format info
    Move(FFormats[0], EnumFormatEtc.FFormats[0], Length(Formats) * SizeOf(TFormatEtc));
    EnumFormatEtc.InternalIndex := InternalIndex;
    Enum := EnumFormatEtc as IEnumFormatEtc;   // Sets COM reference to 1
  end else
    Result := E_UNEXPECTED
end;

constructor TCommonEnumFormatEtc.Create;
begin
  inherited Create;
  InternalIndex := 0;
end;

destructor TCommonEnumFormatEtc.Destroy;
begin
  inherited;
end;

function TCommonEnumFormatEtc.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult;
// Another EnumXXXX function.  This function returns the number of objects
// requested by the caller in celt.  The return buffer, elt, is a pointer to an}
// array of, in this case, TFormatEtc structures.  The total number of
// structures returned is placed in pceltFetched.  pceltFetched may be nil if
// celt is only asking for one structure at a time.
var
  i: integer;
begin
  if Assigned(Formats) then
  begin
    i := 0;
    while (i < celt) and (InternalIndex < Length(Formats)) do
    begin
      TeltArray( elt)[i] := Formats[InternalIndex];
      inc(i);
      inc(FInternalIndex);
    end; // while
    if assigned(pceltFetched) then
      pceltFetched^ := i;
    if i = celt then
      Result := S_OK
    else
      Result := S_FALSE
  end else
    Result := E_UNEXPECTED
end;

function TCommonEnumFormatEtc.Reset: HResult;
begin
  InternalIndex := 0;
  Result := S_OK
end;

function TCommonEnumFormatEtc.Skip(celt: Integer): HResult;
// Allows the caller to skip over unwanted TFormatEtc structures.  Simply adds
// celt to the index as long as it does not skip past the last structure in
// the list.
begin
  if Assigned(Formats) then
  begin
    if InternalIndex + celt < Length(Formats) then
    begin
      InternalIndex := InternalIndex + celt;
      Result := S_OK
    end else
      Result := S_FALSE
  end else
    Result := E_UNEXPECTED
end;

procedure TCommonEnumFormatEtc.SetFormatLength(Size: Integer);
begin
  SetLength(FFormats, Size)
end;

{ TCommonDataObject }
class function TCommonDataObject.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TCommonDataObject(Result).FRefCount := 1;
end;


function TCommonDataObject.AssignDragImage(Image: TBitmap;
  HotSpot: TPoint; TransparentColor: TColor): Boolean;
//
// Stores the Bitmap in the IDataObject to support the IDragSourceHelper drag image
// in Win2K and above.
//
var
  DragSourceHelper: IDragSourceHelper;
  SHDragImage: TSHDragImage;
begin
  Result := False;
  // NT can't swallow this CoCreateInstance call
  if not IsWinNT4 then
  begin
    if Succeeded(CoCreateInstance(CLSID_DragDropHelper, nil, CLSCTX_INPROC_SERVER, IID_IDragSourceHelper, DragSourceHelper)) then
    begin
      FillChar(SHDragImage, SizeOf(SHDragImage), #0);

      SHDragImage.sizeDragImage.cx := Image.Width;
      SHDragImage.sizeDragImage.cy := Image.Height;
      SHDragImage.ptOffset := HotSpot;
      SHDragImage.ColorRef := ColorToRGB(TransparentColor);
      SHDragImage.hbmpDragImage := CopyImage(Image.Handle, IMAGE_BITMAP, Image.Width,
        Image.Height, LR_COPYRETURNORG);
      if SHDragImage.hbmpDragImage <> 0 then
        if Succeeded(DragSourceHelper.InitializeFromBitmap(SHDragImage, Self as IDataObject)) then
          Result := True
        else
          DeleteObject(SHDragImage.hbmpDragImage);
    end
  end
end;

function TCommonDataObject.CanonicalIUnknown(TestUnknown: IUnknown): IUnknown;
// Uses COM object identity: An explicit call to the IUnknown::QueryInterface
// method, requesting the IUnknown interface, will always return the same
// pointer.
begin
  if Assigned(TestUnknown) then
  begin
    if CommonSupports(TestUnknown, IUnknown, Result) then
      IUnknown(Result)._Release // Don't actually need it just need the pointer value
    else
      Result := TestUnknown
  end else
    Result := TestUnknown
end;

constructor TCommonDataObject.Create;
begin
  inherited;
end;

function TCommonDataObject.DAdvise(const formatetc: TFormatEtc; advf: Integer;
  const advSink: IAdviseSink; out dwConnection: Integer): HResult;
begin
  if not Assigned(AdviseHolder) then
    CreateDataAdviseHolder(FAdviseHolder);
  if Assigned(FAdviseHolder) then
    Result := AdviseHolder.Advise(Self as IDataObject, formatetc, advf, advSink, dwConnection)
  else
    Result := OLE_E_ADVISENOTSUPPORTED;
end;

destructor TCommonDataObject.Destroy;
begin
  inherited;
end;

function TCommonDataObject.GetObj: TObject;
begin
  Result := Self
end;

function TCommonDataObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TCommonDataObject._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
  {$IFDEF GX_DEBUG}
  SendDebug('TCommonDataObject:  ' + IntToHex(Integer(Self), 8) + '  AddRef:  Count = ' + IntToStr(FRefCount));
  {$ENDIF}
end;

function TCommonDataObject._Release: Integer;
begin
   Result := InterlockedDecrement(FRefCount);
   {$IFDEF GX_DEBUG}
   SendDebug('TCommonDataObject:  ' + IntToHex(Integer(Self), 8) + '  Release:  Count = ' + IntToStr(FRefCount));
   {$ENDIF}
   if Result = 0 then
     Destroy;
end;

procedure TCommonDataObject.AfterConstruction;
begin
// Release the constructor's implicit refcount
  InterlockedDecrement(FRefCount);
end;

procedure TCommonDataObject.BeforeDestruction;
begin
//  if RefCount <> 0 then
//    Error(reInvalidPtr);
end;

procedure TCommonDataObject.DoGetCustomFormats(var Formats: TFormatEtcArray);
begin
  
end;

procedure TCommonDataObject.DoOnGetData(const FormatEtcIn: TFormatEtc;
  var Medium: TStgMedium; var Handled: Boolean);
begin
  if Assigned(FOnGetData) then
    OnGetData(Self, FormatEtcIn, Medium, Handled);
end;

procedure TCommonDataObject.DoOnQueryGetData(
  const FormatEtcIn: TFormatEtc; var FormatAvailable: Boolean; var Handled: Boolean);
begin
  if Assigned(FOnQueryGetData) then
    OnQueryGetData(Self, FormatEtcIn, FormatAvailable, Handled);
end;

function TCommonDataObject.DUnadvise(dwConnection: Integer): HResult;
begin
  if Assigned(AdviseHolder) then
    Result := AdviseHolder.Unadvise(dwConnection)
  else
    Result := OLE_E_ADVISENOTSUPPORTED;
end;

function TCommonDataObject.EnumDAdvise(out enumAdvise: IEnumStatData): HResult;
begin
  if Assigned(AdviseHolder) then
    Result := AdviseHolder.EnumAdvise(enumAdvise)
  else
    Result := OLE_E_ADVISENOTSUPPORTED;
end;

function TCommonDataObject.EnumFormatEtc(dwDirection: Integer;
  out enumFormatEtc: IEnumFormatEtc): HResult;
// Called when DoDragDrop wants to know what clipboard formats are supported
// by Enumerating the TFormatEtc array through an IEnumFormatEtc object.
var
  LocalEnumFormatEtc: TCommonEnumFormatEtc;
  i: integer;
begin
  // Always return an object even if it is empty
  LocalEnumFormatEtc := TCommonEnumFormatEtc.Create;
  // Get the reference count in sync
  enumFormatEtc := LocalEnumFormatEtc as IEnumFormatEtc;
{  if Assigned(Formats) then
  begin        }
    Result := S_OK;
    if dwDirection = DATADIR_GET then
    begin
      // Copy the internal supported Formats for the EnumFormatEtc
      SetLength(LocalEnumFormatEtc.FFormats, Length(Formats));
      for i := 0 to Length(Formats) - 1 do
        LocalEnumFormatEtc.Formats[i] := Formats[i].FormatEtc;

      // Now copy any custom formats
      DoGetCustomFormats(LocalEnumFormatEtc.FFormats);

      if not Assigned(enumFormatEtc) then
        Result := E_OUTOFMEMORY
    end else
    begin
      enumFormatEtc := nil;
      Result := E_NOTIMPL;
    end;
 { end else
  begin
    if dwDirection = DATADIR_GET then
      Result := S_OK
    else
      Result := E_NOTIMPL
  end }
end;

function TCommonDataObject.EqualFormatEtc(FormatEtc1, FormatEtc2: TFormatEtc): Boolean;
begin
  Result := (FormatEtc1.cfFormat = FormatEtc2.cfFormat) and
            (FormatEtc1.ptd = FormatEtc2.ptd) and
            (FormatEtc1.dwAspect = FormatEtc2.dwAspect) and
            (FormatEtc1.lindex = FormatEtc2.lindex) and
            (FormatEtc1.tymed = FormatEtc2.tymed)
end;

function TCommonDataObject.FindFormatEtc(TestFormatEtc: TFormatEtc): integer;
var
  i: integer;
  Found: Boolean;
begin
  i := 0;
  Found := False;
  Result := -1;
  while (i < Length(FFormats)) and not Found do
  begin
    Found := EqualFormatEtc(Formats[i].FormatEtc, TestFormatEtc);
    if Found then
      Result := i;
    Inc(i);
  end
end;

function TCommonDataObject.GetCanonicalFormatEtc(const formatetc: TFormatEtc;
  out formatetcOut: TFormatEtc): HResult;
// Since we do not have two TFormatEtcs that return the same type of data we can
// ingore this function.  It is only for TFormatEtc structures that will return
// the exact same data if each is called.  This could happen if the data is
// target dependant and the target can handle both types of data.  This keeps
// the target from asking for redundant information.
begin
  formatetcOut.ptd := nil;
  Result := E_NOTIMPL;
end;

function TCommonDataObject.GetData(const FormatEtcIn: TFormatEtc;
  out Medium: TStgMedium): HResult;
// This is the workhorse of the functions.  It looks at the clipboard format
// the IDropTarget wants, makes sure we can support it.  If supported then see
// if it is owned by the object or the program will supply the data.
var
  Handled: Boolean;
begin
  Result := E_UNEXPECTED;
  Handled := False;
  DoOnGetData(FormatEtcIn, Medium, Handled);
  if not Handled then
  begin
  if Assigned(Formats) then
    begin
      { Do we support this type of Data? }
      Result := QueryGetData(FormatEtcIn);
      if Result = S_OK then
      begin
        // If the data is owned by the IDataObject just retrieve and return it.
        if RetrieveOwnedStgMedium(FormatEtcIn, Medium) = E_INVALIDARG then
        { This data is defined by the Object Inspector or a custom format need to }
        { Retrive it from the DragSourceManager                                   }
          if not GetUserData(FormatEtcIn, Medium) then
            Result := E_UNEXPECTED
      end
    end
  end else
    Result := S_OK
end;

function TCommonDataObject.GetDataHere(const formatetc: TFormatEtc;
  out medium: TStgMedium): HResult;
begin
  Result := E_NOTIMPL;
end;

function TCommonDataObject.GetUserData(Format: TFormatEtc; var StgMedium: TStgMedium): Boolean;
begin
  Result := False;
end;

function TCommonDataObject.HGlobalClone(HGlobal: THandle): THandle;
// Returns a global memory block that is a copy of the passed memory block.
var
  Size: LongWord;
  Data, NewData: PChar;
begin
  Size := GlobalSize(HGlobal);
  Result := GlobalAlloc(GPTR, Size);
  Data := GlobalLock(hGlobal);
  try
    NewData := GlobalLock(Result);
    try
      Move(Data, NewData, Size);
    finally
      GlobalUnLock(Result);
    end
  finally
    GlobalUnLock(hGlobal)
  end
end;

function TCommonDataObject.LoadGlobalBlock(Format: TClipFormat;
  var MemoryBlock: Pointer): Boolean;
var
  FormatEtc: TFormatEtc;
  StgMedium: TStgMedium;
  GlobalObject: Pointer;
begin
  Result := False;

  FormatEtc.cfFormat := Format;
  FormatEtc.ptd := nil;
  FormatEtc.dwAspect := DVASPECT_CONTENT;
  FormatEtc.lindex := -1;
  FormatEtc.tymed := TYMED_HGLOBAL;

  if Succeeded(QueryGetData(FormatEtc)) and Succeeded(GetData(FormatEtc, StgMedium)) then
  begin
    MemoryBlock := AllocMem( GlobalSize(StgMedium.hGlobal));
    GlobalObject := GlobalLock(StgMedium.hGlobal);
    try
      if Assigned(MemoryBlock) and Assigned(GlobalObject) then
      begin
        Move(GlobalObject^, MemoryBlock^, GlobalSize(StgMedium.hGlobal));
      end
    finally
      GlobalUnLock(StgMedium.hGlobal);
    end
  end;
end;

function TCommonDataObject.QueryGetData(const formatetc: TFormatEtc): HResult;
// This function allows the IDragTarget to see if we can possibly support some
// type of data transfer.
var
  i: integer;
  FormatAvailable, Handled: Boolean;
begin
  Handled := False;
  FormatAvailable := False;
  DoOnQueryGetData(FormatEtc, FormatAvailable, Handled);
  if Handled then
  begin
    if FormatAvailable then
      Result := S_OK
    else
      Result := DV_E_FORMATETC
  end else
  begin
    if not FormatAvailable then
    begin
      if Assigned(Formats) then
      begin
        i := 0;
        Result := DV_E_FORMATETC;
        while (i < Length(Formats)) and (Result = DV_E_FORMATETC)  do
        begin
          if Formats[i].FormatEtc.cfFormat = formatetc.cfFormat then
          begin
            if (Formats[i].FormatEtc.dwAspect = formatetc.dwAspect) then
            begin
              if (Formats[i].FormatEtc.tymed and formatetc.tymed <> 0) then
                Result := S_OK
              else
                Result := DV_E_TYMED;
            end else
              Result := DV_E_DVASPECT;
          end else
            Result := DV_E_FORMATETC;
          Inc(i)
        end
      end else
        Result := E_UNEXPECTED;
    end else
      Result := S_OK
  end
end;

function TCommonDataObject.RetrieveOwnedStgMedium(Format: TFormatEtc; var StgMedium: TStgMedium): HRESULT;
var
  i: integer;
begin
  Result := E_INVALIDARG;
  i := FindFormatEtc(Format);
  if (i > -1) and Formats[i].OwnedByDataObject then
    Result := StgMediumIncRef(Formats[i].StgMedium, StgMedium, False)
end;

function TCommonDataObject.SaveGlobalBlock(Format: TClipFormat;
  MemoryBlock: Pointer; MemoryBlockSize: integer): Boolean;
var
  FormatEtc: TFormatEtc;
  StgMedium: TStgMedium;
  GlobalObject: Pointer;
begin
  FormatEtc.cfFormat := Format;
  FormatEtc.ptd := nil;
  FormatEtc.dwAspect := DVASPECT_CONTENT;
  FormatEtc.lindex := -1;
  FormatEtc.tymed := TYMED_HGLOBAL;

  StgMedium.tymed := TYMED_HGLOBAL;
  StgMedium.unkForRelease := nil;
  StgMedium.hGlobal := GlobalAlloc(GHND or GMEM_SHARE, MemoryBlockSize);
  GlobalObject := GlobalLock(StgMedium.hGlobal);
  try
    try
      Move(MemoryBlock^, GlobalObject^, MemoryBlockSize);
      Result := Succeeded( SetData(FormatEtc, StgMedium, True))
    except
      Result := False;
    end
  finally
    GlobalUnLock(StgMedium.hGlobal);
  end
end;

function TCommonDataObject.SetData(const formatetc: TFormatEtc; var medium: TStgMedium; fRelease: BOOL): HResult;
// Allows dynamic adding to the IDataObject during its existance.  Most noteably
// it is used to implement IDropSourceHelper in win2k
var
  Index: integer;
begin
  // See if we already have a format of that type available.
  Index := FindFormatEtc(FormatEtc);
  if Index > - 1 then
  begin
    // Yes we already have that format type stored.  Just use the TCommonClipboardFormat
    // in the List after releasing the data
    ReleaseStgMedium(Formats[Index].StgMedium);
    FillChar(Formats[Index].StgMedium, SizeOf(Formats[Index].StgMedium), #0);
  end else
  begin
    // It is a new format so create a new TDataObjectInfo record and store it in
    // the Format array
    SetLength(FFormats, Length(Formats) + 1);
    Formats[Length(Formats) - 1].FormatEtc := FormatEtc;
    Index := Length(Formats) - 1;
  end;
  // The data is owned by the TCommonClipboardFormat object
  Formats[Index].OwnedByDataObject := True;

  if fRelease then
  begin
    // We are simply being given the data and we take control of it.
    Formats[Index].StgMedium := Medium;
    Result := S_OK
  end else
    // We need to reference count or copy the data and keep our own references
    // to it.
    Result := StgMediumIncRef(Medium, Formats[Index].StgMedium, True);

    // Can get a circular reference if the client calls GetData then calls
    // SetData with the same StgMedium.  Because the unkForRelease and for
    // the IDataObject can be marshalled it is necessary to get pointers that
    // can be correctly compared.
    // See the IDragSourceHelper article by Raymond Chen at MSDN.
    if Assigned(Formats[Index].StgMedium.unkForRelease) then
    begin
      if CanonicalIUnknown(Self) =
        CanonicalIUnknown(IUnknown( Formats[Index].StgMedium.unkForRelease)) then
      begin
        IUnknown( Formats[Index].StgMedium.unkForRelease)._Release;
        Formats[Index].StgMedium.unkForRelease := nil
      end;
    end;
  // Tell all registered advice sinks about the data change.
  if Assigned(AdviseHolder) then
    AdviseHolder.SendOnDataChange(Self as IDataObject, 0, 0);
end;

function TCommonDataObject.StgMediumIncRef(const InStgMedium: TStgMedium;
  var OutStgMedium: TStgMedium; CopyInMedium: Boolean): HRESULT;
// This function increases the reference count of the passed StorageMedium in a
// variety of ways depending on the value of CopyInMedium.
// InStgMedium is the data that is requested a copy of, OutStgMedium is the data that
// we are to return either a copy of or increase the IDataObject's reference and
// send ourselves back as the data (unkForRelease). The InStgMedium is usually
// the result of a call to find a particular FormatEtc that has been stored
// locally through a call to SetData.     If CopyInMedium is not true we
// already have a local copy of the data when the SetData function was called
// (during that call the CopyInMedium must be true).  Then as the caller asks
// for the data through GetData we do not have to make copy of the data for the
// caller only to have them destroy it then need us to copy it again if
// necessary.  This way we increase the reference count to ourselves and pass
// the STGMEDIUM structure initially stored in SetData.  This way when the
// caller frees the structure it sees the unkForRelease is not nil and calls
// Release on the object instead of destroying the actual data.
begin
  Result := S_OK;
  // Simply copy all fields to start with.
  OutStgMedium := InStgMedium;
  case InStgMedium.tymed of
    TYMED_HGLOBAL:
      begin
        if CopyInMedium then
        begin
          // Generate a unique copy of the data passed
          OutStgMedium.hGlobal := HGlobalClone(InStgMedium.hGlobal);
          if OutStgMedium.hGlobal = 0 then
            Result := E_OUTOFMEMORY
        end else
          // Don't generate a copy just use ourselves and the copy previoiusly saved
          OutStgMedium.unkForRelease := Pointer(Self as IDataObject) // Does increase RefCount
      end;
    TYMED_FILE:
      begin
        if CopyInMedium then
        begin
          OutStgMedium.lpszFileName := CoTaskMemAlloc(lstrLenW(InStgMedium.lpszFileName));
          MoveMemory(PWideChar(OutStgMedium.lpszFileName), PWideChar(InStgMedium.lpszFileName), lstrlenW(InStgMedium.lpszFileName) * 2);
        end else
          OutStgMedium.unkForRelease := Pointer(Self as IDataObject) // Does increase RefCount
      end;
    TYMED_ISTREAM:
      // Simply increase the reference so the stream object
      // Note here stm is a pointer to the auto reference counting won't work and
      // we have to call _AddRef explicitly
      IUnknown( OutStgMedium.stm)._AddRef;
    TYMED_ISTORAGE:
      // Simply increase the reference so the storage object
      // Note here stm is a pointer to the auto reference counting won't work and
      // we have to call _AddRef explicitly
      IUnknown( OutStgMedium.stg)._AddRef;
    TYMED_GDI:
      if not CopyInMedium then
      // Don't generate a copy just use ourselves and the copy previoiusly saved data
        OutStgMedium.unkForRelease := Pointer(Self as IDataObject) // Does not increase RefCount
     else
       Result := DV_E_TYMED; // Don't know how to copy GDI objects right now
    TYMED_MFPICT:
      if not CopyInMedium then
        OutStgMedium.unkForRelease := Pointer(Self as IDataObject) // Does not increase RefCount
      else
        Result := DV_E_TYMED; // Don't know how to copy MetaFile objects right now
    TYMED_ENHMF:
      if not CopyInMedium then
        { Don't generate a copy just use ourselves and the copy previoiusly saved data }
        OutStgMedium.unkForRelease := Pointer(Self as IDataObject) // Does not increase RefCount
      else
        Result := DV_E_TYMED; // Don't know how to copy enhanced metafiles objects right now
  else
    Result := DV_E_TYMED
  end;

  // I still have to do this. The Compiler will call _Release on the above Self as IDataObject
  // casts which is not what is necessary.  The DataObject is released correctly.
  if Assigned(OutStgMedium.unkForRelease) and (Result = S_OK) then
    IUnknown(OutStgMedium.unkForRelease)._AddRef
end;

{ TFileGroupDescriptorA }

procedure TFileGroupDescriptorA.AddFileDescriptor(
  FileDescriptor: TFileDescriptorA);
begin
  SetLength(FFileDescriptors, Length(FFileDescriptors) + 1);
  FFileDescriptors[Length(FFileDescriptors) - 1] := FileDescriptor;
end;

procedure TFileGroupDescriptorA.DeleteFileDescriptor(Index: integer);
var
  i: Integer;
begin
  for i := Index to Length(FFileDescriptors) - 1 do
    FileDescriptor[i] := FileDescriptor[i+1];
  SetLength(FFileDescriptors, Length(FFileDescriptors) - 1);
end;

function TFileGroupDescriptorA.FillDescriptor(FileName: string): TFileDescriptorA;
begin
  FillChar(Result, SizeOf(Result), #0);
  StrCopy(Result.cFileName, PChar(FileName));
end;

function TFileGroupDescriptorA.GetDescriptorCount: Integer;
begin
  Result := Length(FFileDescriptors)
end;

function TFileGroupDescriptorA.GetFileDescriptorA(Index: Integer): TFileDescriptorA;
begin
  FillChar(Result, SizeOf(Result), #0);
  if (Index > -1) and (Index < Length(FFileDescriptors)) then
    Result := FFileDescriptors[Index]
end;

function TFileGroupDescriptorA.GetFileStream(const DataObject: IDataObject;
  FileIndex: Integer): TStream;
var
  Format: TFormatEtc;
  Medium: TStgMedium;
begin
  if Assigned(Stream) then
    FreeAndNil(FStream);

  if Assigned(DataObject) and (FileIndex > 0) and (FileIndex < DescriptorCount) then
  begin
    Format.cfFormat := CF_FILECONTENTS;
    Format.ptd := nil;
    Format.dwAspect := DVASPECT_CONTENT;
    Format.lindex := FileIndex;
    Format.tymed := TYMED_ISTREAM;
    if Succeeded(DataObject.GetData(Format, Medium)) then
    begin
      Stream := TOLEStream.Create(IStream(Medium.stm));
      ReleaseStgMedium(Medium);
    end
  end;
  Result := Stream;
end;

function TFileGroupDescriptorA.GetFormatEtc: TFormatEtc;
begin
  Result := FileDescriptorAFormat 
end;

procedure TFileGroupDescriptorA.LoadFileGroupDestriptor(FileGroupDiscriptor: PFileGroupDescriptorA);
var
  i: Cardinal;
begin
  if Assigned(FileGroupDiscriptor) then
  begin
    SetLength(FFileDescriptors, FileGroupDiscriptor.cItems);
    for i := 0 to FileGroupDiscriptor.cItems - 1 do
    begin
      FFileDescriptors[i] := FileGroupDiscriptor.fgd[i]
    end
  end else
    FFileDescriptors := nil;
end;

function TFileGroupDescriptorA.LoadFromClipboard: Boolean;
var
  DataObject: IDataObject;
begin
  Result := False;
  if Succeeded(OleGetClipboard(DataObject)) then
    Result := LoadFromDataObject(DataObject);
end;

function TFileGroupDescriptorA.LoadFromDataObject(DataObject: IDataObject): Boolean;
var
  GroupDescriptor: PFileGroupDescriptorA;
  Medium: TStgMedium;
  i: Integer;
begin
  Result := False;
  if Succeeded(DataObject.GetData(GetFormatEtc, Medium)) then
  begin
    GroupDescriptor := GlobalLock(Medium.hGlobal);
    try
      for i := 0 to GroupDescriptor^.cItems - 1 do
        AddFileDescriptor(GroupDescriptor^.fgd[i])
    finally
      GlobalUnlock(Medium.hGlobal);
      ReleaseStgMedium(Medium);
      Result := True
    end
  end
end;

function TFileGroupDescriptorA.SaveToClipboard: Boolean;
var
  DataObject: IDataObject;
begin
  Result := False;
  DataObject := TCommonDataObject.Create;
  if SaveToDataObject(DataObject) then
    Result := Succeeded(OleSetClipboard(DataObject))
end;

function TFileGroupDescriptorA.SaveToDataObject(DataObject: IDataObject): Boolean;
var
  Mem: THandle;
  GroupDescriptor: PFileGroupDescriptorA;
  Medium: TStgMedium;
  Format: TFormatEtc;
begin
  Result := False;
  if Assigned(DataObject) and (DescriptorCount > 0) then
  begin
    Mem := GlobalAlloc(GHND, DescriptorCount * SizeOf(TFileDescriptorA) + SizeOf(GroupDescriptor.cItems));
    GroupDescriptor := GlobalLock(Mem);
    try
      GroupDescriptor.cItems := DescriptorCount;
      CopyMemory(@GroupDescriptor^.fgd[0], @FFileDescriptors[0], DescriptorCount * SizeOf(TFileDescriptorA));
    finally
      GlobalUnlock(Mem)
    end;
    FillChar(Medium, SizeOf(Medium), #0);
    Medium.tymed := TYMED_HGLOBAL;
    Medium.hGlobal := Mem;

    DataObject.SetData(GetFormatEtc, Medium, True);

    Medium.tymed := TYMED_ISTREAM;
    Medium.stm := nil;

    Format.cfFormat := CF_FILECONTENTS;
    Format.ptd := nil;
    Format.dwAspect := DVASPECT_CONTENT;
    Format.lindex := -1;
    Format.tymed := TYMED_ISTREAM;
    DataObject.SetData(Format, Medium, True);
  end
end;

procedure TFileGroupDescriptorA.SetFileDescriptor(Index: Integer; const Value: TFileDescriptorA);
begin
  if (Index > -1) and (Index < Length(FFileDescriptors)) then
    FFileDescriptors[Index] := Value
end;

{ TFileGroupDescriptorW }

procedure TFileGroupDescriptorW.AddFileDescriptor(
  FileDescriptor: TFileDescriptorW);
begin
  SetLength(FFileDescriptors, Length(FFileDescriptors) + 1);
  FFileDescriptors[Length(FFileDescriptors) - 1] := FileDescriptor;
end;

procedure TFileGroupDescriptorW.DeleteFileDescriptor(Index: integer);
var
  i: Integer;
begin
  for i := Index to Length(FFileDescriptors) - 1 do
    FileDescriptor[i] := FileDescriptor[i+1];
  SetLength(FFileDescriptors, Length(FFileDescriptors) - 1);
end;

function TFileGroupDescriptorW.FillDescriptor(FileName: WideString): TFileDescriptorW;
begin
  FillChar(Result, SizeOf(Result), #0);
  StrCopyW(Result.cFileName, PWideChar(FileName));
end;

function TFileGroupDescriptorW.GetDescriptorCount: Integer;
begin
  Result := Length(FFileDescriptors)
end;

function TFileGroupDescriptorW.GetFileDescriptorW(Index: Integer): TFileDescriptorW;
begin
  FillChar(Result, SizeOf(Result), #0);
  if (Index > -1) and (Index < Length(FFileDescriptors)) then
    Result := FFileDescriptors[Index]
end;

function TFileGroupDescriptorW.GetFileStream(const DataObject: IDataObject; FileIndex: Integer): TStream;
var
  Format: TFormatEtc;
  Medium: TStgMedium;
begin
  if Assigned(Stream) then
    FreeAndNil(FStream);

  if Assigned(DataObject) and (FileIndex > -1) and (FileIndex < DescriptorCount) then
  begin
    Format.cfFormat := CF_FILECONTENTS;
    Format.ptd := nil;
    Format.dwAspect := DVASPECT_CONTENT;
    Format.lindex := FileIndex;
    Format.tymed := TYMED_ISTREAM;
    if Succeeded(DataObject.GetData(Format, Medium)) then
    begin
      Stream := TOLEStream.Create(IStream(Medium.stm));
      ReleaseStgMedium(Medium);
    end;
  end;
  Result := Stream;
end;

function TFileGroupDescriptorW.GetFormatEtc: TFormatEtc;
begin
  Result := FileDescriptorWFormat 
end;

procedure TFileGroupDescriptorW.LoadFileGroupDestriptor(FileGroupDiscriptor: PFileGroupDescriptorW);
var
  i: Cardinal;
begin
  if Assigned(FileGroupDiscriptor) then
  begin
    SetLength(FFileDescriptors, FileGroupDiscriptor.cItems);
    for i := 0 to FileGroupDiscriptor.cItems - 1 do
    begin
      FFileDescriptors[i] := FileGroupDiscriptor.fgd[i]
    end
  end else
    FFileDescriptors := nil;
end;

function TFileGroupDescriptorW.LoadFromClipboard: Boolean;
var
  DataObject: IDataObject;
begin
  Result := False;
  if Succeeded(OleGetClipboard(DataObject)) then
    Result := LoadFromDataObject(DataObject);
end;

function TFileGroupDescriptorW.LoadFromDataObject(DataObject: IDataObject): Boolean;
var
  GroupDescriptor: PFileGroupDescriptorW;
  Medium: TStgMedium;
  i: Integer;
begin
  Result := False;
  if Succeeded(DataObject.GetData(GetFormatEtc, Medium)) then
  begin
    GroupDescriptor := GlobalLock(Medium.hGlobal);
    try
      for i := 0 to GroupDescriptor^.cItems - 1 do
        AddFileDescriptor(GroupDescriptor^.fgd[i])
    finally
      GlobalUnlock(Medium.hGlobal);
      ReleaseStgMedium(Medium);
      Result := True;
    end
  end
end;

function TFileGroupDescriptorW.SaveToClipboard: Boolean;
var
  DataObject: IDataObject;
begin
  Result := False;
  DataObject := TCommonDataObject.Create;
  if SaveToDataObject(DataObject) then
    Result := Succeeded(OleSetClipboard(DataObject))
end;

function TFileGroupDescriptorW.SaveToDataObject(DataObject: IDataObject): Boolean;
var
  Mem: THandle;
  GroupDescriptor: PFileGroupDescriptorW;
  Medium: TStgMedium;
  Format: TFormatEtc;
begin
  Result := False;
  if Assigned(DataObject) and (DescriptorCount > 0) then
  begin
    Mem := GlobalAlloc(GHND, DescriptorCount * SizeOf(TFileDescriptorW) + SizeOf(GroupDescriptor.cItems));
    GroupDescriptor := GlobalLock(Mem);
    try
      GroupDescriptor.cItems := DescriptorCount;
      CopyMemory(@GroupDescriptor^.fgd[0], @FFileDescriptors[0], DescriptorCount * SizeOf(TFileDescriptorW));
    finally
      GlobalUnlock(Mem)
    end;
    FillChar(Medium, SizeOf(Medium), #0);
    Medium.tymed := TYMED_HGLOBAL;
    Medium.hGlobal := Mem;

    DataObject.SetData(GetFormatEtc, Medium, True);

    Medium.tymed := TYMED_ISTREAM;
    Medium.stm := nil;

    Format.cfFormat := CF_FILECONTENTS;
    Format.ptd := nil;
    Format.dwAspect := DVASPECT_CONTENT;
    Format.lindex := -1;
    Format.tymed := TYMED_ISTREAM;
    DataObject.SetData(Format, Medium, True);
  end
end;

procedure TFileGroupDescriptorW.SetFileDescriptor(Index: Integer; const Value: TFileDescriptorW);
begin
  if (Index > -1) and (Index < Length(FFileDescriptors)) then
    FFileDescriptors[Index] := Value
end;

{ TCommonInShellDragLoop }
function TCommonInShellDragLoop.GetFormatEtc: TFormatEtc;
begin
  Result.cfFormat := CF_INDRAGLOOP;
  Result.ptd := nil;
  Result.dwAspect := DVASPECT_CONTENT;
  Result.lindex := -1;
  Result.tymed := TYMED_HGLOBAL
end;

function TCommonInShellDragLoop.LoadFromDataObject(DataObject: IDataObject): Boolean;
var
  StgMedium: TStgMedium;
  DataPtr: PCardinal;
begin
  Result := False;
  FillChar(StgMedium, SizeOf(StgMedium), #0);

  if Succeeded(DataObject.GetData(GetFormatEtc, StgMedium)) then
  try
    DataPtr := GlobalLock(StgMedium.hGlobal);
    try
      if Assigned(DataPtr) then
      begin
        Data := DataPtr^;
        Result := True;
      end;
    finally
      GlobalUnLock(StgMedium.hGlobal);
    end
  finally
    ReleaseStgMedium(StgMedium)
  end
end;

function TCommonInShellDragLoop.SaveToDataObject(DataObject: IDataObject): Boolean;
var
  StgMedium: TStgMedium;
  Ptr: PCardinal;
begin
  FillChar(StgMedium, SizeOf(StgMedium), #0);
  StgMedium.hGlobal := GlobalAlloc(GPTR, SIZE_SHELLDRAGLOOPDATA);
  Ptr := GlobalLock(StgMedium.hGlobal);
  try
    StgMedium.tymed := TYMED_HGLOBAL;
    Ptr^ := FData;
    Result := Succeeded(DataObject.SetData(GetFormatEtc, StgMedium, True))
  finally
    GlobalUnLock(StgMedium.hGlobal);
  end
end;

initialization
  CF_SHELLIDLIST := RegisterClipboardFormat(CFSTR_SHELLIDLIST);
  CF_LOGICALPERFORMEDDROPEFFECT := RegisterClipboardFormat(CFSTR_LOGICALPERFORMEDDROPEFFECT);
  CF_PREFERREDDROPEFFECT := RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
  CF_PERFORMEDDROPEFFECT := RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT);
  CF_PASTESUCCEEDED := RegisterClipboardFormat(CFSTR_PASTESUCCEEDED);
  CF_INDRAGLOOP := RegisterClipboardFormat(CFSTR_INDRAGLOOP);
  CF_SHELLIDLISTOFFSET := RegisterClipboardFormat(CFSTR_SHELLIDLISTOFFSET);
  CF_FILECONTENTS := RegisterClipboardFormat(CFSTR_FILECONTENTS);
  CF_FILEDESCRIPTORA := RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
  CF_FILEDESCRIPTORW := RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
  PIDLMgr := TCommonPIDLManager.Create;

finalization
  PIDLMgr.Free;

end.
