Componentes.Terceros.TB2k-TBX/official/2.1.6+2.1.beta1/Source/TB2Common.pas

1088 lines
34 KiB
ObjectPascal

unit TB2Common;
{
Toolbar2000
Copyright (C) 1998-2005 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.31 2005/06/29 20:10:10 jr Exp $
}
interface
{$I TB2Ver.inc}
uses
Windows, Classes, SysUtils, Messages, Controls, Forms;
type
TListSortExCompare = function(const Item1, Item2, ExtraData: Pointer): Integer;
THandleWMPrintNCPaintProc = procedure(Wnd: HWND; DC: HDC; AppData: Longint);
function AddToFrontOfList(var List: TList; Item: Pointer): Boolean;
function AddToList(var List: TList; Item: Pointer): Boolean;
function ApplicationIsActive: Boolean;
function AreFlatMenusEnabled: Boolean;
function AreKeyboardCuesEnabled: Boolean;
function CallTrackMouseEvent(const Wnd: HWND; const Flags: DWORD): Boolean;
function CreateHalftoneBrush: HBRUSH;
function CreateNullRegion: HRGN;
function CreateRotatedFont(DC: HDC): HFONT;
function DivRoundUp(const Dividend, Divisor: Integer): Integer;
procedure DrawHalftoneInvertRect(const DC: HDC; const NewRect, OldRect: PRect;
const NewSize, OldSize: TSize);
procedure DrawRotatedText(const DC: HDC; AText: String; const ARect: TRect;
const AFormat: Cardinal);
procedure DrawInvertRect(const DC: HDC; const NewRect, OldRect: PRect;
const NewSize, OldSize: TSize; const Brush: HBRUSH; BrushLast: HBRUSH);
function EscapeAmpersands(const S: String): String;
function FindAccelChar(const S: String): Char;
{$IFNDEF JR_D5}
procedure FreeAndNil(var Obj);
{$ENDIF}
function GetInputLocaleCodePage: UINT;
function GetMenuShowDelay: Integer;
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 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: Longint);
procedure HandleWMPrintClient(const Control: TWinControl;
var Message: TMessage);
function IsWindowsXP: Boolean;
procedure ListSortEx(const List: TList; const Compare: TListSortExCompare;
const ExtraData: Pointer);
procedure InitTrackMouseEvent;
function Max(A, B: Integer): Integer;
function Min(A, B: Integer): Integer;
function MethodsEqual(const M1, M2: TMethod): Boolean;
function NeedToPlaySound(const Alias: String): Boolean;
procedure ProcessPaintMessages;
procedure RemoveMessages(const AMin, AMax: Integer);
procedure RemoveFromList(var List: TList; Item: Pointer);
procedure SelectNCUpdateRgn(Wnd: HWND; DC: HDC; Rgn: HRGN);
function StripAccelChars(const S: String): String;
function StripTrailingPunctuation(const S: String): String;
function UsingMultipleMonitors: Boolean;
const
PopupMenuWindowNCSize = 3;
DT_HIDEPREFIX = $00100000;
var
TrackMouseEventFunc: function(var EventTrack: TTrackMouseEvent): BOOL; stdcall;
implementation
uses
TB2Version;
function ApplicationIsActive: Boolean;
{ Returns True if the application is in the foreground }
begin
Result := GetActiveWindow <> 0;
end;
{$IFNDEF JR_D3}
function CopyPalette(Palette: HPALETTE): HPALETTE;
var
PaletteSize: Integer;
LogPal: TMaxLogPalette;
begin
Result := 0;
if Palette = 0 then Exit;
PaletteSize := 0;
if GetObject(Palette, SizeOf(PaletteSize), @PaletteSize) = 0 then Exit;
if PaletteSize = 0 then Exit;
with LogPal do begin
palVersion := $0300;
palNumEntries := PaletteSize;
GetPaletteEntries(Palette, 0, PaletteSize, palPalEntry);
end;
Result := CreatePalette(PLogPalette(@LogPal)^);
end;
{$ENDIF}
procedure ListSortEx(const List: TList; const Compare: TListSortExCompare;
const ExtraData: Pointer);
{ Similar to TList.Sort, but lets you pass a user-defined ExtraData pointer }
procedure QuickSortEx(L: Integer; const R: Integer);
var
I, J: Integer;
P: Pointer;
begin
repeat
I := L;
J := R;
P := List[(L + R) shr 1];
repeat
while Compare(List[I], P, ExtraData) < 0 do Inc(I);
while Compare(List[J], P, ExtraData) > 0 do Dec(J);
if I <= J then
begin
List.Exchange(I, J);
Inc(I);
Dec(J);
end;
until I > J;
if L < J then QuickSortEx(L, J);
L := I;
until I >= R;
end;
begin
if List.Count > 1 then
QuickSortEx(0, List.Count-1);
end;
type
PPrintEnumProcData = ^TPrintEnumProcData;
TPrintEnumProcData = record
PrintChildren: Boolean;
ParentWnd: HWND;
DC: HDC;
PrintFlags: LPARAM;
end;
function PrintEnumProc(Wnd: HWND; LParam: LPARAM): BOOL; stdcall;
var
R: TRect;
SaveIndex: Integer;
begin
Result := True; { continue enumerating }
with PPrintEnumProcData(LParam)^ do 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: Longint);
{ 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);
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 := PRF_NONCLIENT or PRF_CLIENT or PRF_ERASEBKGND or
PRF_CHILDREN; { same flags as Windows passes to children }
EnumChildWindows(Wnd, @PrintEnumProc, LPARAM(@Data));
end;
if Message.LParam and PRF_OWNED <> 0 then begin
Data.PrintChildren := False;
Data.PrintFlags := Message.LParam;
EnumWindows(@PrintEnumProc, LPARAM(@Data));
end;
end;
finally
RestoreDC(DC, SaveIndex2);
end;
end;
{ Windows' WM_PRINT returns 1. I'm not sure why. }
Message.Result := 1;
end;
type
TWinControlAccess = class(TWinControl);
procedure HandleWMPrintClient(const Control: TWinControl; var Message: TMessage);
var
Msg: TWMPaint;
SaveIndex: Integer;
begin
Msg.Msg := WM_PAINT;
Msg.DC := HDC(Message.WParam);
Msg.Unused := 0;
Msg.Result := 0;
SaveIndex := SaveDC(HDC(Message.WParam));
try
TWinControlAccess(Control).PaintHandler(Msg);
finally
RestoreDC(HDC(Message.WParam), SaveIndex);
end;
end;
function DivRoundUp(const Dividend, Divisor: Integer): Integer;
{ Similar to the 'div' operator, but if there is a remainder it always rounds
the result up one (or down if the result is negative). }
asm
mov ecx, edx
cdq
idiv ecx
test edx, edx
jz @@1
test eax, eax
jns @@2
dec eax
jmp @@1
@@2:
inc eax
@@1:
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
if not(Result[I] in LeadBytes) then begin
if Result[I] = '&' then
System.Delete(Result, I, 1);
Inc(I);
end
else
Inc(I, 2);
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
if not(Result[I] in LeadBytes) then begin
if Result[I] = '&' then begin
Inc(I);
Insert('&', Result, I);
end;
Inc(I);
end
else
Inc(I, 2);
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] = ':') and (ByteType(Result, L) = mbSingleByte) then
SetLength(Result, L-1)
else if (L > 3) and (Result[L-2] = '.') and (Result[L-1] = '.') and
(Result[L] = '.') and (ByteType(Result, L-2) = mbSingleByte) 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);
GetTextExtentPoint32(DC, PChar(S), Length(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(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(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: Pointer): 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: Pointer): 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: Pointer);
begin
if Assigned(List) then begin
List.Remove(Item);
if List.Count = 0 then begin
List.Free;
List := nil;
end;
end;
end;
var
RegMenuShowDelay: Integer;
RegMenuShowDelayInited: BOOL = False;
function GetMenuShowDelay: Integer;
const
DefaultMenuShowDelay = 400;
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;
function AreFlatMenusEnabled: Boolean;
{ Returns True if "flat menus" are enabled. Always returns False on pre-XP
Windows versions. }
const
SPI_GETFLATMENU = $1022;
var
FlatMenusEnabled: BOOL;
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 SystemParametersInfo(SPI_GETFLATMENU, 0,
@FlatMenusEnabled, 0) and FlatMenusEnabled;
end;
function AreKeyboardCuesEnabled: Boolean;
{ Returns True if "keyboard cues" are enabled. Always returns True on
pre-2000 Windows versions. }
const
SPI_GETKEYBOARDCUES = $100A;
var
CuesEnabled: BOOL;
begin
Result := (Win32MajorVersion < 5) or
not SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, @CuesEnabled, 0) or
CuesEnabled;
end;
function CreateNullRegion: HRGN;
var
R: TRect;
begin
SetRectEmpty(R);
Result := CreateRectRgnIndirect(R);
end;
procedure DrawInvertRect(const DC: HDC; const NewRect, OldRect: PRect;
const NewSize, OldSize: TSize; const Brush: HBRUSH; BrushLast: HBRUSH);
{ Draws a dragging outline, hiding the old one if neccessary. This code is
based on MFC sources.
Either NewRect or OldRect can be nil or empty. }
var
SaveIndex: Integer;
rgnNew, rgnOutside, rgnInside, rgnLast, rgnUpdate: HRGN;
R: TRect;
begin
rgnLast := 0;
rgnUpdate := 0;
{ First, determine the update region and select it }
if NewRect = nil then begin
SetRectEmpty(R);
rgnOutside := CreateRectRgnIndirect(R);
end
else begin
R := NewRect^;
rgnOutside := CreateRectRgnIndirect(R);
InflateRect(R, -NewSize.cx, -NewSize.cy);
IntersectRect(R, R, NewRect^);
end;
rgnInside := CreateRectRgnIndirect(R);
rgnNew := CreateNullRegion;
CombineRgn(rgnNew, rgnOutside, rgnInside, RGN_XOR);
if BrushLast = 0 then
BrushLast := Brush;
if OldRect <> nil then begin
{ Find difference between new region and old region }
rgnLast := CreateNullRegion;
with OldRect^ do
SetRectRgn(rgnOutside, Left, Top, Right, Bottom);
R := OldRect^;
InflateRect(R, -OldSize.cx, -OldSize.cy);
IntersectRect(R, R, OldRect^);
SetRectRgn(rgnInside, R.Left, R.Top, R.Right, R.Bottom);
CombineRgn(rgnLast, rgnOutside, rgnInside, RGN_XOR);
{ Only diff them if brushes are the same }
if Brush = BrushLast then begin
rgnUpdate := CreateNullRegion;
CombineRgn(rgnUpdate, rgnLast, rgnNew, RGN_XOR);
end;
end;
{ Save the DC state so that the clipping region can be restored }
SaveIndex := SaveDC(DC);
try
if (Brush <> BrushLast) and (OldRect <> nil) then begin
{ Brushes are different -- erase old region first }
SelectClipRgn(DC, rgnLast);
GetClipBox(DC, R);
SelectObject(DC, BrushLast);
PatBlt(DC, R.Left, R.Top, R.Right-R.Left, R.Bottom-R.Top, PATINVERT);
end;
{ Draw into the update/new region }
if rgnUpdate <> 0 then
SelectClipRgn(DC, rgnUpdate)
else
SelectClipRgn(DC, rgnNew);
GetClipBox(DC, R);
SelectObject(DC, Brush);
PatBlt(DC, R.Left, R.Top, R.Right-R.Left, R.Bottom-R.Top, PATINVERT);
finally
{ Clean up DC }
RestoreDC(DC, SaveIndex);
end;
{ Free regions }
if rgnNew <> 0 then DeleteObject(rgnNew);
if rgnOutside <> 0 then DeleteObject(rgnOutside);
if rgnInside <> 0 then DeleteObject(rgnInside);
if rgnLast <> 0 then DeleteObject(rgnLast);
if rgnUpdate <> 0 then DeleteObject(rgnUpdate);
end;
function CreateHalftoneBrush: HBRUSH;
const
Patterns: array[Boolean] of Word = ($5555, $AAAA);
var
I: Integer;
GrayPattern: array[0..7] of Word;
GrayBitmap: HBITMAP;
begin
for I := 0 to 7 do
GrayPattern[I] := Patterns[Odd(I)];
GrayBitmap := CreateBitmap(8, 8, 1, 1, @GrayPattern);
Result := CreatePatternBrush(GrayBitmap);
DeleteObject(GrayBitmap);
end;
procedure DrawHalftoneInvertRect(const DC: HDC; const NewRect, OldRect: PRect;
const NewSize, OldSize: TSize);
var
Brush: HBRUSH;
begin
Brush := CreateHalftoneBrush;
try
DrawInvertRect(DC, NewRect, OldRect, NewSize, OldSize, Brush, Brush);
finally
DeleteObject(Brush);
end;
end;
function MethodsEqual(const M1, M2: TMethod): Boolean;
begin
Result := (M1.Code = M2.Code) and (M1.Data = M2.Data);
end;
function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect;
begin
if not WorkArea or not SystemParametersInfo(SPI_GETWORKAREA, 0, @Result, 0) then
Result := Rect(0, 0, Screen.Width, Screen.Height);
end;
function UsingMultipleMonitors: Boolean;
{ Returns True if the system has more than one display monitor configured. }
var
NumMonitors: Integer;
begin
NumMonitors := GetSystemMetrics(80 {SM_CMONITORS});
Result := (NumMonitors <> 0) and (NumMonitors <> 1);
{ ^ NumMonitors will be zero if not running Win98, NT 5, or later }
end;
type
HMONITOR = type Integer;
PMonitorInfoA = ^TMonitorInfoA;
TMonitorInfoA = record
cbSize: DWORD;
rcMonitor: TRect;
rcWork: TRect;
dwFlags: DWORD;
end;
const
MONITOR_DEFAULTTONEAREST = $2;
type
TMultiMonApis = record
funcMonitorFromRect: function(lprcScreenCoords: PRect; dwFlags: DWORD): HMONITOR; stdcall;
funcMonitorFromPoint: function(ptScreenCoords: TPoint; dwFlags: DWORD): HMONITOR; stdcall;
funcMonitorFromWindow: function(hWnd: HWND; dwFlags: DWORD): HMONITOR; stdcall;
funcGetMonitorInfoA: function(hMonitor: HMONITOR; lpMonitorInfo: PMonitorInfoA): BOOL; stdcall;
end;
{ Under D4 I could be using the MultiMon unit for the multiple monitor
function imports, but its stubs for MonitorFromRect and MonitorFromPoint
are seriously bugged... So I chose to avoid the MultiMon unit entirely. }
function InitMultiMonApis(var Apis: TMultiMonApis): Boolean;
var
User32Handle: THandle;
begin
User32Handle := GetModuleHandle(user32);
Apis.funcMonitorFromRect := GetProcAddress(User32Handle, 'MonitorFromRect');
Apis.funcMonitorFromPoint := GetProcAddress(User32Handle, 'MonitorFromPoint');
Apis.funcMonitorFromWindow := GetProcAddress(User32Handle, 'MonitorFromWindow');
Apis.funcGetMonitorInfoA := GetProcAddress(User32Handle, 'GetMonitorInfoA');
Result := Assigned(Apis.funcMonitorFromRect) and
Assigned(Apis.funcMonitorFromPoint) and Assigned(Apis.funcGetMonitorInfoA);
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
Apis: TMultiMonApis;
M: HMONITOR;
MonitorInfo: TMonitorInfoA;
begin
if UsingMultipleMonitors and InitMultiMonApis(Apis) then begin
M := Apis.funcMonitorFromRect(@R, MONITOR_DEFAULTTONEAREST);
MonitorInfo.cbSize := SizeOf(MonitorInfo);
if Apis.funcGetMonitorInfoA(M, @MonitorInfo) then begin
if not WorkArea then
Result := MonitorInfo.rcMonitor
else
Result := MonitorInfo.rcWork;
Exit;
end;
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
Apis: TMultiMonApis;
M: HMONITOR;
MonitorInfo: TMonitorInfoA;
begin
if UsingMultipleMonitors and InitMultiMonApis(Apis) then begin
M := Apis.funcMonitorFromPoint(P, MONITOR_DEFAULTTONEAREST);
MonitorInfo.cbSize := SizeOf(MonitorInfo);
if Apis.funcGetMonitorInfoA(M, @MonitorInfo) then begin
if not WorkArea then
Result := MonitorInfo.rcMonitor
else
Result := MonitorInfo.rcWork;
Exit;
end;
end;
Result := GetRectOfPrimaryMonitor(WorkArea);
end;
function GetRectOfMonitorContainingWindow(const W: HWND;
const WorkArea: Boolean): TRect;
var
Apis: TMultiMonApis;
M: HMONITOR;
MonitorInfo: TMonitorInfoA;
begin
if UsingMultipleMonitors and InitMultiMonApis(Apis) then begin
M := Apis.funcMonitorFromWindow(W, MONITOR_DEFAULTTONEAREST);
MonitorInfo.cbSize := SizeOf(MonitorInfo);
if Apis.funcGetMonitorInfoA(M, @MonitorInfo) then begin
if not WorkArea then
Result := MonitorInfo.rcMonitor
else
Result := MonitorInfo.rcWork;
Exit;
end;
end;
Result := GetRectOfPrimaryMonitor(WorkArea);
end;
var
TrackMouseEventInited: BOOL;
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;
function CallTrackMouseEvent(const Wnd: HWND; const Flags: DWORD): Boolean;
var
Track: TTrackMouseEvent;
begin
Result := False;
if Assigned(TrackMouseEventFunc) then begin
Track.cbSize := SizeOf(Track);
Track.dwFlags := Flags;
Track.hwndTrack := Wnd;
Track.dwHoverTime := 0;
Result := TrackMouseEventFunc(Track);
end;
end;
{$IFNDEF JR_D5}
procedure FreeAndNil(var Obj);
var
P: TObject;
begin
P := TObject(Obj);
TObject(Obj) := nil;
P.Free;
end;
{$ENDIF}
function EnumFontsProc(const lplf: TLogFont; const lptm: TTextMetric;
dwType: DWORD; lpData: LPARAM): Integer; stdcall;
begin
Boolean(Pointer(lpData)^) := True;
Result := 0;
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: array[0..LF_FACESIZE-1] of Char;
VerticalFontExists: Boolean;
begin
if GetObject(GetCurrentObject(DC, OBJ_FONT), SizeOf(LogFont),
@LogFont) = 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. Hard-code Arial. }
if (StrIComp(LogFont.lfFaceName, 'MS Sans Serif') = 0) or
(StrIComp(LogFont.lfFaceName, 'Microsoft Sans Serif') = 0) then begin
StrPCopy(LogFont.lfFaceName, 'Arial');
{ 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 }
if StrLen(LogFont.lfFaceName) < SizeOf(VerticalFontName)-1 then begin
VerticalFontName[0] := '@';
StrCopy(@VerticalFontName[1], LogFont.lfFaceName);
VerticalFontExists := False;
EnumFonts(DC, VerticalFontName, @EnumFontsProc, @VerticalFontExists);
if VerticalFontExists then
StrCopy(LogFont.lfFaceName, VerticalFontName);
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 format flag this function respects are
DT_NOPREFIX, DT_HIDEPREFIX, DT_CENTER, DT_END_ELLIPSIS, DT_NOCLIP }
var
RotatedFont, SaveFont: HFONT;
TextMetrics: TTextMetric;
X, Y, P, I, SU, FU, W: Integer;
SaveAlign: UINT;
SavePen, Pen: HPEN;
Clip: Boolean;
function GetSize(DC: HDC; const S: string): Integer;
var
Size: TSize;
begin
GetTextExtentPoint32(DC, PChar(S), Length(S), Size);
Result := Size.cx;
end;
begin
if Length(AText) = 0 then Exit;
RotatedFont := CreateRotatedFont(DC);
SaveFont := SelectObject(DC, RotatedFont);
GetTextMetrics(DC, TextMetrics);
X := ARect.Left + ((ARect.Right - ARect.Left) - TextMetrics.tmHeight) div 2;
Clip := (AFormat and DT_NOCLIP) <> DT_NOCLIP;
{ 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;
if (AFormat and DT_NOPREFIX) <> DT_NOPREFIX then
while I <= Length(AText) do begin
if AText[I] in LeadBytes then
Inc(I)
else if AText[I] = '&' then begin
Delete(AText, I, 1);
{ Note: PChar cast is so that if Delete deleted the last character in
the string, we don't step past the end of the string (which would cause
an AV if AText is now empty), but rather look at the null character
and treat it as an accelerator key like DrawText. }
if PChar(AText)[I-1] <> '&' then
P := I;
end;
Inc(I);
end;
if (AFormat and DT_END_ELLIPSIS) = DT_END_ELLIPSIS then
begin
if (Length(AText) > 1) and (GetSize(DC, AText) > ARect.Bottom - ARect.Top) then
begin
W := ARect.Bottom - ARect.Top;
if W > 2 then
begin
Delete(AText, Length(AText), 1);
while (Length(AText) > 1) and (GetSize(DC, AText + '...') > W) do
Delete(AText, Length(AText), 1);
end
else AText := AText[1];
if P > Length(AText) then P := 0;
AText := AText + '...';
end;
end;
if (AFormat and DT_CENTER) = DT_CENTER then
Y := ARect.Top + ((ARect.Bottom - ARect.Top) - GetSize(DC, AText)) div 2
else
Y := ARect.Top;
if Clip then
begin
SaveDC(DC);
with ARect do IntersectClipRect(DC, Left, Top, Right, Bottom);
end;
SaveAlign := SetTextAlign(DC, TA_BOTTOM);
TextOut(DC, X, Y, PChar(AText), Length(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, PChar(AText)[P-1], 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;
if Clip then RestoreDC(DC, -1);
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
K: HKEY;
Data: array[0..3] of WideChar;
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;
if RegOpenKeyEx(HKEY_CURRENT_USER,
PChar('AppEvents\Schemes\Apps\.Default\' + Alias + '\.Current'),
0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
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;
RegCloseKey(K);
end;
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. }
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;
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
Buf: array[0..15] of Char;
ErrorCode: Integer;
begin
if GetLocaleInfo(GetKeyboardLayout(0) and $FFFF, LOCALE_IDEFAULTANSICODEPAGE,
Buf, SizeOf(Buf)) > 0 then begin
Buf[High(Buf)] := #0; { ensure null termination, just in case... }
Val(Buf, Result, ErrorCode);
{ 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;
end.