unit MPShellUtilities;

// 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>
//
//----------------------------------------------------------------------------
//
//  Credits for valuable information and code gathered from newsgroups and
//  websites:
//      Angus Johnson for his GetDiskFreeSpaceFAT32 function from UNDO
//
//----------------------------------------------------------------------------

interface

{$I ..\Include\Addins.inc}

{.$DEFINE GXDEBUG_DEFMENUCREATE_CALLBACK}
{.$DEFINE GXDEBUG_VIRTUALCONTEXTMENU}

{$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
  {$DEFINE GX_DEBUG}
{$ENDIF}
{$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
  {$DEFINE GX_DEBUG}
{$ENDIF}


{$B-}

{$include Compilers.inc}
{$include Options.inc}

uses
  {$IFDEF GX_DEBUG}
  DbugIntf,
  {$ENDIF}
  Windows,
  Messages,
  SysUtils,
  Classes,
  Graphics,
  Controls,
  Forms,
  ImgList,
  ShlObj,
  ShellAPI,
  ActiveX,
  Registry,
  MPShellTypes,
  MPCommonObjects,
  MPCommonUtilities,
  MPThreadManager,
  MPResources,
  MPDataObject,
  {$IFDEF TNTSUPPORT}
  TntSysUtils,
  TntClasses,
  TntRegistry,
  TntMenus,
  {$ENDIF}
  {$IFDEF COMPILER_6_UP}
  Variants,
  {$ENDIF}
  {$IFDEF COMPILER_5_UP}
  Contnrs,
  {$ENDIF}
  Menus;

const
  DefaultDetailColumns = 5;

  ID_TIMER_NOTIFY = 100;
  ID_TIMER_ENUMBKGND = 101;
  ID_TIMER_AUTOSCROLL = 102;
  ID_TIMER_SHELLNOTIFY = 103;

  SHORTCUT_ICON_INDEX = 29;  // This is cheezy crappy stupid and dumb but
                             // I can't find a way to get the link index

  SHELL_NAMESPACE_ID = -1;   // ID of a basic Shell Namespace based TNamespace

  SHGDN_FOREDITING = $1000;

  DEFAULTPIDLARRAYSIZE = 8192;     // Default size for the TPIDLArray

  STREAM_VERSION_DEFAULT = -1;  // Default Stream version for TStreamableClass.LoadFromStream
                                // if this value is seen the LoadFromStream method should read the version
                                // from the stream else it should use the passed version in the method

  STR_IMAGE_THREAD_EVENT = 'jdkImageThreadEvent';

  VET_DEFAULT_COLUMNWIDTHS: array[0..36] of integer = (
    180,                // Name
    96,                 // Size
    120,                // Type
    120,                // Modified
    60,                 // Attributes
    180,                // Comment
    120,                // Created
    120,                // Accessed
    120,                // Owner
    120,                // Author
    120,                // Title
    120,                // Subject
    120,                // Catagory
    60,                 // Pages
    120,                // Copywrite
    120,                // Company Name
    120,                // Module Description
    120,                // Module Version
    120,                // Product Name
    120,                // Product Version
    72,                 // Sender Name
    90,                 // Recipient Name
    102,                // Recipient Number
    30,                 // Csid
    30,                 // Tsid
    108,                // Transmission Time
    60,                 // Caller ID
    48,                 // Routing
    180,                // Audio Format
    180,                // Sample Rate
    180,                // Audio Sample Size
    180,                // Channels
    180,                // Play Length
    180,                // Frame Count
    180,                // Frame Rate
    180,                // Video Sample Size
    180                 // Video Compression
  );

  VET_DEFAULT_DRIVES_COLUMNWIDTHS: array[0..3] of integer = (
    180,                // Name
    120,                // Type
    96,                 // Total Size
    96                  // Free Space
  );

  VET_DEFAULT_CONTROLPANEL_COLUMNWIDTHS: array[0..1] of integer = (
    180,                // Name
    300                 // Description
  );

  VET_DEFAULT_NETWORK_COLUMNWIDTHS: array[0..1] of integer = (
    180,                // Name
    300                 // Description
  );


{-------------------------------------------------------------------------------}
{ Custom enumerated types                                                       }
{-------------------------------------------------------------------------------}

type     

  TDefaultFolderIcon = (
    diNormalFolder,        // Retrieve the index for a normal file folder icon
    diOpenFolder,          // Retrieve the index for a normal file folder icon in the open state
    diUnknownFile,         // Retrieve the index for an icon for a file that has no association
    diLink,                // Retrieve the index for link overlay icon
    diMyDocuments          // Index of MyDocuments icon
  );

  TMPBrowseFlag = (
    mpbfComputers,         // Only allow computers to be returned
    mpbfPrinters,          // Only allow printers to be returned
    mpbfDontGoBelowDomain, // Don't allow browsing into other network domains
    mpbfFileSysAncestors,  // Only allow File system ancestors to be returned
    mpbfFileSysFolder,     // Only allow file system folders to be returned
//    mpbfIncludeStatusText, // Includes status test in the dialog
    mpbfIncludeFiles,      // Include files
    mpbfNewStyleDialog,    // New style dialog
    mpbfEditBox,           // Add a edtibox for the use to type into
    mpbfIncludeURLs,       // Include URLs
    mpbfSharable,          // sharable folders
    mpbfMustExist          // Returned folder must exist or cancel pressed before dialog will close
  );
  TMPBrowseFlags = set of TMPBrowseFlag;

type
  TNamespaceState = (
    nsFreePIDLOnDestroy,       // If true the object free the PIDL with the system allocator when freed
    nsIsRecycleBin,            // Recyclebin does not cooperate very well so we have do extra checks for various reasons so cache this
    nsRecycleBinChecked,       // Flag to see if above is valid.
    nsOwnsParent,              // If a namespace is created from a complex PIDL for some methods the parent is needed.  If so a Parent namespace is created so this instance owns it and must free it.
    nsShellDetailsSupported,   // Instead of a costly call to see if interface exists when it does not we only check once then cache the result
    nsShellFolder2Supported,   // Same idea as ShellDetailsSupported, if it does not exist don't waste time constantly checking
    nsShellOverlaySupported,   // Same idea as ShellDetailsSupported, if it does not exist don't waste time constantly checking
    nsThreadedIconLoaded,      // To keep the threaded icon option fast and interuptable need to track if a thread is currently trying to extract the namespace icon index. Retrieved.
    nsThreadedIconLoading,     // To keep the threaded icon option fast and interuptable need to track if a thread is currently trying to extract the namespace icon index. In queue.
    nsThreadedImageLoaded,     // To keep the threaded image option fast and interuptable need to track if a thread is currently trying to extract the namespace thumbnail. Retrieved.
    nsThreadedImageLoading,    // To keep the threaded image option fast and interuptable need to track if a thread is currently trying to extract the namespace thumbnail. In queue.
    nsThreadedImageResizing,   // To keep the threaded image option fast and interuptable need to track if a thread is currently trying to resize the namespace thumbnail. In queue.
    nsThreadedTileInfoLoaded,  // To keep the threaded Tile Info option fast and interuptable need to track if a thread is currently trying to extract Tile Info for the namespace, Threaded Tile Info is retrieved
    nsThreadedTileInfoLoading, // To keeping the threaded Tile Info option fast and interuptable need to track if a thread is currently trying to extract Tile Info for the namespace, it is in the queue to be retieved
    nsIconIndexChanged         // Sets a flag to track when the IconIndex changed between calls to GetIconIndex.  Usually caused by a Thread Setting the icon index
  );
  TNamespaceStates = set of TNamespaceState;

type
//  {$HPPEMIT 'class TNamespace;'}

  TSHLimitInputEdit = function(hWndEdit: HWND; psf: IShellFolder): HRESULT; stdcall;

  {$IFNDEF CPPB_6_UP}
     IVETShellDetails = IShellDetails;
   {$ELSE}
     IVETShellDetails = IShellDetailsBCB6;
   {$ENDIF}

  TShellCache = set of (     // Valid entries in the Namespace data cache
    scInFolderName,
    scNormalName,
    scParsedName,
    scSmallIcon,
    scSmallOpenIcon,
    scOverlayIndex,
    scCreationTime,
    scLastAccessTime,
    scLastWriteTime,
    scFileSize,
    scFileSizeKB,
    scFileSizeInt64,
    scFileType,
    scInvalidIDListData,   // If SHGetDataFromIDList fails flag it so we won't try again.
    scFileSystem,
    scFolder,
    scCanDelete,
    scCanRename,
    scGhosted,
    scCanCopy,
    scCanMove,
    scCanLink,
    scLink,
    scFileSysAncestor,
    scCompressed,
    scFileTimes,
    scSupportedColumns,
    scFolderSize,            // Recursivly calculated size of folder contents
    scVirtualHook,           // Namespace is the invisible parent of the RootVirtualNamespace
    scHookedNamespace,       // Namespace has custom sub items injected through SubFolderHook property
    scVirtualNamespace,      // Namespace is a virtual namespace
    scRootVirtualNamespace,  // Namespace is the Root of the Custom Namespace Branch. If a real NS is hooked it is the namespace under the caSubItemHook namespace
    scHardHookedNamespace,    // Namespace is a hard hooked, this means it will show user defined details if defined instead of using the real namespaces parent to create the details
    scDetailsOfCache,
    scBrowsable
    );

{ This stores the state of the cached folder attribute.                         }
  TCacheAttributes = set of (
    caFileSystem,      // Namespace is part of the file system
    caFolder,          // Namespace is a Folder (not necessarily a directory)
    caCanDelete,       // Namespace can be deleted
    caCanRename,       // Namespace can be renamed
    caGhosted,         // Namespace is should display a ghosted icon
    caCanCopy,         // Namespace can be copied
    caCanMove,         // Namespace can be moved to a different location
    caCanLink,         // Namespace can create a link to itself
    caLink,            // Namespace *is* a link
    caFileSysAncestor, // Namespace is an ancestor of a file system namespace
    caCompressed,      // Namespace represents a compressed folder
    caVirtualHook,           // Namespace is the invisible parent of the RootVirtualNamespace
    caHookedNamespace,       // Namespace has custom sub items injected through SubFolderHook property
    caVirtualNamespace,      // Namespace is a virtual namespace
    caRootVirtualNamespace,  // Namespace is the Root of the Custom Namespace Branch. If a real NS is hooked it is the namespace under the caSubItemHook namespace
    caHardHookedNamespace,   // Namespace is a hard hooked, this means it will show user defined details if defined instead of using the real namespaces parent to create the details
    caBrowsable
    );

  { Used in IShellFolder2.GetDefaultColumnState }
  TSHColumnState = (
    csTypeString,     // A string.
    csTypeInt,        // An integer.
    csTypeDate,       // A date.
    csOnByDefault,    // Should be shown by default in the Microsoft® Windows® Explorer Details view.
    csSlow,           // Extracting information about the column can be time consuming.
    csExtended,       // Provided by a handler, not the folder object.
    csSecondaryUI,    // Not displayed in the context menu, but listed in the More dialog box.
    csHidden          // Not displayed in the user interface.
  );
  TSHColumnStates = set of TSHColumnState;

  THotKeyModifier = (      // For IShellLink
    hkmAlt,                // HOTKEYF_ALT
    hkmControl,            // HOTKEYF_CONTROL
    hkmExtendedKey,        // HOTKEYF_EXT
    hkmShift               // HOTKEYF_SHIFT
  );
  THotKeyModifiers = set of THotKeyModifier;

  TCmdShow = (           // For IShellLink
    swHide,              // Hides the window and activates another window.
    swMaximize,          // Maximizes the specified window.
    swMinimize,          // Minimizes the specified window and activates the next top-level window in the Z order.
    swRestore,           // Activates and displays the window. If the window is minimized or maximized, Windows restores it to its original size and position. An application should specify this flag when restoring a minimized window.
    swShow,              // Activates the window and displays it in its current size and position.
    swShowDefault,       // Sets the show state based on the SW_ flag specified in the STARTUPINFO structure passed to the CreateProcess function by the program that started the application.
    swShowMinimized,     // Activates the window and displays it as a minimized window.
    swShowMinNoActive,   // Displays the window as a minimized window. The active window remains active.
    swShowNA,            // Displays the window in its current state. The active window remains active.
    swShowNoActive,      // Displays a window in its most recent size and position. The active window remains active.
    swShowNormal         // Activates and displays a window. If the window is minimized or maximized, Windows
  );                     //    restores it to its original size and position. An application should specify this flag
                         //    when displaying the window for the first time.

  TIconSize = (
    icSmall,             // Small Shell size icon, usually 16x16
    icLarge              // Large TListview size Icon, usually 32x32
  );

  TFileSort = (       // Used in the ShellSortHelper class
    fsFileType,       // Sort by the File Type name
    fsFileExtension   // Sort by the file extenstion
  );


  TObjectDescription = ( // Return from SHGetDataFromIDList with SHGDFIL_DESCRIPTIONID param
    odError,          // The call Failed for some reason
    odRootRegistered, // The item is a registered item on the desktop.
    odFile,           // The item is a file.
    odDirectory,      // The item is a folder.
    odUnidentifiedFileItem, // The item is an unidentified item in the file system.
    od35Floppy,       // The item is a 3.5-inch floppy drive.
    od525Floppy,      // The item is a 5.25-inch floppy drive.
    odRemovableDisk,  // The item is a removable disk drive.
    odFixedDrive,     // The item is a fixed disk drive.
    odMappedDrive,    // The item is a drive that is mapped to a network share.
    odCDROMDrive,     // The item is a CD-ROM drive.
    odRAMDisk,        // The item is a RAM disk.
    odUnidentifiedDevice, // The item is an unidentified system device.
    odNetworkDomain,  // The item is a network domain.
    odNetworkServer,  // The item is a network server.
    odNetworkShare,   // The item is a network share.
    odNetworkRestOfNet, // Not currently used.
    odUnidentifiedNetwork, // The item is an unidentified network resource.
    odComputerImaging, // Not currently used.
    odComputerAudio,   // Not currently used.
    odShareDocuments  // The item is the system shared documents folder.
  );

  TDetailsColumnTitleInfo = (
    tiCenterAlign,      // The header title is Center Aligned
    tiLeftAlign,        // The header title is Left Aligned
    tiRightAlign,       // The header title is Right Aligned
    tiContainsImage     // The header title is Contains an Image (were do you get the image???)
  );


 { Selects what type of namespaces are enumerated and displayed in VET.          }
  TFileObjects = set of (
    foFolders,
    foNonFolders,
    foHidden,
    foShareable,
    foNetworkPrinters
  );

  TSHColumnIDArray = array of TSHColumnID;
  TGUIDArray = array of TGUID;
  TWideStringArray = array of WideString;
  TCategoryArray = array of ICategorizer;

  TCategoryInfo = record
    Description: WideString;
    Collapsed: Boolean;
    Hidden: Boolean;
  end;

  TCategoryInfoArray = array of TCategoryInfo;
  TBooleanArray = array of Boolean;

  TCategoriesInfo = record
    ColumnID: TSHColumnIDArray;
    CatGUID: TGUIDArray;
    CategoryNames: TWideStringArray;
    Categories: TCategoryInfoArray;
    CanCatatorize: TBooleanArray;
    DefaultColumn: Integer; // Index into above arrays to the default grouping column
    CategoryCount: Integer;
  end;

  TBtyeSize = (
    bsKiloBytes,
    bsMegaBytes,
    bsGigiBytes,
    bsTereBytes,
    bsCustom
  );

{-------------------------------------------------------------------------------}


{-------------------------------------------------------------------------------}
{ Custom Data structures                                                        }
{-------------------------------------------------------------------------------}

type
  TNamespace = class;       // Forward
  TExtractImage = class;    // Forward
  TCommonShellContextMenu = class; // Forward

  TMenuItemIDArray = array of cardinal;

  TVisibleColumnIndexArray = array of Word; // Array of the column indexes that are currently visible for a namespace

{ Array that contains the cached information for the folder.                    }

  TDetailsOfCacheFlag = (
    docCaptionValid,
    docStatesValid,
    docThreadLoading,
    docThreadLoaded
  );
  TDetailsOfCacheFlags = set of TDetailsOfCacheFlag;

  PDetailsOfCacheRec = ^TDetailsOfCacheRec;
  TDetailsOfCacheRec = packed record
    Cached: TDetailsOfCacheFlags;
    Caption: WideString;
    States: TSHColumnStates;
  end;

  TDetailsOfCacheArray = array of TDetailsOfCacheRec;

  TCacheData = packed record
    Attributes: TCacheAttributes;  // Boolean attributes for the namespace are saved as bits
    SmallIcon,                     // Index in the ShellImageList of the normal icon
    SmallOpenIcon: integer;        // Index in the ShellImageList of the open or selected icon
    InFolderName,                  // InFolder display name for the namespace
    NormalName,                    // Normal display name for the namespace
    ParsedName,                    // The Path of the namespace if it is a file object, if not it is usually the same as NameNormal
    CreationTime,                  // String of the object creation time in details mode
    LastAccessTime,                // String of the last accessed time in details mode
    LastWriteTime,                 // String of the last write time in details mode
    FileSize,                      // String of the file size "23,0000"
    FileSizeKB,                    // String of the file size ala Explorer style i.e. "23 KB"
    FileType: WideString;          // @@@@ FileType shown in Explorer details mode
    FileSizeInt64: Int64;          // Actual File Size
    SupportedColumns: integer;     // Number of supported columns in details mode
    FolderSize: Int64;             // Recursivly calcuated size of folder contents
    OverlayIndex,                  // Cache the Index of the Overlay
    OverlayIconIndex: Integer;      // Cache the Index of the Overlay Icon
    DetailsOfCache: TDetailsOfCacheArray // Cached strings for report view
  end;

{ Cache record tracks which information in the Data structure is valid with the }
{ ShellCacheFlags.                                                              }

  TShellCacheRec = packed record
    ShellCacheFlags: TShellCache;     // If flag is set the corresponding data stored in Data is valid
    Data: TCacheData;                 // Cached data for fast retrieval
  end;

  PSHGetFileInfoRec = ^TSHGetFileInfoRec;
  TSHGetFileInfoRec = packed record
    FileType: WideString;             // Holds the File Type column detail if not using ShellColumns (using VET or custom columns)
  end;


  TShellContextMenuFlag = (
    cmfCanRename,   // This flag is set if the calling application supports renaming of items. A shortcut menu extension or drag-and-drop handler should ignore this flag. A namespace extension should add a rename item to the menu if applicable.
    cmfDefaultOnly, // This flag is set when the user is activating the default action, typically by double-clicking. This flag provides a hint for the shortcut menu extension to add nothing if it does not modify the default item in the menu. A shortcut menu extension or drag-and-drop handler should not add any menu items if this value is specified. A namespace extension should add only the default item (if any).
 //   cmfDisableVerbs, // Microsoft Windows Vista and later: This flag is set when the calling application wants to invoke verbs that are otherwise disabled, such as legacy menus.
    cmfExplore,     // This flag is set when the Microsoft Windows Explorer tree window is present.
    cmfExtendedVerbs, // This flag is set when the calling application wants extended verbs. Normal verbs are displayed when the user right-clicks an object. To display extended verbs, the user must right-click while pressing the SHIFT key.
    cmfIncludeStatic, // Deprecated, do not use.
    cmfItemMenu,     // Microsoft Windows Vista and later: This flag is set when the calling application is invoking a context menu on an item in the view (as opposed to the background of the view).
    cmfNoDefault,  // This flag is set if no item in the menu has been set as the default. A drag-and-drop handler should ignore this flag. A namespace extension should not set any of the menu items to the default.
    cmfNormal,     // Indicates normal operation. A shortcut menu extension, namespace extension, or drag-and-drop handler can add all menu items.
    cmfNoVerbs,   // This flag is set for items displayed in the Send To menu. Shortcut menu handlers should ignore this value.
    cmfVerbsOnly  // This flag is set if the shortcut menu is for a shortcut object. Shortcut menu handlers should ignore this value.
  );
  TShellContextMenuFlags = set of TShellContextMenuFlag;


  TNamespaceArray = array of TNamespace;

  TCommonShellMenuEvent = procedure(Sender: TCommonShellContextMenu) of object;
  {$IFDEF TNTSUPPORT}
  TCommonShellMenuInvokeEvent = procedure(Sender: TCommonShellContextMenu; MenuItem: TTntMenuItem; InvokeInfo: PCMInvokeCommandInfo; var Handled: Boolean) of object;
  {$ELSE}
  TCommonShellMenuInvokeEvent = procedure(Sender: TCommonShellContextMenu; MenuItem: TMenuItem; InvokeInfo: PCMInvokeCommandInfo; var Handled: Boolean) of object;
  {$ENDIF}
  TCommonShellMenuMergeEvent = procedure(Sender: TCommonShellContextMenu; Menu: HMENU; IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags) of object;
  TCommonShellMenuItemEvent = procedure(Sender: TCommonShellContextMenu; ShellFolder: IShellFolder; DataObject: IDataObject; var Handled: Boolean) of object;
  TCommonShellMenuNewItemEvent = procedure(Sender: TCommonShellContextMenu; NS: TNamespace) of object;
  TCommonShellMenuCopyEvent = TCommonShellMenuItemEvent;
  TCommonShellMenuCreateShortcutEvent = TCommonShellMenuItemEvent;
  TCommonShellMenuCutEvent = TCommonShellMenuItemEvent;
  TCommonShellMenuDeleteEvent = TCommonShellMenuItemEvent;
  TCommonShellMenuNewFolderEvent = TCommonShellMenuItemEvent;
  TCommonShellMenuPasteEvent = TCommonShellMenuItemEvent;
  TCommonShellMenuPasteLinkEvent = TCommonShellMenuItemEvent;
  TCommonShellMenuPasteShortCutEvent = TCommonShellMenuItemEvent;
  TCommonShellMenuProperitesEvent = TCommonShellMenuItemEvent;

{-------------------------------------------------------------------------------}


// Custom Exceptions
  EVSTInvalidFileName = class(Exception)
  end;

  
{-------------------------------------------------------------------------------}
{ Persistent Storing and Recreating VET                                         }
{-------------------------------------------------------------------------------}

  TStreamableClass = class(TPersistent)
  private
    FStreamVersion: integer;
  public
    constructor Create;
    procedure LoadFromFile(FileName: WideString; Version: integer = 0; ReadVerFromStream: Boolean = False); virtual;
    procedure LoadFromStream(S: TStream; Version: integer = 0; ReadVerFromStream: Boolean = False); virtual;
    procedure SaveToFile(FileName: WideString; Version: integer = 0; ReadVerFromStream: Boolean = False); virtual;
    procedure SaveToStream(S: TStream; Version: integer = 0; WriteVerToStream: Boolean = False); virtual;

    property StreamVersion: integer read FStreamVersion;
  end;

  TStreamableList = class(TList)
  private
    FStreamVersion: integer;
  public
    constructor Create;
    procedure LoadFromFile(FileName: WideString; Version: integer = 0; ReadVerFromStream: Boolean = False); virtual;
    procedure LoadFromStream(S: TStream; Version: integer = 0; ReadVerFromStream: Boolean = False); virtual;
    procedure SaveToFile(FileName: WideString; Version: integer = 0; ReadVerFromStream: Boolean = False); virtual;
    procedure SaveToStream(S: TStream; Version: integer = 0; WriteVerToStream: Boolean = False); virtual;

    property StreamVersion: integer read FStreamVersion;
  end;
{-------------------------------------------------------------------------------}


{-------------------------------------------------------------------------------}
{ Our own COM like referenced classes                                           }
{-------------------------------------------------------------------------------}

  TReferenceCounted = class
  protected
    FRefCount: integer;
  public
    procedure AddRef;
    procedure Release;
  end;

{ Reference counted TList, much like a COM object but the compiler does not     }
{ add the AddRef and Release call automaticlly.                                 }
  TReferenceCountedList = class(TList)
  protected
    FRefCount: integer;
  public
    procedure AddRef;
    procedure Release;
    property RefCount: integer read FRefCount;
  end;

{-------------------------------------------------------------------------------}
{ Encapsulates IExtractImage, ASCI and Unicode                                  }
{-------------------------------------------------------------------------------}

  TExtractImage = class
  private
    FFlags: Longword;          // Sets how the image is to be handled see IEIFLAG_xxxx
    FPriority: Longword;       // Returns from GetLocation call the priority if IEIFLAG_ASYNC is used above
    FHeight: Longword;         // Desired image height
    FWidth: Longword;          // Desired image Width
    FColorDepth: Longword;     // Desired color depth
    FExtractImageInterface: IExtractImage;    // The interface
    FExtractImage2Interface: IExtractImage2;  // The interface for image2
    FOwner: TNamespace;                       // The Owner namespace
    FPathExtracted: Boolean;
    function GetImage: TBitmap;
    function GetImagePath: WideString;
    function GetExtractImageInterface: IExtractImage;
    function GetExtractImageInterface2: IExtractImage2;
  protected
    property PathExtracted: Boolean read FPathExtracted write FPathExtracted;
  public
    constructor Create;
    property ColorDepth: Longword read FColorDepth write FColorDepth;
    property ImagePath: WideString read GetImagePath;
    property Image: TBitmap read GetImage;
    property ExtractImageInterface: IExtractImage read GetExtractImageInterface;
    property ExtractImage2Interface: IExtractImage2 read GetExtractImageInterface2;
    property Flags: Longword read FFlags write FFlags;
    property Height: Longword read FHeight write FHeight;
    property Owner: TNamespace read FOwner write FOwner;
    property Priority: Longword read FPriority;
    property Width: Longword read FWidth write FWidth;
  end;
{-------------------------------------------------------------------------------}

{-------------------------------------------------------------------------------}
{ Encapsulates IShellLink, ASCI and Unicode                                     }
{-------------------------------------------------------------------------------}

  TVirtualShellLink = class(TComponent)
  private
    FFileName: WideString;            // File name of the lnk file
    FShellLinkA: IShellLink;           // ShellLink interface
    FShellLinkW: IShellLinkW;         // ShellLinkW interface
    FIconIndex: integer;              // Index of the icon to be used with the link
    FTargetIDList: PItemIDList;       // If the Target is a virtual object the PIDL is the only way to make the link
    FShowCmd: TCmdShow;               // How to show the window of the target application
    FHotKeyModifiers: THotKeyModifiers;  // The key modifiers for short cuts
    FTargetPath: WideString;          // The target that will be executed
    FArguments: WideString;           // Any arguments to be passed to the target
    FDescription: WideString;         // A description that will be shown in the properties dialog
    FWorkingDirectory: WideString;    // The directory the target application will have set as its current directory
    FIconLocation: WideString;        // The file that has the icon for the link
    FHotKey: Word;                    // The HotKey to execute the link, used with the FHotKeyModifiers
    FSilentWrite: Boolean;            // Do not check parameters before writing lnk file and show a warning
    function GetShellLinkAInterface: IShellLinkA;
    function GetShellLinkWInterface: IShellLinkW;
  protected
    procedure FreeTargetIDList;

  public
    destructor Destroy; override;
    function ReadLink(LinkFileName: WideString): Boolean;
    function WriteLink(LinkFileName: WideString): Boolean;

    property Arguments: WideString read FArguments write FArguments;
    property Description: WideString read FDescription write FDescription;
    property FileName: WideString read FFileName write FFileName;
    property HotKey: Word read FHotKey write FHotKey;
    property HotKeyModifiers: THotKeyModifiers read FHotKeyModifiers write FHotKeyModifiers;
    property IconIndex: integer read FIconIndex write FIconIndex;
    property IconLocation: WideString read FIconLocation write FIconLocation;
    property TargetIDList: PItemIDList read FTargetIDList write FTargetIDList;
    property ShellLinkAInterface: IShellLink read GetShellLinkAInterface;
    property ShellLinkWInterface: IShellLinkW read GetShellLinkWInterface;
    property ShowCmd: TCmdShow read FShowCmd write FShowCmd; // SW_XXXX contants
    property SilentWrite: Boolean read FSilentWrite write FSilentWrite;
    property TargetPath: WideString read FTargetPath write FTargetPath;
    property WorkingDirectory: WideString read FWorkingDirectory write FWorkingDirectory;
  end;
{-------------------------------------------------------------------------------}

  // General helper class to sort Shell related objects.  Uses mainly to sort
  // columns in details mode
  TShellSortHelper = class
  private
    FFileSort: TFileSort;  // Defines if SortType sorts by the type string or the file extension
  public
    function CompareIDSort(SortColumn: integer; NS1, NS2: TNamespace): Integer; virtual;
    function DiscriminateFolders(NS1, NS2: TNamespace): Integer; virtual;
    function SortFileSize(NS1, NS2: TNamespace): Integer; virtual;
    function SortFileTime(FT1, FT2: TFileTime; NS1, NS2: TNamespace): Integer; virtual;
    function SortString(S1, S2: WideString; NS1, NS2: TNamespace): Integer; virtual;
    function SortType(NS1, NS2: TNamespace): Integer; virtual;

    property FileSort: TFileSort read FFileSort write FFileSort;
  end;
{-------------------------------------------------------------------------------}
{ Function definitions                                                          }
{-------------------------------------------------------------------------------}

  // Return True if VT adds a node to the tree this keeps the item count returned
  // by TNamespace.EnumFolder correct.  To stop the enumeration set Terminate to true
  TEnumFolderCallback = function(MessageWnd: HWnd; APIDL: PItemIDList; AParent: TNamespace;
    Data: Pointer; var Terminate: Boolean): Boolean of object;

  TContextMenuCmdCallback = procedure(Namespace: TNamespace; Verb: WideString;
    MenuItemID: Integer;  var Handled: Boolean) of object;
  TContextMenuShowCallback = procedure(Namespace: TNamespace; Menu: hMenu;
    var Allow: Boolean) of object;
  TContextMenuAfterCmdCallback = procedure(Namespace: TNamespace; Verb: WideString;
    MenuItemID: Integer; Successful: Boolean) of object;
{-------------------------------------------------------------------------------}


{-------------------------------------------------------------------------------}
{ TNamespace, encapsulates the Windows Shell Namespace                          }
{-------------------------------------------------------------------------------}

  { TNamespace is a class that encapsulates the IShellFolder interface.  It     }
  { simplifies shell interfaces by hiding the overhead of PIDLs and COM.        }
  { Most properties and methods have a direct corrolation to the functions      }
  { exposed by IShellFolder.                                                    }

  TNamespace = class
  private
    FAbsolutePIDL: PItemIDList;            // The Absolute PIDL of that represents the namespace
    FCatInfo: TCategoriesInfo;
    FCurrentContextMenu: IContextMenu;     // The basic interface to create a shell context menu, need to save because of ownerdraw callbacks (maybe this one is not necessary IContextMenu2 only supports this)
    FCurrentContextMenu2: IContextMenu2;   // Extends the context menu interface to include ownerdraw items, need to save because of ownerdraw callbacks
    FDropTargetInterface: IDropTarget;     // Interface to handle a drag Drop on the namespace
    FExtractImage: TExtractImage;          // Encapsulate the seldom used IExtractImage inteface saving memory allocation in the TNamespace when not used
    FImage: TBitmap;                       // The image extracted from the IExtractImage interface
    FNamespaceID: integer;                 // ID of the namespace. Used to pick out any custom namespace objects from real shell supplied ones
    FOldWndProcForContextMenu: TWndMethod; // OldWndProc of the ContextMenu owner used in InternalShowContextMenu
    FParent: TNamespace;                   // The parent of this namespace, may be owned by this decenant see OwnsParent property
    FRelativePIDL: PItemIDList;            // The relative PILD that can be used the the ParentNamespace.  It is a pointer to the last ID of of AbsolutePILD so *don't* free it
    FShellDetailsInterface: IVETShellDetails; // Interface to deal with the information in the columns in details view (superceded by IShellFolder2 )
    FShellFolder: IShellFolder;            // IShellFolder is the building block interface that defines the namespaces attributes
    FShellFolder2: IShellFolder2;          // Expands IShellFolder handling the column details in Details mode, only works on Win2k-WinMe and up
    FShellIconInterface: IShellIcon;       // Interface to extract only the index of the icon in the system imagelist
    FShellLink: TVirtualShellLink;         // Object to read and write attributes to shortcut namespaces (files)
    FSHGetFileInfoRec: PSHGetFileInfoRec;  // Stores cached info from a call to SHGetFileInfo(A or W)
    FStates: TNamespaceStates;             // Dynamic state of the TNamespace
    FTag: integer;
    FTileDetail: TCommonIntegerDynArray; //
    FQueryInfoInterface: IQueryInfo;       // Interface for the popup InfoTips on folders in Win2k-WinME and up
    FWin32FindDataA: PWin32FindDataA;      // pointer to an allocated structure for an ASCI window file information if is is a file object
    FWin32FindDataW: PWin32FindDataW;      // pointer to an allocated structure for an Unicode window file information if is is a file object
    FSystemIsSuperHidden: Boolean;         // Holds the result of if the system has the SuperHiddenFile flag set in the registry
    FShellIconOverlayInterface: IShellIconOverlay;
    FCategoryProviderInterface: ICategoryProvider;
    FBrowserFrameOptionsInterface: IBrowserFrameOptions;
    FQueryAssociationsInterface: IQueryAssociations;

    function GetCategoryCount: Integer;
    function GetParent: TNamespace;
  protected
    { Make the Cache Data and property getters available to decendants. This    }
    { will allow decendants of TNamespace to be created so "virtual namespaces" }
    { can be created.  It is possible to create a "namespace extension" without }
    { really doing it!                                                          }
    { None of interface properties are here because they only make sense for    }
    { actual COM namespaces.                                                    }
    FShellCache: TShellCacheRec;

    { Virtual Property Setters }
    function GetArchive: Boolean; virtual;
    function GetAttributesString: WideString; virtual;
    function GetBrowsable: Boolean; virtual;
    function GetBrowserFrameOptionsInterface: IBrowserFrameOptions; virtual;
    function GetCanCopy: Boolean; virtual;
    function GetCanDelete: Boolean; virtual;
    function GetCanLink: Boolean; virtual;
    function GetCanMoniker: Boolean; virtual;
    function GetCanMove: Boolean; virtual;
    function GetCanRename: Boolean; virtual;
    function GetCategoryProviderInterface: ICategoryProvider; virtual;
    function GetCLSID: TGUID; virtual;
    function GetCompressed: Boolean; virtual;
    function GetContextMenu2Interface: IContextMenu2; virtual;
    function GetContextMenu3Interface: IContextMenu3; virtual;
    function GetContextMenuInterface: IContextMenu; virtual;
    function GetCreationDateTime: TDateTime; virtual;
    function GetCreationTime: WideString; virtual;
    function GetCreationTimeRaw: TFileTime; virtual;
    function GetDataObjectInterface: IDataObject; virtual;
    function GetDescription: TObjectDescription; virtual;
    function GetDetailsSupported: Boolean; virtual;
    function GetDirectory: Boolean; virtual;
    function GetDropTarget: Boolean; virtual;
    function GetDropTargetInterface: IDropTarget; virtual;
    function GetEncrypted: Boolean; virtual;
    function GetExtension: WideString; virtual;
    function GetExtractIconAInterface: IExtractIconA; virtual;
    function GetExtractIconWInterface: IExtractIconW; virtual;
    function GetExtractImage: TExtractImage; virtual;
    function GetFileName: WideString; virtual;
    function GetFileSysAncestor: Boolean; virtual;
    function GetFileSystem: Boolean; virtual;
    function GetFileType: WideString; virtual;
    function GetFolder: Boolean; virtual;
    function GetFreePIDLOnDestroy: Boolean; virtual;
    function GetGhosted: Boolean; virtual;
    function GetHasPropSheet: Boolean; virtual;
    function GetHasStorage: Boolean; virtual;
    function GetHasSubFolder: Boolean; virtual;
    function GetHidden: Boolean; virtual;
    function GetIconIndexChanged: Boolean; virtual;
    function GetInfoTip: WideString; virtual;
    function GetIsSlow: Boolean; virtual;
    function GetLastAccessDateTime: TDateTime; virtual;
    function GetLastAccessTime: WideString; virtual;
    function GetLastAccessTimeRaw: TFileTime; virtual;
    function GetLastWriteDateTime: TDateTime; virtual;
    function GetLastWriteTime: WideString; virtual;
    function GetLastWriteTimeRaw: TFileTime; virtual;
    function GetLink: Boolean; virtual;
    function GetNameAddressbar: WideString; virtual;
    function GetNameAddressbarInFolder: WideString; virtual;
    function GetNameForEditing: WideString; virtual;
    function GetNameForEditingInFolder: WideString; virtual;
    function GetNameForParsing: WideString; virtual;
    function GetNameForParsingInFolder: WideString; virtual;
    function GetNameInFolder: WideString; virtual;
    function GetNameNormal: WideString; virtual;
    function GetNameParseAddress: WideString; virtual;
    function GetNameParseAddressInFolder: WideString; virtual;
    function GetNewContent: Boolean; virtual;
    function GetNonEnumerated: Boolean; virtual;
    function GetNormal: Boolean; virtual;
    function GetOffLine: Boolean; virtual;
    function GetOverlayIconIndex: Integer; virtual;
    function GetOverlayIndex: Integer; virtual;
    function GetParentShellDetailsInterface: IVETShellDetails; virtual;
    function GetParentShellFolder: IShellFolder; virtual;
    function GetParentShellFolder2: IShellFolder2; virtual;
    function GetQueryAssociationsInterface: IQueryAssociations; virtual;
    function GetQueryInfoInterface: IQueryInfo;virtual;
    function GetReadOnly: Boolean; virtual;
    function GetReadOnlyFile: Boolean; virtual;
    function GetReparsePoint: Boolean; virtual;
    function GetRemovable: Boolean; virtual;
    function GetShare: Boolean; virtual;
    function GetShellDetailsInterface: IVETShellDetails; virtual;
    function GetShellFolder: IShellFolder; virtual;
    function GetShellFolder2: IShellFolder2; virtual;
    function GetShellIconInterface: IShellIcon; virtual;
    function GetShellIconOverlayInterface: IShellIconOverlay; virtual;
    function GetShellLink: TVirtualShellLink; virtual;
    function GetShortFileName: WideString; virtual;
    function GetSizeOfFile: WideString; virtual;
    function GetSizeOfFileDiskUsage: WideString; virtual;
    function GetSizeOfFileInt64: Int64; virtual;
    function GetSizeOfFileKB: WideString; virtual;
    function GetSparseFile: Boolean; virtual;
    function GetStorage: Boolean; virtual;
    function GetStorageAncestor: Boolean; virtual;
    function GetStream: Boolean; virtual;
    function GetSubFolders: Boolean; virtual;
    function GetSubItems: Boolean; virtual;
    function GetSystem: Boolean; virtual;
    function GetTemporary: Boolean; virtual;
    function GetThreadedDetailLoaded(ColumnIndex: Integer): Boolean; virtual;
    function GetThreadedDetailLoading(ColumnIndex: Integer): Boolean; virtual;
    function GetThreadedIconLoaded: Boolean; virtual;
    function GetThreadedImageLoaded: Boolean; virtual;
    function GetThreadedImageLoading: Boolean; virtual;
    function GetThreadIconLoading: Boolean; virtual;
    function GetValid: Boolean; virtual;
    function ParentWnd: HWnd;
    procedure EnsureDetailCache;
    procedure ExecuteContextMenuVerbMultiPath(Verb: WideString; Namespaces: TNamespaceArray);
    procedure LoadCategoryInfo;
    procedure SetFreePIDLOnDestroy(const Value: Boolean); virtual;
    procedure SetIconIndexChanged(const Value: Boolean); virtual;
    procedure SetThreadedDetailLoaded(ColumnIndex: Integer; Value: Boolean); virtual;
    procedure SetThreadedDetailLoading(ColumnIndex: Integer; Value: Boolean); virtual;
    procedure SetThreadIconLoading(const Value: Boolean); virtual;
    procedure SetThreadImageLoading(const Value: Boolean); virtual;

    function CreateCategory(GUID: TGUID): ICategorizer;
    function EnumFuncDummy(MessageWnd: HWnd; APIDL: PItemIDList; AParent: TNamespace; Data: Pointer; var Terminate: Boolean): Boolean;
    function ExplorerStyleAttributeStringList(CapitalLetters: Boolean): WideString;
    function DisplayNameOf(Flags: Longword): WideString;
    procedure GetDataFromIDList;
    procedure GetFileTimes;
    procedure GetSHFileInfo;
    function InjectCustomSubMenu(Menu: HMenu; Caption: string; PopupMenu: TPopupMenu; var SubMenu: HMenu): TMenuItemIDArray;
    function InternalGetContextMenuInterface(PIDLArray: TRelativePIDLArray): IContextMenu;
    function InternalGetDataObjectInterface(PIDLArray: TRelativePIDLArray): IDataObject;
    function InternalShowContextMenu(Owner: TWinControl; ContextMenuCmdCallback: TContextMenuCmdCallback;
      ContextMenuShowCallback: TContextMenuShowCallback; ContextMenuAfterCmdCallback: TContextMenuAfterCmdCallback;
      PIDLArray: TRelativePIDLArray; Position: PPoint;
      CustomShellSubMenu: TPopupMenu; CustomSubMenuCaption: WideString): Boolean;
    function InternalSubItems(Flags: Longword): Boolean;
    procedure ReplacePIDL(NewPIDL: PItemIDList; AParent: TNamespace);
    function ShowContextMenuMultiPath(Owner: TWinControl; Focused: TNamespace;
      Namespaces: TNamespaceArray; Position: PPoint = nil): Boolean;
    procedure WindowProcForContextMenu(var Message: TMessage);

    property CatInfo: TCategoriesInfo read FCatInfo write FCatInfo;
    property CurrentContextMenu2: IContextMenu2 read FCurrentContextMenu2 write FCurrentContextMenu2;
    property CurrentContextMenu: IContextMenu read FCurrentContextMenu write FCurrentContextMenu;
    property ShellCache: TShellCacheRec read FShellCache write FShellCache;
    property SystemIsSuperHidden: Boolean read FSystemIsSuperHidden write FSystemIsSuperHidden;

  public
    constructor Create(PIDL: PItemIdList; AParent: TNamespace);
    destructor Destroy; override;

    constructor CreateCustomNamespace(CustomID: Integer; AParent: TNamespace); virtual;
    constructor CreateFromFileName(FileName: WideString); virtual;
    function CanCopyAll(NamespaceArray: TNamespaceArray): Boolean; virtual;
    function CanCutAll(NamespaceArray: TNamespaceArray): boolean; virtual;
    function CanDeleteAll(NamespaceArray: TNamespaceArray): Boolean; virtual;
    function CanPasteToAll(NamespaceArray: TNamespaceArray): Boolean; virtual;
    function CanShowPropertiesOfAll(NamespaceArray: TNamespaceArray): Boolean; virtual;
    function Clone(ReleasePIDLOnDestroy: Boolean): TNameSpace; virtual;
    function ComparePIDL(PIDLToCompare: PItemIDList; IsAbsolutePIDL: Boolean; Column: Integer = 0): ShortInt; virtual;
    function ContextMenuItemHelp(MenuItemID: LongWord): WideString; virtual;
    function ContextMenuVerb(MenuItemID: LongWord): WideString; virtual;
    function Copy(NamespaceArray: TNamespaceArray): Boolean; virtual;
    function Cut(NamespaceArray: TNamespaceArray): Boolean; virtual;
    function DataObjectMulti(NamespaceArray: TNamespaceArray): IDataObject; virtual;
    function Delete(NamespaceArray: TNamespaceArray): Boolean; virtual;
    function DetailsAlignment(ColumnIndex: Integer): TAlignment; virtual;
    function DetailsColumnTitle(ColumnIndex: integer): WideString; virtual;
    function DetailsDefaultColumnTitle(ColumnIndex: integer): WideString; virtual;
    function DetailsDefaultOf(ColumnIndex: integer): WideString; virtual;
    function DetailsDefaultSupportedColumns: integer; virtual;
    function DetailsGetDefaultColumnState(ColumnIndex: integer): TSHColumnStates; virtual;
    function DetailsOf(ColumnIndex: integer): WideString; virtual;
    function DetailsOfEx(ColumnIndex: integer): WideString; virtual;
    function DetailsSupportedColumns: integer; virtual;
    function DetailsSupportedVisibleColumns: TVisibleColumnIndexArray; virtual;
    function DetailsValidIndex(DetailsIndex: integer): Boolean; virtual;
    function DragEffect(grfKeyState: integer): HRESULT; virtual;
    function DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; virtual;
    function DragLeave: HResult;  virtual;
    function DragOver(grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;  virtual;
    function Drop(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;  virtual;
    function EnumerateFolder(MessageWnd: HWnd; Folders, NonFolders, IncludeHidden: Boolean;
      EnumFunc: TEnumFolderCallback; UserData: pointer): integer;  virtual;
    function EnumerateFolderEx(MessageWnd: HWnd; FileObjects: TFileObjects; EnumFunc: TEnumFolderCallback; UserData: pointer; AfterValidEnumIDList: TNotifyEvent = nil): integer; virtual;
    function ExecuteContextMenuVerb(AVerb: WideString; APIDLArray: TRelativePIDLArray; MessageWindowParent: HWnd = 0): Boolean;  virtual;
    function FolderSize(Invalidate: Boolean; RecurseFolder: Boolean = False): Int64;  virtual;
    function GetIconIndex(OpenIcon: Boolean; IconSize: TIconSize; ForceLoad: Boolean = True): integer; virtual;
    function GetImage: TBitmap;  virtual;
    function VerifyPIDLRelationship(NamespaceArray: TNamespaceArray;
      Silent: Boolean = False): Boolean;
    procedure HandleContextMenuMsg(Msg, wParam, lParam: Longint; var Result: LRESULT);  virtual;
    procedure InvalidateCache;  virtual;
    procedure InvalidateDetailsOfCache(FlushStrings: Boolean);
    procedure InvalidateNamespace(RefreshIcon: Boolean = True);  virtual;
    procedure InvalidateRelativePIDL(FileObjects: TFileObjects);  virtual;
    procedure InvalidateThumbImage;  virtual;
    function IsChildByNamespace(TestNamespace: TNamespace; Immediate: Boolean): Boolean;  virtual;
    function IsChildByPIDL(TestPIDL: PItemIDList; Immediate: Boolean): Boolean;  virtual;
    function IsChildOfRemovableDrive: Boolean;  virtual;
    function IsControlPanel: Boolean;  virtual;
    function IsControlPanelChildFolder: Boolean;  virtual;
    function IsDesktop: Boolean; virtual;

    function IsMyComputer: Boolean;  virtual;
    function IsNetworkNeighborhood: Boolean;  virtual;
    function IsNetworkNeighborhoodChild: Boolean;  virtual;
    function IsParentByNamespace(TestNamespace: TNamespace; Immediate: Boolean): Boolean;  virtual;
    function IsParentByPIDL(TestPIDL: PItemIDList; Immediate: Boolean): Boolean;  virtual;
    function IsRecycleBin: Boolean;
    function OkToBrowse(ShowExplorerMsg: Boolean): Boolean;  virtual;
    function ParseDisplayName: PItemIDList;  overload;  virtual;
    function ParseDisplayName(Path: WideString): PItemIDList; overload;  virtual;
    function Paste(NamespaceArray: TNamespaceArray; AsShortCut: Boolean = False): Boolean;  virtual;
    procedure SetDetailByThread(ColumnIndex: Integer; Detail: WideString);
    procedure SetIconIndexByThread(IconIndex: Integer; ClearThreadLoading: Boolean);  virtual;
    procedure SetImageByThread(Bitmap: TBitmap; ClearThreadLoading: Boolean);  virtual;
    function SetNameOf(NewName: WideString): Boolean;  virtual;
    function ShellExecuteNamespace(WorkingDir, CmdLineArguments: WideString; ExecuteFolder: Boolean = False;
      ExecuteFolderShortCut: Boolean = False; RunInThread: Boolean = False): Boolean;  virtual;
    function ShowContextMenu(Owner: TWinControl;
      ContextMenuCmdCallback: TContextMenuCmdCallback; ContextMenuShowCallback: TContextMenuShowCallback;
      ContextMenuAfterCmdCallback: TContextMenuAfterCmdCallback; Position: PPoint = nil;
      CustomShellSubMenu: TPopupMenu = nil; CustomSubMenuCaption: WideString = ''): Boolean; virtual;
    function ShowContextMenuMulti(Owner: TWinControl;
      ContextMenuCmdCallback: TContextMenuCmdCallback;
      ContextMenuShowCallback: TContextMenuShowCallback;
      ContextMenuAfterCmdCallback: TContextMenuAfterCmdCallback;
      NamespaceArray: TNamespaceArray; Position: PPoint = nil;
      CustomShellSubMenu: TPopupMenu = nil;
      CustomSubMenuCaption: WideString = ''): Boolean; virtual;
    procedure ShowPropertySheet;  virtual;
    procedure ShowPropertySheetMulti(NamespaceArray: TNamespaceArray;
      UseSHMultiFileProperties: Boolean = True;
      ForceNonMultiPath: Boolean = False); virtual;
    function SubFoldersEx(Flags: Longword = SHCONTF_FOLDERS): Boolean;  virtual;
    function SubItemsEx(Flags: Longword = SHCONTF_NONFOLDERS): Boolean;  virtual;
    function TestAttributesOf(Flags: Longword; FlushCache: Boolean; SoftFlush: Boolean = False): Boolean;  virtual;

    property AbsolutePIDL: PItemIDList read FAbsolutePIDL write FAbsolutePIDL;
    property AdvDetailsSupported: Boolean read GetDetailsSupported;
    property Browsable: Boolean read GetBrowsable;
    property BrowserFrameOptionsInterface: IBrowserFrameOptions read GetBrowserFrameOptionsInterface;
    property CanCopy: Boolean read GetCanCopy;
    property CanDelete: Boolean read GetCanDelete;
    property CanLink: Boolean read GetCanLink;
    property CanMoniker: Boolean read GetCanMoniker;
    property CanMove: Boolean read GetCanMove;
    property CanRename: Boolean read GetCanRename;

    property CategoryCount: Integer read GetCategoryCount;
    property CategoryProviderInterface: ICategoryProvider read GetCategoryProviderInterface;


    property CLSID: TGUID read GetCLSID;
    property ContextMenuInterface: IContextMenu read GetContextMenuInterface;
    property ContextMenu2Interface: IContextMenu2 read GetContextMenu2Interface;
    property ContextMenu3Interface: IContextMenu3 read GetContextMenu3Interface;
    property DataObjectInterface: IDataObject read GetDataObjectInterface;
    property Description: TObjectDescription read GetDescription;
    property DropTarget: Boolean read GetDropTarget;
    property DropTargetInterface: IDropTarget read GetDropTargetInterface;
    property Encrypted: Boolean read GetEncrypted;
    property ExtractImage: TExtractImage read GetExtractImage;
    property ExtractIconAInterface: IExtractIconA read GetExtractIconAInterface;
    property ExtractIconWInterface: IExtractIconW read GetExtractIconWInterface;
    property FileSystem: Boolean read GetFileSystem;
    property FileSysAncestor: Boolean read GetFileSysAncestor;
    property Folder: Boolean read GetFolder;
    property FreePIDLOnDestroy: Boolean read GetFreePIDLOnDestroy write SetFreePIDLOnDestroy;
    property Ghosted: Boolean read GetGhosted;
    property HasPropSheet: Boolean read GetHasPropSheet;
    property HasStorage: Boolean read GetHasStorage;
    property HasSubFolder: Boolean read GetHasSubFolder;
    property IconIndexChanged: Boolean read GetIconIndexChanged write SetIconIndexChanged;
    property IsSlow: Boolean read GetIsSlow;
    property Link: Boolean read GetLink;
    property InfoTip: WideString read GetInfoTip;
    property NameAddressbar: WideString read GetNameAddressbar;
    property NameAddressbarInFolder: WideString read GetNameAddressbarInFolder;
    property NameForEditing: WideString read GetNameForEditing;
    property NameForEditingInFolder: WideString read GetNameForEditingInFolder;
    property NameForParsing: WideString read GetNameForParsing;
    property NameForParsingInFolder: WideString read GetNameForParsingInFolder;
    property NameInFolder: WideString read GetNameInFolder;
    property NameNormal: WideString read GetNameNormal;
    property NameParseAddress: WideString read GetNameParseAddress;
    property NameParseAddressInFolder: WideString read GetNameParseAddressInFolder;
    property NamespaceID: integer read FNamespaceID;
    property NewContent: Boolean read GetNewContent;
    property NonEnumerated: Boolean read GetNonEnumerated;
    property Parent: TNamespace read GetParent;
    property ParentShellFolder: IShellFolder read GetParentShellFolder;
    property ParentShellFolder2: IShellFolder2 read GetParentShellFolder2;
    property ParentShellDetailsInterface: IVETShellDetails read GetParentShellDetailsInterface;
    property QueryAssociationsInterface: IQueryAssociations read GetQueryAssociationsInterface;
    property ReadOnly: Boolean read GetReadOnly;
    property RelativePIDL: PItemIDList read FRelativePIDL;  // Single Item ID of this namespace
    property Removable: Boolean read GetRemovable;
    property Share: Boolean read GetShare;
    property ShellFolder: IShellFolder read GetShellFolder;
    property ShellFolder2: IShellFolder2 read GetShellFolder2;
    property ShellDetailsInterface: IVETShellDetails read GetShellDetailsInterface;
    property ShellLink: TVirtualShellLink read GetShellLink;
    property ShellIconInterface: IShellIcon read GetShellIconInterface;
    property ShellIconOverlayInterface: IShellIconOverlay read GetShellIconOverlayInterface;
    property ShortFileName: WideString read GetShortFileName;
    property SparseFile: Boolean read GetSparseFile;
    property States: TNamespaceStates read FStates write FStates;
    property Storage: Boolean read GetStorage;
    property StorageAncestor: Boolean read GetStorageAncestor;
    property Stream: Boolean read GetStream;
    property SubFolders: Boolean read GetSubFolders;
    property SubItems: Boolean read GetSubItems;
    property Tag: integer read FTag write FTag;
    property ThreadedDetailLoaded[Column: Integer]: Boolean read GetThreadedDetailLoaded write SetThreadedDetailLoaded;
    property ThreadedDetailLoading[Column: Integer]: Boolean read GetThreadedDetailLoading write SetThreadedDetailLoading;
    property ThreadedIconLoaded: Boolean read GetThreadedIconLoaded;
    property ThreadIconLoading: Boolean read GetThreadIconLoading write SetThreadIconLoading;
    property ThreadImageLoaded: Boolean read GetThreadedImageLoaded;
    property ThreadImageLoading: Boolean read GetThreadedImageLoading write SetThreadImageLoading;
    property TileDetail: TCommonIntegerDynArray read FTileDetail write FTileDetail;
    property QueryInfoInterface: IQueryInfo read GetQueryInfoInterface;
    property Win32FindDataA: PWin32FindDataA read FWin32FindDataA;
    property Win32FindDataW: PWin32FindDataW read FWin32FindDataW;
    { Information on namespaces that are actual files.                          }
    property AttributesString: WideString read GetAttributesString; // Explorer type 'RHSA'
    property Archive: Boolean read GetArchive;
    property Compressed: Boolean read GetCompressed;
    property CreationTime: WideString read GetCreationTime;
    property CreationDateTime: TDateTime read GetCreationDateTime;
    property CreationTimeRaw: TFileTime read GetCreationTimeRaw;
    property Directory: Boolean read GetDirectory;
    property Extension: WideString read GetExtension;
    property FileName: WideString read GetFileName;
    property FileType: WideString read GetFileType;
    property Hidden: Boolean read GetHidden;
    property LastAccessTime: WideString read GetLastAccessTime;
    property LastAccessDateTime: TDateTime read GetLastAccessDateTime;
    property LastAccessTimeRaw: TFileTime read GetLastAccessTimeRaw;
    property LastWriteTime: WideString read GetLastWriteTime;
    property LastWriteDateTime: TDateTime read GetLastWriteDateTime;
    property LastWriteTimeRaw: TFileTime read GetLastWriteTimeRaw;
    property Normal: Boolean read GetNormal;
    property OffLine: Boolean read GetOffLine;
    property OverlayIndex: Integer read GetOverlayIndex;
    property OverlayIconIndex: Integer read GetOverlayIconIndex;
    property ReadOnlyFile: Boolean read GetReadOnlyFile;
    property ReparsePoint: Boolean read GetReparsePoint;
    property SizeOfFile: WideString read GetSizeOfFile;
    property SizeOfFileInt64: Int64 read GetSizeOfFileInt64;
    property SizeOfFileKB: WideString read GetSizeOfFileKB;
    property SizeOfFileDiskUsage: WideString read GetSizeOfFileDiskUsage;
    property SystemFile: Boolean read GetSystem;
    property Temporary: Boolean read GetTemporary;
    property Valid: Boolean read GetValid;
 end;
{-------------------------------------------------------------------------------}
  
  {$IFNDEF COMPILER_5_UP}
  TObjectList = class(TList)
  private
    FOwnsObjects: Boolean;
  protected
    function GetItem(Index: Integer): TObject;
    procedure SetItem(Index: Integer; AObject: TObject);
  public
    constructor Create; overload;
    constructor Create(AOwnsObjects: Boolean); overload;

    function Add(AObject: TObject): Integer;
    function Remove(AObject: TObject): Integer;
    function IndexOf(AObject: TObject): Integer;
    function FindInstanceOf(AClass: TClass; AExact: Boolean = True; AStartAt: Integer = 0): Integer;
    procedure Insert(Index: Integer; AObject: TObject);
    function First: TObject;
    function Last: TObject;
    property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects;
    property Items[Index: Integer]: TObject read GetItem write SetItem; default;
  end;
  {$ENDIF}

  TVirtualNameSpaceList  = class(TObjectList)
  private
    function GetItems(Index: Integer): TNamespace;
    procedure SetItems(Index: Integer; ANamespace: TNamespace);
  public
    function Add(ANamespace: TNamespace): Integer;
    procedure FillArray(var NamespaceArray: TNamespaceArray);
    function IndexOf(ANamespace: TNamespace): Integer;
    procedure Insert(Index: Integer; ANamespace: TNamespace);
    property Items[Index: Integer]: TNamespace read GetItems write SetItems; default;
  end;

 //
  // Used to operate on files that are not the immediate children of the parent
  // for instance in a search list
  //
  // This class will only work on a system that has Shell32.dll version 4.71 and
  // higher.  If the system has at least IE 4.0 it should be compatible
  //
  // Use this class as the starting point for a custom Shell Context Menu.  It
  // will handle all of the localization of names for you.
  // It will also handle objects that do not have the same immediate parent such
  // as in a search list.
  //
  // There is some danger to this as the app could free the object while
  // the shell still has a connection to it through IShellFolder or IDropTarget.
  // It should be virtually impossible to do this as the shell only connects to
  // the interfaces during the time the menu is open.
  PMenuItemLink = ^TMenuItemLink;
  TMenuItemLink = record
    MenuID: UINT;
    {$IFDEF TNTSUPPORT}
    Item: TTntMenuItem;
    {$ELSE}
    Item: TMenuItem
    {$ENDIF}
  end;

  TMenuItemMap = class(TList)
  protected
    function Get(Index: Integer): PMenuItemLink;
    procedure Put(Index: Integer; Item: PMenuItemLink);
  public
    function Add: PMenuItemLink;
    function First: PMenuItemLink;
    function IndexOf(Item: PMenuItemLink): Integer;
    procedure Clear; override;
    function Insert(Index: Integer): PMenuItemLink; reintroduce;
    function Last: PMenuItemLink;
    function Remove(Item: PMenuItemLink): Integer;
    property Items[Index: Integer]: PMenuItemLink read Get write Put; default;
  end;

  TCommonShellContextMenu = class(TComponent, IUnknown, IShellFolder, IDropTarget)
  private
    FActiveFolder: IShellFolder;
    FActivePIDLs: TPIDLArray;
    FCopyValidated: Boolean;
    FCurrentContextMenu: IContextMenu;
    FCurrentContextMenu2: IContextMenu2;
    FCutValidated: Boolean;
    FExtensions: TCommonShellContextMenuExtensions;
    FFromDesktop: Boolean;
    FLocalFocused: TNamespace;
    FLocalNamespaces: TNamespaceArray;
    FMenuMap: TMenuItemMap;
    FOldWndProcForContextMenu: TWndMethod;
    {$IFDEF TNTSUPPORT}
    FKeyStrings: TTntStringList;
    {$ELSE}
    FKeyStrings: TStringList;
    {$ENDIF}
    FOnHide: TCommonShellMenuEvent;
    FOnInvokeCommand: TCommonShellMenuInvokeEvent;
    FOnMenuMerge: TCommonShellMenuMergeEvent;
    FOnMenuMergeBottom: TCommonShellMenuMergeEvent;
    FOnMenuMergeTop: TCommonShellMenuMergeEvent;
    FOnShellMenuCopy: TCommonShellMenuCopyEvent;
    FOnShellMenuCreateShortcut: TCommonShellMenuCreateShortcutEvent;
    FOnShellMenuCut: TCommonShellMenuCutEvent;
    FOnShellMenuDelete: TCommonShellMenuDeleteEvent;
    FOnShellMenuNewFolder: TCommonShellMenuNewFolderEvent;
    FOnShellMenuPaste: TCommonShellMenuPasteEvent;
    FOnShellMenuPasteLink: TCommonShellMenuPasteLinkEvent;
    FOnShellMenuPasteShortCut: TCommonShellMenuPasteShortCutEvent;
    FOnShellMenuProperites: TCommonShellMenuProperitesEvent;
    FOnShow: TCommonShellMenuEvent;
    FRenameMenuItem: Boolean;
    FStub: Pointer;
    FPasteMenuItem: Boolean;
    FUIObjectOfDataObject: IDataObject;
    FUIObjectOfDropTarget: IDropTarget;
  protected
  // IUnknown
    function QueryInterface(const IID: TGUID; out Obj): HResult; override; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    
  // IShellFolder
    function ParseDisplayName(hwndOwner: HWND; pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG; out ppidl: PItemIDList; var dwAttributes: ULONG): HResult; stdcall;
    function EnumObjects(hwndOwner: HWND; grfFlags: DWORD; out EnumIDList: IEnumIDList): HResult; stdcall;
    function BindToObject(pidl: PItemIDList; pbcReserved: Pointer; const riid: TIID; out ppvOut{$IFNDEF COMPILER_5_UP}: Pointer{$ENDIF}): HResult; stdcall;
    function BindToStorage(pidl: PItemIDList; pbcReserved: Pointer; const riid: TIID; out ppvObj{$IFNDEF COMPILER_5_UP}: Pointer{$ENDIF}): HResult; stdcall;
    function CompareIDs(lParam: LPARAM; pidl1, pidl2: PItemIDList): HResult; stdcall;
    function CreateViewObject(hwndOwner: HWND; const riid: TIID; out ppvOut{$IFNDEF COMPILER_5_UP}: Pointer{$ENDIF}): HResult; stdcall;
    function GetAttributesOf(cidl: UINT; var apidl: PItemIDList; var rgfInOut: UINT): HResult; stdcall;
    function GetUIObjectOf(hwndOwner: HWND; cidl: UINT; var apidl: PItemIDList; const riid: TIID; prgfInOut: Pointer; out ppvOut{$IFNDEF COMPILER_5_UP}: Pointer{$ENDIF}): HResult; stdcall;
    function GetDisplayNameOf(pidl: PItemIDList; uFlags: DWORD; var lpName: TStrRet): HResult; stdcall;
    function SetNameOf(hwndOwner: HWND; pidl: PItemIDList; lpszName: POLEStr; uFlags: DWORD; var ppidlOut: PItemIDList): HResult; stdcall;
    // IDropTarget
    function DragEnter(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
    function DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
    function DragLeave: HResult; stdcall;
    function Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;

    function DefMenuCreateCallback(const psf: IShellfolder; wnd: HWND; const pdtObj: IDataObject; uMsg: UINT; WParm: WParam; lParm: LParam): HResult; stdcall;

    procedure AddMenuKey(Key: WideString);
    {$IFDEF TNTSUPPORT}
    procedure AddMenuKeys(Keys: TTntStringList);
    {$ELSE}
    procedure AddMenuKeys(Keys: TStringList);
    {$ENDIF}
    procedure ClearKeys;
    procedure DoCopy(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean); virtual;
    procedure DoCreateShortCut(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean); virtual;
    procedure DoCut(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean); virtual;
    procedure DoDelete(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean); virtual;
    procedure DoHide; virtual;
    {$IFDEF TNTSUPPORT}
    procedure DoInvokeCommand(MenuItem: TTntMenuItem; InvokeInfo: PCMInvokeCommandInfo); virtual;
    {$ELSE}
    procedure DoInvokeCommand(MenuItem: TMenuItem; InvokeInfo: PCMInvokeCommandInfo); virtual;
    {$ENDIF}
    procedure DoMenuMerge(Menu: HMENU; IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags); virtual;
    procedure DoMenuMergeBottom(Menu: HMENU; IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags); virtual;
    procedure DoMenuMergeTop(Menu: HMENU; IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags); virtual;
    procedure DoNewFolder(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean); virtual;
    procedure DoPaste(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean); virtual;
    procedure DoPasteLink(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean); virtual;
    procedure DoPasteShortCut(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean); virtual;
    procedure DoProperties(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean); virtual;
    procedure DoShow; virtual;
    function DuplicateKey(Key: HKEY): HKEY;
    {$IFDEF TNTSUPPORT}
    function FindCommandId(CmdID: UINT; var MenuItem: TTntMenuItem): Boolean;
    {$ELSE}
    function FindCommandId(CmdID: UINT; var MenuItem: TMenuItem): Boolean;
    {$ENDIF}
    procedure HandleContextMenuMsg(Msg, wParam, lParam: Longint; var Result: LRESULT); stdcall;
    function InternalShowContextMenu(Owner: TWinControl; ParentPIDL: PItemIDList; ChildPIDLs: TAbsolutePIDLArray; Verb: WideString; Position: PPoint = nil): Boolean;
    procedure LoadMultiFolderPIDLArray(Namespaces: TNamespaceArray; var PIDLs: TAbsolutePIDLArray);
    procedure LoadRegistryKeyStrings(Focused: TNamespace); virtual; abstract;
    procedure WindowProcForContextMenu(var Message: TMessage);

    property ActiveFolder: IShellFolder read FActiveFolder write FActiveFolder;
    property ActivePIDLs: TPIDLArray read FActivePIDLs write FActivePIDLs;
    property CopyValidated: Boolean read FCopyValidated write FCopyValidated;
    property CurrentContextMenu2: IContextMenu2 read FCurrentContextMenu2 write FCurrentContextMenu2;
    property CurrentContextMenu: IContextMenu read FCurrentContextMenu write FCurrentContextMenu;
    property CutValidated: Boolean read FCutValidated write FCutValidated;
    property Extensions: TCommonShellContextMenuExtensions read FExtensions write FExtensions;
    property FromDesktop: Boolean read FFromDesktop write FFromDesktop;
    {$IFDEF TNTSUPPORT}
    property KeyStrings: TTntStringList read FKeyStrings write FKeyStrings;
    {$ELSE}
    property KeyStrings: TStringList read FKeyStrings write FKeyStrings;
    {$ENDIF}
    property LocalFocused: TNamespace read FLocalFocused write FLocalFocused;
    property LocalNamespaces: TNamespaceArray read FLocalNamespaces write FLocalNamespaces;
    property MenuMap: TMenuItemMap read FMenuMap write FMenuMap;
    property OnHide: TCommonShellMenuEvent read FOnHide write FOnHide;
    property OnInvokeCommand: TCommonShellMenuInvokeEvent read FOnInvokeCommand write FOnInvokeCommand;
    property OnMenuMerge: TCommonShellMenuMergeEvent read FOnMenuMerge write FOnMenuMerge;
    property OnMenuMergeBottom: TCommonShellMenuMergeEvent read FOnMenuMergeBottom write FOnMenuMergeBottom;
    property OnMenuMergeTop: TCommonShellMenuMergeEvent read FOnMenuMergeTop write FOnMenuMergeTop;
    property OnShellMenuCopy: TCommonShellMenuCopyEvent read FOnShellMenuCopy write FOnShellMenuCopy;
    property OnShellMenuCreateShortCut: TCommonShellMenuCreateShortcutEvent read FOnShellMenuCreateShortcut write FOnShellMenuCreateShortcut;
    property OnShellMenuCut: TCommonShellMenuCutEvent read FOnShellMenuCut write FOnShellMenuCut;
    property OnShellMenuDelete: TCommonShellMenuDeleteEvent read FOnShellMenuDelete write FOnShellMenuDelete;
    property OnShellMenuNewFolder: TCommonShellMenuNewFolderEvent read FOnShellMenuNewFolder write FOnShellMenuNewFolder;
    property OnShellMenuPaste: TCommonShellMenuPasteEvent read FOnShellMenuPaste write FOnShellMenuPaste;
    property OnShellMenuPasteLink: TCommonShellMenuPasteLinkEvent read FOnShellMenuPasteLink write FOnShellMenuPasteLink;
    property OnShellMenuPasteShortCut: TCommonShellMenuPasteShortCutEvent read FOnShellMenuPasteShortCut write FOnShellMenuPasteShortCut;
    property OnShellMenuProperites: TCommonShellMenuProperitesEvent read FOnShellMenuProperites write FOnShellMenuProperites;
    property OnShow: TCommonShellMenuEvent read FOnShow write FOnShow;
    property Stub: Pointer read FStub write FStub;
    property RenameMenuItem: Boolean read FRenameMenuItem write FRenameMenuItem default True;
    property PasteMenuItem: Boolean read FPasteMenuItem write FPasteMenuItem default True;
    property UIObjectOfDataObject: IDataObject read FUIObjectOfDataObject write FUIObjectOfDataObject;
    property UIObjectOfDropTarget: IDropTarget read FUIObjectOfDropTarget write FUIObjectOfDropTarget;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    {$IFDEF TNTSUPPORT}
    function MergeMenuIntoContextMenu(Menu: TTntPopupMenu; ContextMenu: HMenu; Index: Integer; idStart: UINT): Integer;
    {$ELSE}
    function MergeMenuIntoContextMenu(Menu: TPopupMenu; ContextMenu: HMenu; Index: Integer; idStart: UINT): Integer;
    {$ENDIF}
    procedure ClearMenuMap;
  end;

{*******************************************************************************}
{  Shell ContextMenu                                                            }
{*******************************************************************************}
  TCommonShellBackgroundContextMenu = class(TCommonShellContextMenu)
  private
    FAutoDetectNewItem: Boolean;
    FFinalItemList: TCommonPIDLList;
    FInitialItemList: TCommonPIDLList;
    FOnNewItem: TCommonShellMenuNewItemEvent;
    {$IFDEF TNTSUPPORT}
    FPaste: TTntMenuItem;
    FPasteShortCut: TTntMenuItem;
    FPopupMenuProperties: TTntPopupMenu;
    FPopupMenuPaste: TTntPopupMenu;
    FPopupMenuPasteShortCut: TTntPopupMenu;
    FProperties: TTntMenuItem;
    {$ELSE}
    FPaste: TMenuItem;
    FPasteShortCut: TMenuItem;
    FPopupMenuProperties: TPopupMenu;
    FPopupMenuPaste: TPopupMenu;
    FPopupMenuPasteShortCut: TPopupMenu;
    FProperties: TMenuItem;
    {$ENDIF}
    FShowPasteItem: Boolean;
    FShowPasteShortCutItem: Boolean;
    FShowPropertiesItem: Boolean;
  protected
    procedure ClickPaste(Sender: TObject);
    procedure ClickPasteShortCut(Sender: TObject);
    procedure ClickProperties(Sender: TObject);
    procedure DoHide; override;
    procedure DoMenuMerge(Menu: HMENU; IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags); override;
    procedure DoMenuMergeBottom(Menu: HMENU; IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags); override;
    procedure DoMenuMergeTop(Menu: HMENU; IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags); override;
    procedure DoNewItem(NS: TNamespace); virtual;
    procedure DoShow; override;
    procedure LoadRegistryKeyStrings(Focused: TNamespace); override;
    property FinalItemList: TCommonPIDLList read FFinalItemList write FFinalItemList;
    property InitialItemList: TCommonPIDLList read FInitialItemList write FInitialItemList;
    {$IFDEF TNTSUPPORT}
    property Paste: TTntMenuItem read FPaste write FPaste;
    property PasteShortCut: TTntMenuItem read FPasteShortCut write FPasteShortCut;
    property PopupMenuProperties: TTntPopupMenu read FPopupMenuProperties write FPopupMenuProperties;
    property PopupMenuPaste: TTntPopupMenu read FPopupMenuPaste write FPopupMenuPaste;
    property PopupMenuPasteShortCut: TTntPopupMenu read FPopupMenuPasteShortCut write FPopupMenuPasteShortCut;
    property Properties: TTntMenuItem read FProperties write FProperties;
    {$ELSE}
    property Paste: TMenuItem read FPaste write FPaste;
    property PasteShortCut: TMenuItem read FPasteShortCut write FPasteShortCut;
    property PopupMenuProperties: TPopupMenu read FPopupMenuProperties write FPopupMenuProperties;
    property PopupMenuPaste: TPopupMenu read FPopupMenuPaste write FPopupMenuPaste;
    property PopupMenuPasteShortCut: TPopupMenu read FPopupMenuPasteShortCut write FPopupMenuPasteShortCut;
    property Properties: TMenuItem read FProperties write FProperties;
    {$ENDIF}
    property RenameMenuItem default False;
    property PasteMenuItem default False;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function EnumCallback(MessageWnd: HWnd; APIDL: PItemIDList; AParent: TNamespace; Data: Pointer; var Terminate: Boolean): Boolean;
    function ShowContextMenu(Owner: TWinControl; Root: TNamespace; Position: PPoint = nil): Boolean; virtual;
  published
    property AutoDetectNewItem: Boolean read FAutoDetectNewItem write FAutoDetectNewItem;
    property OnHide;
    property OnInvokeCommand;
    property OnMenuMerge;
    property OnMenuMergeBottom;
    property OnMenuMergeTop;
    property OnNewItem: TCommonShellMenuNewItemEvent read FOnNewItem write FOnNewItem;
    property OnShow;
    property ShowPasteItem: Boolean read FShowPasteItem write FShowPasteItem default True;
    property ShowPasteShortCutItem: Boolean read FShowPasteShortCutItem write FShowPasteShortCutItem default True;
    property ShowPropertiesItem: Boolean read FShowPropertiesItem write FShowPropertiesItem default True;
  end;

  TCommonShellMultiParentContextMenu = class(TCommonShellContextMenu)
  protected
    procedure LoadRegistryKeyStrings(Focused: TNamespace); override;
  public
    function ExecuteContextMenuVerb(Owner: TWinControl; Namespaces: TNamespaceArray; Verb: string): Boolean; virtual;
    function ShowContextMenu(Owner: TWinControl; Focused: TNamespace; Namespaces: TNamespaceArray; Position: PPoint = nil): Boolean; virtual;
  published
    property OnHide;
    property OnShellMenuCopy;
    property OnShellMenuCreateShortCut;
    property OnShellMenuCut;
    property OnShellMenuDelete;
    property OnShellMenuNewFolder;
    property OnShellMenuPaste;
    property OnShellMenuPasteLink;
    property OnShellMenuPasteShortCut;
    property OnShellMenuProperites;
    property RenameMenuItem;
    property PasteMenuItem;
    property OnShow;
  end;


{-------------------------------------------------------------------------------}
{ Exported Functions                                                            }
{-------------------------------------------------------------------------------}

  // Rectange Functions
  function RectWidth(ARect: TRect): integer;
  function RectHeight(ARect: TRect): integer;

  // PIDL Functions
  function FindBrowseableRootPIDL(NS: TNamespace): PItemIDList;
  function IsParentBrowseable(NS: TNamespace): Boolean;
  function NamespaceToAbsolutePIDLArray(Namespaces: TNamespaceArray): TAbsolutePIDLArray;
  function NamespaceToRelativePIDLArray(Namespaces: TNamespaceArray): TRelativePIDLArray;
  function PathToPIDL(APath: WideString): PItemIDList;
  function PIDLToPath(APIDL: PItemIDList): WideString;
  function DirExistsVET(APath: WideString; ShowSystemMessages: Boolean): Boolean; overload;
  function DirExistsVET(NS: TNamespace; ShowSystemMessages: Boolean): Boolean; overload;
  procedure PIDLListQuickSort(PIDLList: TCommonPIDLList; const ParentFolder: IShellFolder; L, R: Integer);
  procedure PIDLQuickSort(PIDLList: TPIDLArray; const ParentFolder: IShellFolder; L, R: Integer);

  // Time Conversions
  //** NOTE these are not WideString functions they will use ANSI strings internally
  function ConvertLocalStrToTFileTime(LocalStr: WideString; var FileTime: TFileTime): Boolean;
  function ConvertTFileTimeToLocalStr(AFileTime: TFILETIME): WideString;
  function ConvertFileTimetoDateTime(AFileTime : TFileTime): TDateTime;

  // Various Functions
  function CreateSpecialNamespace(FolderID: integer): TNamespace;
  function DefaultSystemImageIndex(FolderType: TDefaultFolderIcon): integer;
  function FileIconInit(FullInit: BOOL): BOOL; stdcall;
  function IENamespaceShown(PerUser: Boolean): Boolean;
  function GUIDToInterfaceStr(riid: TGUID): String;
  function CFM_FlagsToShellContextMenuFlags(Flags: DWORD): TShellContextMenuFlags;
  function ClipboardContainsShellFormats: Boolean;

// IShellLink (ShortCut) helpers
  function CreateShellLink(
                           ALnkFilePath,
                           ATargetFilePath: WideString;
                           AnArguments: WideString = '';
                           AWorkingDir: WideString = '';
                           ADescription: WideString = '';
                           AShowCmd: TCmdShow = swShowNormal;
                           AHotKey: Word = 0;
                           AHotKeyModifier: THotKeyModifiers = [];
                           AnIconLocation: WideString = '';
                           AnIconIndex: integer = 0
                         ): Boolean;
  function HotKeyModifiersToStr(HotKeyMod: THotKeyModifiers): WideString;
  function PotentialMappedDrive(NS: TNamespace): Boolean;
  function FileObjectsToFlags(FileObjects: TFileObjects): DWORD;
  function FileObjectsToString(FileObjects: TFileObjects): WideString;

  {$ifdef COMPILER_4}
  procedure FreeAndNil(var Obj);
  function Supports(const Instance: IUnknown; const Intf: TGUID; out Inst): Boolean;
  {$endif}

  function GetDiskFreeSpaceMP(Drive: PWideChar; var SectorsperCluster, BytesperSector, FreeClusters, TotalClusters: DWORD): boolean;
  function DriveSize(Drive: PWideChar): Int64; overload;
  function DriveSize(Drive: PWideChar; ByteSize: TBtyeSize): WideString; overload;
  function DriveFreeSpace(Drive: PWideChar): Int64; overload;
  function DriveFreeSpace(Drive: PWideChar; ByteSize: TBtyeSize): WideString; overload;

  function IsSpecialVariable(TestPath: WideString; var NS: TNamespace): Boolean;
  function SpecialVariableReplacePath(var Path: WideString): Boolean;

//  function MPBrowseForFolder(Title, InitialPath: WideString; BrowseFlags: TMPBrowseFlags): WideString; overload;
  function MPBrowseForFolder(Title, RootFolder, InitialPath: WideString; BrowseFlags: TMPBrowseFlags; var SelectedPath: WideString): Boolean; overload;
  function MPBrowseForFolder(Title: WideString; RootFolder, InitialPath: PItemIDList; BrowseFlags: TMPBrowseFlags; var SelectedPath: PItemIDList): Boolean; overload;

  // Merges a TVirtualShellPopupMenu object into a Shell Context Menu
  {$IFDEF TNTSUPPORT}
  function MergeMenuIntoContextMenu(Menu: TTntPopupMenu; ContextMenu: HMenu; Index: Integer; idStart: UINT): Integer;
  {$ELSE}
  function MergeMenuIntoContextMenu(Menu: TPopupMenu; ContextMenu: HMenu; Index: Integer; idStart: UINT): Integer;
  {$ENDIF}

var
 { A few global common Namespaces to be used for various purposes.               }
  PIDLMgr: TCommonPIDLManager;
  DesktopFolder,
  RecycleBinFolder,
  PhysicalDesktopFolder,
  DrivesFolder,
  HistoryFolder,
  PrinterFolder,
  ControlPanelFolder,
  NetworkNeighborHoodFolder,
  TemplatesFolder,
  MyDocumentsFolder,
  FavoritesFolder,
  ProgramFilesFolder,
  UserDocumentsFolder: TNamespace;
  SHLimitInputEdit: TSHLimitInputEdit;

  AnimateWindow: function(Wnd: HWND; dwTime: Cardinal; dwFlags: Cardinal): WordBool; stdcall;

  // Fundamental Change in Version 2.0.  Should be more correct in what shell dialogs
  // are Modal and if they stay on top of the correct window.
  MP_UseModalDialogs: Boolean = True; // Causes calls to GetUIIObject to use a parent window
  MP_UseSpecialReparsePointOverlay: Boolean = False; // Shows an overlay on Reparse points
  MP_ThreadedShellExecute: Boolean = False; // Causes shell Execute to be launched in a separate thread

implementation

uses
  Dialogs;

type
  TShellILIsParent = function(PIDL1: PItemIDList; PIDL2: PItemIDList;
    ImmediateParent: LongBool): LongBool; stdcall;
  TShellILIsEqual = function(PIDL1: PItemIDList; PIDL2: PItemIDList): LongBool; stdcall;

var
  ShellILIsParent: TShellILIsParent;
  ShellILIsEqual: TShellILIsEqual;

////////////////////////////////////////////////////////////////////////////////
// Global Functions
////////////////////////////////////////////////////////////////////////////////

function SpecialVariableReplacePath(var Path: WideString): Boolean;

  function ReplacePath(Path, Variable, VarPath: WideString): WideString;
  begin
    Result := WideStringReplace(Path, Variable, VarPath, [rfReplaceAll, rfIgnoreCase]); 
  end;

var
  OldPath: WideString;
begin
  OldPath := Path;

  // Psudo Variables
  Path := ReplacePath(Path, '%sysdir%', WideLowerCase(WideStripTrailingBackslash(SystemDirectory)));
  Path := ReplacePath(Path, '%temp%', WideLowerCase(WideStripTrailingBackslash(WideGetTempDir)));
  Path := ReplacePath(Path, '%appdata%', WideLowerCase(WideStripTrailingBackslash(UserDocumentsFolder.NameForParsing)));
  Path := ReplacePath(Path, '%favorites%', WideLowerCase(WideStripTrailingBackslash(FavoritesFolder.NameForParsing)));
  Path := ReplacePath(Path, '%personal%', WideLowerCase(WideStripTrailingBackslash(MyDocumentsFolder.NameForParsing)));
  Path := ReplacePath(Path, '%templates%', WideLowerCase(WideStripTrailingBackslash(TemplatesFolder.NameForParsing)));
  Path := ReplacePath(Path, '%history%', WideLowerCase(WideStripTrailingBackslash(HistoryFolder.NameForParsing)));
  Path := ReplacePath(Path, '%desktopfolder%', WideLowerCase(WideStripTrailingBackslash(PhysicalDesktopFolder.NameForParsing)));

  // Environment variables
  Path := ReplacePath(Path, '%userprofile%', WideStripTrailingBackslash(WideExpandEnviromentString('%USERPROFILE%')));
  Path := ReplacePath(Path, '%allusersprofile%', WideStripTrailingBackslash(WideExpandEnviromentString('%ALLUSERSPROFILE%')));
  Path := ReplacePath(Path, '%programfiles%', WideStripTrailingBackslash(WideExpandEnviromentString('%ProgramFiles%')));
  Path := ReplacePath(Path, '%systemroot%', WideStripTrailingBackslash(WideExpandEnviromentString('%SystemRoot%')));
  Path := ReplacePath(Path, '%systemdrive%', WideStripTrailingBackslash(WideExpandEnviromentString('%SystemDrive%')));
  Path := ReplacePath(Path, '%windir%', WideStripTrailingBackslash(WideExpandEnviromentString('%windir%')));
  Path := ReplacePath(Path, '%tmp%', WideStripTrailingBackslash(WideExpandEnviromentString('%TMP%')));
  Path := ReplacePath(Path, '%temp%', WideStripTrailingBackslash(WideExpandEnviromentString('%TEMP%')));
  Path := ReplacePath(Path, '%public%', WideStripTrailingBackslash(WideExpandEnviromentString('%PUBLIC%')));
  Path := ReplacePath(Path, '%programdata%', WideStripTrailingBackslash(WideExpandEnviromentString('%ProgramData%')));
  Path := ReplacePath(Path, '%homedrive%', WideStripTrailingBackslash(WideExpandEnviromentString('%HOMEDRIVE%')));
  Path := ReplacePath(Path, '%homepath%', WideStripTrailingBackslash(WideExpandEnviromentString('%HOMEPATH%')));
  Path := ReplacePath(Path, '%commonprogramfiles%', WideStripTrailingBackslash(WideExpandEnviromentString('%CommonProgramFiles%')));
  Path := ReplacePath(Path, '%appdata%', WideStripTrailingBackslash(WideExpandEnviromentString('%APPDATA%')));

  Result := OldPath <> Path
end;

function IsSpecialVariable(TestPath: WideString; var NS: TNamespace): Boolean;
var
  PIDL: PItemIDList;
begin
  NS := nil;

  PIDL := nil;

  if WideLowerCase(TestPath) = '%desktop%' then
    PIDL := PIDLMgr.CopyPIDL(DesktopFolder.AbsolutePIDL)
  else
  if WideLowerCase(TestPath) = '%network%' then
    PIDL := PIDLMgr.CopyPIDL(NetworkNeighborHoodFolder.AbsolutePIDL)
  else
  if WideLowerCase(TestPath) = '%printer%' then
    PIDL := PIDLMgr.CopyPIDL(PrinterFolder.AbsolutePIDL)
  else
  if (WideLowerCase(TestPath) = '%drives%') or (WideLowerCase(TestPath) = '%mycomputer%') then
    PIDL := PIDLMgr.CopyPIDL(DrivesFolder.AbsolutePIDL)
  else begin
    if SpecialVariableReplacePath(TestPath) then
      if WideDirectoryExists(TestPath) then
        PIDL := PathToPIDL(TestPath)
  end;

  if Assigned(PIDL) then
    NS := TNamespace.Create(PIDL, nil)
  else

  if not Assigned(NS) and not WideDirectoryExists(TestPath) then
  begin
    // See if it a specially formated CLSID
    PIDL := PathToPIDL(TestPath);
    if Assigned(PIDL) then
      NS := TNamespace.Create(PIDL, nil);
  end;
  Result := Assigned(NS)
end;

function GUIDToInterfaceStr(riid: TGUID): String;
begin
  if IsEqualGUID(riid, IOleCommandTarget) then
     Result := 'IOleCommandTarget'
  else
  if IsEqualGUID(riid, IID_IPersistFreeThreadedObject) then
     Result := 'IPersistFreeThreadedObject'
  else
  if IsEqualGUID(riid, IShellView) then
     Result := 'IShellView'
  else
  if IsEqualGUID(riid, IID_ICategoryProvider) then
     Result := 'ICategoryProvider'
  else
  if IsEqualGUID(riid, IID_IQueryAssociations) then
     Result := 'IQueryAssociations'
  else
  if IsEqualGUID(riid, IID_IAssociationArray) then
     Result := 'IAssociationArray'
  else
  if IsEqualGUID(riid, IPersistIDList) then
     Result := 'IPersistIDList'
  else
  if IsEqualGUID(riid, IID_IInternetSecurityManager) then
      Result := 'IInternetSecurityManager'
   else
   if IsEqualGUID(riid, IObjectWithSite) then
      Result := 'IObjectWithSite'
   else
   if IsEqualGUID(riid, IPersist) then
      Result := 'IPersist'
   else
   if IsEqualGUID(riid, IPersistFolder) then
      Result := 'IPersistFolder'
   else
   if IsEqualGUID(riid, IPersistFolder2) then
      Result := 'IPersistFolder2'
   else
   if IsEqualGUID(riid, IShellFolder) then
      Result := 'IShellFolder'
   else
   if IsEqualGUID(riid, IShellFolder2) then
     Result := 'IShellFolder2'
   else
   if IsEqualGUID(riid, IShellFolderViewCB) then
     Result := 'IShellFolderViewCB'
   else
   if IsEqualGUID(riid, IContextMenu) then
     Result := 'IContextMenu'
   else
   if IsEqualGUID(riid, IContextMenu2) then
     Result := 'IContextMenu2'
   else
   if IsEqualGUID(riid, IContextMenu3) then
     Result := 'IContextMenu3'
 (*  else
   {$IFDEF CPPB_6_UP}
     if IsEqualGUID(riid, IBCB6ShellDetails) then
   {$ELSE}
     if IsEqualGUID(riid, IShellDetails) then
   {$ENDIF}
     Result := 'IShellDetails'   *)
   else
   if IsEqualGUID(riid, IStream) then
     Result := 'IStream'
   else
   if IsEqualGUID(riid, IDataObject) then
     Result := 'IDataObject'
   else
   if IsEqualGUID(riid, IDropSource) then
     Result := 'IDropSource'
   else
   if IsEqualGUID(riid, IDropTarget) then
     Result := 'IDropTarget'
   else
   if IsEqualGUID(riid, IExtractIconA) then
     Result := 'IExtractIconA'
   else
   if IsEqualGUID(riid, IExtractIconW) then
     Result := 'IExtractIconW'
   else
   if IsEqualGUID(riid, IExtractImage) then
     Result := 'IExtractImage'
   else
   if IsEqualGUID(riid, IQueryInfo) then
     Result := 'IQueryInfo'
   else
   if IsEqualGUID(riid, IShellIcon) then
     Result := 'IShellIcon'
   else
   if IsEqualGUID(riid, IBrowserFrameOptions) then
     Result := 'IBrowserFrameOptions'
   else
   if IsEqualGUID(riid, IBindHost) then
     Result := 'IBindHost'
   else
   if IsEqualGUID(riid, IID_IBindProtocol) then
     Result := 'IBindProtocol'
   else
   if IsEqualGUID(riid, IClassFactory) then
     Result := 'IClassFactory'
   else
   if IsEqualGUID(riid, IShellExtInit) then
     Result := 'IShellExtInit'
   else
   if IsEqualGUID(riid, IShellLinkW) then
     Result := 'IShellLinkW'
   else
   if IsEqualGUID(riid, IShellLinkA) then
     Result := 'IShellLinkW'  
   else
     Result := 'Unknown GUID: ' + GUIDToString(riid)
end;

function CFM_FlagsToShellContextMenuFlags(Flags: DWORD): TShellContextMenuFlags;
begin
  Result := [];
  if CMF_CANRENAME and Flags <> 0 then
    Include(Result, cmfCanRename);
  if CMF_CANRENAME and Flags <> 0 then
    Include(Result, cmfCanRename);
//  if CMF_DISABLEVERBS and Flags <> 0 then
 //   Include(Result, cmfDisableVerbs);
  if CMF_EXPLORE and Flags <> 0 then
    Include(Result, cmfExplore);
  if CMF_INCLUDESTATIC and Flags <> 0 then
    Include(Result, cmfIncludeStatic);
  if CMF_NODEFAULT and Flags <> 0 then
    Include(Result, cmfNoDefault);
  if CMF_NORMAL and Flags <> 0 then
    Include(Result, cmfNormal);
  if CMF_NOVERBS and Flags <> 0 then
    Include(Result, cmfNoVerbs);
  if CMF_VERBSONLY and Flags <> 0 then
    Include(Result, cmfVerbsOnly);
end;

function ClipboardContainsShellFormats: Boolean;
begin
  Result := IsClipboardFormatAvailable(CF_SHELLIDLIST) or IsClipboardFormatAvailable(CF_HDROP)
end;

function MPBrowseForFolderCallback(Wnd: hWnd; uMsg: UINT; lParam: LPARAM; lpData: LPARAM): Integer; stdcall;
begin
  case uMsg of
    BFFM_INITIALIZED:
      begin
        // This is the PIDL of the starting folder
        if lpData <> 0 then
          SendMessage(Wnd, BFFM_SETSELECTION, 0, lpData);
      end;
    BFFM_SELCHANGED:
      begin
      end;
    BFFM_VALIDATEFAILEDA:
      begin
      end;
    BFFM_VALIDATEFAILEDW:
      begin
      end;
  end;
  Result := 0;
end;

function MPBrowseForFolder(Title: WideString; RootFolder, InitialPath: PItemIDList; BrowseFlags: TMPBrowseFlags; var SelectedPath: PItemIDList): Boolean;

  function FlagsToTMPBrowseFlags(Flags: TMPBrowseFlags): DWORD;
  begin
    Result := 0;
    if mpbfComputers in Flags then
      Result := Result or BIF_BROWSEFORCOMPUTER;
    if mpbfPrinters in Flags then
      Result := Result or BIF_BROWSEFORPRINTER;
    if mpbfDontGoBelowDomain in Flags then
      Result := Result or BIF_DONTGOBELOWDOMAIN;
    if mpbfFileSysAncestors in Flags then
      Result := Result or BIF_RETURNFSANCESTORS;
    if mpbfFileSysFolder in Flags then
      Result := Result or BIF_RETURNONLYFSDIRS;
  //  if mpbfIncludeStatusText in Flags then
 //     Result := Result or BIF_STATUSTEXT;
    if mpbfIncludeFiles in Flags then
      Result := Result or BIF_BROWSEINCLUDEFILES;
    if mpbfNewStyleDialog in Flags then
      Result := Result or BIF_NEWDIALOGSTYLE;
    if mpbfEditBox in Flags then
      Result := Result or BIF_EDITBOX;
    if mpbfIncludeURLs in Flags then
      Result := Result or BIF_BROWSEINCLUDEURLS;
    if mpbfSharable in Flags then
      Result := Result or BIF_SHAREABLE;
    if mpbfMustExist in Flags then
      Result := Result or BIF_VALIDATE;
  end;

var
  BrowseInfoW: TBrowseInfoW;
  BrowseInfoA: TBrowseInfoA;
  DisplayNameA: array [0..MAX_PATH] of Char;
  DisplayNameW: array [0..MAX_PATH] of WideChar;
begin
  if IsUnicode then
  begin
    FillChar(BrowseInfoW, SizeOf(BrowseInfoW), #0);
    BrowseInfoW.hwndOwner := GetActiveWindow;
    BrowseInfoW.pidlRoot := RootFolder;
    BrowseInfoW.lParam := Integer( InitialPath);
    BrowseInfoW.pszDisplayName := DisplayNameW;
    {$IFDEF CPPB}
    BrowseInfoW.lpfn := MPBrowseForFolderCallback;
    {$ELSE}
    BrowseInfoW.lpfn := @MPBrowseForFolderCallback;
    {$ENDIF}
    BrowseInfoW.lpszTitle := PWideChar(Title);
    BrowseInfoW.ulFlags := FlagsToTMPBrowseFlags(BrowseFlags);
    SelectedPath := SHBrowseForFolderW_MP(BrowseInfoW);
  end else
  begin
    FillChar(BrowseInfoA, SizeOf(BrowseInfoA), #0);
    BrowseInfoA.hwndOwner := GetActiveWindow;
    BrowseInfoW.pidlRoot := RootFolder;
    BrowseInfoA.lParam := Integer( InitialPath);
    BrowseInfoA.pszDisplayName := DisplayNameA;
    {$IFDEF CPPB}
    BrowseInfoA.lpfn := MPBrowseForFolderCallback;
    {$ELSE}
    BrowseInfoA.lpfn := @MPBrowseForFolderCallback;
    {$ENDIF}
    BrowseInfoA.lpszTitle := PChar(string(Title));
    BrowseInfoA.ulFlags := FlagsToTMPBrowseFlags(BrowseFlags);
    SelectedPath := SHBrowseForFolderA(BrowseInfoA);
  end;
  Result := Assigned(SelectedPath)
end;

function MPBrowseForFolder(Title, RootFolder, InitialPath: WideString; BrowseFlags: TMPBrowseFlags; var SelectedPath: WideString): Boolean; overload;
var
  NS: TNamespace;
  RootPIDL, InitialPathPIDL, ReturnPIDL: PItemIDList;
begin
  RootPIDL := PathToPIDL(RootFolder);
  InitialPathPIDL := PathToPIDL(InitialPath);
  ReturnPIDL := nil;
  Result := MPBrowseForFolder(Title, RootPIDL, InitialPathPIDL, BrowseFlags, ReturnPIDL);
  if Result then
  begin
    NS := TNamespace.Create(ReturnPIDL, nil);
    SelectedPath := NS.NameParseAddress;
    NS.Free;
  end;
  PIDLMgr.FreePIDL(RootPIDL);
  PIDLMgr.FreePIDL(InitialPathPIDL);
end;

{$IFDEF TNTSUPPORT}
function MergeMenuIntoContextMenu(Menu: TTntPopupMenu; ContextMenu: HMenu; Index: Integer; idStart: UINT): Integer;
var
  i: Integer;
begin
  Result := -1;
  if Assigned(Menu) and (ContextMenu <> 0) then
  begin
    Result := idStart;
    for i := Menu.Items.Count - 1 downto 0 do
    begin
      AddContextMenuItem(ContextMenu, Menu.Items[i].Caption, Index, Result);
      Inc(Result);
      if Menu.Items[i].Count > 0 then
        beep;
    end
  end
end;
{$ELSE}
function MergeMenuIntoContextMenu(Menu: TPopupMenu; ContextMenu: HMenu; Index: Integer; idStart: UINT): Integer;
var
  i: Integer;
begin
  Result := -1;
  if Assigned(Menu) and (ContextMenu <> 0) then
  begin
    Result := idStart;
    for i := Menu.Items.Count - 1 downto 0 do
    begin
      AddContextMenuItem(ContextMenu, Menu.Items[i].Caption, Index, Result);
      Inc(Result);
      if Menu.Items[i].Count > 0 then
        beep;
    end
  end
end;
{$ENDIF}



// PIDL Functions

{ ----------------------------------------------------------------------------- }
function NamespaceToRelativePIDLArray(Namespaces: TNamespaceArray): TRelativePIDLArray;
var
  i: integer;
begin
  Result := nil;
  if Assigned(Namespaces) then
  begin
    SetLength(Result, Length(Namespaces));
    for i := 0 to Length(Namespaces) - 1 do
      Result[i] := Namespaces[i].RelativePIDL;
  end
end;

function FindBrowseableRootPIDL(NS: TNamespace): PItemIDList;
var
  Found: Boolean;
begin
  Result := nil;
  Found := False;
  if Assigned(NS) then
  begin
    if not NS.Folder then
      NS := NS.Parent;
    if IsParentBrowseable(NS) then
    begin
      while not Found and not NS.IsDesktop do
      begin
        if NS.Browsable then
          Found := True
        else
          NS := NS.Parent
      end
    end;
    Result := PIDLMgr.CopyPIDL(NS.AbsolutePIDL)
  end
end;

function IsParentBrowseable(NS: TNamespace): Boolean;
begin
  Result := False;
  if Assigned(NS) then
  begin
    while not Result and not NS.IsDesktop do
    begin
      Result := NS.Browsable;
      if not Result then
        NS := NS.Parent
    end
  end
end;

function NamespaceToAbsolutePIDLArray(Namespaces: TNamespaceArray): TAbsolutePIDLArray;
var
  i: integer;
begin
  Result := nil;
  if Assigned(Namespaces) then
  begin
    SetLength(Result, Length(Namespaces));
    for i := 0 to Length(Namespaces) - 1 do
      Result[i] := Namespaces[i].AbsolutePIDL;
  end
end;
{ ----------------------------------------------------------------------------- }

function PathToPIDL(APath: WideString): PItemIDList;
// Takes the passed Path and attempts to convert it to the equavalent PIDL
var
  Desktop: IShellFolder;
  pchEaten, dwAttributes: ULONG;
begin
  Result := nil;
  begin
    SHGetDesktopFolder(Desktop);
    dwAttributes := 0;
    if Assigned(Desktop) then
      Desktop.ParseDisplayName(0, nil, PWideChar(APath), pchEaten, Result, dwAttributes)
  end
end;
{ ----------------------------------------------------------------------------- }

{ ----------------------------------------------------------------------------- }
function PIDLToPath(APIDL: PItemIDList): WideString;
var
  Folder: TNamespace;
begin
  Result := '';
  Folder := TNamespace.Create(APIDL, nil);
  try
    Folder.FreePIDLOnDestroy := False;
    if Assigned(Folder) then
      Result := Folder.NameForParsing;
  finally
    Folder.Free
  end
end;
{ ----------------------------------------------------------------------------- }

function IENamespaceShown(PerUser: Boolean): Boolean;
var
  Reg: TRegistry;
begin
  Result := True;
  Reg := TRegistry.Create;
  try
    if PerUser then
      Reg.RootKey := HKEY_CURRENT_USER
    else
      Reg.RootKey := HKEY_LOCAL_MACHINE;
    if Reg.KeyExists('Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel') then
    begin
      if Reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel', False) then
      begin
        if Reg.ValueExists('{871C5380-42A0-1069-A2EA-08002B30309D}') then
        begin
          Result := Reg.ReadInteger('{871C5380-42A0-1069-A2EA-08002B30309D}') = 0;
        end
      end
    end;
  finally
    Reg.Free
  end;
end;

{ ----------------------------------------------------------------------------- }
function DirExistsVET(APath: WideString; ShowSystemMessages: Boolean): Boolean; overload;
const
  FLAGS = SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN;
var
  Desktop, Folder: IShellFolder;
  TempPIDL, PIDL: PItemIDList;
  EnumIDList: IEnumIDList;
  hWndOwner: THandle;
  TempPath: WideString;
begin
  Result := False;
  PIDL := nil;
  if ShowSystemMessages then
    hWndOwner := GetActiveWindow //Application.Handle
  else
    hWndOwner := 0;

  TempPath := WideExtractFileDrive(APath) + '\';
  // First make sure the drive is available, it may be a remoted password protected drive
  TempPIDL := PathToPIDL(TempPath);
  if Succeeded(SHGetDesktopFolder(Desktop)) then
    if Succeeded(Desktop.BindToObject(TempPIDL, nil, IShellFolder, Pointer(Folder))) then
      if Folder.EnumObjects(hWndOwner, FLAGS, EnumIDList)= NOERROR then
      begin
        PIDL := PathToPIDL(APath);
        if Succeeded(Desktop.BindToObject(PIDL, nil, IShellFolder, Pointer(Folder))) then
          Result := Folder.EnumObjects(hWndOwner, FLAGS, EnumIDList)= NOERROR
      end;
  coTaskMemFree(TempPIDL);
  if Assigned(PIDL) then
    coTaskMemFree(PIDL);
end;
{ ----------------------------------------------------------------------------- }

{ ----------------------------------------------------------------------------- }
function DirExistsVET(NS: TNamespace; ShowSystemMessages: Boolean): Boolean; overload;
begin
  Result := DirExistsVET(NS.NameForParsing, ShowSystemMessages)
end;

{ ----------------------------------------------------------------------------- }
function RectWidth(ARect: TRect): integer;
begin
  Result := ARect.Right - ARect.Left
end;
{ ----------------------------------------------------------------------------- }

{ ----------------------------------------------------------------------------- }
function RectHeight(ARect: TRect): integer;
begin
  Result := ARect.Bottom - ARect.Top
end;
{ ----------------------------------------------------------------------------- }

function PotentialMappedDrive(NS: TNamespace): Boolean;
// A mapped drive will not return valid information, other then
// its display name under some conditions so always try it.
var
  DriveType: DWORD;
begin
  Result := False;
  if WideIsDrive(NS.NameForParsing) then
  begin
    if Assigned(GetDriveTypeW_MP) then
      DriveType := GetDriveTypeW_MP(PWideChar(NS.NameForParsing))
    else
      DriveType := GetDriveType(PChar(string(NS.NameForParsing)));
    Result := (DriveType = DRIVE_NO_ROOT_DIR) or (DriveType = DRIVE_REMOTE)
  end
end;
{ ----------------------------------------------------------------------------- }

function FileObjectsToFlags(FileObjects: TFileObjects): DWORD;
begin
  Result := 0;
  if foFolders in FileObjects then
    Result := Result or SHCONTF_FOLDERS;
  if foNonFolders in FileObjects then
    Result := Result or SHCONTF_NONFOLDERS;
  if foHidden in FileObjects then
    Result := Result or SHCONTF_INCLUDEHIDDEN;
  if IsUnicode and not IsWinNT4 then
  begin
    if foShareable in FileObjects then
      Result := Result or SHCONTF_SHAREABLE;
    if foNetworkPrinters in FileObjects then
      Result := Result or SHCONTF_NETPRINTERSRCH;
  end;
end;

function FileObjectsToString(FileObjects: TFileObjects): WideString;
begin
  Result := '';
  if foFolders in FileObjects then
    Result := Result + 'Folders, ';
  if foNonFolders in FileObjects then
    Result := Result + 'NonFolders, ';
  if foHidden in FileObjects then
    Result := Result + 'Hidden, ';
  if foShareable in FileObjects then
    Result := Result + 'Shareable, ';
  if foNetworkPrinters in FileObjects then
    Result := Result + 'NetworkPrinters, ';
  if Length(Result) > 0 then
    SetLength(Result, Length(Result) - 2)
end;

// Time Conversions

{ ----------------------------------------------------------------------------- }
// ANSI
function ConvertLocalStrToTFileTime(LocalStr: WideString;
  var FileTime: TFileTime): Boolean;
var
  SystemTime: TSystemTime;
begin
  Result := True;
  try
     DateTimeToSystemTime(StrToDateTime(LocalStr), SystemTime)
  except
    on EConvertError do Result := False;
  end;
  if Result then
    Result := SystemTimeToFileTime(SystemTime, FileTime);
end;
{ ----------------------------------------------------------------------------- }

function ValidFileTime(FileTime: TFileTime): Boolean;
begin
 Result := (FileTime.dwLowDateTime <> 0) or (FileTime.dwHighDateTime <> 0);
end;

{ ----------------------------------------------------------------------------- }
// Converts a TFileTime structure into a local Time/Date String.  This requires
// a check to make sure the TFileTime structure contains some info through
// the local function ValidFileTime then trying to convert the UTC time to Local
// UTC time.  Then finally changing the UTC time to System time.
// ANSI/
function ConvertTFileTimeToLocalStr(AFileTime: TFILETIME): WideString;
var
  SysTime: TSystemTime;
  LocalFileTime: TFILETIME;
begin
  if ValidFileTime(AFileTime)
  and FileTimeToLocalFileTime(AFileTime, LocalFileTime)
  and FileTimeToSystemTime(LocalFileTime, SysTime) then
  try
    Result := DateTimeToStr(SystemTimeToDateTime(SysTime))
  except
    Result := '';
  end
  else
     Result := '';
end;
{ ----------------------------------------------------------------------------- }

         
function ConvertFileTimetoDateTime(AFileTime : TFileTime): TDateTime;
var
  SysTime: TSystemTime;
  LocalFileTime: TFILETIME;
begin
  if ValidFileTime(AFileTime)
  and FileTimeToLocalFileTime(AFileTime, LocalFileTime)
  and FileTimeToSystemTime(LocalFileTime, SysTime) then
  try
    Result := SystemTimeToDateTime(SysTime);
  except
    Result := 0;
  end
  else
     Result := 0;
end;

  // Various Functions

{ ----------------------------------------------------------------------------- }
function CreateSpecialNamespace(FolderID: integer): TNamespace;
{ Creates a TNamespace based on the SpecialFolders defined by                   }
{ SHGetSpecialFolderLocation.                                                   }
var
  PIDL: PItemIDList;
  F: IShellFolder;
begin
  SHGetspecialFolderLocation(0, FolderID, PIDL);
  if Assigned(PIDL) then
  begin
    Result := TNamespace.Create(PIDL, nil);
    F := Result.ParentShellFolder // just force the namespace to have Parent
  end else
    Result := nil
end;
{ ----------------------------------------------------------------------------- }

{ ----------------------------------------------------------------------------- }
function DefaultSystemImageIndex(FolderType: TDefaultFolderIcon): integer;

{ Extracts the default Icon for the given folder type passed to it.             }

var
  FileInfoA: TSHFileInfo;
  FileInfoW: TSHFileInfoW;
  FileExampleW: WideString;
  FileExampleA: string;
  Attrib, Flags: DWORD;
  PIDL: PItemIDList;
  NS: TNamespace;
begin
  Result := -1;
  Attrib := 0;
  Flags := 0;
  case FolderType of
    diNormalFolder:
      begin
        FileExampleW := '*.*';
        Attrib := FILE_ATTRIBUTE_DIRECTORY;
        Flags := SHGFI_USEFILEATTRIBUTES or SHGFI_SHELLICONSIZE or SHGFI_SYSICONINDEX
      end;
    diOpenFolder:
      begin
        FileExampleW := '*.*';
        Attrib := FILE_ATTRIBUTE_DIRECTORY;
        Flags := SHGFI_USEFILEATTRIBUTES or SHGFI_SHELLICONSIZE or SHGFI_SYSICONINDEX or SHGFI_OPENICON
      end;
    diUnknownFile:
      begin
        FileExampleW := '*.zyxwv';
        Attrib := FILE_ATTRIBUTE_NORMAL;
        Flags := SHGFI_USEFILEATTRIBUTES or SHGFI_SHELLICONSIZE or SHGFI_SYSICONINDEX
      end;
    diLink:
      begin
        FileExampleW := '';
        Result := SHORTCUT_ICON_INDEX;
      end;
    diMyDocuments:
      begin
        if Assigned(DesktopFolder) then
        begin
          PIDL := DesktopFolder.ParseDisplayName('::{450d8fba-ad25-11d0-98a8-0800361b1103}');
          if Assigned(PIDL) then
          begin
            NS := TNamespace.Create(PIDL, nil);
            Result := NS.GetIconIndex(False, icSmall, True);
            NS.Free
          end
        end else
          Result := DefaultSystemImageIndex(diNormalFolder)
      end
  else
    FileExampleW := ''
  end;
  if FileExampleW <> '' then
  begin
    if IsUnicode then
    begin
      FillChar(FileInfoW, SizeOf(FileInfoW), #0);
      SHGetFileInfoW_MP(PWideChar(FileExampleW), Attrib, FileInfoW, SizeOf(TSHFileInfoW), Flags);
      Result := FileInfoW.iIcon;
    end else
    begin
      FileExampleA := FileExampleW;
      FillChar(FileInfoA, SizeOf(FileInfoA), #0);
      SHGetFileInfoA(PChar(FileExampleA), Attrib, FileInfoA, SizeOf(TSHFileInfoA), Flags);
      Result := FileInfoA.iIcon;
    end
  end
end;
{ ----------------------------------------------------------------------------- }

{ ----------------------------------------------------------------------------- }
// Forces the correct icons for the Common Program Groups on Windows NT 4.0.
// Borrowed from John T and GXExplorer <g>
function FileIconInit(FullInit: BOOL): BOOL; stdcall;
type
  TFileIconInit = function(FullInit: BOOL): BOOL; stdcall;
var
  ShellDLL: HMODULE;
  PFileIconInit: TFileIconInit;
begin
  Result := False;
  if (Win32Platform = VER_PLATFORM_WIN32_NT) then
  begin
    ShellDLL := GetModuleHandle(PChar(Shell32));
 //   ShellDLL := LoadLibrary(PChar(Shell32));
    PFileIconInit := GetProcAddress(ShellDLL, PChar(660));
    if (Assigned(PFileIconInit)) then
      Result := PFileIconInit(FullInit);
  end;
end;
{ ----------------------------------------------------------------------------- }

// IShellLink (ShortCut) helpers
{ ----------------------------------------------------------------------------- }
function CreateShellLink(ALnkFilePath, ATargetFilePath: WideString; AnArguments: WideString = '';
 AWorkingDir: WideString = ''; ADescription: WideString = ''; AShowCmd: TCmdShow = swShowNormal;
 AHotKey: Word = 0; AHotKeyModifier: THotKeyModifiers = []; AnIconLocation: WideString = '';
 AnIconIndex: integer = 0): Boolean;
var
  ShellLink: TVirtualShellLink;
begin
  Result := True;
  ShellLink := TVirtualShellLink.Create(nil);
  if Assigned(ShellLink) then
  try
    try
      ShellLink.FileName := ALnkFilePath;
      ShellLink.TargetPath := ATargetFilePath;
      if AnArguments <> '' then
        ShellLink.Arguments := AnArguments;
      if AWorkingDir <> '' then
        ShellLink.WorkingDirectory := AWorkingDir;
      if ADescription <> '' then
        ShellLink.Description := ADescription;
      if AShowCmd <> swShowNormal then
        ShellLink.ShowCmd := AShowCmd;
      if (AHotKey <> 0) then
        ShellLink.HotKey := AHotKey;
      if AHotKeyModifier <> [] then
        ShellLink.HotKeyModifiers := AHotKeyModifier;
      if AnIconLocation <> '' then
        ShellLink.IconLocation := AnIconLocation;
      if AnIconIndex <> 0 then
        ShellLink.IconIndex := AnIconIndex;
      ShellLink.WriteLink(ShellLink.FileName);
    except
      Result := False;
      raise;
    end
  finally
    ShellLink.Free
  end
end;
{ ----------------------------------------------------------------------------- }

{ ----------------------------------------------------------------------------- }
function HotKeyModifiersToStr(HotKeyMod: THotKeyModifiers): WideString;
begin
  Result := '';
  if hkmAlt in HotKeyMod then
    Result := Result + ' Alt';
  if hkmControl in HotKeyMod then
    Result := Result + ' Control';
  if hkmExtendedKey in HotKeyMod then
    Result := Result + ' ExtendedKey';
  if hkmShift in HotKeyMod then
    Result := Result + ' Shift';
end;
{ ----------------------------------------------------------------------------- }

{ Some Stuff D4 lacks.
                                                }
{$ifdef COMPILER_4}
{ ----------------------------------------------------------------------------- }
procedure FreeAndNil(var Obj);
var
  P: TObject;
begin
  P := TObject(Obj);
  TObject(Obj) := nil;
  P.Free;
end;
{ ----------------------------------------------------------------------------- }

{ ----------------------------------------------------------------------------- }
function Supports(const Instance: IUnknown; const Intf: TGUID; out Inst): Boolean;
begin
  Result := (Instance <> nil) and (Instance.QueryInterface(Intf, Inst) = 0);
end;
{ ----------------------------------------------------------------------------- }
{$endif}


////////////////////////////////////////////////////////////////////////////////
// Local Functions
////////////////////////////////////////////////////////////////////////////////

procedure PIDLQuickSort(PIDLList: TPIDLArray; const ParentFolder: IShellFolder; L, R: Integer);
var
  I, J: Integer;
  P, T: PItemIDList;
begin
  if L < R then
  repeat
    I := L;
    J := R;
    P := PIDLList[(L + R) shr 1];
    repeat
      while ShortInt(ParentFolder.CompareIDs(0, PIDLList[I], P)) < 0 do
        Inc(I);
      while ShortInt(ParentFolder.CompareIDs(0, PIDLList[J], P)) > 0 do
        Dec(J);
      if I <= J then
      begin
        T := PIDLList[I];
        PIDLList[I] := PIDLList[J];
        PIDLList[J] := T;
        Inc(I);
        Dec(J);
      end;
    until I > J;
    if L < J then
      PIDLQuickSort(PIDLList, ParentFolder, L, J);
    L := I;
  until I >= R;
end;


procedure PIDLListQuickSort(PIDLList: TCommonPIDLList; const ParentFolder: IShellFolder; L, R: Integer);
var
  I, J: Integer;
  P, T: PItemIDList;
begin
  if L < R then
  repeat
    I := L;
    J := R;
    P := PIDLList[(L + R) shr 1];
    repeat
      while ShortInt(ParentFolder.CompareIDs(0, PIDLList[I], P)) < 0 do
        Inc(I);
      while ShortInt(ParentFolder.CompareIDs(0, PIDLList[J], P)) > 0 do
        Dec(J);
      if I <= J then
      begin
        T := PIDLList[I];
        PIDLList[I] := PIDLList[J];
        PIDLList[J] := T;
        Inc(I);
        Dec(J);
      end;
    until I > J;
    if L < J then
      PIDLListQuickSort(PIDLList, ParentFolder, L, J);
    L := I;
  until I >= R;
end;


{ ----------------------------------------------------------------------------- }
function RequestedDragEffect(grfKeyState: integer): HResult;
{ Looks at the KeyState during a IDragTarget notification. The return value}
{ is the expected behavior common in Windows. Note this does not mean that }
{ the DragSource is actually capable of this action.                       }
begin
  // Strip off the mouse button information keep only Ctrl and Shift information
  grfKeyState := grfKeyState and (MK_CONTROL or MK_SHIFT);
  // Standard Windows Shell behavior
  if grfKeyState = 0 then Result := DROPEFFECT_MOVE  // Windows default
  else
  if grfKeyState = MK_CONTROL then Result := DROPEFFECT_COPY
  else
  if grfKeyState = (MK_CONTROL or MK_SHIFT) then Result := DROPEFFECT_LINK
  else
  Result := DROPEFFECT_NONE;
end;
{ ----------------------------------------------------------------------------- }


{ ----------------------------------------------------------------------------- }
{ Thank you Angus Johnson for this article in UNDO          }
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
//Structures used in GetDiskFreeSpaceFAT32
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
type
  //DeviceIoControl registers structure...
  TDevIoCtl_Registers = packed record
    Reg_EBX   : DWord;
    Reg_EDX   : DWord;
    Reg_ECX   : DWord;
    Reg_EAX   : DWord;
    Reg_EDI   : DWord;
    Reg_ESI   : DWord;
    Reg_Flags : DWord;
  end;

  //Structure passed in Get_ExtFreeSpace ...
  TExtGetDskFreSpcStruc = packed record
    ExtFree_Size                      : Word;
    ExtFree_Level                     : Word;
    ExtFree_SectorsPerCluster         : Integer;
    ExtFree_BytesPerSector            : Integer;
    ExtFree_AvailableClusters         : Integer;
    ExtFree_TotalClusters             : Integer;
    ExtFree_AvailablePhysSectors      : Integer;
    ExtFree_TotalPhysSectors          : Integer;
    ExtFree_AvailableAllocationUnits  : Integer;
    ExtFree_TotalAllocationUnits      : Integer;
    ExtFree_Rsvd                      : array [0..1] of Integer;
  end;

{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
//Angus Johnson's Delphi implimentation of - Int 21h Function 7303h Get_ExtFreeSpace (FAT32)
{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
function GetDiskFreeSpaceFAT32(Drive: PChar; var SectorsperCluster,
  BytesperSector, FreeClusters, TotalClusters: DWORD): boolean;
const
  VWIN32_DIOC_DOS_IOCTL = 6;
var
  DevIoHandle         : THandle;
  BytesReturned       : DWord;
  Reg                 : TDevIoCtl_Registers;
  ExtGetDskFreSpcStruc: TExtGetDskFreSpcStruc;
begin
  result := false;
  FillChar(ExtGetDskFreSpcStruc, sizeof(TExtGetDskFreSpcStruc),0);
  FillChar(Reg, sizeof(TDevIoCtl_Registers),0);
  with Reg do begin
    reg_EAX :=  $7303;
    reg_EDX := DWord(Drive); //DS:DX
    Reg_EDI := DWord(@ExtGetDskFreSpcStruc); //ES:DI
    reg_ECX := sizeof(TExtGetDskFreSpcStruc);
    reg_Flags := 1; //set carry flag to assume error.
  end;

  if IsUnicode then
    DevIoHandle := CreateFileW_MP( '\\.\vwin32', Generic_Read,
      File_Share_Read or File_Share_Write, nil, Open_Existing, File_Attribute_Normal, 0)
  else
    DevIoHandle := CreateFile( '\\.\vwin32', Generic_Read,
      File_Share_Read or File_Share_Write, nil, Open_Existing, File_Attribute_Normal, 0);

  if DevIoHandle <> Invalid_Handle_Value then begin
    result := DeviceIoControl(DevIoHandle, VWIN32_DIOC_DOS_IOCTL,
        @Reg, SizeOf(Reg), @Reg, SizeOf(Reg), BytesReturned, nil);
    CloseHandle(DevIoHandle);
    if not result then
    begin
      exit //error
    end
    else if (Reg.reg_Flags and 1 <> 0) then begin
      result := false; //If carry flag not cleared then => error
      exit;
      end
    else with ExtGetDskFreSpcStruc do begin
      BytesperSector := ExtFree_BytesPerSector;
      SectorsperCluster := ExtFree_SectorsPerCluster;
      TotalClusters := ExtFree_TotalClusters;
      FreeClusters :=  ExtFree_AvailableClusters;
      result := true;
    end;
  end;
end; {GetDiskFreeSpaceFAT32}
{ ----------------------------------------------------------------------------- }

function GetDiskFreeSpaceMP(Drive: PWideChar; var SectorsperCluster,
    BytesperSector, FreeClusters, TotalClusters: DWORD): boolean;
begin
  if Assigned(GetDiskFreeSpaceW_MP) then
    Result := GetDiskFreeSpaceW_MP(
      PWideChar( Drive), SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters)
  else
  if not IsWin95_SR1 then
    Result := GetDiskFreeSpaceFAT32(
      PChar( string(Drive)), SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters)
  else
    Result := GetDiskFreeSpaceA(
      PChar( string(Drive)), SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters);
end;

function DriveSize(Drive: PWideChar): Int64;
var
  SectorsperCluster, BytesperSector, FreeClusters, TotalClusters: DWORD;
begin
  Result := 0;
  if GetDiskFreeSpaceMP(Drive, SectorsperCluster, BytesperSector, FreeClusters, TotalClusters) then
      Result := Int64(BytesperSector) * Int64(SectorsperCluster) * Int64(TotalClusters)
end;

function DriveSize(Drive: PWideChar; ByteSize: TBtyeSize): WideString;
begin
  case ByteSize of
    bsKiloBytes: Result := Format('%0.0n '+ 'KB', [DriveSize(Drive) / 1024]);
    bsMegaBytes: Result := Format('%0.0n '+ 'MB', [DriveSize(Drive) / 1048576]);
    bsGigiBytes: Result := Format('%0.1n '+ 'GB', [DriveSize(Drive) / 1073741824]);
    bsTereBytes: Result := Format('%0.1n '+ 'TB', [DriveSize(Drive) / 1099511627776]);
  end
end;

function DriveFreeSpace(Drive: PWideChar): Int64;
var
  SectorsperCluster, BytesperSector, FreeClusters, TotalClusters: DWORD;
begin
  Result := 0;
  if GetDiskFreeSpaceMP(Drive, SectorsperCluster, BytesperSector, FreeClusters, TotalClusters) then
      Result := Int64(BytesperSector) * Int64(SectorsperCluster) * Int64(FreeClusters)
end;

function DriveFreeSpace(Drive: PWideChar; ByteSize: TBtyeSize): WideString; 
begin
  case ByteSize of
    bsKiloBytes: Result := Format('%0.0n '+ 'KB', [DriveFreeSpace(Drive) / 1024]);
    bsMegaBytes: Result := Format('%0.0n '+ 'MB', [DriveFreeSpace(Drive) / 1048576]);
    bsGigiBytes: Result := Format('%0.1n '+ 'GB', [DriveFreeSpace(Drive) / 1073741824]);
    bsTereBytes: Result := Format('%0.1n '+ 'TB', [DriveFreeSpace(Drive) / 1099511627776]);
  end
end;


{ ----------------------------------------------------------------------------- }
function LoadShell32Functions: Boolean;
var
  ShellDLL: HMODULE;
begin
  { Don't see a point in making this all WideString compatible }
  ShellDLL := GetModuleHandle(PChar(Shell32));
//  ShellDLL := LoadLibrary(PChar(Shell32));
  if ShellDll <> 0 then
  begin
    AnimateWindow := GetProcAddress(GetModuleHandle('user32'), 'AnimateWindow');
    ShellILIsEqual := GetProcAddress(ShellDLL, PChar(21));
    ShellILIsParent := GetProcAddress(ShellDLL, PChar(23));
    SHLimitInputEdit := GetProcAddress(ShellDLL, PChar(747));
    Result := Assigned(ShellILIsEqual) and Assigned(ShellILIsParent)
  end else
    Result := False;
end;
{ ----------------------------------------------------------------------------- }


{ TNamespace }

function TNamespace.CanCopyAll(NamespaceArray: TNamespaceArray): Boolean;
var
  i: integer;
begin
  if Assigned(NamespaceArray) then
  begin
    Result := True;
    i := 0;
    while Result and (i < Length(NamespaceArray)) do
    begin
      Result := NamespaceArray[i].CanCopy;
      Inc(i)
    end
  end else
    Result := False
end;

function TNamespace.CanCutAll(NamespaceArray: TNamespaceArray): boolean;
begin
  Result := CanDeleteAll(NamespaceArray)
end;

function TNamespace.CanDeleteAll(NamespaceArray: TNamespaceArray): Boolean;
var
  i: integer;
begin
  if Assigned(NamespaceArray) then
  begin
    Result := True;
    i := 0;
    while Result and (i < Length(NamespaceArray)) do
    begin
      Result := NamespaceArray[i].CanDelete;
      Inc(i)
    end
  end else
    Result := False
end;

function TNamespace.CanPasteToAll(NamespaceArray: TNamespaceArray): Boolean;
begin
  Result := False;
  if Assigned(NamespaceArray) then
    if Length(NamespaceArray) > 0 then
      Result := True  // Can try to paste to anything?
end;

function TNamespace.CanShowPropertiesOfAll(NamespaceArray: TNamespaceArray): Boolean;
var
  i: integer;
begin
  if Assigned(NamespaceArray) then
  begin
    Result := True;
    i := 0;
    while Result and (i < Length(NamespaceArray)) do
    begin
      Result := NamespaceArray[i].HasPropSheet;
      Inc(i)
    end
  end else
    Result := False
end;

function TNamespace.Clone(ReleasePIDLOnDestroy: Boolean): TNameSpace;
begin
// This is not really a true clone since we don't copy the parent, but it is
// dangerous to do that.  Be careful using this function since things can
// potentially change in the shell.
  Result := TNamespace.Create(PIDLMgr.CopyPIDL(AbsolutePIDL), nil);
  Result.FreePIDLOnDestroy := ReleasePIDLOnDestroy;
end;

function TNamespace.ComparePIDL(PIDLToCompare: PItemIDList;
  IsAbsolutePIDL: Boolean; Column: Integer = 0): ShortInt;
// Encapsulation of the CompareID Function of IShellFolder
// Returns    > 0 if PIDLToCompare > RelativePIDL
//            0 if PIDLToCompare = RelativePIDL
//            < 0 if PIDLToCompare < RelativePIDL
var
  PIDL: PItemIDList;
begin
  if Assigned(PIDLToCompare) then
  begin
    if Column < 0 then
      Column := 0;

    if PIDLMgr.IsDesktopFolder(PIDLToCompare) and IsDesktop then
      Result := 0
    else begin
      PIDL := PIDLMgr.GetPointerToLastID(PIDLToCompare);
      if Assigned(ParentShellFolder) then
      begin
        if IsAbsolutePIDL then
        begin
          Result := -1;
          // First test is if the PIDL length is the same
          if PIDLMgr.IDCount(PIDLToCompare) = PIDLMgr.IDCount(AbsolutePIDL) then
          begin
            if Assigned(Parent) then
            begin
              // Desktop items won't have a valid parent
              if ILIsParent(Parent.AbsolutePIDL, PIDLToCompare, True) then
                Result := ShortInt(ParentShellFolder.CompareIDs(Column, PIDL, RelativePIDL))
            end else
              Result := ShortInt(ParentShellFolder.CompareIDs(Column, PIDL, RelativePIDL))
          end
        end else
          Result := ShortInt(ParentShellFolder.CompareIDs(Column, PIDL, RelativePIDL));
      end else
        Result := 0
    end
  end else
    Result := -1 // If the pidl is not assigned then we clearly are greater!
end;

function TNamespace.ContextMenuItemHelp(MenuItemID: LongWord): WideString;
const
  BufferLen = 128;
var
  S: string;
  Found: Boolean;
  P: Pointer;
begin
  Found := False;
  if Assigned(CurrentContextMenu) and (MenuItemID <> $FFFFFFFF) and (MenuItemID > 0)then
  begin
    if IsUnicode then
    begin
      SetLength(Result, BufferLen);
      { Keep D6 from complaining about suspicious PChar cast }
      P := @Result[1];
      Found := CurrentContextMenu.GetCommandString(MenuItemID-1, GCS_HELPTEXTW, nil, PChar(P),
        BufferLen) = NOERROR
    end;
    if not Found then
    begin
      SetLength(S, BufferLen);
      if CurrentContextMenu.GetCommandString(MenuItemID-1, GCS_HELPTEXTA, nil, PChar(S),
        BufferLen) <> NOERROR
      then
        Result := ''
      else begin
        SetLength(S, StrLen( PChar(S)));
        Result := S
      end
    end else
      SetLength(Result,  lstrlenW(PWideChar( Result)))
  end;
end;

function TNamespace.ContextMenuVerb(MenuItemID: Longword): WideString;
{ Returns the cononical (or not) verb that is equal to the MenuItemID, which is }
{ the HMenu identifer for a menu item.                                          }
const
  BufferLen = 128;
var
  S: string;
  Found: Boolean;
  P: Pointer;
begin
  Found := False;
  if Assigned(CurrentContextMenu) and (MenuItemID <> $FFFFFFFF) and (MenuItemID > 0) then
  begin
    if IsUnicode then
    begin
      SetLength(Result, BufferLen);
     { Keep D6 from complaining about suspicious PChar cast }
      P := @Result[1];
      Found := CurrentContextMenu.GetCommandString(MenuItemID-1, GCS_VERBW, nil, PChar(P),
        BufferLen) = NOERROR
    end;
    if not Found then
    begin
      SetLength(S, BufferLen);
      if CurrentContextMenu.GetCommandString(MenuItemID-1, GCS_VERBA, nil, PChar(S),
        BufferLen) <> NOERROR
      then
        Result := ''
      else begin
        SetLength(S, StrLen( PChar(S)));
        Result := S
      end
    end else
      SetLength(Result,  lstrlenW(PWideChar( Result)))
  end;
end;

function TNamespace.Copy(NamespaceArray: TNamespaceArray): Boolean;
begin
  Result := False;
  if CanCopyAll(NamespaceArray) then
  begin
    if VerifyPIDLRelationship(NamespaceArray, True) then
      Result := ExecuteContextMenuVerb('copy', NamespaceToRelativePIDLArray(NamespaceArray))
    else
      ExecuteContextMenuVerbMultiPath('copy', NamespaceArray)
  end
end;

constructor TNamespace.Create(PIDL: PItemIdList; AParent: TNamespace);
{ Pass the PIDL of a Namespace Object Folder to create along with its parent    }
{ to create a new TNamespace.                                                   }
begin
  inherited Create;

  FParent := AParent;
  FShellCache.Data.SmallIcon := -1;
  FShellCache.Data.SmallOpenIcon := -1;
  FShellCache.Data.OverlayIndex := -1;
  FShellCache.Data.OverlayIconIndex := -1;
  Include(FStates, nsShellDetailsSupported);  // Be optomistic
  Include(FStates, nsShellFolder2Supported);  // Be optomistic
  Include(FStates, nsShellOverlaySupported);  // Be optomistic
  FreePIDLOnDestroy := True;
  FNamespaceID := SHELL_NAMESPACE_ID;
  { It is the Root Folder since it has no parent }
  if not Assigned(AParent) then
  begin
    { Either a nil for PID or if the PID is the Desktop PIDL means a full tree }
    if not Assigned(PIDL) or PIDLMgr.IsDesktopFolder(PIDL) then
    begin
      { If PID is already assigned then use it }
      if not Assigned(PIDL) then
        SHGetSpecialFolderLocation(ParentWnd, CSIDL_DESKTOP, FRelativePIDL)
      else
        FRelativePIDL := PIDL;
      FAbsolutePIDL := FRelativePIDL;
    end else
    { The PIDL is the Root PIDL but is NOT the Desktop namespace  it is a }
    { FULLY QUALIFIED PIDL to a namespace that is to be the Root.         }
    begin
      FAbsolutePIDL := PIDL;
      FRelativePIDL := PIDLMgr.GetPointerToLastID(FAbsolutePIDL);
    end;
  end else
  { If the folder is a child of the desktop special conditions apply see above }
  if PIDLMgr.IsDesktopFolder(AParent.AbsolutePIDL) then
  begin
    FRelativePIDL := PIDL;
    FAbsolutePIDL := PIDL;
  end else
  { Normal building of the PIDLs and Shells }
  begin
    FAbsolutePIDL := PIDLMgr.AppendPIDL(AParent.FAbsolutePIDL, PIDL);
    FRelativePIDL := PIDLMgr.GetPointerToLastID(FAbsolutePIDL);
    PIDLMgr.FreePIDL(PIDL);
  end;
end;

constructor TNamespace.CreateCustomNamespace(CustomID: Integer; AParent: TNamespace);
begin
  FShellCache.Data.SmallIcon := -1;
  FShellCache.Data.SmallOpenIcon := -1;
  Exclude(FStates, nsShellDetailsSupported);
  Exclude(FStates, nsShellFolder2Supported);
  FreePIDLOnDestroy := False;
  FNamespaceID := CustomID;
  FParent := AParent
end;

constructor TNamespace.CreateFromFileName(FileName: WideString);
var
  PIDL: PItemIDList;
begin
  PIDL := PathToPIDL(FileName);
  if Assigned(PIDL) then
    Create(PIDL, nil)
  else
    // This will be called often with the autocomplete component while debugging
    // in the IDE
    // To turn off exception break go to Tools>Debugger Options>Add and type in
    //  "EVSTInvalidFileName" without the quotes.  Make sure that is is checked.
    // This will keep delphi from breaking on this exception
    raise EVSTInvalidFileName.Create('Trying to create a TNamespace on a non existant File object');
end;

function TNamespace.Cut(NamespaceArray: TNamespaceArray): Boolean;
begin
  Result := False;
  if CanCutAll(NamespaceArray) then
  begin
    if VerifyPIDLRelationship(NamespaceArray, True) then
      Result := ExecuteContextMenuVerb('cut', NamespaceToRelativePIDLArray(NamespaceArray))
    else
      ExecuteContextMenuVerbMultiPath('cut', NamespaceArray)
  end
end;

function TNamespace.DataObjectMulti(NamespaceArray: TNamespaceArray): IDataObject;
begin
  if VerifyPIDLRelationship(NamespaceArray, True) then
    Result := InternalGetDataObjectInterface(NamespaceToRelativePIDLArray(NamespaceArray))
  else
    CreateFullyQualifiedShellDataObject(NamespaceToAbsolutePIDLArray(NamespaceArray), Result);
end;

function TNamespace.Delete(NamespaceArray: TNamespaceArray): Boolean;
begin
  Result := False;
  if CanDeleteAll(NamespaceArray) then
  begin
    if VerifyPIDLRelationship(NamespaceArray, True) then
      Result := ExecuteContextMenuVerb('delete', NamespaceToRelativePIDLArray(NamespaceArray))
    else
      ExecuteContextMenuVerbMultiPath('delete', NamespaceArray)
  end
end;

destructor TNamespace.Destroy;
begin
  // Remember RelativePIDL points to end of AbsolutePIDL so only 1 actual PIDL.
  if FreePIDLOnDestroy and Assigned(PIDLMgr) then
    PIDLMgr.FreePIDL(FAbsolutePIDL);
  if IsUnicode then
  begin
    if Assigned(FWin32FindDataW) then
      FreeMem(FWin32FindDataW, SizeOf(TWin32FindDataW));
  end else
    if Assigned(FWin32FindDataA) then
      FreeMem(FWin32FindDataA, SizeOf(TWin32FindDataA));
  begin
  end;
  if Assigned(FSHGetFileInfoRec) then
  begin
    Finalize(FSHGetFileInfoRec^);
    FreeMem(FSHGetFileInfoRec, SizeOf(TSHGetFileInfoRec));
  end;
  FreeAndNil(FExtractImage);
  FreeAndNil(FShellLink);
  FShellFolder := nil;
  FreeAndNIL(FImage);

  inherited;
  if (nsOwnsParent in States) then
    FreeAndNil(FParent);
end;

function TNamespace.DetailsAlignment(ColumnIndex: Integer): TAlignment;
{ Returns the Text that is in the Header of the Explorer Listview based on what }
{ the folder in the Treeview is displaying.  Only implemented partially on      }
{ different versions of Windows.  It was undocumented until about Win98.        }
{ Win2k implements this using IShellFolder2                                     }
{ Be careful of the reference point using DetailsXXXX functions.  This method   }
{ get the header titles a folder will show for its children.                    }
var
  Details: TShellDetails;
  Found: Boolean;
begin
  // Default
  Result := taLeftJustify;

  FillChar(Details, SizeOf(Details), #0);
  Found := False;
  if DetailsValidIndex(ColumnIndex) then
  begin
    if Assigned(ShellFolder2) then
      Found := ShellFolder2.GetDetailsOf(nil, UINT(ColumnIndex), Details) = S_OK;
    if not Found and Assigned(ShellDetailsInterface) then
      Found :=  ShellDetailsInterface.GetDetailsOf(nil, UINT(ColumnIndex), Details) = S_OK;
    if Found then
    begin
      case Details.Fmt of
        LVCFMT_CENTER: Result := taCenter;
        LVCFMT_LEFT:   Result := taLeftJustify;
        LVCFMT_RIGHT:  Result := taRightJustify;
//        LVCFMT_COL_HAS_IMAGES: Result := tiContainsImage
      end;
      if (Details.str.uType = STRRET_WSTR) and Assigned(Details.str.pOleStr) then
        PIDLMgr.FreeOLEStr(Details.str.pOLEStr);
    end
  end
end;

function TNamespace.DetailsColumnTitle(ColumnIndex: integer): WideString;
{ Returns the Text that is in the Header of the Explorer Listview based on what }
{ the folder in the Treeview is displaying.  Only implemented partially on      }
{ different versions of Windows.  It was undocumented until about Win98.        }
{ Win2k implements this using IShellFolder2                                     }

{ Be careful of the reference point using DetailsXXXX functions.  This method   }
{ gets the header titles a folder will show for its children.                   }
var
  Details: TShellDetails;
  Found: Boolean;
begin
  FillChar(Details, SizeOf(Details), #0);
  Found := False;
  if DetailsValidIndex(ColumnIndex) then
  begin
    if Assigned(ShellFolder2) then
      Found := ShellFolder2.GetDetailsOf(nil, UINT(ColumnIndex), Details) = S_OK;
    if not Found and Assigned(ShellDetailsInterface) then
      Found :=  ShellDetailsInterface.GetDetailsOf(nil, UINT(ColumnIndex), Details) = S_OK;
    if Found then
      Result := StrRetToStr(Details.str, RelativePIDL)
    else
      Result := DetailsDefaultColumnTitle(ColumnIndex)
  end else
    Result := ''
end;

function TNamespace.DetailsDefaultColumnTitle(ColumnIndex: integer): WideString;
{ If IShellDetails is not implemented then these are returned for the Header    }
{ text as a default.  Can be overridden.                                        }
begin
  case ColumnIndex of
   -1, 0:  Result := STR_COLUMN_NAMES[0];
    1:  Result := STR_COLUMN_NAMES[1];
    2:  Result := STR_COLUMN_NAMES[2];
    3:  Result := STR_COLUMN_NAMES[3];
    4:  Result := STR_COLUMN_NAMES[4];
    5:  Result := STR_COLUMN_NAMES[5];
    6:  Result := STR_COLUMN_NAMES[6];
    7:  Result := STR_COLUMN_NAMES[7];
    8:  Result := STR_COLUMN_NAMES[8];
    9:  Result := STR_COLUMN_NAMES[9];
  end;
end;

function TNamespace.DetailsDefaultOf(ColumnIndex: integer): WideString;
{ If IShellDetail is not implemented the call to DetailsOf calls this and       }
{ returns what it can to mimic the values in columns for a plain file, Name,    }
{ size, type, date, attributes.                                                 }
var
  IsSystemFolder: Boolean;
begin
  Result := '';
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) then
      { This is totally undocumented. It works on Win98 will test on NT 4 soon }
      { Not a valid file so it has no size.  #8 appears to mean "System File" }
      IsSystemFolder := not ((FWin32FindDataW^.cFileName[0] = WideChar(#8)) or
                            (FWin32FindDataW^.cFileName[0] = WideChar(#0)) or
                             not FileSystem)
    else
      IsSystemFolder := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) then
      { This is totally undocumented. It works on Win98 will test on NT 4 soon }
      { Not a valid file so it has no size.  #8 appears to mean "System File" }
      IsSystemFolder := not ((FWin32FindDataA^.cFileName[0] = #8) or
                            (FWin32FindDataA^.cFileName[0] = #0) or
                             not FileSystem)
    else
      IsSystemFolder := False;
  end;
  case ColumnIndex of
    -1, 0:  Result := NameInFolder;
    1:  Result := SizeOfFileKB;
    2:  if IsSystemFolder then
          Result := FileType
        else
          Result := STR_SYSTEMFOLDER;
    3:  Result := LastWriteTime;
    4:  Result := AttributesString;
  else
    Result := ''
  end;
end;

function TNamespace.DetailsDefaultSupportedColumns: integer;
{ If IShellDetail is not implemented the call to SupportedColumns calls this    }
{ and returns 5.  It mimics the titles in the header for a plain file, Name,    }
{ size, type, date, attributes.                                                 }
begin
  Result := DefaultDetailColumns;
end;

function TNamespace.DetailsGetDefaultColumnState(ColumnIndex: integer): TSHColumnStates;
{ Be careful of the reference point using DetailsXXXX functions.  This function }
{ gets the GetDefaultColumnState of the folder for its children if it exposes   }
{ IShellFolder2.  If it does not it returns csOnByDefault so it will be shown   }
var
  Flags: Longword;
begin
  Result := [];
//  Assert(DetailsValidIndex(ColumnIndex), 'Invalid DetailsGetDefaultColumnState in TNamespace.DetailsOf');
  if DetailsValidIndex(ColumnIndex) then
  begin
    EnsureDetailCache;
    if (docStatesValid in ShellCache.Data.DetailsOfCache[ColumnIndex].Cached) then
      Result := ShellCache.Data.DetailsOfCache[ColumnIndex].States
    else begin
      if Assigned(ShellFolder2) then
      begin
        Flags := 0;
        if ShellFolder2.GetDefaultColumnState(ColumnIndex, Flags) = NOERROR then
        begin
          if SHCOLSTATE_TYPE_STR and Flags <> 0 then Include(ShellCache.Data.DetailsOfCache[ColumnIndex].States, csTypeString);
          if SHCOLSTATE_TYPE_INT and Flags <> 0 then Include(ShellCache.Data.DetailsOfCache[ColumnIndex].States, csTypeInt);
          if SHCOLSTATE_TYPE_DATE and Flags <> 0 then Include(ShellCache.Data.DetailsOfCache[ColumnIndex].States, csTypeDate);
          if SHCOLSTATE_ONBYDEFAULT and Flags <> 0 then Include(ShellCache.Data.DetailsOfCache[ColumnIndex].States, csOnByDefault);
          if SHCOLSTATE_TYPE_SLOW and Flags <> 0 then Include(ShellCache.Data.DetailsOfCache[ColumnIndex].States, csSlow);
          if SHCOLSTATE_EXTENDED and Flags <> 0 then Include(ShellCache.Data.DetailsOfCache[ColumnIndex].States, csExtended);
          if SHCOLSTATE_SECONDARYUI and Flags <> 0 then Include(ShellCache.Data.DetailsOfCache[ColumnIndex].States, csSecondaryUI);
          if SHCOLSTATE_HIDDEN and Flags <> 0 then Include(ShellCache.Data.DetailsOfCache[ColumnIndex].States, csHidden);
        end else
          { Some of the old namespaces will expose ShellFolder2 but don't support    }
          { it completely.  These resort to IShellDetails so assume this is the case }
          ShellCache.Data.DetailsOfCache[ColumnIndex].States := [csOnByDefault];
      end else
      { Some of the old namespaces don't expose ShellFolder2.  These resort to  }
      { IShellDetails so assume this is the case                                }
        ShellCache.Data.DetailsOfCache[ColumnIndex].States := [csOnByDefault];

      Result := ShellCache.Data.DetailsOfCache[ColumnIndex].States;
      Include(ShellCache.Data.DetailsOfCache[ColumnIndex].Cached, docStatesValid)
    end
  end
end;

function TNamespace.DetailsOf(ColumnIndex: integer): WideString;
{ Returns the text for the desired column (detail view in the listview in       }
{ Explorer) using IShellDetail or using information pulled from the namespace   }
{ by other means.                                                               }
{ Be careful of the reference point using DetailsXXXX functions.  This function }
{ gets the Details of the current namespace using its parent folder.            }
// Threading only works on Namespaces that support IShellFolder2 (WinME, Win2k and up)
var
  Details: TShellDetails;
  OldError: Integer;   
  TempCache: PDetailsOfCacheRec;
begin
  Result := '';
  if DetailsValidIndex(ColumnIndex) then
  begin
    OldError := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
    try
      { Force parent namespace creation if necessary }
      if Assigned(ParentShellFolder) and not IsDesktop then
      begin
        { The parent is responsible for the columns }
        if DetailsValidIndex(ColumnIndex) then
        begin
          EnsureDetailCache;

         if (docCaptionValid in ShellCache.Data.DetailsOfCache[ColumnIndex].Cached) then
            Result := ShellCache.Data.DetailsOfCache[ColumnIndex].Caption
         else begin
           FillChar(Details, SizeOf(Details), #0);
           TempCache := @ShellCache.Data.DetailsOfCache[ColumnIndex];

           if ColumnIndex = 0 then
             TempCache^.Caption := NameInFolder
           else
           if Assigned(ParentShellFolder2) then
           begin
             if ParentShellFolder2.GetDetailsOf(RelativePIDL, UINT(ColumnIndex), Details) <> S_OK then
             begin
               if Assigned(ParentShellDetailsInterface) and (ParentShellDetailsInterface.GetDetailsOf(RelativePIDL, UINT(ColumnIndex), Details) = S_OK) then
                 TempCache^.Caption := StrRetToStr(Details.Str, RelativePIDL)
               else
                 TempCache^.Caption := DetailsDefaultOf(ColumnIndex)
             end else
               TempCache^.Caption := StrRetToStr(Details.Str, RelativePIDL);
           Include(TempCache^.Cached, docCaptionValid)
           end else
           begin
             if Assigned(ParentShellDetailsInterface) then
             begin
               if ParentShellDetailsInterface.GetDetailsOf(RelativePIDL, UINT(ColumnIndex), Details) = S_OK then
                 TempCache^.Caption := StrRetToStr(Details.Str, RelativePIDL);
             end else
               TempCache^.Caption := DetailsDefaultOf(ColumnIndex);
             Include(TempCache^.Cached, docCaptionValid);
           end;
           Result := TempCache^.Caption;
          end
        end
      end
    finally
      SetErrorMode(OldError);
    end
  end
end;

function TNamespace.DetailsOfEx(ColumnIndex: integer): WideString;
var
  ColumnID: TSHColumnID;
  V: OLEVariant;
  ColState: TSHColumnStates;
//  Date: TDateTime;
begin
  Result := '';
  V := Null;   
  if Assigned(ParentShellFolder2) then
  begin
    ColState := Parent.DetailsGetDefaultColumnState(ColumnIndex);
    FillChar(ColumnID, SizeOf(ColumnID), #0);
    if ParentShellFolder2.MapColumnToSCID(ColumnIndex, ColumnID) = NOERROR then
      if ParentShellFolder2.GetDetailsEx(RelativePIDL, ColumnID, V) = NOERROR then
      begin
        if csTypeString in ColState then
          Result := WideString(V)
        else
        if csTypeInt in ColState then
          Result := IntToStr( Integer(V))
        else
    //    if csTypeDate in ColState then
    //       Date := V;
      end else
        Result := DetailsDefaultOf(ColumnIndex)
    else
      Result := DetailsDefaultOf(ColumnIndex)
  end
end;

function TNamespace.DetailsSupportedColumns: integer;
{ If IShellDetail or IShellFolder2 is implemented the call to                   }
{ DetailsSupportedColumns returns total number of columns the namespace         }
{ supports.  This allows the header to change dynamiclly.                       }

{ Be careful of the reference point using DetailsXXXX functions.  This function }
{ gets number of columns this folder will display for its children.             }
const
      { ShellFolder2 is broken on WinME for "Scanners and Cameras" folders.     }
      { It goes into an infinate loop.                                          }
      { WinXP is just as broken.                                                }
  COLUMNLIMIT = 1000;  // Safely valve for namespaces that don't follow the rules
var
  Details: TShellDetails;
  Flags: DWord;
  Found: Boolean;
begin
  FillChar(Details, SizeOf(Details), #0);
  if not (scSupportedColumns in ShellCache.ShellCacheFlags) then
  begin
    FShellCache.Data.SupportedColumns := 0;
    if Assigned(ShellFolder2) then
    begin
      while (ShellFolder2.GetDefaultColumnState(FShellCache.Data.SupportedColumns, Flags) = NOERROR) and
        (FShellCache.Data.SupportedColumns < COLUMNLIMIT) do
        Inc(FShellCache.Data.SupportedColumns);

      // Error detected, the namespace does not follow the rules
      if FShellCache.Data.SupportedColumns = COLUMNLIMIT then
        FShellCache.Data.SupportedColumns := 0;

      Found := FShellCache.Data.SupportedColumns > 0;
    { Some folders support both methods but only work right with GetDetailsOf   }
      if not Found then
        while (ShellFolder2.GetDetailsOf(nil, UINT(ShellCache.Data.SupportedColumns), Details) = S_OK) and
          (FShellCache.Data.SupportedColumns < COLUMNLIMIT) do
        begin
          Inc(FShellCache.Data.SupportedColumns);
          if (Details.str.uType = STRRET_WSTR) and Assigned(Details.str.pOleStr) then
            PIDLMgr.FreeOLEStr(Details.str.pOLEStr);
        end;

      // Error detected, the namespace does not follow the rules
      if FShellCache.Data.SupportedColumns = COLUMNLIMIT then
        FShellCache.Data.SupportedColumns := 0;
    end;

    { Some folders support both but only work right with IShellDetials          }
    { The History Folder is an example.                                         }
    Found := FShellCache.Data.SupportedColumns > 0;
    { DO NOT PASS A FREAKING UNInitialIZED TSHELLDETAIL STRUCTURE TO THIS FUNCTION }
    { IT WILL CAUSE THE RESULT TO BE CORRECT BUT INTERLACED WITH GARBAGE.         }
    if not Found and Assigned(ShellDetailsInterface) then
      while ShellDetailsInterface.GetDetailsOf(nil, UINT(ShellCache.Data.SupportedColumns), Details) = S_OK do
        Inc(FShellCache.Data.SupportedColumns);

    if ShellCache.Data.SupportedColumns = 0 then
      FShellCache.Data.SupportedColumns := DetailsDefaultSupportedColumns;

    Include(FShellCache.ShellCacheFlags, scSupportedColumns);
  end;
  Result := ShellCache.Data.SupportedColumns
end;

function TNamespace.DetailsSupportedVisibleColumns: TVisibleColumnIndexArray;
// Returns and array of currently visible columns in details mode.  Two bits of info
// are returned with this method.
// 1) The number of visible column:   Length(DetailsSupportedVisibleColumns)
// 2) The indicies of visible columns: [0, 2, 4, 6] Details index 0, 2, 4, 6 are shown\
var
  i: integer;
begin
  Result := nil;
  for i := 0 to DetailsSupportedColumns - 1 do
  begin
    if csOnByDefault in DetailsGetDefaultColumnState(i) then
    begin
      if DetailsColumnTitle(i) <> '' then
      begin
        SetLength(Result, Length(Result) + 1);
        Result[Length(Result) - 1] := i
      end
    end
  end;
end;

function TNamespace.DetailsValidIndex(DetailsIndex: integer): Boolean;
{ Test to see if the passed index is in the range of the number of detail       }
{ columns the namespace has.                                                    }
begin 
  Result := (DetailsIndex > -1) and (DetailsIndex < DetailsSupportedColumns)
end;

function TNamespace.DragEffect(grfKeyState: integer): HRESULT;
{ Looks at the KeyState during a IDragDrop notification.  The return value }
{ is the Effect that is desired by the user, using the GetDesiredDragEffect}
{ function, and what Effects are supported by the IDragSource              }

  function AvailableEffects: LongInt;
  begin
    Result := DROPEFFECT_NONE;
    if CanMove then Result := DROPEFFECT_MOVE;
    if CanCopy then Result := Result or DROPEFFECT_COPY;
    if CanLink then Result := Result or DROPEFFECT_LINK;
  end;

var
  KeyEffect: HResult;
  ValidEffects: Longword;
begin
  // See what the user is requesting by looking at the key board
  KeyEffect := RequestedDragEffect(grfKeyState);
  // What effects do the namespace support?
  ValidEffects := AvailableEffects;
  // Let the users desires prevail
  if KeyEffect and ValidEffects > 0 then Result := KeyEffect
  else  // If the users desires are undo-able pick the first effect avaiable
  if ValidEffects and DROPEFFECT_MOVE > 1 then Result := DROPEFFECT_MOVE
  else  // Windows default is MOVE so check it first
  if ValidEffects and DROPEFFECT_COPY > 1 then Result := DROPEFFECT_COPY
  else
  if ValidEffects and DROPEFFECT_LINK > 1 then Result := DROPEFFECT_LINK
  else
    Result := DROPEFFECT_NONE;
end;

function TNamespace.DisplayNameOf(Flags: Longword): WideString;
var
  StrRet: TSTRRET;
begin
  if Assigned(ParentShellFolder) then
  begin
    FillChar(StrRet, SizeOf(StrRet), #0);
    if ParentShellFolder.GetDisplayNameOf(RelativePIDL, Flags, StrRet) = NOERROR
    then
      Result := StrRetToStr(StrRet, RelativePIDL)
    else
      Result := '';
  end else
    Result := ''
end;

function TNamespace.DragEnter(const dataObj: IDataObject;
  grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
{ Called when there is a pending COM drop on the namespace.  The namespace will }
{ decide if it can handle the information passed.                               }
begin
  if DropTarget and Assigned(DropTargetInterface) then
    Result := DropTargetInterface.DragEnter(dataObj, grfKeyState, pt, dwEffect)
  else begin
    dwEffect := DROPEFFECT_NONE;
    Result := S_OK
  end
end;

function TNamespace.DragLeave: HResult;
{ Called when there is a pending COM drop on the namespace.  The namespace will }
{ decide if it can handle the information passed.                               }
begin
  if DropTarget and Assigned(DropTargetInterface) then
    Result := DropTargetInterface.DragLeave
  else
    Result := S_OK
end;

function TNamespace.DragOver(grfKeyState: Integer; pt: TPoint;
  var dwEffect: Integer): HResult;
{ Called when there is a pending COM drop on the namespace.  The namespace will }
{ decide if it can handle the information passed.                               }
begin
  if DropTarget and Assigned(DropTargetInterface) then
    Result := DropTargetInterface.DragOver(grfKeyState, pt, dwEffect)
  else begin
    dwEffect := DROPEFFECT_NONE;
    Result := S_OK
  end
end;

function TNamespace.Drop(const dataObj: IDataObject; grfKeyState: Integer;
  pt: TPoint; var dwEffect: Integer): HResult;
{ Called when there is a COM object is dropped on the namespace.  The namespace }
{ will handle the action as well.                                               }
begin
  if DropTarget and Assigned(DropTargetInterface) then
    Result := DropTargetInterface.Drop(dataObj, grfKeyState, pt, dwEffect)
  else begin
    dwEffect := DROPEFFECT_NONE;
    Result := S_OK
  end
end;

function TNamespace.EnumerateFolder(MessageWnd: HWnd; Folders, NonFolders,
  IncludeHidden: Boolean; EnumFunc: TEnumFolderCallback;
  UserData: Pointer): integer;
{ Enumerate a folder to get its subfolders.  For each subfolder the the         }
{ callback function is called so a new TNamespace may be created.               }
{ You have a choice to receive Folders, NonFolders (files), and Hidden          }
{ objects,  UserData is useful to pass info back to the callback function.      }
{ Encapsulates the IShellFolder.EnumObjects function                            }
{ The reciever of the Callback function is responsible for Freeing the PIDLs!   }
{ Returns the number of objects in the folder.                                  }
var
  Enum: IEnumIDList;
  Flags: Longword;
  Fetched: Longword;
  Item: PItemIDList;
  Terminate: Boolean;
  OldError: integer;
begin
  Result := 0;
  { This fixed a problem Rik Baker had:                                         }
  { "The error message is "C:\WINDOWS\SYSTEM\ODBCINST.DLL is not a valid        }
  { Windows Image", however the file appears fine and I've now seen the same    }
  { message on 9 different 2000 boxes spread across the country.                }

  OldError := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
  try
    if Assigned(ShellFolder) then
    begin
      if Assigned(EnumFunc) then
      begin
        Terminate := False;
        Flags := 0;
        if Folders then
          Flags := Flags or SHCONTF_FOLDERS;
        if NonFolders then
          Flags := Flags or SHCONTF_NONFOLDERS;
        if IncludeHidden then
          Flags := Flags or SHCONTF_INCLUDEHIDDEN;


        if Valid then
        begin
          // Right now you can't mix custom items and real shell items in the same folder
          if ShellFolder.EnumObjects(MessageWnd, Flags, Enum) = NOERROR  then
          begin
            // Vista Enum is nil every once in a while
            if Assigned(Enum) then
            begin
              while (Enum.Next(1, Item, Fetched) = NOERROR) and not Terminate do
              begin
                if EnumFunc(MessageWnd, Item, Self, UserData, Terminate) then
                  Inc(Result)
              end
            end
          end
        end
      end
    end
  finally
    SetErrorMode(OldError);
  end
end;

function TNamespace.EnumerateFolderEx(MessageWnd: HWnd; FileObjects: TFileObjects;
      EnumFunc: TEnumFolderCallback; UserData: pointer; AfterValidEnumIDList: TNotifyEvent = nil): integer;
{ Enumerate a folder to get its subfolders.  For each subfolder the the         }
{ callback function is called so a new TNamespace may be created.               }
{ You have a choice to receive Folders, NonFolders (files), and Hidden          }
{ objects,  UserData is useful to pass info back to the callback function.      }
{ Encapsulates the IShellFolder.EnumObjects function                            }
{ The reciever of the Callback function is responsible for Freeing the PIDLs!   }
{ Returns the number of objects in the folder.                                  }
var
  Enum: IEnumIDList;
  Flags: Longword;
  Fetched: Longword;
  Item: PItemIDList;
  Terminate: Boolean;
  OldError: integer;
begin
  Result := 0;
  { This fixed a problem Rik Baker had:                                         }
  { "The error message is "C:\WINDOWS\SYSTEM\ODBCINST.DLL is not a valid        }
  { Windows Image", however the file appears fine and I've now seen the same    }
  { message on 9 different 2000 boxes spread across the country.                }    
  OldError := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
  try
    if not MP_UseModalDialogs then
      MessageWnd := 0;
      
    if Assigned(ShellFolder)  then
    begin
      Flags := FileObjectsToFlags(FileObjects);
      Terminate := False;
      if Valid then
      begin
        // Right now you can't mix custom items and real shell items in the same folder
        if ShellFolder.EnumObjects(MessageWnd, Flags, Enum) = NOERROR then
        begin
          // Vista Enum is nil every once in a while
          if Assigned(Enum) then
          begin
            if Assigned(AfterValidEnumIDList) then
              AfterValidEnumIDList(Self);
            // Allows calling with a nil EnumFunc to pop messages in the EnumObjects call above
            if not Assigned(EnumFunc) then
              EnumFunc := EnumFuncDummy;
            if Assigned(Enum) then
              while (Enum.Next(1, Item, Fetched) = NOERROR) and not Terminate do
              begin
                if EnumFunc(MessageWnd, Item, Self, UserData, Terminate) then
                  Inc(Result)
            end
          end
        end
      end
    end
  finally
    SetErrorMode(OldError);
  end
end;

function TNamespace.ExecuteContextMenuVerb(AVerb: WideString;
  APIDLArray: TRelativePIDLArray; MessageWindowParent: HWnd = 0): Boolean;
const
  MaxVerbLen = 128;

var
  ContextMenu, ContextMenu2: IContextMenu;
  Menu: hMenu;
  InvokeInfo: TCMInvokeCommandInfoEx;
  i: integer;
  VerbA, AVerbA: string;
  VerbW: WideString;
  VerbFound, StrFound: Boolean;
  MenuID: LongWord;
  GenericVerb: Pointer;
begin
  if Assigned(ParentShellFolder) then
  begin
    if Assigned(APIDLArray) then
      ContextMenu := InternalGetContextMenuInterface(APIDLArray)
    else
      ContextMenu := ContextMenuInterface;

    if Assigned(ContextMenu) then
      ContextMenu.QueryInterface(IID_IContextMenu2, ContextMenu2);
    Menu := CreatePopupMenu;
    if Assigned(ContextMenu) or Assigned(ContextMenu2) then
    begin
      try
        if Assigned(ContextMenu2) then
          ContextMenu2.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_NORMAL or CMF_EXPLORE or CMF_DEFAULTONLY)
        else
          ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_NORMAL or CMF_EXPLORE or CMF_DEFAULTONLY);
        FillChar(InvokeInfo, SizeOf(InvokeInfo), #0);

        if IsUnicode then
        begin
          SetLength(VerbW, MaxVerbLen);
          GenericVerb := @VerbW[1];
        end
        else begin
          SetLength(VerbA, MaxVerbLen);
          GenericVerb := @VerbA[1];;
          AVerbA := AVerb
        end;

        VerbFound := False;
        i := 0;
        { The result of using the 'verb' string and the MakeIntResource is      }
        { different expecially on system folders.  This forces it to use        }
        { MakeIntResource if it can.                                            }
        while (i < GetMenuItemCount(Menu)) and not VerbFound do
        begin
          MenuID := GetMenuItemID(Menu, i);
          if (MenuID <> $FFFFFFFF) and (MenuID > 0) then
          begin
            FillChar(GenericVerb^, Length(VerbW) * 2, #0);
            if IsUnicode then
            begin
              if Assigned(ContextMenu2) then
                StrFound := Succeeded(ContextMenu2.GetCommandString(MenuID-1, GCS_VERBW, nil, GenericVerb, MaxVerbLen))
              else
                StrFound := Succeeded(ContextMenu.GetCommandString(MenuID-1, GCS_VERBW, nil, GenericVerb, MaxVerbLen));
              if StrFound then
              begin
                SetLength(VerbW, lstrlenW(PWideChar( VerbW)));
                if lstrcmpiW_MP(PWideChar(VerbW), PWideChar(AVerb)) = 0 then
                begin
                  InvokeInfo.fMask := CMIC_MASK_UNICODE;
                  { For some reason the lpVerbW won't work }
                  InvokeInfo.lpVerb := MakeIntResourceA(MenuID-1);
                  InvokeInfo.lpVerbW := MakeIntResourceW(MenuID-1);
                  VerbFound := True
                end;
                SetLength(VerbW, MaxVerbLen);
              end
            end else
            begin
              if Assigned(ContextMenu2) then
                StrFound := Succeeded(ContextMenu2.GetCommandString(MenuID-1, GCS_VERB, nil, GenericVerb, MaxVerbLen))
              else
                StrFound := Succeeded(ContextMenu.GetCommandString(MenuID-1, GCS_VERB, nil, GenericVerb, MaxVerbLen));
              if StrFound then
              begin
                SetLength(VerbA, StrLen(PChar( VerbA)));
                if lstrcmpi(PChar( VerbA), PChar(AVerbA)) = 0 then
                begin
                  InvokeInfo.lpVerb := MakeIntResourceA(MenuID-1);
                  VerbFound := True
                end;
                SetLength(VerbA, MaxVerbLen);
              end
            end
          end;
          Inc(i)
        end;

        if not VerbFound then
        begin
          if IsUnicode then
          begin
            InvokeInfo.fMask := CMIC_MASK_UNICODE;
            InvokeInfo.lpVerbW := PWideChar( AVerb);
            InvokeInfo.lpVerb := PChar( string( AVerb))
          end else
            InvokeInfo.lpVerb := PChar( AVerbA);
        end;

        if IsUnicode then
          InvokeInfo.cbSize := SizeOf(TCMInvokeCommandInfoEx)
        else
          InvokeInfo.cbSize := SizeOf(TCMInvokeCommandInfo);
        if MessageWindowParent = 0 then
          InvokeInfo.hWnd := ParentWnd
        else
          InvokeInfo.hWnd := MessageWindowParent;
        InvokeInfo.nShow := SW_SHOWNORMAL;
        if Assigned(ContextMenu2) then
          Result := Succeeded(ContextMenu2.InvokeCommand(InvokeInfo))
        else
          Result := Succeeded(ContextMenu.InvokeCommand(InvokeInfo))
      finally
        if Menu <> 0 then
          DestroyMenu(Menu);
      end;
    end else
      Result := False
  end else
    Result := False
end;

function TNamespace.FolderSize(Invalidate: Boolean; RecurseFolder: Boolean = False): Int64;
begin
  if not(scFolderSize in ShellCache.ShellCacheFlags) or Invalidate then
  begin
    if Folder and FileSystem then
      FShellCache.Data.FolderSize := CalcuateFolderSize(NameForParsing, RecurseFolder);
    Include(FShellCache.ShellCacheFlags, scFolderSize);
  end;
  Result := FShellCache.Data.FolderSize
end;

function TNamespace.ExplorerStyleAttributeStringList(CapitalLetters: Boolean): WideString;
begin
  Result := '';
  if Archive then
    Result := Result + STR_ARCHIVE;
  if Hidden then
    Result := Result + STR_HIDDEN;
  if ReadOnlyFile then
    Result := Result + STR_READONLY;
  if SystemFile then
    Result := Result + STR_SYSTEM;
  if Compressed then
    Result := Result + STR_COMPRESS;
  if not CapitalLetters then
    Result := WideLowerCase(Result)

end;

function TNamespace.GetArchive: Boolean;
{ GETTER: Does the file attributes contain Archive?                             }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_ARCHIVE <> 0
    else
      Result := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_ARCHIVE <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetAttributesString: WideString;
begin
  if FileSystem then
    Result := ExplorerStyleAttributeStringList(True)
  else
    Result := ''
end;

function TNamespace.GetBrowsable: Boolean;
begin
  if not (scBrowsable in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_BROWSABLE, False) then
      Include(FShellCache.Data.Attributes, caBrowsable);
    Include(FShellCache.ShellCacheFlags, scBrowsable);
  end;
  Result := caBrowsable in ShellCache.Data.Attributes
end;

function TNamespace.GetCanCopy: Boolean;
begin
  if not (scCanCopy in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_CANCOPY, False) then
      Include(FShellCache.Data.Attributes, caCanCopy);
    Include(FShellCache.ShellCacheFlags, scCanCopy);
  end;
  Result := caCanCopy in ShellCache.Data.Attributes
end;

function TNamespace.GetCanDelete: Boolean;
{ GETTER: Can we delete the namespace?                                          }
begin
  if not (scCanDelete in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_CANDELETE, False) then
      Include(FShellCache.Data.Attributes, caCanDelete);
    Include(FShellCache.ShellCacheFlags, scCanDelete);
  end;
  Result := caCanDelete in ShellCache.Data.Attributes
end;

function TNamespace.GetCanLink: Boolean;
begin
  if not (scCanLink in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_CANLINK, False) then
      Include(FShellCache.Data.Attributes, caCanLink);
    Include(FShellCache.ShellCacheFlags, scCanLink);
  end;
  Result := caCanLink in ShellCache.Data.Attributes
end;

function TNamespace.GetCanMove: Boolean;
begin
  if not (scCanMove in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_CANMOVE, False) then
      Include(FShellCache.Data.Attributes, caCanMove);
    Include(FShellCache.ShellCacheFlags, scCanMove);
  end;
  Result := caCanMove in ShellCache.Data.Attributes
end;

function TNamespace.GetCanRename: Boolean;
{ GETTER: Can we Rename the namespace?                                         }
begin
  if not (scCanRename in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_CANRENAME, False) then
      Include(FShellCache.Data.Attributes, caCanRename);
    Include(FShellCache.ShellCacheFlags, scCanRename);
  end;
  Result := caCanRename in ShellCache.Data.Attributes
end;

function TNamespace.GetCategoryCount: Integer;
begin
  LoadCategoryInfo;
  Result := 0;
end;

function TNamespace.GetCLSID: TGUID;
var
  DescriptionID: TSHDESCRIPTIONID;
  PersistFolder: IPersistFolder;
begin
  Result := GUID_NULL;
  if Assigned(ParentShellFolder) then
    if Succeeded(SHGetDataFromIDList(ParentShellFolder, RelativePIDL, SHGDFIL_DESCRIPTIONID, @DescriptionID, SizeOf(TSHDESCRIPTIONID))) then
      Result := DescriptionID.Id;
  if IsEqualGUID(Result, GUID_NULL) then
  begin
    if Succeeded(ShellFolder.QueryInterface(IPersistFolder, PersistFolder)) then
      if not Succeeded(PersistFolder.GetClassID(Result)) then
        Result := GUID_NULL;
  end;
end;

function TNamespace.GetCompressed: Boolean;
{ GETTER: Does the file attributes contain Compressed?                          }
begin
  if not (scCompressed in ShellCache.ShellCacheFlags) then
  begin
    if IsUnicode then
    begin
      if not Assigned(FWin32FindDataW) then
        GetDataFromIDList;
      if Assigned(FWin32FindDataW) and FileSystem then
        if FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_COMPRESSED <> 0 then
          Include(FShellCache.Data.Attributes, caCompressed)
    end else
    begin
      if not Assigned(FWin32FindDataA) then
        GetDataFromIDList;
      if Assigned(FWin32FindDataA) and FileSystem then
        if FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_COMPRESSED <> 0 then
          Include(FShellCache.Data.Attributes, caCompressed)
    end;
    Include(FShellCache.ShellCacheFlags, scCompressed);
  end;
  Result := caCompressed in ShellCache.Data.Attributes;
end;

function TNamespace.GetContextMenuInterface: IContextMenu;
var
  PIDLArray: TRelativePIDLArray;
begin
  if not Assigned(Result) then
  begin
    SetLength(PIDLArray, 1);
    PIDLArray[0] := RelativePIDL;
    Result := InternalGetContextMenuInterface(PIDLArray);
  end
end;

function TNamespace.GetContextMenu2Interface: IContextMenu2;
var
  Found: Boolean;
  ContextMenu: IContextmenu;
begin
  Found := False;
  ContextMenu := ContextMenuInterface;
  if Assigned(ContextMenu) then
  begin
    Found := ContextMenu.QueryInterface(IID_IContextMenu2, Pointer(Result)) <>  E_NOINTERFACE;
    CurrentContextMenu2 := Result
  end;
  if not Found then
    Result := nil
end;

function TNamespace.GetContextMenu3Interface: IContextMenu3;
var
  Found: Boolean;
  ContextMenu: IContextmenu;
begin
  Found := False;
  ContextMenu := ContextMenuInterface;
  if Assigned(ContextMenu) then
  begin
    Found := ContextMenu.QueryInterface(IContextMenu3, Pointer(Result)) <>  E_NOINTERFACE;
    CurrentContextMenu2 := Result
  end;
  if not Found then
    Result := nil
end;

function TNamespace.GetCreationTime: WideString;
{ GETTER: Creation time of the file.                                            }
begin
  if not (scCreationTime in ShellCache.ShellCacheFlags) then
  begin
    { Don't use Win32FindData cache, re-read the file times }
    GetFileTimes;

    if IsUnicode then
    begin
      if Assigned(FWin32FindDataW) and FileSystem then
        FShellCache.Data.CreationTime := ConvertTFileTimeToLocalStr(FWin32FindDataW^.ftCreationTime)
      else
        FShellCache.Data.CreationTime := '';
    end else
    begin
      if Assigned(FWin32FindDataA) and FileSystem then
        FShellCache.Data.CreationTime := ConvertTFileTimeToLocalStr(FWin32FindDataA^.ftCreationTime)
      else
        FShellCache.Data.CreationTime := '';
    end;
    Include(FShellCache.ShellCacheFlags, scCreationTime);
  end;
  Result := ShellCache.Data.CreationTime
end;

function TNamespace.GetCreationDateTime: TDateTime;
begin
  Result :=  ConvertFileTimetoDateTime(CreationTimeRaw)
end;

function TNamespace.GetCreationTimeRaw: TFileTime;
begin
  { Don't use Win32FindData cache, re-read the file times }
  GetFileTimes;

  if IsUnicode then
  begin
    if Assigned(FWin32FindDataW) then
      Result := FWin32FindDataW^.ftCreationTime
    else
      FillChar(Result, SizeOf(Result), #0);
  end else
  begin
    if Assigned(FWin32FindDataA) then
      Result := FWin32FindDataA^.ftCreationTime
    else
      FillChar(Result, SizeOf(Result), #0);
  end
end;

function TNamespace.GetParent: TNamespace;
var
  P: PItemIDList;
begin
  if not Assigned(FParent) then
  begin
    P := PIDLMgr.CopyPIDL(AbsolutePIDL);
    if PIDLMgr.IDCount(P) > 1 then
      FParent := TNamespace.Create(PIDLMgr.StripLastID(P), nil)
    else
      FParent := TNamespace.Create(nil, nil);
    Include(FStates, nsOwnsParent);
  end;
  Result := FParent;
end;

function TNamespace.GetThreadedDetailLoaded(ColumnIndex: Integer): Boolean;
begin
  Result := False;
  EnsureDetailCache;
  if DetailsValidIndex(ColumnIndex) then
    Result := docThreadLoaded in ShellCache.Data.DetailsOfCache[ColumnIndex].Cached
end;

function TNamespace.GetThreadedDetailLoading(ColumnIndex: Integer): Boolean;
begin
  Result := False;
  EnsureDetailCache;
  if DetailsValidIndex(ColumnIndex) then
    Result := docThreadLoading in ShellCache.Data.DetailsOfCache[ColumnIndex].Cached
end;

function TNamespace.ParentWnd: HWnd;
begin
  Result := 0;
  if MP_UseModalDialogs then
    Result := GetActiveWindow
end;

procedure TNamespace.EnsureDetailCache;
var
  i: Integer;
  TempCache: PDetailsOfCacheRec;
begin
  if not (scDetailsOfCache in ShellCache.ShellCacheFlags) then
  begin
    SetLength(FShellCache.Data.DetailsOfCache, DetailsSupportedColumns);
    for i := 0 to Length(FShellCache.Data.DetailsOfCache) - 1 do
    begin
      TempCache := @ShellCache.Data.DetailsOfCache[i];
      TempCache^.Cached := [];
      TempCache^.Caption := '';
      TempCache^.States := [];
    end;
    Include(FShellCache.ShellCacheFlags, scDetailsOfCache);
  end;
end;

procedure TNamespace.ExecuteContextMenuVerbMultiPath(Verb: WideString;
  Namespaces: TNamespaceArray);
var
  Menu: TCommonShellMultiParentContextMenu;
  Temp: TWinControl;
begin
  Menu := TCommonShellMultiParentContextMenu.Create(nil);
  Temp := TWinControl.CreateParented(GetDesktopWindow);
  try
    Temp.Width := 0;
    Temp.Height := 0;
    Temp.Visible := True;
    Menu.ExecuteContextMenuVerb(Temp, Namespaces, Verb);
    Temp.Visible := False;
  finally
    Temp.Free;
    Menu.Free
  end
end;

procedure TNamespace.GetDataFromIDList;
{ Retrieves and caches the Data stored by the shell PIDL.                       }
var
  Error: Boolean;
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) and not IsDesktop then
    begin
      if not (scInvalidIDListData in ShellCache.ShellCacheFlags) then
      begin
        Error := True;
        try
          if Assigned(ParentShellFolder) then
          begin
            GetMem(FWin32FindDataW, SizeOf(TWin32FindDataW));
            FillChar(FWin32FindDataW^, SizeOf(FWin32FindDataW^), #0);
            { Children of the Desktop won't work if accessed from the Desktop       }
            { ShellFolder, they must use the physical Desktop folder.               }
            if Assigned(Parent) and (Parent.IsDesktop) and Assigned(PhysicalDesktopFolder) then
            begin
              Error := SHGetDataFromIDListW_MP(PhysicalDesktopFolder.ShellFolder, RelativePIDL,
                SHGDFIL_FINDDATA, FWin32FindDataW, SizeOf(TWin32FindDataW)) <> NOERROR;
            end else
              Error := SHGetDataFromIDListW_MP(ParentShellFolder, RelativePIDL, SHGDFIL_FINDDATA,
                         FWin32FindDataW, SizeOf(TWin32FindDataW)) <> NOERROR;
          end
        finally
          if Error then
          begin
            if Assigned(FWin32FindDataW) then
              FreeMem(FWin32FindDataW, SizeOf(TWin32FindDataW));
            FWin32FindDataW := nil;
            Include(FShellCache.ShellCacheFlags, scInvalidIDListData)
          end
        end;
      end
    end
  end else
  begin
    if not Assigned(FWin32FindDataA) and not IsDesktop then
    begin
      if not (scInvalidIDListData in ShellCache.ShellCacheFlags) then
      begin
        Error := True;
        try
          if Assigned(ParentShellFolder) then
          begin
            GetMem(FWin32FindDataA, SizeOf(TWin32FindDataA));
            FillChar(FWin32FindDataA^, SizeOf(TWin32FindDataA), #0);
            { Children of the Desktop won't work if accessed from the Desktop       }
            { ShellFolder, they must use the physical Desktop folder.               }
            if Assigned(Parent) and (Parent.IsDesktop) and Assigned(PhysicalDesktopFolder) then
            begin
              Error := SHGetDataFromIDListA(PhysicalDesktopFolder.ShellFolder, RelativePIDL,
                SHGDFIL_FINDDATA, FWin32FindDataA, SizeOf(TWin32FindDataA)) <> NOERROR;
            end else
              Error := SHGetDataFromIDListA(ParentShellFolder, RelativePIDL, SHGDFIL_FINDDATA,
                         FWin32FindDataA, SizeOf(TWin32FindDataA)) <> NOERROR;
          end
        finally
          if Error then
          begin
            if Assigned(FWin32FindDataA) then
              FreeMem(FWin32FindDataA, SizeOf(TWin32FindDataA));
            FWin32FindDataA := nil;
            Include(FShellCache.ShellCacheFlags, scInvalidIDListData)
          end
        end;
      end
    end
  end
end;

function TNamespace.GetDataObjectInterface: IDataObject;
begin
  Result := InternalGetDataObjectInterface(nil)
end;

function TNamespace.GetDescription: TObjectDescription;
var
  DescriptionID: TSHDESCRIPTIONID;
begin
  Result := odError;
  if Assigned(ParentShellFolder) then
  begin
    if Succeeded(SHGetDataFromIDList(ParentShellFolder, RelativePIDL, SHGDFIL_DESCRIPTIONID, @DescriptionID, SizeOf(TSHDESCRIPTIONID))) then
      Result := TObjectDescription(DescriptionID.dwDescriptionId)
  end
end;

function TNamespace.GetDetailsSupported: Boolean;
begin
  { IShellDetails depends on the parent folder implementing the interface }
  if Assigned(Parent) then
    Result := Assigned(Parent.ShellFolder2) or Assigned(ParentShellDetailsInterface)
  else
    Result := False
end;

function TNamespace.GetDirectory: Boolean;
{ GETTER: Does the file attributes contain Directory?                           }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0
    else
      Result := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetDropTarget: Boolean;
{ GETTER: Can we drop another object on this namespace?  Note the Desktop is    }
{ handled as a special case.  The IDropTarget is mapped to the physical folder  }
{ location in the DropTargetInterface property.                                 }
begin
  Result := TestAttributesOf(SFGAO_DROPTARGET, False) or
    PIDLMgr.IsDesktopFolder(RelativePIDL);
end;

function TNamespace.GetDropTargetInterface: IDropTarget;
var
  Found: Boolean;
begin
  if not Assigned(FDropTargetInterface) then
  begin
    Found := False;
    if Assigned(ParentShellFolder) then
    begin
      Found := ParentShellFolder.GetUIObjectOf(ParentWnd, 1, FRelativePIDL,
        IID_IDropTarget, nil, Pointer(FDropTargetInterface)) = NOERROR;
    end;
    if not Found and IsDesktop then
      FDropTargetInterface := PhysicalDesktopFolder.DropTargetInterface;
  end;
  Result := FDropTargetInterface
end;

function TNamespace.GetExtension: WideString;
begin
  Result := WideExtractFileExt(NameForParsingInFolder);
end;

function TNamespace.GetExtractImage: TExtractImage;
begin
  if not Assigned(FExtractImage) then
  begin
    FExtractImage := TExtractImage.Create;
    FExtractImage.Owner := Self
  end;
  Result := FExtractImage
end;

function TNamespace.GetExtractIconAInterface: IExtractIcon;
var
  Found: Boolean;
begin
  if Assigned(ParentShellFolder) then
  begin
    Found := Succeeded(ParentShellFolder.GetUIObjectOf(ParentWnd, 1, FRelativePIDL, IExtractIconA, nil, Pointer(Result)));
    if not Found and Assigned(ShellFolder) then
      Found := Succeeded(ShellFolder.CreateViewObject(ParentWnd, IExtractIconA, Pointer(Result)));
    if not Found then
      Result := nil
  end
end;

function TNamespace.GetExtractIconWInterface: IExtractIconW;
var
  Found: Boolean;
begin
  if Assigned(ParentShellFolder) then
  begin
    Found := Succeeded(ParentShellFolder.GetUIObjectOf(ParentWnd, 1, FRelativePIDL, IExtractIconW, nil, Pointer(Result)));
    if not Found and Assigned(ShellFolder) then
      Found := Succeeded(ShellFolder.CreateViewObject(ParentWnd, IExtractIconW, Pointer(Result)));
    if not Found then
      Result := nil
  end
end;

function TNamespace.GetFileName: WideString;
{ GETTER: FileName from the file system (FindFirst)                             }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.cFileName
    else
      Result := '';
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.cFileName
    else
      Result := '';
  end
end;

function TNamespace.GetFileSysAncestor: Boolean;
// Only works reliablely on Win2k and above
begin
  if not (scFileSysAncestor in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_FILESYSANCESTOR, False) then
      Include(FShellCache.Data.Attributes, caFileSysAncestor);
    Include(FShellCache.ShellCacheFlags, scFileSysAncestor);
  end;
  Result := caFileSysAncestor in ShellCache.Data.Attributes
end;

function TNamespace.GetFileSystem: Boolean;
{ GETTER: Is the namespace part of the physical file system?                    }
begin
  if not (scFileSystem in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_FILESYSTEM, False) then
      Include(FShellCache.Data.Attributes, caFileSystem);
    Include(FShellCache.ShellCacheFlags, scFileSystem);
  end;
  Result := caFileSystem in ShellCache.Data.Attributes
end;

procedure TNamespace.GetFileTimes;
var
  Handle: THandle;
  FileDataA: TWin32FindData;
  FileDataW: TWin32FindDataW;
  S: string;
begin
  if not (scFileTimes in ShellCache.ShellCacheFlags) then
  begin
    if IsUnicode then
    begin
      if not Assigned(FWin32FindDataW) then
        GetDataFromIDList;
      if FileSystem and Assigned(FWin32FindDataW)  then
      begin
        FillChar(FileDataW, SizeOf(FileDataW), #0);
        Handle := FindFirstFileW_MP(PWideChar( NameParseAddress), FileDataW);
        if Handle <> INVALID_HANDLE_VALUE then
        begin
          Windows.FindClose(Handle); // There is no FindCloseW
          FWin32FindDataW.ftLastAccessTime := FileDataW.ftLastAccessTime;
          FWin32FindDataW.ftCreationTime := FileDataW.ftCreationTime;
          FWin32FindDataW.ftLastWriteTime := FileDataW.ftLastWriteTime
        end
      end;
    end else
    begin
      if not Assigned(FWin32FindDataA) then
        GetDataFromIDList;
      if FileSystem and Assigned(FWin32FindDataA)  then
      begin
        FillChar(FileDataA, SizeOf(FileDataA), #0);
        S := NameParseAddress;
        Handle := FindFirstFileA(PChar( S), FileDataA);
        if Handle <> INVALID_HANDLE_VALUE then
        begin
          Windows.FindClose(Handle);  // There is no ASCI and Wide version
          FWin32FindDataA.ftLastAccessTime := FileDataA.ftLastAccessTime;
          FWin32FindDataA.ftCreationTime := FileDataA.ftCreationTime;
          FWin32FindDataA.ftLastWriteTime := FileDataA.ftLastWriteTime
        end
      end;
    end;
    Include(FShellCache.ShellCacheFlags, scFileTimes)
  end;
end;

function TNamespace.GetFileType: WideString;
// File type string shown in column 3 of Explorer Listview
begin
  if not (scFileType in ShellCache.ShellCacheFlags) then
  begin
    if not Assigned(FSHGetFileInfoRec) then
      GetSHFileInfo;
    if Assigned(FSHGetFileInfoRec) then
    begin
      FShellCache.Data.FileType := FSHGetFileInfoRec^.FileType;
     { NT only half-assed supports the SHGetFileInfo...only if the ext is      }
     { associated with a program. So we build it ourselves                     }
      if FShellCache.Data.FileType = '' then
        FShellCache.Data.FileType := WideUpperCase(WideExtractFileExt(NameForParsing)) + STR_FILE;
    end else
      FShellCache.Data.FileType := '';
    Include(FShellCache.ShellCacheFlags, scFileType);
  end;
  Result := ShellCache.Data.FileType
end;

function TNamespace.GetFolder: Boolean;
// Ask the Folder if it is a Folder, as opposed to files.  Folders can  contain
// other objects.                                                        
begin
  if not (scFolder in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_FOLDER, False) then
      Include(FShellCache.Data.Attributes, caFolder);
    Include(FShellCache.ShellCacheFlags, scFolder);
  end;
  Result := caFolder in ShellCache.Data.Attributes;
end;

function TNamespace.GetFreePIDLOnDestroy: Boolean;
begin
  Result := nsFreePIDLOnDestroy in States
end;

function TNamespace.GetGhosted: Boolean;
// Ask the Folder if it is a ghosted file object. Partially encapsulates the
// IShellFolder.GetAttributesOf function.
begin
  if not (scGhosted in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_GHOSTED, False) then
      Include(FShellCache.Data.Attributes, caGhosted);
    Include(FShellCache.ShellCacheFlags, scGhosted);
  end;
  Result := caGhosted in ShellCache.Data.Attributes
end;

function TNamespace.GetHasPropSheet: Boolean;
begin
  Result := TestAttributesOf(SFGAO_HASPROPSHEET, False);
end;

function TNamespace.GetHasSubFolder: Boolean;
begin
  Result := TestAttributesOf(SFGAO_HASSUBFOLDER, False);
end;

function TNamespace.GetHidden: Boolean;
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_HIDDEN <> 0
    else
      Result := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_HIDDEN <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetIconIndexChanged: Boolean;
begin
  Result := nsIconIndexChanged in States
end;

function TNamespace.GetIconIndex(OpenIcon: Boolean; IconSize: TIconSize; ForceLoad: Boolean = True): integer;
{ Retrieve the Icon Index either selected or not selected (open folder or       }
{ closed folder)                                                                }

  function GetIconByIShellIcon(AnOpenIcon: Boolean; Size: TIconSize; var Index: integer): Boolean;
  var
    Flags: Longword;
  begin
    Result := False;
    if Assigned(ShellIconInterface) then
    begin
      Flags := 0;
      if Size = icLarge then
        Flags := GIL_FORSHELL;
      if AnOpenIcon then
        Flags := GIL_OPENICON or Flags;
      Result := ShellIconInterface.GetIconOf(RelativePIDL, Flags, Index) = NOERROR
    end
  end;

  procedure GetIconBySHGetFileInfo(AnOpenIcon: Boolean; Size: TIconSize; var Index: Integer);
  { A little undocumented trick.  If you use the SHGFI_USEFILEATTRIBUTES flags  }
  { the SHGetFileInfo function does not fully access the object and is much     }
  { faster.                                                                     }
  { UPDATE: Unfortunatly this does not work well in Win98 :^(                   }
  var
    Flags: integer;
    InfoA: TSHFileInfoA;
    InfoW: TSHFileInfoW;
  begin
    Flags := SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SHELLICONSIZE;
    if IconSize = icLarge then
      Flags := Flags or SHGFI_LARGEICON
    else
      Flags := Flags or SHGFI_SMALLICON;
    if AnOpenIcon then
      Flags := Flags or SHGFI_OPENICON;
    if IsUnicode then
    begin
      FillChar(InfoW, SizeOf(InfoW), #0);
      if SHGetFileInfoW_MP(PWideChar(AbsolutePIDL), 0, InfoW, SizeOf(InfoW), Flags) <> 0 then
        Index := InfoW.iIcon
      else
        Index := 0
    end else
    begin
      FillChar(InfoA, SizeOf(InfoA), #0);
      if SHGetFileInfoA(PChar(AbsolutePIDL), 0, InfoA, SizeOf(InfoA), Flags) <> 0 then
        Index := InfoA.iIcon
      else
        Index := 0
    end
  end;

  function GetIcon(IsOpen: Boolean; IconSize: TIconSize): integer;
  begin
    if not GetIconByIShellIcon(IsOpen, IconSize, Result) then
      GetIconBySHGetFileInfo(IsOpen, IconSize, Result);
  end;

begin
  if not OpenIcon then
  begin
    if not (scSmallIcon in ShellCache.ShellCacheFlags) or ForceLoad then
    begin
      FShellCache.Data.SmallIcon := GetIcon(False, icSmall);
      Include(FShellCache.ShellCacheFlags, scSmallIcon);
    end;
    Result := ShellCache.Data.SmallIcon;
  end else
  begin
    if not (scSmallOpenIcon in ShellCache.ShellCacheFlags) or ForceLoad then
    begin
      { Some Control panel icons return 0 for open but have icons for not open }
      { and it looks bad to show the default icon when the item is selected.   }
      { In NT4 some ControlPanel icons are the Mouse icons when selected! }
      if Assigned(Parent) and Parent.IsControlPanel then
        FShellCache.Data.SmallOpenIcon := GetIcon(False, icSmall)
      else begin
        FShellCache.Data.SmallOpenIcon := GetIcon(True, icSmall);
        { If it is 0 then try the normal icon }
        if FShellCache.Data.SmallOpenIcon = 0 then
          FShellCache.Data.SmallOpenIcon := GetIcon(False, icSmall)
      end;

      Include(FShellCache.ShellCacheFlags, scSmallOpenIcon)
    end;
    Result := ShellCache.Data.SmallOpenIcon;
  end;
end;

function TNamespace.GetInfoTip: WideString;
{ Retrieves the text from the IInfoTip interface in Win2k.                      }
var
  Buffer: PWideChar;
begin
  Result := '';
  if Assigned(QueryInfoInterface) then
  begin
    if QueryInfoInterface.GetInfoTip(0, Buffer) = S_OK then
    begin
      Result := Buffer;
      PIDLMgr.FreeOLEStr(Buffer);
    end;
  end;
end;

function TNamespace.GetLastAccessTime: WideString;
{ GETTER: Last Access time of the file.                                         }
begin
  if not (scLastAccessTime in ShellCache.ShellCacheFlags) then
  begin
    { Don't use Win32FindData cache, re-read the file times }
    GetFileTimes;
    
    if IsUnicode then
    begin
      if Assigned(FWin32FindDataW) and FileSystem then
        FShellCache.Data.LastAccessTime := ConvertTFileTimeToLocalStr(FWin32FindDataW^.ftLastAccessTime)
      else
        FShellCache.Data.LastAccessTime := '';
    end else
    begin
      if Assigned(FWin32FindDataA) and FileSystem then
        FShellCache.Data.LastAccessTime := ConvertTFileTimeToLocalStr(FWin32FindDataA^.ftLastAccessTime)
      else
        FShellCache.Data.LastAccessTime := '';
    end;
    Include(FShellCache.ShellCacheFlags, scLastAccessTime);
  end;
  Result := FShellCache.Data.LastAccessTime
end;

function TNamespace.GetLastAccessDateTime: TDateTime;
begin
  Result := ConvertFileTimetoDateTime(LastAccessTimeRaw)
end;

function TNamespace.GetLastAccessTimeRaw: TFileTime;
begin
  { Don't use Win32FindData cache, re-read the file times }
  GetFileTimes;

  if IsUnicode then
  begin
    if Assigned(FWin32FindDataW) then
      Result := FWin32FindDataW^.ftLastAccessTime
    else
      FillChar(Result, SizeOf(Result), #0);
  end else
  begin
    if Assigned(FWin32FindDataA) then
      Result := FWin32FindDataA^.ftLastAccessTime
    else
      FillChar(Result, SizeOf(Result), #0);
  end
end;


function TNamespace.GetLastWriteTime: WideString;
{ GETTER: Last write time for the file.                                         }
begin
  if not (scLastWriteTime in ShellCache.ShellCacheFlags) then
  begin
    { Don't use Win32FindData cache, re-read the file times }
    GetFileTimes;

    if IsUnicode then
    begin
      if Assigned(FWin32FindDataW) and FileSystem then
        FShellCache.Data.LastWriteTime := ConvertTFileTimeToLocalStr(FWin32FindDataW^.ftLastWriteTime)
      else
        FShellCache.Data.LastWriteTime := '';
    end else
    begin
      if Assigned(FWin32FindDataA) and FileSystem then
        FShellCache.Data.LastWriteTime := ConvertTFileTimeToLocalStr(FWin32FindDataA^.ftLastWriteTime)
      else
        FShellCache.Data.LastWriteTime := '';
    end;
    Include(FShellCache.ShellCacheFlags, scLastWriteTime);
  end;
  Result := FShellCache.Data.LastWriteTime
end;

function TNamespace.GetLastWriteDateTime: TDateTime;
begin
  Result :=  ConvertFileTimetoDateTime(LastWriteTimeRaw)
end;

function TNamespace.GetLastWriteTimeRaw: TFileTime;
{ GETTER: Last Write time for the file in raw TFileTime format.                 }
begin
  { Don't use Win32FindData cache, re-read the file times }
  GetFileTimes;

  if IsUnicode then
  begin
    if Assigned(FWin32FindDataW) then
      Result := FWin32FindDataW^.ftLastWriteTime
    else
      FillChar(Result, SizeOf(Result), #0);
  end else
  begin
    if Assigned(FWin32FindDataA) then
      Result := FWin32FindDataA^.ftLastWriteTime
    else
      FillChar(Result, SizeOf(Result), #0);
  end
end;

function TNamespace.GetLink: Boolean;
begin
  if not (scLink in ShellCache.ShellCacheFlags) then
  begin
    if TestAttributesOf(SFGAO_LINK, False) then
      Include(FShellCache.Data.Attributes, caLink);
    Include(FShellCache.ShellCacheFlags, scLink);
  end;
  Result := caLink in ShellCache.Data.Attributes
end;

function TNamespace.GetNameAddressbar: WideString;
begin
  Result := DisplayNameOf(SHGDN_FORADDRESSBAR or SHGDN_NORMAL)
end;

function TNamespace.GetNameAddressbarInFolder: WideString;
begin
  Result := DisplayNameOf(SHGDN_INFOLDER or SHGDN_FORADDRESSBAR)
end;

function TNamespace.GetNameForEditing: WideString;
begin
  Result := DisplayNameOf(SHGDN_FOREDITING)
end;

function TNamespace.GetNameForEditingInFolder: WideString;
begin
  Result := DisplayNameOf(SHGDN_FOREDITING or SHGDN_INFOLDER)
end;

function TNamespace.GetNameForParsing: WideString;
begin
  // Early versions of Windows returned "Desktop" instead of the full path
  if IsDesktop then
    Result := PhysicalDesktopFolder.NameForParsing
  else
    Result := DisplayNameOf(SHGDN_FORPARSING or SHGDN_NORMAL)
end;

function TNamespace.GetNameForParsingInFolder: WideString;
begin
// Early versions of Windows returned "Desktop" instead of the full path
  if IsDesktop then
    Result := PhysicalDesktopFolder.NameForParsingInFolder
  else
  Result := DisplayNameOf(SHGDN_INFOLDER or SHGDN_FORPARSING)
end;

function TNamespace.GetNameInFolder: WideString;
begin
  if not (scInFolderName in ShellCache.ShellCacheFlags) then
  begin
    FShellCache.Data.InFolderName := DisplayNameOf(SHGDN_INFOLDER);
    Include(FShellCache.ShellCacheFlags, scInFolderName)
  end;
  Result := FShellCache.Data.InFolderName
end;

function TNamespace.GetNameNormal: WideString;
begin
  if not (scNormalName in ShellCache.ShellCacheFlags) then
  begin
    FShellCache.Data.NormalName := DisplayNameOf(SHGDN_NORMAL);
    Include(FShellCache.ShellCacheFlags, scNormalName)
  end;
  Result := FShellCache.Data.NormalName
end;

function TNamespace.GetNameParseAddress: WideString;
begin
  if not (scParsedName in ShellCache.ShellCacheFlags) then
  begin
    FShellCache.Data.ParsedName := DisplayNameOf(SHGDN_FORADDRESSBAR or SHGDN_FORPARSING);
    Include(FShellCache.ShellCacheFlags, scParsedName)
  end;
  Result := FShellCache.Data.ParsedName
end;

function TNamespace.GetNameParseAddressInFolder: WideString;
begin
  Result := DisplayNameOf(SHGDN_FORADDRESSBAR or SHGDN_FORPARSING or SHGDN_INFOLDER)
end;

function TNamespace.GetNewContent: Boolean;
{ GETTER: Does this namespace contain new content?                             }
begin
  Result := TestAttributesOf(SFGAO_NEWCONTENT, False);
end;

function TNamespace.GetNonEnumerated: Boolean;
{ GETTER: Is this namespace able to be enumerated?                              }
begin
  Result := TestAttributesOf(SFGAO_NONENUMERATED, False);
end;

function TNamespace.GetNormal: Boolean;
{ GETTER: Does the file attributes contain Normal?                             }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_NORMAL <> 0
    else
      Result := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_NORMAL <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetOffLine: Boolean;
{ GETTER: Does the file attributes contain OffLine?                             }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_OFFLINE <> 0
    else
      Result := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_OFFLINE <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetImage: TBitmap;
begin
 Result := FImage;
end;

procedure TNamespace.InvalidateDetailsOfCache(FlushStrings: Boolean);
var
  i: Integer;
begin
  for i := 0 to Length( ShellCache.Data.DetailsOfCache) - 1 do
  begin
    ShellCache.Data.DetailsOfCache[i].Cached := [];
    if FlushStrings then
      ShellCache.Data.DetailsOfCache[i].Caption := ''
  end;
  FShellCache.ShellCacheFlags := ShellCache.ShellCacheFlags - [scDetailsOfCache];
  FShellCache.ShellCacheFlags := ShellCache.ShellCacheFlags - [scInFolderName];
end;

procedure TNamespace.InvalidateThumbImage;
begin
  FreeAndNIL(FImage);
  Exclude(FStates, nsThreadedImageLoaded);
  Exclude(FStates, nsThreadedImageLoading);
end;

function TNamespace.SubFoldersEx(Flags: Longword = SHCONTF_FOLDERS): Boolean;
begin
  Result := InternalSubItems(Flags)
end;

function TNamespace.SubItemsEx(Flags: Longword = SHCONTF_NONFOLDERS): Boolean;
begin
  Result := InternalSubItems(Flags)
end;

function TNamespace.GetOverlayIconIndex: Integer;
begin
  if Assigned(Parent) then
  begin
    if Assigned(Parent.ShellIconOverlayInterface) then
    begin
      if FShellCache.Data.OverlayIconIndex < 0 then
      begin
        if Parent.ShellIconOverlayInterface.GetOverlayIconIndex(FRelativePIDL, FShellCache.Data.OverlayIconIndex) <> S_OK then
          FShellCache.Data.OverlayIconIndex := -1;
      end
    end
  end;
  Result := FShellCache.Data.OverlayIconIndex
end;

function TNamespace.GetOverlayIndex: Integer;
begin
  if Assigned(Parent) then
  begin
    if not (scOverlayIndex in FShellCache.ShellCacheFlags) then
    begin
      if Assigned(Parent.ShellIconOverlayInterface) then
      begin
        if FShellCache.Data.OverlayIndex < 0 then
        begin
          if Parent.ShellIconOverlayInterface.GetOverlayIndex(FRelativePIDL, FShellCache.Data.OverlayIndex) <> S_OK then
          begin
            if MP_UseSpecialReparsePointOverlay and IsUnicode and not IsWinNT4 and ReparsePoint then
              FShellCache.Data.OverlayIndex := 4
            else
              FShellCache.Data.OverlayIndex := -1;
          end
        end
      end;
      Include(FShellCache.ShellCacheFlags, scOverlayIndex)
    end
  end;
  Result := FShellCache.Data.OverlayIndex;
end;

function TNamespace.GetCanMoniker: Boolean;
begin
  Result := TestAttributesOf(SFGAO_CANMONIKER, False)
end;

function TNamespace.GetEncrypted: Boolean;
begin
  Result := TestAttributesOf(SFGAO_ENCRYPTED, False)
end;

function TNamespace.GetHasStorage: Boolean;
begin
  Result := TestAttributesOf(SFGAO_HASSTORAGE, False)
end;

function TNamespace.GetIsSlow: Boolean;
begin
  Result := TestAttributesOf(SFGAO_ISSLOW, False)
end;

function TNamespace.GetStorage: Boolean;
begin
  Result := TestAttributesOf(SFGAO_STORAGE, False)
end;

function TNamespace.GetStorageAncestor: Boolean;
begin
  Result := TestAttributesOf(SFGAO_STORAGEANCESTOR, False)
end;

function TNamespace.GetStream: Boolean;
begin
  Result := TestAttributesOf(SFGAO_STREAM, False)
end;

function TNamespace.GetParentShellDetailsInterface: IVETShellDetails;
begin
  { This forces the Parent to be created if necessary }
  if Assigned(ParentShellFolder) then
    Result := Parent.ShellDetailsInterface
  else
    Result := ShellDetailsInterface
end;

function TNamespace.GetParentShellFolder: IShellFolder;
//var
 // P: PItemIDList;
begin

  Result := Parent.ShellFolder
 (* Result := nil;

  // 08.31.02
  // Going to try to allow the Parent to persist if the namespace creates it itself.
  // This is called a LOT more than I thought expecially for the ExplorerListview

  if Assigned(Parent) then
  begin
    Result := Parent.ShellFolder
  end else
  if not IsDesktop then
  begin
    if Assigned(Parent) then
      FreeAndNil(FParent);
    P := PIDLMgr.CopyPIDL(AbsolutePIDL);
    FParent := TNamespace.Create(PIDLMgr.StripLastID(P), nil);

    if Assigned(FParent) then
    begin
      Result := FParent.ShellFolder;
      { Since we created the parent we own it in case we have to destroy it in  }
      { our destructor.                                                         }
      Include(FStates, nsOwnsParent);
    end
  end else
    Result := ShellFolder  *)
end;

function TNamespace.GetParentShellFolder2: IShellFolder2;
begin
  { This flag keeps us from constantly trying to get FShellFolder2 if it is not }
  { supported by the namespace.                                                 }
  { This forces the Parent to be created if necessary }
  if Assigned(ParentShellFolder) then
    Result := Parent.ShellFolder2
  else
    Result := ShellFolder2
end;

function TNamespace.GetQueryInfoInterface: IQueryInfo;
var
  Found: Boolean;
begin
  if not Assigned(FQueryInfoInterface) then
  begin
    Found := False;
    if Assigned(ParentShellFolder) then
    begin
      Found := ParentShellFolder.GetUIObjectOf(ParentWnd, 1, FRelativePIDL,
        IQueryInfo, nil, Pointer(FQueryInfoInterface)) = NOERROR;
    end;
    if not Found and Assigned(ShellFolder) then
    begin
      Found := ShellFolder.CreateViewObject(ParentWnd, IQueryInfo,
        Pointer(FQueryInfoInterface)) = NOERROR;
    end;
    if not Found and IsDesktop then
      FQueryInfoInterface := PhysicalDesktopFolder.QueryInfoInterface;
  end;
  Result := FQueryInfoInterface
end;

function TNamespace.GetReadOnly: Boolean;
{ GETTER: Is this namespace ReadOnly?                                          }
begin
  Result := TestAttributesOf(SFGAO_READONLY, False);
end;

function TNamespace.GetReadOnlyFile: Boolean;
{ GETTER: Does the file attributes contain ReadOnly?                           }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_READONLY <> 0
    else
      Result := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_READONLY <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetReparsePoint: Boolean;
{ GETTER: Does the file attributes contain ReadOnly?                           }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT <> 0
    else
      Result := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetRemovable: Boolean;
{  GETTER: Is this a removeable object?                                         }
begin
  Result := TestAttributesOf(SFGAO_REMOVABLE, False);
end;

function TNamespace.GetShellDetailsInterface: IVETShellDetails;
var
  Found: Boolean;
begin
  { This flag keeps us from constantly trying to get IShellDetails if it is not }
  { supported by the namespace.                                                 }
  if (nsShellDetailsSupported in States) and not Assigned(FShellDetailsInterface) then
  begin
    Found := False;
    if not Found and Assigned(ShellFolder) then
      Found := ShellFolder.CreateViewObject(ParentWnd, IID_IShellDetails, Pointer(FShellDetailsInterface)) = NOERROR;
    if not Found and Assigned(ParentShellFolder) then
      Found := ParentShellFolder.GetUIObjectOf(ParentWnd, 1, FRelativePIDL, IID_IShellDetails, nil,
        Pointer(FShellDetailsInterface)) = NOERROR;
    if Found then
      Include(FStates, nsShellDetailsSupported)
    else
    begin
      Exclude(FStates, nsShellDetailsSupported);
      FShellDetailsInterface := nil
    end
  end;
  Result := FShellDetailsInterface;
end;

function TNamespace.GetShellIconInterface: IShellIcon;
var
  Found: Boolean;
begin
  if not Assigned(FShellIconInterface) then
  begin
    Found := False;
    if Assigned(ParentShellFolder) then
      Found :=  ParentShellFolder.QueryInterface(IID_IShellIcon,
        Pointer(FShellIconInterface)) <> E_NOINTERFACE;
    if not Found then
      FShellIconInterface := nil
  end;
  Result := FShellIconInterface
end;

function TNamespace.GetShellFolder: IShellFolder;
var
  P: PItemIDList;
  ParentFolder, Desktop: IShellFolder;
begin
  if not Assigned(FShellFolder) then
  begin
    SHGetDesktopFolder(Desktop);
    if PIDLMgr.IDCount(AbsolutePIDL) > 1 then
    begin
      P := PIDLMgr.StripLastID(PIDLMgr.CopyPIDL(AbsolutePIDL));
      Desktop.BindToObject(P, nil, IID_IShellFolder, Pointer(ParentFolder));
      PIDLMgr.FreePIDL(P)
    end else
      ParentFolder := Desktop;
      
    if Assigned(ParentFolder) then
    begin
      if IsDesktop or not Folder then
        FShellFolder := ParentFolder
      else begin
        if not Succeeded(ParentFolder.BindToObject(FRelativePIDL, nil, IID_IShellFolder, Pointer(FShellFolder))) then
          FShellFolder := nil
      end
    end else
      FShellFolder := nil
  end;
  Result := FShellFolder
end;

function TNamespace.GetShellFolder2: IShellFolder2;
begin
  { This flag keeps us from constantly trying to get FShellFolder2 if it is not }
  { supported by the namespace.                                                 }
  if (nsShellFolder2Supported in States) and not Assigned(FShellFolder2) then
  begin
    if Assigned(ShellFolder) then
      if ShellFolder.QueryInterface(IID_IShellFolder2, Pointer(FShellFolder2)) = E_NOINTERFACE
    then begin
      FShellFolder2 := nil;
      Exclude(FStates, nsShellFolder2Supported)
    end else
      Include(FStates, nsShellFolder2Supported)
  end;
  Result := FShellFolder2;
end;

function TNamespace.GetShellLink: TVirtualShellLink;
begin
  if Link then
  begin
    if not Assigned(FShellLink) then
      FShellLink := TVirtualShellLink.Create(nil);
    FShellLink.ReadLink(NameParseAddress);
  end;
  Result := FShellLink
end;

function TNamespace.GetSizeOfFile: WideString;
{ GETTER: Get the size of the file in string format}
begin
  if not (scFileSize in ShellCache.ShellCacheFlags) then
  begin
    if not Folder then
    begin
      FShellCache.Data.FileSize := Format('%0.0n', [SizeOfFileInt64 + 0.0]);
  //    FShellCache.Data.FileSize := AddCommas(WideIntToStr(SizeOfFileInt64));
      Include(FShellCache.ShellCacheFlags, scFileSize);
    end else
      FShellCache.Data.FileSize := ''
  end;
  Result := ShellCache.Data.FileSize
end;

function TNamespace.GetSizeOfFileDiskUsage: WideString;
var
  Size, BytesPerCluster: Int64;
  Drive: string;
  DriveW: WideString;
  SectorsPerCluster,
  BytesPerSector,
  FreeClusters,
  TotalClusters,
  i : DWORD;
  ValidData: Boolean;
begin
  if not Folder then
  begin
    Size := SizeOfFileInt64;
    DriveW := WideExtractFileDrive(Self.NameForParsing) + '\';
    Drive := DriveW;
    if WideDirectoryExists(Drive) then
    begin
      ValidData := GetDiskFreeSpaceMP(PWideChar( DriveW), SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters);

      if ValidData then
      begin
        BytesPerCluster := Int64(SectorsPerCluster) * Int64(BytesPerSector);
        if BytesPerCluster <> 0 then
        begin
          { In the *rare* instance where the actual size is equal to multiple of  }
          { the sector size don't do the math :^)                                 }
          if Size mod BytesPerCluster <> 0 then
            i := 1
          else
            i := 0;
            Result := Format('%0.0n', [Int64(BytesPerCluster) *(Size div Int64(BytesPerCluster) + i) + 0.0])
        end else
          Result := SizeOfFile
      end else
        Result := SizeOfFile
    end else
      Result := SizeOfFile
  end;
end;

function TNamespace.GetSizeOfFileInt64: Int64;
var
  H: THandle;
  FindDataW: TWin32FindDataW;
  FindDataA: TWin32FindDataA;
{ GETTER: Get the file size in bytes.                                           }
// The PIDL does not store the file size for > 4G files, need to use FindFirstFile
begin
  if not (scFileSizeInt64 in ShellCache.ShellCacheFlags) then
  begin
    if FileSystem then
    begin
      if IsUnicode then
      begin
        H := FindFirstFileW_MP(PWideChar(NameForParsing), FindDataW);
        if H <> INVALID_HANDLE_VALUE then
        begin
          Windows.FindClose(H);
          FShellCache.Data.FileSizeInt64 := FindDataW.nFileSizeLow;
          if FShellCache.Data.FileSizeInt64 < 0 then
            FShellCache.Data.FileSizeInt64 := FShellCache.Data.FileSizeInt64 + 4294967296;
          if FindDataW.nFileSizeHigh > 0 then
              FShellCache.Data.FileSizeInt64 := FShellCache.Data.FileSizeInt64 + (FindDataW.nFileSizeHigh * 4294967296)
        end
      end else
      begin
        H := FindFirstFileA(PChar(string(NameForParsing)), FindDataA);
        if H <> INVALID_HANDLE_VALUE then
        begin
          Windows.FindClose(H);
          FShellCache.Data.FileSizeInt64 := FindDataA.nFileSizeLow;
          if FShellCache.Data.FileSizeInt64 < 0 then
            FShellCache.Data.FileSizeInt64 := FShellCache.Data.FileSizeInt64 + 4294967296;
          if FindDataA.nFileSizeHigh > 0 then
            FShellCache.Data.FileSizeInt64 := FShellCache.Data.FileSizeInt64 + (FindDataA.nFileSizeHigh * 4294967296)
        end
      end
    end;
    Include(FShellCache.ShellCacheFlags, scFileSizeInt64)
  end;
  Result := FShellCache.Data.FileSizeInt64
end;

function TNamespace.GetSizeOfFileKB: WideString;
{ GETTER: Get the file  size in Explorer KiloByte format.                       }
begin
  if not (scFileSizeKB in ShellCache.ShellCacheFlags) then
  begin
    if not Folder then
    begin
      if SizeOfFileInt64 > 0 then
      begin                              
        FShellCache.Data.FileSizeKB := Format('%0.0n '+ 'KB', [SizeOfFileInt64/1024]);
        if (FShellCache.Data.FileSizeKB = STR_ZERO_KB) then
          FShellCache.Data.FileSizeKB := STR_ONE_KB;
      end else
        FShellCache.Data.FileSizeKB := STR_ONE_KB;
      Include(FShellCache.ShellCacheFlags, scFileSizeKB)
    end else
      FShellCache.Data.FileSizeKB := '';
  end;
  Result := FShellCache.Data.FileSizeKB;
end;

function TNamespace.GetSparseFile: Boolean;
{ GETTER: Does the file attributes contain ReadOnly?                           }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_SPARSE_FILE <> 0
    else
      Result := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_SPARSE_FILE <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetShare: Boolean;
begin
  Result := TestAttributesOf(SFGAO_SHARE, False);
end;

procedure TNamespace.GetSHFileInfo;
{ Retrieves and caches the some information about the namespace with            }
{ ShGetFileInfo.                                                                }
var
  InfoA: TSHFileInfoA;
  InfoW: TSHFileInfoW;
begin
  if not Assigned(FSHGetFileInfoRec) then
  begin
    if IsUnicode then
    begin
      GetMem(FSHGetFileInfoRec, SizeOf(FSHGetFileInfoRec^));
      Initialize(FSHGetFileInfoRec^.FileType);
      if Assigned(FSHGetFileInfoRec) then
      begin
        SHGetFileInfoW_MP(PWideChar(AbsolutePIDL), 0, InfoW, SizeOf(InfoW), SHGFI_TYPENAME or SHGFI_PIDL);
        FSHGetFileInfoRec^.FileType := InfoW.szTypeName;
        { NT only half-assed supports the SHGetFileInfo...only if the ext is      }
        { associated with a program. So we build it ourselves                     }
        if FSHGetFileInfoRec^.FileType = '' then
          FSHGetFileInfoRec^.FileType := WideUpperCase(WideExtractFileExt(NameForParsing)) + STR_FILE;
      end
    end else
    begin
      GetMem(FSHGetFileInfoRec, SizeOf(FSHGetFileInfoRec^));
      Initialize(FSHGetFileInfoRec^.FileType);
      if Assigned(FSHGetFileInfoRec) then
      begin
        SHGetFileInfoA(PChar(AbsolutePIDL), 0, InfoA, SizeOf(InfoA), SHGFI_TYPENAME or SHGFI_PIDL);
        FSHGetFileInfoRec^.FileType := InfoA.szTypeName;
        { NT only half-assed supports the SHGetFileInfo...only if the ext is      }
        { associated with a program. So we build it ourselves                     }
        if FSHGetFileInfoRec^.FileType = '' then
          FSHGetFileInfoRec^.FileType := WideUpperCase(WideExtractFileExt(NameForParsing)) + STR_FILE;
      end
    end;
  end;
end;

function TNamespace.GetShortFileName: WideString;
{ GETTER: Get the 8:3 short file name (DOS)                                     }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
    begin
      Result := FWin32FindDataW^.cAlternateFileName;
      if Result = '' then
        Result := WideUpperCase(FWin32FindDataW^.CFileName)
    end else
      Result := '';
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
    begin
      Result := FWin32FindDataA^.cAlternateFileName;
      if Result = '' then
        Result := WideUpperCase(FWin32FindDataA^.CFileName)
    end else
      Result := '';
  end
end;

function TNamespace.GetSubFolders: Boolean;
{ Tests to see if a namespace is a true folder and has at least one             }
{ sub-namespace within it.                                                      }
begin
  Result := InternalSubItems(SHCONTF_FOLDERS or SHCONTF_INCLUDEHIDDEN)
end;

function TNamespace.GetSubItems: Boolean;
{ Tests to see if a namespace is a true folder and has at least one             }
{ sub-namespace within it.                                                      }
begin
  Result := InternalSubItems(SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN)
end;

function TNamespace.GetSystem: Boolean;
{ GETTER: Does the file attributes contain System?                             }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_SYSTEM <> 0
    else
      Result := False;
  end else
  begin
if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_SYSTEM <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetTemporary: Boolean;
{ GETTER: Does the file attributes contain Temporary?                           }
begin
  if IsUnicode then
  begin
    if not Assigned(FWin32FindDataW) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataW) and FileSystem then
      Result := FWin32FindDataW^.dwFileAttributes and FILE_ATTRIBUTE_TEMPORARY <> 0
    else
      Result := False;
  end else
  begin
    if not Assigned(FWin32FindDataA) then
      GetDataFromIDList;
    if Assigned(FWin32FindDataA) and FileSystem then
      Result := FWin32FindDataA^.dwFileAttributes and FILE_ATTRIBUTE_TEMPORARY <> 0
    else
      Result := False;
  end
end;

function TNamespace.GetThreadedIconLoaded: Boolean;
begin
  Result := nsThreadedIconLoaded in States
end;

function TNamespace.GetThreadIconLoading: Boolean;
begin
  Result := nsThreadedIconLoading in States
end;

function TNamespace.GetShellIconOverlayInterface: IShellIconOverlay;
var
  Found: Boolean;
begin
  if (nsShellOverlaySupported in States) and not Assigned(FShellIconOverlayInterface) then
  begin
    Found := False;
    if Assigned(ShellFolder) then
      Found := ShellFolder.QueryInterface(IShellIconOverlay,
        Pointer(FShellIconOverlayInterface)) <> E_NOINTERFACE;
    if not Found then         // Here we have to check again
    begin
      IF Assigned(ParentShellFolder) then
        Found :=  ParentShellFolder.QueryInterface(IShellIconOverlay,
          Pointer(FShellIconOverlayInterface)) <> E_NOINTERFACE;
    end;
    if not Found then
    begin
      Exclude(FStates, nsShellOverlaySupported);
      FShellIconOverlayInterface := nil
    end
  end;
  Result := FShellIconOverlayInterface
end;

procedure TNamespace.HandleContextMenuMsg(Msg, wParam, lParam: Longint; var Result: LRESULT);
{ This is called when the ContextMenu calls back to its owner window to ask     }
{ questions to implement the addition of icons to the menu.  The messages sent  }
{ to the owner window are:  WM_INITMENUPOPUP, WM_DRAWITEM, or WM_MEASUREITEM.   }
{ Which must be passed on to the ContextMenu2 interface to display items with   }
{ icons.                                                                        }
var
  ContextMenu3: IContextMenu3;
begin
  if Assigned(CurrentContextMenu2) then
    if CurrentContextMenu2.QueryInterface(IContextMenu3, ContextMenu3) <> E_NOINTERFACE then
      ContextMenu3.HandleMenuMsg2(Msg, wParam, lParam, Result)
    else
      CurrentContextMenu2.HandleMenuMsg(Msg, wParam, lParam);
end;


function TNamespace.InjectCustomSubMenu(Menu: HMenu; Caption: string; PopupMenu: TPopupMenu;
  var SubMenu: HMenu): TMenuItemIDArray;
const
  MENUMASK = MIIM_CHECKMARKS or MIIM_DATA or MIIM_ID or MIIM_STATE or MIIM_TYPE;


{ Searchs through the passed menu looking for an item identifer that is not   }
{ currently being used.                                                       }

  function FindUniqueMenuID(AMenu: HMenu; StartID: cardinal): cardinal;
  var
    ItemCount, i: integer;
    Duplicate, Done: Boolean;
  begin
    ItemCount := GetMenuItemCount(Menu);
    Duplicate := True;
    Result := StartID;
    while Duplicate do
    begin
      i := 0;
      Done := False;
      while (i < ItemCount) and not Done do
      begin
        Done := GetMenuItemID(Menu, i) = Result;
        Inc(i);
      end;
      Duplicate := Done;
      if Duplicate then
        Inc(Result)
    end;
  end;

var
  ItemCount, i: integer;
  ItemInfo: TMenuItemInfo;
  LastID: cardinal;
begin
  Result := nil;
  SubMenu := 0;
  LastID := 0;
  ItemCount := GetMenuItemCount(Menu);
  SubMenu := CreatePopupMenu;
  FillChar(ItemInfo, SizeOf(ItemInfo), #0);
  ItemInfo.cbSize := SizeOf(ItemInfo);
  ItemInfo.fmask := MIIM_TYPE;
  ItemInfo.fType := MFT_SEPARATOR;
  InsertMenuItem(Menu, ItemCount, True, ItemInfo);

  FillChar(ItemInfo, SizeOf(ItemInfo), #0);
  ItemInfo.cbSize := SizeOf(ItemInfo);
  ItemInfo.fmask := MIIM_SUBMENU or MIIM_TYPE;
  ItemInfo.hSubMenu := SubMenu;
  ItemInfo.dwTypeData := PChar(Caption);
  // Insert the Root Menu Item
  if InsertMenuItem(Menu, ItemCount + 1, True, ItemInfo) then
  begin
    SetLength(Result, PopupMenu.Items.Count);

    for i := PopupMenu.Items.Count - 1 downto 0 do
    begin
      FillChar(ItemInfo, SizeOf(ItemInfo), #0);

      ItemInfo.cbSize := SizeOf(ItemInfo);

      ItemInfo.fmask := MENUMASK;

      if PopupMenu.Items[i].Caption <> '-' then
        ItemInfo.fType := MFT_STRING
      else
        ItemInfo.fType := MFT_SEPARATOR;


      if PopupMenu.Items[i].RadioItem then
        ItemInfo.fType := ItemInfo.fType or MFT_RADIOCHECK;
      if PopupMenu.BiDiMode = bdRightToLeft then
        ItemInfo.fType := ItemInfo.fType or MFT_RIGHTJUSTIFY;
      if PopupMenu.Items[i].Break = mbBreak then
        ItemInfo.fType := ItemInfo.fType or MFT_MENUBREAK;
      if PopupMenu.Items[i].Break = mbBarBreak then
        ItemInfo.fType := ItemInfo.fType or MFT_MENUBARBREAK;

      if PopupMenu.Items[i].Checked then
        ItemInfo.fState := ItemInfo.fState or MFS_CHECKED
      else
        ItemInfo.fState := ItemInfo.fState or MFS_UNCHECKED;
      if PopupMenu.Items[i].Default then
        ItemInfo.fState := ItemInfo.fState or MFS_DEFAULT;
      if PopupMenu.Items[i].Enabled then
        ItemInfo.fState := ItemInfo.fState or MFS_ENABLED
      else
        ItemInfo.fState := ItemInfo.fState or MFS_DISABLED;

      ItemInfo.wID := FindUniqueMenuID(Menu, LastID + 1);
      LastID := ItemInfo.wID;
      Result[i] := ItemInfo.wID;

      // Store the TMenuItem so we can get it later
      ItemInfo.dwItemData := Cardinal( PopupMenu.Items[i]);

      if not( ItemInfo.fType and MFT_SEPARATOR <> 0) then
        ItemInfo.dwTypeData := PChar(PopupMenu.Items[i].Caption);

      InsertMenuItem(SubMenu, 0, True, ItemInfo)
    end
  end
end;

function TNamespace.InternalGetContextMenuInterface(PIDLArray: TRelativePIDLArray): IContextMenu;
var
  Found: Boolean;
begin
  Found := False;
  CurrentContextMenu2 := nil;  // Clear since not sure if it is avaiable yet
  if Assigned(PIDLArray) then
  begin
    if Assigned(ParentShellFolder)  then
    begin
      Found := Succeeded(ParentShellFolder.GetUIObjectOf(ParentWnd,
        Length(PIDLArray), PItemIDList( PIDLArray[0]),
        IID_IContextMenu, nil, Pointer(Result)))
    end;
    if not Found and Assigned(ShellFolder) and (Length(PIDLArray) = 1) then
    begin
      Found := ShellFolder.CreateViewObject(ParentWnd, IID_IContextMenu,
        Pointer(Result)) = NOERROR;
    end;
    if not Found then
      Result := nil
  end else
    Result := nil
end;

function TNamespace.InternalGetDataObjectInterface(PIDLArray: TRelativePIDLArray): IDataObject;
{ Creates an IDataObject using the passed relative PIDLs (actually siblings of }
{ the TNamespace) If a nil is passed for PIDLArray a single object based on    }
{ TNamespace is created.                                                       }
var
  Found: Boolean;
begin
  if not Assigned(PIDLArray) then
  begin
    SetLength(PIDLArray, 1);
    PIDLArray[0] := RelativePIDL
  end;
  Found := False;
  if Assigned(PIDLArray) then
  begin
    if Assigned(ParentShellFolder)  then
    begin
      Found := ParentShellFolder.GetUIObjectOf(ParentWnd,
        Length(PIDLArray), PItemIDList( PIDLArray[0]),
        IDataObject, nil, Pointer(Result)) = NOERROR;
    end;
    if not Found and Assigned(ShellFolder) and (Length(PIDLArray) = 1) then
    begin
      Found := ShellFolder.CreateViewObject(ParentWnd, IDataObject,
        Pointer(Result)) = NOERROR;
    end;
    if not Found then
      Result := nil
  end else
    Result := nil
end;

function TNamespace.InternalShowContextMenu(Owner: TWinControl;
  ContextMenuCmdCallback: TContextMenuCmdCallback;
  ContextMenuShowCallback: TContextMenuShowCallback;
  ContextMenuAfterCmdCallback: TContextMenuAfterCmdCallback;
  PIDLArray: TRelativePIDLArray; Position: PPoint; CustomShellSubMenu: TPopupMenu;
  CustomSubMenuCaption: WideString): Boolean;
// Displays the ContextMenu of the namespace.
const
  MaxVerbLen = 128;
var
  Menu: hMenu;
  InvokeInfo: TCMInvokeCommandInfoEx;
  MenuCmd: Cardinal;
  x, y, i: integer;
  OldErrorMode: integer;
  VerbA: string;
  VerbW: WideString;
  GenericVerb: Pointer;
  Handled, AllowShow: Boolean;
  Flags: Longword;
  ContextMenu: IContextMenu;
  ContextMenu2: IContextMenu2;
  ContextMenu3: IContextMenu3;
  MenuIDs: TMenuItemIDArray;
  ItemInfo: TMenuItemInfo;
  SubMenu: HMenu;
  OldMode: UINT;
begin
  OldMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  try
    MenuIDs := nil;
    Result := False;
    Assert(Assigned(Owner), 'To show a Context Menu using TNamespace you must pass a valid Owner TWinControl');
    if Assigned(Owner) then
    begin
      FOldWndProcForContextMenu := Owner.WindowProc;
      try
        // Hook the owner for the Window message for owner draw menus like
        // Send To..
        Owner.WindowProc := WindowProcForContextMenu;

        if Assigned(PIDLArray) then
        begin
          ContextMenu := nil;
          ContextMenu2 := nil;
          ContextMenu3 := nil;
          Result := False;
          if Assigned(Position) then
          begin
            x := Position.x;
            y := Position.y
          end else
          begin
            x := Mouse.CursorPos.X;  // Snag these fast. The mouse can move a fair amount
            y := Mouse.CursorPos.Y;  // before the popup menu is shown.
          end;
          FillChar(InvokeInfo, SizeOf(InvokeInfo), #0);
          Menu := CreatePopupMenu;
          OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
          try
            { The application must handle a rename, rename makes no sense for more than 1 item }
            if Assigned(ContextMenuCmdCallback) and (Length(PIDLArray) = 1) then
              Flags :=  CMF_CANRENAME or CMF_NORMAL or CMF_EXPLORE
            else
              Flags := CMF_NORMAL or CMF_EXPLORE;

            if GetKeyState(VK_SHIFT) and $8000 <> 0 then
              Flags := Flags or CMF_EXTENDEDVERBS;

            if Assigned(PIDLArray) then
              ContextMenu := InternalGetContextMenuInterface(PIDLArray)
            else
              ContextMenu := ContextMenuInterface;

            CurrentContextMenu := ContextMenu;

            CurrentContextMenu2 := nil;  // not sure it is available yet
            if Assigned(ContextMenu) then
            begin
              if ContextMenu.QueryInterface(IContextMenu3, Pointer(ContextMenu3)) = E_NOINTERFACE then
              begin
                if ContextMenu.QueryInterface(IID_IContextMenu2, Pointer(ContextMenu2)) <> E_NOINTERFACE then
                  CurrentContextMenu2 := ContextMenu2;
              end else
                CurrentContextMenu2 := ContextMenu3;

              if Assigned(ContextMenu3) then
                ContextMenu3.QueryContextMenu(Menu, 0, 1, $7FFF, Flags)
              else
              if Assigned(ContextMenu2) then
                ContextMenu2.QueryContextMenu(Menu, 0, 1, $7FFF, Flags)
              else
              if Assigned(ContextMenu) then
                ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, Flags);

              // Inject our custom menu item
              if Assigned(CustomShellSubMenu) then
                MenuIDs := InjectCustomSubMenu(Menu, CustomSubMenuCaption, CustomShellSubMenu, SubMenu);

              AllowShow := True;
              if Assigned(ContextMenuShowCallback) then
                ContextMenuShowCallback(Self, Menu, AllowShow);

              if AllowShow then
                MenuCmd := Cardinal( TrackPopupMenuEx(
                                     Menu,
                                     TPM_LEFTALIGN or TPM_RETURNCMD or TPM_RIGHTBUTTON,
                                     x, y, Owner.Handle, nil))
              else
                MenuCmd := 0;

              if MenuCmd <> 0 then
              begin
                if IsUnicode then
                begin
                  SetLength(VerbW, MaxVerbLen);
                  GenericVerb := @VerbW[1];
                  Flags := GCS_VERBW
                end else
                begin
                  SetLength(VerbA, MaxVerbLen);
                  GenericVerb := @VerbA[1];
                  Flags := GCS_VERBA
                end;
                if Assigned(ContextMenu3) then
                  Result := Succeeded(ContextMenu3.GetCommandString(MenuCmd-1, Flags, nil, GenericVerb, MaxVerbLen))
                else
                if Assigned(ContextMenu2) then
                  Result := Succeeded(ContextMenu2.GetCommandString(MenuCmd-1, Flags, nil, GenericVerb, MaxVerbLen))
                else
                if Assigned(ContextMenu) then
                  Result := Succeeded(ContextMenu.GetCommandString(MenuCmd-1, Flags, nil, GenericVerb, MaxVerbLen));

                if IsUnicode then
                  SetLength(VerbW, lstrlenW(PWideChar( VerbW)))
                else begin
                  SetLength(VerbA, StrLen(PChar( VerbA)));
                  VerbW := VerbA
                end;

                if not Result then
                  VerbW := STR_UNKNOWNCOMMAN;

                Handled := False;
                for i := 0 to Length(MenuIDs) - 1 do
                begin
                  if MenuCmd = MenuIDs[i] then
                  begin
                    if SubMenu <> 0 then
                    begin
                      Handled := True;
                      FillChar(ItemInfo, SizeOf(ItemInfo), #0);
                      ItemInfo.cbSize := SizeOf(TMenuItemInfo);
                      ItemInfo.fMask := MIIM_DATA;
                      GetMenuItemInfo(SubMenu, i, True, ItemInfo);
                      if ItemInfo.dwItemData <> 0 then
                        TMenuItem(ItemInfo.dwItemData).Click
                    end
                  end
                end;

                if not Handled then
                  if Assigned(ContextMenuCmdCallback) then
                    ContextMenuCmdCallBack(Self, VerbW, MenuCmd, Handled);

                if not Handled then
                begin
                  FillChar(InvokeInfo, SizeOf(InvokeInfo), #0);
                  with InvokeInfo do
                  begin
                    { For some reason the lpVerbW won't work }
                    lpVerb := MakeIntResourceA(MenuCmd-1);
                    if IsUnicode then
                    begin
                      fMask := CMIC_MASK_UNICODE;
                      lpVerbW := MakeIntResourceW(MenuCmd-1)
                    end;
                    // Win95 get confused if size = TCMInvokeCommandInfoEx
                    if IsUnicode then
                      cbSize := SizeOf(TCMInvokeCommandInfoEx)
                    else
                      cbSize := SizeOf(TCMInvokeCommandInfo);

                    hWnd := Owner.Handle;
                    nShow := SW_SHOWNORMAL;
                  end;
                  if Assigned(ContextMenu3) then
                    Result := Succeeded(ContextMenu3.InvokeCommand(InvokeInfo))
                  else
                  if Assigned(ContextMenu2) then
                    Result := Succeeded(ContextMenu2.InvokeCommand(InvokeInfo))
                  else
                  if Assigned(ContextMenu) then
                    Result := Succeeded(ContextMenu.InvokeCommand(InvokeInfo));

                end
              end;
              if Assigned(ContextMenuAfterCmdCallback) then
                ContextMenuAfterCmdCallback(Self, VerbW, MenuCmd, Result);
            end;
          finally
            { Don't access any properties or field of the object.  If the verb is     }
            { 'delete' the component using this class could have freed the instance   }
            { of the object through a ShellNotifyRegister or some other way.          }
            DestroyMenu(Menu);
            SetErrorMode(OldErrorMode);
          end
        end
      finally
        Owner.WindowProc := FOldWndProcForContextMenu;
        FOldWndProcForContextMenu := nil;
        // Don't nil until after the hook is unset
        CurrentContextMenu := nil;
        CurrentContextMenu2 := nil;  // not sure it is available yet
      end
    end
  finally
    SetErrorMode(OldMode)
  end
end;

function TNamespace.OkToBrowse(ShowExplorerMsg: Boolean): Boolean;
var
  S: WideString;
begin
  Result := True;
  S := NameForParsing;
  if ((Length(S) = 3) and (S[2] = ':') and (S[3] = '\')) then
    Result := DiskInDrive(Char(S[1]));
  if not Result and ShowExplorerMsg then
    EnumerateFolderEx(ParentWnd, [foFolders, foNonFolders, foHidden], nil, nil)
end;

function TNamespace.InternalSubItems(Flags: Longword): Boolean;
{ Tests to see if a namespace is a true folder and has at least one             }
{ sub-namespace within it.                                                      }
var
  Enum: IEnumIDList;
  Fetched: Longword;
  Item: PItemIDList;
  OldError: DWORD;
begin
  Result := False;
  OldError := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
  try
    { The recycle bin enumerates slow if it is full.  VT will InitNode for various }
    { reasons eventhough the node is not expanded.  We will always assume there is }
    { is something in the bin.  If not when it is clicked it will clear the "+"    }
    if IsRecycleBin then
      Result := True
    else 
   // if ILIsParent(HistoryFolder.AbsolutePIDL, AbsolutePIDL, False) then
   //   Result := True
   // else
    begin
      if Folder and Assigned(ShellFolder) then
      begin
        Item := nil;
        Enum := nil;
        // Since this is just an internal test don't show any dialogs so send a 0
        if ShellFolder.EnumObjects(0, Flags, Enum) = NOERROR then
        begin
          // Vista Enum is nil every once in a while
          if Assigned(Enum) then
          begin
            Result := Enum.Next(1, Item, Fetched) = NOERROR;
            if Assigned(Item) then
              PIDLMgr.FreePIDL(Item)
          end
        end
      end
    end
   finally
     SetErrorMode(OldError);
   end
end;

procedure TNamespace.InvalidateCache;
{ Forces the class to reload any cached data the next time it is retrieved.     }
begin
  FShellCache.Data.InFolderName := '';
  FShellCache.Data.NormalName := '';
  FShellCache.Data.ParsedName := '';
  FShellCache.Data.SmallIcon := -1;
  FShellCache.Data.SmallOpenIcon := -1;
  FShellCache.Data.OverlayIndex := -1;
  FShellCache.Data.OverlayIconIndex := -1;
  FShellCache.Data.CreationTime := '';
  FShellCache.Data.LastAccessTime := '';
  FShellCache.Data.LastWriteTime := '';
  FShellCache.Data.FileSize := '';
  FShellCache.Data.FileSizeKB := '';
  FShellCache.Data.FileType := '';
  FShellCache.Data.FileSizeInt64 := 0;
  FShellCache.Data.SupportedColumns := 0;
  FShellCache.Data.Attributes := [];
  SetLength(FShellCache.Data.DetailsOfCache, 0);
  FShellCache.ShellCacheFlags := [];
  FreeAndNil(FExtractImage);
  FreeAndNil(FShellLink);
  InvalidateThumbImage
end;

procedure TNamespace.InvalidateNamespace(RefreshIcon: Boolean = True);
var
  Icon1, Icon2: integer;
  Icon1Initialized, Icon2Initialized: Boolean;
begin
  Icon1 := 0;
  Icon2 := 0;
  Icon1Initialized := False;
  Icon2Initialized := False;
  if not RefreshIcon then
  begin
    if scSmallIcon in FShellCache.ShellCacheFlags then
    begin
      Icon1Initialized := True;
      Icon1 := FShellCache.Data.SmallIcon;
    end;
    if scSmallOpenIcon in FShellCache.ShellCacheFlags then
    begin
      Icon2Initialized := True;
      Icon2 := FShellCache.Data.SmallOpenIcon;
    end;
  end else
  begin
    // Flush the thread state so the icon is reloaded by the thread
    States := States - [nsThreadedIconLoading];
    States := States - [nsThreadedIconLoaded];
  end;

  InvalidateDetailsOfCache(True);
  InvalidateCache;
  if nsOwnsParent in States then
    FreeAndNil(FParent);
  FDropTargetInterface := nil;
  FShellDetailsInterface := nil;
  FShellIconOverlayInterface := nil;
  FShellFolder := nil;
  if Assigned(Parent) then
    if Parent.IsDesktop then
      PhysicalDesktopFolder.InvalidateNamespace;
  if IsUnicode then
  begin
    if Assigned(FWin32FindDataW) then
      FreeMem(FWin32FindDataW, SizeOf(TWin32FindDataW));
    FWin32FindDataW := nil;
  end else
  begin
    if Assigned(FWin32FindDataA) then
      FreeMem(FWin32FindDataA, SizeOf(TWin32FindDataA));
    FWin32FindDataA := nil;
  end;
  if Assigned(FSHGetFileInfoRec) then
  begin
    Finalize(FSHGetFileInfoRec^);
    FreeMem(FSHGetFileInfoRec, SizeOf(TSHGetFileInfoRec));
  end;
  FSHGetFileInfoRec := nil;
  Include(FStates, nsShellDetailsSupported);  // Be optomistic
  Include(FStates, nsShellFolder2Supported);  // Be optomistic
  FQueryInfoInterface := nil;
  FShellIconInterface := nil;
  FCurrentContextMenu2 := nil;

  if not RefreshIcon then
  begin
    if Icon1Initialized then
    begin
      Include(FShellCache.ShellCacheFlags, scSmallIcon);
      FShellCache.Data.SmallIcon := Icon1;
    end;
    if Icon2Initialized then
    begin
      Include(FShellCache.ShellCacheFlags, scSmallOpenIcon);
      FShellCache.Data.SmallOpenIcon := Icon2;
    end;
  end;
end;

procedure TNamespace.InvalidateRelativePIDL(FileObjects: TFileObjects);
var
  Enum: IEnumIDList;
  Flags: Longword;
  Fetched: Longword;
  Item: PItemIDList;
  Done: Boolean;
begin
  if Assigned(ParentShellFolder) then
  begin
    Flags := FileObjectsToFlags(FileObjects);
    Done := False;
    if ParentShellFolder.EnumObjects(ParentWnd, Flags, Enum) = NOERROR then
    begin
      // Vista Enum is nil every once in a while
      if Assigned(Enum) then
      begin
        while (Enum.Next(1, Item, Fetched) = NOERROR) and not Done do
        begin
          if ComparePIDL(Item, False) = 0 then
          begin
            PIDLMgr.FreePIDL(FAbsolutePIDL);
            FAbsolutePIDL := PIDLMgr.AppendPIDL(Parent.AbsolutePIDL, Item);
            FRelativePIDL := PIDLMgr.GetPointerToLastID(FAbsolutePIDL);
            InvalidateNamespace;
            Done := True
          end;
          PIDLMgr.FreePIDL(Item)
        end
      end
    end
  end
end;

function TNamespace.IsChildByNamespace(TestNamespace: TNamespace;
  Immediate: Boolean): Boolean;
{ Returns True if the TestNamespace is a child of the namespace.  Immediate     }
{ forces function to be true only of the passed PIDL is the immidiate child     }
{ of the namespace.                                                             }
begin
  Result := Boolean( ILIsParent(FAbsolutePIDL, TestNamespace.FAbsolutePIDL, Immediate));
end;

function TNamespace.IsChildByPIDL(TestPIDL: PItemIDList;
  Immediate: Boolean): Boolean;
{ Returns True if the TestPIDL is a child of the namespace.  Immediate forces   }
{ function to be true only of the passed PIDL is the immidiate child of the     }
{ namespace.                                                                    }
begin
  Result := Boolean( ILIsParent(FAbsolutePIDL, TestPIDL, Immediate));
end;

function TNamespace.IsChildOfRemovableDrive: Boolean;
// Checks to see if the namespace is a child of a removable drive.  If the drive
// is removed then ILIsParent fails because the drive is no longer valid so any
// PIDL walking routines will fail and the PIDL is orphaned
var
  NS: TNamespace;
  PIDL, NewPIDL: PItemIDList;
  OldCB: Word;
begin
  Result := False;
  if PIDLMgr.IDCount(AbsolutePIDL) > 1 then
  begin
    PIDL := PIDLMgr.NextID(FAbsolutePIDL);
    PIDL := PIDLMgr.NextID(PIDL);  // Now we have the Drive
    PIDL := PIDLMgr.NextID(PIDL);  // Now we have the one past the Drive
    OldCb := PIDL.mkid.cb;
    PIDL.mkid.cb := 0;
    NewPIDL := PIDLMgr.CopyPIDL(FAbsolutePIDL);
    PIDL.mkid.cb := OldCB;
    // NS is now a TNamespace to the Drive
    NS := TNamespace.Create(NewPIDL, nil);
    Result := NS.Removable;
    NS.Free
  end
end;

function TNamespace.IsControlPanel: Boolean;
begin
  if Assigned(ControlPanelFolder) then
    Result := ILIsEqual(AbsolutePIDL, ControlPanelFolder.AbsolutePIDL)
  else
    Result := False
end;

function TNamespace.IsControlPanelChildFolder: Boolean;
begin
  if Assigned(ControlPanelFolder) then
    Result := ILIsParent(ControlPanelFolder.AbsolutePIDL, AbsolutePIDL, True)
  else
    Result := False
end;

function TNamespace.IsDesktop: Boolean;
begin
  Result := PIDLMgr.IsDesktopFolder(AbsolutePIDL)
end;

function TNamespace.IsMyComputer: Boolean;
begin
  if Assigned(DrivesFolder) then
    Result := ILIsEqual(DrivesFolder.AbsolutePIDL, AbsolutePIDL)
  else
    Result := False;
end;

function TNamespace.IsNetworkNeighborhood: Boolean;
begin
  if Assigned(NetworkNeighborHoodFolder) then
    Result := ILIsEqual(NetworkNeighborHoodFolder.AbsolutePIDL, AbsolutePIDL)
  else
    Result := False;
end;

function TNamespace.IsNetworkNeighborhoodChild: Boolean;
begin
  if Assigned(NetworkNeighborHoodFolder) then
    Result := ILIsParent(NetworkNeighborHoodFolder.AbsolutePIDL, AbsolutePIDL, False)
  else
    Result := False;
end;

function TNamespace.IsParentByNamespace(TestNamespace: TNamespace;
  Immediate: Boolean): Boolean;
{ Returns True if the TestNamespace is a parent of the namespace.  Immediate    }
{ forces function to be true only of the passed PIDL is the immidiate parent    }
{ of the namespace.                                                             }
begin
  Result := Boolean( ILIsParent(TestNamespace.FAbsolutePIDL, FAbsolutePIDL, Immediate));
end;

function TNamespace.IsParentByPIDL(TestPIDL: PItemIDList;
  Immediate: Boolean): Boolean;
{ Returns True if the TestPIDL is a parent of the namespace.  Immediate forces  }
{ function to be true only of the passed PIDL is the immidiate parent of the    }
{ namespace.                                                                    }
begin
   Result := Boolean( ILIsParent(TestPIDL, FAbsolutePIDL, Immediate));
end;

function TNamespace.IsRecycleBin: Boolean;
begin
  { RecycleBin may not be avaiable if System Administrator has removed it in Win2k at least }
  if Assigned(RecycleBinFolder) and not (nsRecycleBinChecked in States) then
  begin
    if ILIsEqual(AbsolutePIDL, RecycleBinFolder.AbsolutePIDL) then
      Include(FStates, nsIsRecycleBin)
    else
      Exclude(FStates, nsIsRecycleBin);
    Include(FStates, nsRecycleBinChecked);
  end;
  Result := nsIsRecycleBin in States;
end;

function TNamespace.ParseDisplayName: PItemIDList;
begin
  Result := ParseDisplayName(NameForParsing)
end;

function TNamespace.ParseDisplayName(Path: WideString): PItemIDList;
var
  chEaten: ULONG;
  Attrib: ULONG;
  Desktop: IShellFolder;
begin
  Result := nil;
  Attrib := 0;
  SHGetDesktopFolder(Desktop);
  if Assigned(Desktop) then
  begin
    if Desktop.ParseDisplayName(ParentWnd, nil, PWideChar( Path),
      chEaten, Result, Attrib) <> NOERROR
    then
      Result := nil;
  end
end;

function TNamespace.Paste(NamespaceArray: TNamespaceArray; AsShortCut: Boolean = False): Boolean;
var
  NSA: TNamespaceArray;
  i: integer;
begin
  Result := False;
  if CanPasteToAll(NamespaceArray) then
  begin
    if IsDesktop then
    begin
      SetLength(NSA, Length(NamespaceArray));
      // Convert the virtual Desktop based TNamespaces to the Physical Desktop Folder based TNamespaces
      for i := 0 to Length(NSA) - 1 do
        NSA[i] := TNamespace.Create(PathToPIDL(NamespaceArray[i].NameForParsing), nil);
      Result := PhysicalDesktopFolder.Paste(NSA, AsShortCut);
    end else
    begin
      if VerifyPIDLRelationship(NamespaceArray, True) then
      begin
        if AsShortCut then
          Result := ExecuteContextMenuVerb('pastelink', NamespaceToRelativePIDLArray(NamespaceArray))
        else
          Result := ExecuteContextMenuVerb('paste', NamespaceToRelativePIDLArray(NamespaceArray))
      end else
      begin
        if AsShortCut then
          ExecuteContextMenuVerbMultiPath('pastelink', NamespaceArray)
        else
          ExecuteContextMenuVerbMultiPath('paste', NamespaceArray)
      end
    end
  end
end;

procedure TNamespace.LoadCategoryInfo;
var
  ColumnID: TSHColumnID;
  CatGUID: TGUID;
  Buffer: WideString;
  i: Integer;
  Done: Boolean;
begin
  if (CatInfo.CategoryCount = 0) and Assigned(ShellFolder2) and Assigned(CategoryProviderInterface) then
  begin
    CatGUID := GUID_NULL;
    FCatInfo.CategoryCount := DetailsSupportedColumns;
    SetLength(FCatInfo.Categories, DetailsSupportedColumns);
    SetLength(FCatInfo.CategoryNames, DetailsSupportedColumns);
    SetLength(FCatInfo.CatGUID, DetailsSupportedColumns);
    SetLength(FCatInfo.ColumnID, DetailsSupportedColumns);
    SetLength(FCatInfo.CanCatatorize, DetailsSupportedColumns);
    SetLength(Buffer, 256);
    for i := 0 to DetailsSupportedColumns - 1 do
    begin
      FillChar(ColumnID, SizeOf(ColumnID), #0);
      FillChar(PWideChar( Buffer)^, 256 * 2, #0);
      CatGUID := GUID_NULL;
      FCatInfo.Categories[i].Description := '';
      FCatInfo.Categories[i].Collapsed := False;
      FCatInfo.Categories[i].Hidden := False;
      FCatInfo.CategoryNames[i] := '';
      FCatInfo.ColumnID[i].pid := 0;
      FCatInfo.ColumnID[i].fmtid := GUID_NULL;
      FCatInfo.CatGUID[i] := GUID_NULL;
      FCatInfo.CanCatatorize[i] := False;
      FCatInfo.DefaultColumn := -1;
      if Succeeded(ShellFolder2.MapColumnToSCID(i, ColumnID)) then
      begin
        CatInfo.ColumnID[i] := ColumnID;
        if Succeeded(CategoryProviderInterface.CanCategorizeOnSCID(ColumnID)) then
        begin
          ColumnID := CatInfo.ColumnID[i];
          CatInfo.CanCatatorize[i] := True;
          if Succeeded(CategoryProviderInterface.GetCategoryForSCID(ColumnID, CatGUID)) then
          begin
            ColumnID := CatInfo.ColumnID[i];
            CatInfo.CatGUID[i] := CatGUID;
            if not IsEqualGUID(CatGUID, GUID_NULL) then
              if Succeeded(CategoryProviderInterface.GetCategoryName(CatGUID, PWideChar(Buffer), 256)) then
              begin
                FCatInfo.CategoryNames[i] := Buffer;
                SetLength(FCatInfo.CategoryNames[i], lstrlenW(PWideChar(FCatInfo.CategoryNames[i])));
              end
          end
        end
      end
    end;
    CatGUID := GUID_NULL;
    if Succeeded(CategoryProviderInterface.GetDefaultCategory(CatGUID, ColumnID)) then
    begin
      i := 0;
      Done := False;
      while (i < Length(CatInfo.ColumnID)) and not Done do
      begin
        if IsEqualGUID(ColumnID.fmtid, CatInfo.ColumnID[i].fmtid) and (ColumnID.pid = CatInfo.ColumnID[i].pid) then
        begin
          FCatInfo.DefaultColumn := i;
          Done := True
        end;
        Inc(i)
      end
    end
  end
end;

procedure TNamespace.SetDetailByThread(ColumnIndex: Integer; Detail: WideString);
var
  TempCache: PDetailsOfCacheRec;
begin
  EnsureDetailCache;
  if DetailsValidIndex(ColumnIndex) then
  begin
    TempCache := @ShellCache.Data.DetailsOfCache[ColumnIndex];
    TempCache.Caption := Detail;
    Include(TempCache.Cached, docCaptionValid);
    Include(TempCache.Cached, docThreadLoaded);
    Exclude(TempCache.Cached, docThreadLoading);
  end
end;

procedure TNamespace.SetFreePIDLOnDestroy(const Value: Boolean);
begin
  if Value then
    Include(FStates, nsFreePIDLOnDestroy)
  else
    Exclude(FStates, nsFreePIDLOnDestroy)
end;

procedure TNamespace.SetIconIndexChanged(const Value: Boolean);
// Sets or resets if the index changed.  Currently the SetIconIndexByThread method sets
// this flag.  It is not reset automaticlly it is up to the application to reset then
// when it has detected and used it.
begin
  if Value then
    Include(FStates, nsIconIndexChanged)
  else
    Exclude(FStates, nsIconIndexChanged);
end;

procedure TNamespace.SetIconIndexByThread(IconIndex: Integer; ClearThreadLoading: Boolean);
begin
  Include(FStates, nsThreadedIconLoaded); // Small Normal Icon is now Cached
  FShellCache.Data.SmallIcon := IconIndex;
  Include(FShellCache.ShellCacheFlags, scSmallIcon);
  if ClearThreadLoading then
    Exclude(FStates, nsThreadedIconLoading);
  IconIndexChanged := True;
end;

procedure TNamespace.SetImageByThread(Bitmap: TBitmap;
  ClearThreadLoading: Boolean);
begin
  Include(FStates, nsThreadedImageLoaded);
  FImage := Bitmap;
  if ClearThreadLoading then
    Exclude(FStates, nsThreadedImageLoading);
end;

function TNamespace.SetNameOf(NewName: WideString): Boolean;
const
  ALL_FOLDERS = SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN;
var
  P, NewPIDL, NewAbsPIDL: PItemIDList;
  Oldcb: Word;
  OldCursor: TCursor;
begin
  Result := False;
  if CanRename and Assigned(ParentShellFolder) then
  begin
    OldCursor := Screen.Cursor;
    Screen.Cursor := crHourglass;
    try
    { The shell frees the PIDL so we need a copy }
      P := PIDLMgr.CopyPIDL(FRelativePIDL);
      NewPIDL := nil;
      if Succeeded(ParentShellFolder.SetNameOf(ParentWnd, P, PWideChar(NewName), ALL_FOLDERS, NewPIDL)) then
      begin
        // Win98 will return success but never touch NewPIDL when trying to change name
        // of dialup connection.  Not sure how do it if this fails though??
        if Assigned(NewPIDL) then
        begin
          Result := True;
          { Temporary shortening of AbsolutePIDL }
          Oldcb := RelativePIDL.mkid.cb;
          RelativePIDL.mkid.cb := 0;
          NewAbsPIDL := PIDLMgr.AppendPIDL(AbsolutePIDL, NewPIDL);
          RelativePIDL.mkid.cb := Oldcb;
          PIDLMgr.FreePIDL(FAbsolutePIDL); // Remember Relative PIDL overlays AbsPIDL
          FAbsolutePIDL := NewAbsPIDL;
          FRelativePIDL := PIDLMgr.GetPointerToLastID(AbsolutePIDL);
        end
      end
    finally
      Screen.Cursor := OldCursor
    end
  end;
end;

procedure TNamespace.SetThreadedDetailLoaded(ColumnIndex: Integer; Value: Boolean);
begin
  EnsureDetailCache;
  if DetailsValidIndex(ColumnIndex) then
    Include(ShellCache.Data.DetailsOfCache[ColumnIndex].Cached, docThreadLoaded)
end;

procedure TNamespace.SetThreadedDetailLoading(ColumnIndex: Integer; Value: Boolean);
begin
  EnsureDetailCache;
  if DetailsValidIndex(ColumnIndex) then
    Include(ShellCache.Data.DetailsOfCache[ColumnIndex].Cached, docThreadLoading)
end;

procedure TNamespace.SetThreadIconLoading(const Value: Boolean);
begin
  if Value then
    Include(FStates, nsThreadedIconLoading)
  else
    Exclude(FStates, nsThreadedIconLoading)
end;

function TNamespace.GetThreadedImageLoaded: Boolean;
begin
  Result := nsThreadedImageLoaded in States
end;

function TNamespace.GetThreadedImageLoading: Boolean;
begin
  Result := nsThreadedImageLoading in States
end;

procedure TNamespace.SetThreadImageLoading(const Value: Boolean);
begin
  if Value then
    Include(FStates, nsThreadedImageLoading)
  else
    Exclude(FStates, nsThreadedImageLoading)
end;

function TNamespace.ShellExecuteNamespace(WorkingDir, CmdLineArguments: WideString;
  ExecuteFolder: Boolean = False; ExecuteFolderShortCut: Boolean = False;
  RunInThread: Boolean = False): Boolean;
{ Attempts execute the object that the namespace is representing.  WorkingDir   }
{ is the directory that will be the current directory of the application that   }
{ is being executed.  If the directory does not exist the directory where the   }
{ file being executed resides.  CmdLineArguments are any switches or parameters }
{ that can be added to the file being executed.                                 }
{ ExecuteFolder stops the call from being performed if the namespace is a folder}
{ Doing so usually opens an explorer window to Explore the folder.              }
var
  ShellExecuteInfoA: TShellExecuteInfoA;
  ShellExecuteInfoW: TShellExecuteInfoW;
  ShortWorkingDir, ShortCmdLine: string;
  DoExecute: Boolean;
  ShellLink: TVirtualShellLink;
  ShellExecuteThread: TCommonShellExecuteThread;
begin
  Result := False;
  DoExecute := True;

  if not ExecuteFolder then
    DoExecute := not Folder;

  if not ExecuteFolderShortCut and DoExecute then
  begin
    if Link then
    begin
      ShellLink := TVirtualShellLink.Create(nil);
      try
        ShellLink.ReadLink(NameParseAddress);
        DoExecute := not WideDirectoryExists(ShellLink.TargetPath);
      finally
        ShellLink.Free
      end
    end
  end;

  if DoExecute then
  begin
    if Win32Platform = VER_PLATFORM_WIN32_NT then
    begin
      FillChar(ShellExecuteInfoW, SizeOf(TShellExecuteInfoW), #0);
      if WideDirectoryExists(WorkingDir) then
        ShellExecuteInfoW.lpDirectory := PWideChar(WorkingDir)
      else // This should always be a file not a folder so this is ok
        ShellExecuteInfoW.lpDirectory := PWideChar(WideExtractFileDir(NameParseAddress));
        ShellExecuteInfoW.cbSize := SizeOf(TShellExecuteInfoW);
        ShellExecuteInfoW.fMask := SEE_MASK_INVOKEIDLIST or SEE_MASK_NOCLOSEPROCESS;
        if RunInThread then
          ShellExecuteInfoW.fMask := ShellExecuteInfoW.fMask or SEE_MASK_FLAG_DDEWAIT;
        ShellExecuteInfoW.Wnd:= ParentWnd;
        ShellExecuteInfoW.nShow := SW_SHOWNORMAL;
        ShellExecuteInfoW.lpIDList:= AbsolutePIDL;
        ShellExecuteInfoW.lpParameters := PWideChar(CmdLineArguments);
        if RunInThread then
        begin
          ShellExecuteThread := TCommonShellExecuteThread.Create(True);
          ShellExecuteThread.ShellExecuteInfoW.cbSize := ShellExecuteInfoW.cbSize;
          ShellExecuteThread.ShellExecuteInfoW.fMask := ShellExecuteInfoW.fMask;
          ShellExecuteThread.ShellExecuteInfoW.Wnd := ShellExecuteInfoW.Wnd;
          ShellExecuteThread.ShellExecuteInfoW.nShow := ShellExecuteInfoW.nShow;
          ShellExecuteThread.ShellExecuteInfoW.hInstApp := ShellExecuteInfoW.hInstApp;
          ShellExecuteThread.ShellExecuteInfoW.hkeyClass := ShellExecuteInfoW.hkeyClass;
          ShellExecuteThread.ShellExecuteInfoW.dwHotKey := ShellExecuteInfoW.dwHotKey;
          ShellExecuteThread.ShellExecuteInfoW.hIcon := ShellExecuteInfoW.hIcon;
          ShellExecuteThread.ShellExecuteInfoW.hProcess := ShellExecuteInfoW.hProcess;
          ShellExecuteThread.lpDirectory := ShellExecuteInfoW.lpDirectory;
          ShellExecuteThread.lpParameters := ShellExecuteInfoW.lpParameters;
          ShellExecuteThread.PIDL := PIDLMgr.CopyPIDL(ShellExecuteInfoW.lpIDList);
          ShellExecuteThread.Resume;
          Result := True;
        end else
          Result := ShellExecuteExW_MP(@ShellExecuteInfoW);
    end else
    begin
      FillChar(ShellExecuteInfoA, SizeOf(TShellExecuteInfo), #0);
      if WideDirectoryExists(WorkingDir) then
        ShortWorkingDir := WorkingDir
      else
        ShortWorkingDir := ExtractFileDir(NameParseAddress);
      ShellExecuteInfoA.lpDirectory := PChar(ShortWorkingDir);
      ShellExecuteInfoA.cbSize := SizeOf(TShellExecuteInfo);
      ShellExecuteInfoA.fMask := SEE_MASK_INVOKEIDLIST or SEE_MASK_NOCLOSEPROCESS;
      if RunInThread then
        ShellExecuteInfoA.fMask := ShellExecuteInfoA.fMask or SEE_MASK_FLAG_DDEWAIT;
      ShellExecuteInfoA.Wnd:= ParentWnd;
      ShellExecuteInfoA.nShow := SW_SHOWNORMAL;
      ShellExecuteInfoA.lpIDList:= AbsolutePIDL;
      ShortCmdLine := CmdLineArguments;
      ShellExecuteInfoA.lpParameters := PChar(ShortCmdLine);
      if RunInThread then
      begin
        ShellExecuteThread := TCommonShellExecuteThread.Create(True);
        ShellExecuteThread.ShellExecuteInfoA.cbSize := ShellExecuteInfoA.cbSize;
        ShellExecuteThread.ShellExecuteInfoA.fMask := ShellExecuteInfoA.fMask;
        ShellExecuteThread.ShellExecuteInfoA.Wnd := ShellExecuteInfoA.Wnd;
        ShellExecuteThread.ShellExecuteInfoA.nShow := ShellExecuteInfoA.nShow;
        ShellExecuteThread.ShellExecuteInfoA.hInstApp := ShellExecuteInfoA.hInstApp;
        ShellExecuteThread.ShellExecuteInfoA.hkeyClass := ShellExecuteInfoA.hkeyClass;
        ShellExecuteThread.ShellExecuteInfoA.dwHotKey := ShellExecuteInfoA.dwHotKey;
        ShellExecuteThread.ShellExecuteInfoA.hIcon := ShellExecuteInfoA.hIcon;
        ShellExecuteThread.ShellExecuteInfoA.hProcess := ShellExecuteInfoA.hProcess;
        ShellExecuteThread.lpDirectory := ShellExecuteInfoA.lpDirectory;
        ShellExecuteThread.lpParameters := ShellExecuteInfoA.lpParameters;
        ShellExecuteThread.PIDL := PIDLMgr.CopyPIDL(ShellExecuteInfoA.lpIDList);
        ShellExecuteThread.Resume;
        Result := True;
      end else
        Result := ShellExecuteEx(@ShellExecuteInfoA);
    end
  end
end;

function TNamespace.ShowContextMenu(Owner: TWinControl;
  ContextMenuCmdCallback: TContextMenuCmdCallback;
  ContextMenuShowCallback: TContextMenuShowCallback;
  ContextMenuAfterCmdCallback: TContextMenuAfterCmdCallback;
  Position: PPoint = nil;
  CustomShellSubMenu: TPopupMenu = nil;
  CustomSubMenuCaption: WideString = ''): Boolean;
{ Displays the ContextMenu of the namespace.                                    }
var
  PIDLArray: TRelativePIDLArray;
begin
  SetLength(PIDLArray, 1);
  PIDLArray[0] := RelativePIDL;
  Result := InternalShowContextMenu(Owner, ContextMenuCmdCallback, ContextMenuShowCallback,
    ContextMenuAfterCmdCallback, PIDLArray, Position, CustomShellSubMenu, CustomSubMenuCaption);
end;

function TNamespace.ShowContextMenuMulti(Owner: TWinControl;
  ContextMenuCmdCallback: TContextMenuCmdCallback;
  ContextMenuShowCallback: TContextMenuShowCallback;
  ContextMenuAfterCmdCallback: TContextMenuAfterCmdCallback;
  NamespaceArray: TNamespaceArray; Position: PPoint = nil;
  CustomShellSubMenu: TPopupMenu = nil; CustomSubMenuCaption: WideString = ''): Boolean;
begin
  Result := False;
  if VerifyPIDLRelationship(NamespaceArray, True) then
  begin
      Result := InternalShowContextMenu(Owner, ContextMenuCmdCallBack,
        ContextMenuShowCallback, ContextMenuAfterCmdCallback,
        NamespaceToRelativePIDLArray(NamespaceArray), Position, CustomShellSubMenu,
        CustomSubMenuCaption)
  end else
  begin
    if Length(NamespaceArray) > 0 then
      ShowContextMenuMultiPath(Owner, NamespaceArray[0], NamespaceArray, Position)
  end
end;

function TNamespace.ShowContextMenuMultiPath(Owner: TWinControl;
  Focused: TNamespace; Namespaces: TNamespaceArray; Position: PPoint = nil): Boolean;
var
  Menu: TCommonShellMultiParentContextMenu;
  Temp: TWinControl;
begin
  Menu := TCommonShellMultiParentContextMenu.Create(nil);
  Temp := TWinControl.CreateParented(GetDesktopWindow);
  try
    Temp.Width := 0;
    Temp.Height := 0;
    Temp.Visible := True;
    Result := Menu.ShowContextMenu(Owner, Focused, Namespaces, Position);
    Temp.Visible := False;
  finally
    Temp.Free;
    Menu.Free
  end
end;

procedure TNamespace.ShowPropertySheet;
var
  NamespaceArray: TNamespaceArray;
begin
  if HasPropSheet then
  begin
    SetLength(NamespaceArray, 1);
    NamespaceArray[0] := Self;
    if VerifyPIDLRelationship(NamespaceArray, True) then
      ExecuteContextMenuVerb('properties', NamespaceToRelativePIDLArray(NamespaceArray))
    else
      ExecuteContextMenuVerbMultiPath('properties', NamespaceArray)
  end
end;

procedure TNamespace.ShowPropertySheetMulti(NamespaceArray: TNamespaceArray;
  UseSHMultiFileProperties: Boolean = True; ForceNonMultiPath: Boolean = False);
var
  IDO: IDataObject;
begin
  if CanShowPropertiesOfAll(NamespaceArray) then
  begin
    // Call SHMultiFileProperties_MP to show the property sheet when the
    // APIDLArray items are from different folders.
    // Minimum OS: Win2k
    if not ForceNonMultiPath and (UseSHMultiFileProperties and Assigned(SHMultiFileProperties_MP)) then
    begin
      CreateFullyQualifiedShellDataObject(NamespaceToAbsolutePIDLArray(NamespaceArray), IDO);
      if Assigned(IDO) then
        SHMultiFileProperties_MP(IDO, 0);
      IDO._Release // I did this in DefMenuCreateCallback, I don't know why I have to but I do
    end else
    begin
      if VerifyPIDLRelationship(NamespaceArray, True) or ForceNonMultiPath then
        ExecuteContextMenuVerb('properties', NamespaceToRelativePIDLArray(NamespaceArray))
      else
        ExecuteContextMenuVerbMultiPath('properties', NamespaceArray)
    end
  end
end;

function TNamespace.TestAttributesOf(Flags: Longword; FlushCache: Boolean; SoftFlush: Boolean = False): Boolean;
// Pass any of the flags for IShellFolder.GetAttributesOf to see if they exist
// for the Folder. FlushCache forces the shell to reload the information on the
// namespace.  Useful to handle the bug where the shell caches the icon for a
// CD drive and never changes it.  Flushing it will force it to reload the Index
// Note this is dangerous with 3rd party namespaces.  M$ suggests this method for
// their namespaces but at least Hummingbird network namespace crashes with this
// they apparently don't check for 0 PIDL's
// Soft Flush add the SFGAO_VALIDATE flag to get the fresh info
var
  x: Longword;
begin
  if Assigned(ParentShellFolder) then
  begin
    x := Flags;
    if FlushCache then
    begin
      x := x or SFGAO_VALIDATE;
      ParentShellFolder.GetAttributesOf(0, FRelativePIDL, x);
    end else
    if SoftFlush then
    begin
      x := x or SFGAO_VALIDATE;
      ParentShellFolder.GetAttributesOf(1, FRelativePIDL, x)
    end else
      ParentShellFolder.GetAttributesOf(1, FRelativePIDL, x);
    Result := Flags and x = Flags;
  end else
    Result := False;
end;

function TNamespace.VerifyPIDLRelationship(NamespaceArray: TNamespaceArray;
  Silent: Boolean = False): Boolean;
var
  i: integer;
begin
  Result := True;
  i := 0;
  while (i < Length(NamespaceArray)) and Result do
  begin
    { TNamespace is based off using the parent to access the data so it is      }
    { correct to do the test for childPIDLs relative from the parent.           }
    if IsDesktop and (Length(NamespaceArray) = 1) then
    begin
      Result := (PIDLMgr.IDCount(NamespaceArray[i].AbsolutePIDL) = 1) or (NamespaceArray[0].IsDesktop)
    end else
    begin
      if Assigned(Parent) then
        Result := ILIsParent(Parent.AbsolutePIDL, NamespaceArray[i].AbsolutePIDL, True)
      else begin
        if (Length(NamespaceArray) = 1) and NamespaceArray[0].IsDesktop then
          Result := True
        else
          Result := False;
      end
    end;
    Inc(i)
  end;
  if not Silent and (not Result and not IsDesktop) then
    WideShowMessage(ParentWnd, STR_ERROR, STR_ERR_BAD_PIDL_RELATIONSHIP);
end;

procedure TNamespace.WindowProcForContextMenu(var Message: TMessage);
begin
  if Assigned(FOldWndProcForContextMenu) then
    FOldWndProcForContextMenu(Message); // Call the OldWindProc of the ContextMenu owner
  case Message.Msg of
    WM_DRAWITEM, WM_INITMENUPOPUP, WM_MEASUREITEM, WM_MENUCHAR:
      HandleContextMenuMsg(Message.Msg, Message.WParam, Message.LParam, Message.Result);
  end;
end;

function TNamespace.GetCategoryProviderInterface: ICategoryProvider;
begin
  if not Assigned(FCategoryProviderInterface) and Folder and Assigned(ShellFolder) then
  begin
    if not Succeeded(ShellFolder.CreateViewObject(ParentWnd, IID_ICategoryProvider, Pointer(FCategoryProviderInterface))) then
      FCategoryProviderInterface := nil
  end;
  Result := FCategoryProviderInterface
end;

function TNamespace.CreateCategory(GUID: TGUID): ICategorizer;
begin
  Result := nil;
  if Assigned(CategoryProviderInterface) then
  begin
    if not Succeeded(CategoryProviderInterface.CreateCategory(GUID, IID_ICategorizer, Result)) then
      Result := nil;
  end
end;

function TNamespace.EnumFuncDummy(MessageWnd: HWnd; APIDL: PItemIDList; AParent: TNamespace; Data: Pointer; var Terminate: Boolean): Boolean;
begin
  PIDLMgr.FreePIDL(APIDL);
  Result := True;
end;

function TNamespace.GetBrowserFrameOptionsInterface: IBrowserFrameOptions;
var
  Found: Boolean;
begin
  if not Assigned(FBrowserFrameOptionsInterface) then
  begin
    Found := False;
    if Assigned(Parent) then
    begin
       Found := Succeeded(Parent.ShellFolder.GetUIObjectOf(ParentWnd, 1, FRelativePIDL, IBrowserFrameOptions, nil, Pointer(FBrowserFrameOptionsInterface)));
       if not Found and Folder then
       begin
           Found := Succeeded(ShellFolder.CreateViewObject(ParentWnd, IBrowserFrameOptions, Pointer(FBrowserFrameOptionsInterface)));
         if not Found then
           Found := Succeeded(ShellFolder.QueryInterface(IBrowserFrameOptions, Pointer(FBrowserFrameOptionsInterface)))
       end
    end;
    if not Found then
      FBrowserFrameOptionsInterface := nil
  end;
  Result := FBrowserFrameOptionsInterface
end;

function TNamespace.GetQueryAssociationsInterface: IQueryAssociations;
var
  Found: Boolean;
begin
  if not Assigned(FQueryAssociationsInterface) then
  begin
    Found := False;
    if Assigned(Parent) then
    begin
       Found := Succeeded(Parent.ShellFolder.GetUIObjectOf(ParentWnd, 1, FRelativePIDL, IQueryAssociations, nil, Pointer(FQueryAssociationsInterface)));
       if not Found and Folder then
       begin
           Found := Succeeded(ShellFolder.CreateViewObject(ParentWnd, IQueryAssociations, Pointer(FQueryAssociationsInterface)));
         if not Found then
           Found := Succeeded(ShellFolder.QueryInterface(IQueryAssociations, Pointer(FQueryAssociationsInterface)))
       end
    end;
    if not Found then
      FQueryAssociationsInterface := nil
  end;
  Result := FQueryAssociationsInterface
end;

function TNamespace.GetValid: Boolean;
var
  rgfInOut: UINT;
begin
  // Does not work on floppy drives and such
  // password proctected network drives also return false regardless if they have
  // not been logged into yet so return true for those too.
  if (not Removable and (Assigned(ParentShellFolder))) and not PotentialMappedDrive(Self) then
  begin
    rgfInOut := SFGAO_VALIDATE;
    // This returns false on a password protected network folder
    Result := ParentShellFolder.GetAttributesOf(1, FRelativePIDL, rgfInOut) = NOERROR
  end else
    Result := True
end;

procedure TNamespace.ReplacePIDL(NewPIDL: PItemIDList; AParent: TNamespace);
begin
  InvalidateNamespace(True);
  PIDLMgr.FreeAndNilPIDL(FAbsolutePIDL);
  if Assigned(Parent) then
    FAbsolutePIDL := PIDLMgr.AppendPIDL(AParent.FAbsolutePIDL, PIDLMgr.CopyPIDL(NewPIDL))
  else
    FAbsolutePIDL := PIDLMgr.CopyPIDL(NewPIDL);
  FRelativePIDL := PIDLMgr.GetPointerToLastID(FAbsolutePIDL);
end;

{ TExtractImage }

constructor TExtractImage.Create;
begin
  FWidth := 200;
  FHeight := 200;
  FColorDepth := 32;
  FFlags := IEIFLAG_SCREEN;
end;

function TExtractImage.GetImage: TBitmap;

   function BitsToPixelFormat(Bits: Windows.TBITMAP): TPixelFormat;
   begin
     case Bits.bmBitsPixel of
       32: Result := pf32Bit;
       24: Result := pf24Bit;
       16: Result := pf16Bit;
       15: Result := pf15Bit;
       8:  Result := pf8Bit;
       4:  Result := pf4Bit;
       1:  Result := pf1Bit;
     else
       Result := pfDevice
     end
   end;

var
  Bits: HBITMAP;
begin
  Bits := 0;
  Result := nil;
  if Assigned(ExtractImageInterface) then
    if Succeeded(ExtractImageInterface.Extract(Bits)) then
    begin
      // Can't just assign the bitmap handle to the canvas because the bitmap
      // may not be a DIB.  If not then if the bitmap is written to as stream then
      // TBitmap will be created as a DIB when it is read back from the stream and
      // it will be displayed upside down
      Result := TBitmap.Create;
      Result.PixelFormat := pf32Bit; //BitsToPixelFormat(BitInfo);
      Result.Transparent := True;
      Result.Handle := Bits;
    end
end;

function TExtractImage.GetExtractImageInterface2: IExtractImage2;
var
  Found: Boolean;
begin
  if not Assigned(FExtractImage2Interface) then
  begin
    Found := False;
    if Assigned(ExtractImageInterface) then
      Found :=  ExtractImageInterface.QueryInterface(IID_IExtractImage2,
        Pointer(FExtractImage2Interface)) <> E_NOINTERFACE;
    if not Found then
      FExtractImage2Interface := nil
  end;
  Result := FExtractImage2Interface
end;


function TExtractImage.GetExtractImageInterface: IExtractImage;
var
  Found: Boolean;
begin
  if not Assigned(FExtractImageInterface) then
  begin
    Found := False;
    if Assigned(Owner.ParentShellFolder) then
    begin
      Found := Owner.ParentShellFolder.GetUIObjectOf(0, 1, Owner.FRelativePIDL,
        IExtractImage, nil, Pointer(FExtractImageInterface)) = NOERROR;
    end;
    if not Found and Assigned(Owner.ShellFolder) then
    begin
      Found := Owner.ShellFolder.CreateViewObject(0, IExtractImage,
        Pointer(FExtractImageInterface)) = NOERROR;
    end;
    if not Found then
      FExtractImageInterface := nil
  end;
  Result := FExtractImageInterface

end;

function TExtractImage.GetImagePath: WideString;
var
  Size: TSize;
  Buffer: PWideChar;
begin
  if Assigned(ExtractImageInterface) then
  begin
    GetMem(Buffer, MAX_PATH * 4);
    try
      try
        Size.cx := Width;
        Size.cy := Height;
        if ExtractImageInterface.GetLocation(Buffer, MAX_PATH, FPriority, Size,
          ColorDepth, FFlags) = NOERROR then
        begin
          Result := Buffer;
          PathExtracted := True
        end else
          Result := '';
      finally
        FreeMem(Buffer);
      end except
      Result := ''
    end
  end;
end;



{ ----------------------------------------------------------------------------- }
{ Encapsulation of IShellLink                                                   }
{ ----------------------------------------------------------------------------- }

{ TVirtualShellLink }

destructor TVirtualShellLink.Destroy;
begin
  FreeTargetIDList;
  inherited;
end;

procedure TVirtualShellLink.FreeTargetIDList;
var
  Malloc: IMalloc;
  PIDL: PItemIDList;
begin
  if Assigned(TargetIDList) then
  begin
    PIDL := TargetIDLIst;
    TargetIDList := nil;
    SHGetMalloc(Malloc);
    Malloc.Free(PIDL);
  end;
end;

function TVirtualShellLink.GetShellLinkAInterface: IShellLink;
begin
  if not Assigned(FShellLinkA) then
  begin
    if not Succeeded(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
      IShellLinkA, FShellLinkA))
    then
      FShellLinkA := nil;
  end;
  Result := FShellLinkA
end;

function TVirtualShellLink.GetShellLinkWInterface: IShellLinkW;
begin   
  if not Assigned(FShellLinkW) then
  begin
    if not Succeeded(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
      IShellLinkW, FShellLinkW))
    then
      FShellLinkW := nil
  end;
  Result := FShellLinkW
end;

function TVirtualShellLink.ReadLink(LinkFileName: WideString): Boolean;
const
  BUFFERSIZE = 1024;
var
  Success: Boolean;
  S: string;
  PersistFile: IPersistFile;
  pwHotKey: Word;
  Cmd: integer;
  FindData: WIN32_FIND_DATA;
  FindDataW: WIN32_FIND_DATAW;
begin
  Result := False;
  Success := False;
  if Assigned(ShellLinkWInterface) then
  begin
    if CommonSupports(ShellLinkWInterface, IPersistFile, PersistFile) then
    begin
      FFileName := LinkFileName;
      Success := Succeeded(PersistFile.Load(PWideChar(FileName), STGM_READWRITE));
      if Success then
      begin
        Result := True;

        SetLength(FTargetPath, BUFFERSIZE);
        Success := Succeeded(ShellLinkWInterface.GetPath(PWideChar( FTargetPath), MAX_PATH, FindDataW, SLGP_UNCPRIORITY));
        if Success then
          SetLength(FTargetPath, lstrlenW(PWideChar( FTargetPath)));

        SetLength(FArguments, BUFFERSIZE);
        Success := Succeeded(ShellLinkWInterface.GetArguments(PWideChar( FArguments), BUFFERSIZE));
        if Success then
          SetLength(FArguments, lstrlenW(PWideChar( FArguments)));

        SetLength(FDescription, BUFFERSIZE);
        Success := Succeeded(ShellLinkWInterface.GetDescription(PWideChar( FDescription), BUFFERSIZE));
        if Success then
          SetLength(FDescription, lstrlenW(PWideChar( FDescription)));

        SetLength(FWorkingDirectory, BUFFERSIZE);
        Success := Succeeded(ShellLinkWInterface.GetWorkingDirectory(PWideChar( FWorkingDirectory), BUFFERSIZE));
        if Success then
          SetLength(FWorkingDirectory, lstrlenW(PWideChar( FWorkingDirectory)));

        SetLength(FIconLocation, BUFFERSIZE);
        Success := Succeeded(ShellLinkWInterface.GetIconLocation(PWideChar( FIconLocation), BUFFERSIZE, FIconIndex));
        if Success then
          SetLength(FIconLocation, lstrlenW(PWideChar( FIconLocation)));

        FreeTargetIDList;
        ShellLinkWInterface.GetIDList(FTargetIDList);

        Success := Succeeded(ShellLinkWInterface.GetHotKey(pwHotKey));
        if Success then
        begin
          FHotKey := LoByte(pwHotKey);
          FHotKeyModifiers := [];
          if HiByte(pwHotKey) and HOTKEYF_ALT <> 0 then Include(FHotKeyModifiers, hkmAlt);
          if HiByte(pwHotKey) and HOTKEYF_CONTROL <> 0 then Include(FHotKeyModifiers, hkmControl);
          if HiByte(pwHotKey) and HOTKEYF_EXT <> 0 then Include(FHotKeyModifiers, hkmExtendedKey);
          if HiByte(pwHotKey) and HOTKEYF_SHIFT <> 0 then Include(FHotKeyModifiers, hkmShift);
        end;

        Success := Succeeded(ShellLinkWInterface.GetShowCmd(Cmd));
        if Success then
        case Cmd of
          SW_HIDE:            ShowCmd := swHide;
          SW_MAXIMIZE:        ShowCmd := swMaximize;
          SW_MINIMIZE:        ShowCmd := swMinimize;
          SW_RESTORE:         ShowCmd := swRestore;
          SW_SHOW:            ShowCmd := swShow;
          SW_SHOWDEFAULT:     ShowCmd := swShowDefault;
          SW_SHOWMINIMIZED:   ShowCmd := swShowMinimized;
          SW_SHOWMINNOACTIVE: ShowCmd := swShowMinNoActive;
          SW_SHOWNA :         ShowCmd := swShowNA;
          SW_SHOWNOACTIVATE : ShowCmd := swShowNoActive;
          SW_SHOWNORMAL:      ShowCmd := swShowNormal;
        end;
 // Why was that here?  Removed 11.12.02
 //       PersistFile.Save(PWideChar(FileName), True)
      end else
        FFileName := ''
    end
  end;
  if not Success and Assigned(ShellLinkAInterface) then
  begin
    if CommonSupports(ShellLinkAInterface, IPersistFile, PersistFile) then
    begin
      FFileName := LinkFileName;
      Success := Succeeded(PersistFile.Load(PWideChar(FileName), STGM_READWRITE));
      if Success then
      begin
        Result := True;

        SetLength(S, BUFFERSIZE);
        Success := Succeeded(ShellLinkAInterface.GetPath(PChar( S), MAX_PATH, FindData, SLGP_UNCPRIORITY));
        if Success then
        begin
          SetLength(S, lstrlen(PChar( S)));
          FTargetPath := S
        end;

        SetLength(S, BUFFERSIZE);
        Success := Succeeded(ShellLinkAInterface.GetArguments(PChar( S), BUFFERSIZE));
        if Success then
        begin
          SetLength(S, lstrlen(PChar( S)));
          FArguments := S
        end;

        SetLength(S, BUFFERSIZE);
        Success := Succeeded(ShellLinkAInterface.GetDescription(PChar( S), BUFFERSIZE));
        if Success then
        begin
          SetLength(S, lstrlen(PChar( S)));
          FDescription := S
        end;

        SetLength(S, BUFFERSIZE);
        Success := Succeeded(ShellLinkAInterface.GetWorkingDirectory(PChar( S), BUFFERSIZE));
        if Success then
        begin
          SetLength(S, lstrlen(PChar( S)));
          FWorkingDirectory := S
        end;

        SetLength(S, BUFFERSIZE);
        Success := Succeeded(ShellLinkAInterface.GetIconLocation(PChar( S), BUFFERSIZE, FIconIndex));
        if Success then
        begin
          SetLength(S, lstrlen(PChar( S)));
          FIconLocation := S
        end;

        FreeTargetIDList;
        ShellLinkAInterface.GetIDList(FTargetIDList);

        Success := Succeeded(ShellLinkAInterface.GetHotKey(pwHotKey));
        if Success then
        begin
          FHotKey := LoByte(pwHotKey);
          FHotKeyModifiers := [];
          if HiByte(pwHotKey) and HOTKEYF_ALT <> 0 then Include(FHotKeyModifiers, hkmAlt);
          if HiByte(pwHotKey) and HOTKEYF_CONTROL <> 0 then Include(FHotKeyModifiers, hkmControl);
          if HiByte(pwHotKey) and HOTKEYF_EXT <> 0 then Include(FHotKeyModifiers, hkmExtendedKey);
          if HiByte(pwHotKey) and HOTKEYF_SHIFT <> 0 then Include(FHotKeyModifiers, hkmShift);
        end;

        Success := Succeeded(ShellLinkAInterface.GetShowCmd(Cmd));
        if Success then
        case Cmd of
          SW_HIDE:            ShowCmd := swHide;
          SW_MAXIMIZE:        ShowCmd := swMaximize;
          SW_MINIMIZE:        ShowCmd := swMinimize;
          SW_RESTORE:         ShowCmd := swRestore;
          SW_SHOW:            ShowCmd := swShow;
          SW_SHOWDEFAULT:     ShowCmd := swShowDefault;
          SW_SHOWMINIMIZED:   ShowCmd := swShowMinimized;
          SW_SHOWMINNOACTIVE: ShowCmd := swShowMinNoActive;
          SW_SHOWNA :         ShowCmd := swShowNA;
          SW_SHOWNOACTIVATE : ShowCmd := swShowNoActive;
          SW_SHOWNORMAL:      ShowCmd := swShowNormal;
        end;

        PersistFile.Save(PWideChar(FileName), True)
      end else
        FFileName := '';
    end
  end
end;

function TVirtualShellLink.WriteLink(LinkFileName: WideString): Boolean;
var
  S: string;
  PersistFile: IPersistFile;
  pwHotKey, pwHotKeyHi: Word;
  KeyModifier: THotKeyModifiers;
  Cmd: integer;
begin
  Result := False;
  if (TargetPath = '') and not Assigned(TargetIDList) and not SilentWrite then
    WideShowMessage(Application.Handle, STR_NOTARGETDEFINED, STR_ERROR)
  else begin
    if Assigned(ShellLinkWInterface) then
    begin
      if CommonSupports(ShellLinkWInterface, IPersistFile, PersistFile) then
      begin
        FFileName := LinkFileName;
        ShellLinkWInterface.SetPath(PWideChar( FTargetPath));
        ShellLinkWInterface.SetArguments(PWideChar( FArguments));
        ShellLinkWInterface.SetDescription(PWideChar( FDescription));
        ShellLinkWInterface.SetPath(PWideChar( FTargetPath));
        ShellLinkWInterface.SetWorkingDirectory(PWideChar( FWorkingDirectory));
        ShellLinkWInterface.SetIconLocation(PWideChar( FIconLocation), FIconIndex);
        if Assigned(FTargetIDList) then
          ShellLinkWInterface.SetIDList(FTargetIDList);

        pwHotKey := HotKey;
        pwHotKeyHi := 0;
        KeyModifier := HotKeyModifiers;
        if hkmAlt in KeyModifier then pwHotKeyHi := pwHotKeyHi or HOTKEYF_ALT;
        if hkmControl in KeyModifier then pwHotKeyHi := pwHotKeyHi or HOTKEYF_CONTROL;
        if hkmExtendedKey in KeyModifier then pwHotKeyHi := pwHotKeyHi or HOTKEYF_EXT;
        if hkmShift in KeyModifier then pwHotKeyHi := pwHotKeyHi or HOTKEYF_SHIFT;

        pwHotKeyHi := pwHotKeyHi shl 8;     // Make lower 8 bits the upper 8 bits
        pwHotKeyHi := pwHotKeyHi and $FF00;  // Make sure lower 8 bits clear
        pwHotKey := pwHotKey or pwHotKeyHi;
        ShellLinkWInterface.SetHotkey(pwHotKey);

        case ShowCmd of
          swHide:             Cmd := SW_HIDE;
          swMaximize:         Cmd := SW_MAXIMIZE;
          swMinimize:         Cmd := SW_MINIMIZE;
          swRestore:          Cmd := SW_RESTORE;
          swShow:             Cmd := SW_SHOW;
          swShowDefault:      Cmd := SW_SHOWDEFAULT;
          swShowMinimized:    Cmd := SW_SHOWMINIMIZED;
          swShowMinNoActive:  Cmd := SW_SHOWMINNOACTIVE;
          swShowNA:           Cmd := SW_SHOWNA;
          swShowNoActive:     Cmd := SW_SHOWNOACTIVATE;
          swShowNormal:       Cmd := SW_SHOWNORMAL;
        else
          Cmd := SW_SHOWNORMAL
        end;
        ShellLinkWInterface.SetShowCmd(Cmd);

        Result := Succeeded(PersistFile.Save(PWideChar(FileName), True))
      end;
    end;
    if not Result and Assigned(ShellLinkAInterface) then
    begin
      if CommonSupports(ShellLinkAInterface, IPersistFile, PersistFile) then
      begin
        FFileName := LinkFileName;
        S := FTargetPath;
        ShellLinkAInterface.SetPath(PChar( S));
        S := FArguments;
        ShellLinkAInterface.SetArguments(PChar(S));
        S := FDescription;
        ShellLinkAInterface.SetDescription(PChar( S));
        S := FTargetPath;
        ShellLinkAInterface.SetPath(PChar( S));
        S := FWorkingDirectory;
        ShellLinkAInterface.SetWorkingDirectory(PChar( S));
        S := FIconLocation;
        ShellLinkAInterface.SetIconLocation(PChar( S), FIconIndex);

        if Assigned(FTargetIDList) then
          ShellLinkAInterface.SetIDList(FTargetIDList);

        pwHotKey := HotKey;
        pwHotKeyHi := 0;
        KeyModifier := HotKeyModifiers;
        if hkmAlt in KeyModifier then pwHotKeyHi := pwHotKeyHi or HOTKEYF_ALT;
        if hkmControl in KeyModifier then pwHotKeyHi := pwHotKeyHi or HOTKEYF_CONTROL;
        if hkmExtendedKey in KeyModifier then pwHotKeyHi := pwHotKeyHi or HOTKEYF_EXT;
        if hkmShift in KeyModifier then pwHotKeyHi := pwHotKeyHi or HOTKEYF_SHIFT;

        pwHotKeyHi := pwHotKeyHi shl 8;     // Make lower 8 bits the upper 8 bits
        pwHotKeyHi := pwHotKeyHi and $FF00;  // Make sure lower 8 bits clear
        pwHotKey := pwHotKey or pwHotKeyHi;
        ShellLinkAInterface.SetHotkey(pwHotKey);

        case ShowCmd of
          swHide:             Cmd := SW_HIDE;
          swMaximize:         Cmd := SW_MAXIMIZE;
          swMinimize:         Cmd := SW_MINIMIZE;
          swRestore:          Cmd := SW_RESTORE;
          swShow:             Cmd := SW_SHOW;
          swShowDefault:      Cmd := SW_SHOWDEFAULT;
          swShowMinimized:    Cmd := SW_SHOWMINIMIZED;
          swShowMinNoActive:  Cmd := SW_SHOWMINNOACTIVE;
          swShowNA:           Cmd := SW_SHOWNA;
          swShowNoActive:     Cmd := SW_SHOWNOACTIVATE;
          swShowNormal:       Cmd := SW_SHOWNORMAL;
        else
          Cmd := SW_SHOWNORMAL
        end;
        ShellLinkAInterface.SetShowCmd(Cmd);

        Result := Succeeded(PersistFile.Save(PWideChar(FileName), True))
      end
    end
  end
end;

{ ----------------------------------------------------------------------------- }
{ TList that implements basic streaming                                         }
{ ----------------------------------------------------------------------------- }

{ TStreamableList }

constructor TStreamableList.Create;
begin
  FStreamVersion := STREAM_VERSION_DEFAULT
end;

procedure TStreamableList.LoadFromFile(FileName: WideString; Version: integer = 0;
  ReadVerFromStream: Boolean = False);
var
  {$IFDEF TNTSUPPORT}
  FileStream: TTntFileStream;
  {$ELSE}
  FileStream: TFileStream;
  {$ENDIF}
begin
  FileStream := nil;
  try
    {$IFDEF TNTSUPPORT}
    FileStream := TTntFileStream.Create(FileName, fmOpenRead or fmShareExclusive);
    {$ELSE}
    FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareExclusive);
    {$ENDIF}
    LoadFromStream(FileStream);
  finally
    FileStream.Free
  end;
end;

procedure TStreamableList.LoadFromStream(S: TStream; Version: integer;
  ReadVerFromStream: Boolean);
begin
  Clear;  
  if ReadVerFromStream then
    S.ReadBuffer(FStreamVersion, Sizeof(FStreamVersion))
  else
    FStreamVersion := Version;
end;

procedure TStreamableList.SaveToFile(FileName: WideString; Version: integer = 0;
  ReadVerFromStream: Boolean = False);
var
  {$IFDEF TNTSUPPORT}
  FileStream: TTntFileStream;
  {$ELSE}
  FileStream: TFileStream;
  {$ENDIF}

begin
  FileStream := nil;
  try
   {$IFDEF TNTSUPPORT}
   FileStream := TTntFileStream.Create(FileName, fmCreate or fmShareExclusive);
   {$ELSE}
   FileStream := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
   {$ENDIF}
    SaveToStream(FileStream);
  finally
    FileStream.Free
  end;
end;

procedure TStreamableList.SaveToStream(S: TStream; Version: integer;
  WriteVerToStream: Boolean);
begin
  if WriteVerToStream then
    S.WriteBuffer(Version, Sizeof(Version));
  FStreamVersion := Version;
end;


{ ----------------------------------------------------------------------------- }
{ TClass that implements basic streaming                                        }
{ ----------------------------------------------------------------------------- }

{ TStreamableClass }

constructor TStreamableClass.Create;
begin
  FStreamVersion := STREAM_VERSION_DEFAULT
end;

procedure TStreamableClass.LoadFromFile(FileName: WideString; Version: integer = 0; ReadVerFromStream: Boolean = False);
var
  {$IFDEF TNTSUPPORT}
  FileStream: TTntFileStream;
  {$ELSE}
  FileStream: TFileStream;
  {$ENDIF}
begin
  FileStream := nil;
  try
    {$IFDEF TNTSUPPORT}
    FileStream := TTntFileStream.Create(FileName, fmOpenRead or fmShareExclusive);
    {$ELSE}
    FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareExclusive);
    {$ENDIF}
    LoadFromStream(FileStream, Version, ReadVerFromStream);
  finally
    FileStream.Free
  end;
end;

procedure TStreamableClass.LoadFromStream(S: TStream; Version: integer;
  ReadVerFromStream: Boolean);
begin
  if ReadVerFromStream then
    S.ReadBuffer(FStreamVersion, Sizeof(FStreamVersion))
  else
    FStreamVersion := Version;
end;

procedure TStreamableClass.SaveToFile(FileName: WideString; Version: integer = 0; ReadVerFromStream: Boolean = False);
var
  {$IFDEF TNTSUPPORT}
  FileStream: TTntFileStream;
  {$ELSE}
  FileStream: TFileStream;
  {$ENDIF}
begin
  FileStream := nil;
  try
    {$IFDEF TNTSUPPORT}
    FileStream := TTntFileStream.Create(FileName, fmCreate or fmShareExclusive);
    {$ELSE}
    FileStream := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
    {$ENDIF}
    SaveToStream(FileStream, Version, ReadVerFromStream);
  finally
    FileStream.Free
  end;
end;

procedure TStreamableClass.SaveToStream(S: TStream; Version: integer;
  WriteVerToStream: Boolean);
begin
  if WriteVerToStream then
    S.WriteBuffer(Version, Sizeof(Version));
  FStreamVersion := Version;
end;


{ ----------------------------------------------------------------------------- }
{ Class that frees it self when the reference count goes to 0.  Like a com      }
{ object but the compiler does not inc/dec automaticlly                         }
{ ----------------------------------------------------------------------------- }

{ TReferenceCounted }

procedure TReferenceCounted.AddRef;
begin
  InterlockedIncrement(FRefCount)
end;

procedure TReferenceCounted.Release;
begin
  InterlockedDecrement (FRefCount);
  if FRefCount <= 0 then
    Free;
end;


{ ----------------------------------------------------------------------------- }
{ TList that frees it self when the reference count goes to 0.  Like a com      }
{ object but the compiler does not inc/dec automaticlly                         }
{ ----------------------------------------------------------------------------- }

{ TReferenceCountedList }

procedure TReferenceCountedList.AddRef;
begin
  InterlockedIncrement(FRefCount)
end;

procedure TReferenceCountedList.Release;
begin
  InterlockedDecrement (FRefCount);
  if FRefCount <= 0 then
    Free;
end;

{ TShellSortHelper }

function TShellSortHelper.CompareIDSort(SortColumn: integer; NS1,
  NS2: TNamespace): Integer;
begin
  if Assigned(NS1.ParentShellFolder) then
  begin
    Result := NS2.ComparePIDL(NS1.RelativePIDL, False, SortColumn);
    { If we are not sorting the Name column then do a sub-sort on the name if   }
    { the items are equal.                                                      }
    if (SortColumn > 0) and (Result = 0) then
       Result := WideCompareText(NS1.NameInFolder, NS2.NameInFolder)
  end else
    Result := 0;
end;

function TShellSortHelper.DiscriminateFolders(NS1,
  NS2: TNamespace): Integer;
begin
  Result := 0;
  if NS1.Folder xor NS2.Folder then
  begin
    if NS1.Folder and not NS2.Folder then
      Result := -1
    else
    if not NS1.Folder and NS2.Folder then
      Result := 1
  end
end;

function TShellSortHelper.SortFileSize(NS1, NS2: TNamespace): Integer;
begin
  Result := DiscriminateFolders(NS1, NS2);
  if Result = 0 then
  begin
    if NS1.SizeOfFileInt64 > NS2.SizeOfFileInt64 then
      Result := 1
    else
    if NS1.SizeOfFileInt64 < NS2.SizeOfFileInt64 then
      Result := -1
    else
       Result := CompareIDSort(0, NS1, NS2)
  end
end;

function TShellSortHelper.SortFileTime(FT1, FT2: TFileTime; NS1,
  NS2: TNamespace): Integer;
begin
  Result := DiscriminateFolders(NS1, NS2);
  if Result = 0 then
  begin
    Result := CompareFileTime(FT1, FT2);
    if Result = 0 then
       Result := CompareIDSort(0, NS1, NS2)
  end
end;

function TShellSortHelper.SortString(S1, S2: WideString; NS1,
  NS2: TNamespace): Integer;
begin
  Result := DiscriminateFolders(NS1, NS2);
  if Result = 0 then
  begin
    Result := WideCompareText(S1, S2);
    if Result = 0 then
      Result := CompareIDSort(0, NS1, NS2)
  end
end;

function TShellSortHelper.SortType(NS1, NS2: TNamespace): Integer;
begin
  if FileSort = fsFileType then
    Result := SortString(NS1.FileType, NS2.FileType, NS1, NS2)
  else begin
    { Must be  fsFileExtension }
    Result := DiscriminateFolders(NS1, NS2);
    if Result = 0 then
    begin
      if NS1.FileSystem and NS2.FileSystem then
      begin
        Result := SortString(ExtractFileExt(NS1.NameParseAddress), ExtractFileExt(NS2.NameParseAddress), NS1, NS2);
        if Result = 0 then
          CompareIDSort(0, NS1, NS2);  // Secondary sort
      end;
    end
  end
end;

{$IFNDEF COMPILER_5_UP}
{ TObjectList }

function TObjectList.Add(AObject: TObject): Integer;
begin
  Result := inherited Add(AObject);
end;

constructor TObjectList.Create;
begin
  inherited Create;
  FOwnsObjects := True;
end;

constructor TObjectList.Create(AOwnsObjects: Boolean);
begin
  inherited Create;
  FOwnsObjects := AOwnsObjects;
end;

function TObjectList.FindInstanceOf(AClass: TClass; AExact: Boolean;
  AStartAt: Integer): Integer;
var
  I: Integer;
begin
  Result := -1;
  for I := AStartAt to Count - 1 do
    if (AExact and
        (Items[I].ClassType = AClass)) or
       (not AExact and
        Items[I].InheritsFrom(AClass)) then
    begin
      Result := I;
      break;
    end;
end;

function TObjectList.First: TObject;
begin
  Result := TObject(inherited First);
end;

function TObjectList.GetItem(Index: Integer): TObject;
begin
  Result := inherited Items[Index];
end;

function TObjectList.IndexOf(AObject: TObject): Integer;
begin
  Result := inherited IndexOf(AObject);
end;

procedure TObjectList.Insert(Index: Integer; AObject: TObject);
begin
  inherited Insert(Index, AObject);
end;

function TObjectList.Last: TObject;
begin
  Result := TObject(inherited Last);
end;

function TObjectList.Remove(AObject: TObject): Integer;
begin
  Result := inherited Remove(AObject);
end;

procedure TObjectList.SetItem(Index: Integer; AObject: TObject);
begin
  inherited Items[Index] := AObject;
end;
{$ENDIF}

{ TVirtualNamespaceList }

function TVirtualNamespaceList.Add(ANamespace: TNamespace): Integer;
begin
  Result := inherited Add(ANamespace);
end;

procedure TVirtualNamespaceList.FillArray(var NamespaceArray: TNamespaceArray);
var
  I: Integer;
begin
  SetLength(NamespaceArray, Count);
  for I := 0 to Count - 1 do
    NamespaceArray[0] := Items[I];
end;

function TVirtualNamespaceList.GetItems(Index: Integer): TNamespace;
begin
  Result := TNamespace(inherited Items[Index]);
end;

function  TVirtualNamespaceList.IndexOf(ANamespace: TNamespace): Integer;
begin
  Result := inherited IndexOf(ANamespace);
end;

procedure TVirtualNamespaceList.Insert(Index: Integer; ANamespace: TNamespace);
begin
  inherited Insert(Index, ANamespace);
end;

procedure TVirtualNamespaceList.SetItems(Index: Integer; ANamespace: TNamespace);
begin
  inherited Items[Index] := ANamespace;
end;

{ TCommonShellContextMenu }
constructor TCommonShellContextMenu.Create(AOwner: TComponent);
begin
  inherited;
  {$IFDEF TNTSUPPORT}
  KeyStrings := TTntStringList.Create;
  {$ELSE}
  KeyStrings := TStringList.Create;
  {$ENDIF}
  MenuMap := TMenuItemMap.Create;
  KeyStrings.Duplicates := dupIgnore;
  KeyStrings.Sorted := True;
  Stub := CreateStub(Self, @TCommonShellContextMenu.DefMenuCreateCallback);
  FRenameMenuItem := True;
  FPasteMenuItem := True;
end;

destructor TCommonShellContextMenu.Destroy;
begin
  DisposeStub(Stub);
  FreeAndNil(FKeyStrings);
  inherited Destroy;
end;

function TCommonShellContextMenu.BindToObject(pidl: PItemIDList; pbcReserved: Pointer; const riid: TIID; out ppvOut{$IFNDEF COMPILER_5_UP}: Pointer{$ENDIF}): HResult;
begin
  Result := ActiveFolder.BindToObject(pidl, pbcReserved, riid, ppvOut);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('BindToObject - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.BindToStorage(pidl: PItemIDList; pbcReserved: Pointer; const riid: TIID; out ppvObj{$IFNDEF COMPILER_5_UP}: Pointer{$ENDIF}): HResult;
begin
  Result := ActiveFolder.BindToStorage(pidl, pbcReserved, riid, ppvObj);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('BindToStorage - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.CompareIDs(lParam: LPARAM; pidl1, pidl2: PItemIDList): HResult;
begin
  Result := ActiveFolder.CompareIDs(lParam, pidl1, pidl2);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('CompareIDs - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.CreateViewObject(hwndOwner: HWND; const riid: TIID; out ppvOut{$IFNDEF COMPILER_5_UP}: Pointer{$ENDIF}): HResult;
begin
  Result := ActiveFolder.CreateViewObject(hwndOwner, riid, ppvOut);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('CreateViewObject - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.DefMenuCreateCallback(const psf: IShellfolder; wnd: HWND; const pdtObj: IDataObject; uMsg: UINT; WParm: WParam; lParm: LParam): HResult;
var
  DoDefault: Boolean;
  IDO: IDataObject;
  QCMInfo: PQCMINFO;
  DFMICS: PDFMICS;
  MapCount, i: Integer;
  MergeOffset: UINT;
  {$IFDEF TNTSUPPORT}
  MenuItem: TTntMenuItem;
  {$ELSE}
  MenuItem: TMenuItem;
  {$ENDIF}
begin
  Result := E_NOTIMPL;
  DoDefault := True;
  case uMsg of
    DFM_MERGECONTEXTMENU:
      begin
        if cmeShellDefault in Extensions then
          Result := S_OK
        else
          Result := S_FALSE;
        QCMInfo := PQCMINFO( lParm);
        // This seems to be broken.  The items added "normally" (using idCmdFirst as is)
        // don't get registered and send a 0 when selected. DFM_MERGECONTEXTMENUTOP works
        // but the return in DFM_INVOKECOMMAND is the OFFSET of the idCmdFirst passed
        // so we have to make sure that the offset does not overlap the idCmdFirst we
        // use here so choose a large enough number.   If we increase idCmdFirst from
        // its passed value it seems to make it work and in DFM_INVOKECOMMAND we get that
        // the (Value - 1) rather then then Offset.
        MapCount := MenuMap.Count;
        MergeOffset := QCMInfo^.idCmdFirst;
        QCMInfo^.idCmdFirst := QCMInfo^.idCmdFirst + 200; // Allow up to 200 items to be added in DFM_MERGECONTEXTMENUTOP;
        DoMenuMerge(QCMInfo^.Menu, QCMInfo^.IndexMenu, QCMInfo^.idCmdFirst, QCMInfo^.idCmdLast, CFM_FLAGSToShellContextMenuFlags(WParm));
        // Fix the MenuIDs to be commonly offset once all merging is done
        if MenuMap.Count > MapCount then
          for i := MapCount to MenuMap.Count - 1 do
            MenuMap[i].MenuID := MenuMap[i].MenuID - MergeOffset;
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_MERGECONTEXTMENU');
        {$ENDIF}
      end;
    DFM_INVOKECOMMAND:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_INVOKECOMMAND');
        {$ENDIF}
        case Cardinal( wParm) of
          DFM_CMD_DELETE:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_DELETE');
              {$ENDIF}
              DoDelete(psf, pdtObj, DoDefault);
            end;
          DFM_CMD_CUT:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_CUT');
              {$ENDIF}
              DoCut(psf, pdtObj, DoDefault)
            end;
          DFM_CMD_COPY:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_COPY');
              {$ENDIF}
              DoCopy(psf, pdtObj, DoDefault)
            end;
          DFM_CMD_CREATESHORTCUT:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_CREATESHORTCUT');
              {$ENDIF}
              DoCreateShortCut(psf, pdtObj, DoDefault)
            end;
          DFM_CMD_PROPERTIES:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_PROPERTIES');
              {$ENDIF}
              DoProperties(psf, pdtObj, DoDefault);
             if DoDefault and not IsWinVistaOrUp then
              begin
                if Assigned(SHMultiFileProperties_MP) then
                begin
                  CreateFullyQualifiedShellDataObject(NamespaceToAbsolutePIDLArray(LocalNamespaces), IDO);
                  if Assigned(IDO) then
                    SHMultiFileProperties_MP(IDO, 0);
                  // Not sure why I have to do this but I have to in order for the counting to work right..
                  IDO._Release;
                end else
                  DesktopFolder.ShowPropertySheetMulti(LocalNamespaces, False, True);
                DoDefault := False
              end
            end;
          DFM_CMD_NEWFOLDER:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_NEWFOLDER');
              {$ENDIF}
              DoNewFolder(psf, pdtObj, DoDefault)
            end;
          DFM_CMD_PASTE:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_PASTE');
              {$ENDIF}
              DoPaste(psf, pdtObj, DoDefault)
            end;
          DFM_CMD_VIEWLIST:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_VIEWLIST');
              {$ENDIF}
            end;
          DFM_CMD_VIEWDETAILS:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_VIEWDETAILS');
              {$ENDIF}
            end;
          DFM_CMD_PASTELINK:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_PASTELINK');
              {$ENDIF}
              DoPasteLink(psf, pdtObj, DoDefault)
            end;
          DFM_CMD_PASTESPECIAL:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_PasteShortCut');
              {$ENDIF}
              DoPasteShortCut(psf, pdtObj, DoDefault)
            end;
          DFM_CMD_MODALPROP:
            begin
              {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
              SendDebug('DFM_INVOKECOMMAND: DFM_CMD_MODALPROP');
              {$ENDIF}
            end;
        end;
        if DoDefault then
          Result := S_FALSE
        else
          Result := S_OK
      end;
    DFM_CREATE:
      begin
        DoShow;
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_CREATE');
        {$ENDIF}
      end;
    DFM_DESTROY:
      begin
        DoHide;
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_DESTROY');
        {$ENDIF}
      end;
    DFM_GETHELPTEXTA:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_GETHELPTEXTA');
        {$ENDIF}
      end;
    DFM_MEASUREITEM:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_MEASUREITEM');
        {$ENDIF}
      end;
    DFM_DRAWITEM:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_DRAWITEM');
        {$ENDIF}
      end;
    DFM_INITMENUPOPUP:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_INITMENUPOPUP');
        {$ENDIF}
      end;
    DFM_VALIDATECMD:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_VALIDATECMD');
        {$ENDIF}
      end;
    DFM_MERGECONTEXTMENU_TOP:
      begin
       if cmeShellDefault in Extensions then
          Result := S_OK
        else
          Result := S_FALSE; 
        QCMInfo := PQCMINFO( lParm);
        // This seems to be broken.  The items added "normally" (using idCmdFirst as is)
        // don't get registered and send a 0 when selected. DFM_MERGECONTEXTMENUTOP works
        // but the return in DFM_INVOKECOMMAND is the OFFSET of the idCmdFirst passed
        // so we have to make sure that the offset does not overlap the idCmdFirst we
        // use here so choose a large enough number.   If we increase idCmdFirst from
        // its passed value it seems to make it work and in DFM_INVOKECOMMAND we get that
        // the (Value - 1) rather then then Offset.
        MapCount := MenuMap.Count;
        MergeOffset := QCMInfo^.idCmdFirst;
        QCMInfo^.idCmdFirst := QCMInfo^.idCmdFirst + 600; // Allow up to 200 items to be added in DFM_MERGECONTEXTMENU;
        DoMenuMergeTop(QCMInfo^.Menu, QCMInfo^.IndexMenu, QCMInfo^.idCmdFirst, QCMInfo^.idCmdLast, CFM_FLAGSToShellContextMenuFlags(WParm));
        DoMenuMergeBottom(QCMInfo^.Menu, GetMenuItemCount(QCMInfo^.Menu), QCMInfo^.idCmdFirst, QCMInfo^.idCmdLast, CFM_FLAGSToShellContextMenuFlags(WParm));
        // Fix the MenuIDs to be commonly offset once all merging is done
        if MenuMap.Count > MapCount then
          for i := MapCount to MenuMap.Count - 1 do
            MenuMap[i].MenuID := MenuMap[i].MenuID - MergeOffset;
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_MERGECONTEXTMENU_TOP');
        {$ENDIF}
      end;
    DFM_MERGECONTEXTMENU_BOTTOM:
      begin
      // Only works in XP and up.  Faked in DFM_MERGECONTEXTMENU_TOP
       if cmeShellDefault in Extensions then
          Result := S_OK
        else
          Result := S_FALSE;
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_MERGECONTEXTMENU_BOTTOM');
        {$ENDIF}
      end;
    DFM_GETHELPTEXTW:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_GETHELPTEXTW');
        {$ENDIF}
      end;
    DFM_INVOKECOMMANDEX:
      begin
        DFMICS := PDFMICS( lparm);
        if FindCommandId(wParm, MenuItem) then
          DoInvokeCommand(MenuItem, DFMICS^.pici);
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_INVOKECOMMANDEX');
        {$ENDIF}
      end;
    DFM_MAPCOMMANDNAME:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_MAPCOMMANDNAME');
        {$ENDIF}
      end;
    DFM_GETDEFSTATICID:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_GETDEFSTATICID');
        {$ENDIF}
      end;
    DFM_GETVERBW:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_GETVERBW');
        {$ENDIF}
      end;
    DFM_GETVERBA:
      begin
        {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
         SendDebug('DFM_GETVERBA');
        {$ENDIF}
      end;
  else
    {$IFDEF GXDEBUG_DEFMENUCREATE_CALLBACK}
    SendDebug('Undefined: ' + IntToStr(UMsg));
    {$ENDIF}
  end;
end;

function TCommonShellContextMenu.DragEnter(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult;
begin
 Result := S_OK;
 {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('DragEnter - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.DragLeave: HResult;
begin
  Result := S_OK;
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('DragLeave - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult;
begin
  Result := E_NOTIMPL;
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('DragOver - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult;
begin
  Result := E_NOTIMPL;
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('Drop - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.DuplicateKey(Key: HKEY): HKEY;
begin
  if Assigned(RegOpenKeyExW_MP) then
    RegOpenKeyExW_MP(Key, '', 0, MAXIMUM_ALLOWED, Result)
  else
    RegOpenKeyExA(Key, '', 0, MAXIMUM_ALLOWED, Result)
end;

function TCommonShellContextMenu.EnumObjects(hwndOwner: HWND; grfFlags: DWORD; out EnumIDList: IEnumIDList): HResult;
begin
  Result := ActiveFolder.EnumObjects(hwndOwner, grfFlags, EnumIDList);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('EnumObjects - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

{$IFDEF TNTSUPPORT}
function TCommonShellContextMenu.FindCommandId(CmdID: UINT; var MenuItem: TTntMenuItem): Boolean;
var
  i: Integer;
begin
  Result := False;
  MenuItem := nil;
  i := 0;
  while not Result and (i < MenuMap.Count) do
  begin
    Result := MenuMap[i].MenuID = CmdID;
    if Result then
      MenuItem := MenuMap[i].Item
    else
      Inc(i)
  end
end;
{$ELSE}
function TCommonShellContextMenu.FindCommandId(CmdID: UINT; var MenuItem: TMenuItem): Boolean;
var
  i: Integer;
begin
  Result := False;
  MenuItem := nil;
  i := 0;
  while not Result and (i < MenuMap.Count) do
  begin
    Result := MenuMap[i].MenuID = CmdID;
    if Result then
      MenuItem := MenuMap[i].Item
    else
      Inc(i)
  end
end;
{$ENDIF}

function TCommonShellContextMenu.GetAttributesOf(cidl: UINT; var apidl: PItemIDList; var rgfInOut: UINT): HResult;
var
  RequestedFlags: UINT;
begin
  RequestedFlags := rgfInOut;
  Result := ActiveFolder.GetAttributesOf(cidl, apidl, rgfInOut);
  if RequestedFlags and SFGAO_CANCOPY <> 0 then
    if CopyValidated then
      rgfInOut := rgfInOut or SFGAO_CANCOPY;
  if RequestedFlags and SFGAO_CANMOVE <> 0 then
    if CutValidated then
      rgfInOut := rgfInOut or SFGAO_CANMOVE;

end;

function TCommonShellContextMenu.GetDisplayNameOf(pidl: PItemIDList; uFlags: DWORD; var lpName: TStrRet): HResult;
begin
  Result := ActiveFolder.GetDisplayNameOf(pidl, uFlags, lpName);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('GetDisplayNameOf - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.GetUIObjectOf(hwndOwner: HWND; cidl: UINT; var apidl: PItemIDList; const riid: TIID; prgfInOut: Pointer; out ppvOut{$IFNDEF COMPILER_5_UP}: Pointer{$ENDIF}): HResult;
var
  DataObject: IDataObject;
begin
  Result := E_NOINTERFACE;
  if FromDesktop then
  begin
    // Need to create a correctly defined fully qualified HDROP in the DataObject
    if IsEqualGUID(riid, IDataObject) then
    begin
      DataObject := nil;
      CreateFullyQualifiedShellDataObject(ActivePIDLs, DataObject);
      IDataObject( ppvOut) := DataObject;
      UIObjectOfDataObject := IDataObject( ppvOut);
      if Assigned(IDataObject( ppvOut)) then
        Result := S_OK
    end else
    if IsEqualGUID(riid, IDropTarget) then
    begin
      if  PasteMenuItem then
      begin
        // Need to support this to get a Paste menu item
        IDropTarget( ppvOut) := Self;
        UIObjectOfDropTarget := IDropTarget( ppvOut);
        Result := S_OK
      end
    end else
      Result := ActiveFolder.GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, ppvOut)
  end else
    Result := ActiveFolder.GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, ppvOut);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('GetUIObjectOf - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.ParseDisplayName(hwndOwner: HWND; pbcReserved: Pointer; lpszDisplayName: POLESTR; out pchEaten: ULONG; out ppidl: PItemIDList; var dwAttributes: ULONG): HResult;
begin
  Result := ActiveFolder.ParseDisplayName(hwndOwner, pbcReserved, lpszDisplayName, pchEaten, ppidl, dwAttributes);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('ParseDisplayName - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.SetNameOf(hwndOwner: HWND; pidl: PItemIDList; lpszName: POLEStr; uFlags: DWORD; var ppidlOut: PItemIDList): HResult;
begin
  Result := ActiveFolder.SetNameOf(hwndOwner, pidl, lpszName, uFlags, ppidlOut);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('SetNameOf - Result = ' + InttoStr(Result));
  {$ENDIF}
end;

function TCommonShellContextMenu.InternalShowContextMenu(Owner: TWinControl; ParentPIDL: PItemIDList; ChildPIDLs: TAbsolutePIDLArray; Verb: WideString; Position: PPoint = nil): Boolean;
//
// If ParentPIDL is nil the assumption is that the Parent is the
// Desktop and the PIDLs are fully qualified PIDLs.  If ParentPIDL is assigned then
// the PIDLs MUST be immediate children of the ParentPIDL
//
const
  MaxVerbLen = 128;
type
  THKeyArray = array of HKey;

  procedure AddKey(Key: HKey; var KeyArray: THKeyArray);
  begin
    SetLength(KeyArray, Length(KeyArray) + 1);
    KeyArray[Length(KeyArray)-1] := Key
  end;

var
  Menu: hMenu;
  InvokeInfo: TCMInvokeCommandInfoEx;
  MenuCmd: Cardinal;
  x, y, i: integer;
  VerbA: string;
  VerbW: WideString;
  GenericVerb: Pointer;
  Handled, AllowShow, Success: Boolean;
  Flags: Longword;
  ContextMenu: IContextMenu;
  ContextMenu2: IContextMenu2;
  ContextMenu3: IContextMenu3;
  OldMode: UINT;
  Keys: THKeyArray;
  Desktop: IShellFolder;
  DesktopPIDL, ChildrenPIDLs: PItemIDList;
  {$IFDEF TNTSUPPORT}
  Reg: TTntRegistry;
  {$ELSE}
  Reg: TRegistry;
  {$ENDIF}
  WS, CurVer: WideString;
  UnknownAdded: Boolean;
begin
  Result := False;
  DesktopPIDL := nil;
  Desktop := nil;
  begin
  OldMode := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
  try
    try
      Result := False;
      Assert(Assigned(Owner), 'To show a Context Menu using TNamespace you must pass a valid Owner TWinControl');
      if Assigned(Owner) then
      begin
        if Assigned(CDefFolderMenu_Create2_MP) then
        begin
          FOldWndProcForContextMenu := Owner.WindowProc;
          try
            // Hook the owner for the Window message for owner draw menus like
            // Send To..
            Owner.WindowProc := WindowProcForContextMenu;

            ContextMenu := nil;
            ContextMenu2 := nil;
            ContextMenu3 := nil;
            Result := False;
            if Assigned(Position) then
            begin
              x := Position.x;
              y := Position.y
            end else
            begin
              x := Mouse.CursorPos.X;  // Snag these fast. The mouse can move a fair amount
              y := Mouse.CursorPos.Y;  // before the popup menu is shown.
            end;
            FillChar(InvokeInfo, SizeOf(InvokeInfo), #0);
            Menu := CreatePopupMenu;
            try
              Flags := CMF_NORMAL or CMF_EXPLORE;
              if RenameMenuItem then
                 Flags := Flags or CMF_CANRENAME;

              if GetKeyState(VK_SHIFT) and $8000 <> 0 then
                Flags := Flags or CMF_EXTENDEDVERBS;  

              if cmeDirectory in Extensions then
                AddMenuKey('Directory');
              if cmeDirBackground in Extensions then
                AddMenuKey('Directory\Background');
                if cmeFolder in Extensions then
                AddMenuKey('Folder');
              if cmeAsterik in Extensions then
                AddMenuKey('*');
              if cmeAllFilesystemObjects in Extensions then
                AddMenuKey('AllFilesystemObjects');


              // Need to rework this to do it right
              // 1) look at the extension under HKCR, read default value
              // 2) open key pointed to by above default value (file class)
              // 3) see if file class has a "CurVer" key
              // 4) if so open that key as the current version of the file class
              // 5) open HKCR/SystemFileAssociations for the file extension
              // 6) if 1 was unsuccessful (no association) use the "Unknown" key
              // 7) see if the extension or file class key have \PerceivedType key
              // 8) if so look in HKCR/SystemFileAssociations for a key of the perceived type from above
              //
              //  Aug 25th - If I just pass the ext OR the file class then the Merge Extended items works ("Open" for instance)

              UnknownAdded := False;
              {$IFDEF TNTSUPPORT}
              Reg := TTntRegistry.Create;
              {$ELSE}
              Reg := TRegistry.Create;
              {$ENDIF}
              try
                Reg.RootKey := HKEY_CLASSES_ROOT;
                for i := 0 to KeyStrings.Count - 1 do
                begin
                  if (KeyStrings[i] <> '') then
                  begin
                    if Reg.OpenKeyReadOnly(KeyStrings[i]) then
                    begin
                      // The extension key is open {.ext}
                      AddKey(DuplicateKey(Reg.CurrentKey), Keys);
                      WS := Reg.ReadString('');
                      Reg.CloseKey;
                      if WS <> '' then
                      begin
                        if Reg.OpenKeyReadOnly(WS) then
                        begin
                          // The FileClass is open, see if there is mulitiple version and a current version
                          if Reg.KeyExists('CurVer') then
                          begin
                            if Reg.OpenKeyReadOnly('CurVer') then
                            begin
                              CurVer := Reg.ReadString('');
                              if Reg.OpenKeyReadOnly('\' + CurVer) then
                                AddKey(DuplicateKey(Reg.CurrentKey), Keys);
                            end;
                            Reg.CloseKey;
                          end else
                          begin
                            AddKey(DuplicateKey(Reg.CurrentKey), Keys);
                            Reg.CloseKey;
                          end
                        end
                      end
                    end else
                    begin
                      if not UnknownAdded and Reg.OpenKeyReadOnly('Unknown') then
                      begin
                        AddKey(DuplicateKey(Reg.CurrentKey), Keys);
                        Reg.CloseKey;
                        UnknownAdded := True
                      end
                    end;

                    if cmeFileSystemAssociations in Extensions then
                    begin
                      if Reg.OpenKeyReadOnly('SystemFileAssociations\' + KeyStrings[i]) then
                      begin
                        AddKey(DuplicateKey(Reg.CurrentKey), Keys);
                        Reg.CloseKey;
                      end
                    end;

                    if cmePerceivedType in Extensions then
                    begin
                      if Reg.OpenKeyReadOnly(KeyStrings[i]) then
                      begin
                        WS := Reg.ReadString('PerceivedType');
                        if WS <> '' then
                        begin
                          if Reg.OpenKeyReadOnly('\SystemFileAssociations\' + WS) then
                            AddKey(DuplicateKey(Reg.CurrentKey), Keys);
                        end;
                        Reg.CloseKey;
                      end
                    end
                  end
                end
              finally
                Reg.CloseKey;
                Reg.Free
              end;

              Success := False;
              ActivePIDLs := ChildPIDLs;
              if not Assigned(ParentPIDL) and Assigned(ChildPIDLs) then
              begin
                FromDesktop := True;
                SHGetDesktopFolder(FActiveFolder);
                SHGetSpecialFolderLocation(0, CSIDL_DESKTOP, DesktopPIDL);
                Success := CDefFolderMenu_Create2_MP(DesktopPIDL, Owner.Handle,
                  Length(ChildPIDLs), PItemIDList(ChildPIDLs[0]), Self,
                  Stub, Length(Keys), PHKey(@Keys[0]), ContextMenu) = S_OK;
              end else
              begin
                FromDesktop := False;
                SHGetDesktopFolder(Desktop);
                if PIDLMgr.IsDesktopFolder(ParentPIDL) then
                  FActiveFolder := Desktop
                else
                  Desktop.BindToObject(ParentPIDL, nil, IShellFolder, Pointer( FActiveFolder));

                  if Assigned(ActiveFolder) then
                  begin
                    if Assigned(ParentPIDL) and Assigned(ChildPIDLs) then
                      Success := CDefFolderMenu_Create2_MP(ParentPIDL, Owner.Handle,
                        Length(ChildPIDLs), PItemIDList(ChildPIDLs[0]),
                        Self, Stub, Length(Keys), PHKey(@Keys[0]), ContextMenu) = S_OK
                    else begin
                      // Must be a background menu call
                      ChildrenPIDLs := nil;
                      if Assigned(ParentPIDL) and not Assigned(ChildPIDLs) then
                        Success := CDefFolderMenu_Create2_MP(ParentPIDL, Owner.Handle,
                          0, ChildrenPIDLs, Self, Stub, Length(Keys),
                          PHKey(@Keys[0]), ContextMenu) = S_OK
                    end
                  end
              end;

              if Success then
              begin

                CurrentContextMenu := ContextMenu;

                CurrentContextMenu2 := nil;  // not sure it is available yet
                if Assigned(ContextMenu) then
                begin
                  if ContextMenu.QueryInterface(IContextMenu3, Pointer(ContextMenu3)) = E_NOINTERFACE then
                  begin
                    if ContextMenu.QueryInterface(IID_IContextMenu2, Pointer(ContextMenu2)) <> E_NOINTERFACE then
                      CurrentContextMenu2 := ContextMenu2;
                  end else
                    CurrentContextMenu2 := ContextMenu3;      

                  if Assigned(ContextMenu3) then
                    ContextMenu3.QueryContextMenu(Menu, 0, 1, $7FFF, Flags)
                  else
                  if Assigned(ContextMenu2) then
                    ContextMenu2.QueryContextMenu(Menu, 0, 1, $7FFF, Flags)
                  else
                  if Assigned(ContextMenu) then
                    ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, Flags);

                  // May just be executing a verb
                  AllowShow := Verb = '';

                  if AllowShow then
                    MenuCmd := Cardinal( TrackPopupMenuEx(
                                         Menu,
                                         TPM_LEFTALIGN or TPM_RETURNCMD or TPM_RIGHTBUTTON,
                                         x, y, Owner.Handle, nil))
                  else
                    MenuCmd := 0;

                  if (MenuCmd <> 0) or (Verb <> '') then
                  begin
                    if (MenuCmd <> 0) then
                    begin
                      if IsUnicode then
                      begin
                        SetLength(VerbW, MaxVerbLen);
                        GenericVerb := @VerbW[1];
                        Flags := GCS_VERBW
                      end else
                      begin
                        SetLength(VerbA, MaxVerbLen);
                        GenericVerb := @VerbA[1];
                        Flags := GCS_VERBA
                      end;
                      if Assigned(ContextMenu3) then
                        Result := Succeeded(ContextMenu3.GetCommandString(MenuCmd-1, Flags, nil, GenericVerb, MaxVerbLen))
                      else
                      if Assigned(ContextMenu2) then
                        Result := Succeeded(ContextMenu2.GetCommandString(MenuCmd-1, Flags, nil, GenericVerb, MaxVerbLen))
                      else
                      if Assigned(ContextMenu) then
                        Result := Succeeded(ContextMenu.GetCommandString(MenuCmd-1, Flags, nil, GenericVerb, MaxVerbLen));

                      if IsUnicode then
                        SetLength(VerbW, lstrlenW(PWideChar( VerbW)))
                      else begin
                        SetLength(VerbA, StrLen(PChar( VerbA)));
                        VerbW := VerbA
                      end;
                    end else
                    begin
                      VerbW := Verb;
                      VerbA := Verb;
                      Result := True;
                    end;

                    if not Result then
                      VerbW := STR_UNKNOWNCOMMAN;

                    Handled := False;
                    if not Handled then
                    begin
                      FillChar(InvokeInfo, SizeOf(InvokeInfo), #0);
                      with InvokeInfo do
                      begin
                        if MenuCmd > 0 then
                        begin
                          { For some reason the lpVerbW won't work }
                          lpVerb := MakeIntResourceA(MenuCmd-1);
                          if IsUnicode then
                          begin
                            fMask := CMIC_MASK_UNICODE;
                            lpVerbW := MakeIntResourceW(MenuCmd-1);
                          end;
                        end else
                        begin
                          if IsUnicode then
                          begin
                            fMask := CMIC_MASK_UNICODE;
                            lpVerbW := PWideChar( VerbW);
                            lpVerb := PChar( VerbA)
                          end else
                            lpVerb := PChar( VerbA)
                        end;
                        // Win95 get confused if size = TCMInvokeCommandInfoEx
                        if IsUnicode then
                          cbSize := SizeOf(TCMInvokeCommandInfoEx)
                        else
                          cbSize := SizeOf(TCMInvokeCommandInfo);

                        hWnd := Owner.Handle;
                        nShow := SW_SHOWNORMAL;
                      end;
                      if Assigned(ContextMenu3) then
                        Result := Succeeded(ContextMenu3.InvokeCommand(InvokeInfo))
                      else
                      if Assigned(ContextMenu2) then
                        Result := Succeeded(ContextMenu2.InvokeCommand(InvokeInfo))
                      else
                      if Assigned(ContextMenu) then
                        Result := Succeeded(ContextMenu.InvokeCommand(InvokeInfo));
                    end
                  end
                end
              end
            finally
              PIDLMgr.FreePIDL(DesktopPIDL);
              CurrentContextMenu := nil;
              CurrentContextMenu2 := nil;
              ContextMenu := nil;
              ContextMenu2 := nil;
              ContextMenu3 := nil;    
              FActiveFolder := nil;
              Desktop := nil;
              // Special handling because this is a non reference counted Component
              if Assigned(UIObjectOfDataObject) then
                UIObjectOfDataObject._Release;
              UIObjectOfDataObject := nil;
              if Assigned(UIObjectOfDropTarget) then
                UIObjectOfDropTarget._Release;
              UIObjectOfDropTarget := nil;
              { Don't access any properties or field of the object.  If the verb is     }
              { 'delete' the component using this class could have freed the instance   }
              { of the object through a ShellNotifyRegister or some other way.          }
              DestroyMenu(Menu);
              for i := 0 to Length(Keys) - 1 do
                RegCloseKey(Keys[i]);
              Keys := nil;
            end;
          finally
            Owner.WindowProc := FOldWndProcForContextMenu;
            FOldWndProcForContextMenu := nil;
          end;
        end
      end;
      finally
        SetErrorMode(OldMode);
      end
    except
    end
  end
end;

procedure TCommonShellContextMenu.AddMenuKey(Key: WideString);
begin
  KeyStrings.Add(Key)
end;

{$IFDEF TNTSUPPORT}
procedure TCommonShellContextMenu.AddMenuKeys(Keys: TTntStringList);
begin
  KeyStrings.Assign(Keys)
end;
{$ELSE}
procedure TCommonShellContextMenu.AddMenuKeys(Keys: TStringList);
begin
  KeyStrings.Assign(Keys)
end;
{$ENDIF}

procedure TCommonShellContextMenu.ClearKeys;
begin
  KeyStrings.Clear
end;

procedure TCommonShellContextMenu.ClearMenuMap;
begin
  MenuMap.Clear
end;

procedure TCommonShellContextMenu.DoCopy(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean);
begin
  if Assigned(OnShellMenuCopy) then
    OnShellMenuCopy(Self, ShellFolder, DataObject, DoDefault)
end;

procedure TCommonShellContextMenu.DoCreateShortCut(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean);
begin
  if Assigned(OnShellMenuCreateShortCut) then
    OnShellMenuCreateShortCut(Self, ShellFolder, DataObject, DoDefault)
end;

procedure TCommonShellContextMenu.DoCut(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean);
begin
  if Assigned(OnShellMenuCut) then
    OnShellMenuCut(Self, ShellFolder, DataObject, DoDefault)
end;

procedure TCommonShellContextMenu.DoDelete(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean);
begin
  if Assigned(OnShellMenuDelete) then
    OnShellMenuDelete(Self, ShellFolder, DataObject, DoDefault)
end;

procedure TCommonShellContextMenu.DoHide;
begin
  if Assigned(OnHide) then
    OnHide(Self);
end;

{$IFDEF TNTSUPPORT}
procedure TCommonShellContextMenu.DoInvokeCommand(MenuItem: TTntMenuItem; InvokeInfo: PCMInvokeCommandInfo);
var
  Handled: Boolean;
begin
  Handled := False;
  if Assigned(OnInvokeCommand) then
    OnInvokeCommand(Self, MenuItem, InvokeInfo, Handled);
  if not Handled then
    MenuItem.Click;
end;
{$ELSE}
procedure TCommonShellContextMenu.DoInvokeCommand(MenuItem: TMenuItem; InvokeInfo: PCMInvokeCommandInfo);
var
  Handled: Boolean;
begin
  Handled := False;
  if Assigned(OnInvokeCommand) then
    OnInvokeCommand(Self, MenuItem, InvokeInfo, Handled);
  if not Handled then
    MenuItem.Click;
end;
{$ENDIF}

procedure TCommonShellContextMenu.DoMenuMerge(Menu: HMENU; IndexMenu: UINT;
  var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags);
begin
  if Assigned(OnMenuMerge) then
    OnMenuMerge(Self, Menu, IndexMenu, CmdFirst, CmdLast, Flags);
end;

procedure TCommonShellContextMenu.DoMenuMergeBottom(Menu: HMENU;
  IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT;
  Flags: TShellContextMenuFlags);
begin
  if Assigned(OnMenuMergeBottom) then
    OnMenuMergeBottom(Self, Menu, IndexMenu, CmdFirst, CmdLast, Flags);
end;

procedure TCommonShellContextMenu.DoMenuMergeTop(Menu: HMENU; IndexMenu: UINT;
  var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags);
begin
  if Assigned(OnMenuMergeTop) then
    OnMenuMergeTop(Self, Menu, IndexMenu, CmdFirst, CmdLast, Flags);
end;

procedure TCommonShellContextMenu.DoNewFolder(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean);
begin
  if Assigned(OnShellMenuNewFolder) then
    OnShellMenuNewFolder(Self, ShellFolder, DataObject, DoDefault)
end;

procedure TCommonShellContextMenu.DoPaste(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean);
begin
  if Assigned(OnShellMenuPaste) then
    OnShellMenuPaste(Self, ShellFolder, DataObject, DoDefault)
end;

procedure TCommonShellContextMenu.DoPasteLink(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean);
begin
  if Assigned(OnShellMenuPasteLink) then
    OnShellMenuPasteLink(Self, ShellFolder, DataObject, DoDefault)
end;

procedure TCommonShellContextMenu.DoPasteShortCut(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean);
begin
  if Assigned(OnShellMenuPasteShortCut) then
    OnShellMenuPasteShortCut(Self, ShellFolder, DataObject, DoDefault)
end;

procedure TCommonShellContextMenu.DoProperties(ShellFolder: IShellFolder; DataObject: IDataObject; var DoDefault: Boolean);
begin
  if Assigned(OnShellMenuProperites) then
    OnShellMenuProperites(Self, ShellFolder, DataObject, DoDefault)
end;

procedure TCommonShellContextMenu.DoShow;
begin
  if Assigned(OnShow) then
    OnShow(Self);
end;

procedure TCommonShellContextMenu.HandleContextMenuMsg(Msg, wParam, lParam: Longint; var Result: LRESULT);
{ This is called when the ContextMenu calls back to its owner window to ask     }
{ questions to implement the addition of icons to the menu.  The messages sent  }
{ to the owner window are:  WM_INITMENUPOPUP, WM_DRAWITEM, or WM_MEASUREITEM.   }
{ Which must be passed on to the ContextMenu2 interface to display items with   }
{ icons.                                                                        }
var
  ContextMenu3: IContextMenu3;
begin   
  if Assigned(CurrentContextMenu2) then
    if CurrentContextMenu2.QueryInterface(IContextMenu3, ContextMenu3) <> E_NOINTERFACE then
      Result := ContextMenu3.HandleMenuMsg2(Msg, wParam, lParam, Result)
    else
      Result := CurrentContextMenu2.HandleMenuMsg(Msg, wParam, lParam);
end;

procedure TCommonShellContextMenu.LoadMultiFolderPIDLArray(Namespaces: TNamespaceArray; var PIDLs: TAbsolutePIDLArray);
var
  i: Integer;
begin
  CopyValidated := True;
  CutValidated := True;
  SetLength(PIDLs, Length(Namespaces));
  for i := 0 to Length(Namespaces) - 1 do
  begin
    PIDLs[i] := Namespaces[i].AbsolutePIDL;
    if Namespaces[i].Folder and not Namespaces[i].Browsable then
      Include(FExtensions, cmeDirectory);
    if CopyValidated then
      CopyValidated := Namespaces[i].CanCopy;
    if CutValidated then
      CutValidated := Namespaces[i].CanMove;
    if Namespaces[i].Folder and not Namespaces[i].Directory then
      Include(FExtensions, cmeFolder);
  end;
end;

procedure TCommonShellContextMenu.WindowProcForContextMenu(var Message: TMessage);
begin
  FOldWndProcForContextMenu(Message); // Call the OldWindProc of the ContextMenu owner
  case Message.Msg of
    WM_DRAWITEM, WM_INITMENUPOPUP, WM_MEASUREITEM, WM_MENUCHAR:
      HandleContextMenuMsg(Message.Msg, Message.WParam, Message.LParam, Message.Result);
  end;
end;

function TCommonShellContextMenu._AddRef: Integer;
begin
  Result := -1
end;

function TCommonShellContextMenu._Release: Integer;
begin
  Result := -1
end;

function TCommonShellContextMenu.QueryInterface(const IID: TGUID;
  out Obj): HResult;
begin
  Result := inherited QueryInterface(IID, Obj);
  {$IFDEF GXDEBUG_VIRTUALCONTEXTMENU}
    SendDebug('QueryInterface - ' + GUIDToInterfaceStr(IID));
  {$ENDIF}
end;

{$IFDEF TNTSUPPORT}
function TCommonShellContextMenu.MergeMenuIntoContextMenu(Menu: TTntPopupMenu;
  ContextMenu: HMenu; Index: Integer; idStart: UINT): Integer;
  //
  // Returns the ItemID of the last item it added to the ContextMenu
  //

  function RunMenu(MenuItem: TTntMenuItem; hPopupMenu: hMenu; MenuID: UINT): Integer;
  var
    i: Integer;
    SubMenu: hMenu;
    Map: PMenuItemLink;
    NewIndex: Integer;
  begin
    Result := MenuID;
    if MenuItem.Count > 0 then
    begin
      // Item has sub-items and won't take a MenuID
      SubMenu := CreatePopupMenu;
      NewIndex := AddContextMenuItem(hPopupMenu, MenuItem.Caption, Index, Result, SubMenu);
      for i := MenuItem.Count - 1 downto 0 do
        Result := RunMenu(MenuItem.Items[i] as TTntMenuItem, SubMenu, Result);
    end else
      NewIndex := AddContextMenuItem(hPopupMenu, MenuItem.Caption, Index, Result, 0);
    if NewIndex <> $FFFF then
    begin
      Map := MenuMap.Add;
      Map.MenuID := Result;
      Map.Item := MenuItem;
      Inc(Result)
    end;
  end;

var
  i: Integer;
begin
  Result := idStart;
  for i := Menu.Items.Count - 1 downto 0 do
    Result := RunMenu(Menu.Items[i] as TTntMenuItem, ContextMenu, Result)
end;
{$ELSE}
function TCommonShellContextMenu.MergeMenuIntoContextMenu(Menu: TPopupMenu;
  ContextMenu: HMenu; Index: Integer; idStart: UINT): Integer;
  //
  // Returns the ItemID of the last item it added to the ContextMenu
  //

  function RunMenu(MenuItem: TMenuItem; hPopupMenu: hMenu; MenuID: UINT): Integer;
  var
    i: Integer;
    SubMenu: hMenu;
    Map: PMenuItemLink;
    NewIndex: Integer;
  begin
    Result := MenuID;
    if MenuItem.Count > 0 then
    begin
      // Item has sub-items and won't take a MenuID
      SubMenu := CreatePopupMenu;
      NewIndex := AddContextMenuItem(hPopupMenu, MenuItem.Caption, Index, Result, SubMenu, MenuItem.Enabled, MenuItem.Checked, MenuItem.Default);
      for i := MenuItem.Count - 1 downto 0 do
        Result := RunMenu(MenuItem.Items[i], SubMenu, Result);
    end else
      NewIndex := AddContextMenuItem(hPopupMenu, MenuItem.Caption, Index, Result, 0, MenuItem.Enabled, MenuItem.Checked, MenuItem.Default);
    if NewIndex <> $FFFF then
    begin
      Map := MenuMap.Add;
      Map.MenuID := Result;
      Map.Item := MenuItem;
      Inc(Result)
    end;
  end;

var
  i: Integer;
begin
  Result := idStart;
  for i := Menu.Items.Count - 1 downto 0 do
    Result := RunMenu(Menu.Items[i], ContextMenu, Result)
end;
{$ENDIF}

{ TCommonShellMultiParentContextMenu}
function TCommonShellMultiParentContextMenu.ExecuteContextMenuVerb(
  Owner: TWinControl; Namespaces: TNamespaceArray; Verb: string): Boolean;
var
  PIDLs: TAbsolutePIDLArray;
begin
  PIDLs := nil;
  LocalFocused := nil;
  LocalNamespaces := Namespaces;
  RenameMenuItem := True;
  LoadRegistryKeyStrings(nil);
  LoadMultiFolderPIDLArray(Namespaces, PIDLs);
  Result := InternalShowContextMenu(Owner, nil, PIDLs, Verb, nil);
end;

function TCommonShellMultiParentContextMenu.ShowContextMenu(Owner: TWinControl; Focused: TNamespace; Namespaces: TNamespaceArray; Position: PPoint = nil): Boolean;
var
  PIDLs: TAbsolutePIDLArray;
begin
  PIDLs := nil;
  LocalFocused := Focused;
  LocalNamespaces := Namespaces;
  RenameMenuItem := True;
  LoadRegistryKeyStrings(Focused);
  LoadMultiFolderPIDLArray(Namespaces, PIDLs);
  Result := InternalShowContextMenu(Owner, nil, PIDLs, '', Position);
end;

procedure TCommonShellMultiParentContextMenu.LoadRegistryKeyStrings(Focused: TNamespace);
begin
  KeyStrings.Clear;
  Extensions := [cmeShellDefault, cmeAsterik, cmeAllFilesystemObjects, cmeFileSystemAssociations];

  // Only add the menu handlers for the focused item class
  if Assigned(Focused) then
  begin
    if Focused.Folder then
      Extensions := [cmeShellDefault, {cmeDirectory,} cmeFolder]
    else
      AddMenuKey(WideExtractFileExt(Focused.NameForParsing));
  end
end;

{ TCommonShellBackgroundContextMenu }
constructor TCommonShellBackgroundContextMenu.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FInitialItemList := TCommonPIDLList.Create;
  FFinalItemList := TCommonPIDLList.Create;
  FRenameMenuItem := False;
  FPasteMenuItem := False;
  FShowPasteItem := True;
  FShowPasteShortCutItem := True;
  FShowPropertiesItem := True;
  FAutoDetectNewItem := True;
  {$IFDEF TNTSUPPORT}
  PopupMenuProperties := TTntPopupMenu.Create(Self);
  PopupMenuPasteShortCut := TTntPopupMenu.Create(Self);
  PopupMenuPaste := TTntPopupMenu.Create(Self);
  Paste := TTntMenuItem.Create(PopupMenuPaste);
  PasteShortCut := TTntMenuItem.Create(PopupMenuPasteShortCut);
  Properties := TTntMenuItem.Create(PopupMenuProperties);
  {$ELSE}
  PopupMenuProperties := TPopupMenu.Create(Self);
  PopupMenuPasteShortCut := TPopupMenu.Create(Self);
  PopupMenuPaste := TPopupMenu.Create(Self);
  Paste := TMenuItem.Create(PopupMenuPaste);
  PasteShortCut := TMenuItem.Create(PopupMenuPasteShortCut);
  Properties := TMenuItem.Create(PopupMenuProperties);
  {$ENDIF}
  Paste.Caption := STR_PASTELINK;
  PasteShortCut.Caption := STR_PASTE;
  Properties.Caption := STR_PROPERTIES;
  Paste.OnClick := ClickPaste;
  PasteShortCut.OnClick := ClickPasteShortCut;
  Properties.OnClick := ClickProperties;
  PopupMenuProperties.Items.Add(Properties);
  PopupMenuPasteShortCut.Items.Add(PasteShortCut);
  PopupMenuPaste.Items.Add(Paste);
end;

destructor TCommonShellBackgroundContextMenu.Destroy;
begin
  FreeAndNil(FInitialItemList);
  FreeAndNil(FFinalItemList);
  inherited Destroy;
end;

function TCommonShellBackgroundContextMenu.EnumCallback(MessageWnd: HWnd;
  APIDL: PItemIDList; AParent: TNamespace; Data: Pointer; var Terminate: Boolean): Boolean;
begin
  // copy the relative PIDLs
  TCommonPIDLList( Data).Add(APIDL);
  Result := True;
end;


function TCommonShellBackgroundContextMenu.ShowContextMenu(
  Owner: TWinControl; Root: TNamespace; Position: PPoint): Boolean;
begin
  Result := False;
  LocalFocused := Root;
  LocalNamespaces := nil;
  if Assigned(Root) then
  begin
    if Root.Folder then
    begin
      RenameMenuItem := True;
      LoadRegistryKeyStrings(Root);
      Result := InternalShowContextMenu(Owner, Root.AbsolutePIDL, nil, '', Position);
    end
  end
end;

procedure TCommonShellBackgroundContextMenu.ClickPaste(Sender: TObject);
var
  NSA: TNamespaceArray;
begin
  if Assigned(LocalFocused) then
  begin
    SetLength(NSA, 1);
    NSA[0] := LocalFocused;
    LocalFocused.Paste(NSA, False)
  end
end;

procedure TCommonShellBackgroundContextMenu.ClickPasteShortCut(Sender: TObject);
var
  NSA: TNamespaceArray;
begin
  if Assigned(LocalFocused) then
  begin
    SetLength(NSA, 1);
    NSA[0] := LocalFocused;
    LocalFocused.Paste(NSA, True)
  end
end;

procedure TCommonShellBackgroundContextMenu.ClickProperties(Sender: TObject);
begin
  if Assigned(LocalFocused) then
    LocalFocused.ShowPropertySheet
end;

procedure TCommonShellBackgroundContextMenu.DoHide;
var
  i: Integer;
  Found: Boolean;
  NS: TNamespace;
begin
  inherited DoHide;
  if AutoDetectNewItem and Assigned(LocalFocused) then
  begin
    LocalFocused.EnumerateFolder(0, True, True, False, EnumCallback, FinalItemList);
    if (InitialItemList.Count + 1) = FinalItemList.Count then
    begin
      // One item was added, time to find it.
      PIDLListQuickSort(InitialItemList, LocalFocused.ShellFolder, 0, InitialItemList.Count - 1);
      PIDLListQuickSort(FinalItemList, LocalFocused.ShellFolder, 0, FinalItemList.Count - 1);
      Found := False;
      i := 0;
      while not Found and (i < FinalItemList.Count) do
      begin
        Found := LocalFocused.ShellFolder.CompareIDs(0, InitialItemList[i], FinalItemList[i]) <> 0;
        if Found then
        begin
          NS := TNamespace.Create(FinalItemList[i], LocalFocused);
          NS.FreePIDLOnDestroy := False;
          DoNewItem(NS);
          NS.Free
        end;
        Inc(i)
      end
    end
  end;
  FinalItemList.Clear;
  InitialItemList.Clear;
end;

procedure TCommonShellBackgroundContextMenu.DoMenuMerge(Menu: HMENU;
  IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT;
  Flags: TShellContextMenuFlags);
begin
  inherited DoMenuMerge(Menu, IndexMenu, CmdFirst, CmdLast, Flags);
end;

procedure TCommonShellBackgroundContextMenu.DoMenuMergeBottom(Menu: HMENU;
  IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT;
  Flags: TShellContextMenuFlags);
begin
  if ShowPropertiesItem then
  begin
    if Assigned(LocalFocused) then
      Properties.Enabled := LocalFocused.HasPropSheet
    else
      Properties.Enabled := False;
    CmdFirst := MergeMenuIntoContextMenu(PopupMenuProperties, Menu, IndexMenu, CmdFirst);
    AddContextMenuItem(Menu, '-', IndexMenu);
  end;
  inherited DoMenuMergeBottom(Menu, IndexMenu, CmdFirst, CmdLast, Flags);
end;

procedure TCommonShellBackgroundContextMenu.DoMenuMergeTop(Menu: HMENU; IndexMenu: UINT; var CmdFirst: UINT; CmdLast: UINT; Flags: TShellContextMenuFlags);
begin
  Paste.Enabled := ClipboardContainsShellFormats;
  PasteShortCut.Enabled := ClipboardContainsShellFormats;
  if ShowPasteItem or ShowPasteShortCutItem then
    AddContextMenuItem(Menu, '-', IndexMenu);
  if ShowPasteItem then
    CmdFirst := MergeMenuIntoContextMenu(PopupMenuPaste, Menu, IndexMenu, CmdFirst);
  if ShowPasteShortCutItem then
    CmdFirst := MergeMenuIntoContextMenu(PopupMenuPasteShortCut, Menu, IndexMenu, CmdFirst);
  if ShowPasteItem or ShowPasteShortCutItem then
    AddContextMenuItem(Menu, '-', IndexMenu);

  inherited DoMenuMergeTop(Menu, IndexMenu, CmdFirst, CmdLast, Flags);
end;

procedure TCommonShellBackgroundContextMenu.DoNewItem(NS: TNamespace);
begin
  if Assigned(OnNewItem) then
    OnNewItem(Self, NS)
end;

procedure TCommonShellBackgroundContextMenu.DoShow;

begin
  inherited DoShow;
  if AutoDetectNewItem and Assigned(LocalFocused) then
  begin
    InitialItemList.Clear;
    LocalFocused.EnumerateFolder(0, True, True, False, EnumCallback, InitialItemList)
  end
end;

procedure TCommonShellBackgroundContextMenu.LoadRegistryKeyStrings(Focused: TNamespace);
begin
  CopyValidated := False;
  CutValidated := False;
  KeyStrings.Clear;
  PasteMenuItem := True;
  Extensions := [cmeShellDefault, cmeDirBackground];
end;

{ TMenuItemMap }
function TMenuItemMap.Add: PMenuItemLink;
begin
  New(Result);
  if Assigned(Result) then
    inherited Insert(Count, Result)
end;

function TMenuItemMap.First: PMenuItemLink;
begin
  Result := PMenuItemLink( inherited First)
end;

function TMenuItemMap.Get(Index: Integer): PMenuItemLink;
begin
  Result := PMenuItemLink( inherited Get(Index))
end;

function TMenuItemMap.IndexOf(Item: PMenuItemLink): Integer;
begin
  Result := inherited IndexOf(Item)
end;

function TMenuItemMap.Last: PMenuItemLink;
begin
  Result := PMenuItemLink( inherited Last)
end;

function TMenuItemMap.Remove(Item: PMenuItemLink): Integer;
begin
  Result := inherited Remove(Item)
end;

procedure TMenuItemMap.Clear;
var
  i: Integer;
begin
  try
    for i := 0 to Count - 1 do
      Dispose(  Items[i]);
  finally
    SetCount(0);
    SetCapacity(0);
  end;
end;

function TMenuItemMap.Insert(Index: Integer): PMenuItemLink;
begin
  New(Result);
  if Assigned(Result) then
    inherited Insert(Index, Result)
end;

procedure TMenuItemMap.Put(Index: Integer; Item: PMenuItemLink);
begin
  inherited Put(Index, Item)
end;

initialization
  CoInitialize(nil);
 // if IsWinNT4 then
    FileIconInit(True);  // This MUST be before the Namespaces are created or it won't work because the IconCache may have an icon in from the namespace
  if not LoadShell32Functions then
    Halt(0);
  PIDLMgr := TCommonPIDLManager.Create;
  DesktopFolder := CreateSpecialNamespace(CSIDL_DESKTOP);
  RecycleBinFolder := CreateSpecialNamespace(CSIDL_BITBUCKET);
  PhysicalDesktopFolder := CreateSpecialNamespace(CSIDL_DESKTOPDIRECTORY);
  DrivesFolder := CreateSpecialNamespace(CSIDL_DRIVES);
  PrinterFolder := CreateSpecialNamespace(CSIDL_PRINTERS);
  HistoryFolder := CreateSpecialNamespace(CSIDL_HISTORY);
  ControlPanelFolder :=  CreateSpecialNamespace(CSIDL_CONTROLS);
  NetworkNeighborHoodFolder :=  CreateSpecialNamespace(CSIDL_NETWORK);
  TemplatesFolder := CreateSpecialNamespace(CSIDL_TEMPLATES);
  MyDocumentsFolder := CreateSpecialNamespace(CSIDL_PERSONAL);
  FavoritesFolder := CreateSpecialNamespace(CSIDL_FAVORITES);
  UserDocumentsFolder := CreateSpecialNamespace(CSIDL_APPDATA);
  ProgramFilesFolder := CreateSpecialNamespace(CSIDL_PROGRAMS);

finalization
  FreeAndNil(DesktopFolder);
  FreeAndNil(RecycleBinFolder);
  FreeAndNil(PhysicalDesktopFolder);
  FreeAndNil(DrivesFolder);
  FreeAndNil(HistoryFolder);
  FreeAndNil(PrinterFolder);
  FreeAndNil(ControlPanelFolder);
  FreeAndNil(NetworkNeighborHoodFolder);
  FreeAndNil(TemplatesFolder);
  FreeAndNil(MyDocumentsFolder);
  FreeAndNil(FavoritesFolder);
  FreeAndNil(UserDocumentsFolder);
  FreeAndNil(ProgramFilesFolder);
  FreeAndNil(PIDLMgr);
  CoUninitialize;
end.










