TB2Common.pas 33 KB

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