TB2Common.pas 34 KB

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