unit TB2Common; {$MODE Delphi} { Toolbar2000 Copyright (C) 1998-2008 by Jordan Russell All rights reserved. The contents of this file are subject to the "Toolbar2000 License"; you may not use or distribute this file except in compliance with the "Toolbar2000 License". A copy of the "Toolbar2000 License" may be found in TB2k-LICENSE.txt or at: http://www.jrsoftware.org/files/tb2k/TB2k-LICENSE.txt Alternatively, the contents of this file may be used under the terms of the GNU General Public License (the "GPL"), in which case the provisions of the GPL are applicable instead of those in the "Toolbar2000 License". A copy of the GPL may be found in GPL-LICENSE.txt or at: http://www.jrsoftware.org/files/tb2k/GPL-LICENSE.txt If you wish to allow use of your version of this file only under the terms of the GPL and not to allow others to use your version of this file under the "Toolbar2000 License", indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under either the "Toolbar2000 License" or the GPL. $jrsoftware: tb2k/Source/TB2Common.pas,v 1.48 2008/09/17 19:46:30 jr Exp $ } interface {$I TB2Ver.inc} uses LCLIntf, LCLType, LMessages, Classes, SysUtils, Messages, Controls, Forms; type THandleWMPrintNCPaintProc = procedure(Wnd: HWND; DC: HDC; AppData: TObject); TPaintHandlerProc = procedure(var Message: TWMPaint) of object; { The type of item a TList holds; it differs between Win32 and .NET VCL } TListItemType = {$IFNDEF CLR} Pointer {$ELSE} TObject {$ENDIF}; {$IFNDEF CLR} ClipToLongint = Longint; {$ENDIF} function AddToFrontOfList(var List: TList; Item: TObject): Boolean; function AddToList(var List: TList; Item: TObject): Boolean; function ApplicationIsActive: Boolean; function AreFlatMenusEnabled: Boolean; function AreKeyboardCuesEnabled: Boolean; procedure CallLockSetForegroundWindow(const ALock: Boolean); function CallTrackMouseEvent(const Wnd: HWND; const Flags: DWORD): Boolean; function CharToLower(const C: Char): Char; {$IFDEF CLR} function ClipToLongint(const I: Int64): Longint; inline; {$ENDIF} function CreateHalftoneBrush: HBRUSH; function CreateMonoBitmap(const AWidth, AHeight: Integer; const ABits: array of Byte): HBITMAP; function CreateRotatedFont(DC: HDC): HFONT; procedure DoubleBufferedRepaint(const Wnd: HWND); procedure DrawHalftoneInvertRect(const DC: HDC; const NewRect, OldRect: TRect; const NewSize, OldSize: TSize); procedure DrawRotatedText(const DC: HDC; AText: String; const ARect: TRect; const AFormat: Cardinal); procedure DrawSmallWindowCaption(const Wnd: HWND; const DC: HDC; const ARect: TRect; const AText: String; const AActive: Boolean); function DrawTextStr(const DC: HDC; const AText: String; var ARect: TRect; const AFormat: UINT): Integer; function EscapeAmpersands(const S: String): String; procedure FillRectWithGradient(const DC: HDC; const R: TRect; const StartColor, EndColor: TColorRef; const HorizontalDirection: Boolean); function FindAccelChar(const S: String): Char; {$IFNDEF JR_D5} procedure FreeAndNil(var Obj); {$ENDIF} function GetInputLocaleCodePage: UINT; function GetMenuShowDelay: Integer; function GetMessagePosAsPoint: TPoint; function GetRectOfMonitorContainingPoint(const P: TPoint; const WorkArea: Boolean): TRect; function GetRectOfMonitorContainingRect(const R: TRect; const WorkArea: Boolean): TRect; function GetRectOfMonitorContainingWindow(const W: HWND; const WorkArea: Boolean): TRect; function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect; function GetSystemNonClientMetrics(var Metrics: TNonClientMetrics): Boolean; function GetSystemParametersInfoBool(const Param: UINT; const Default: BOOL): BOOL; function GetTextExtentPoint32Str(const DC: HDC; const AText: String; out ASize: TSize): BOOL; function GetTextHeight(const DC: HDC): Integer; function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer; procedure HandleWMPrint(const Wnd: HWND; var Message: TMessage; const NCPaintFunc: THandleWMPrintNCPaintProc; const AppData: TObject); procedure HandleWMPrintClient(const PaintHandlerProc: TPaintHandlerProc; const Message: {$IFNDEF CLR} TMessage {$ELSE} TWMPrintClient {$ENDIF}); function IsWindowsXP: Boolean; procedure InitTrackMouseEvent; {$IFNDEF JR_D6} function InvalidPoint(const At: TPoint): Boolean; {$ENDIF} function IsFillRectWithGradientAvailable: Boolean; function Max(A, B: Integer): Integer; function Min(A, B: Integer): Integer; {$IFNDEF CLR} function MethodsEqual(const M1, M2: TMethod): Boolean; {$ENDIF} function NeedToPlaySound(const Alias: String): Boolean; procedure PlaySystemSound(const Alias: String); procedure ProcessPaintMessages; {$IFNDEF JR_D6} procedure RaiseLastOSError; {$ENDIF} procedure RemoveMessages(const AMin, AMax: Integer); procedure RemoveFromList(var List: TList; Item: TObject); procedure SelectNCUpdateRgn(Wnd: HWND; DC: HDC; Rgn: HRGN); function StripAccelChars(const S: String): String; function StripTrailingPunctuation(const S: String): String; function TextOutStr(const DC: HDC; const X, Y: Integer; const AText: String): BOOL; function UsingMultipleMonitors: Boolean; const PopupMenuWindowNCSize = 3; DT_HIDEPREFIX = $00100000; implementation uses {$IFDEF CLR} Types, System.Security, System.Runtime.InteropServices, System.Text, MultiMon, {$ENDIF} MMSYSTEM, TB2Version; function ApplicationIsActive: Boolean; { Returns True if the application is in the foreground } begin Result := GetActiveWindow <> 0; end; type {$IFNDEF CLR} PPrintEnumProcData = ^TPrintEnumProcData; TPrintEnumProcData = record {$ELSE} TPrintEnumProcData = class private {$ENDIF} PrintChildren: Boolean; ParentWnd: HWND; DC: HDC; PrintFlags: LPARAM; {$IFDEF CLR} function PrintEnumProc(Wnd: HWND; LParam: LPARAM): BOOL; {$ENDIF} end; {$IFNDEF CLR} function PrintEnumProc(Wnd: HWND; LParam: LPARAM): BOOL; stdcall; {$ELSE} function TPrintEnumProcData.PrintEnumProc(Wnd: HWND; LParam: LPARAM): BOOL; {$ENDIF} var R: TRect; SaveIndex: Integer; begin Result := True; { continue enumerating } {$IFNDEF CLR} with PPrintEnumProcData(LParam)^ do {$ENDIF} begin { Skip window if it isn't a child/owned window of ParentWnd or isn't visible } if (HWND(GetWindowLong(Wnd, GWL_HWNDPARENT)) <> ParentWnd) or (GetWindowLong(Wnd, GWL_STYLE) and WS_VISIBLE = 0) then { ^ don't use IsWindowVisible since it returns False if the window's parent window is not visible } Exit; GetWindowRect(Wnd, R); MapWindowPoints(0, ParentWnd, R, 2); SaveIndex := SaveDC(DC); { Like Windows, offset the window origin to the top-left coordinates of the child/owned window } MoveWindowOrg(DC, R.Left, R.Top); { Like Windows, intersect the clipping region with the entire rectangle of the child/owned window } OffsetRect(R, -R.Left, -R.Top); IntersectClipRect(DC, R.Left, R.Top, R.Right, R.Bottom); { Send a WM_PRINT message to the child/owned window } SendMessage(Wnd, WM_PRINT, WPARAM(DC), PrintFlags); { Restore the DC's state, in case the WM_PRINT handler didn't put things back the way it found them } RestoreDC(DC, SaveIndex); end; end; procedure HandleWMPrint(const Wnd: HWND; var Message: TMessage; const NCPaintFunc: THandleWMPrintNCPaintProc; const AppData: TObject); { note: AppData is an application-defined value which is passed to NCPaintFunc } var DC: HDC; SaveIndex, SaveIndex2: Integer; R: TRect; P: TPoint; Data: TPrintEnumProcData; begin if (Message.LParam and PRF_CHECKVISIBLE = 0) or IsWindowVisible(Wnd) then begin DC := HDC(Message.WParam); SaveIndex2 := SaveDC(DC); try if Message.LParam and PRF_NONCLIENT <> 0 then begin SaveIndex := SaveDC(DC); if Assigned(NCPaintFunc) then NCPaintFunc(Wnd, DC, AppData); RestoreDC(DC, SaveIndex); end; { Calculate the difference between the top-left corner of the window and the top-left corner of its client area } GetWindowRect(Wnd, R); P.X := 0; P.Y := 0; ClientToScreen(Wnd, P); Dec(P.X, R.Left); Dec(P.Y, R.Top); if Message.LParam and PRF_CLIENT <> 0 then begin { Like Windows, the flags PRF_ERASEBKGND, PRF_CHILDREN, and PRF_OWNED are ignored if PRF_CLIENT isn't also specified } if Message.LParam and PRF_ERASEBKGND <> 0 then begin { Send WM_ERASEBKGND } SaveIndex := SaveDC(DC); if Message.LParam and PRF_NONCLIENT <> 0 then MoveWindowOrg(DC, P.X, P.Y); SendMessage(Wnd, WM_ERASEBKGND, Message.WParam, 0); RestoreDC(DC, SaveIndex); end; { Send WM_PRINTCLIENT } SaveIndex := SaveDC(DC); if Message.LParam and PRF_NONCLIENT <> 0 then MoveWindowOrg(DC, P.X, P.Y); SendMessage(Wnd, WM_PRINTCLIENT, Message.WParam, 0); RestoreDC(DC, SaveIndex); { Like Windows, always offset child/owned windows by the size of the client area even if PRF_NONCLIENT isn't specified (a bug?) } MoveWindowOrg(DC, P.X, P.Y); {$IFDEF CLR} Data := TPrintEnumProcData.Create; {$ENDIF} Data.ParentWnd := Wnd; Data.DC := DC; { Send WM_PRINT to child/owned windows } if Message.LParam and PRF_CHILDREN <> 0 then begin Data.PrintChildren := True; Data.PrintFlags := (Message.LParam or (PRF_NONCLIENT or PRF_CLIENT or PRF_ERASEBKGND or PRF_CHILDREN)) and not PRF_CHECKVISIBLE; {$IFNDEF CLR} EnumChildWindows(Wnd, @PrintEnumProc, LPARAM(@Data)); {$ELSE} EnumChildWindows(Wnd, Data.PrintEnumProc, 0); {$ENDIF} end; if Message.LParam and PRF_OWNED <> 0 then begin Data.PrintChildren := False; Data.PrintFlags := Message.LParam; {$IFNDEF CLR} EnumWindows(@PrintEnumProc, LPARAM(@Data)); {$ELSE} EnumWindows(Data.PrintEnumProc, 0); {$ENDIF} end; end; finally RestoreDC(DC, SaveIndex2); end; Message.Result := 1; end else begin { Like Windows, return 0 when the PRF_CHECKVISIBLE flag is specified and the window is not visible } Message.Result := 0; end; end; procedure HandleWMPrintClient(const PaintHandlerProc: TPaintHandlerProc; const Message: {$IFNDEF CLR} TMessage {$ELSE} TWMPrintClient {$ENDIF}); var DC: HDC; Msg: TWMPaint; SaveIndex: Integer; begin {$IFNDEF CLR} DC := HDC(Message.WParam); {$ELSE} DC := Message.DC; Msg := TWMPaint.Create; {$ENDIF} Msg.Msg := WM_PAINT; Msg.DC := DC; {$IFNDEF CLR} Msg.Unused := 0; {$ENDIF} Msg.Result := 0; SaveIndex := SaveDC(DC); try PaintHandlerProc(Msg); finally RestoreDC(DC, SaveIndex); end; end; function GetTextHeight(const DC: HDC): Integer; var TextMetric: TTextMetric; begin GetTextMetrics(DC, TextMetric); Result := TextMetric.tmHeight; end; function StripAccelChars(const S: String): String; var I: Integer; begin Result := S; I := 1; while I <= Length(Result) do begin {$IFNDEF JR_WIDESTR} if not(Result[I] in LeadBytes) then begin {$ENDIF} if Result[I] = '&' then Delete(Result, I, 1); Inc(I); {$IFNDEF JR_WIDESTR} end else Inc(I, 2); {$ENDIF} end; end; function EscapeAmpersands(const S: String): String; { Replaces any '&' characters with '&&' } var I: Integer; begin Result := S; I := 1; while I <= Length(Result) do begin {$IFNDEF JR_WIDESTR} if not(Result[I] in LeadBytes) then begin {$ENDIF} if Result[I] = '&' then begin Inc(I); Insert('&', Result, I); end; Inc(I); {$IFNDEF JR_WIDESTR} end else Inc(I, 2); {$ENDIF} end; end; function StripTrailingPunctuation(const S: String): String; { Removes any colon (':') or ellipsis ('...') from the end of S and returns the resulting string } var L: Integer; begin Result := S; L := Length(Result); if (L > 1) and (Result[L] = ':') {$IFNDEF JR_WIDESTR} and (ByteType(Result, L) = mbSingleByte) {$ENDIF} then SetLength(Result, L-1) else if (L > 3) and (Result[L-2] = '.') and (Result[L-1] = '.') and (Result[L] = '.') {$IFNDEF JR_WIDESTR} and (ByteType(Result, L-2) = mbSingleByte) {$ENDIF} then SetLength(Result, L-3); end; function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer; { Returns the width of the specified string using the font currently selected into DC. If Prefix is True, it first removes "&" characters as necessary. } var Size: TSize; begin { This procedure is 10x faster than using DrawText with the DT_CALCRECT flag } if Prefix then S := StripAccelChars(S); GetTextExtentPoint32Str(DC, S, Size); Result := Size.cx; end; procedure ProcessPaintMessages; { Dispatches all pending WM_PAINT messages. In effect, this is like an 'UpdateWindow' on all visible windows } var Msg: TMsg; begin while PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_NOREMOVE) do begin case Integer(GetMessage(Msg, 0, WM_PAINT, WM_PAINT)) of -1: Break; { if GetMessage failed } 0: begin { Repost WM_QUIT messages } PostQuitMessage(ClipToLongint(Msg.wParam)); Break; end; end; DispatchMessage(Msg); end; end; procedure RemoveMessages(const AMin, AMax: Integer); { Removes any messages with the specified ID from the queue } var Msg: TMsg; begin while PeekMessage(Msg, 0, AMin, AMax, PM_REMOVE) do begin if Msg.message = WM_QUIT then begin { Repost WM_QUIT messages } PostQuitMessage(ClipToLongint(Msg.wParam)); Break; end; end; end; procedure SelectNCUpdateRgn(Wnd: HWND; DC: HDC; Rgn: HRGN); var R: TRect; NewClipRgn: HRGN; begin if (Rgn <> 0) and (Rgn <> 1) then begin GetWindowRect(Wnd, R); if SelectClipRgn(DC, Rgn) = ERROR then begin NewClipRgn := CreateRectRgnIndirect(R); SelectClipRgn(DC, NewClipRgn); DeleteObject(NewClipRgn); end; OffsetClipRgn(DC, -R.Left, -R.Top); end; end; function AddToList(var List: TList; Item: TObject): Boolean; { Returns True if Item didn't already exist in the list } begin if List = nil then List := TList.Create; Result := List.IndexOf(Item) = -1; if Result then List.Add(Item); end; function AddToFrontOfList(var List: TList; Item: TObject): Boolean; { Returns True if Item didn't already exist in the list } begin if List = nil then List := TList.Create; Result := List.IndexOf(Item) = -1; if Result then List.Insert(0, Item); end; procedure RemoveFromList(var List: TList; Item: TObject); begin if Assigned(List) then begin List.Remove(Item); if List.Count = 0 then begin List.Free; List := nil; end; end; end; const DefaultMenuShowDelay = 400; {$IFNDEF CLR} var RegMenuShowDelay: Integer; RegMenuShowDelayInited: BOOL = False; function GetMenuShowDelay: Integer; function ReadMenuShowDelayFromRegistry: Integer; var K: HKEY; Typ, DataSize: DWORD; Data: array[0..31] of Char; Res: Longint; E: Integer; begin Result := DefaultMenuShowDelay; if RegOpenKeyEx(HKEY_CURRENT_USER, 'Control Panel\Desktop', 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin DataSize := SizeOf(Data); Res := RegQueryValueEx(K, 'MenuShowDelay', nil, @Typ, @Data, @DataSize); RegCloseKey(K); if Res <> ERROR_FILE_NOT_FOUND then begin if (Res <> ERROR_SUCCESS) or (Typ <> REG_SZ) then Result := 0 else begin Val(Data, Result, E); if E <> 0 then Result := 0; end; end; end; end; begin if Lo(GetVersion) >= 4 then begin if not SystemParametersInfo(106{SPI_GETMENUSHOWDELAY}, 0, @Result, 0) then begin { SPI_GETMENUSHOWDELAY is only supported by Windows NT 4.0 and Windows 98. On Windows 95, it must use the registry to retrieve this setting. } if not RegMenuShowDelayInited then begin RegMenuShowDelay := ReadMenuShowDelayFromRegistry; InterlockedExchange(Integer(RegMenuShowDelayInited), Ord(True)); end; Result := RegMenuShowDelay; end; if Result < 0 then Result := 0; end else Result := DefaultMenuShowDelay; end; {$ELSE} function GetMenuShowDelay: Integer; begin { Since .NET requires Windows 98 or later, we can assume that SPI_GETMENUSHOWDELAY is available } if not SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, Result, 0) then Result := DefaultMenuShowDelay; end; {$ENDIF} function AreFlatMenusEnabled: Boolean; { Returns True if "flat menus" are enabled. Always returns False on pre-XP Windows versions. } const SPI_GETFLATMENU = $1022; begin { Interestingly, on Windows 2000, SystemParametersInfo(SPI_GETFLATMENU, ...) succeeds and can return True in pvParam^ if the proper bit is set in UserPreferencesMask. Since flat menus are not really used on Windows 2000, call IsWindowsXP first to see if we're running at least XP. } Result := IsWindowsXP and GetSystemParametersInfoBool(SPI_GETFLATMENU, False); end; function AreKeyboardCuesEnabled: Boolean; { Returns True if "keyboard cues" are enabled. Always returns True on pre-2000 Windows versions. } const SPI_GETKEYBOARDCUES = $100A; begin Result := (Win32MajorVersion < 5) or GetSystemParametersInfoBool(SPI_GETKEYBOARDCUES, True); end; function CreateFrameRectRgn(const ARect: TRect; const ASize: TSize): HRGN; var R: TRect; InsideRgn: HRGN; begin if IsRectEmpty(ARect) then begin { The rectangle is empty, so simply return a normalized empty region } SetRectEmpty(R); Result := CreateRectRgnIndirect(R); end else begin Result := CreateRectRgnIndirect(ARect); if Result <> 0 then begin { Now hollow out the resulting region so that only a frame is left } R := ARect; InflateRect(R, -ASize.cx, -ASize.cy); { If ASize is greater than the size of ARect, then InflateRect will return a non-normalized rectangle larger than ARect. Test for this condition by calling IsRectEmpty. } if not IsRectEmpty(R) then begin InsideRgn := CreateRectRgnIndirect(R); if InsideRgn <> 0 then begin CombineRgn(Result, Result, InsideRgn, RGN_XOR); DeleteObject(InsideRgn); end; end; end; end; end; procedure DrawInvertRect(const DC: HDC; const NewRect, OldRect: TRect; const NewSize, OldSize: TSize; const Brush: HBRUSH); { Draws a dragging outline, hiding the old one if necessary. NewRect and/or OldRect may be empty. } var SaveIndex: Integer; UpdateRgn, OldRgn: HRGN; R: TRect; begin { Create region containing the new rectangle } UpdateRgn := CreateFrameRectRgn(NewRect, NewSize); if UpdateRgn <> 0 then begin { Combine that region with a region containing the old rectangle } OldRgn := CreateFrameRectRgn(OldRect, OldSize); if OldRgn <> 0 then begin CombineRgn(UpdateRgn, OldRgn, UpdateRgn, RGN_XOR); DeleteObject(OldRgn); end; { Save the DC state so that the clipping region can be restored } SaveIndex := SaveDC(DC); try { Draw the updated region } SelectClipRgn(DC, UpdateRgn); GetClipBox(DC, R); SelectObject(DC, Brush); PatBlt(DC, R.Left, R.Top, R.Right-R.Left, R.Bottom-R.Top, PATINVERT); finally RestoreDC(DC, SaveIndex); end; DeleteObject(UpdateRgn); end; end; function CreateMonoBitmap(const AWidth, AHeight: Integer; const ABits: array of Byte): HBITMAP; begin {$IFNDEF CLR} Result := CreateBitmap(AWidth, AHeight, 1, 1, @ABits[0]); {$ELSE} { For some reason there isn't an overloaded version of CreateBitmap that takes a TBytes parameter, so we have to use two calls } Result := CreateBitmap(AWidth, AHeight, 1, 1, nil); SetBitmapBits(Result, Length(ABits), ABits); {$ENDIF} end; function CreateHalftoneBrush: HBRUSH; const GrayPattern: array[0..15] of Byte = ( $55, 0, $AA, 0, $55, 0, $AA, 0, $55, 0, $AA, 0, $55, 0, $AA, 0); var GrayBitmap: HBITMAP; begin GrayBitmap := CreateMonoBitmap(8, 8, GrayPattern); Result := CreatePatternBrush(GrayBitmap); DeleteObject(GrayBitmap); end; procedure DrawHalftoneInvertRect(const DC: HDC; const NewRect, OldRect: TRect; const NewSize, OldSize: TSize); var Brush: HBRUSH; begin Brush := CreateHalftoneBrush; try DrawInvertRect(DC, NewRect, OldRect, NewSize, OldSize, Brush); finally DeleteObject(Brush); end; end; var GradientFillAvailable: Boolean; {$IFNDEF CLR} type { Note: TTriVertex is unusable on Delphi 7 and earlier (COLOR16 is misdeclared as a Shortint instead of a Word). } TNewTriVertex = record x: Longint; y: Longint; Red: Word; Green: Word; Blue: Word; Alpha: Word; end; var GradientFillFunc: function(DC: HDC; var Vertex: TNewTriVertex; NumVertex: ULONG; Mesh: Pointer; NumMesh, Mode: ULONG): BOOL; stdcall; {$ENDIF} procedure InitGradientFillFunc; {$IFNDEF CLR} var M: HMODULE; {$ENDIF} begin if (Win32MajorVersion >= 5) or ((Win32MajorVersion = 4) and (Win32MinorVersion >= 10)) then begin {$IFNDEF CLR} M := {$IFDEF JR_D5} SafeLoadLibrary {$ELSE} LoadLibrary {$ENDIF} ('msimg32.dll'); if M <> 0 then begin GradientFillFunc := GetProcAddress(M, 'GradientFill'); if Assigned(GradientFillFunc) then GradientFillAvailable := True; end; {$ELSE} GradientFillAvailable := True; {$ENDIF} end; end; function IsFillRectWithGradientAvailable: Boolean; begin Result := GradientFillAvailable; end; procedure FillRectWithGradient(const DC: HDC; const R: TRect; const StartColor, EndColor: TColorRef; const HorizontalDirection: Boolean); var Vertexes: array[0..1] of {$IFNDEF CLR} TNewTriVertex {$ELSE} TTriVertex {$ENDIF}; GradientRect: TGradientRect; Mode: ULONG; begin if not GradientFillAvailable then Exit; Vertexes[0].x := R.Left; Vertexes[0].y := R.Top; Vertexes[0].Red := GetRValue(StartColor) shl 8; Vertexes[0].Blue := GetBValue(StartColor) shl 8; Vertexes[0].Green := GetGValue(StartColor) shl 8; Vertexes[0].Alpha := 0; Vertexes[1].x := R.Right; Vertexes[1].y := R.Bottom; Vertexes[1].Red := GetRValue(EndColor) shl 8; Vertexes[1].Blue := GetBValue(EndColor) shl 8; Vertexes[1].Green := GetGValue(EndColor) shl 8; Vertexes[1].Alpha := 0; GradientRect.UpperLeft := 0; GradientRect.LowerRight := 1; if HorizontalDirection then Mode := GRADIENT_FILL_RECT_H else Mode := GRADIENT_FILL_RECT_V; {$IFNDEF CLR} GradientFillFunc(DC, Vertexes[0], 2, @GradientRect, 1, Mode); {$ELSE} GradientFill(DC, Vertexes, 2, GradientRect, 1, Mode); {$ENDIF} end; procedure DrawSmallWindowCaption(const Wnd: HWND; const DC: HDC; const ARect: TRect; const AText: String; const AActive: Boolean); { Draws a (non-themed) small window caption bar. On Windows Vista, a custom routine is used to work around an ugly bug in DrawCaption that causes the text to be painted at the wrong coordinates. Note: The value of the AText parameter may be ignored depending on which routine is chosen. } procedure FillBackground; const CaptionBkColors: array[Boolean, Boolean] of Integer = ((COLOR_INACTIVECAPTION, COLOR_ACTIVECAPTION), (COLOR_GRADIENTINACTIVECAPTION, COLOR_GRADIENTACTIVECAPTION)); var LeftColor, RightColor: TColorRef; begin if GetSystemParametersInfoBool(SPI_GETGRADIENTCAPTIONS, False) and IsFillRectWithGradientAvailable then begin LeftColor := GetSysColor(CaptionBkColors[False, AActive]); RightColor := GetSysColor(CaptionBkColors[True, AActive]); if LeftColor <> RightColor then begin FillRectWithGradient(DC, ARect, LeftColor, RightColor, True); Exit; end; end; FillRect(DC, ARect, GetSysColorBrush(CaptionBkColors[False, AActive])); end; const CaptionTextColors: array[Boolean] of Integer = (COLOR_INACTIVECAPTIONTEXT, COLOR_CAPTIONTEXT); var Flags: UINT; TextRect: TRect; NonClientMetrics: TNonClientMetrics; CaptionFont, SaveFont: HFONT; SaveBkMode: Integer; SaveTextColor: TColorRef; begin if ARect.Right <= ARect.Left then Exit; { Prior to Windows Vista, continue to use DrawCaption. Don't want to risk introducing new bugs on old OSes, plus on Windows 98, it's several times faster than our custom routine. } if Win32MajorVersion < 6 then begin Flags := DC_TEXT or DC_SMALLCAP; if AActive then Flags := Flags or DC_ACTIVE; if GetSystemParametersInfoBool(SPI_GETGRADIENTCAPTIONS, False) then Flags := Flags or DC_GRADIENT; DrawCaption(Wnd, DC, ARect, Flags); end else begin FillBackground; TextRect := ARect; Inc(TextRect.Left, GetSystemMetrics(SM_CXEDGE)); if (TextRect.Right > TextRect.Left) and GetSystemNonClientMetrics(NonClientMetrics) then begin CaptionFont := CreateFontIndirect(NonClientMetrics.lfSmCaptionFont); if CaptionFont <> 0 then begin SaveFont := SelectObject(DC, CaptionFont); SaveBkMode := SetBkMode(DC, TRANSPARENT); SaveTextColor := SetTextColor(DC, GetSysColor(CaptionTextColors[AActive])); try DrawTextStr(DC, AText, TextRect, DT_SINGLELINE or DT_NOPREFIX or DT_VCENTER or DT_END_ELLIPSIS); finally SetTextColor(DC, SaveTextColor); SetBkMode(DC, SaveBkMode); SelectObject(DC, SaveFont); DeleteObject(CaptionFont); end; end; end; end; end; procedure DoubleBufferedRepaint(const Wnd: HWND); var ClientRect, ClipRect, R: TRect; WndDC, BmpDC: HDC; Bmp: HBITMAP; SaveIndex: Integer; begin if IsWindowVisible(Wnd) and GetClientRect(Wnd, ClientRect) and not IsRectEmpty(ClientRect) then begin ValidateRect(Wnd, nil); BmpDC := 0; Bmp := 0; WndDC := GetDC(Wnd); if WndDC <> 0 then begin try { Only repaint the area that intersects the clipping rectangle } if (GetClipBox(WndDC, ClipRect) <> Windows.ERROR) and IntersectRect(R, ClientRect, ClipRect) then begin Bmp := CreateCompatibleBitmap(WndDC, R.Right - R.Left, R.Bottom - R.Top); if Bmp <> 0 then begin BmpDC := CreateCompatibleDC(WndDC); if BmpDC <> 0 then begin SelectObject(BmpDC, Bmp); SaveIndex := SaveDC(BmpDC); SetWindowOrgEx(BmpDC, R.Left, R.Top, nil); SendMessage(Wnd, WM_ERASEBKGND, WPARAM(BmpDC), 0); SendMessage(Wnd, WM_PAINT, WPARAM(BmpDC), 0); RestoreDC(BmpDC, SaveIndex); BitBlt(WndDC, R.Left, R.Top, R.Right - R.Left, R.Bottom - R.Top, BmpDC, 0, 0, SRCCOPY); Exit; end; end; end; finally if BmpDC <> 0 then DeleteDC(BmpDC); if Bmp <> 0 then DeleteObject(Bmp); ReleaseDC(Wnd, WndDC); end; end; end; { Fall back to invalidating if we didn't or couldn't double-buffer } InvalidateRect(Wnd, nil, True); end; {$IFNDEF CLR} function MethodsEqual(const M1, M2: TMethod): Boolean; begin Result := (M1.Code = M2.Code) and (M1.Data = M2.Data); end; {$ENDIF} function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect; begin if not WorkArea or not SystemParametersInfo(SPI_GETWORKAREA, 0, {$IFNDEF CLR}@{$ENDIF} Result, 0) then Result := Rect(0, 0, Screen.Width, Screen.Height); end; {$IFNDEF CLR} { On Delphi for Win32, we don't use the MultiMon unit because its stubs for MonitorFromRect and MonitorFromPoint are seriously bugged on Delphi 4. } type HMONITOR = type THandle; TMonitorInfo = record cbSize: DWORD; rcMonitor: TRect; rcWork: TRect; dwFlags: DWORD; end; const MONITOR_DEFAULTTONEAREST = $2; var MultiMonApis: record MonitorFromRect: function(const lprcScreenCoords: TRect; dwFlags: DWORD): HMONITOR; stdcall; MonitorFromPoint: function(ptScreenCoords: TPoint; dwFlags: DWORD): HMONITOR; stdcall; MonitorFromWindow: function(hWnd: HWND; dwFlags: DWORD): HMONITOR; stdcall; GetMonitorInfo: function(hMonitor: HMONITOR; var lpMonitorInfo: TMonitorInfo): BOOL; stdcall; end; MultiMonApisAvailable: Boolean; procedure InitMultiMonApis; var User32Handle: THandle; begin User32Handle := GetModuleHandle(user32); MultiMonApis.MonitorFromRect := GetProcAddress(User32Handle, 'MonitorFromRect'); MultiMonApis.MonitorFromPoint := GetProcAddress(User32Handle, 'MonitorFromPoint'); MultiMonApis.MonitorFromWindow := GetProcAddress(User32Handle, 'MonitorFromWindow'); MultiMonApis.GetMonitorInfo := GetProcAddress(User32Handle, 'GetMonitorInfoA'); MultiMonApisAvailable := Assigned(MultiMonApis.MonitorFromRect) and Assigned(MultiMonApis.MonitorFromPoint) and Assigned(MultiMonApis.MonitorFromWindow) and Assigned(MultiMonApis.GetMonitorInfo); end; {$ENDIF} function UsingMultipleMonitors: Boolean; { Returns True if the system has more than one display monitor configured. } const SM_CMONITORS = 80; begin { Note: On a single monitor Windows 95 or NT 4 system, GetSystemMetrics will return 0 since those OSes do not support multiple monitors. On later versions it returns 1. } Result := {$IFNDEF CLR} MultiMonApisAvailable and {$ENDIF} (GetSystemMetrics(SM_CMONITORS) > 1); end; function GetRectOfMonitor(const M: HMONITOR; const WorkArea: Boolean; var R: TRect): Boolean; var MonitorInfo: TMonitorInfo; begin {$IFNDEF CLR} MonitorInfo.cbSize := SizeOf(MonitorInfo); {$ELSE} MonitorInfo.cbSize := Marshal.SizeOf(TypeOf(TMonitorInfo)); {$ENDIF} Result := {$IFNDEF CLR}MultiMonApis.{$ENDIF} GetMonitorInfo(M, MonitorInfo); if Result then begin if not WorkArea then R := MonitorInfo.rcMonitor else R := MonitorInfo.rcWork; end; end; function GetRectOfMonitorContainingRect(const R: TRect; const WorkArea: Boolean): TRect; { Returns the work area of the monitor which the rectangle R intersects with the most, or the monitor nearest R if no monitors intersect. } var M: HMONITOR; begin if UsingMultipleMonitors then begin M := {$IFNDEF CLR}MultiMonApis.{$ENDIF} MonitorFromRect(R, MONITOR_DEFAULTTONEAREST); if GetRectOfMonitor(M, WorkArea, Result) then Exit; end; Result := GetRectOfPrimaryMonitor(WorkArea); end; function GetRectOfMonitorContainingPoint(const P: TPoint; const WorkArea: Boolean): TRect; { Returns the screen area of the monitor containing the point P, or the monitor nearest P if P isn't in any monitor's work area. } var M: HMONITOR; begin if UsingMultipleMonitors then begin M := {$IFNDEF CLR}MultiMonApis.{$ENDIF} MonitorFromPoint(P, MONITOR_DEFAULTTONEAREST); if GetRectOfMonitor(M, WorkArea, Result) then Exit; end; Result := GetRectOfPrimaryMonitor(WorkArea); end; function GetRectOfMonitorContainingWindow(const W: HWND; const WorkArea: Boolean): TRect; var M: HMONITOR; begin if UsingMultipleMonitors then begin M := {$IFNDEF CLR}MultiMonApis.{$ENDIF} MonitorFromWindow(W, MONITOR_DEFAULTTONEAREST); if GetRectOfMonitor(M, WorkArea, Result) then Exit; end; Result := GetRectOfPrimaryMonitor(WorkArea); end; {$IFNDEF CLR} var TrackMouseEventInited: BOOL; TrackMouseEventFunc: function(var EventTrack: TTrackMouseEvent): BOOL; stdcall; procedure InitTrackMouseEvent; var TrackMouseEventComCtlModule: THandle; begin { First look for TrackMouseEvent which is available on Windows 98 & NT 4 only. If it doesn't exist, look for _TrackMouseEvent which is available on Windows 95 if IE 3.0 or later is installed. } if not TrackMouseEventInited then begin TrackMouseEventFunc := GetProcAddress(GetModuleHandle(user32), 'TrackMouseEvent'); if @TrackMouseEventFunc = nil then begin TrackMouseEventComCtlModule := {$IFDEF JR_D5} SafeLoadLibrary {$ELSE} LoadLibrary {$ENDIF} (comctl32); if TrackMouseEventComCtlModule <> 0 then TrackMouseEventFunc := GetProcAddress(TrackMouseEventComCtlModule, '_TrackMouseEvent'); end; InterlockedExchange(Integer(TrackMouseEventInited), Ord(True)); end; end; {$ELSE} procedure InitTrackMouseEvent; begin end; {$ENDIF} function CallTrackMouseEvent(const Wnd: HWND; const Flags: DWORD): Boolean; var Track: TTrackMouseEvent; begin {$IFNDEF CLR} Result := False; if Assigned(TrackMouseEventFunc) then begin Track.cbSize := SizeOf(Track); Track.dwFlags := Flags; Track.hwndTrack := Wnd; Track.dwHoverTime := 0; Result := TrackMouseEventFunc(Track); end; {$ELSE} { .NET doesn't run on 95, so we can assume TrackMouseEvent is available } Track.cbSize := Marshal.SizeOf(TypeOf(TTrackMouseEvent)); Track.dwFlags := Flags; Track.hwndTrack := Wnd; Track.dwHoverTime := 0; Result := TrackMouseEvent(Track); {$ENDIF} end; {$IFNDEF CLR} var LockSetForegroundWindowFunc: function(uLockCode: UINT): BOOL; stdcall; {$ELSE} [SuppressUnmanagedCodeSecurity, DllImport(user32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'LockSetForegroundWindow')] function LockSetForegroundWindowFunc(uLockCode: UINT): BOOL; external; {$ENDIF} procedure CallLockSetForegroundWindow(const ALock: Boolean); const LSFW_LOCK = 1; LSFW_UNLOCK = 2; begin {$IFNDEF CLR} if Assigned(LockSetForegroundWindowFunc) then begin {$ELSE} if (Win32MajorVersion >= 5) or ((Win32MajorVersion = 4) and (Win32MinorVersion >= 90)) then begin {$ENDIF} if ALock then LockSetForegroundWindowFunc(LSFW_LOCK) else LockSetForegroundWindowFunc(LSFW_UNLOCK); end; end; {$IFNDEF JR_D5} procedure FreeAndNil(var Obj); var P: TObject; begin P := TObject(Obj); TObject(Obj) := nil; P.Free; end; {$ENDIF} {$IFNDEF JR_D6} procedure RaiseLastOSError; begin RaiseLastWin32Error; end; {$ENDIF} {$IFDEF CLR} { On .NET, when calling DrawText, GetTextExtentPoint32, or TextOut we can't rely on the marshaller's automatic A/W function selection because they take a character count. If we passed the result of Length(), as the VCL incorrectly does in many places, the behavior would be incorrect on DBCS Windows 9x/Me systems because when a Unicode string is downconverted to ANSI the character count can increase (i.e. one Unicode character can become two ANSI characters). Below we define our own "A" function prototypes that take byte array parameters, allowing us to pass the result of AnsiEncoding.GetBytes straight to the functions without any conversion. (Borland's "A" prototypes use "string" type parameters.) } [SuppressUnmanagedCodeSecurity, DllImport(user32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'DrawTextA')] function _DrawTextA(hDC: HDC; [in] lpString: TBytes; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; external; [SuppressUnmanagedCodeSecurity, DllImport(gdi32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'GetTextExtentPoint32A')] function _GetTextExtentPoint32A(DC: HDC; [in] Str: TBytes; Count: Integer; out Size: TSize): BOOL; external; [SuppressUnmanagedCodeSecurity, DllImport(gdi32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'TextOutA')] function _TextOutA(DC: HDC; X, Y: Integer; [in] Str: TBytes; Count: Integer): BOOL; external; {$ENDIF} function DrawTextStr(const DC: HDC; const AText: String; var ARect: TRect; const AFormat: UINT): Integer; {$IFNDEF CLR} begin Result := DrawText(DC, PChar(AText), Length(AText), ARect, AFormat); end; {$ELSE} var AnsiStr: TBytes; begin if Marshal.SystemDefaultCharSize = 1 then begin AnsiStr := AnsiEncoding.GetBytes(AText); Result := _DrawTextA(DC, AnsiStr, Length(AnsiStr), ARect, AFormat); end else Result := DrawTextW(DC, AText, Length(AText), ARect, AFormat); end; {$ENDIF} function GetTextExtentPoint32Str(const DC: HDC; const AText: String; out ASize: TSize): BOOL; {$IFNDEF CLR} begin Result := GetTextExtentPoint32(DC, PChar(AText), Length(AText), ASize); end; {$ELSE} var AnsiStr: TBytes; begin if Marshal.SystemDefaultCharSize = 1 then begin AnsiStr := AnsiEncoding.GetBytes(AText); Result := _GetTextExtentPoint32A(DC, AnsiStr, Length(AnsiStr), ASize); end else Result := GetTextExtentPoint32W(DC, AText, Length(AText), ASize); end; {$ENDIF} function TextOutStr(const DC: HDC; const X, Y: Integer; const AText: String): BOOL; {$IFNDEF CLR} begin Result := TextOut(DC, X, Y, PChar(AText), Length(AText)); end; {$ELSE} var AnsiStr: TBytes; begin if Marshal.SystemDefaultCharSize = 1 then begin AnsiStr := AnsiEncoding.GetBytes(AText); Result := _TextOutA(DC, X, Y, AnsiStr, Length(AnsiStr)); end else Result := TextOutW(DC, X, Y, AText, Length(AText)); end; {$ENDIF} threadvar FontExistsResult: Boolean; {$IFNDEF CLR} function FontExistsCallback(const lplf: TLogFont; const lptm: TTextMetric; dwType: DWORD; lpData: LPARAM): Integer; stdcall; {$ELSE} function FontExistsCallback([in] var lplf: TLogFont; [in] var lptm: TTextMetric; dwType: DWORD; lpData: LPARAM): Integer; {$ENDIF} begin FontExistsResult := True; Result := 0; end; function FontExists(const DC: HDC; const FaceName: String): Boolean; begin FontExistsResult := False; EnumFonts(DC, {$IFNDEF CLR}PChar{$ENDIF}(FaceName), @FontExistsCallback, {$IFNDEF CLR} nil {$ELSE} 0 {$ENDIF}); Result := FontExistsResult; end; function CreateRotatedFont(DC: HDC): HFONT; { Creates a font based on the DC's current font, but rotated 270 degrees } var LogFont: TLogFont; TM: TTextMetric; VerticalFontName: String; begin if GetObject(GetCurrentObject(DC, OBJ_FONT), {$IFNDEF CLR} SizeOf(LogFont), @LogFont {$ELSE} Marshal.SizeOf(TypeOf(TLogFont)), LogFont {$ENDIF} ) = 0 then begin { just in case... } Result := 0; Exit; end; LogFont.lfEscapement := 2700; LogFont.lfOrientation := 2700; LogFont.lfOutPrecision := OUT_TT_ONLY_PRECIS; { needed for Win9x } { Don't let a random TrueType font be substituted when MS Sans Serif or Microsoft Sans Serif are used. On Windows 2000 and later, hard-code Tahoma because Arial can't display Japanese or Thai Unicode characters (on Windows 2000 at least). On earlier versions, hard-code Arial since NT 4.0 doesn't ship with Tahoma, and 9x doesn't do Unicode. } {$IFNDEF CLR} if (StrIComp(LogFont.lfFaceName, 'MS Sans Serif') = 0) or (StrIComp(LogFont.lfFaceName, 'Microsoft Sans Serif') = 0) then begin if Win32MajorVersion >= 5 then StrPCopy(LogFont.lfFaceName, 'Tahoma') else StrPCopy(LogFont.lfFaceName, 'Arial'); {$ELSE} if SameText(LogFont.lfFaceName, 'MS Sans Serif', loInvariantLocale) or SameText(LogFont.lfFaceName, 'Microsoft Sans Serif', loInvariantLocale) then begin if Win32MajorVersion >= 5 then LogFont.lfFaceName := 'Tahoma' else LogFont.lfFaceName := 'Arial'; {$ENDIF} { Set lfHeight to the actual height of the current font. This is needed to work around a Windows 98 issue: on a clean install of the OS, SPI_GETNONCLIENTMETRICS returns -5 for lfSmCaptionFont.lfHeight. This is wrong; it should return -11 for an 8 pt font. With normal, unrotated text this actually displays correctly, since MS Sans Serif doesn't support sizes below 8 pt. However, when we change to a TrueType font like Arial, this becomes a problem because it'll actually create a font that small. } if GetTextMetrics(DC, TM) then begin { If the original height was negative, keep it negative } if LogFont.lfHeight <= 0 then LogFont.lfHeight := -(TM.tmHeight - TM.tmInternalLeading) else LogFont.lfHeight := TM.tmHeight; end; end; { Use a vertical font if available so that Asian characters aren't drawn sideways } VerticalFontName := String('@') + LogFont.lfFaceName; if FontExists(DC, VerticalFontName) then begin {$IFNDEF CLR} StrPLCopy(LogFont.lfFaceName, VerticalFontName, (SizeOf(LogFont.lfFaceName) div SizeOf(LogFont.lfFaceName[0])) - 1); {$ELSE} LogFont.lfFaceName := VerticalFontName; {$ENDIF} end; Result := CreateFontIndirect(LogFont); end; procedure DrawRotatedText(const DC: HDC; AText: String; const ARect: TRect; const AFormat: Cardinal); { Like DrawText, but draws the text at a 270 degree angle. The only format flag this function respects is DT_HIDEPREFIX. Text is always drawn centered. } var RotatedFont, SaveFont: HFONT; TextMetrics: TTextMetric; X, Y, P, I, SU, FU: Integer; SaveAlign: UINT; SavePen, Pen: HPEN; begin RotatedFont := CreateRotatedFont(DC); SaveFont := SelectObject(DC, RotatedFont); GetTextMetrics(DC, TextMetrics); X := ARect.Left + ((ARect.Right - ARect.Left) - TextMetrics.tmHeight) div 2; Y := ARect.Top + ((ARect.Bottom - ARect.Top) - GetTextWidth(DC, AText, True)) div 2; { Find the index of the character that should be underlined. Delete '&' characters from the string. Like DrawText, only the last prefixed character will be underlined. } P := 0; I := 1; while I <= Length(AText) do begin {$IFNDEF JR_WIDESTR} if AText[I] in LeadBytes then Inc(I) else {$ENDIF} if AText[I] = '&' then begin Delete(AText, I, 1); { If the '&' was the last character, don't underline anything } if I > Length(AText) then P := 0 else if AText[I] <> '&' then P := I; end; Inc(I); end; SaveAlign := SetTextAlign(DC, TA_BOTTOM); TextOutStr(DC, X, Y, AText); SetTextAlign(DC, SaveAlign); { Underline } if (P > 0) and (AFormat and DT_HIDEPREFIX = 0) then begin SU := GetTextWidth(DC, Copy(AText, 1, P-1), False); FU := SU + GetTextWidth(DC, AText[P], False); Inc(X, TextMetrics.tmDescent - 2); Pen := CreatePen(PS_SOLID, 1, GetTextColor(DC)); SavePen := SelectObject(DC, Pen); MoveToEx(DC, X, Y + SU, nil); LineTo(DC, X, Y + FU); SelectObject(DC, SavePen); DeleteObject(Pen); end; SelectObject(DC, SaveFont); DeleteObject(RotatedFont); end; function NeedToPlaySound(const Alias: String): Boolean; { This function checks the registry to see if the specified sound event alias is assigned to a file. The purpose of having this function is so it can avoid calls to PlaySound if possible, because on Windows 2000 there is an annoying 1/3 second delay on the first call to PlaySound. Windows Explorer actually uses this same technique when playing sounds for the Start menu. } var KeyName: String; K: HKEY; {$IFNDEF CLR} Data: array[0..3] of WideChar; {$ELSE} Data: TBytes; DataType: DWORD; {$ENDIF} DataSize: DWORD; ErrorCode: Longint; begin if (Win32MajorVersion < 5) or (Win32Platform <> VER_PLATFORM_WIN32_NT) then begin { No need to check pre-Windows 2000 versions since their PlaySound functions don't have the delay; always return True. } Result := True; Exit; end; Result := False; KeyName := 'AppEvents\Schemes\Apps\.Default\' + Alias + '\.Current'; if RegOpenKeyEx(HKEY_CURRENT_USER, {$IFNDEF CLR}PChar{$ENDIF}(KeyName), 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin try {$IFNDEF CLR} DataSize := SizeOf(Data); { Note: Use the 'W' version of RegQueryValueEx for more speed } ErrorCode := RegQueryValueExW(K, nil, nil, nil, @Data, @DataSize); if ((ErrorCode = ERROR_SUCCESS) and (Data[0] <> #0)) or (ErrorCode = ERROR_MORE_DATA) then Result := True; {$ELSE} DataSize := 4 * SizeOf(WideChar); SetLength(Data, DataSize); ErrorCode := RegQueryValueExW(K, nil, nil, DataType, Data, DataSize); if ((ErrorCode = ERROR_SUCCESS) and (Data[0] or Data[1] <> 0)) or (ErrorCode = ERROR_MORE_DATA) then Result := True; {$ENDIF} finally RegCloseKey(K); end; end; end; procedure PlaySystemSound(const Alias: String); const SND_SYSTEM = $00200000; var Flags: DWORD; begin Flags := SND_ALIAS or SND_ASYNC or SND_NODEFAULT; if Win32Platform <> VER_PLATFORM_WIN32_NT then Flags := Flags or SND_NOSTOP; { On 9x, native menus' sounds are NOSTOP } if Win32MajorVersion >= 6 then Flags := Flags or SND_SYSTEM; PlaySound({$IFNDEF CLR}PChar{$ENDIF}(Alias), 0, Flags); end; function Max(A, B: Integer): Integer; begin if A >= B then Result := A else Result := B; end; function Min(A, B: Integer): Integer; begin if A <= B then Result := A else Result := B; end; function FindAccelChar(const S: String): Char; { Finds the last accelerator key in S. Returns #0 if no accelerator key was found. '&&' is ignored. } {$IFNDEF CLR} var P: PChar; begin P := PChar(S); Result := #0; while True do begin P := AnsiStrScan(P, '&'); if P = nil then Break; Inc(P); if P^ <> '&' then begin if P^ = #0 then Break; Result := P^; end; Inc(P); end; end; {$ELSE} var Len, I: Integer; begin Result := #0; Len := Length(S); if Len > 0 then begin { ensures S isn't nil } I := 1; while True do begin I := System.String(S).IndexOf('&', I - 1) + 1; if (I = 0) or (I >= Len) then Break; Inc(I); if S[I] <> '&' then Result := S[I]; Inc(I); end; end; end; {$ENDIF} function IsWindowsXP: Boolean; begin Result := (Win32Platform = VER_PLATFORM_WIN32_NT) and ((Win32MajorVersion > 5) or ((Win32MajorVersion = 5) and (Win32MinorVersion >= 1))); end; function GetInputLocaleCodePage: UINT; { Returns the code page identifier of the active input locale, or CP_ACP if for some unknown reason it couldn't be determined. } var {$IFNDEF CLR} Buf: array[0..15] of Char; {$ELSE} Buf: StringBuilder; {$ENDIF} ErrorCode: Integer; begin {$IFNDEF CLR} if GetLocaleInfo(GetKeyboardLayout(0) and $FFFF, LOCALE_IDEFAULTANSICODEPAGE, Buf, SizeOf(Buf) div SizeOf(Buf[0])) > 0 then begin Buf[High(Buf)] := #0; { ensure null termination, just in case... } Val(Buf, Result, ErrorCode); {$ELSE} Buf := StringBuilder.Create(16); if GetLocaleInfo(GetKeyboardLayout(0) and $FFFF, LOCALE_IDEFAULTANSICODEPAGE, Buf, Buf.Capacity) > 0 then begin Val(Buf.ToString, Result, ErrorCode); {$ENDIF} { Just to be *completely* safe, verify that the code page returned by GetLocaleInfo actually exists. The result of this function may be fed into WideCharToMultiByte, and we don't want WideCharToMultiByte to fail entirely because of a bad code page. } if (ErrorCode <> 0) or not IsValidCodePage(Result) then Result := CP_ACP; end else Result := CP_ACP; end; function GetMessagePosAsPoint: TPoint; var Pos: DWORD; begin Pos := GetMessagePos; Result.X := Smallint(Pos and $FFFF); Result.Y := Smallint(Pos shr 16); end; function GetSystemNonClientMetrics(var Metrics: TNonClientMetrics): Boolean; {$IFNDEF CLR} begin Metrics.cbSize := SizeOf(Metrics); Result := SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SizeOf(Metrics), @Metrics, 0); end; {$ELSE} begin {$IFDEF JR_D11} { On Delphi.NET 2007, Forms.GetNonClientMetrics is marked deprecated } Metrics.cbSize := Marshal.SizeOf(TypeOf(TNonClientMetrics)); Result := SystemParametersInfo(SPI_GETNONCLIENTMETRICS, Metrics.cbSize, Metrics, 0); {$ELSE} Result := Forms.GetNonClientMetrics(Metrics); {$ENDIF} end; {$ENDIF} function GetSystemParametersInfoBool(const Param: UINT; const Default: BOOL): BOOL; { Returns the value of the specified BOOL-type system parameter, or Default if the function fails } begin if not SystemParametersInfo(Param, 0, {$IFNDEF CLR}@{$ENDIF} Result, 0) then Result := Default; end; {$IFDEF CLR} { Use our own declaration for CharLowerBuffA that takes a byte array directly instead of StringBuilder } [SuppressUnmanagedCodeSecurity, DllImport(user32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'CharLowerBuffA')] function _CharLowerBuffA([in, out] lpsz: TBytes; cchLength: DWORD): DWORD; external; {$ENDIF} function CharToLower(const C: Char): Char; { Converts a single character to lowercase using the current code page } {$IFNDEF CLR} begin Result := Char(CharLower(Pointer(Word(C)))); end; {$ELSE} var AnsiBytes: TBytes; begin { Note: On .NET we can't use LowerCase()/String.ToLower() because it uses linguistic casing rules -- on a Turkish locale "I" is NOT mapped to "i". This would break accelerator keys when running English apps. With CharLower, "I" is always mapped to "i". } if Marshal.SystemDefaultCharSize = 1 then begin { On Windows 9x/Me we have to use CharLowerBuff since the character may be two bytes when downconverted to ANSI. And we have to handle the Unicode->ANSI conversion ourself so that we know the correct length to pass to the function. } AnsiBytes := AnsiEncoding.GetBytes(C); _CharLowerBuffA(AnsiBytes, Length(AnsiBytes)); Result := AnsiEncoding.GetChars(AnsiBytes)[0]; end else Result := Char(Word(CharLowerW(IntPtr(Word(C))))); end; {$ENDIF} {$IFNDEF JR_D6} function InvalidPoint(const At: TPoint): Boolean; begin Result := (At.X = -1) and (At.Y = -1); end; {$ENDIF} {$IFDEF CLR} function ClipToLongint(const I: Int64): Longint; inline; { On Delphi.NET 2007, casting a LPARAM (THandle) directly into a Longint can raise an overflow exception (possibly a bug?). By passing the LPARAM to this function, which acts like a Longint(Int64()) cast, the exception can be avoided. } begin Result := Longint(I); end; {$ENDIF} initialization InitGradientFillFunc; {$IFNDEF CLR} InitMultiMonApis; LockSetForegroundWindowFunc := GetProcAddress(GetModuleHandle(user32), 'LockSetForegroundWindow'); {$ENDIF} end.