TB2Common.pas 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. unit TB2Common;
  2. {
  3. Toolbar2000
  4. Copyright (C) 1998-2005 by Jordan Russell
  5. All rights reserved.
  6. The contents of this file are subject to the "Toolbar2000 License"; you may
  7. not use or distribute this file except in compliance with the
  8. "Toolbar2000 License". A copy of the "Toolbar2000 License" may be found in
  9. TB2k-LICENSE.txt or at:
  10. http://www.jrsoftware.org/files/tb2k/TB2k-LICENSE.txt
  11. Alternatively, the contents of this file may be used under the terms of the
  12. GNU General Public License (the "GPL"), in which case the provisions of the
  13. GPL are applicable instead of those in the "Toolbar2000 License". A copy of
  14. the GPL may be found in GPL-LICENSE.txt or at:
  15. http://www.jrsoftware.org/files/tb2k/GPL-LICENSE.txt
  16. If you wish to allow use of your version of this file only under the terms of
  17. the GPL and not to allow others to use your version of this file under the
  18. "Toolbar2000 License", indicate your decision by deleting the provisions
  19. above and replace them with the notice and other provisions required by the
  20. GPL. If you do not delete the provisions above, a recipient may use your
  21. version of this file under either the "Toolbar2000 License" or the GPL.
  22. $jrsoftware: tb2k/Source/TB2Common.pas,v 1.31 2005/06/29 20:10:10 jr Exp $
  23. }
  24. interface
  25. {$I TB2Ver.inc}
  26. uses
  27. Windows, Classes, SysUtils, Messages, Controls, Forms;
  28. type
  29. TListSortExCompare = function(const Item1, Item2, ExtraData: Pointer): Integer;
  30. THandleWMPrintNCPaintProc = procedure(Wnd: HWND; DC: HDC; AppData: Longint);
  31. function AddToFrontOfList(var List: TList; Item: Pointer): Boolean;
  32. function AddToList(var List: TList; Item: Pointer): Boolean;
  33. function ApplicationIsActive: Boolean;
  34. function AreFlatMenusEnabled: Boolean;
  35. function AreKeyboardCuesEnabled: Boolean;
  36. function CallTrackMouseEvent(const Wnd: HWND; const Flags: DWORD): Boolean;
  37. function CreateHalftoneBrush: HBRUSH;
  38. function CreateNullRegion: HRGN;
  39. function CreateRotatedFont(DC: HDC): HFONT;
  40. function DivRoundUp(const Dividend, Divisor: Integer): Integer;
  41. procedure DrawHalftoneInvertRect(const DC: HDC; const NewRect, OldRect: PRect;
  42. const NewSize, OldSize: TSize);
  43. procedure DrawRotatedText(const DC: HDC; AText: String; const ARect: TRect;
  44. const AFormat: Cardinal);
  45. procedure DrawInvertRect(const DC: HDC; const NewRect, OldRect: PRect;
  46. const NewSize, OldSize: TSize; const Brush: HBRUSH; BrushLast: HBRUSH);
  47. function EscapeAmpersands(const S: String): String;
  48. function FindAccelChar(const S: String): Char;
  49. {$IFNDEF JR_D5}
  50. procedure FreeAndNil(var Obj);
  51. {$ENDIF}
  52. function GetInputLocaleCodePage: UINT;
  53. function GetMenuShowDelay: Integer;
  54. function GetRectOfMonitorContainingPoint(const P: TPoint; const WorkArea: Boolean): TRect;
  55. function GetRectOfMonitorContainingRect(const R: TRect; const WorkArea: Boolean): TRect;
  56. function GetRectOfMonitorContainingWindow(const W: HWND; const WorkArea: Boolean): TRect;
  57. function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect;
  58. function GetTextHeight(const DC: HDC): Integer;
  59. function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer;
  60. procedure HandleWMPrint(const Wnd: HWND; var Message: TMessage;
  61. const NCPaintFunc: THandleWMPrintNCPaintProc; const AppData: Longint);
  62. procedure HandleWMPrintClient(const Control: TWinControl;
  63. var Message: TMessage);
  64. function IsWindowsXP: Boolean;
  65. procedure ListSortEx(const List: TList; const Compare: TListSortExCompare;
  66. const ExtraData: Pointer);
  67. procedure InitTrackMouseEvent;
  68. function Max(A, B: Integer): Integer;
  69. function Min(A, B: Integer): Integer;
  70. function MethodsEqual(const M1, M2: TMethod): Boolean;
  71. function NeedToPlaySound(const Alias: String): Boolean;
  72. procedure ProcessPaintMessages;
  73. procedure RemoveMessages(const AMin, AMax: Integer);
  74. procedure RemoveFromList(var List: TList; Item: Pointer);
  75. procedure SelectNCUpdateRgn(Wnd: HWND; DC: HDC; Rgn: HRGN);
  76. function StripAccelChars(const S: String): String;
  77. function StripTrailingPunctuation(const S: String): String;
  78. function UsingMultipleMonitors: Boolean;
  79. const
  80. PopupMenuWindowNCSize = 3;
  81. DT_HIDEPREFIX = $00100000;
  82. var
  83. TrackMouseEventFunc: function(var EventTrack: TTrackMouseEvent): BOOL; stdcall;
  84. implementation
  85. uses
  86. TB2Version;
  87. function ApplicationIsActive: Boolean;
  88. { Returns True if the application is in the foreground }
  89. begin
  90. Result := GetActiveWindow <> 0;
  91. end;
  92. {$IFNDEF JR_D3}
  93. function CopyPalette(Palette: HPALETTE): HPALETTE;
  94. var
  95. PaletteSize: Integer;
  96. LogPal: TMaxLogPalette;
  97. begin
  98. Result := 0;
  99. if Palette = 0 then Exit;
  100. PaletteSize := 0;
  101. if GetObject(Palette, SizeOf(PaletteSize), @PaletteSize) = 0 then Exit;
  102. if PaletteSize = 0 then Exit;
  103. with LogPal do begin
  104. palVersion := $0300;
  105. palNumEntries := PaletteSize;
  106. GetPaletteEntries(Palette, 0, PaletteSize, palPalEntry);
  107. end;
  108. Result := CreatePalette(PLogPalette(@LogPal)^);
  109. end;
  110. {$ENDIF}
  111. procedure ListSortEx(const List: TList; const Compare: TListSortExCompare;
  112. const ExtraData: Pointer);
  113. { Similar to TList.Sort, but lets you pass a user-defined ExtraData pointer }
  114. procedure QuickSortEx(L: Integer; const R: Integer);
  115. var
  116. I, J: Integer;
  117. P: Pointer;
  118. begin
  119. repeat
  120. I := L;
  121. J := R;
  122. P := List[(L + R) shr 1];
  123. repeat
  124. while Compare(List[I], P, ExtraData) < 0 do Inc(I);
  125. while Compare(List[J], P, ExtraData) > 0 do Dec(J);
  126. if I <= J then
  127. begin
  128. List.Exchange(I, J);
  129. Inc(I);
  130. Dec(J);
  131. end;
  132. until I > J;
  133. if L < J then QuickSortEx(L, J);
  134. L := I;
  135. until I >= R;
  136. end;
  137. begin
  138. if List.Count > 1 then
  139. QuickSortEx(0, List.Count-1);
  140. end;
  141. type
  142. PPrintEnumProcData = ^TPrintEnumProcData;
  143. TPrintEnumProcData = record
  144. PrintChildren: Boolean;
  145. ParentWnd: HWND;
  146. DC: HDC;
  147. PrintFlags: LPARAM;
  148. end;
  149. function PrintEnumProc(Wnd: HWND; LParam: LPARAM): BOOL; stdcall;
  150. var
  151. R: TRect;
  152. SaveIndex: Integer;
  153. begin
  154. Result := True; { continue enumerating }
  155. with PPrintEnumProcData(LParam)^ do begin
  156. { Skip window if it isn't a child/owned window of ParentWnd or isn't visible }
  157. if (HWND(GetWindowLong(Wnd, GWL_HWNDPARENT)) <> ParentWnd) or
  158. (GetWindowLong(Wnd, GWL_STYLE) and WS_VISIBLE = 0) then
  159. { ^ don't use IsWindowVisible since it returns False if the window's
  160. parent window is not visible }
  161. Exit;
  162. GetWindowRect(Wnd, R);
  163. MapWindowPoints(0, ParentWnd, R, 2);
  164. SaveIndex := SaveDC(DC);
  165. { Like Windows, offset the window origin to the top-left coordinates of
  166. the child/owned window }
  167. MoveWindowOrg(DC, R.Left, R.Top);
  168. { Like Windows, intersect the clipping region with the entire rectangle of
  169. the child/owned window }
  170. OffsetRect(R, -R.Left, -R.Top);
  171. IntersectClipRect(DC, R.Left, R.Top, R.Right, R.Bottom);
  172. { Send a WM_PRINT message to the child/owned window }
  173. SendMessage(Wnd, WM_PRINT, WPARAM(DC), PrintFlags);
  174. { Restore the DC's state, in case the WM_PRINT handler didn't put things
  175. back the way it found them }
  176. RestoreDC(DC, SaveIndex);
  177. end;
  178. end;
  179. procedure HandleWMPrint(const Wnd: HWND; var Message: TMessage;
  180. const NCPaintFunc: THandleWMPrintNCPaintProc; const AppData: Longint);
  181. { note: AppData is an application-defined value which is passed to NCPaintFunc }
  182. var
  183. DC: HDC;
  184. SaveIndex, SaveIndex2: Integer;
  185. R: TRect;
  186. P: TPoint;
  187. Data: TPrintEnumProcData;
  188. begin
  189. if (Message.LParam and PRF_CHECKVISIBLE = 0) or IsWindowVisible(Wnd) then begin
  190. DC := HDC(Message.WParam);
  191. SaveIndex2 := SaveDC(DC);
  192. try
  193. if Message.LParam and PRF_NONCLIENT <> 0 then begin
  194. SaveIndex := SaveDC(DC);
  195. if Assigned(NCPaintFunc) then
  196. NCPaintFunc(Wnd, DC, AppData);
  197. RestoreDC(DC, SaveIndex);
  198. end;
  199. { Calculate the difference between the top-left corner of the window
  200. and the top-left corner of its client area }
  201. GetWindowRect(Wnd, R);
  202. P.X := 0; P.Y := 0;
  203. ClientToScreen(Wnd, P);
  204. Dec(P.X, R.Left); Dec(P.Y, R.Top);
  205. if Message.LParam and PRF_CLIENT <> 0 then begin
  206. { Like Windows, the flags PRF_ERASEBKGND, PRF_CHILDREN, and PRF_OWNED
  207. are ignored if PRF_CLIENT isn't also specified }
  208. if Message.LParam and PRF_ERASEBKGND <> 0 then begin
  209. { Send WM_ERASEBKGND }
  210. SaveIndex := SaveDC(DC);
  211. if Message.LParam and PRF_NONCLIENT <> 0 then
  212. MoveWindowOrg(DC, P.X, P.Y);
  213. SendMessage(Wnd, WM_ERASEBKGND, Message.WParam, 0);
  214. RestoreDC(DC, SaveIndex);
  215. end;
  216. { Send WM_PRINTCLIENT }
  217. SaveIndex := SaveDC(DC);
  218. if Message.LParam and PRF_NONCLIENT <> 0 then
  219. MoveWindowOrg(DC, P.X, P.Y);
  220. SendMessage(Wnd, WM_PRINTCLIENT, Message.WParam, 0);
  221. RestoreDC(DC, SaveIndex);
  222. { Like Windows, always offset child/owned windows by the size of the
  223. client area even if PRF_NONCLIENT isn't specified (a bug?) }
  224. MoveWindowOrg(DC, P.X, P.Y);
  225. Data.ParentWnd := Wnd;
  226. Data.DC := DC;
  227. { Send WM_PRINT to child/owned windows }
  228. if Message.LParam and PRF_CHILDREN <> 0 then begin
  229. Data.PrintChildren := True;
  230. Data.PrintFlags := PRF_NONCLIENT or PRF_CLIENT or PRF_ERASEBKGND or
  231. PRF_CHILDREN; { same flags as Windows passes to children }
  232. EnumChildWindows(Wnd, @PrintEnumProc, LPARAM(@Data));
  233. end;
  234. if Message.LParam and PRF_OWNED <> 0 then begin
  235. Data.PrintChildren := False;
  236. Data.PrintFlags := Message.LParam;
  237. EnumWindows(@PrintEnumProc, LPARAM(@Data));
  238. end;
  239. end;
  240. finally
  241. RestoreDC(DC, SaveIndex2);
  242. end;
  243. end;
  244. { Windows' WM_PRINT returns 1. I'm not sure why. }
  245. Message.Result := 1;
  246. end;
  247. type
  248. TWinControlAccess = class(TWinControl);
  249. procedure HandleWMPrintClient(const Control: TWinControl; var Message: TMessage);
  250. var
  251. Msg: TWMPaint;
  252. SaveIndex: Integer;
  253. begin
  254. Msg.Msg := WM_PAINT;
  255. Msg.DC := HDC(Message.WParam);
  256. Msg.Unused := 0;
  257. Msg.Result := 0;
  258. SaveIndex := SaveDC(HDC(Message.WParam));
  259. try
  260. TWinControlAccess(Control).PaintHandler(Msg);
  261. finally
  262. RestoreDC(HDC(Message.WParam), SaveIndex);
  263. end;
  264. end;
  265. function DivRoundUp(const Dividend, Divisor: Integer): Integer;
  266. { Similar to the 'div' operator, but if there is a remainder it always rounds
  267. the result up one (or down if the result is negative). }
  268. asm
  269. mov ecx, edx
  270. cdq
  271. idiv ecx
  272. test edx, edx
  273. jz @@1
  274. test eax, eax
  275. jns @@2
  276. dec eax
  277. jmp @@1
  278. @@2:
  279. inc eax
  280. @@1:
  281. end;
  282. function GetTextHeight(const DC: HDC): Integer;
  283. var
  284. TextMetric: TTextMetric;
  285. begin
  286. GetTextMetrics(DC, TextMetric);
  287. Result := TextMetric.tmHeight;
  288. end;
  289. function StripAccelChars(const S: String): String;
  290. var
  291. I: Integer;
  292. begin
  293. Result := S;
  294. I := 1;
  295. while I <= Length(Result) do begin
  296. if not(Result[I] in LeadBytes) then begin
  297. if Result[I] = '&' then
  298. System.Delete(Result, I, 1);
  299. Inc(I);
  300. end
  301. else
  302. Inc(I, 2);
  303. end;
  304. end;
  305. function EscapeAmpersands(const S: String): String;
  306. { Replaces any '&' characters with '&&' }
  307. var
  308. I: Integer;
  309. begin
  310. Result := S;
  311. I := 1;
  312. while I <= Length(Result) do begin
  313. if not(Result[I] in LeadBytes) then begin
  314. if Result[I] = '&' then begin
  315. Inc(I);
  316. Insert('&', Result, I);
  317. end;
  318. Inc(I);
  319. end
  320. else
  321. Inc(I, 2);
  322. end;
  323. end;
  324. function StripTrailingPunctuation(const S: String): String;
  325. { Removes any colon (':') or ellipsis ('...') from the end of S and returns
  326. the resulting string }
  327. var
  328. L: Integer;
  329. begin
  330. Result := S;
  331. L := Length(Result);
  332. if (L > 1) and (Result[L] = ':') and (ByteType(Result, L) = mbSingleByte) then
  333. SetLength(Result, L-1)
  334. else if (L > 3) and (Result[L-2] = '.') and (Result[L-1] = '.') and
  335. (Result[L] = '.') and (ByteType(Result, L-2) = mbSingleByte) then
  336. SetLength(Result, L-3);
  337. end;
  338. function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer;
  339. { Returns the width of the specified string using the font currently selected
  340. into DC. If Prefix is True, it first removes "&" characters as necessary. }
  341. var
  342. Size: TSize;
  343. begin
  344. { This procedure is 10x faster than using DrawText with the DT_CALCRECT flag }
  345. if Prefix then
  346. S := StripAccelChars(S);
  347. GetTextExtentPoint32(DC, PChar(S), Length(S), Size);
  348. Result := Size.cx;
  349. end;
  350. procedure ProcessPaintMessages;
  351. { Dispatches all pending WM_PAINT messages. In effect, this is like an
  352. 'UpdateWindow' on all visible windows }
  353. var
  354. Msg: TMsg;
  355. begin
  356. while PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_NOREMOVE) do begin
  357. case Integer(GetMessage(Msg, 0, WM_PAINT, WM_PAINT)) of
  358. -1: Break; { if GetMessage failed }
  359. 0: begin
  360. { Repost WM_QUIT messages }
  361. PostQuitMessage(Msg.WParam);
  362. Break;
  363. end;
  364. end;
  365. DispatchMessage(Msg);
  366. end;
  367. end;
  368. procedure RemoveMessages(const AMin, AMax: Integer);
  369. { Removes any messages with the specified ID from the queue }
  370. var
  371. Msg: TMsg;
  372. begin
  373. while PeekMessage(Msg, 0, AMin, AMax, PM_REMOVE) do begin
  374. if Msg.message = WM_QUIT then begin
  375. { Repost WM_QUIT messages }
  376. PostQuitMessage(Msg.WParam);
  377. Break;
  378. end;
  379. end;
  380. end;
  381. procedure SelectNCUpdateRgn(Wnd: HWND; DC: HDC; Rgn: HRGN);
  382. var
  383. R: TRect;
  384. NewClipRgn: HRGN;
  385. begin
  386. if (Rgn <> 0) and (Rgn <> 1) then begin
  387. GetWindowRect(Wnd, R);
  388. if SelectClipRgn(DC, Rgn) = ERROR then begin
  389. NewClipRgn := CreateRectRgnIndirect(R);
  390. SelectClipRgn(DC, NewClipRgn);
  391. DeleteObject(NewClipRgn);
  392. end;
  393. OffsetClipRgn(DC, -R.Left, -R.Top);
  394. end;
  395. end;
  396. function AddToList(var List: TList; Item: Pointer): Boolean;
  397. { Returns True if Item didn't already exist in the list }
  398. begin
  399. if List = nil then
  400. List := TList.Create;
  401. Result := List.IndexOf(Item) = -1;
  402. if Result then
  403. List.Add(Item);
  404. end;
  405. function AddToFrontOfList(var List: TList; Item: Pointer): Boolean;
  406. { Returns True if Item didn't already exist in the list }
  407. begin
  408. if List = nil then
  409. List := TList.Create;
  410. Result := List.IndexOf(Item) = -1;
  411. if Result then
  412. List.Insert(0, Item);
  413. end;
  414. procedure RemoveFromList(var List: TList; Item: Pointer);
  415. begin
  416. if Assigned(List) then begin
  417. List.Remove(Item);
  418. if List.Count = 0 then begin
  419. List.Free;
  420. List := nil;
  421. end;
  422. end;
  423. end;
  424. var
  425. RegMenuShowDelay: Integer;
  426. RegMenuShowDelayInited: BOOL = False;
  427. function GetMenuShowDelay: Integer;
  428. const
  429. DefaultMenuShowDelay = 400;
  430. function ReadMenuShowDelayFromRegistry: Integer;
  431. var
  432. K: HKEY;
  433. Typ, DataSize: DWORD;
  434. Data: array[0..31] of Char;
  435. Res: Longint;
  436. E: Integer;
  437. begin
  438. Result := DefaultMenuShowDelay;
  439. if RegOpenKeyEx(HKEY_CURRENT_USER, 'Control Panel\Desktop', 0,
  440. KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  441. DataSize := SizeOf(Data);
  442. Res := RegQueryValueEx(K, 'MenuShowDelay', nil, @Typ, @Data, @DataSize);
  443. RegCloseKey(K);
  444. if Res <> ERROR_FILE_NOT_FOUND then begin
  445. if (Res <> ERROR_SUCCESS) or (Typ <> REG_SZ) then
  446. Result := 0
  447. else begin
  448. Val(Data, Result, E);
  449. if E <> 0 then Result := 0;
  450. end;
  451. end;
  452. end;
  453. end;
  454. begin
  455. if Lo(GetVersion) >= 4 then begin
  456. if not SystemParametersInfo(106{SPI_GETMENUSHOWDELAY}, 0, @Result, 0) then begin
  457. { SPI_GETMENUSHOWDELAY is only supported by Windows NT 4.0 and Windows 98.
  458. On Windows 95, it must use the registry to retrieve this setting. }
  459. if not RegMenuShowDelayInited then begin
  460. RegMenuShowDelay := ReadMenuShowDelayFromRegistry;
  461. InterlockedExchange(Integer(RegMenuShowDelayInited), Ord(True));
  462. end;
  463. Result := RegMenuShowDelay;
  464. end;
  465. if Result < 0 then Result := 0;
  466. end
  467. else
  468. Result := DefaultMenuShowDelay;
  469. end;
  470. function AreFlatMenusEnabled: Boolean;
  471. { Returns True if "flat menus" are enabled. Always returns False on pre-XP
  472. Windows versions. }
  473. const
  474. SPI_GETFLATMENU = $1022;
  475. var
  476. FlatMenusEnabled: BOOL;
  477. begin
  478. { Interestingly, on Windows 2000, SystemParametersInfo(SPI_GETFLATMENU, ...)
  479. succeeds and can return True in pvParam^ if the proper bit is set in
  480. UserPreferencesMask. Since flat menus are not really used on Windows
  481. 2000, call IsWindowsXP first to see if we're running at least XP. }
  482. Result := IsWindowsXP and SystemParametersInfo(SPI_GETFLATMENU, 0,
  483. @FlatMenusEnabled, 0) and FlatMenusEnabled;
  484. end;
  485. function AreKeyboardCuesEnabled: Boolean;
  486. { Returns True if "keyboard cues" are enabled. Always returns True on
  487. pre-2000 Windows versions. }
  488. const
  489. SPI_GETKEYBOARDCUES = $100A;
  490. var
  491. CuesEnabled: BOOL;
  492. begin
  493. Result := (Win32MajorVersion < 5) or
  494. not SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, @CuesEnabled, 0) or
  495. CuesEnabled;
  496. end;
  497. function CreateNullRegion: HRGN;
  498. var
  499. R: TRect;
  500. begin
  501. SetRectEmpty(R);
  502. Result := CreateRectRgnIndirect(R);
  503. end;
  504. procedure DrawInvertRect(const DC: HDC; const NewRect, OldRect: PRect;
  505. const NewSize, OldSize: TSize; const Brush: HBRUSH; BrushLast: HBRUSH);
  506. { Draws a dragging outline, hiding the old one if neccessary. This code is
  507. based on MFC sources.
  508. Either NewRect or OldRect can be nil or empty. }
  509. var
  510. SaveIndex: Integer;
  511. rgnNew, rgnOutside, rgnInside, rgnLast, rgnUpdate: HRGN;
  512. R: TRect;
  513. begin
  514. rgnLast := 0;
  515. rgnUpdate := 0;
  516. { First, determine the update region and select it }
  517. if NewRect = nil then begin
  518. SetRectEmpty(R);
  519. rgnOutside := CreateRectRgnIndirect(R);
  520. end
  521. else begin
  522. R := NewRect^;
  523. rgnOutside := CreateRectRgnIndirect(R);
  524. InflateRect(R, -NewSize.cx, -NewSize.cy);
  525. IntersectRect(R, R, NewRect^);
  526. end;
  527. rgnInside := CreateRectRgnIndirect(R);
  528. rgnNew := CreateNullRegion;
  529. CombineRgn(rgnNew, rgnOutside, rgnInside, RGN_XOR);
  530. if BrushLast = 0 then
  531. BrushLast := Brush;
  532. if OldRect <> nil then begin
  533. { Find difference between new region and old region }
  534. rgnLast := CreateNullRegion;
  535. with OldRect^ do
  536. SetRectRgn(rgnOutside, Left, Top, Right, Bottom);
  537. R := OldRect^;
  538. InflateRect(R, -OldSize.cx, -OldSize.cy);
  539. IntersectRect(R, R, OldRect^);
  540. SetRectRgn(rgnInside, R.Left, R.Top, R.Right, R.Bottom);
  541. CombineRgn(rgnLast, rgnOutside, rgnInside, RGN_XOR);
  542. { Only diff them if brushes are the same }
  543. if Brush = BrushLast then begin
  544. rgnUpdate := CreateNullRegion;
  545. CombineRgn(rgnUpdate, rgnLast, rgnNew, RGN_XOR);
  546. end;
  547. end;
  548. { Save the DC state so that the clipping region can be restored }
  549. SaveIndex := SaveDC(DC);
  550. try
  551. if (Brush <> BrushLast) and (OldRect <> nil) then begin
  552. { Brushes are different -- erase old region first }
  553. SelectClipRgn(DC, rgnLast);
  554. GetClipBox(DC, R);
  555. SelectObject(DC, BrushLast);
  556. PatBlt(DC, R.Left, R.Top, R.Right-R.Left, R.Bottom-R.Top, PATINVERT);
  557. end;
  558. { Draw into the update/new region }
  559. if rgnUpdate <> 0 then
  560. SelectClipRgn(DC, rgnUpdate)
  561. else
  562. SelectClipRgn(DC, rgnNew);
  563. GetClipBox(DC, R);
  564. SelectObject(DC, Brush);
  565. PatBlt(DC, R.Left, R.Top, R.Right-R.Left, R.Bottom-R.Top, PATINVERT);
  566. finally
  567. { Clean up DC }
  568. RestoreDC(DC, SaveIndex);
  569. end;
  570. { Free regions }
  571. if rgnNew <> 0 then DeleteObject(rgnNew);
  572. if rgnOutside <> 0 then DeleteObject(rgnOutside);
  573. if rgnInside <> 0 then DeleteObject(rgnInside);
  574. if rgnLast <> 0 then DeleteObject(rgnLast);
  575. if rgnUpdate <> 0 then DeleteObject(rgnUpdate);
  576. end;
  577. function CreateHalftoneBrush: HBRUSH;
  578. const
  579. Patterns: array[Boolean] of Word = ($5555, $AAAA);
  580. var
  581. I: Integer;
  582. GrayPattern: array[0..7] of Word;
  583. GrayBitmap: HBITMAP;
  584. begin
  585. for I := 0 to 7 do
  586. GrayPattern[I] := Patterns[Odd(I)];
  587. GrayBitmap := CreateBitmap(8, 8, 1, 1, @GrayPattern);
  588. Result := CreatePatternBrush(GrayBitmap);
  589. DeleteObject(GrayBitmap);
  590. end;
  591. procedure DrawHalftoneInvertRect(const DC: HDC; const NewRect, OldRect: PRect;
  592. const NewSize, OldSize: TSize);
  593. var
  594. Brush: HBRUSH;
  595. begin
  596. Brush := CreateHalftoneBrush;
  597. try
  598. DrawInvertRect(DC, NewRect, OldRect, NewSize, OldSize, Brush, Brush);
  599. finally
  600. DeleteObject(Brush);
  601. end;
  602. end;
  603. function MethodsEqual(const M1, M2: TMethod): Boolean;
  604. begin
  605. Result := (M1.Code = M2.Code) and (M1.Data = M2.Data);
  606. end;
  607. function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect;
  608. begin
  609. if not WorkArea or not SystemParametersInfo(SPI_GETWORKAREA, 0, @Result, 0) then
  610. Result := Rect(0, 0, Screen.Width, Screen.Height);
  611. end;
  612. function UsingMultipleMonitors: Boolean;
  613. { Returns True if the system has more than one display monitor configured. }
  614. var
  615. NumMonitors: Integer;
  616. begin
  617. NumMonitors := GetSystemMetrics(80 {SM_CMONITORS});
  618. Result := (NumMonitors <> 0) and (NumMonitors <> 1);
  619. { ^ NumMonitors will be zero if not running Win98, NT 5, or later }
  620. end;
  621. type
  622. HMONITOR = type Integer;
  623. PMonitorInfoA = ^TMonitorInfoA;
  624. TMonitorInfoA = record
  625. cbSize: DWORD;
  626. rcMonitor: TRect;
  627. rcWork: TRect;
  628. dwFlags: DWORD;
  629. end;
  630. const
  631. MONITOR_DEFAULTTONEAREST = $2;
  632. type
  633. TMultiMonApis = record
  634. funcMonitorFromRect: function(lprcScreenCoords: PRect; dwFlags: DWORD): HMONITOR; stdcall;
  635. funcMonitorFromPoint: function(ptScreenCoords: TPoint; dwFlags: DWORD): HMONITOR; stdcall;
  636. funcMonitorFromWindow: function(hWnd: HWND; dwFlags: DWORD): HMONITOR; stdcall;
  637. funcGetMonitorInfoA: function(hMonitor: HMONITOR; lpMonitorInfo: PMonitorInfoA): BOOL; stdcall;
  638. end;
  639. { Under D4 I could be using the MultiMon unit for the multiple monitor
  640. function imports, but its stubs for MonitorFromRect and MonitorFromPoint
  641. are seriously bugged... So I chose to avoid the MultiMon unit entirely. }
  642. function InitMultiMonApis(var Apis: TMultiMonApis): Boolean;
  643. var
  644. User32Handle: THandle;
  645. begin
  646. User32Handle := GetModuleHandle(user32);
  647. Apis.funcMonitorFromRect := GetProcAddress(User32Handle, 'MonitorFromRect');
  648. Apis.funcMonitorFromPoint := GetProcAddress(User32Handle, 'MonitorFromPoint');
  649. Apis.funcMonitorFromWindow := GetProcAddress(User32Handle, 'MonitorFromWindow');
  650. Apis.funcGetMonitorInfoA := GetProcAddress(User32Handle, 'GetMonitorInfoA');
  651. Result := Assigned(Apis.funcMonitorFromRect) and
  652. Assigned(Apis.funcMonitorFromPoint) and Assigned(Apis.funcGetMonitorInfoA);
  653. end;
  654. function GetRectOfMonitorContainingRect(const R: TRect;
  655. const WorkArea: Boolean): TRect;
  656. { Returns the work area of the monitor which the rectangle R intersects with
  657. the most, or the monitor nearest R if no monitors intersect. }
  658. var
  659. Apis: TMultiMonApis;
  660. M: HMONITOR;
  661. MonitorInfo: TMonitorInfoA;
  662. begin
  663. if UsingMultipleMonitors and InitMultiMonApis(Apis) then begin
  664. M := Apis.funcMonitorFromRect(@R, MONITOR_DEFAULTTONEAREST);
  665. MonitorInfo.cbSize := SizeOf(MonitorInfo);
  666. if Apis.funcGetMonitorInfoA(M, @MonitorInfo) then begin
  667. if not WorkArea then
  668. Result := MonitorInfo.rcMonitor
  669. else
  670. Result := MonitorInfo.rcWork;
  671. Exit;
  672. end;
  673. end;
  674. Result := GetRectOfPrimaryMonitor(WorkArea);
  675. end;
  676. function GetRectOfMonitorContainingPoint(const P: TPoint;
  677. const WorkArea: Boolean): TRect;
  678. { Returns the screen area of the monitor containing the point P, or the monitor
  679. nearest P if P isn't in any monitor's work area. }
  680. var
  681. Apis: TMultiMonApis;
  682. M: HMONITOR;
  683. MonitorInfo: TMonitorInfoA;
  684. begin
  685. if UsingMultipleMonitors and InitMultiMonApis(Apis) then begin
  686. M := Apis.funcMonitorFromPoint(P, MONITOR_DEFAULTTONEAREST);
  687. MonitorInfo.cbSize := SizeOf(MonitorInfo);
  688. if Apis.funcGetMonitorInfoA(M, @MonitorInfo) then begin
  689. if not WorkArea then
  690. Result := MonitorInfo.rcMonitor
  691. else
  692. Result := MonitorInfo.rcWork;
  693. Exit;
  694. end;
  695. end;
  696. Result := GetRectOfPrimaryMonitor(WorkArea);
  697. end;
  698. function GetRectOfMonitorContainingWindow(const W: HWND;
  699. const WorkArea: Boolean): TRect;
  700. var
  701. Apis: TMultiMonApis;
  702. M: HMONITOR;
  703. MonitorInfo: TMonitorInfoA;
  704. begin
  705. if UsingMultipleMonitors and InitMultiMonApis(Apis) then begin
  706. M := Apis.funcMonitorFromWindow(W, MONITOR_DEFAULTTONEAREST);
  707. MonitorInfo.cbSize := SizeOf(MonitorInfo);
  708. if Apis.funcGetMonitorInfoA(M, @MonitorInfo) then begin
  709. if not WorkArea then
  710. Result := MonitorInfo.rcMonitor
  711. else
  712. Result := MonitorInfo.rcWork;
  713. Exit;
  714. end;
  715. end;
  716. Result := GetRectOfPrimaryMonitor(WorkArea);
  717. end;
  718. var
  719. TrackMouseEventInited: BOOL;
  720. procedure InitTrackMouseEvent;
  721. var
  722. TrackMouseEventComCtlModule: THandle;
  723. begin
  724. { First look for TrackMouseEvent which is available on Windows 98 & NT 4 only.
  725. If it doesn't exist, look for _TrackMouseEvent which is available on
  726. Windows 95 if IE 3.0 or later is installed. }
  727. if not TrackMouseEventInited then begin
  728. TrackMouseEventFunc := GetProcAddress(GetModuleHandle(user32),
  729. 'TrackMouseEvent');
  730. if @TrackMouseEventFunc = nil then begin
  731. TrackMouseEventComCtlModule :=
  732. {$IFDEF JR_D5} SafeLoadLibrary {$ELSE} LoadLibrary {$ENDIF} (comctl32);
  733. if TrackMouseEventComCtlModule <> 0 then
  734. TrackMouseEventFunc := GetProcAddress(TrackMouseEventComCtlModule,
  735. '_TrackMouseEvent');
  736. end;
  737. InterlockedExchange(Integer(TrackMouseEventInited), Ord(True));
  738. end;
  739. end;
  740. function CallTrackMouseEvent(const Wnd: HWND; const Flags: DWORD): Boolean;
  741. var
  742. Track: TTrackMouseEvent;
  743. begin
  744. Result := False;
  745. if Assigned(TrackMouseEventFunc) then begin
  746. Track.cbSize := SizeOf(Track);
  747. Track.dwFlags := Flags;
  748. Track.hwndTrack := Wnd;
  749. Track.dwHoverTime := 0;
  750. Result := TrackMouseEventFunc(Track);
  751. end;
  752. end;
  753. {$IFNDEF JR_D5}
  754. procedure FreeAndNil(var Obj);
  755. var
  756. P: TObject;
  757. begin
  758. P := TObject(Obj);
  759. TObject(Obj) := nil;
  760. P.Free;
  761. end;
  762. {$ENDIF}
  763. function EnumFontsProc(const lplf: TLogFont; const lptm: TTextMetric;
  764. dwType: DWORD; lpData: LPARAM): Integer; stdcall;
  765. begin
  766. Boolean(Pointer(lpData)^) := True;
  767. Result := 0;
  768. end;
  769. function CreateRotatedFont(DC: HDC): HFONT;
  770. { Creates a font based on the DC's current font, but rotated 270 degrees }
  771. var
  772. LogFont: TLogFont;
  773. TM: TTextMetric;
  774. VerticalFontName: array[0..LF_FACESIZE-1] of Char;
  775. VerticalFontExists: Boolean;
  776. begin
  777. if GetObject(GetCurrentObject(DC, OBJ_FONT), SizeOf(LogFont),
  778. @LogFont) = 0 then begin
  779. { just in case... }
  780. Result := 0;
  781. Exit;
  782. end;
  783. LogFont.lfEscapement := 2700;
  784. LogFont.lfOrientation := 2700;
  785. LogFont.lfOutPrecision := OUT_TT_ONLY_PRECIS; { needed for Win9x }
  786. { Don't let a random TrueType font be substituted when MS Sans Serif or
  787. Microsoft Sans Serif are used. Hard-code Arial. }
  788. if (StrIComp(LogFont.lfFaceName, 'MS Sans Serif') = 0) or
  789. (StrIComp(LogFont.lfFaceName, 'Microsoft Sans Serif') = 0) then begin
  790. StrPCopy(LogFont.lfFaceName, 'Arial');
  791. { Set lfHeight to the actual height of the current font. This is needed
  792. to work around a Windows 98 issue: on a clean install of the OS,
  793. SPI_GETNONCLIENTMETRICS returns -5 for lfSmCaptionFont.lfHeight. This is
  794. wrong; it should return -11 for an 8 pt font. With normal, unrotated text
  795. this actually displays correctly, since MS Sans Serif doesn't support
  796. sizes below 8 pt. However, when we change to a TrueType font like Arial,
  797. this becomes a problem because it'll actually create a font that small. }
  798. if GetTextMetrics(DC, TM) then begin
  799. { If the original height was negative, keep it negative }
  800. if LogFont.lfHeight <= 0 then
  801. LogFont.lfHeight := -(TM.tmHeight - TM.tmInternalLeading)
  802. else
  803. LogFont.lfHeight := TM.tmHeight;
  804. end;
  805. end;
  806. { Use a vertical font if available so that Asian characters aren't drawn
  807. sideways }
  808. if StrLen(LogFont.lfFaceName) < SizeOf(VerticalFontName)-1 then begin
  809. VerticalFontName[0] := '@';
  810. StrCopy(@VerticalFontName[1], LogFont.lfFaceName);
  811. VerticalFontExists := False;
  812. EnumFonts(DC, VerticalFontName, @EnumFontsProc, @VerticalFontExists);
  813. if VerticalFontExists then
  814. StrCopy(LogFont.lfFaceName, VerticalFontName);
  815. end;
  816. Result := CreateFontIndirect(LogFont);
  817. end;
  818. procedure DrawRotatedText(const DC: HDC; AText: String; const ARect: TRect;
  819. const AFormat: Cardinal);
  820. { Like DrawText, but draws the text at a 270 degree angle.
  821. The format flag this function respects are
  822. DT_NOPREFIX, DT_HIDEPREFIX, DT_CENTER, DT_END_ELLIPSIS, DT_NOCLIP }
  823. var
  824. RotatedFont, SaveFont: HFONT;
  825. TextMetrics: TTextMetric;
  826. X, Y, P, I, SU, FU, W: Integer;
  827. SaveAlign: UINT;
  828. SavePen, Pen: HPEN;
  829. Clip: Boolean;
  830. function GetSize(DC: HDC; const S: string): Integer;
  831. var
  832. Size: TSize;
  833. begin
  834. GetTextExtentPoint32(DC, PChar(S), Length(S), Size);
  835. Result := Size.cx;
  836. end;
  837. begin
  838. if Length(AText) = 0 then Exit;
  839. RotatedFont := CreateRotatedFont(DC);
  840. SaveFont := SelectObject(DC, RotatedFont);
  841. GetTextMetrics(DC, TextMetrics);
  842. X := ARect.Left + ((ARect.Right - ARect.Left) - TextMetrics.tmHeight) div 2;
  843. Clip := (AFormat and DT_NOCLIP) <> DT_NOCLIP;
  844. { Find the index of the character that should be underlined. Delete '&'
  845. characters from the string. Like DrawText, only the last prefixed character
  846. will be underlined. }
  847. P := 0;
  848. I := 1;
  849. if (AFormat and DT_NOPREFIX) <> DT_NOPREFIX then
  850. while I <= Length(AText) do begin
  851. if AText[I] in LeadBytes then
  852. Inc(I)
  853. else if AText[I] = '&' then begin
  854. Delete(AText, I, 1);
  855. { Note: PChar cast is so that if Delete deleted the last character in
  856. the string, we don't step past the end of the string (which would cause
  857. an AV if AText is now empty), but rather look at the null character
  858. and treat it as an accelerator key like DrawText. }
  859. if PChar(AText)[I-1] <> '&' then
  860. P := I;
  861. end;
  862. Inc(I);
  863. end;
  864. if (AFormat and DT_END_ELLIPSIS) = DT_END_ELLIPSIS then
  865. begin
  866. if (Length(AText) > 1) and (GetSize(DC, AText) > ARect.Bottom - ARect.Top) then
  867. begin
  868. W := ARect.Bottom - ARect.Top;
  869. if W > 2 then
  870. begin
  871. Delete(AText, Length(AText), 1);
  872. while (Length(AText) > 1) and (GetSize(DC, AText + '...') > W) do
  873. Delete(AText, Length(AText), 1);
  874. end
  875. else AText := AText[1];
  876. if P > Length(AText) then P := 0;
  877. AText := AText + '...';
  878. end;
  879. end;
  880. if (AFormat and DT_CENTER) = DT_CENTER then
  881. Y := ARect.Top + ((ARect.Bottom - ARect.Top) - GetSize(DC, AText)) div 2
  882. else
  883. Y := ARect.Top;
  884. if Clip then
  885. begin
  886. SaveDC(DC);
  887. with ARect do IntersectClipRect(DC, Left, Top, Right, Bottom);
  888. end;
  889. SaveAlign := SetTextAlign(DC, TA_BOTTOM);
  890. TextOut(DC, X, Y, PChar(AText), Length(AText));
  891. SetTextAlign(DC, SaveAlign);
  892. { Underline }
  893. if (P > 0) and (AFormat and DT_HIDEPREFIX = 0) then begin
  894. SU := GetTextWidth(DC, Copy(AText, 1, P-1), False);
  895. FU := SU + GetTextWidth(DC, PChar(AText)[P-1], False);
  896. Inc(X, TextMetrics.tmDescent - 2);
  897. Pen := CreatePen(PS_SOLID, 1, GetTextColor(DC));
  898. SavePen := SelectObject(DC, Pen);
  899. MoveToEx(DC, X, Y + SU, nil);
  900. LineTo(DC, X, Y + FU);
  901. SelectObject(DC, SavePen);
  902. DeleteObject(Pen);
  903. end;
  904. if Clip then RestoreDC(DC, -1);
  905. SelectObject(DC, SaveFont);
  906. DeleteObject(RotatedFont);
  907. end;
  908. function NeedToPlaySound(const Alias: String): Boolean;
  909. { This function checks the registry to see if the specified sound event alias
  910. is assigned to a file.
  911. The purpose of having this function is so it can avoid calls to PlaySound if
  912. possible, because on Windows 2000 there is an annoying 1/3 second delay on
  913. the first call to PlaySound.
  914. Windows Explorer actually uses this same technique when playing sounds for
  915. the Start menu. }
  916. var
  917. K: HKEY;
  918. Data: array[0..3] of WideChar;
  919. DataSize: DWORD;
  920. ErrorCode: Longint;
  921. begin
  922. if (Win32MajorVersion < 5) or (Win32Platform <> VER_PLATFORM_WIN32_NT) then begin
  923. { No need to check pre-Windows 2000 versions since their PlaySound
  924. functions don't have the delay; always return True. }
  925. Result := True;
  926. Exit;
  927. end;
  928. Result := False;
  929. if RegOpenKeyEx(HKEY_CURRENT_USER,
  930. PChar('AppEvents\Schemes\Apps\.Default\' + Alias + '\.Current'),
  931. 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  932. DataSize := SizeOf(Data);
  933. { Note: Use the 'W' version of RegQueryValueEx for more speed }
  934. ErrorCode := RegQueryValueExW(K, nil, nil, nil, @Data, @DataSize);
  935. if ((ErrorCode = ERROR_SUCCESS) and (Data[0] <> #0)) or
  936. (ErrorCode = ERROR_MORE_DATA) then
  937. Result := True;
  938. RegCloseKey(K);
  939. end;
  940. end;
  941. function Max(A, B: Integer): Integer;
  942. begin
  943. if A >= B then
  944. Result := A
  945. else
  946. Result := B;
  947. end;
  948. function Min(A, B: Integer): Integer;
  949. begin
  950. if A <= B then
  951. Result := A
  952. else
  953. Result := B;
  954. end;
  955. function FindAccelChar(const S: String): Char;
  956. { Finds the last accelerator key in S. Returns #0 if no accelerator key was
  957. found. '&&' is ignored. }
  958. var
  959. P: PChar;
  960. begin
  961. P := PChar(S);
  962. Result := #0;
  963. while True do begin
  964. P := AnsiStrScan(P, '&');
  965. if P = nil then Break;
  966. Inc(P);
  967. if P^ <> '&' then begin
  968. if P^ = #0 then Break;
  969. Result := P^;
  970. end;
  971. Inc(P);
  972. end;
  973. end;
  974. function IsWindowsXP: Boolean;
  975. begin
  976. Result := (Win32Platform = VER_PLATFORM_WIN32_NT) and
  977. ((Win32MajorVersion > 5) or
  978. ((Win32MajorVersion = 5) and (Win32MinorVersion >= 1)));
  979. end;
  980. function GetInputLocaleCodePage: UINT;
  981. { Returns the code page identifier of the active input locale, or CP_ACP if
  982. for some unknown reason it couldn't be determined. }
  983. var
  984. Buf: array[0..15] of Char;
  985. ErrorCode: Integer;
  986. begin
  987. if GetLocaleInfo(GetKeyboardLayout(0) and $FFFF, LOCALE_IDEFAULTANSICODEPAGE,
  988. Buf, SizeOf(Buf)) > 0 then begin
  989. Buf[High(Buf)] := #0; { ensure null termination, just in case... }
  990. Val(Buf, Result, ErrorCode);
  991. { Just to be *completely* safe, verify that the code page returned by
  992. GetLocaleInfo actually exists. The result of this function may be fed
  993. into WideCharToMultiByte, and we don't want WideCharToMultiByte to fail
  994. entirely because of a bad code page. }
  995. if (ErrorCode <> 0) or not IsValidCodePage(Result) then
  996. Result := CP_ACP;
  997. end
  998. else
  999. Result := CP_ACP;
  1000. end;
  1001. end.