TB2Common.pas 33 KB

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