unit MPCommonUtilities;

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

// The following are implemented in Win 95:
//  EnumResourceLanguagesW
//  EnumResourceNamesW
//  EnumResourceTypesW
//  ExtTextOutW
//  FindResourceW
//  FindResourceExW
//  GetCharWidthW
//  GetCommandLineW
//  GetTextExtentPoint32W
//  GetTextExtentPointW
//  lstrlenW
//  MessageBoxExW
//  MessageBoxW
//  MultiByteToWideChar
//  TextOutW
//  WideCharToMultiByte

  {$IFDEF TNTSUPPORT}
//   IMPORTANT - PLEASE READ then comment this line out.
//  If using TNT you MUST include the TNT package for your specific compiler in the
//  Requires section of this package.  It may be possible to compile without doing
//  this but you WILL eventually have strange crashes in your application that will
//  be difficult to understand.  The best way to do this in my opinion is to create
//  a new folder in the package install directory called "Delphi_TNT" (or CBuilder_TNT)
//  and copy all the files from the Delphi (or CBuilder) folder into it.  Now open the
//  MPCommonLibDx.dpk (or bpk) file in the "Delphi_TNT" (or CBuilder_TNT) based on your compiler
//  version in a text editor.  In the "Requires" section add "TNTUnicodeVcl_Rx0", where
//  the "x" is the version of Delphi you are using.  Open the dpk (bpk) file in your
//  IDE. Select the menu option Projects>Options>Directories/Conditionals>Conditional
//  and enter TNTSUPPORT. Compile the package, then open the MPCommonLibDxD.dpk (or bpk)
//  and compile and press the Install button.
//  Now when you update the packages you won't have to redo all this.  Just install
//  the update then compile the packages in the "Delphi_TNT" (or CBuilder_TNT) folders
//  an you are done.
  {$ENDIF}

{$B-}

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

interface

uses
  Windows,
  Messages,
  SysUtils,
  Classes,
  Graphics,
  Controls,
  Forms,
  Math,
  ActiveX,
  ShlObj,
  {$IFDEF COMPILER_6_UP}
  Variants,
  RTLConsts,
  {$ELSE}
  Consts,
  {$ENDIF COMPILER_6_UP}
  {$IFNDEF COMPILER_6_UP}
  Menus,
  {$ENDIF}
  {$IFDEF TNTSUPPORT}
  TntSysUtils,
  TntClasses,
  {$ENDIF}
  ShellAPI,
  ComCtrls,
  ComObj,
  CommCtrl,
  MPShellTypes,
  MPResources;

const
  WideNull = WideChar(#0);
  WideCR = WideChar(#13);
  WideLF = WideChar(#10);
  WideLineSeparator = WideChar(#2028);
  WideSpace = WideChar(#32);
  WidePeriod = WideChar('.');

  {$IFNDEF COMPILER_6_UP}
  NullAsStringValue: string = '';
  {$ENDIF}

  Shlwapi = 'shlwapi.dll';

var
  SEasyNSEMsg_Caption: WideString = 'Shell extension registration';
  SEasyNSEMsg_CannotRegister: WideString = 'Cannot register shell extension.';
  SEasyNSEMsg_CannotUnRegister: WideString = 'Cannot unregister shell extension.';
  SEasyNSEMsg_CannotFindRegSvr: WideString = 'Unable to find RegSvr32.exe executable.';
  SEasyNSEMsg_CannotFindDLL: WideString = 'Unable to find extension DLL.';

type
  TCommonWideCharArray = array of WideChar;
  TCommonPWideCharArray = array of PWideChar;
  TCommonWideStringDynArray = array of WideString;
  TCommonIntegerDynArray = array of Integer;

  {$IFNDEF COMPILER_6_UP}
  TValueRelationship = -1..1;
  TSeekOrigin = (soBeginning, soCurrent, soEnd);
  {$ENDIF}

  TCommonDropEffect = (
    cdeNone,                 // No drop effect (the circle with the slash through it
    cdeCopy,                 // Copy the dropped object
    cdeMove,                 // Move the dropped object
    cdeLink,                 // Make a shortcut to the dropped object
    cdeScroll                // The dragging is in the middle of a scroll
  );
  TCommonDropEffects = set of TCommonDropEffect;

  TCommonOLEDragResult = (
    cdrDrop,                 // The drag resulted in a drop
    cdrCancel,               // The drag resulted in being canceled
    cdrError                 // The drag resulted in an unknown error
  );

  TCommonKeyState = (
    cksControl,              // Control Key is down
    cksLButton,              // Left Mouse is down
    cksMButton,              // Middle Mouse is down
    cksRButton,              // Right Mouse is down
    cksShift,                // Shift Key is down
    cksAlt,                  // Alt Key is down
    cksButton                // One of the mouse buttons are down
  );
  TCommonKeyStates = set of TCommonKeyState;

  TCommonMouseButton = (
    cmbNone,          // No Button
    cmbLeft,          // Left Button
    cmbRight,         // Right Button
    cmbMiddle         // Middle Button
  );

  TCommonVAlignment = (
    cvaTop,                            // The vertical alignment of the text is at the top of the object
    cvaBottom,                         // The vertical alignment of the text is at the bottom of the object
    cvaCenter                          // The vertical alignment of the text is at the center of the object
  );

  // Flags for the DrawTextWEx function
  TCommonDrawTextWFlag = (
    dtSingleLine,       // Put Caption on one line
    dtLeft,             // Aligns Text Left
    dtRight,            // Aligns Text Right
    dtCenter,           // Aligns Text Center
    dtTop,              // Vertical Align Text to Bottom of Rect
    dtBottom,           // Vertical Align Text to Bottom of Rect
                        //   Only valid with: dtSingleLine
    dtVCenter,          // Vertical Align Text to Center of Rect
                        //   Only valid with: dtSingleLine
    dtCalcRect,         // Modifies the Rectangle to the size required for the Text does
                        // not draw the text. By default it does not modify the right
                        // edge of the rectangle, it only changes the height to fit
                        // the text, see dtCalcRectAdjR
    dtCalcRectAdjR,     // Modifies the Rectangles right edge for a best fit of the text
                        // Does not increase the width only shortens it,
                        //   Only valid with: dtCalcRect
    dtCalcRectAlign,    // Modifies the rectangle by aligning it with the original
                        // rectangle based on the dtLeft, dtRight, dtCenter flag.
                        // In other words it ensures that if the text won't fit that
                        // only the end of the text is clipped.  For instance if
                        // the text is horz centered the calculation could clip
                        // both ends of the text.  Just using the dtCalcRectAdjR
                        // flag will only stretch the Right edge and the left will
                        // still be clipped. Using this flag will shift the rect
                        // to the edge of the passed rect so that the beginning
                        // of the text is always shown.
                        //   Only valid with: dtCalcRect and dtCalcRectAdjR
    dtEndEllipsis,      // Adds a "..." to the end of the string if it will not fit in
                        // the passed rectangle
    dtWordBreak,        // Breaks the passed string to best fit in the rectangle
                        // The default Characters to break the line are:
                        //      WideSpace                               ( WideChar(#32) )
                        //      WideCR/WideLF sequence or individually  ( WideChar(#13 or #10) )
                        //      WideLineSeparator                       ( WideChar(#2028) )
    dtUserBreakChars,   // The UserBreakChars parameters should be used for defining
                        // what to use to break the passed string into lines
                        //   Only valid with: dtWordBreak
    dtRTLReading,       // Right to Left reading
    dtNoClip            // Do not clip the text in the rectangle
  );
  TCommonDrawTextWFlags = set of TCommonDrawTextWFlag;

  // Describes the mode how to blend pixels.
  TCommonBlendMode = (
    cbmConstantAlpha,         // apply given constant alpha
    cbmPerPixelAlpha,         // use alpha value of the source pixel
    cbmMasterAlpha,           // use alpha value of source pixel and multiply it with the constant alpha value
    cbmConstantAlphaAndColor  // blend the destination color with the given constant color und the constant alpha value
  );

  TShortenStringEllipsis = (
    sseEnd,            // Ellipsis on end of string
    sseFront,          // Ellipsis on begining of string
    sseMiddle,         // Ellipsis in middle of string
    sseFilePathMiddle  // Ellipsis is in middle of string but tries to show the entire filename
  );

  // RGB (red, green, blue) color given in the range [0..1] per component.
  TCommonRGB = record
    R, G, B: Double;
  end;

  // Hue, luminance, saturation color with all three components in the range [0..1]
  // (so hue's 0..360° is normalized to 0..1).
  TCommonHLS = record
    H, L, S: Double;
  end;

// Enhanced library loading functions that reference counts the loading to make
// sure that libraries are freed when using cool controls in COM applications
function CommonLoadLibrary(LibraryName: string): THandle;
function CommonUnloadLibrary(LibraryName: string): Boolean;
procedure CommonUnloadAllLibraries;

function FlipReverseCopyRect(const Flip, Reverse: Boolean; const Bitmap: TBitmap): TBitmap; overload;
procedure FlipReverseCopyRect(const Flip, Reverse: Boolean; R: TRect; const Canvas: TCanvas); overload;

procedure DrawRadioButton(Canvas: TCanvas; Pos: TPoint; Size: Integer; clBackground, clHotBkGnd,
  clLeftOuter, clRightOuter, clLeftInner, clRightInner: TColor; Checked, Enabled, Hot: Boolean);
procedure DrawCheckBox(Canvas: TCanvas; Pos: TPoint; Size: Integer; clBackground, clHotBkGnd,
  clLeftOuter, clRightOuter, clLeftInner, clRightInner: TColor; Checked, Enabled, Hot: Boolean);
function CheckBounds(Size: Integer): TRect;



{$IFNDEF COMPILER_6_UP}
function VarToWideStrDef(const V: Variant; const ADefault: WideString): WideString;
function VarToWideStr(const V: Variant): WideString;
function BoolToStr(B: Boolean; UseBoolStrs: Boolean = False): string;
function CompareTime(const A, B: TDateTime): TValueRelationship;
function WideCompareText(S1, S2: WideString): Integer;
function ExcludeTrailingBackslash(Path: WideString): WideString;
function IncludeTrailingBackslash(Path: WideString): WideString;
function GUIDToString(const GUID: TGUID): string;
{$ENDIF}

function AbsRect(ARect: TRect): TRect;
function AddContextMenuItem(Menu: HMenu; ACaption: WideString; Index: Integer;
  MenuID: UINT = $FFFF; hSubMenu: UINT = 0; Enabled: Boolean = True;
  Checked: Boolean = False; Default: Boolean = False): Integer;
function AddCommas(NumberString: WideString): WideString;
function CalcuateFolderSize(FolderPath: WideString; Recurse: Boolean): Int64;
function CenterRectHorz(OuterRect, InnerRect: TRect): TRect;
function CenterRectInRect(OuterRect, InnerRect: TRect): TRect;
function CenterRectVert(OuterRect, InnerRect: TRect): TRect;
function CommonSupports(const Instance: IUnknown; const IID: TGUID): Boolean; overload;
function CommonSupports(const Instance: IUnknown; const IID: TGUID; out Intf): Boolean; overload;
function CommonSupports(const Instance: TObject; const IID: TGUID): Boolean; overload;
function CommonSupports(const Instance: TObject; const IID: TGUID; out Intf): Boolean; overload;
procedure CopyToNullBufferA(S: WideString; Buffer: PChar; CharCount: Cardinal);
procedure CopyToNullBufferW(S: WideString; Buffer: PWideChar; CharCount: Cardinal);
procedure CreateProcessMP(ExeFile, Parameters, InitalDir: WideString);
function DiffRectHorz(Rect1, Rect2: TRect): TRect;
function DiffRectVert(Rect1, Rect2: TRect): TRect;
{$IFNDEF COMPILER_6_UP}
function DirectoryExists(const Directory: string): Boolean;
{$ENDIF}
function DiskInDrive(C: Char): Boolean;
function DragDetectPlus(Handle: HWND; Pt: TPoint): Boolean;
function DrawTextWEx(DC: HDC; Text: WideString; var lpRect: TRect; Flags: TCommonDrawTextWFlags; MaxLineCount: Integer): Integer;
function DropEffectToStr(DropEffect: DWORD): WideString;
function EqualWndMethod(A, B: TWndMethod): Boolean;
function FileExistsW(const FileName: WideString): Boolean;
function FileIconInit(FullInit: BOOL): BOOL; stdcall;
function FindUniqueMenuID(AMenu: HMenu): Cardinal;
function GetMyDocumentsVirtualFolder: PItemIDList;
function HasMMX: Boolean;
function IncludeTrailingBackslashW(const S: WideString): WideString;
function IsAnyMouseButtonDown: Boolean;
function IsFTPPath(Path: WideString): Boolean;
function IsMappedDrivePath(const Path: WideString): Boolean;
function IsRectNull(ARect: TRect): Boolean;
function IsTextTrueType(Canvas: TCanvas): Boolean; overload;
function IsTextTrueType(DC: HDC): Boolean; overload;
function IsUNCPath(const Path: WideString): Boolean;
function IsUnicode: Boolean;    // OS supports Unicode functions (basiclly means IsWinNT or XP)
function IsWin2000: Boolean;
function IsWin95_SR1: Boolean;
function IsWinME: Boolean;
function IsWinNT: Boolean;
function IsWinNT4: Boolean;
function IsWinXP: Boolean;
function IsWinXPOrUp: Boolean;
function IsWinVista: Boolean;
function IsWinVistaOrUp: Boolean;
procedure MakeFindDataW(const FindFileDataA: TWIN32FindDataA; var FindFileDataW: TWIN32FindDataW);
function ModuleFileName(PathOnly: Boolean = True): WideString;
function PIDLToPath(PIDL: PItemIDList): WideString;
function SHGetImageList(iImageList: Integer; const RefID: TGUID; out ppvOut): HRESULT; stdcall;
function ShiftStateToKeys(Keys: TShiftState): LongWord;
function ShiftStateToStr(Keys: TShiftState): WideString;
function ShortenStringEx(DC: HDC; const S: WideString; Width: Integer; RTL: Boolean; EllipsisPlacement: TShortenStringEllipsis): WideString;
function ShortenTextW(DC: hDC; TextToShorten: WideString; MaxSize: Integer): WideString;
function ShortFileName(const FileName: WideString): WideString;
function ShortPath(const Path: WideString): WideString;
function Size(cx, cy: Integer): TSize;
function SplitTextW(DC: hDC; TextToSplit: WideString; MaxWidth: Integer; var Buffer: TCommonWideCharArray; MaxSplits: Integer): Integer;
function StrRetToStr(StrRet: TStrRet; APIDL: PItemIDList): WideString;
function SystemDirectory: WideString;
function SysMenuFont: HFONT;
function SysMenuHeight: Integer;
function TextExtentW(Text: PWideChar; Canvas: TCanvas): TSize; overload;
function TextExtentW(Text: PWideChar; DC: hDC): TSize; overload;
function TextExtentW(Text: WideString; Canvas: TCanvas): TSize; overload;
function TextExtentW(Text: WideString; Font: TFont): TSize; overload;
function TextTrueExtentsW(Text: WideString; DC: HDC): TSize;
function TNTConditionallyDefined: Boolean;
function UnicodeStringLists: Boolean;
function UniqueDirName(const ADirPath: WideString): WideString;
function UniqueFileName(const AFilePath: WideString): WideString;
function VariantToCaption(const V: Variant): WideString;
function WideCreateDir(Path: WideString): Boolean;
function WideDirectoryExists(const Name: WideString): Boolean;
function WideExcludeTrailingBackslash(Path: WideString): WideString;
function WideExpandEnviromentString(EnviromentString: WideString): WideString;
function WideExtractFileDir(Path: WideString): WideString;
function WideExtractFileDrive(Path: WideString): WideString;
function WideExtractFileExt(Path: WideString): WideString;
function WideExtractFileName(Path: WideString): WideString;
function WideExtractFilePath(Path: WideString): WideString;
function WideFileExists(Path: WideString): Boolean;
function WideFindFirstFileEx(FileName: WideString; var lpFindFileData: TWIN32FindDataW; Mask: WideString; CaseSensitive: Boolean): THandle;
function WideFindFirstFileExExists: Boolean;
function WideGetCurrentDir: WideString;
function WideGetTempDir: WideString;
function WideIncludeTrailingBackslash(Path: WideString): WideString;
function WideIncrementalSearch(CompareStr, Mask: WideString): Integer;
function WideIntToStr(Value: integer): WideString;
function WideIsDrive(Drive: WideString): Boolean;
function WideIsFloppy(FileFolder: WideString): boolean;
function WideIsPathDelimiter(const S: WideString; Index: Integer): Boolean;
function WideLowerCase(const Str: WideString): WideString;
function WideMessageBox(Window: HWND; const ACaption, AMessage: WideString; uType: integer): integer;
function WideNewFolderName(ParentFolder: WideString; SuggestedFolderName: WideString = ''): WideString;
function WidePathMatchSpec(Path, Mask: WideString): Boolean;
function WidePathMatchSpecExists: Boolean;
function WideShellExecute(hWnd: HWND; Operation, FileName, Parameters, Directory: WideString; ShowCmd: Integer = SW_NORMAL): HINST;
function WideStrComp(Str1, Str2: PWideChar): Integer;
function WideStrLower(Str: PWideChar): PWideChar;
function WideStrIComp(Str1, Str2: PWideChar): Integer;
function WideStringReplace(const S, OldPattern, NewPattern: WideString; Flags: TReplaceFlags; WholeWord: Boolean = False): WideString;
function WideStripExt(AFile: WideString): WideString;
function WideStripLeadingBackslash(const S: WideString): WideString;
function WideStripRemoteComputer(const UNCPath: WideString): WideString;
function WideStripTrailingBackslash(const S: WideString; Force: Boolean = False): WideString;
function WideStrMove(Dest, Source: PWideChar; Count: Cardinal): PWideChar;
function WideStrPos(Str, SubStr: PWideChar): PWideChar;
function WideStrRScan(Str: PWideChar; Chr: WideChar): PWideChar;
function WideStrScan(Str: PWideChar; Chr: WideChar): PWideChar;
function WideUpperCase(const S: WideString): WideString;
function WindowsDirectory: WideString;
procedure AlphaBlend(Source, Destination: HDC; R: TRect; Target: TPoint; Mode: TCommonBlendMode; ConstantAlpha, Bias: Integer);
procedure ConvertBitmapEx(Image32: TBitmap; var OutImage: TBitmap; const BackGndColor: TColor);
procedure ShadowBlendBits(Bits: TBitmap; BackGndColor: TColor);
procedure FillWideChar(var Dest; count: Integer; Value: WideChar);
procedure FreeMemAndNil(var P: Pointer);
procedure LoadWideString(S: TStream; var Str: WideString);
procedure MinMax(var A, B: Integer);
procedure SaveWideString(S: TStream; Str: WideString);
procedure WideInsert(Source: WideString; var S: WideString; Index: Integer);
procedure WideShowMessage(Window: HWND; ACaption, AMessage: WideString);
procedure WideStrLCopy(Str1, Str2: PWideChar; Count: Integer);

// Rectangle functions
function ProperRect(Rect: TRect): TRect;
function RectHeight(R: TRect): Integer;
function RectToStr(R: TRect): string;
function RectToSquare(R: TRect): TRect;
function RectWidth(R: TRect): Integer;
function ContainsRect(OuterRect, InnerRect: TRect): Boolean;
function IsRectProper(Rect: TRect): Boolean;

// WideString routines (many borrowed from Mike Liscke)
function StrCopyW(Dest, Source: PWideChar): PWideChar;

function KeyDataToShiftState(KeyData: Longint): TShiftState;
function KeyToKeyStates(Keys: Word): TCommonKeyStates;
function KeyStatesToMouseButton(Keys: Word): TCommonMouseButton;
function KeyStatesToKey(Keys: TCommonKeyStates): Longword;   
function DropEffectToDropEffectStates(Effect: Integer): TCommonDropEffects;
function DropEffectStatesToDropEffect(Effect: TCommonDropEffects): Integer;
function DropEffectToDropEffectState(Effect: Integer): TCommonDropEffect;
function DropEffectStateToDropEffect(Effect: TCommonDropEffect): Integer;
function KeyStateToDropEffect(Keys: TCommonKeyStates): TCommonDropEffect;
function KeyStateToMouseButton(KeyState: TCommonKeyStates): TCommonMouseButton;

// Color Functions
function RGBToHLS(const RGB: TCommonRGB): TCommonHLS;
function HLSToRGB(const HLS: TCommonHLS): TCommonRGB;
function BrightenColor(const RGB: TCommonRGB; Amount: Double): TCommonRGB;
function DarkenColor(const RGB: TCommonRGB; Amount: Double): TCommonRGB;
function MakeTRBG(Color: TColor): TCommonRGB;
function MakeTColor(RGB: TCommonRGB): TColor;
function MakeColorRef(RGB: TCommonRGB; Gamma: Double = 1): COLORREF;
procedure GammaCorrection(var RGB: TCommonRGB; Gamma: Double);
function MakeSafeColor(var RGB: TCommonRGB): Boolean;
function UpsideDownDIB(Bits: TBitmap): Boolean;
procedure FixFormFont(AFont: TFont);

// Window Manipulation
procedure ActivateTopLevelWindow(Child: HWND);

// Graphics
procedure FillGradient(X1, Y1, X2, Y2: integer; fStartColor, fStopColor: TColor;
  StartPoint, EndPoint: integer; fDrawCanvas: TCanvas);

// Helpers to create a callback function out of a object method
function CreateStub(ObjectPtr: Pointer; MethodPtr: Pointer): Pointer;
procedure DisposeStub(Stub: Pointer);

{$IFNDEF COMPILER_6_UP}
function Supports(const Instance: IUnknown; const IID: TGUID): Boolean; overload;
procedure ClearMenuItems(Menu: TMenu);
{$ENDIF}

{$IFNDEF COMPILER_5_UP}
function Supports(const Instance: IUnknown; const IID: TGUID; out Intf): Boolean; overload;
function Supports(const Instance: TObject; const IID: TGUID; out Intf): Boolean; overload;
procedure FreeAndNil(var Obj);

type
  TWMContextMenu = packed record
    Msg: Cardinal;
    hWnd: HWND;
    case Integer of
      0: (
        XPos: Smallint;
        YPos: Smallint);
      1: (
        Pos: TSmallPoint;
        Result: Longint);
  end;
{$ENDIF}

// ****************************************************************************
 // Registration function for Shell and Namespace Extensions
 // Donated by by Alexey Torgashin
 // Note: we assume that paths to both RegSvr32.exe and extension DLL are ANSI (not Unicode).
// ****************************************************************************
type
  TEasyNSERegMessages = set of (enseMsgShowErrors, enseMsgRegSvr);

 //True to register or False to unregister
function RegUnregNSE(const AFileName: WideString; DoRegister: boolean; AMessages: TEasyNSERegMessages = [enseMsgShowErrors]): boolean;
function RegisterNSE(const AFileName: WideString; AMessages: TEasyNSERegMessages = [enseMsgShowErrors]): boolean;
function UnregisterNSE(const AFileName: WideString; AMessages: TEasyNSERegMessages = [enseMsgShowErrors]): boolean;
function ExecShellEx(const Cmd, Params, Dir: WideString; ShowCmd: integer;
  DoWait: boolean): boolean;
//
// Dynamically linked Unicode functions that do not have stubs on Win9x
//
var
  GetDriveTypeW_MP: function(lpRootPathName: PWideChar): UINT; stdcall;
  DrawTextW_MP: function(hDC: HDC; lpString: PWideChar; nCount: Integer;
    var lpRect: TRect; uFormat: UINT): Integer; stdcall;
  SHGetFileInfoW_MP: function(pszPath: PWideChar; dwFileAttributes: DWORD;
    var psfi: TSHFileInfoW; cbFileInfo, uFlags: UINT): DWORD; stdcall;
  CreateFileW_MP: function(lpFileName: PWideChar; dwDesiredAccess, dwShareMode: DWORD;
    lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: DWORD;
    hTemplateFile: THandle): THandle; stdcall;
  SHGetDataFromIDListW_MP: function(psf: IShellFolder; pidl: PItemIDList;
    nFormat: Integer; ptr: Pointer; cb: Integer): HResult; stdcall;
  FindFirstFileW_MP: function(lpFileName: PWideChar; var lpFindFileData: TWIN32FindDataW): THandle; stdcall;
  FindNextFileW_MP: function(hFindFile: THandle; var lpFindFileData: TWIN32FindDataW): BOOL; stdcall;
  GetDiskFreeSpaceW_MP: function(lpRootPathName: PWideChar; var lpSectorsPerCluster,
    lpBytesPerSector, lpNumberOfFreeClusters, lpTotalNumberOfClusters: DWORD): BOOL; stdcall;
  GetCurrentDirectoryW_MP: function(nBufferLength: DWORD; lpBuffer: PWideChar): DWORD; stdcall;
  GetTempPathW_MP: function(nBufferLength: DWORD; lpBuffer: PWideChar): DWORD; stdcall;
  lstrcmpiW_MP: function(lpString1, lpString2: PWideChar): Integer; stdcall;
  lstrcmpW_MP: function(lpString1, lpString2: PWideChar): Integer; stdcall;
  lstrcpynW_MP: function(lpString1, lpString2: PWideChar; iMaxLength: Integer): PWideChar; stdcall;
  lstrcpyW_MP: function(lpString1, lpString2: PWideChar): PWideChar; stdcall;
  CharLowerBuffW_MP: function(lpsz: PWideChar; cchLength: DWORD): DWORD; stdcall;
  CharUpperBuffW_MP: function(lpsz: PWideChar; cchLength: DWORD): DWORD; stdcall;
  CreateDirectoryW_MP: function(lpPathName: PWideChar; lpSecurityAttributes: PSecurityAttributes): BOOL; stdcall;
  GetFullPathNameW_MP: function(lpFileName: PWideChar; nBufferLength: DWORD; lpBuffer: PWideChar; var lpFilePart: PWideChar): DWORD; stdcall;
  ShellExecuteExW_MP: function(lpExecInfo: PShellExecuteInfoW):BOOL; stdcall;
  ShellExecuteW_MP: function(hWnd: HWND; Operation, FileName, Parameters, Directory: PWideChar; ShowCmd: Integer): HINST; stdcall;
  FindFirstChangeNotificationW_MP: function(lpPathName: PWideChar; bWatchSubtree: BOOL; dwNotifyFilter: DWORD): THandle; stdcall;
  GetCharABCWidthsW_MP: function(DC: HDC; FirstChar, LastChar: UINT; const ABCStructs): BOOL; stdcall;
  GetFileAttributesW_MP: function(lpFileName: PWideChar): DWORD; stdcall;
  GetSystemDirectoryW_MP: function(lpBuffer: PWideChar; uSize: UINT): UINT; stdcall;
  GetWindowsDirectoryW_MP: function(lpBuffer: PWideChar; uSize: UINT): UINT; stdcall;
  SetWindowTextW_MP: function(hWnd: HWND; lpString: PWideChar): BOOL; stdcall;
  // Robert
  SHMultiFileProperties_MP: function(pdtobj: IDataObject; dwFlags: DWORD): HResult; stdcall;
  SHDoDragDrop_MP: function(wnd : HWND; dtObj : IDataObject; dsrc : IDropSource; OKEffect : DWORD; var Effect : Integer) : HResult; stdcall;
  GetDiskFreeSpaceExA_MP: function(lpDirectoryName: PAnsiChar;
    var lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes; lpTotalNumberOfFreeBytes: PLargeInteger): BOOL; stdcall;
  GetDiskFreeSpaceExW_MP: function(lpDirectoryName: PWideChar; var lpFreeBytesAvailableToCaller,
    lpTotalNumberOfBytes; lpTotalNumberOfFreeBytes: PLargeInteger): BOOL; stdcall;
  GetNumberFormatW_MP: function(Locale: LCID; dwFlags: DWORD; lpValue: PWideChar;
    lpFormat: PNumberFmtW; lpNumberStr: PWideChar; cchNumber: Integer): Integer; stdcall;
  CDefFolderMenu_Create2_MP: function(pidlFolder: PItemIdList; wnd: HWnd; cidl: uint; var apidl: PItemIdList; psf: IShellFolder; lpfn: TFNDFMCallback; nKeys: UINT; ahkeyClsKeys: PHKEY; var ppcm: IContextMenu): HRESULT; stdcall;
  CDefFolderMenu_Create_MP: function(pidlFolder: PItemIdList): HRESULT; stdcall; // THIS IS NOT THE RIGHT PROTOTYPE DO NOT USE FOR TEST ONLY
  RegOpenKeyW_MP: function(hKey: HKEY; lpSubKey: PWideChar; var phkResult: HKEY): Longint; stdcall;
  RegOpenKeyExW_MP: function(hKey: HKEY; lpSubKey: PWideChar; ulOptions: DWORD; samDesired: REGSAM; var phkResult: HKEY): Longint; stdcall;
  RegQueryValueW_MP: function(hKey: HKEY; lpSubKey: PWideChar; lpValue: PWideChar; var lpcbValue: Longint): Longint; stdcall;
  WritePrivateProfileStringW_MP: function(lpAppName, lpKeyName, lpString, lpFileName: PWideChar): BOOL; stdcall;
  GetPrivateProfileStringW_MP: function(lpAppName, lpKeyName, lpDefault: PWideChar; lpReturnedString: PWideChar; nSize: DWORD; lpFileName: PWideChar): DWORD; stdcall;
  TryEnterCriticalSection_MP: function(var lpCriticalSection: TRTLCriticalSection): BOOL; stdcall;
  CreateFontIndirectW_MP: function(const p1: TLogFontW): HFONT; stdcall;
  SendMessageW_MP: function(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
  InsertMenuItemW_MP: function(p1: HMENU; p2: UINT; p3: BOOL; const p4: TMenuItemInfoW): BOOL; stdcall;
  SetFileAttributesW_MP: function(lpFileName: PWideChar; dwFileAttributes: DWORD): BOOL; stdcall;
  SystemParametersInfoW_MP: function(uiAction, uiParam: UINT; pvParam: Pointer; fWinIni: UINT): BOOL; stdcall;
  SHBrowseForFolderW_MP: function(var lpbi: TBrowseInfoW): PItemIDList; stdcall;
  SHGetPathFromIDListW_MP: function(pidl: PItemIDList; pszPath: PWideChar): BOOL; stdcall;
  SHFileOperationW_MP: function(const lpFileOp: TSHFileOpStructW): Integer; stdcall;
  PathMatchSpecA_MP: function(const pszFileParam, pszSpec: PAnsiChar): Bool; stdcall;
  PathMatchSpecW_MP: function(const pszFileParam, pszSpec: PWideChar): Bool; stdcall;
  CreateProcessW_MP: function(lpApplicationName: PWideChar; lpCommandLine: PWideChar;
    lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
    bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
    lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfoW;
    var lpProcessInformation: TProcessInformation): BOOL; stdcall;
  DeleteVolumeMountPoint_MP: function(lpszVolumeMountPoint: LPCSTR): BOOL; stdcall;
  GetVolumeNameForVolumeMountPoint_MP: function(lpszVolumeMountPoint: LPCSTR; lpszVolumeName: LPSTR; cchBufferLength: DWORD): BOOL; stdcall;
  GetVolumePathName_MP: function(lpszFileName: LPCSTR; lpszVolumePathName: LPTSTR; cchBufferLength: DWORD): BOOL; stdcall;
  SetVolumeMountPoint_MP: function(lpszVolumeMountPoint: LPCSTR; lpszVolumeName: LPCSTR): BOOL;
  FindFirstVolume_MP: function(lpszVolumeName: LPTSTR; cchBufferLength: DWORD): THandle; stdcall;
  FindNextVolume_MP: function(hFindVolume: THandle; lpszVolumeName: LPTSTR; cchBufferLength: DWORD): BOOL; stdcall;
  FindVolumeClose_MP: function(hFindVolume: THandle): BOOL; stdcall;
  FindFirstVolumeMountPoint_MP: function(lpszRootPathName: LPTSTR; lpszVolumeMountPoint: LPTSTR; cchBufferLength: DWORD): THandle stdcall;
  FindNextVolumeMountPoint_MP: function(hFindVolumeMountPoint: THandle; lpszVolumeMountPoint: LPTSTR; cchBufferLength: DWORD): BOOL; stdcall;
  FindVolumeMountPointClose_MP: function(hFindVolumeMountPoint: THandle): BOOL; stdcall;
  SHGetKnownFolderPath_MP: function(const rfid: TGUID; dwFlags: DWord; hToken: THandle; out ppszPath: PWideChar): HRESULT; stdcall;
  FindFirstFileExW_MP: function(lpFileName: PWideChar; fInfoLevelId: DWORD; var lpFindFileData: TWIN32FindDataW; fSearchOp: DWORD; lpSearchFilter: Pointer; dwAdditionalFlags: DWORD): THandle; stdcall;
  FindFirstFileExA_MP: function(lpFileName: PChar; fInfoLevelId: DWORD; var lpFindFileData: TWIN32FindDataA; fSearchOp: DWORD; lpSearchFilter: Pointer; dwAdditionalFlags: DWORD): THandle; stdcall;
  ExpandEnvironmentStringsW_MP: function(lpSrc: PWideChar; lpDst: PWideChar; nSize: DWORD): DWORD; stdcall;
  TrackMouseEvent_MP: function(var EventTrack: TTrackMouseEvent): BOOL; stdcall;

  // Usage of this flag makes the SumFolder Function not thread safe
  SumFolderAbort: Boolean = False;

implementation

uses
  MPCommonObjects;

type
  PLibRec = ^TLibRec;
  TLibRec = packed record
    LibraryName: string;
    ReferenceCount: Integer;
    Handle: THandle;
  end;

var
  FLibList: TList;
  PIDLMgr: TCommonPIDLManager;
  Shell32Handle,
  Kernel32Handle,
  User32Handle,
  GDI32Handle,
  AdvAPI32Handle,
  ShlwapiHandle: THandle;


function ShiftStateToKeys(Keys: TShiftState): LongWord;
begin
  Result := 0;
  if ssShift in Keys then
    Result := Result or MK_SHIFT;
  if ssCtrl in Keys then
    Result := Result or MK_CONTROL;
  if ssLeft in Keys then
    Result := Result or MK_LBUTTON;
  if ssRight in Keys then
    Result := Result or MK_RBUTTON;
  if ssMiddle in Keys then
    Result := Result or MK_MBUTTON;
  if ssAlt in Keys then
    Result := Result or MK_ALT;
end;

function ShiftStateToStr(Keys: TShiftState): WideString;
begin
  Result := '[';
  if ssShift in Keys then
    Result := Result + 'ssShift, ';
  if ssCtrl in Keys then
    Result := Result + 'ssCtrl, ';
  if ssLeft in Keys then
    Result := Result + 'ssLeft, ';
  if ssRight in Keys then
    Result := Result + 'ssRight, ';
  if ssMiddle in Keys then
    Result := Result + 'ssMiddle, ';
  if ssAlt in Keys then
    Result := Result + 'ssAlt, ';
  if ssDouble in Keys then
    Result := Result + 'ssDouble, ';
  if Length(Result) > 1 then
    SetLength(Result, Length(Result) - 2);
  Result := Result + ']';
end;

function RegSvrPath: WideString;
const
  ExeName: WideString = 'RegSvr32.exe';
var
  Path: WideString;
begin
  Result:= '';
  //Look in System dir
  Path := SystemDirectory + '\' + ExeName;
  if FileExistsW(Path) then
    Result := Path
  else begin
    //Look in Windows dir
    Path:= WindowsDirectory + '\' + ExeName;
    if FileExistsW(Path) then
      Result:= Path;
  end
end;

function ExecShellEx(const Cmd, Params, Dir: WideString; ShowCmd: integer; DoWait: boolean): boolean;
var
  InfoA: TShellExecuteInfoA;
  InfoW: TShellExecuteInfoW;
  CmdA, ParamsA, DirA: string;
begin
  if Assigned(ShellExecuteExW_MP) then
  begin
    FillChar(InfoW, SizeOf(InfoW), 0);
    InfoW.cbSize := SizeOf(InfoW);
    InfoW.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS;
    InfoW.lpFile := PWideChar(Cmd);
    InfoW.lpParameters := PWideChar(Params);
    InfoW.lpDirectory := PWideChar(Dir);
    InfoW.nShow := ShowCmd;
    Result := ShellExecuteExW_MP(@InfoW);
    if Result and DoWait then
    begin
      WaitForSingleObject(InfoW.hProcess, INFINITE);
      CloseHandle(InfoW.hProcess)
    end;
  end else
  begin
    CmdA := Cmd;
    ParamsA := Params;
    DirA := Dir;
    FillChar(InfoA, SizeOf(InfoA), 0);
    InfoA.cbSize := SizeOf(InfoA);
    InfoA.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS;
    InfoA.lpFile := PChar(CmdA);
    InfoA.lpParameters := PChar(ParamsA);
    InfoA.lpDirectory := PChar(DirA);
    InfoA.nShow := ShowCmd;
    Result := ShellExecuteEx(@InfoA);
    if Result and DoWait then
    begin
      WaitForSingleObject(InfoA.hProcess, INFINITE);
      CloseHandle(InfoA.hProcess)
    end;
  end;
end;

function RegUnregNSE(const AFileName: WideString; DoRegister: boolean; AMessages: TEasyNSERegMessages = [enseMsgShowErrors]): boolean;
var
  Fn, Exe, Msg: WideString;
begin
  Result:= false;

  if not FileExistsW(AFileName) then
    begin
      if enseMsgShowErrors in AMessages then
      begin
        if DoRegister then
          Msg := SEasyNSEMsg_CannotRegister
        else
          Msg := SEasyNSEMsg_CannotUnRegister;

        
        WideMessageBox(Application.Handle,
                   PWideChar( Msg + #13 + SEasyNSEMsg_CannotFindDLL),
                   PWideChar( SEasyNSEMsg_Caption),
                   MB_OK or MB_ICONERROR);
      end;
      Exit
    end;

  Fn := AFileName;
  // If includes spaces then " " it
  if Pos(' ', Fn) > 0 then
    Fn := '"' + Fn + '"';

  Exe := RegSvrPath;

  if not FileExistsW(Exe) then
  begin
    if enseMsgShowErrors in AMessages then
    begin
      if DoRegister then
        Msg := SEasyNSEMsg_CannotRegister
      else
        Msg := SEasyNSEMsg_CannotUnRegister;

      MessageBoxW(Application.Handle,
                 PWideChar( Msg + #13 + SEasyNSEMsg_CannotFindRegSvr),
                 PWideChar( SEasyNSEMsg_Caption),
                 MB_OK or MB_ICONERROR);
    end;
    Exit
  end;

  if DoRegister then
    Msg := '' else
  Msg := '/U ';
  if not (enseMsgRegSvr in AMessages) then
    Msg := Msg + '/S ';

  Result:= ExecShellEx(exe, Msg + Fn, '', SW_SHOW, True);
end;

function RegisterNSE(const AFileName: WideString; AMessages: TEasyNSERegMessages = [enseMsgShowErrors]): boolean;
begin
  Result:= RegUnregNSE(AFileName, True, AMessages);
end;

function UnregisterNSE(const AFileName: WideString; AMessages: TEasyNSERegMessages = [enseMsgShowErrors]): boolean;
begin
  Result:= RegUnregNSE(AFileName, False, AMessages);
end;

{$IFNDEF COMPILER_6_UP}
function DirectoryExists(const Directory: string): Boolean;
var
  Code: Integer;
begin
  Code := GetFileAttributes(PChar(Directory));
  Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
end;
{$ENDIF}

{$IFNDEF COMPILER_6_UP}
function GUIDToString(const GUID: TGUID): string;
var
  P: PWideChar;
begin
  Result := '';
  if Succeeded(StringFromCLSID(GUID, P)) then
  begin
    Result := P;
    CoTaskMemFree(P);
  end
end;
{$ENDIF}

procedure MakeFindDataW(const FindFileDataA: TWIN32FindDataA; var FindFileDataW: TWIN32FindDataW);
//
// Makes TWIN32FindDataW from a TWIN32FindDataA structure
//
var
  i: Integer;
  WS: WideString;
begin
  FindFileDataW.dwFileAttributes := FindFileDataA.dwFileAttributes;
  FindFileDataW.ftCreationTime := FindFileDataA.ftCreationTime;
  FindFileDataW.ftLastAccessTime := FindFileDataA.ftLastAccessTime;
  FindFileDataW.ftLastWriteTime := FindFileDataA.ftLastWriteTime;
  FindFileDataW.nFileSizeHigh := FindFileDataA.nFileSizeHigh;
  FindFileDataW.nFileSizeLow := FindFileDataA.nFileSizeLow;
  FindFileDataW.dwReserved0 := FindFileDataA.dwReserved0;
  FindFileDataW.dwReserved1 := FindFileDataA.dwReserved1;
  FillChar(FindFileDataW.cAlternateFileName, SizeOf(FindFileDataW.cAlternateFileName), #0);
  FillChar(FindFileDataW.cFileName, SizeOf(FindFileDataW.cFileName), #0);
  i := 0;
  while FindFileDataA.cAlternateFileName[i] <> #0 do
  begin
    WS := FindFileDataA.cAlternateFileName[i];
    FindFileDataW.cAlternateFileName[i] := WS[1];
    Inc(i)
  end;
  i := 0;
  while FindFileDataA.cFileName[i] <> #0 do
  begin
    WS := FindFileDataA.cFileName[i];
    FindFileDataW.cFileName[i] := WS[1];
    Inc(i)
  end
end;

function WideDirectoryExists(const Name: WideString): Boolean;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideDirectoryExists(Name);
  {$ELSE}
  Result := DirectoryExists(Name)
  {$ENDIF}
end;

function WideCreateDir(Path: WideString): Boolean;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideCreateDir(Path);
  {$ELSE}
  Result := CreateDir(Path)
  {$ENDIF}
end;

function WideExcludeTrailingBackslash(Path: WideString): WideString;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideExcludeTrailingBackslash(Path);
  {$ELSE}
  {$IFDEF COMPILER_6_UP}
    {$WARN SYMBOL_PLATFORM OFF}
  {$ENDIF}
  Result := ExcludeTrailingBackslash(Path)
  {$IFDEF COMPILER_6_UP}
    {$WARN SYMBOL_PLATFORM ON}
  {$ENDIF}
  {$ENDIF}
end;

function WideExpandEnviromentString(EnviromentString: WideString): WideString;
var
  Length: Integer;
  EnviromentStringA, ResultA: string;
begin
  Result := EnviromentString;
  if Assigned(ExpandEnvironmentStringsW_MP) then
  begin
    Length := ExpandEnvironmentStringsW_MP(PWideChar( EnviromentString), nil, 0);
    if Length > 0 then
    begin
      SetLength(Result, Length - 1); // Includes the null
      ExpandEnvironmentStringsW_MP( PWideChar( EnviromentString), PWideChar( @Result[1]), Length);
    end
  end else
  begin
    EnviromentStringA := EnviromentString;
    Length := ExpandEnvironmentStringsA(PChar( EnviromentStringA), nil, 0);
    if Length > 0 then
    begin
      SetLength(ResultA, Length - 2); // There is a magic 1 per the MSDN docs for the ANSI version
      ExpandEnvironmentStringsA( PChar( EnviromentStringA), PChar( @ResultA[1]), Length);
      Result := ResultA
    end
  end
end;

function WideExtractFilePath(Path: WideString): WideString;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideExtractFilePath(Path);
  {$ELSE}
  Result := ExtractFilePath(Path);
  {$ENDIF}
end;

function WideExtractFileName(Path: WideString): WideString;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideExtractFileName(Path);
  {$ELSE}
  Result := ExtractFileName(Path);
  {$ENDIF}
end;

function WideFileExists(Path: WideString): Boolean;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideFileExists(Path);
  {$ELSE}
  Result := FileExists(Path);
  {$ENDIF}
end;

function WideExtractFileDir(Path: WideString): WideString;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideExtractFileDir(Path);
  {$ELSE}
  Result := ExtractFileDir(Path);
  {$ENDIF}
end;

function WideExtractFileDrive(Path: WideString): WideString;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideExtractFileDrive(Path);
  {$ELSE}
  Result := ExtractFileDrive(Path);
  {$ENDIF}
end;

function WideExtractFileExt(Path: WideString): WideString;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideExtractFileExt(Path);
  {$ELSE}
  Result := ExtractFileExt(Path);
  {$ENDIF}
end;

function WideFindFirstFileEx(FileName: WideString;
  var lpFindFileData: TWIN32FindDataW; Mask: WideString; CaseSensitive: Boolean): THandle;
var
  CaseFlag: DWORD;
  FindFileDataA: TWIN32FindDataA;
begin
  Result := 0;

  if CaseSensitive then
    CaseFlag := FIND_FIRST_EX_CASE_SENSITIVE
  else
    CaseFlag := 0;

  if Assigned(FindFirstFileExW_MP) then
    Result := FindFirstFileExW_MP(PWideChar(WideString( FileName + '\' + Mask)), FINDEX_INFO_STANDARD, lpFindFileData, FINDEX_SEARCH_NAMEMATCH, nil, CaseFlag)
  else
  if Assigned(FindFirstFileExA_MP) then
  begin
    Result := FindFirstFileExA_MP(PChar( String( FileName + '\' + Mask)), FINDEX_INFO_STANDARD, FindFileDataA, FINDEX_SEARCH_NAMEMATCH, nil, CaseFlag);
    MakeFindDataW(FindFileDataA, lpFindFileData)
  end
end;

function WideFindFirstFileExExists: Boolean;
begin
  Result := Assigned(FindFirstFileExA_MP) or Assigned(FindFirstFileExW_MP)
end;

procedure FixFormFont(AFont: TFont);
var
  LogFont: TLogFont;
begin
  if SystemParametersInfo(SPI_GETICONTITLELOGFONT, SizeOf(LogFont), @LogFont, 0) then
    AFont.Handle := CreateFontIndirect(LogFont)
  else
    AFont.Handle := GetStockObject(DEFAULT_GUI_FONT);
end;

procedure FillGradient(X1, Y1, X2, Y2: integer; fStartColor, fStopColor: TColor;
  StartPoint, EndPoint: integer; fDrawCanvas: TCanvas);
// X1, Y1, X2, Y2: TopLeft and BottomRight coordinates of fill area...
//fStartColor: color to begin the gradient fill with
//FStopColor: color to end the gradient fill with
//StartPoint: the first point between X1 and X2 to draw (useful for faster updating of
// specific areas instead of redrawing the entire gradient fill area such as progress bars)
//EndPoint: the last point between X1 and X2 to draw
//fDrawCanvas: the canvas to draw the gradient on
var
  y: integer;
  tmpColor: TColor;

begin
  fStartColor := ColorToRGB(fStartColor);
  fStopColor := ColorToRGB(fStopColor);
  tmpColor := fDrawCanvas.Pen.Color;
  for y := Y1 to Y2 do begin
    fDrawCanvas.MoveTo(X1, y);
    if (EndPoint > 0) and (y <= EndPoint) and (y >= StartPoint) then begin
      fDrawCanvas.Pen.Color := RGB(Round(GetRValue(fStartColor) + (((GetRValue(fStopColor) - GetRValue(fStartColor)) / (Y2 - Y1)) *Abs(y - Y1))),
          Round(GetGValue(fStartColor) + (((GetGValue(fStopColor) - GetGValue(fStartColor)) / (Y2 - Y1)) * Abs(y - Y1))),
          Round(GetBValue(fStartColor) + (((GetBValue(fStopColor) - GetBValue(fStartColor)) / (y2 - Y1)) * Abs(y - Y1))));
      fDrawCanvas.LineTo(X2, y);
    end;
  end;
  fDrawCanvas.Brush.Color := tmpColor;
end;

function DropEffectToStr(DropEffect: DWORD): WideString;
begin
  Result := '';
  if DropEffect and DROPEFFECT_COPY <> 0 then
    Result := Result + 'DROPEFFECT_COPY|';
  if DropEffect and DROPEFFECT_MOVE <> 0 then
    Result := Result + 'DROPEFFECT_MOVE|';
  if DropEffect and DROPEFFECT_LINK <> 0 then
    Result := Result + 'DROPEFFECT_LINK|';
  if DropEffect and DROPEFFECT_SCROLL <> 0 then
    Result := Result + 'DROPEFFECT_SCROLL|';
  if Length(Result) > 0 then
    SetLength(Result, Length(Result) - 1)
  else
    Result := '(none)';
end;

function EqualWndMethod(A, B: TWndMethod): Boolean;
begin
  Result := (TMethod(A).Code = TMethod(B).Code) and
            (TMethod(A).Data = TMethod(B).Data)
end;

function FindRootToken(const Path: WideString): PWideChar;
const
  RootToken = WideString(':\');
begin
  Result := WideStrPos(PWideChar(Path), RootToken);
end;

function ExtractFileDirW(const FileName: WideString): WideString;
var
  WP: PWideChar;
begin
  Result := '';
  if (Length(FileName) < 3) and (Length(FileName) > 0) then
  begin
    if (((FileName[1] >= 'A') and (FileName[1] >= 'Z')) or
    ((FileName[1] >= 'a') and (FileName[1] >= 'z'))) then
    begin
      Result := WideString(FileName[1]) + ':\';
    end
  end else
  begin
    WP := FindRootToken(FileName);
    if Assigned(WP) then
    begin
      { Find the last '\' }
      WP := WideStrRScan(PWideChar( FileName), WideChar( '\'));
      if Assigned(WP) then
      begin
        { The stripped file name leaves just the root directory }
        if (Length(FileName) > 1) and ( (WP - 1)^ = ':') then
          WP := WP + 1;  // Tack on the '\'
        SetLength(Result, WP - @FileName[1]);
        WideStrMove(PWideChar(Result), PWideChar(FileName), WP - @FileName[1]);
      end
    end
  end
end;

function DirExistsW(const FileName: PWideChar): Boolean;
var
  ErrorCode: LongWord;
  S: string;
begin
  if FileName <> '' then
  begin
    if (Win32Platform = VER_PLATFORM_WIN32_NT) then
      ErrorCode := GetFileAttributesW_MP(FileName)
    else begin
      S := FileName;
      ErrorCode := GetFileAttributesA(PChar(S))
    end;
    Result := (Integer(ErrorCode) <> -1) and (FILE_ATTRIBUTE_DIRECTORY and ErrorCode <> 0);
  end else
    Result := False
end;

function FlipReverseCopyRect(const Flip, Reverse: Boolean; const Bitmap: TBitmap): TBitmap;
var
  Bottom, Left, Right, Top:  integer;
begin
  Result := TBitmap.Create;
  Result.Width := Bitmap.Width;
  Result.Height := Bitmap.Height;
  Result.PixelFormat := Bitmap.PixelFormat;

  // Flip Top to Bottom
  if Flip then
  begin
    // Unclear why extra "-1" is needed here.
    Top  := Bitmap.Height-1;
    Bottom := -1
  end
  else begin
    Top  := 0;
    Bottom := Bitmap.Height
  end;

  // Reverse Left to Right
  if Reverse then
  begin
    // Unclear why extra "-1" is needed here.
    Left := Bitmap.Width-1;
    Right := -1;
  end
  else begin
    Left := 0;
    Right := Bitmap.Width;
  end;

  Result.Canvas.CopyRect(Rect(Left,Top, Right,Bottom),
                         Bitmap.Canvas,
                         Rect(0,0, Bitmap.Width,Bitmap.Height));
end;

procedure FlipReverseCopyRect(const Flip, Reverse: Boolean; R: TRect; const Canvas: TCanvas); overload;
var
  Bottom, Left, Right, Top:  integer;
begin
  // Flip Top to Bottom
  if Flip then
  begin
    // Unclear why extra "-1" is needed here.
    Top  := RectHeight(R)-1;
    Bottom := -1
  end
  else begin
    Top  := 0;
    Bottom := RectHeight(R)
  end;

  // Reverse Left to Right
  if Reverse then
  begin
    // Unclear why extra "-1" is needed here.
    Left := RectWidth(R)-1;
    Right := -1;
  end
  else begin
    Left := 0;
    Right := RectWidth(R);
  end;

  Canvas.CopyRect(Rect(Left, Top, Right, Bottom),
                         Canvas,
                         Rect(0,0, RectWidth(R), RectHeight(R)));
end;

function FileExistsW(const FileName: WideString): Boolean;
var
  Handle: THandle;
  FindData: TWin32FindData;
  FindDataW: TWin32FindDataW;
  FileNameA: string;
begin
  Result := True;
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    Handle := FindFirstFileW_MP(PWideChar(FileName), FindDataW)
  else begin
    FileNameA := FileName;
    Handle := FindFirstFileA(PChar(FileNameA), FindData)
  end;
  if Handle <> INVALID_HANDLE_VALUE then
    Windows.FindClose(Handle)
  else
    Result := False;
end;

function IsMappedDrivePath(const Path: WideString): Boolean;
var
  WS: WideString;
begin
  WS := Path;
  SetLength(WS, 3);
  if IsWinNT then
    Result := GetDriveTypeW_MP(PWideChar(WS)) = DRIVE_REMOTE
  else
    Result := GetDriveType(PChar(string(WS))) = DRIVE_REMOTE;
end;

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

function FindUniqueMenuID(AMenu: HMenu): Cardinal;


    function RunMenu(AMenu: HMenu; var ID: Cardinal): Boolean;
    var
      MenuInfoW: TMenuItemInfoW;
      MenuInfoA: TMenuItemInfoA;
      i, ItemCount: Integer;
      Reset, IsDuplicate: Boolean;
    begin
      Reset := False;
      IsDuplicate := False;
      ItemCount := GetMenuItemCount(AMenu);
      i := 0;
      while (i < ItemCount) and not IsDuplicate do
      begin
        if IsUnicode then
        begin
          FillChar(MenuInfoW, SizeOf(MenuInfoW), #0);
          MenuInfoW.cbSize := SizeOf(MenuInfoW);
          MenuInfoW.fMask := MIIM_SUBMENU or MIIM_ID;
          GetMenuItemInfoW(AMenu, i, True, MenuInfoW);
           if MenuInfoW.hSubMenu <> 0 then
            Reset := RunMenu(MenuInfoW.hSubMenu, ID);
           IsDuplicate := MenuInfoW.wID = ID
        end else
        begin
          FillChar(MenuInfoA, SizeOf(MenuInfoA), #0);
          MenuInfoA.cbSize := SizeOf(MenuInfoA);
          MenuInfoA.fMask := MIIM_SUBMENU or MIIM_ID;
          GetMenuItemInfoA(AMenu, i, True, MenuInfoA);
          if MenuInfoA.hSubMenu <> 0 then
            Reset := RunMenu(MenuInfoA.hSubMenu, ID);
          IsDuplicate := MenuInfoA.wID = ID
        end;
        Inc(i);
      end;
      Result := IsDuplicate and not Reset
    end;

begin
  Result := 1000;
  while RunMenu(AMenu, Result) do
    Inc(Result)
end;

function AddContextMenuItem(Menu: HMenu; ACaption: WideString; Index: Integer;
  MenuID: UINT = $FFFF; hSubMenu: UINT = 0; Enabled: Boolean = True;
  Checked: Boolean = False; Default: Boolean = False): Integer;
//
// Pass '-' for a separator
//      -1 to add to the end
//      if MenuID = -1 then the function will create a unique ID
//      if hSubMenu > 0 then the item contain sub-items
// Returns ID of new Item
//
var
  InfoA: TMenuItemInfoA;
  InfoW: TMenuItemInfoW;
begin
  if IsUnicode and Assigned(InsertMenuItemW_MP) then
  begin
    FillChar(InfoW, SizeOf(InfoW), #0);
    InfoW.cbSize := SizeOf(InfoW);
    InfoW.fMask := MIIM_TYPE or MIIM_ID or MIIM_STATE;

    if Enabled or (ACaption = '-') then
      InfoW.fState := InfoW.fState or MFS_ENABLED
    else
      InfoW.fState := InfoW.fState or MFS_DISABLED;

    if Checked and (ACaption <> '-') then
      InfoW.fState := InfoW.fState or MFS_CHECKED;
    if Default and (ACaption <> '-') then
      InfoW.fState := InfoW.fState or MFS_DEFAULT;

    if ACaption = '-' then
      InfoW.fType := MFT_SEPARATOR
    else begin
      InfoW.fType := MFT_STRING;
      if hSubMenu > 0 then
      begin
        InfoW.fMask := InfoW.fMask or MIIM_SUBMENU;
        InfoW.hSubMenu := hSubMenu
      end
    end;
    InfoW.dwTypeData := PWideChar(ACaption);
    InfoW.cch := Length(ACaption);

    if InfoW.fType = MFT_STRING then
    begin
      if MenuID = $FFFF then
        InfoW.wID := FindUniqueMenuID(Menu)
      else
      if InfoW.fMask and MIIM_SUBMENU <> 0 then
        InfoW.wID := $FFFF // Sub-Item Parents don't get an unique ID
      else
        InfoW.wID := MenuID;
    end else
      InfoW.wID := $FFFF; // Separators don't get an unique ID

    Result := InfoW.wID;
    if Index < 0 then
      InsertMenuItemW_MP(Menu, GetMenuItemCount(Menu), True, InfoW)
    else
      InsertMenuItemW_MP(Menu, Index, True, InfoW);  // Inserts by Position
  end else
  begin
    FillChar(InfoA, SizeOf(InfoA), #0);
    InfoA.cbSize := SizeOf(InfoA);
    
    InfoA.fMask := MIIM_TYPE or MIIM_ID or MIIM_STATE;
    if Enabled or (ACaption = '-') then
      InfoA.fState := InfoA.fState or MFS_ENABLED
    else
      InfoA.fState := InfoA.fState or MFS_DISABLED;

    if Checked and (ACaption <> '-') then
      InfoA.fState := InfoA.fState or MFS_CHECKED;
    if Default and (ACaption <> '-') then
      InfoA.fState := InfoA.fState or MFS_DEFAULT;

    if ACaption = '-' then
      InfoA.fType := MFT_SEPARATOR
    else begin
      InfoA.fType := MFT_STRING;
      if hSubMenu > 0 then
      begin
        InfoA.fMask := InfoA.fMask or MIIM_SUBMENU;
        InfoA.hSubMenu := hSubMenu
      end
    end;

    InfoA.dwTypeData := PChar( string(ACaption));
    InfoA.cch := Length(ACaption);

    if InfoA.fType = MFT_STRING then
    begin
      if MenuID = $FFFF then
        InfoA.wID := FindUniqueMenuID(Menu)
      else
      if InfoA.fMask and MIIM_SUBMENU <> 0 then
        InfoA.wID := $FFFF // Sub-Item Parents don't get an unique ID
      else
        InfoA.wID := MenuID;
    end else
      InfoA.wID := $FFFF; // Separators don't get an unique ID

    Result := InfoA.wID;
    if Index < 0 then
      InsertMenuItemA(Menu, GetMenuItemCount(Menu), True, InfoA)
    else
      InsertMenuItemA(Menu, Index, True, InfoA);
  end
end;

procedure ShadowBlendBits(Bits: TBitmap; BackGndColor: TColor);
begin
  if Assigned(Bits) and (Bits.PixelFormat = pf32Bit) then
  begin
    AlphaBlend(Bits.Canvas.Handle, Bits.Canvas.Handle,
          Rect(0, 0, Bits.Width,Bits.Height), Point(0, 0),
          cbmConstantAlphaAndColor, 0, ColorToRGB(BackGndColor));
    ConvertBitmapEx(Bits, Bits, BackGndColor)
  end
end;


procedure SumFolder(FolderPath: WideString; Recurse: Boolean; var Size: Int64);
{ Returns the size of all files within the passed folder, including all         }
{ sub-folders. This is recurcive don't initialize Size to 0 in the function!    }
var
  Info: TWin32FindData;
  InfoW: TWin32FindDataW;
  FHandle: THandle;
  FolderPathA: string;
begin
  if IsUnicode then
  begin
    FHandle := FindFirstFileW_MP(PWideChar( FolderPath + '\*.*'), InfoW);
    if FHandle <> INVALID_HANDLE_VALUE then
    try
      if Recurse and (InfoW.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0) then
      begin
        if (lstrcmpiW_MP(InfoW.cFileName, '.') <> 0) and (lstrcmpiW_MP(InfoW.cFileName, '..') <> 0) and
          (InfoW.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT = 0) then
          SumFolder(FolderPath + '\' + InfoW.cFileName, Recurse, Size)
      end else
        Size := Size + InfoW.nFileSizeHigh * MAXDWORD + InfoW.nFileSizeLow;
      while FindNextFileW_MP(FHandle, InfoW) and not SumFolderAbort do
      begin
        if Recurse and (InfoW.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0) then
        begin
          if (lstrcmpiW_MP(InfoW.cFileName, '.') <> 0) and (lstrcmpiW_MP(InfoW.cFileName, '..') <> 0) and
            (InfoW.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT = 0) then
           SumFolder(FolderPath + '\' + InfoW.cFileName, Recurse, Size)
        end else
         Size := Size + InfoW.nFileSizeHigh * MAXDWORD + InfoW.nFileSizeLow;
      end;
    finally
      Windows.FindClose(FHandle)
    end
  end else
  begin
    FolderPathA := FolderPath;
    FHandle := FindFirstFile(PChar( FolderPathA + '\*.*'), Info);
    if FHandle <> INVALID_HANDLE_VALUE then
    try
      if Recurse and (Info.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0) then
      begin
        if (lstrcmpi(Info.cFileName, '.') <> 0) and (lstrcmpi(Info.cFileName, '..') <> 0) and
            (InfoW.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT = 0) then
          SumFolder(FolderPathA + '\' + Info.cFileName, Recurse, Size)
      end else
        Size := Size + Info.nFileSizeHigh * MAXDWORD + Info.nFileSizeLow;
      while FindNextFile(FHandle, Info) and not SumFolderAbort do
      begin
        if Recurse and (Info.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0) then
        begin
          if (lstrcmpi(Info.cFileName, '.') <> 0) and (lstrcmpi(Info.cFileName, '..') <> 0) and
            (InfoW.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT = 0) then
            SumFolder(FolderPathA + '\' + Info.cFileName, Recurse, Size)
        end else
          Size := Size + Info.nFileSizeHigh * MAXDWORD + Info.nFileSizeLow;
      end;
    finally
      Windows.FindClose(FHandle)
    end
  end
end;

function InternalTextExtentW(Text: PWideChar; DC: HDC): TSize;
var
  S: string;
begin
  if IsUnicode then
    GetTextExtentPoint32W(DC, PWideChar(Text), Length(Text), Result)
  else begin
    S := WideString( Text);
    GetTextExtentPoint32(DC, PChar(S), Length(S), Result)
  end;
end;

function WideStrMove(Dest, Source: PWideChar; Count: Cardinal): PWideChar;
// Copies the specified number of characters to the destination string and returns Dest
// also as result. Dest must have enough room to store at least Count characters.
asm
       PUSH    ESI
       PUSH    EDI
       MOV     ESI, EDX
       MOV     EDI, EAX
       MOV     EDX, ECX
       CMP     EDI, ESI
       JG      @@1
       JE      @@2
       SHR     ECX, 1
       REP     MOVSD
       MOV     ECX, EDX
       AND     ECX, 1
       REP     MOVSW
       JMP     @@2

@@1:
       LEA     ESI, [ESI + 2 * ECX - 2]
       LEA     EDI, [EDI + 2 * ECX - 2]
       STD
       AND     ECX, 1
       REP     MOVSW
       SUB     EDI, 2
       SUB     ESI, 2
       MOV     ECX, EDX
       SHR     ECX, 1
       REP     MOVSD
       CLD
@@2:
       POP EDI
       POP ESI
end;

function WideStrRScan(Str: PWideChar; Chr: WideChar): PWideChar;
// returns a pointer to the last occurance of Chr in Str
asm
       PUSH    EDI
       MOV     EDI, Str
       MOV     ECX, 0FFFFFFFFH
       XOR     AX, AX
       REPNE   SCASW
       NOT     ECX
       STD
       SUB     EDI, 2
       MOV     AX, Chr
       REPNE   SCASW
       MOV     EAX, 0
       JNE     @@1
       MOV     EAX, EDI
       ADD     EAX, 2
@@1:
       CLD
       POP     EDI
end;

function WideStrScan(Str: PWideChar; Chr: WideChar): PWideChar;
// returns a pointer to first occurrence of a specified character in a string
asm
        PUSH    EDI
        PUSH    EAX
        MOV     EDI, Str
        MOV     ECX, 0FFFFFFFFH
        XOR     AX, AX
        REPNE   SCASW
        NOT     ECX
        POP     EDI
        MOV     AX, Chr
        REPNE   SCASW
        MOV     EAX, 0
        JNE     @@1
        MOV     EAX, EDI
        SUB     EAX, 2
@@1:
        POP     EDI
end;

function WideUpperCase(const S: WideString): WideString;
begin
  Result := S;
  if IsUnicode then
    CharUpperBuffW_MP(PWideChar(Result), Length(Result))
  else
   CharUpperBuffA(PChar(string(Result)), Length(Result))
end;

function LibList: TList;
begin
  if not Assigned(FLibList) then
    FLibList := TList.Create;
  Result := FLibList
end;

function CommonLoadLibrary(LibraryName: string): THandle;
var
  i: Integer;
  Found: Boolean;
  LibRec: PLibRec;
begin
  Result := 0;
  Found := False;
  i := 0;
  while (i < LibList.Count) and not Found do
  begin
    LibRec := PLibRec(LibList[i]);
    if lstrcmpi(PChar(LibRec.LibraryName), PChar(LibraryName)) = 0 then
    begin
      Inc(LibRec.ReferenceCount);
      Result := LibRec.Handle;
      Found := True
    end;
    Inc(i)
  end;
  if not Found then
  begin
    New(LibRec);
    LibRec.Handle := LoadLibrary(PChar(LibraryName));
    if LibRec.Handle <> 0 then
    begin
      LibRec.LibraryName := LibraryName;
      LibRec.ReferenceCount := 1;
      LibList.Add(LibRec);
      Result := LibRec.Handle
    end else
    Dispose(LibRec)
  end
end;

function CommonUnloadLibrary(LibraryName: string): Boolean;
var
  i: Integer;
  LibRec: PLibRec;
begin
  Result := False;
  i := 0;
  while (i < LibList.Count) and not Result do
  begin
    LibRec := PLibRec(LibList[i]);
    if lstrcmpi(PChar(LibRec.LibraryName), PChar(LibraryName)) = 0 then
    begin
      Dec(LibRec.ReferenceCount);
      FreeLibrary(LibRec.Handle);
      if LibRec.ReferenceCount = 0 then
      begin
        LibList.Delete(i);
        Dispose(LibRec);
      end;
      Result := True
    end;
    Inc(i)
  end;
  if LibList.Count = 0 then
    FreeAndNil(FLibList)
end;

procedure CommonUnloadAllLibraries;
var
  i, LibIndex: Integer;
  LibRec: PLibRec;
begin
  i := 0;
  while i < LibList.Count do
  begin
    LibRec := PLibRec(LibList[i]);
    for LibIndex := 0 to LibRec.ReferenceCount - 1 do
      FreeLibrary(LibRec.Handle);
    LibList.Delete(i);
    Dispose(LibRec);
    Inc(i)
  end;
  FreeAndNil(FLibList);
end;

procedure DrawRadioButton(Canvas: TCanvas; Pos: TPoint; Size: Integer; clBackground, clHotBkGnd,
  clLeftOuter, clRightOuter, clLeftInner, clRightInner: TColor; Checked, Enabled, Hot: Boolean);
begin
  MarlettFont.Size := Size;
  Canvas.Brush.Style := bsClear;
  Canvas.Font.Assign(MarlettFont);

    // Draw the background
  if Hot then
    Canvas.Font.Color := clHotBkGnd
  else
    Canvas.Font.Color := clBackground;
  Canvas.TextOut(Pos.X, Pos.Y, 'n');

  Canvas.Brush.Style := bsClear;
  // Draw the Outer Circle
  Canvas.Font.Color := clLeftOuter;
  Canvas.TextOut(Pos.X, Pos.Y, 'j');
  Canvas.Font.Color := clRightOuter;
  Canvas.TextOut(Pos.X, Pos.Y, 'k');
  // Draw the Inner Circle
  Canvas.Font.Color := clLeftInner;
  Canvas.TextOut(Pos.X, Pos.Y, 'l');
  Canvas.Font.Color := clRightInner;
  Canvas.TextOut(Pos.X, Pos.Y, 'm');
  if Checked then
  begin
    if Enabled then
      Canvas.Font.Color := clBlack
    else
      Canvas.Font.Color := clBtnShadow;
    Canvas.TextOut(Pos.X, Pos.Y, 'i');
  end
end;

procedure DrawCheckBox(Canvas: TCanvas; Pos: TPoint; Size: Integer; clBackground, clHotBkGnd,
  clLeftOuter, clRightOuter, clLeftInner, clRightInner: TColor; Checked, Enabled, Hot: Boolean);
begin
  MarlettFont.Size := Size;
  Canvas.Brush.Style := bsSolid;
  Canvas.Font.Assign(MarlettFont);

  // Draw the background
  if Hot then
    Canvas.Font.Color := clHotBkGnd
  else
    Canvas.Font.Color := clBackground;
  Canvas.TextOut(Pos.X, Pos.Y, Char($67));

  Canvas.Brush.Style := bsClear;
  // Draw the Outer Frame
  Canvas.Font.Color := clLeftOuter;
  Canvas.TextOut(Pos.X, Pos.Y, Char($63));
  Canvas.Font.Color := clRightOuter;
  Canvas.TextOut(Pos.X, Pos.Y, Char($64));
  // Draw the Inner Frame
  Canvas.Font.Color := clLeftInner;
  Canvas.TextOut(Pos.X, Pos.Y, Char($65));
  Canvas.Font.Color := clRightInner;
  Canvas.TextOut(Pos.X, Pos.Y, Char($66));
  if Checked then
  begin
    if Enabled then
      Canvas.Font.Color := clBlack
    else
      Canvas.Font.Color := clBtnShadow;
    Canvas.TextOut(Pos.X, Pos.Y, Char($62));
  end
end;

function CheckBounds(Size: Integer): TRect;
var
  Canvas: TCanvas;
begin
  Result := Rect(0, 0, 0, 0);
  Canvas := TCanvas.Create;
  try
    Canvas.Handle := GetDC(0);
    Canvas.Font.Name := 'Marlett';
    Canvas.Font.Size := Size;
    // Use the background for the Checkbox for the size, the Radio will be this
    // size or a bit smaller
    Result.Right := Canvas.TextWidth(Char($67));
    Result.Bottom := Canvas.TextHeight(Char($67));
  finally
    if Assigned(Canvas) then
    begin
      ReleaseDC(0, Canvas.Handle);
      Canvas.Handle := 0
    end;
    Canvas.Free
  end;
end;


function HasMMX: Boolean;

// Helper method to determine whether the current processor supports MMX.

asm
        PUSH    EBX
        XOR     EAX, EAX     // Result := False
        PUSHFD               // determine if the processor supports the CPUID command
        POP     EDX
        MOV     ECX, EDX
        XOR     EDX, $200000
        PUSH    EDX
        POPFD
        PUSHFD
        POP     EDX
        XOR     ECX, EDX
        JZ      @1           // no CPUID support so we can't even get to the feature information 
        PUSH    EDX
        POPFD

        MOV     EAX, 1
        DW      $A20F        // CPUID, EAX contains now version info and EDX feature information
        MOV     EBX, EAX     // free EAX to get the result value
        XOR     EAX, EAX     // Result := False
        CMP     EBX, $50
        JB      @1           // if processor family is < 5 then it is not a Pentium class processor
        TEST    EDX, $800000
        JZ      @1           // if the MMX bit is not set then we don't have MMX
        INC     EAX          // Result := True
@1:
        POP     EBX
end;

procedure AlphaBlendLineConstant(Source, Destination: Pointer; Count: Integer; ConstantAlpha, Bias: Integer);
// Blends a line of Count pixels from Source to Destination using a constant alpha value.
// The layout of a pixel must be BGRA where A is ignored (but is calculated as the other components).
// ConstantAlpha must be in the range 0..255 where 0 means totally transparent (destination pixel only)
// and 255 totally opaque (source pixel only).
// Bias is an additional value which gets added to every component and must be in the range -128..127
//
// EAX contains Source
// EDX contains Destination
// ECX contains Count
// ConstantAlpha and Bias are on the stack
asm
        PUSH    ESI                    // save used registers
        PUSH    EDI

        MOV     ESI, EAX               // ESI becomes the actual source pointer
        MOV     EDI, EDX               // EDI becomes the actual target pointer

        // Load MM6 with the constant alpha value (replicate it for every component).
        // Expand it to word size.
        MOV     EAX, [ConstantAlpha]
        DB      $0F, $6E, $F0          /// MOVD      MM6, EAX
        DB      $0F, $61, $F6          /// PUNPCKLWD MM6, MM6
        DB      $0F, $62, $F6          /// PUNPCKLDQ MM6, MM6

        // Load MM5 with the bias value.
        MOV     EAX, [Bias]
        DB      $0F, $6E, $E8          /// MOVD      MM5, EAX
        DB      $0F, $61, $ED          /// PUNPCKLWD MM5, MM5
        DB      $0F, $62, $ED          /// PUNPCKLDQ MM5, MM5

        // Load MM4 with 128 to allow for saturated biasing.
        MOV     EAX, 128
        DB      $0F, $6E, $E0          /// MOVD      MM4, EAX
        DB      $0F, $61, $E4          /// PUNPCKLWD MM4, MM4
        DB      $0F, $62, $E4          /// PUNPCKLDQ MM4, MM4

@1:     // The pixel loop calculates an entire pixel in one run.
        // Note: The pixel byte values are expanded into the higher bytes of a word due
        //       to the way unpacking works. We compensate for this with an extra shift.
        DB      $0F, $EF, $C0          /// PXOR      MM0, MM0,   clear source pixel register for unpacking
        DB      $0F, $60, $06          /// PUNPCKLBW MM0, [ESI], unpack source pixel byte values into words
        DB      $0F, $71, $D0, $08     /// PSRLW     MM0, 8,     move higher bytes to lower bytes
        DB      $0F, $EF, $C9          /// PXOR      MM1, MM1,   clear target pixel register for unpacking
        DB      $0F, $60, $0F          /// PUNPCKLBW MM1, [EDI], unpack target pixel byte values into words
        DB      $0F, $6F, $D1          /// MOVQ      MM2, MM1,   make a copy of the shifted values, we need them again
        DB      $0F, $71, $D1, $08     /// PSRLW     MM1, 8,     move higher bytes to lower bytes

        // calculation is: target = (alpha * (source - target) + 256 * target) / 256
        DB      $0F, $F9, $C1          /// PSUBW     MM0, MM1,   source - target
        DB      $0F, $D5, $C6          /// PMULLW    MM0, MM6,   alpha * (source - target)
        DB      $0F, $FD, $C2          /// PADDW     MM0, MM2,   add target (in shifted form)
        DB      $0F, $71, $D0, $08     /// PSRLW     MM0, 8,     divide by 256

        // Bias is accounted for by conversion of range 0..255 to -128..127,
        // doing a saturated add and convert back to 0..255.
        DB      $0F, $F9, $C4          /// PSUBW     MM0, MM4
        DB      $0F, $ED, $C5          /// PADDSW    MM0, MM5
        DB      $0F, $FD, $C4          /// PADDW     MM0, MM4
        DB      $0F, $67, $C0          /// PACKUSWB  MM0, MM0,   convert words to bytes with saturation
        DB      $0F, $7E, $07          /// MOVD      [EDI], MM0, store the result
@3:
        ADD     ESI, 4
        ADD     EDI, 4
        DEC     ECX
        JNZ     @1
        POP     EDI
        POP     ESI
end;

procedure AlphaBlendLinePerPixel(Source, Destination: Pointer; Count, Bias: Integer);
// Blends a line of Count pixels from Source to Destination using the alpha value of the source pixels.
// The layout of a pixel must be BGRA.
// Bias is an additional value which gets added to every component and must be in the range -128..127
//
// EAX contains Source
// EDX contains Destination
// ECX contains Count
// Bias is on the stack
asm
        PUSH    ESI                    // save used registers
        PUSH    EDI

        MOV     ESI, EAX               // ESI becomes the actual source pointer
        MOV     EDI, EDX               // EDI becomes the actual target pointer

        // Load MM5 with the bias value.
        MOV     EAX, [Bias]
        DB      $0F, $6E, $E8          /// MOVD      MM5, EAX
        DB      $0F, $61, $ED          /// PUNPCKLWD MM5, MM5
        DB      $0F, $62, $ED          /// PUNPCKLDQ MM5, MM5

        // Load MM4 with 128 to allow for saturated biasing.
        MOV     EAX, 128
        DB      $0F, $6E, $E0          /// MOVD      MM4, EAX
        DB      $0F, $61, $E4          /// PUNPCKLWD MM4, MM4
        DB      $0F, $62, $E4          /// PUNPCKLDQ MM4, MM4

@1:     // The pixel loop calculates an entire pixel in one run.
        // Note: The pixel byte values are expanded into the higher bytes of a word due
        //       to the way unpacking works. We compensate for this with an extra shift.
        DB      $0F, $EF, $C0          /// PXOR      MM0, MM0,   clear source pixel register for unpacking
        DB      $0F, $60, $06          /// PUNPCKLBW MM0, [ESI], unpack source pixel byte values into words
        DB      $0F, $71, $D0, $08     /// PSRLW     MM0, 8,     move higher bytes to lower bytes
        DB      $0F, $EF, $C9          /// PXOR      MM1, MM1,   clear target pixel register for unpacking
        DB      $0F, $60, $0F          /// PUNPCKLBW MM1, [EDI], unpack target pixel byte values into words
        DB      $0F, $6F, $D1          /// MOVQ      MM2, MM1,   make a copy of the shifted values, we need them again
        DB      $0F, $71, $D1, $08     /// PSRLW     MM1, 8,     move higher bytes to lower bytes

        // Load MM6 with the source alpha value (replicate it for every component).
        // Expand it to word size.
        DB      $0F, $6F, $F0          /// MOVQ MM6, MM0
        DB      $0F, $69, $F6          /// PUNPCKHWD MM6, MM6
        DB      $0F, $6A, $F6          /// PUNPCKHDQ MM6, MM6

        // calculation is: target = (alpha * (source - target) + 256 * target) / 256
        DB      $0F, $F9, $C1          /// PSUBW     MM0, MM1,   source - target
        DB      $0F, $D5, $C6          /// PMULLW    MM0, MM6,   alpha * (source - target)
        DB      $0F, $FD, $C2          /// PADDW     MM0, MM2,   add target (in shifted form)
        DB      $0F, $71, $D0, $08     /// PSRLW     MM0, 8,     divide by 256

        // Bias is accounted for by conversion of range 0..255 to -128..127,
        // doing a saturated add and convert back to 0..255.
        DB      $0F, $F9, $C4          /// PSUBW     MM0, MM4
        DB      $0F, $ED, $C5          /// PADDSW    MM0, MM5
        DB      $0F, $FD, $C4          /// PADDW     MM0, MM4
        DB      $0F, $67, $C0          /// PACKUSWB  MM0, MM0,   convert words to bytes with saturation
        DB      $0F, $7E, $07          /// MOVD      [EDI], MM0, store the result
@3:
        ADD     ESI, 4
        ADD     EDI, 4
        DEC     ECX
        JNZ     @1
        POP     EDI
        POP     ESI
end;

procedure AlphaBlendLineMaster(Source, Destination: Pointer; Count: Integer; ConstantAlpha, Bias: Integer);
// Blends a line of Count pixels from Source to Destination using the source pixel and a constant alpha value.
// The layout of a pixel must be BGRA.
// ConstantAlpha must be in the range 0..255.
// Bias is an additional value which gets added to every component and must be in the range -128..127
//
// EAX contains Source
// EDX contains Destination
// ECX contains Count
// ConstantAlpha and Bias are on the stack
asm
        PUSH    ESI                    // save used registers
        PUSH    EDI

        MOV     ESI, EAX               // ESI becomes the actual source pointer
        MOV     EDI, EDX               // EDI becomes the actual target pointer

        // Load MM6 with the constant alpha value (replicate it for every component).
        // Expand it to word size.
        MOV     EAX, [ConstantAlpha]
        DB      $0F, $6E, $F0          /// MOVD      MM6, EAX
        DB      $0F, $61, $F6          /// PUNPCKLWD MM6, MM6
        DB      $0F, $62, $F6          /// PUNPCKLDQ MM6, MM6

        // Load MM5 with the bias value.
        MOV     EAX, [Bias]
        DB      $0F, $6E, $E8          /// MOVD      MM5, EAX
        DB      $0F, $61, $ED          /// PUNPCKLWD MM5, MM5
        DB      $0F, $62, $ED          /// PUNPCKLDQ MM5, MM5

        // Load MM4 with 128 to allow for saturated biasing.
        MOV     EAX, 128
        DB      $0F, $6E, $E0          /// MOVD      MM4, EAX
        DB      $0F, $61, $E4          /// PUNPCKLWD MM4, MM4
        DB      $0F, $62, $E4          /// PUNPCKLDQ MM4, MM4

@1:     // The pixel loop calculates an entire pixel in one run.
        // Note: The pixel byte values are expanded into the higher bytes of a word due
        //       to the way unpacking works. We compensate for this with an extra shift.
        DB      $0F, $EF, $C0          /// PXOR      MM0, MM0,   clear source pixel register for unpacking
        DB      $0F, $60, $06          /// PUNPCKLBW MM0, [ESI], unpack source pixel byte values into words
        DB      $0F, $71, $D0, $08     /// PSRLW     MM0, 8,     move higher bytes to lower bytes
        DB      $0F, $EF, $C9          /// PXOR      MM1, MM1,   clear target pixel register for unpacking
        DB      $0F, $60, $0F          /// PUNPCKLBW MM1, [EDI], unpack target pixel byte values into words
        DB      $0F, $6F, $D1          /// MOVQ      MM2, MM1,   make a copy of the shifted values, we need them again
        DB      $0F, $71, $D1, $08     /// PSRLW     MM1, 8,     move higher bytes to lower bytes

        // Load MM7 with the source alpha value (replicate it for every component).
        // Expand it to word size.
        DB      $0F, $6F, $F8          /// MOVQ      MM7, MM0
        DB      $0F, $69, $FF          /// PUNPCKHWD MM7, MM7
        DB      $0F, $6A, $FF          /// PUNPCKHDQ MM7, MM7
        DB      $0F, $D5, $FE          /// PMULLW    MM7, MM6,   source alpha * master alpha
        DB      $0F, $71, $D7, $08     /// PSRLW     MM7, 8,     divide by 256

        // calculation is: target = (alpha * master alpha * (source - target) + 256 * target) / 256
        DB      $0F, $F9, $C1          /// PSUBW     MM0, MM1,   source - target
        DB      $0F, $D5, $C7          /// PMULLW    MM0, MM7,   alpha * (source - target)
        DB      $0F, $FD, $C2          /// PADDW     MM0, MM2,   add target (in shifted form)
        DB      $0F, $71, $D0, $08     /// PSRLW     MM0, 8,     divide by 256

        // Bias is accounted for by conversion of range 0..255 to -128..127,
        // doing a saturated add and convert back to 0..255.
        DB      $0F, $F9, $C4          /// PSUBW     MM0, MM4
        DB      $0F, $ED, $C5          /// PADDSW    MM0, MM5
        DB      $0F, $FD, $C4          /// PADDW     MM0, MM4
        DB      $0F, $67, $C0          /// PACKUSWB  MM0, MM0,   convert words to bytes with saturation
        DB      $0F, $7E, $07          /// MOVD      [EDI], MM0, store the result
@3:
        ADD     ESI, 4
        ADD     EDI, 4
        DEC     ECX
        JNZ     @1
        POP     EDI
        POP     ESI
end;

procedure AlphaBlendLineMasterAndColor(Destination: Pointer; Count: Integer; ConstantAlpha, Color: Integer);
// Blends a line of Count pixels in Destination against the given color using a constant alpha value.
// The layout of a pixel must be BGRA and Color must be rrggbb00 (as stored by a COLORREF).
// ConstantAlpha must be in the range 0..255.
//
// EAX contains Destination
// EDX contains Count
// ECX contains ConstantAlpha
// Color is passed on the stack
asm
        // The used formula is: target = (alpha * color + (256 - alpha) * target) / 256.
        // alpha * color (factor 1) and 256 - alpha (factor 2) are constant values which can be calculated in advance.
        // The remaining calculation is therefore: target = (F1 + F2 * target) / 256

        // Load MM3 with the constant alpha value (replicate it for every component).
        // Expand it to word size. (Every calculation here works on word sized operands.)
        DB      $0F, $6E, $D9          /// MOVD      MM3, ECX
        DB      $0F, $61, $DB          /// PUNPCKLWD MM3, MM3
        DB      $0F, $62, $DB          /// PUNPCKLDQ MM3, MM3

        // Calculate factor 2.
        MOV     ECX, $100
        DB      $0F, $6E, $D1          /// MOVD      MM2, ECX
        DB      $0F, $61, $D2          /// PUNPCKLWD MM2, MM2
        DB      $0F, $62, $D2          /// PUNPCKLDQ MM2, MM2
        DB      $0F, $F9, $D3          /// PSUBW     MM2, MM3             // MM2 contains now: 255 - alpha = F2

        // Now calculate factor 1. Alpha is still in MM3, but the r and b components of Color must be swapped.
        MOV     ECX, [Color]
        BSWAP   ECX
        ROR     ECX, 8
        DB      $0F, $6E, $C9          /// MOVD      MM1, ECX             // Load the color and convert to word sized values.
        DB      $0F, $EF, $E4          /// PXOR      MM4, MM4
        DB      $0F, $60, $CC          /// PUNPCKLBW MM1, MM4
        DB      $0F, $D5, $CB          /// PMULLW    MM1, MM3             // MM1 contains now: color * alpha = F1

@1:     // The pixel loop calculates an entire pixel in one run.
        DB      $0F, $6E, $00          /// MOVD      MM0, [EAX]
        DB      $0F, $60, $C4          /// PUNPCKLBW MM0, MM4

        DB      $0F, $D5, $C2          /// PMULLW    MM0, MM2             // calculate F1 + F2 * target
        DB      $0F, $FD, $C1          /// PADDW     MM0, MM1
        DB      $0F, $71, $D0, $08     /// PSRLW     MM0, 8               // divide by 256

        DB      $0F, $67, $C0          /// PACKUSWB  MM0, MM0             // convert words to bytes with saturation
        DB      $0F, $7E, $00          /// MOVD      [EAX], MM0           // store the result

        ADD     EAX, 4
        DEC     EDX
        JNZ     @1
end;

procedure EMMS;
// Reset MMX state to use the FPU for other tasks again.
asm
        DB      $0F, $77               /// EMMS
end;

function GetBitmapBitsFromDeviceContext(DC: HDC; var Width, Height: Integer): Pointer;
// Helper function used to retrieve the bitmap selected into the given device context. If there is a bitmap then
// the function will return a pointer to its bits otherwise nil is returned.
// Additionally the dimensions of the bitmap are returned.
var
  Bitmap: HBITMAP;
  DIB: TDIBSection;
begin
  Result := nil;
  Width := 0;
  Height := 0;

  Bitmap := GetCurrentObject(DC, OBJ_BITMAP);
  if Bitmap <> 0 then
  begin
    if GetObject(Bitmap, SizeOf(DIB), @DIB) = SizeOf(DIB) then
    begin
      Assert(DIB.dsBm.bmPlanes * DIB.dsBm.bmBitsPixel = 32, 'Alpha blending error: bitmap must use 32 bpp.');
      Result := DIB.dsBm.bmBits;
      Width := DIB.dsBmih.biWidth;
      Height := DIB.dsBmih.biHeight;
    end;
  end;
  Assert(Result <> nil, 'Alpha blending DC error: no bitmap available.');
end;

function CalculateScanline(Bits: Pointer; Width, Height, Row: Integer): Pointer;
// Helper function to calculate the start address for the given row.
begin
  if Height > 0 then  // bottom-up DIB
    Row := Height - Row - 1;
  // Return DWORD aligned address of the requested scanline.
  Integer(Result) := Integer(Bits) + Row * ((Width * 32 + 31) and not 31) div 8;
end;

procedure ConvertBitmapEx(Image32: TBitmap; var OutImage: TBitmap; const BackGndColor: TColor);

var
  I, N: Integer;
  LongColor: DWORD;
  SourceRed, SourceGreen, SourceBlue, BkGndRed, BkGndGreen, BkGndBlue, RedTarget, GreenTarget, BlueTarget, Alpha: Byte;
  Target, Mask: TBitmap;
  LineDeltaImage32, PixelDeltaImage32, LineDeltaTarget, PixelDeltaTarget, LineDeltaMask, PixelDeltaMask: Integer;
  PLineImage32, PLineTarget, PLineMask, PPixelImage32, PPixelTarget, PPixelMask: PByte;
begin
  // Algorithm only works for bitmaps with a height > 1 pixel, should not be a limitation
  // as it would then be a line!
  if (Image32.PixelFormat = pf32Bit) and (Image32.Height > 1) then
  begin
    Target := TBitmap.Create;
    Mask := TBitmap.Create;
    try
      Target.PixelFormat := pf32Bit;
      Target.Width := Image32.Width;
      Target.Height := Image32.Height;
      Target.Assign(Image32);

      Mask.PixelFormat := pf32Bit;
      Mask.Width := Image32.Width;
      Mask.Height := Image32.Height;
      Mask.Canvas.Brush.Color := BackGndColor;
      Mask.Canvas.FillRect(Mask.Canvas.ClipRect);

      LineDeltaImage32 := DWORD( Image32.ScanLine[1]) - DWORD( Image32.ScanLine[0]);
      LineDeltaTarget := DWORD( Target.ScanLine[1]) - DWORD( Target.ScanLine[0]);
      LineDeltaMask := DWORD( Mask.ScanLine[1]) - DWORD( Mask.ScanLine[0]);

      PixelDeltaImage32 := SizeOf(TRGBQuad); 
      PixelDeltaTarget := SizeOf(TRGBQuad);
      PixelDeltaMask := SizeOf(TRGBQuad);

      PLineImage32 := Image32.ScanLine[0];
      PLineTarget := Target.ScanLine[0];
      PLineMask := Mask.ScanLine[0];

      for I := 0 to Image32.Height - 1 do
      begin
        PPixelImage32 := PLineImage32;
        PPixelTarget := PLineTarget;
        PPixelMask := PLineMask;

        for N := 0 to Image32.Width - 1 do
        begin
          // Source GetColorValues ; Profiled = ~24-30% of time
          LongColor := PDWORD( PPixelImage32)^;
          SourceBlue := LongColor and $000000FF;
          SourceGreen := (LongColor and $0000FF00) shr 8;
          SourceRed := (LongColor and $00FF0000) shr 16;
          Alpha := (LongColor and $FF000000) shr 24;

          // Mask GetColorValues ; Profiled = ~24-30% of time
          LongColor := PDWORD( PPixelMask)^;
          BkGndBlue := LongColor and $000000FF;
          BkGndGreen := (LongColor and $0000FF00) shr 8;
          BkGndRed := (LongColor and $00FF0000) shr 16;

          if Alpha < High(Byte) then
          begin
            // displayColor = sourceColor×alpha / 255 + backgroundColor×(255 – alpha) / 255    
            // Profiled = ~15-24% of time
            RedTarget := SourceRed*Alpha shr 8 + BkGndRed*(255-Alpha) shr 8;
            GreenTarget := SourceGreen*Alpha shr 8 + BkGndGreen*(255-Alpha) shr 8;
            BlueTarget := SourceBlue*Alpha shr 8 + BkGndBlue*(255-Alpha) shr 8;
          end  else
          begin
            // skip non-blended pixels
            RedTarget := SourceRed;
            GreenTarget := SourceGreen;
            BlueTarget := SourceBlue;
          end;

          // Create the RGB DWORD color ; Profiled = ~8%-9%% of time
          // Mask out all but the alpha channel then build the backwards stored RGB preserving the alpha channel bits
          PDWORD(PPixelTarget)^ := ((BlueTarget) or (GreenTarget shl 8)or (RedTarget shl 16));

          Inc(PPixelImage32, PixelDeltaImage32);
          Inc(PPixelTarget, PixelDeltaTarget);
          Inc(PPixelMask, PixelDeltaMask);
        end;
        Inc(PLineImage32, LineDeltaImage32);
        Inc(PLineTarget, LineDeltaTarget);
        Inc(PLineMask, LineDeltaMask);
      end;
      OutImage.Assign(Target);
    finally
      FreeAndNil(Target);
      FreeAndNil(Mask);
    end;
  end else
   OutImage.Assign(Image32)
end;
                    
procedure AlphaBlend(Source, Destination: HDC; R: TRect; Target: TPoint; Mode: TCommonBlendMode; ConstantAlpha, Bias: Integer);

// NOTE:::::::::::::
//    AlphaBlend does not respect any clipping in the DC!!!!!!!
//

// Optimized alpha blend procedure using MMX instructions to perform as quick as possible.
// For this procedure to work properly it is important that both source and target bitmap use the 32 bit color format.
// R describes the source rectangle to work on.
// Target is the place (upper left corner) in the target bitmap where to blend to. Note that source width + X offset
// must be less or equal to the target width. Similar for the height.
// If Mode is bmConstantAlpha then the blend operation uses the given ConstantAlpha value for all pixels.
// If Mode is bmPerPixelAlpha then each pixel is blended using its individual alpha value (the alpha value of the source).
// If Mode is bmMasterAlpha then each pixel is blended using its individual alpha value multiplied by ConstantAlpha.
// If Mode is bmConstantAlphaAndColor then each destination pixel is blended using ConstantAlpha but also a constant
// color which will be obtained from Bias. In this case no offset value is added, otherwise Bias is used as offset.
// Blending of a color into target only (bmConstantAlphaAndColor) ignores Source (the DC) and Target (the position).
// CAUTION: This procedure does not check whether MMX instructions are actually available! Call it only if MMX is really
//          usable.

var
  Y: Integer;
  SourceRun,
  TargetRun: PByte;

  SourceBits,
  DestBits: Pointer;
  SourceWidth,
  SourceHeight,
  DestWidth,
  DestHeight: Integer;

begin                              
  if not IsRectEmpty(R) then
  begin
    // Note: it is tempting to optimize the special cases for constant alpha 0 and 255 by just ignoring soure
    //       (alpha = 0) or simply do a blit (alpha = 255). But this does not take the bias into account.
    case Mode of
      cbmConstantAlpha:
        begin
          // Get a pointer to the bitmap bits for the source and target device contexts.
          // Note: this supposes that both contexts do actually have bitmaps assigned!
          SourceBits := GetBitmapBitsFromDeviceContext(Source, SourceWidth, SourceHeight);
          DestBits := GetBitmapBitsFromDeviceContext(Destination, DestWidth, DestHeight);
          if Assigned(SourceBits) and Assigned(DestBits) then
          begin
            for Y := 0 to R.Bottom - R.Top - 1 do
            begin
              SourceRun := CalculateScanline(SourceBits, SourceWidth, SourceHeight, Y + R.Top);
              Inc(SourceRun, 4 * R.Left);
              TargetRun := CalculateScanline(DestBits, DestWidth, DestHeight, Y + Target.Y);
              Inc(TargetRun, 4 * Target.X);
              AlphaBlendLineConstant(SourceRun, TargetRun, R.Right - R.Left, ConstantAlpha, Bias);
            end;
          end;
          EMMS;
        end;
      cbmPerPixelAlpha:
        begin
          SourceBits := GetBitmapBitsFromDeviceContext(Source, SourceWidth, SourceHeight);
          DestBits := GetBitmapBitsFromDeviceContext(Destination, DestWidth, DestHeight);
          if Assigned(SourceBits) and Assigned(DestBits) then
          begin
            for Y := 0 to R.Bottom - R.Top - 1 do
            begin
              SourceRun := CalculateScanline(SourceBits, SourceWidth, SourceHeight, Y + R.Top);
              Inc(SourceRun, 4 * R.Left);
              TargetRun := CalculateScanline(DestBits, DestWidth, DestHeight, Y + Target.Y);
              Inc(TargetRun, 4 * Target.X);
              AlphaBlendLinePerPixel(SourceRun, TargetRun, R.Right - R.Left, Bias);
            end;
          end;
          EMMS;
        end;
      cbmMasterAlpha:
        begin
          SourceBits := GetBitmapBitsFromDeviceContext(Source, SourceWidth, SourceHeight);
          DestBits := GetBitmapBitsFromDeviceContext(Destination, DestWidth, DestHeight);
          if Assigned(SourceBits) and Assigned(DestBits) then
          begin
            for Y := 0 to R.Bottom - R.Top - 1 do
            begin
              SourceRun := CalculateScanline(SourceBits, SourceWidth, SourceHeight, Y + R.Top);
              Inc(SourceRun, 4 * Target.X);
              TargetRun := CalculateScanline(DestBits, DestWidth, DestHeight, Y + Target.Y);
              AlphaBlendLineMaster(SourceRun, TargetRun, R.Right - R.Left, ConstantAlpha, Bias);
            end;
          end;
          EMMS;
        end;
      cbmConstantAlphaAndColor:
        begin
          // Source is ignore since there is a constant color value.
          DestBits := GetBitmapBitsFromDeviceContext(Destination, DestWidth, DestHeight);
          if Assigned(DestBits) then
          begin
            for Y := 0 to R.Bottom - R.Top - 1 do
            begin
              TargetRun := CalculateScanline(DestBits, DestWidth, DestHeight, Y + R.Top);
              Inc(TargetRun, 4 * R.Left);
              AlphaBlendLineMasterAndColor(TargetRun, R.Right - R.Left, ConstantAlpha, Bias);
            end;
          end;
          EMMS;
        end;
    end;
  end;
end;

function DrawTextWEx(DC: HDC; Text: WideString; var lpRect: TRect;
  Flags: TCommonDrawTextWFlags; MaxLineCount: Integer): Integer;
// Creates and extented version of DrawTextW that works in Win9x as well as
// NT.  If MaxLineCount is -1 then the line count will depend on the Text.  All
// lines that are extracted from the text are drawn or calcuated in the rectangle
//
// The result is the number of lines actually drawn, note if the CalcRect flags are
//   used the result will be the number of lines that would be drawn
var
  TextMetrics: TTextMetric;
  Size: TSize;
  TextPosX, TextPosY, i, NewLineTop: Integer;
  TextOutFlags: Longword;
  LineRect, OldlpRect: TRect;
  Buffer: TCommonWideCharArray;
  BufferIndex: PWideChar;
  ShortText: WideString;
  VOffset, SplitCount: Integer;
begin
  OldlpRect := lpRect;
  GetTextMetrics(DC, TextMetrics);

  TextOutFlags := 0;
  if dtRTLReading in Flags then
    TextOutFlags := TextOutFlags or ETO_RTLREADING;
  if not (dtNoClip in Flags) then
    TextOutFlags := TextOutFlags or ETO_CLIPPED;

  if dtSingleLine in Flags then
  begin
    Result := 1;  // Easy one!

    // Set up the LineRect in the Vertical Direction
    // Default to the top
    LineRect := Rect(lpRect.Left, lpRect.Top, lpRect.Right, lpRect.Top + TextMetrics.tmHeight);
    if dtVCenter in Flags then
      OffsetRect(LineRect, 0, (RectHeight(lpRect) - RectHeight(LineRect)) div 2)
    else
    if dtBottom in Flags then
      OffsetRect(LineRect, 0, RectHeight(lpRect) - RectHeight(LineRect));
    TextPosX := LineRect.Left;
    TextPosY := LineRect.Top;

    if dtEndEllipsis in Flags then
      Text := ShortenTextW(DC, Text, RectWidth(LineRect));

    GetTextExtentPoint32W(DC, PWideChar(Text), Length(Text), Size);
    if Flags * [dtCenter, dtRight] <> [] then
    begin
      if dtCenter in Flags then
        TextPosX := TextPosX + (RectWidth(LineRect) - Size.cx) div 2
      else
        TextPosX := LineRect.Right - Size.cx
    end;

 {   if dtCenter in Flags then
      SetTextAlign(DC, TA_CENTER)
    else
    if dtLeft in Flags then
      SetTextAlign(DC, TA_LEFT)
    else
    if dtRight in Flags then
      SetTextAlign(DC, TA_RIGHT);   }

    // See if the caller wants to only calculate the rectangle for the text
    if dtCalcRect in Flags then
    begin
      // Assume that the text will fit in the calulated line/rect
      lpRect.Left := TextPosX;
      lpRect.Top := TextPosY;
      lpRect.Bottom := LineRect.Bottom;
      lpRect.Right := lpRect.Left + Size.cx;

      // If it does not then we have to do some adjusting
      if Size.cx > RectWidth(OldlpRect) then
      begin
        if dtCalcRectAlign in Flags then
        begin
          lpRect.Left := OldlpRect.Left;
          lpRect.Right := OldlpRect.Right;
        end;
        if dtCalcRectAdjR in Flags then
          lpRect.Right := lpRect.Left + Size.cx;
      end
    end else
      ExtTextOutW(DC, TextPosX, TextPosY, TextOutFlags, @LineRect, PWideChar(Text), Length(Text), nil);

  end else
  begin
    // It is multi-line
    SplitCount := SplitTextW(DC, Text, lpRect.Right-lpRect.Left, Buffer, MaxLineCount);
    i := 0;
    if Length(Buffer) > 0 then
    begin
      // We call ourselves recursivly one line at a time to draw the multi line text
      Include(Flags, dtSingleLine);
      BufferIndex := @Buffer[0];

      // Calculate where the center of the text block is with respect to the
      // rectangle
  {    if dtVCenter in Flags then
      begin
        if (SplitCount > MaxLineCount) and (MaxLineCount > -1) then
          VOffset := (RectHeight(OldlpRect) - (TextMetrics.tmHeight * MaxLineCount)) div 2
        else
          VOffset := (RectHeight(OldlpRect) - (TextMetrics.tmHeight * SplitCount)) div 2;
        if VOffset < 0 then
          VOffset := 0;
      end else
      if dtBottom in Flags then
      begin
        VOffset := (RectHeight(OldlpRect) - (TextMetrics.tmHeight * MaxLineCount))
      end else
        VOffset := 0; }

      // Fix for multitext vertical alignment from Solerman Kaplon 11.9.04
      if (dtVCenter in Flags) or (dtBottom in Flags) then
      begin
        if (SplitCount > MaxLineCount) and (MaxLineCount > -1) then
        VOffset := (RectHeight(OldlpRect) - (TextMetrics.tmHeight * MaxLineCount))
      else
      VOffset := (RectHeight(OldlpRect) - (TextMetrics.tmHeight * SplitCount));
      if VOffset < 0 then
        VOffset := 0
      else
      if dtVCenter in Flags then
        VOffset := VOffset shr 1;
      end else
        VOffset := 0;
      while ((i < MaxLineCount) or (MaxLineCount < 0)) and (BufferIndex^ <> WideNull) do
      begin
        // Calculate where the top of a single line of text starts
        NewLineTop := OldlpRect.Top + (i * TextMetrics.tmHeight) + VOffset;
        LineRect := Rect(OldlpRect.Left, NewLineTop, OldlpRect.Right, NewLineTop + TextMetrics.tmHeight);
        if (dtEndEllipsis in Flags) {and not(dtCalcRect in Flags)} then
        begin
          ShortText := ShortenTextW(DC, WideString(BufferIndex), RectWidth(OldlpRect));
          DrawTextWEx(DC, ShortText, LineRect, Flags, MaxLineCount);
        end else
          DrawTextWEx(DC, WideString(BufferIndex), LineRect, Flags, MaxLineCount);

        if dtCalcRect in Flags then
        begin
          if i = 0 then
            lpRect := LineRect
          else
            UnionRect(lpRect, lpRect, LineRect);
        end;
        Inc(BufferIndex, lStrLenW(BufferIndex) + 1);
        Inc(i)
      end;

      if (SplitCount = 0) and (dtCalcRect in Flags) then
      begin
        if dtCalcRectAdjR in Flags then
        begin
          lpRect.Right := lpRect.Left;
          lpRect.Bottom := lpRect.Top + TextMetrics.tmHeight
        end else
        begin
          lpRect.Bottom := lpRect.Top;
          lpREct.Right := lpRect.Left + TextMetrics.tmAveCharWidth
        end
      end;

      if SplitCount > MaxLineCount then
        Result := MaxLineCount
      else
        Result := SplitCount
    end else
      Result := 0;
  end
end;

  // Helpers to create a callback function out of a object method
{ ----------------------------------------------------------------------------- }
{ This is a piece of magic by Jeroen Mineur.  Allows a class method to be used  }
{ as a callback. Create a stub using CreateStub with the instance of the object }
{ the callback should call as the first parameter and the method as the second  }
{ parameter, ie @TForm1.MyCallback or declare a type of object for the callback }
{ method and then use a variable of that type and set the variable to the       }
{ method and pass it:                                                           }
{                                                                               }
{ type                                                                          }
{   TEnumWindowsFunc = function (AHandle: hWnd; Param: lParam): BOOL of object; stdcall; }
{                                                                               }
{  TForm1 = class(TForm)                                                        }
{  private                                                                      }
{    function EnumWindowsProc(AHandle: hWnd; Param: lParam): BOOL; stdcall;     }
{  end;                                                                         }
{                                                                               }
{  var                                                                          }
{    MyFunc: TEnumWindowsFunc;                                                  }
{    Stub: pointer;                                                             }
{  begin                                                                        }
{    MyFunct := EnumWindowsProc;                                                }
{    Stub := CreateStub(Self, MyFunct);                                         }
{     ....                                                                      }
{  or                                                                           }
{                                                                               }
{  var                                                                          }
{    Stub: pointer;                                                             }
{  begin                                                                        }
{    MyFunct := EnumWindowsProc;                                                }
{    Stub := CreateStub(Self, TForm1.EnumWindowsProc);                          }
{     ....                                                                      }
{  Now Stub can be passed as the callback pointer to any windows API          }
{  Don't forget to call Dispose Stub when not needed        }
{ ----------------------------------------------------------------------------- }
const
  AsmPopEDX = $5A;
  AsmMovEAX = $B8;
  AsmPushEAX = $50;
  AsmPushEDX = $52;
  AsmJmpShort = $E9;

type
  TStub = packed record
    PopEDX: Byte;
    MovEAX: Byte;
    SelfPointer: Pointer;
    PushEAX: Byte;
    PushEDX: Byte;
    JmpShort: Byte;
    Displacement: Integer;
  end;

{ ----------------------------------------------------------------------------- }
function CreateStub(ObjectPtr: Pointer; MethodPtr: Pointer): Pointer;
var
  Stub: ^TStub;
begin
  // Allocate memory for the stub
  // 1/10/04 Support for 64 bit, executable code must be in virtual space
  Stub := VirtualAlloc(nil, SizeOf(TStub), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

  // Pop the return address off the stack
  Stub^.PopEDX := AsmPopEDX;

  // Push the object pointer on the stack
  Stub^.MovEAX := AsmMovEAX;
  Stub^.SelfPointer := ObjectPtr;
  Stub^.PushEAX := AsmPushEAX;

  // Push the return address back on the stack
  Stub^.PushEDX := AsmPushEDX;

  // Jump to the 'real' procedure, the method.
  Stub^.JmpShort := AsmJmpShort;
  Stub^.Displacement := (Integer(MethodPtr) - Integer(@(Stub^.JmpShort))) -
    (SizeOf(Stub^.JmpShort) + SizeOf(Stub^.Displacement));

  // Return a pointer to the stub
  Result := Stub;
end;
{ ----------------------------------------------------------------------------- }

{ ----------------------------------------------------------------------------- }
procedure DisposeStub(Stub: Pointer);
begin
  // 1/10/04 Support for 64 bit, executable code must be in virtual space
  VirtualFree(Stub, SizeOf(TStub),MEM_DECOMMIT);
end;
{ ----------------------------------------------------------------------------- }

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

function Supports(const Instance: TObject; const IID: TGUID; out Intf): Boolean; overload;
begin
  Result := False;
  Pointer( Intf) := nil;
  if Assigned(Instance) then
    Result := Instance.GetInterface(IID, Intf)
end;

procedure FreeAndNil(var Obj);
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;
  Temp.Free;
end;
{$ENDIF}

{$IFNDEF COMPILER_6_UP}
function Supports(const Instance: IUnknown; const IID: TGUID): Boolean;
var
  Temp: IUnknown;
begin
  {$IFNDEF COMPILER_5_UP}
  Result := Supports(Instance, IID, Temp);
  {$ELSE}
  Result := SysUtils.Supports(Instance, IID, Temp)
  {$ENDIF}
end;

procedure ClearMenuItems(Menu: TMenu);
var
  I: Integer;
begin
  for I := Menu.Items.Count - 1 downto 0 do
    Menu.Items[I].Free;
end;
{$ENDIF}

procedure CreateProcessMP(ExeFile, Parameters, InitalDir: WideString);
var
  pi: TProcessInformation;
  siW: TStartupInfoW;
  siA: _STARTUPINFOA;
  wA, wB, wC: PWideChar;
  aA, aB, aC: PChar;
begin
  FillChar(pi, SizeOf(pi), #0);
  if Assigned(CreateProcessW_MP) then
  begin
    FillChar(siW, SizeOf(siW), #0);
    wA := nil;
    wB := nil;
    wC := nil;
    if ExeFile <> '' then
      wA := PWideChar(ExeFile);
    if Parameters <> '' then
      wB := PWideChar(Parameters);
    if InitalDir <> '' then
      wC := PWideChar(InitalDir);

    CreateProcessW_MP(
                  wA, // path to the executable file:
                  wB,
                  nil,
                  nil,
                  False,
                  NORMAL_PRIORITY_CLASS,
                  nil,
                  wC,
                  siW,
                  pi );
  end else
  begin
    FillChar(siA, SizeOf(siA), #0);
    aA := nil;
    aB := nil;
    aC := nil;
    if ExeFile <> '' then
      aA := PChar( string(ExeFile));
    if Parameters <> '' then
      aB := PChar( string(Parameters));
    if InitalDir <> '' then
      aC := PChar( string(InitalDir));
    CreateProcessA(
                  aA,
                  aB, // path to the executable file:
                  nil,
                  nil,
                  False,
                  NORMAL_PRIORITY_CLASS,
                  nil,
                  aC,
                  siA,
                  pi );
  end;
  if pi.hProcess <> 0 then
    CloseHandle(pi.hProcess);
  if pi.hThread <> 0 then
    CloseHandle(pi.hThread)
end;


function DiffRectHorz(Rect1, Rect2: TRect): TRect;
// Returns the "difference" rectangle of the passed rects in the Horz direction.
// Assumes that one corner is common between the two rects
begin
  Rect1 := ProperRect(Rect1);
  Rect2 := ProperRect(Rect2);
  // Make sure we contain every thing horizontally
  Result.Left := Min(Rect1.Left, Rect2.Left);
  Result.Right := Max(Rect1.Right, Rect1.Right);
  // Now find the difference rect height
  if Rect1.Top = Rect2.Top then
  begin
    // The tops are equal so it must be the bottom that contains the difference
    Result.Bottom := Max(Rect1.Bottom, Rect2.Bottom);
    Result.Top := Min(Rect1.Bottom, Rect2.Bottom);
  end else
  begin
   // The bottoms are equal so it must be the tops that contains the difference
    Result.Bottom := Max(Rect1.Top, Rect2.Top);
    Result.Top := Min(Rect1.Top, Rect2.Top);
  end
end;

function DiffRectVert(Rect1, Rect2: TRect): TRect;
// Returns the "difference" rectangle of the passed rects in the Vert direction.
// Assumes that one corner is common between the two rects
begin
  Rect1 := ProperRect(Rect1);
  Rect2 := ProperRect(Rect2);
  // Make sure we contain every thing vertically
  Result.Top := Min(Rect1.Top, Rect2.Bottom);
  Result.Bottom := Max(Rect1.Top, Rect1.Bottom);
  // Now find the difference rect width
  if Rect1.Left = Rect2.Left then
  begin
    // The tops are equal so it must be the bottom that contains the difference
    Result.Right := Max(Rect1.Right, Rect2.Right);
    Result.Left := Min(Rect1.Right, Rect2.Right);
  end else
  begin
   // The bottoms are equal so it must be the tops that contains the difference
    Result.Right := Max(Rect1.Left, Rect2.Left);
    Result.Left := Min(Rect1.Left, Rect2.Left);
  end
end;

function AbsRect(ARect: TRect): TRect;
// Makes all coodinates positive
begin
  Result := ARect;
  if Result.Left < 0 then
    Result.Left := 0;
  if Result.Top < 0 then
    Result.Top := 0;
  if Result.Right < 0 then
    Result.Right := 0;
  if Result.Bottom < 0 then
    Result.Bottom := 0;
end;

function CenterRectInRect(OuterRect, InnerRect: TRect): TRect;
begin
  if RectWidth(InnerRect) > RectWidth(OuterRect) then
  begin
    // If the inner rect is wider than the result is the outer rect x bounds
    Result.Left := OuterRect.Left;
    Result.Right := OuterRect.Right;
  end else
  begin
    // If not then center the inner rectangle in the outer in the x direction
    Result.Left := OuterRect.Left;
    Result.Right := Result.Left + RectWidth(InnerRect);
    OffsetRect(Result, (RectWidth(OuterRect) - RectWidth(InnerRect)) div 2, 0);
  end;
  if RectHeight(InnerRect) > RectHeight(OuterRect) then
  begin
    // If the inner rect is wider than the result is the outer rect y bounds
    Result.Top := OuterRect.Top;
    Result.Bottom := OuterRect.Bottom;
  end else
  begin
    // If not then center the inner rectangle in the outer in the y direction
    Result.Top := OuterRect.Top;
    Result.Bottom := Result.Top + RectHeight(InnerRect);
    OffsetRect(Result, 0, (RectHeight(OuterRect) - RectHeight(InnerRect)) div 2);
  end;
end;

function CenterRectHorz(OuterRect, InnerRect: TRect): TRect;
begin
  if RectWidth(InnerRect) > RectWidth(OuterRect) then
  begin
    // If the inner rect is wider than the result is the outer rect x bounds
    Result.Left := OuterRect.Left;
    Result.Right := OuterRect.Right;
  end else
  begin
    // If not then center the inner rectangle in the outer in the x direction
    Result.Left := OuterRect.Left;
    Result.Right := Result.Left + RectWidth(InnerRect);
    OffsetRect(Result, (RectWidth(OuterRect) - RectWidth(InnerRect)) div 2, 0);
  end;
end;

function CenterRectVert(OuterRect, InnerRect: TRect): TRect;
begin
if RectHeight(InnerRect) > RectHeight(OuterRect) then
  begin
    // If the inner rect is wider than the result is the outer rect y bounds
    Result.Top := OuterRect.Top;
    Result.Bottom := OuterRect.Bottom;
  end else
  begin
    // If not then center the inner rectangle in the outer in the y direction
    Result.Top := OuterRect.Top;
    Result.Bottom := Result.Top + RectHeight(InnerRect);
    OffsetRect(Result, 0, (RectHeight(OuterRect) - RectHeight(InnerRect)) div 2);
  end;
end;

function CommonSupports(const Instance: IUnknown; const IID: TGUID; out Intf): Boolean; overload;
begin
  Result := (Instance <> nil) and (Instance.QueryInterface(IID, Intf) = S_OK);
end;

function CommonSupports(const Instance: TObject; const IID: TGUID; out Intf): Boolean; overload;
var
  LUnknown: IUnknown;
begin
  Result := (Instance <> nil) and
            (Instance.GetInterface(IID, Intf) or
            (Instance.GetInterface(IUnknown, LUnknown) and Supports(LUnknown, IID, Intf)));
end;

function CommonSupports(const Instance: IUnknown; const IID: TGUID): Boolean; overload;
var
  Temp: IUnknown;
begin
  Result := Supports(Instance, IID, Temp);
end;

function CommonSupports(const Instance: TObject; const IID: TGUID): Boolean; overload;
 var
  Temp: IUnknown;
begin
  Result := Supports(Instance, IID, Temp);
end;

procedure CopyToNullBufferA(S: WideString; Buffer: PChar; CharCount: Cardinal);
var
  ANSI: string;
begin
  ANSI := S;
  FillChar(Buffer^, CharCount, #0);
  if Length(ANSI) > 0 then
  begin
    if Cardinal(Length(ANSI)) + 1 > CharCount then
      CharCount := CharCount - 1 // Leave room for the null
    else
      CharCount := Length(ANSI);
    MoveMemory(Buffer, PChar(ANSI), CharCount);
  end
end;

procedure CopyToNullBufferW(S: WideString; Buffer: PWideChar; CharCount: Cardinal);
begin
  FillChar(Buffer^, CharCount * 2, #0);
  if Length(S) > 0 then
  begin
    if Cardinal(Length(S)) + 2 > CharCount then
      CharCount := CharCount - 2 // Leave room for the null
    else
      CharCount := Length(S);
    MoveMemory(Buffer, PWideChar(S), CharCount * 2);
  end
end;

procedure MinMax(var A, B: Integer);
// Makes sure that A < B
var
  Temp: Integer;
begin
  if A > B then
  begin
    Temp := A;
    A := B;
    B := Temp
  end
end;

function IsRectProper(Rect: TRect): Boolean;
begin
  Result := (Rect.Right >= Rect.Left) and (Rect.Bottom >= Rect.Top)
end;

function AddCommas(NumberString: WideString): WideString;
var
//  i: integer;
  BufferA: array[0..128] of Char;
  BufferW: array[0..128] of WideChar;
begin
  // Make the number format based on the local not the US 3 digit comma format
  if Assigned(GetNumberFormatW_MP) then
  begin
    GetNumberFormatW_MP(LOCALE_USER_DEFAULT, 0, PWideChar(NumberString), nil, BufferW, SizeOf(BufferW));
    Result := BufferW;
  end
  else begin
    GetNumberFormatA(LOCALE_USER_DEFAULT, 0, PChar(string(NumberString)), nil, BufferA, SizeOf(BufferA));
    Result := BufferA
  end;

  { Trimming white space in Unicode is tough don't pass any }
 { i := Length(NumberString) mod 3;
  if i = 0 then
    i := 3;
  while i < Length(NumberString) do
  begin
    InsertW(ThousandSeparator, NumberString, i);
    Inc(i, 4);
  end;
  Result := NumberString   }
end;

function CalcuateFolderSize(FolderPath: WideString; Recurse: Boolean): Int64;

// Recursivly gets the size of the folder and subfolders
var
  S: string;
  FreeSpaceAvailable, TotalSpace: Int64;
  SectorsPerCluster,
  BytesPerSector,
  FreeClusters,
  TotalClusters: DWORD;
begin
  Result := 0;
  if Recurse and WideIsDrive(FolderPath) then
  begin
    if IsUnicode and Assigned(GetDiskFreeSpaceExW_MP) then
    begin
      if GetDiskFreeSpaceExW_MP(PWideChar(FolderPath), FreeSpaceAvailable, TotalSpace, nil) then
      Result := TotalSpace - FreeSpaceAvailable
    end else
    if not IsWin95_SR1 and Assigned(GetDiskFreeSpaceExA_MP) then
    begin
      S := FolderPath;
      if GetDiskFreeSpaceExA_MP(PChar(S), FreeSpaceAvailable, TotalSpace, nil) then
        Result := TotalSpace - FreeSpaceAvailable;
    end else
    begin
      GetDiskFreeSpaceA(PChar( S), SectorsPerCluster, BytesPerSector, FreeClusters,
        TotalClusters);
      Result := SectorsPerCluster * BytesPerSector * TotalClusters
    end;
  end else
  begin
    SumFolderAbort := False;
    SumFolder(FolderPath, Recurse, Result);
  end
end;

function GetMyDocumentsVirtualFolder: PItemIDList;

const
  MYCOMPUTER_GUID = WideString('::{450d8fba-ad25-11d0-98a8-0800361b1103}');

var
  dwAttributes, pchEaten: ULONG;
  Desktop: IShellFolder;
begin
  Result := nil;
  dwAttributes := 0;
  SHGetDesktopFolder(Desktop);
  pchEaten := Length(MYCOMPUTER_GUID);
  if not Succeeded(Desktop.ParseDisplayName(0, nil,
    PWideChar(MYCOMPUTER_GUID), pchEaten, Result, dwAttributes))
  then
    Result := nil
end;

procedure WideInsert(Source: WideString; var S: WideString; Index: Integer);
{ It appears there is a WideString Insert in the VCL already but since mine     }
{ looks better and is simpler and I spent my time I will use mine <g>           }
{  _WStrInsert in System through compiler magic.                                }
var
  OriginalLen: integer;
begin
  if (Index < Length(S) + 1) and (Index > - 1)  then
  begin
    OriginalLen := Length(S);
    SetLength(S, Length(Source) + Length(S));
    { We are correct up to Index }
    { Slide to end of new string leaving space for insert }
    Move(S[Index + 1], S[Index + 1 + Length(Source)], (OriginalLen - Index) * 2);
    Move(Source[1], S[Index + 1], Length(Source) * 2);
  end
end;

function WideGetCurrentDir: WideString;
var
  BufferW: array[0..MAX_PATH] of Widechar;
  BufferA: array[0..MAX_PATH] of Char;
begin
  if Assigned(GetCurrentDirectoryW_MP) then
  begin
    if GetCurrentDirectoryW_MP(MAX_PATH, BufferW) > 0 then
      Result := BufferW;
  end else
  begin
    if GetCurrentDirectoryA(MAX_PATH, BufferA) > 0 then
      Result := BufferA;
  end
end;

function WideGetTempDir: WideString;
var
  BufferW: array[0..MAX_PATH] of Widechar;
  BufferA: array[0..MAX_PATH] of Char;
begin
  if Assigned(GetTempPathW_MP) then
  begin
    if GetTempPathW_MP(MAX_PATH, BufferW) > 0 then
      Result := BufferW;
  end else
  begin
    if GetTempPathA(MAX_PATH, BufferA) > 0 then
      Result := BufferA;
  end
end;

function WideIncludeTrailingBackslash(Path: WideString): WideString;
begin
  {$IFDEF TNTSUPPORT}
  Result := TntSysUtils.WideIncludeTrailingBackslash(Path);
  {$ELSE}
  {$IFDEF COMPILER_6_UP}
    {$WARN SYMBOL_PLATFORM OFF}
  {$ENDIF}
  Result := IncludeTrailingBackslash(Path);
  {$IFDEF COMPILER_6_UP}
    {$WARN SYMBOL_PLATFORM ON}
  {$ENDIF}
  {$ENDIF}
end;

function WideIncrementalSearch(CompareStr, Mask: WideString): Integer;
begin
  SetLength(CompareStr, Length(Mask));

  if IsUnicode then
    Result := lstrcmpiW(PWideChar(Mask), PWideChar(CompareStr))
  else
    Result := lstrcmpi(PChar(string(Mask)), PChar(string(CompareStr)));
end;

function WideIntToStr(Value: integer): WideString;
{ Need to find a way to do this in Unicode. }
begin
    Result := IntToStr(Value);
end;

function WideIsDrive(Drive: WideString): Boolean;
begin
  if Length(Drive) = 3 then
    Result := (LowerCase(Drive[1]) >= 'a') and (LowerCase(Drive[1]) <= 'z') and (Drive[2] = ':') and (Drive[3] = '\')
  else
  if Length(Drive) = 2 then
    Result := (LowerCase(Drive[1]) >= 'a') and (LowerCase(Drive[1]) <= 'z') and (Drive[2] = ':')
  else
    Result := False
end;

function WideIsFloppy(FileFolder: WideString): boolean;
begin
  if Length(FileFolder) > 0 then
    Result := WideIsDrive(FileFolder) and (Char(FileFolder[1]) in ['A', 'a', 'B', 'b'])
  else
    Result := False
end;


function IsAnyMouseButtonDown: Boolean;
begin
  Result := not(((GetAsyncKeyState(VK_LBUTTON) and $8000) = 0) and
            ((GetAsyncKeyState(VK_RBUTTON) and $8000) = 0) and
            ((GetAsyncKeyState(VK_MBUTTON) and $8000) = 0))
end;

function IsFTPPath(Path: WideString): Boolean;
begin
  if Length(Path) > 3 then
  begin
    Path := UpperCase(Path);
    Result := (Path[1] = 'F') and (Path[2] = 'T') and (Path[3] = 'P')
  end else
    Result := False
end;

function WideNewFolderName(ParentFolder: WideString; SuggestedFolderName: WideString = ''): WideString;
var
  i: integer;
  TempFoldername: String;
begin
  ParentFolder := WideStripTrailingBackslash(ParentFolder, True); // Strip even if a root folder
  i := 1;
  if SuggestedFolderName = '' then
    Begin
      Result := ParentFolder + '\' + STR_NEWFOLDER;
      TempFoldername := STR_NEWFOLDER;
    end
  else
    Begin
      Result := ParentFolder + '\' + SuggestedFolderName;
      Tempfoldername := SuggestedFolderName;
    End;
  while DirExistsW(PWideChar(Result)) and (i <= High(WORD)) do
  begin
    Result := ParentFolder + '\' + Tempfoldername + ' (' + IntToStr(i) + ')';
    Inc(i);
  end;
  if i > High(WORD) then
    Result := '';
end;

function WidePathMatchSpec(Path, Mask: WideString): Boolean;
begin
  if Assigned(PathMatchSpecW_MP) then
    Result := PathMatchSpecW_MP(PWideChar(Path), PWideChar( Mask))
  else
  if Assigned(PathMatchSpecA_MP) then
    Result := PathMatchSpecA_MP(PChar( string(Path)), PChar( string(Mask)))
  else
    Result := False
end;

function WidePathMatchSpecExists: Boolean;
begin
  Result := Assigned(PathMatchSpecW_MP) or Assigned(PathMatchSpecA_MP)
end;

function WideIsPathDelimiter(const S: WideString; Index: Integer): Boolean;
begin
  Result := (Index > 0) and (Index <= Length(S)) and (S[Index] = '\');
end;

function IsTextTrueType(DC: HDC): Boolean;
var
    TextMetrics: TTextMetric;
begin
   GetTextMetrics(DC, TextMetrics);
   Result := TextMetrics.tmPitchAndFamily and TMPF_TRUETYPE <>  0
end;

function IsTextTrueType(Canvas: TCanvas): Boolean;
begin
  Result := IsTextTrueType(Canvas.Handle);
end;

function IsUNCPath(const Path: WideString): Boolean;
begin
  Result :=  ((Path[1] = '\') and (Path[2] = '\')) and (DirExistsW(PWideChar(Path)) or FileExistsW(Path))
end;

  function StrRetToStr(StrRet: TStrRet; APIDL: PItemIDList): WideString;
{ Extracts the string from the StrRet structure.                                }
var
  P: PChar;
//  S: string;
begin
  case StrRet.uType of
    STRRET_CSTR:
      begin
        SetString(Result, StrRet.cStr, lStrLen(StrRet.cStr));
    //    Result := S
      end;
    STRRET_OFFSET:
      begin
        if Assigned(APIDL) then
        begin
          {$R-}
          P := PChar(@(APIDL).mkid.abID[StrRet.uOffset - SizeOf(APIDL.mkid.cb)]);
          {$R+}
          SetString(Result, P, StrLen(P));
       //   Result := S;
        end else
          Result := '';
      end;
    STRRET_WSTR:
    begin
      Result := StrRet.pOleStr;
      if Assigned(StrRet.pOleStr) then
        PIDLMgr.FreeOLEStr(StrRet.pOLEStr);
     end;
  end;
end;

function SystemDirectory: WideString;
var
  Len: integer;
  S: string;
begin
  Result := '';
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    Len := GetSystemDirectoryW_MP(PWideChar(Result), 0)
  else
    Len := GetSystemDirectoryA(PChar(S), 0);
  if Len > 0 then
  begin
    if Win32Platform = VER_PLATFORM_WIN32_NT then
    begin
      SetLength(Result, Len - 1);
      GetSystemDirectoryW_MP(PWideChar(Result), Len);
    end else
    begin
      SetLength(S, Len - 1);
      GetSystemDirectoryA(PChar(S), Len);
      Result := S
    end
  end
end;

function SysMenuFont: HFONT;
var
  MetricsA: TNonClientMetricsA;
  MetricsW: TNonClientMetricsW;
begin
  if IsUnicode then
  begin
    FillChar(MetricsW, SizeOf(MetricsW), #0);
    MetricsW.cbSize := SizeOf(MetricsW);
    SystemParametersInfoW_MP(SPI_GETNONCLIENTMETRICS, Sizeof(MetricsW), @MetricsW, 0);
    Result := CreateFontIndirectW_MP(MetricsW.lfMenuFont);
  end else
  begin
    FillChar(MetricsA, SizeOf(MetricsA), #0);
    MetricsA.cbSize := SizeOf(MetricsA);
    SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, Sizeof(MetricsA), @MetricsA, 0);
    Result := CreateFontIndirectA(MetricsA.lfMenuFont);
  end
end;

function SysMenuHeight: Integer;
var
  MetricsA: TNonClientMetricsA;
  MetricsW: TNonClientMetricsW;
begin
  if IsUnicode then
  begin
    FillChar(MetricsW, SizeOf(MetricsW), #0);
    MetricsW.cbSize := SizeOf(MetricsW);
    SystemParametersInfoW_MP(SPI_GETNONCLIENTMETRICS, Sizeof(MetricsW), @MetricsW, 0);
    Result := MetricsW.iMenuHeight
  end else
  begin
    FillChar(MetricsA, SizeOf(MetricsA), #0);
    MetricsA.cbSize := SizeOf(MetricsA);
    SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, Sizeof(MetricsA), @MetricsA, 0);
    Result := MetricsA.iMenuHeight
  end
end;

function TextExtentW(Text: WideString; Font: TFont): TSize;
var
  Canvas: TCanvas;
begin
  FillChar(Result, SizeOf(Result), #0);
  if Text <> '' then
  begin
    Canvas := TCanvas.Create;
    try
      Canvas.Handle := GetDC(0);
      Canvas.Lock;
      Canvas.Font.Assign(Font);
      Result := InternalTextExtentW(PWideChar(Text), Canvas.Handle);
    finally
      if Assigned(Canvas) and (Canvas.Handle <> 0) then
        ReleaseDC(0, Canvas.Handle);
      Canvas.Unlock;
      Canvas.Free
    end
  end
end;

function TextExtentW(Text: WideString; Canvas: TCanvas): TSize;
begin
  FillChar(Result, SizeOf(Result), #0);
  if Assigned(Canvas) and (Text <> '') then
  begin
    Canvas.Lock;
    Result := InternalTextExtentW(PWideChar(Text), Canvas.Handle);
    Canvas.Unlock;
  end;
end;

function TextExtentW(Text: PWideChar; Canvas: TCanvas): TSize;
begin
  FillChar(Result, SizeOf(Result), #0);
  if Assigned(Canvas) and (Assigned(Text)) then
  begin
    Canvas.Lock;
    Result := InternalTextExtentW(Text, Canvas.Handle);
    Canvas.Unlock;
  end;
end;

function TextExtentW(Text: PWideChar; DC: hDC): TSize;
begin
  FillChar(Result, SizeOf(Result), #0);
  if (DC <> 0) and (Assigned(Text)) then
    Result := InternalTextExtentW(Text, DC);
end;

type
  TABCArray = array of TABC;

function TextTrueExtentsW(Text: WideString; DC: HDC): TSize;
var
  ABC: TABC;
  TextMetrics: TTextMetric;
  S: string;
  i: integer;
begin
   // Get the Height at least
   GetTextExtentPoint32W(DC, PWideChar(Text), Length(Text), Result);

   GetTextMetrics(DC, TextMetrics);
   if TextMetrics.tmPitchAndFamily and TMPF_TRUETYPE <> 0 then
   begin
     Result.cx := 0;
     // Is TrueType
     if Win32Platform = VER_PLATFORM_WIN32_NT then
     begin
       for i := 1 to Length(Text) do
       begin
         GetCharABCWidthsW_MP(DC, Ord(Text[i]), Ord(Text[i]), ABC);
         Result.cx := Result.cx + ABC.abcA + integer(ABC.abcB) + ABC.abcC;
       end
     end else
     begin
       S := Text;
       for i := 1 to Length(S) do
       begin
         GetCharABCWidthsA(DC, Ord(S[i]), Ord(S[i]), ABC);
         Result.cx := Result.cx + ABC.abcA + integer(ABC.abcB) + ABC.abcC;
       end
     end;
   end
end;

function UniqueFileName(const AFilePath: WideString): WideString;

{ Creates a unique file name in based on other files in the passed path         }

var
  i: integer;
  WP: PWideChar;
begin
  Result := AFilePath;
  i := 2;
  while FileExistsW(Result) and (i < 20) do
  begin
    Result := AFilePath;
    WP := WideStrRScan(PWideChar( Result), '.');
    if Assigned(WP) then
      WideInsert( ' (' + WideIntToStr(i) + ')', Result, PWideChar(WP) - PWideChar(Result))
    else begin
      Result := '';
      Break;
    end;
    Inc(i)
  end;
end;

function TNTConditionallyDefined: Boolean;
begin
  {$IFDEF TNTSUPPORT}
  Result := True;
  {$ELSE}
  Result := False;
  {$ENDIF}
end;

function UnicodeStringLists: Boolean;
begin
  {$IFDEF TNTSUPPORT}
  Result := True;
  {$ELSE}
  Result := False;
  {$ENDIF}
end;

function UniqueDirName(const ADirPath: WideString): WideString;
var
  i: integer;
begin
  Result := ADirPath;
  i := 2;
  while DirExistsW(PWideChar(Result)) and (i < 20) do
  begin
    Result := ADirPath;
    WideInsert( ' (' + WideIntToStr(i) + ')', Result, Length(Result));
    Inc(i)
  end;
end;

function WideStripExt(AFile: WideString): WideString;
{ Strips the extenstion off a file name }
var
  i: integer;
  Done: Boolean;
begin
  i := Length(AFile);
  Done := False;
  Result := AFile;
  while (i > 0) and not Done do
  begin
    if AFile[i] = '.' then
    begin
      Done := True;
      SetLength(Result, i - 1);
    end;
    Dec(i);
  end;
end;

function WideStripRemoteComputer(const UNCPath: WideString): WideString;
  // Strips the \\RemoteComputer\ part of an UNC path
var
  Head: PWideChar;
begin
  Result := '';
  if IsUNCPath(UNCPath) then
  begin
    Result := '';
    if IsUNCPath(UNCPath) then
    begin
      Result := UNCPath;
      Head := @Result[1];
      Head := Head + 2;    // Skip past the '\\'
      Head := WideStrScan(Head, WideChar('\'));
      if Assigned(Head) then
      begin
        Head := Head + 1;
        Move(Head[0], Result[1], (lstrlenW(Head) + 1) * 2);
      end;
      SetLength(Result, lstrlenW(PWideChar(Result)));
    end;
  end;
end;

function WideStripTrailingBackslash(const S: WideString; Force: Boolean = False): WideString;
begin
  Result := S;
  if Result <> '' then
  begin
    // Works with FilePaths and FTP Paths
    if Result[ Length(Result)] in [WideChar('/'), WideChar('\')] then
      if not WideIsDrive(Result) or Force then // Don't strip off if is a root drive
        SetLength(Result, Length(Result) - 1);
  end;
end;

function WideStripLeadingBackslash(const S: WideString): WideString;
begin
  Result := S;
  if Result <> '' then
  begin
    if (S[1] = '\') and (Length(S) > 1) then
      Result := PWideChar( @S[2])
    else
      Result := ''
  end;
end;

function WideStringReplace(const S, OldPattern, NewPattern: WideString; Flags: TReplaceFlags; WholeWord: Boolean = False): WideString;
begin
  {$IFDEF TNTSUPPORT}
  Result := Tnt_WideStringReplace(S, OldPattern, NewPattern, [rfReplaceAll, rfIgnoreCase], WholeWord);
  {$ELSE}
  StringReplace(S, OldPattern, NewPattern, [rfReplaceAll, rfIgnoreCase])
  {$ENDIF};
end;

function WideShellExecute(hWnd: HWND; Operation, FileName, Parameters, Directory: WideString; ShowCmd: Integer = SW_NORMAL): HINST;
var
  OperationA, FileNameA, ParametersA, DirectoryA: string;
  PA, DA: PChar;
  PW, DW: PWideChar;
begin
  if Assigned(ShellExecuteW_MP) then
  begin
    PW := nil;
    DW := nil;
    if Parameters = '' then
      PW := PWideChar(Parameters);
    if Directory = '' then
      DW := PWideChar(Directory);   
    Result := ShellExecuteW_MP(hWnd, PWideChar(Operation), PWideChar(FileName), PW, DW, SW_NORMAL)
  end else
  begin
    OperationA := Operation;
    FileNameA := FileName;
    ParametersA := Parameters;
    DirectoryA := Directory;
    PA := nil;
    DA := nil;
    if ParametersA <> '' then
      PA := PChar( ParametersA);
    if DirectoryA <> '' then
      DA := PChar( DirectoryA);
    Result := ShellExecuteA(hWnd, PChar(OperationA), PChar(FileNameA), PA, DA, SW_NORMAL)
  end
end;

procedure WideShowMessage(Window: HWND; ACaption, AMessage: WideString);
var
  TextA, CaptionA: string;
begin
  if IsUnicode then
    MessageBoxW(Window, PWideChar( AMessage), PWideChar( ACaption), MB_ICONEXCLAMATION or MB_OK)
  else begin
    TextA := AMessage;
    CaptionA := ACaption;
    MessageBoxA(Window, PChar( TextA), PChar( CaptionA), MB_ICONEXCLAMATION or MB_OK)
  end          
end;

function WideLowerCase(const Str: WideString): WideString;
begin
  Result := Str;
  if IsUnicode then
    CharLowerBuffW_MP(PWideChar(Result), Length(Result))
  else
   CharLowerBuffA(PChar(string(Result)), Length(Result))
end;

function WideMessageBox(Window: HWND; const ACaption, AMessage: WideString; uType: integer): integer;
var
  TextA, CaptionA: string;
begin
  if IsUnicode then
    Result := MessageBoxW(Window, PWideChar( AMessage), PWideChar( ACaption), uType)
  else begin
    TextA := AMessage;
    CaptionA := ACaption;
    Result := MessageBoxA(Window, PChar( TextA), PChar( CaptionA), uType)
  end
end;

function IncludeTrailingBackslashW(const S: WideString): WideString;
begin
  Result := S;
  if not WideIsPathDelimiter(Result, Length(Result)) then Result := Result + '\';
end;

function DiskInDrive(C: Char): Boolean;
var
  OldErrorMode: Integer;
begin
  C := UpCase(C);
  if C in ['A'..'Z'] then
  begin
    OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
    Result :=  DiskFree(Ord(C) - Ord('A') + 1) > -1;
    SetErrorMode(OldErrorMode);
  end else
    Result := False
end;

function WideStrIComp(Str1, Str2: PWideChar): Integer;
// Insensitive case comparison
var
  S1, S2: string;
begin
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    Result := lstrcmpiW_MP(Str1, Str2)
  else begin
    S1 := Str1;
    S2 := Str2;
    Result := lstrcmpi(PChar(S1), PChar(S2))
  end
end;

function StrRScanW(Str: PWideChar; Chr: WideChar): PWideChar;
// returns a pointer to the last occurance of Chr in Str
asm
       PUSH    EDI
       MOV     EDI, Str
       MOV     ECX, 0FFFFFFFFH
       XOR     AX, AX
       REPNE   SCASW
       NOT     ECX
       STD
       SUB     EDI, 2
       MOV     AX, Chr
       REPNE   SCASW
       MOV     EAX, 0
       JNE     @@1
       MOV     EAX, EDI
       ADD     EAX, 2
@@1:
       CLD
       POP     EDI
end;

function WideStrComp(Str1, Str2: PWideChar): Integer;
// Sensitive case comparison
var
  S1, S2: string;
begin
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    Result := lstrcmpW_MP(Str1, Str2)
  else begin
    S1 := Str1;
    S2 := Str2;
    Result := lstrcmp(PChar(S1), PChar(S2))
  end
end;

function WideStrLower(Str: PWideChar): PWideChar;
//  Returns the string in Str converted to lower case
var
  S: string;
  WS: WideString;
begin
  Result := Str;
  if IsUnicode then
    CharLowerBuffW_MP(Str, lstrlenW(Str))
  else begin
    S := Str;
    CharLowerBuffA(PChar(S), Length(S));
    WS := S;
    { WS is a string index from 1, Result is PWideChar index from 0 }
    Move(WS[1], Result[0], Length(WS));
  end;
end;

procedure WideStrLCopy(Str1, Str2: PWideChar; Count: Integer);
// Count must include the terminating null
begin
    if Win32Platform = VER_PLATFORM_WIN32_NT then
    lstrcpynW_MP(Str1, Str2, Count)
  else
    Move(Str2, Str1, Count * 2);
end;

function ShortenStringEx(DC: HDC; const S: WideString; Width: Integer; RTL: Boolean;
  EllipsisPlacement: TShortenStringEllipsis): WideString;
// Shortens the passed string and inserts ellipsis "..." in the requested place.
// This is not a fast function but it is clear how it works.  Also the RTL implmentation
// is still being understood.
var
  Len: Integer;
  EllipsisWidth: Integer;
  TargetString: WideString;
  Tail, Head, Middle: PWideChar;
  L, ResultW: integer;
begin
  Len := Length(S);
  if (Len = 0) or (Width <= 0) then
    Result := ''
  else begin
    // Determine width of triple point using the current DC settings
    TargetString := '...';
    EllipsisWidth := TextExtentW(PWideChar(TargetString), DC).cx;

    if Width <= EllipsisWidth then
      Result := ''
    else begin
      TargetString := S;
      Head := PWideChar(TargetString);
      Tail := Head;
      Inc(Tail, lstrlenW(PWideChar(TargetString)));
      case EllipsisPlacement of
        sseEnd:
          begin
            L := EllipsisWidth + TextExtentW(PWideChar(TargetString), DC).cx;
            while (L > Width) do
            begin
              Dec(Tail);
              Tail^ := WideNull;
              L := EllipsisWidth + TextExtentW(PWideChar(TargetString), DC).cx;
            end;
            Result := PWideChar(TargetString) + '...';
          end;
        sseFront:
          begin
            L := EllipsisWidth + TextExtentW(PWideChar(TargetString), DC).cx;
            while (L > Width) do
            begin
              Inc(Head);
              L := EllipsisWidth + TextExtentW(PWideChar(Head), DC).cx;
            end;
            Result := '...' + PWideChar(Head);
          end;
        sseMiddle:
          begin
            L := EllipsisWidth + TextExtentW(PWideChar(TargetString), DC).cx;
            while (L > Width div 2 - EllipsisWidth div 2) do
            begin
              Dec(Tail);
              Tail^ := WideNull;
              L := TextExtentW(PWideChar(TargetString), DC).cx;
            end;
            Result := PWideChar(TargetString) + '...';
            ResultW := TextExtentW(PWideChar(Result), DC).cx;

            TargetString := S;
            Head := PWideChar(TargetString);
            Middle := Head;
            Inc(Middle, lstrlenW(PWideChar(Result)) - 3); // - 3 for ellipsis letters
            Tail := Head;
            Inc(Tail, lstrlenW(PWideChar(TargetString)));
            Head := Tail - 1;

            L := ResultW + TextExtentW(Head, DC).cx;
            while (L < Width) and (Head >= Middle) do
            begin
              Dec(Head);
              L := ResultW + TextExtentW(PWideChar(Head), DC).cx;
            end;
            Inc(Head);

            Result := Result + Head;
          end;
        sseFilePathMiddle:
          begin
            Head := Tail - 1;
            L := EllipsisWidth + TextExtentW(Head, DC).cx;
            while (Head^ <> '\') and (Head <> @TargetString[1]) and (L < Width) do
            begin
              Dec(Head);
              L := EllipsisWidth + TextExtentW(Head, DC).cx;
            end;
            if Head^ <> '\' then
              Inc(Head);
            Result := '...' + Head;

            ResultW := TextExtentW(PWideChar(Result), DC).cx;

            Head^ := WideNull;
            SetLength(TargetString, lstrlenW(PWideChar(TargetString)));

            Head := PWideChar(TargetString);
            Tail := Head;
            Inc(Tail, lstrlenW(Head));

            L := ResultW + TextExtentW(PWideChar(TargetString), DC).cx;
            while (L > Width) and (Tail > @TargetString[1]) do
            begin
              Dec(Tail);
              Tail^ := WideNull;
              L := ResultW + TextExtentW(PWideChar(TargetString), DC).cx;
            end;

            Result := Head + Result;
          end;
      end;

      // Windows 2000 automatically switches the order in the string. For every other system we have to take care.
 {     if IsWin2000 or not RTL then
        Result := Copy(S, 1, N - 1) + '...'
      else
        Result := '...' + Copy(S, 1, N - 1);       }
    end;
  end;
end;

function WindowsDirectory: WideString;
var
  Len: integer;
  S: string;
begin
  Result := '';
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    Len := GetWindowsDirectoryW_MP(PWideChar(Result), 0)
  else
    Len := GetWindowsDirectoryA(PChar(S), 0);
  if Len > 0 then
  begin
    if IsUnicode then
    begin
      SetLength(Result, Len - 1);
      GetWindowsDirectoryW_MP(PWideChar(Result), Len);
    end else
    begin
      SetLength(S, Len - 1);
      GetWindowsDirectoryA(PChar(S), Len);
      Result := S
    end
  end
end;

function ModuleFileName(PathOnly: Boolean = True): Widestring;
var
  BufferA: array[0..MAX_PATH] of Char;
  BufferW: array[0..MAX_PATH] of WideChar;
begin
  if IsUnicode then
  begin
    FillChar(BufferW, SizeOf(BufferW), #0);
    if GetModuleFileNameW(0, BufferW, SizeOf(BufferW)) > 0 then
    begin
      if PathOnly then
        Result := ExtractFileDirW(BufferW)
      else
        Result := BufferW;
    end
  end else
  begin
    FillChar(BufferA, SizeOf(BufferA), #0);
    if GetModuleFileNameA(0 , BufferA, SizeOf(BufferA)) > 0 then
    begin
      if PathOnly then
        Result := ExtractFileDirW(BufferA)
      else
        Result := BufferA
    end
  end
end;

function PIDLToPath(PIDL: PItemIDList): WideString;
var
  PIDLMgr: TCommonPIDLManager;
  LastID: PItemIDList;
  LastCB: Word;
  Desktop, Folder: IShellFolder;
  StrRet: TStrRet;
begin
  Result := '';
  if Assigned(PIDL) then
  begin
    FillChar(StrRet, SizeOf(StrRet), #0);
    PIDLMgr := TCommonPIDLManager.Create;
    try
      SHGetDesktopFolder(Desktop);
      if PIDLMgr.IsDesktopFolder(PIDL) then
      begin
      end else
      begin
        PIDLMgr.StripLastID(PIDL, LastCB, LastID);
        try
          if Succeeded(Desktop.BindToObject(PIDL, nil, IShellFolder, pointer(Folder))) then
          begin
            LastID.mkid.cb := LastCB;
            if Succeeded(Folder.GetDisplayNameOf(LastID, SHGDN_FORPARSING, StrRet)) then
               Result := StrRetToStr(StrRet, LastID);
          end
        finally
          LastID.mkid.cb := LastCB;
        end
      end
    finally
      PIDLMgr.Free
    end
  end
end;

function ShortFileName(const FileName: WideString): WideString;
var
  S: string;
  BufferA: array[0..MAX_PATH] of char;
  BufferW: array[0..MAX_PATH] of WideChar;
begin
  if IsUnicode then
  begin
    if GetShortPathNameW(PWideChar(FileName), BufferW, SizeOf(BufferW)) > 0 then
      Result := BufferW
  end else
  begin
    S := FileName;
    if GetShortPathNameA(PChar(S), BufferA, SizeOf(BufferA)) > 0 then
      Result := BufferA
  end
end;

function ShortPath(const Path: WideString): WideString;
begin
  Result := ShortFileName(Path)
end;

procedure LoadWideString(S: TStream; var Str: WideString);
var
  Count: Integer;
begin
  S.Read(Count, SizeOf(Integer));
  SetLength(Str, Count);
  S.Read(PWideChar(Str)^, Count * 2)
end;

procedure SaveWideString(S: TStream; Str: WideString);
var
  Count: Integer;
begin
  Count := Length(Str);
  S.Write(Count, SizeOf(Integer));
  S.Write(PWideChar(Str)^, Count * 2)
end;


function WideStrPos(Str, SubStr: PWideChar): PWideChar;
// returns a pointer to the first occurance of SubStr in Str
asm
       PUSH    EDI
       PUSH    ESI
       PUSH    EBX
       OR      EAX, EAX
       JZ      @@2
       OR      EDX, EDX
       JZ      @@2
       MOV     EBX, EAX
       MOV     EDI, EDX
       XOR     AX, AX
       MOV     ECX, 0FFFFFFFFH
       REPNE   SCASW
       NOT     ECX
       DEC     ECX
       JZ      @@2
       MOV     ESI, ECX
       MOV     EDI, EBX
       MOV     ECX, 0FFFFFFFFH
       REPNE   SCASW
       NOT     ECX
       SUB     ECX, ESI
       JBE     @@2
       MOV     EDI, EBX
       LEA     EBX, [ESI - 1]
@@1:
       MOV     ESI, EDX
       LODSW
       REPNE   SCASW
       JNE     @@2
       MOV     EAX, ECX
       PUSH    EDI
       MOV     ECX, EBX
       REPE    CMPSW
       POP     EDI
       MOV     ECX, EAX
       JNE     @@1
       LEA     EAX, [EDI - 2]
       JMP     @@3

@@2:
       XOR     EAX, EAX
@@3:
       POP     EBX
       POP     ESI
       POP     EDI
end;

function ProperRect(Rect: TRect): TRect;
// Makes sure a rectangle's left is less than its right and its top is less than its bottom
var
  Temp: integer;
begin
  Result := Rect;
  if Result.Right < Result.Left then
  begin
    Temp := Result.Right;
    Result.Right := Rect.Left;
    Result.Left := Temp;
  end;
  if Rect.Bottom < Rect.Top then
  begin
    Temp := Result.Top;
    Result.Top := Rect.Bottom;
    Result.Bottom := Temp;
  end
end;

function DragDetectPlus(Handle: HWND; Pt: TPoint): Boolean;
//  Replacement for DragDetect API which is buggy.
//    Pt is in Client Coords of the Handle window
var
  DragRect: TRect;
  Msg: TMsg;
  TestPt: TPoint;
  HadCapture, Done: Boolean;
begin
  Result := False;
  Done := False;
  HadCapture := GetCapture = Handle;
  if (not ClientToScreen(Handle, Pt)) then
    Exit;
  DragRect.TopLeft := Pt;
  DragRect.BottomRight := Pt;
  InflateRect(DragRect, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
  SetCapture(Handle);
  try
    while (not Result) and (not Done) do
      if (PeekMessage(Msg, Handle, 0,0, PM_REMOVE)) then
      begin
        case (Msg.message) of
          WM_MOUSEMOVE:
          begin
            TestPt := Msg.Pt;
            // Not sure why this works.  The Message point "should" be in client
            // coordinates but seem to be screen
     //       Windows.ClientToScreen(Msg.hWnd, TestPt);
            Result := not(PtInRect(DragRect, TestPt));
          end;
          WM_RBUTTONUP,
          WM_LBUTTONUP,
          WM_CANCELMODE,
          WM_LBUTTONDBLCLK,
          WM_MBUTTONUP:
            begin
              // Let the window get these messages after we have ended our
              // local message loop
              PostMessage(Msg.hWnd, Msg.message, Msg.wParam, Msg.lParam);
              Done := True;
            end;
          WM_QUIT:
            begin
              PostQuitMessage(Msg.wParam);
              Done := True;
            end
        else
          TranslateMessage(Msg);
          DispatchMessage(Msg)
        end
      end else
        Sleep(0);
  finally
    ReleaseCapture;
    if HadCapture then
      Mouse.Capture := Handle;
  end;
end;

procedure FillWideChar(var Dest; count: Integer; Value: WideChar);
var
  I: Integer;
  P: PWideChar;
begin
  P := PWideChar(@Dest);
  for I := count-1 downto 0 do
    P[I] := Value;
end;

procedure FreeMemAndNil(var P: Pointer);
{ Frees the memeory allocated with GetMem and nils the pointer                  }
var
  Temp: Pointer;
begin
  Temp := P;
  P := nil;
  FreeMem(Temp);
end;

function IsRectNull(ARect: TRect): Boolean;
begin
  Result := EqualRect(ARect, Rect(0, 0, 0, 0))
end;

function IsUnicode: Boolean;
begin
  Result := Win32Platform = VER_PLATFORM_WIN32_NT
end;

function IsWinNT: Boolean;
begin
  Result := Win32Platform = VER_PLATFORM_WIN32_NT
end;

function IsWin2000: Boolean;
begin
  Result := False;
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    Result := LoWord(Win32MajorVersion) >= 5
end;

function IsWin95_SR1: Boolean;
begin
  Result := False;
  if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
    Result :=  ((Win32MajorVersion = 4) and
                (Win32MinorVersion = 0) and
                (LoWord(Win32BuildNumber) <= 1080))
end;

function IsWinME: Boolean;
begin
  Result := False;
  if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
    Result := Win32BuildNumber >= $045A0BB8
end;

function IsWinNT4: Boolean;
begin
  Result := False;
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    Result := Win32MajorVersion < 5
end;

function IsWinXP: Boolean;
begin
  Result := (Win32Platform = VER_PLATFORM_WIN32_NT) and (Win32MajorVersion = 5)
    and (Win32MinorVersion > 0)
end;

function IsWinXPOrUp: Boolean;
begin
  Result := (Win32Platform = VER_PLATFORM_WIN32_NT) and (Win32MajorVersion >= 5)
    and (Win32MinorVersion > 0)
end;

function IsWinVista: Boolean;
begin
  Result := (Win32Platform = VER_PLATFORM_WIN32_NT) and (Win32MajorVersion = 6)
    and (Win32MinorVersion >= 0)
end;

function IsWinVistaOrUp: Boolean;
begin
  Result := (Win32Platform = VER_PLATFORM_WIN32_NT) and (Win32MajorVersion >= 6)
    and (Win32MinorVersion >= 0)
end;

function RectHeight(R: TRect): Integer;
begin
  Result := R.Bottom - R.Top
end;

function RectToStr(R: TRect): string;
begin
  Result := 'Left = ' + IntToStr(R.Left) +
            ' Top = ' + IntToStr(R.Top) +
            ' Right = ' + IntToStr(R.Right) +
            ' Bottom = ' + IntToStr(R.Bottom)
end;

function RectToSquare(R: TRect): TRect;
// Takes the passed rectangle and makes it square based on the longest dimension
begin
  if RectWidth(R) > RectHeight(R) then
    R.Right := R.Left + RectHeight(R)
  else
  if RectHeight(R) > RectWidth(R) then
  begin
    R.Bottom := R.Top + RectWidth(R);
  end else
    Result := R
end;

function RectWidth(R: TRect): Integer;
begin
  Result := R.Right - R.Left
end;

function ContainsRect(OuterRect, InnerRect: TRect): Boolean;
//
//  Returns true if the InnerRect is completely contained within the
//  OuterRect
//
begin
  Result := (InnerRect.Left >= OuterRect.Left) and
            (InnerRect.Right <= OuterRect.Right) and
            (InnerRect.Top >= OuterRect.Top) and
            (InnerRect.Bottom <= OuterRect.Bottom)
end;

function KeyDataToShiftState(KeyData: Longint): TShiftState;
begin
  Result := [];
  if GetKeyState(VK_SHIFT) < 0 then
    Include(Result, ssShift);
  if GetKeyState(VK_CONTROL) < 0 then
    Include(Result, ssCtrl);
  if KeyData and $20000000 <> 0 then
    Include(Result, ssAlt);
end;

function DropEffectToDropEffectState(Effect: Integer): TCommonDropEffect;
begin
  Result := cdeNone;
  if Effect and DROPEFFECT_COPY <> 0 then
    Result := cdeCopy
  else
  if Effect and DROPEFFECT_MOVE <> 0 then
    Result := cdeMove
  else
  if Effect and DROPEFFECT_LINK <> 0 then
    Result := cdeLink
end;

function DropEffectStateToDropEffect(Effect: TCommonDropEffect): Integer;
begin
  Result := 0;
  if cdeCopy = Effect then
    Result := Result or DROPEFFECT_COPY
  else
  if cdeMove = Effect then
    Result := Result or DROPEFFECT_MOVE
  else
  if cdeLink = Effect then
    Result := Result or DROPEFFECT_LINK
end;

function DropEffectToDropEffectStates(Effect: Integer): TCommonDropEffects;
begin
  Result := [];
  if (Effect = 0) or (Longword(Effect) = DROPEFFECT_SCROLL) then
    Include(Result, cdeNone);
  if Effect and DROPEFFECT_COPY <> 0 then
    Include(Result, cdeCopy);
  if Effect and DROPEFFECT_MOVE <> 0 then
    Include(Result, cdeMove);
  if Effect and DROPEFFECT_LINK <> 0 then
    Include(Result, cdeLink);
  if Effect and DROPEFFECT_SCROLL <> 0 then
    Include(Result, cdeScroll)
end;

function DropEffectStatesToDropEffect(Effect: TCommonDropEffects): Integer;
begin
  Result := 0;
  if cdeCopy in Effect then
    Result := Result or DROPEFFECT_COPY;
  if cdeMove in Effect then
    Result := Result or DROPEFFECT_MOVE;
  if cdeLink in Effect then
    Result := Result or DROPEFFECT_LINK;
  if cdeScroll in Effect then
    Result := Result or Integer(DROPEFFECT_SCROLL);
end;

function KeyToKeyStates(Keys: Word): TCommonKeyStates;
begin
  Result := [];
  if Keys and MK_CONTROL <> 0 then
    Include(Result, cksControl);
  if Keys and MK_LBUTTON <> 0 then
    Include(Result, cksLButton);
  if Keys and MK_MBUTTON <> 0 then
    Include(Result, cksMButton);
  if Keys and MK_RBUTTON <> 0 then
    Include(Result, cksRButton);
  if Keys and MK_SHIFT <> 0 then
    Include(Result, cksShift);
  if Keys and MK_ALT <> 0 then
    Include(Result, cksAlt);
  if Keys and MK_BUTTON <> 0 then
    Include(Result, cksButton);
end;

function KeyStatesToMouseButton(Keys: Word): TCommonMouseButton;
begin
  if Keys and MK_LBUTTON <> 0 then
    Result := cmbLeft
  else
  if Keys and MK_MBUTTON <> 0 then
    Result := cmbMiddle
  else
  if Keys and MK_RBUTTON <> 0 then
    Result := cmbRight
  else
    Result := cmbNone
end;

function KeyStatesToKey(Keys: TCommonKeyStates): Longword;
begin
  Result := 0;
  if cksControl in Keys then
    Result := Result or MK_CONTROL;
  if cksLButton in Keys then
    Result := Result or MK_LBUTTON;
  if cksMButton in Keys then
    Result := Result or MK_MBUTTON;
  if cksRButton in Keys then
    Result := Result or MK_RBUTTON;
  if cksShift in Keys then
    Result := Result or MK_SHIFT;
  if cksAlt in Keys then
    Result := Result or MK_ALT;
end;

function KeyStateToDropEffect(Keys: TCommonKeyStates): TCommonDropEffect;
begin
  Result := cdeMove;  // The default
  if (cksControl in Keys) and not ((cksShift in Keys) or (cksAlt in Keys)) then
    Result := cdeCopy
  else
  if ((cksAlt in Keys) and not ((cksShift in Keys) or (cksControl in Keys))) or
     ((cksShift in Keys) and (cksControl in Keys)) then
    Result := cdeLink
end;

function KeyStateToMouseButton(KeyState: TCommonKeyStates): TCommonMouseButton;
begin
  if KeyState * [cksLButton] <> [] then
    Result := cmbLeft
  else
  if KeyState * [cksRButton] <> [] then
    Result := cmbRight
  else
  if KeyState * [cksMButton] <> [] then
    Result := cmbMiddle
  else
    Result := cmbNone
end;

function FileIconInit(FullInit: BOOL): BOOL; stdcall;
// Forces the system to load all Images into the ImageList. Normally with NT
// system icons are only loaded as needed.
type
  TFileIconInit = function(FullInit: BOOL): BOOL; stdcall;
var
  ShellDLL: HMODULE;
  PFileIconInit: TFileIconInit;
begin
  Result := False;
  if (Win32Platform = VER_PLATFORM_WIN32_NT) then
  begin
    ShellDLL := CommonLoadLibrary(Shell32);
    PFileIconInit := GetProcAddress(ShellDLL, PChar(660));
    if (Assigned(PFileIconInit)) then
      Result := PFileIconInit(FullInit);
  end;
end;

function SHGetImageList(iImageList: Integer; const RefID: TGUID; out ppvOut): HRESULT; stdcall;
// Retrieves the system ImageList interface
var
  ShellDLL: HMODULE;
  ImageList: TSHGetImageList;
begin
  Result := E_NOTIMPL;
  if (Win32Platform = VER_PLATFORM_WIN32_NT) then
  begin
 //   if GetFileVersion(comctl32) >= $00060000 then
    begin
      ShellDLL := CommonLoadLibrary(Shell32);
      ImageList := GetProcAddress(ShellDLL, PChar(727));
      if (Assigned(ImageList)) then
        Result := ImageList(iImageList, RefID, ppvOut);
    end
  end;
end;

function Size(cx, cy: Integer): TSize;
begin
  Result.cy := cy;
  Result.cx := cx
end;

function ShortenTextW(DC: hDC; TextToShorten: WideString; MaxSize: Integer): WideString;
// Shortens the passed string in such a way that if it does not fit in the MaxSize
// (in Pixels) a "..." is inserted at the correct place where the new string fixs
// in MaxSize
var
  Size: TSize;
  EllipsisSize: TSize;
//  TailA: String;
  StrLen, Middle, Low, High{, LastHigh}: Cardinal;
// WChar: WideChar;
//  Done: Boolean;
begin
  if TextToShorten <> '' then
  begin
    GetTextExtentPoint32W(DC, PWideChar(TextToShorten), Length(TextToShorten), Size);
    GetTextExtentPoint32W(DC, '...', 3, EllipsisSize);
    StrLen := Length(TextToShorten);
    if Size.cx > MaxSize then
    begin
   (*   Middle := StrLen div 2;
      Done := False;
      Low := 0;
      High := StrLen;
      LastHigh := High;
      // Do a psudo binary search
      while not Done do
      begin
        GetTextExtentPoint32W(DC, @TextToShorten[1], Middle, Size);
        Size.cx := Size.cx + EllipsisSize.cx;

        if Size.cx > MaxSize then
        begin
          // Still to long
          LastHigh := High;
          High := Middle;
          Middle := Low + (High - Low) div 2;
        end else
        begin
          // Too short
          High := LastHigh;
          Low := Middle;

          Middle := Low + (High - Low) div 2;
        end;
        Done := High - Low <= 2;
      end;      *)

      // Milan's version
      Low:=0;
      High:=StrLen-1;
      while (Low<High) do
        begin
          Middle:=(Low+High+1) shr 1;
      //    if IsUnicode then
            GetTextExtentPoint32W(DC, @TextToShorten[1], Middle, Size);
      //    else begin
      //      TailA := TextToShorten[1];
      //      GetTextExtentPoint32(DC, @TailA[1], 1, Size);
      //    end;
          Size.cx := Size.cx + EllipsisSize.cx;
          if (Size.cx<=MaxSize) then
            Low:=Middle
          else
            High:=Middle-1;
        end; 

      SetLength(TextToShorten, Low);
      if Low > 0 then
        Result := TextToShorten + '...'
      else
        Result := '...'
    end else
      Result := TextToShorten
  end else
    Result := '';
end;

(*
function SplitTextW(DC: hDC; TextToSplit: WideString; MaxWidth: Integer;
  var Buffer: TWideCharArray; MaxSplits: Integer): Integer;
// Takes the passed string and breaks it up so each piece fits within the MaxWidth
// The function detects any LF/CR pairs and treats them as one break if CR or LF
// is defined as a break character.
// The Buffer is a set of NULL terminated strings for each line, with the last
// one being terminated with a double NULL.  Much like the SHFileOperation API
// This makes it ready to use to pass the strings to ExtTextOutW in a loop
// If the buffer is too small the Result will be false
// If MaxSplits = -1 then the function splits as many time as necessary
// The Return is the total number of lines the passed text was split into
var
  Head, Tail, LastBreakChar, BufferHead: PWideChar;
  Size: TSize;
  LineWidth, SplitCount, Len: Integer;
  TextMetrics: TTextMetric;
 { PeriodW1, PeriodW2, PeriodW3: Integer;  }
begin
  Result := 0;
  if MaxWidth = 0 then
  begin
    SetLength(Buffer, 2);
    Buffer[0] := WideNull;
    Buffer[1] := WideNull;
  end else
  begin
    GetTextMetrics(DC, TextMetrics);

    // Can get into deep trouble if a single letter won't fit in the MaxWidth
    if TextMetrics.tmMaxCharWidth > MaxWidth then
    begin
      Len := Length(TextToSplit);
      SetLength(Buffer, Len + 2);
      if Len > 0 then
      begin
        Head := @TextToSplit[1];
        CopyMemory(Buffer, Head, Len*2);
        Result := 1;
      end;
      Buffer[Len] := #0;
      Buffer[Len + 1] := #0;
    end else
    begin
      FillChar(Size, SizeOf(Size), #0);
      // Arbitrary size that should be ok in most instances.  This will be enough space
      // for 127 lines
      SetLength(Buffer, Length(TextToSplit) + 128);
      BufferHead := @Buffer[0];
      Head := PWideChar(TextToSplit);
      Tail := Head;
      SplitCount := 0;
      while (Tail^ <> #0) and ((SplitCount < MaxSplits) or (MaxSplits = -1)) do
      begin
        LineWidth := 0;
        LastBreakChar := nil;
        while (Tail^ <> WideNull) and (LineWidth <= MaxWidth) and (Tail^ <> WideCR) and (Tail^ <> WideLF) do
        begin
          GetTextExtentPoint32W(DC, PWideChar(Tail), 1, Size);
          Inc(LineWidth, Size.cx);

          Inc(Tail);

          if (LineWidth <= MaxWidth) and (Tail^ = WideSpace) then
            LastBreakChar := Tail;
        end;

        if (LineWidth > MaxWidth) {and (Tail^ <> WideSpace)} then
        begin
          // Over ran the line unless it exactly fits
          if Assigned(LastBreakChar) and (LineWidth > MaxWidth) then
          begin
            // We have word break to go back to
            Tail := LastBreakChar;
            Inc(SplitCount);
            if SplitCount = MaxSplits then
            begin
              // If no more splits allows copy the entire rest of the string to the buffer
              Len := lStrLenW(Head);
              CopyMemory(BufferHead, Head, Len*2);
              Inc(BufferHead, Len);
            end else
            begin
              CopyMemory(BufferHead, Head, (Tail-Head)*2);
              Inc(BufferHead, Tail-Head);
            end;
            BufferHead^ := WideNull;
            Inc(BufferHead);
          end else
          begin
            // Special case, the Tail is the end of the Text to split
            if Tail^ <> WideNull then
              Dec(Tail);
            Inc(SplitCount);
            if SplitCount = MaxSplits then
            begin
              // If no more splits allows copy the entire rest of the string to the buffer
              Len := lStrLenW(Head);
              CopyMemory(BufferHead, Head, Len*2);
              Inc(BufferHead, Len);
            end else
            begin
              CopyMemory(BufferHead, Head, (Tail-Head)*2);
              Inc(BufferHead, Tail-Head);
            end;
            BufferHead^ := WideNull;
            Inc(BufferHead);
          end;
          Inc(Result);
        end else
        begin
          Inc(SplitCount);
          if SplitCount = MaxSplits then
          begin
            // If no more splits allows copy the entire rest of the string to the buffer
            Len := lStrLenW(Head);
            CopyMemory(BufferHead, Head, Len*2);
            Inc(BufferHead, Len);
          end else
          begin
            CopyMemory(BufferHead, Head, (Tail-Head)*2);
            Inc(BufferHead, Tail-Head);
          end;
          BufferHead^ := WideNull;
          Inc(BufferHead);
          Inc(Result)
        end;

        while (Tail^ <> #0) and ((Tail^ = WideCR) or (Tail^ = WideLF) or (Tail^ = WideSpace)) do
          Inc(Tail);
        Head := Tail
      end
    end
  end
end;
*)


// Solerman's version
function SplitTextW(DC: hDC; TextToSplit: WideString; MaxWidth: Integer;
   var Buffer: TCommonWideCharArray; MaxSplits: Integer): Integer;
// Takes the passed string and breaks it up so each piece fits within the MaxWidth
// The function detects any LF/CR pairs and treats them as one break if CR or LF
// is defined as a break character.
// The Buffer is a set of NULL terminated strings for each line, with the last
// one being terminated with a double NULL.  Much like the SHFileOperation API
// This makes it ready to use to pass the strings to ExtTextOutW in a loop
// If the buffer is too small the Result will be false
// If MaxSplits = -1 then the function splits as many time as necessary
// The Return is the total number of lines the passed text was split into
var
   Head, Tail, LastBreakChar, BufferHead: PWideChar;
//   TailA: String;
   Size: TSize;
   LineWidth, SplitCount, Len: Integer;
   TextMetrics: TTextMetric;
begin
   Result := 0;
   if MaxWidth = 0 then
   begin
     SetLength(Buffer, 2);
     Buffer[0] := WideNull;
     Buffer[1] := WideNull;
   end else
   begin
     GetTextMetrics(DC, TextMetrics);

     // Can get into deep trouble if a single letter won't fit in the MaxWidth
     if TextMetrics.tmMaxCharWidth > MaxWidth then
     begin
       Len := Length(TextToSplit);
       SetLength(Buffer, Len + 2);
       if Len > 0 then
       begin
         Head := @TextToSplit[1];
         CopyMemory(Buffer, Head, Len*2);
         Result := 1;
       end;
       Buffer[Len] := #0;
       Buffer[Len + 1] := #0;
     end else
     begin
       FillChar(Size, SizeOf(Size), #0);
       // Arbitrary size that should be ok in most instances.  This will be enough space
       // for 127 lines
       SetLength(Buffer, Length(TextToSplit) + 128);
       BufferHead := @Buffer[0];
       Head := PWideChar(TextToSplit);
       Tail := Head;
       SplitCount := 0;
       while (Tail^ <> #0) and ((SplitCount < MaxSplits) or (MaxSplits = -1)) do
       begin
         LineWidth := 0;
         LastBreakChar := nil;
         while (Tail^ <> WideNull) and (LineWidth <= MaxWidth) and (Tail^ <> WideCR) and (Tail^ <> WideLF) do
         begin
    //       if IsUnicode then
             GetTextExtentPoint32W(DC, PWideChar(Tail), 1, Size);
   //        else begin
   //          TailA := Tail^;
   //          GetTextExtentPoint32(DC, @TailA[1], 1, Size);
   //        end;
           Inc(LineWidth, Size.cx);

           Inc(Tail);

           if (LineWidth <= MaxWidth) and (Tail^ = WideSpace) then
             LastBreakChar := Tail;
         end;

         if (LineWidth > MaxWidth) then
         begin
           // We overran the MaxWidth if entering this block

           // Over ran the line unless it exactly fits
           if Assigned(LastBreakChar) and (LineWidth > MaxWidth) then
             // We have word break to go back to
             Tail := LastBreakChar
           else
             // Special case, the Tail is the end of the Text to split
             if {(Tail^ <> WideNull) and} ((SplitCount + 1 < MaxSplits) or (MaxSplits < 0)) then
               Dec(Tail);
         end;
         Inc(SplitCount);
         // If we reach the MaxSplits make the last line be the rest of the text.
         if (SplitCount > 0) and (SplitCount = MaxSplits) then
           Inc(Tail, lstrlenW(Tail));
         CopyMemory(BufferHead, Head, (Tail-Head)*2);
         Inc(BufferHead, Tail-Head);
         BufferHead^ := WideNull;
         Inc(BufferHead);
         Inc(Result);

         while (Tail^ <> #0) and ((Tail^ = WideCR) or (Tail^ = WideLF) or (Tail^ = WideSpace)) do
           Inc(Tail);
         Head := Tail
       end
     end
   end
end;

{$IFNDEF COMPILER_6_UP}
function VarToWideStrDef(const V: Variant; const ADefault: WideString): WideString;
begin
  if not VarIsNull(V) then
    Result := V
  else
    Result := ADefault;
end;

function VarToWideStr(const V: Variant): WideString;
begin
  Result := VarToWideStrDef(V, NullAsStringValue);
end;

function BoolToStr(B: Boolean; UseBoolStrs: Boolean = False): string;
const
  cSimpleBoolStrs: array [boolean] of String = ('0', '-1');
begin
  if UseBoolStrs then
  begin
    if B then
      Result := 'True'
    else
      Result := 'False'
  end
  else
    Result := cSimpleBoolStrs[B];
end;

function CompareTime(const A, B: TDateTime): TValueRelationship;
begin
  if Abs(Frac(A) - Frac(B)) < (1 / MSecsPerDay) then
    Result := 0
  else if Frac(A) < Frac(B) then
    Result := Low(TValueRelationship)
  else
    Result := High(TValueRelationship);
end;

function WideCompareText(S1, S2: WideString): Integer;
begin
  Result := WideStrComp( PWideChar(S1), PWideChar(S2))
end;

function ExcludeTrailingBackslash(Path: WideString): WideString;
begin
  Result := Path;
  if Result <> '' then
  begin
    Result := Trim(Result);
    if Length(Result) > 0 then
      if Result[Length(Result)] = '\' then
        SetLength(Result, Length(Result) - 1)
  end
end;

function IncludeTrailingBackslash(Path: WideString): WideString;
begin
  Result := Path;
  if Path <> '' then
    Result := ExcludeTrailingBackslash(Path) + '\'
end;

{$ENDIF}

function VariantToCaption(const V: Variant): WideString;
var
  DateTime: TDateTime;
begin
  case VarType(V) of
    varString, varOleStr:
      Result := VarToWideStr(V);

    varInteger,{$IFDEF COMPILER_6_UP}varWord, varShortInt, varLongWord,{$ENDIF} varSmallint,  varByte:
      Result := IntToStr(V);
    varDate:
      begin
        DateTime := V;
        if Trunc(DateTime) = DateTime then // no time portion
          Result := DateToStr(DateTime)
        else
          Result := DateTimeToStr(DateTime);
      end;
    varCurrency:
      Result := CurrToStr(V);
    varSingle, varDouble:
      Result := FloatToStrF(V, ffFixed, 15, 2);
    varBoolean:
      Result := BoolToStr(V, True);
  else
    Result := VarToWideStrDef(V, '');
  end;
end;

function StrCopyW(Dest, Source: PWideChar): PWideChar;
// copies Source to Dest and returns Dest
asm
       PUSH    EDI
       PUSH    ESI
       MOV     ESI, EAX
       MOV     EDI, EDX
       MOV     ECX, 0FFFFFFFFH
       XOR     AX, AX
       REPNE   SCASW
       NOT     ECX
       MOV     EDI, ESI
       MOV     ESI, EDX
       MOV     EDX, ECX
       MOV     EAX, EDI
       SHR     ECX, 1
       REP     MOVSD
       MOV     ECX, EDX
       AND     ECX, 1
       REP     MOVSW
       POP     ESI
       POP     EDI
end;

//----------------------------------------------------------------------------------------------------------------------

function BrightenColor(const RGB: TCommonRGB; Amount: Double): TCommonRGB;
var
  HLS: TCommonHLS;

begin
  HLS := RGBToHLS(RGB);
  HLS.L := (1 + Amount) * HLS.L;
  Result := HLSToRGB(HLS);
end;

function DarkenColor(const RGB: TCommonRGB; Amount: Double): TCommonRGB;
var
  HLS: TCommonHLS;

begin
  HLS := RGBToHLS(RGB);
  // Darken means to decrease luminance.
  HLS.L := (1 - Amount) * HLS.L;
  Result := HLSToRGB(HLS);
end;

function RGBToHLS(const RGB: TCommonRGB): TCommonHLS;

// Converts from RGB to HLS.
// Input parameters and result values are all in the range 0..1.
// Note: Hue is normalized so 360° corresponds to 1.

var
  Delta,
  Max,
  Min:  Double;

begin
  with RGB, Result do
  begin
    Max := MaxValue([R, G, B]);
    Min := MinValue([R, G, B]);

    L := (Max + Min) / 2;

    if Max = Min then
    begin
      // Achromatic case.
      S := 0;
      H := 0; // Undefined
    end
    else
    begin
      Delta := Max - Min;

      if L < 0.5 then
        S := Delta / (Max + Min)
      else
        S := Delta / (2 - (Max + Min));

      if R = Max then
        H := (G - B) / Delta
      else
        if G = Max then
          H := 2 + (B - R) / Delta
        else
          if B = Max then
            H := 4 + (R - G) / Delta;

      H := H / 6;
      if H < 0 then
        H := H + 1;  
    end
  end;
end;

function HLSToRGB(const HLS: TCommonHLS): TCommonRGB;

// Converts from HLS (hue, luminance, saturation) to RGB.
// Input parameters and result values are all in the range 0..1.
// Note: Hue is normalized so 360° corresponds to 1.

  //--------------- local function --------------------------------------------

  function HueToRGB(m1, m2, Hue: Double): Double;

  begin
    if Hue > 1 then
      Hue := Hue - 1
    else
      if Hue < 0 then
        Hue := Hue + 1;

    if 6 * Hue < 1 then
      Result := m1 + (m2 - m1) * Hue * 6
    else
      if 2 * Hue < 1 then
        Result := m2
      else
        if 3 * Hue < 2 then                    
          Result := m1 + (m2 - m1) * (2 / 3 - Hue) * 6
        else
          Result := m1;
  end;

  //--------------- end local function ----------------------------------------

var
  m1, m2: Double;

begin
  with HLS, Result do
  begin
    if S = 0 then
    begin
      // Achromatic case (no hue).
      R := L;
      G := L;
      B := L;
    end
    else
    begin
      if L <= 0.5 then
        m2 := L * (S + 1)
      else
        m2 := L + S - L * S;
      m1 := 2 * L - m2;

      R := HueToRGB(m1, m2, H + 1 / 3);
      G := HueToRGB(m1, m2, H);
      B := HueToRGB(m1, m2, H - 1 / 3)
    end;
  end;
end;

function MakeTRBG(Color: TColor): TCommonRGB;
var
  RGB: Longint;
begin
  RGB := ColorToRGB(Color);
  Result.B := GetBValue(RGB) / 255;
  Result.G := GetGValue(RGB) / 255;
  Result.R := GetRValue(RGB) / 255;
end;

function MakeTColor(RGB: TCommonRGB): TColor;
begin
  Result := TColor(MakeColorRef(RGB));
end;

function MakeColorRef(RGB: TCommonRGB; Gamma: Double = 1): COLORREF;
// Converts a floating point RGB color to an 8 bit color reference as used by Windows.
// The function takes care not to produce out-of-gamut colors and allows to apply an optional gamma correction
// (inverse gamma).
begin
  GammaCorrection(RGB, Gamma);
  with RGB do
    Result := Windows.RGB(Round(R * 255), Round(G * 255), Round(B * 255));
end;

procedure GammaCorrection(var RGB: TCommonRGB; Gamma: Double);

// Computes the gamma corrected RGB color and ensures the result is in-gamut.

begin
  if Gamma <> 1 then
  begin
    Gamma := 1 / Gamma;
    with RGB do
    begin
      if R > 0 then
        R := Power(R, Gamma)
      else
        R := 0;
      if G > 0 then
        G := Power(G, Gamma)
      else
        G := 0;
      if B > 0 then
        B := Power(B, Gamma)
      else
        B := 0;
    end;
  end;

  MakeSafeColor(RGB);
end;

function MakeSafeColor(var RGB: TCommonRGB): Boolean;
// Ensures the given RGB color is in-gamut, that is, no component is < 0 or > 1.
// Returns True if the color had to be adjusted to be in-gamut, otherwise False.
begin
  Result := False;

  if RGB.R < 0 then
  begin
    Result := True;
    RGB.R := 0;
  end;
  if RGB.R > 1 then
  begin
    Result := True;
    RGB.R := 1;
  end;

  if RGB.G < 0 then
  begin
    Result := True;
    RGB.G := 0;
  end;
  if RGB.G > 1 then
  begin
    Result := True;
    RGB.G := 1;
  end;

  if RGB.B < 0 then
  begin
    Result := True;
    RGB.B := 0;
  end;
  if RGB.B > 1 then
  begin
    Result := True;
    RGB.B := 1;
  end;
end;

  function UpsideDownDIB(Bits: TBitmap): Boolean;
var
  OldRGB, TempRGB: LongInt;
  P: PLongInt;
begin
  Result := False;   
  Assert(Bits.PixelFormat = pf32Bit, 'UpsideDownDIB only works with 32 bit bitmaps');
  if Bits.PixelFormat = pf32Bit then
  begin
    P := Bits.ScanLine[0];
    OldRGB := P^;
    Result := True;
    // if Equal then we can can't be sure if upsidedown
    if (P^ and $00FFFFFF) = (ColorToRGB(Bits.Canvas.Pixels[0, 0]) and $00FFFFFF) then
    begin
      // Flip the pixel bits
      TempRGB := not OldRGB and $00FFFFFF;
      Bits.Canvas.Pixels[0, 0] := TempRGB;

      Result := (P^ and $00FFFFFF) <> (ColorToRGB(Bits.Canvas.Pixels[0, 0]) and $00FFFFFF);
      P^ := OldRGB
    end
  end
end;

procedure ActivateTopLevelWindow(Child: HWND);
var
  Parent: HWND;
  Style: LongWord;
begin
  Parent := GetParent(Child);
  while Parent <> 0 do
  begin
    Style := GetWindowLong(Parent, GWL_STYLE);
    if ((Style and WS_POPUP <> 0)) or (Style and WS_CHILD = 0) then
    begin
      BringWindowToTop(Parent);
      Exit
    end;
    Parent := GetParent(Parent);
  end
end;

initialization
  PIDLMgr := TCommonPIDLManager.Create;

  // We can be sure these are already loaded.  This keeps us from having to
  // reference count when VSTools is being used in an OCX
  Shell32Handle := GetModuleHandle(Shell32);   
  Kernel32Handle := GetModuleHandle(Kernel32);
  User32Handle := GetModuleHandle(User32);
  GDI32Handle := GetModuleHandle(GDI32);
  AdvAPI32Handle := GetModuleHandle(AdvAPI32);
  ShlwapiHandle := CommonLoadLibrary(Shlwapi);

  GetDiskFreeSpaceExA_MP := GetProcAddress(Kernel32Handle, 'GetDiskFreeSpaceA');
  TrackMouseEvent_MP := GetProcAddress(User32Handle, 'TrackMouseEvent');

  if Win32Platform = VER_PLATFORM_WIN32_NT then
  begin
    GetDriveTypeW_MP := GetProcAddress(Kernel32Handle, 'GetDriveTypeW');
    DrawTextW_MP := GetProcAddress(User32Handle, 'DrawTextW');
    SHGetFileInfoW_MP := GetProcAddress(Shell32Handle, 'SHGetFileInfoW');
    CreateFileW_MP := GetProcAddress(Kernel32Handle, 'CreateFileW');
    SHGetDataFromIDListW_MP := GetProcAddress(Shell32Handle, 'SHGetDataFromIDListW');
    FindFirstFileW_MP := GetProcAddress(Kernel32Handle, 'FindFirstFileW');
    FindNextFileW_MP := GetProcAddress(Kernel32Handle, 'FindNextFileW');
    lstrcmpiW_MP := GetProcAddress(Kernel32Handle, 'lstrcmpiW');
    lstrcmpW_MP := GetProcAddress(Kernel32Handle, 'lstrcmpW');
    lstrcpynW_MP := GetProcAddress(Kernel32Handle, 'lstrcpynW');
    lstrcpyW_MP := GetProcAddress(Kernel32Handle, 'lstrcpyW');
    CharLowerBuffW_MP := GetProcAddress(User32Handle, 'CharLowerBuffW');
    CharUpperBuffW_MP := GetProcAddress(User32Handle, 'CharUpperBuffW');
    CreateDirectoryW_MP := GetProcAddress(Kernel32Handle, 'CreateDirectoryW');
    GetFullPathNameW_MP := GetProcAddress(Kernel32Handle, 'GetFullPathNameW');
    ShellExecuteExW_MP := GetProcAddress(Shell32Handle, 'ShellExecuteExW');
    ShellExecuteW_MP := GetProcAddress(Shell32Handle, 'ShellExecuteW');
    FindFirstChangeNotificationW_MP := GetProcAddress(Kernel32Handle, 'FindFirstChangeNotificationW');
    GetCharABCWidthsW_MP := GetProcAddress(GDI32Handle, 'GetCharABCWidthsW');
    GetFileAttributesW_MP := GetProcAddress(Kernel32Handle, 'GetFileAttributesW');
    GetSystemDirectoryW_MP := GetProcAddress(Kernel32Handle, 'GetSystemDirectoryW');
    GetWindowsDirectoryW_MP := GetProcAddress(Kernel32Handle, 'GetWindowsDirectoryW');
    GetDiskFreeSpaceExW_MP := GetProcAddress(Kernel32Handle, 'GetDiskFreeSpaceExW');
    SetWindowTextW_MP := GetProcAddress(User32Handle, 'SetWindowTextW');
    GetNumberFormatW_MP := GetProcAddress(Kernel32Handle, 'GetNumberFormatW');
    RegOpenKeyW_MP := GetProcAddress(AdvAPI32Handle, 'RegOpenKeyW');
    RegOpenKeyExW_MP := GetProcAddress(AdvAPI32Handle, 'RegOpenKeyExW');
    RegQueryValueW_MP := GetProcAddress(AdvAPI32Handle, 'RegQueryValueW');
    WritePrivateProfileStringW_MP := GetProcAddress(Kernel32Handle, 'WritePrivateProfileStringW');
    GetPrivateProfileStringW_MP := GetProcAddress(Kernel32Handle, 'GetPrivateProfileStringW');
    TryEnterCriticalSection_MP := GetProcAddress(Kernel32Handle, 'TryEnterCriticalSection');
    InsertMenuItemW_MP := GetProcAddress(User32Handle, 'InsertMenuItemW');
    SendMessageW_MP := GetProcAddress(User32Handle, 'SendMessageW');
    SetFileAttributesW_MP := GetProcAddress(Kernel32Handle, 'SetFileAttributesW');
    CreateFontIndirectW_MP := GetProcAddress(GDI32Handle, 'CreateFontIndirectW');
    SystemParametersInfoW_MP := GetProcAddress(User32Handle, 'SystemParametersInfoW');
    SHGetPathFromIDListW_MP := GetProcAddress(Shell32Handle, 'SHGetPathFromIDListW');
    SHFileOperationW_MP := GetProcAddress(Shell32Handle, 'SHFileOperationW');
    SHBrowseForFolderW_MP := GetProcAddress(Shell32Handle, 'SHBrowseForFolderW');
    GetDiskFreeSpaceW_MP := GetProcAddress(Kernel32Handle, 'GetDiskFreeSpaceW');
    GetCurrentDirectoryW_MP := GetProcAddress(Kernel32Handle, 'GetCurrentDirW');
    GetTempPathW_MP := GetProcAddress(Kernel32Handle, 'GetTempPathW');
    if ShlwapiHandle <> 0 then
    begin
      PathMatchSpecA_MP := GetProcAddress(ShlwapiHandle, 'PathMatchSpecA');
      PathMatchSpecW_MP := GetProcAddress(ShlwapiHandle, 'PathMatchSpecW');
    end;
    CreateProcessW_MP := GetProcAddress(Kernel32Handle, 'CreateProcessW');
    SHDoDragDrop_MP := GetProcAddress(Shell32Handle, 'SHDoDragDrop');
    SHGetKnownFolderPath_MP := GetProcAddress(Shell32Handle, 'SHGetKnownFolderPath');
    ExpandEnvironmentStringsW_MP := GetProcAddress(Kernel32Handle, 'ExpandEnvironmentStringsW');

    // NTFS Volume (Junction) only functions
    DeleteVolumeMountPoint_MP := GetProcAddress(Kernel32Handle, 'DeleteVolumeMountPointA');
    GetVolumeNameForVolumeMountPoint_MP := GetProcAddress(Kernel32Handle, 'GetVolumeNameForVolumeMountPointA');
    GetVolumePathName_MP := GetProcAddress(Kernel32Handle, 'GetVolumePathNameA');
    SetVolumeMountPoint_MP := GetProcAddress(Kernel32Handle, 'SetVolumeMountPointA');
    FindFirstVolume_MP := GetProcAddress(Kernel32Handle, 'FindFirstVolumeA');
    FindNextVolume_MP := GetProcAddress(Kernel32Handle, 'FindNextVolumeA');
    FindVolumeClose_MP := GetProcAddress(Kernel32Handle, 'FindVolumeClose');
    FindFirstVolumeMountPoint_MP := GetProcAddress(Kernel32Handle, 'FindFirstVolumeMountPointA');
    FindNextVolumeMountPoint_MP := GetProcAddress(Kernel32Handle, 'FindNextVolumeMountPointA');
    FindVolumeMountPointClose_MP := GetProcAddress(Kernel32Handle, 'FindVolumeMountPointClose');
    FindFirstFileExW_MP := GetProcAddress(Kernel32Handle, 'FindFirstFileExW');
  end;
  FindFirstFileExA_MP := GetProcAddress(Kernel32Handle, 'FindFirstFileExA');

  // SHMultiFileProperties only supported on Win2k and WinXP
  // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/functions/shmultifileproperties.asp
  SHMultiFileProperties_MP := GetProcAddress(Shell32Handle, PChar(716));
  CDefFolderMenu_Create2_MP := GetProcAddress(Shell32Handle, PChar(701));
  CDefFolderMenu_Create_MP := GetProcAddress(Shell32Handle, PChar(700));



// If this unit is to be weak packages this must be removed
finalization
  FreeAndNil(PIDLMgr);
  CommonUnloadAllLibraries;

end.

