CmnFunc.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. unit CmnFunc;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2024 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Common VCL functions
  8. }
  9. {$B-}
  10. interface
  11. {$I VERSION.INC}
  12. uses
  13. Windows, Messages, SysUtils, Forms, Graphics, Controls, StdCtrls, Classes;
  14. type
  15. TWindowDisabler = class
  16. private
  17. FFallbackWnd, FOwnerWnd: HWND;
  18. FPreviousActiveWnd, FPreviousFocusWnd: HWND;
  19. FWindowList: Pointer;
  20. public
  21. constructor Create;
  22. destructor Destroy; override;
  23. end;
  24. { Note: This type is also present in ScriptFunc_C.pas }
  25. TMsgBoxType = (mbInformation, mbConfirmation, mbError, mbCriticalError);
  26. TMsgBoxCallbackFunc = procedure(const Flags: LongInt; const After: Boolean;
  27. const Param: LongInt);
  28. { Useful constant }
  29. const
  30. EnableColor: array[Boolean] of TColor = (clBtnFace, clWindow);
  31. procedure UpdateHorizontalExtent(const ListBox: TCustomListBox);
  32. function MinimizePathName(const Filename: String; const Font: TFont;
  33. MaxLen: Integer): String;
  34. function AppMessageBox(const Text, Caption: PChar; Flags: Longint): Integer;
  35. function MsgBoxP(const Text, Caption: PChar; const Typ: TMsgBoxType;
  36. const Buttons: Cardinal): Integer;
  37. function MsgBox(const Text, Caption: String; const Typ: TMsgBoxType;
  38. const Buttons: Cardinal): Integer;
  39. function MsgBoxFmt(const Text: String; const Args: array of const;
  40. const Caption: String; const Typ: TMsgBoxType; const Buttons: Cardinal): Integer;
  41. procedure ReactivateTopWindow;
  42. procedure SetMessageBoxCaption(const Typ: TMsgBoxType; const NewCaption: PChar);
  43. function GetMessageBoxCaption(const Caption: PChar; const Typ: TMsgBoxType): PChar;
  44. procedure SetMessageBoxRightToLeft(const ARightToLeft: Boolean);
  45. function GetMessageBoxRightToLeft: Boolean;
  46. procedure SetMessageBoxCallbackFunc(const AFunc: TMsgBoxCallbackFunc; const AParam: LongInt);
  47. procedure TriggerMessageBoxCallbackFunc(const Flags: LongInt; const After: Boolean);
  48. implementation
  49. uses
  50. Consts, PathFunc, CmnFunc2;
  51. var
  52. MessageBoxCaptions: array[TMsgBoxType] of PChar;
  53. MessageBoxRightToLeft: Boolean;
  54. MessageBoxCallbackFunc: TMsgBoxCallbackFunc;
  55. MessageBoxCallbackParam: LongInt;
  56. MessageBoxCallbackActive: Boolean;
  57. type
  58. TListBoxAccess = class(TCustomListBox);
  59. procedure UpdateHorizontalExtent(const ListBox: TCustomListBox);
  60. var
  61. I: Integer;
  62. Extent, MaxExtent: Longint;
  63. DC: HDC;
  64. Size: TSize;
  65. TextMetrics: TTextMetric;
  66. begin
  67. DC := GetDC(0);
  68. try
  69. SelectObject(DC, TListBoxAccess(ListBox).Font.Handle);
  70. //Q66370 says tmAveCharWidth should be added to extent
  71. GetTextMetrics(DC, TextMetrics);
  72. MaxExtent := 0;
  73. for I := 0 to ListBox.Items.Count-1 do begin
  74. GetTextExtentPoint32(DC, PChar(ListBox.Items[I]), Length(ListBox.Items[I]), Size);
  75. Extent := Size.cx + TextMetrics.tmAveCharWidth;
  76. if Extent > MaxExtent then
  77. MaxExtent := Extent;
  78. end;
  79. finally
  80. ReleaseDC(0, DC);
  81. end;
  82. if MaxExtent > SendMessage(ListBox.Handle, LB_GETHORIZONTALEXTENT, 0, 0) then
  83. SendMessage(ListBox.Handle, LB_SETHORIZONTALEXTENT, MaxExtent, 0);
  84. end;
  85. function MinimizePathName(const Filename: String; const Font: TFont;
  86. MaxLen: Integer): String;
  87. procedure CutFirstDirectory(var S: String);
  88. var
  89. P: Integer;
  90. begin
  91. if Copy(S, 1, 4) = '...\' then
  92. Delete(S, 1, 4);
  93. P := PathPos('\', S);
  94. if P <> 0 then
  95. begin
  96. Delete(S, 1, P);
  97. S := '...\' + S;
  98. end
  99. else
  100. S := '';
  101. end;
  102. var
  103. DC: HDC;
  104. Drive, Dir, Name: String;
  105. DriveLen: Integer;
  106. begin
  107. DC := GetDC(0);
  108. try
  109. SelectObject(DC, Font.Handle);
  110. Result := FileName;
  111. Dir := PathExtractPath(Result);
  112. Name := PathExtractName(Result);
  113. DriveLen := PathDrivePartLength(Dir);
  114. { Include any slash following drive part, or a leading slash if DriveLen=0 }
  115. if (DriveLen < Length(Dir)) and PathCharIsSlash(Dir[DriveLen+1]) then
  116. Inc(DriveLen);
  117. Drive := Copy(Dir, 1, DriveLen);
  118. Delete(Dir, 1, DriveLen);
  119. while ((Dir <> '') or (Drive <> '')) and (GetTextWidth(DC, Result, False) > MaxLen) do
  120. begin
  121. if Dir <> '' then
  122. CutFirstDirectory(Dir);
  123. { If there's no directory left, minimize the drive part.
  124. 'C:\...\filename' -> '...\filename' }
  125. if (Dir = '') and (Drive <> '') then
  126. begin
  127. Drive := '';
  128. Dir := '...\';
  129. end;
  130. Result := Drive + Dir + Name;
  131. end;
  132. finally
  133. ReleaseDC(0, DC);
  134. end;
  135. end;
  136. procedure SetMessageBoxCaption(const Typ: TMsgBoxType; const NewCaption: PChar);
  137. begin
  138. StrDispose(MessageBoxCaptions[Typ]);
  139. MessageBoxCaptions[Typ] := nil;
  140. if Assigned(NewCaption) then
  141. MessageBoxCaptions[Typ] := StrNew(NewCaption);
  142. end;
  143. function GetMessageBoxCaption(const Caption: PChar; const Typ: TMsgBoxType): PChar;
  144. const
  145. DefaultCaptions: array[TMsgBoxType] of PChar =
  146. ('Information', 'Confirm', 'Error', 'Error');
  147. begin
  148. Result := Caption;
  149. if (Result = nil) or (Result[0] = #0) then begin
  150. Result := MessageBoxCaptions[Typ];
  151. if Result = nil then
  152. Result := DefaultCaptions[Typ];
  153. end;
  154. end;
  155. procedure SetMessageBoxRightToLeft(const ARightToLeft: Boolean);
  156. begin
  157. MessageBoxRightToLeft := ARightToLeft;
  158. end;
  159. function GetMessageBoxRightToLeft: Boolean;
  160. begin
  161. Result := MessageBoxRightToLeft;
  162. end;
  163. procedure SetMessageBoxCallbackFunc(const AFunc: TMsgBoxCallbackFunc; const AParam: LongInt);
  164. begin
  165. MessageBoxCallbackFunc := AFunc;
  166. MessageBoxCallbackParam := AParam;
  167. end;
  168. procedure TriggerMessageBoxCallbackFunc(const Flags: LongInt; const After: Boolean);
  169. begin
  170. if Assigned(MessageBoxCallbackFunc) and not MessageBoxCallbackActive then begin
  171. MessageBoxCallbackActive := True;
  172. try
  173. MessageBoxCallbackFunc(Flags, After, MessageBoxCallbackParam);
  174. finally
  175. MessageBoxCallbackActive := False;
  176. end;
  177. end;
  178. end;
  179. function AppMessageBox(const Text, Caption: PChar; Flags: Longint): Integer;
  180. var
  181. ActiveWindow: HWND;
  182. WindowList: Pointer;
  183. begin
  184. if MessageBoxRightToLeft then
  185. Flags := Flags or (MB_RTLREADING or MB_RIGHT);
  186. TriggerMessageBoxCallbackFunc(Flags, False);
  187. try
  188. { If the application window isn't currently visible, show the message box
  189. with no owner window so it'll get a taskbar button }
  190. if IsIconic(Application.Handle) or
  191. (GetWindowLong(Application.Handle, GWL_STYLE) and WS_VISIBLE = 0) or
  192. (GetWindowLong(Application.Handle, GWL_EXSTYLE) and WS_EX_TOOLWINDOW <> 0) then begin
  193. ActiveWindow := GetActiveWindow;
  194. WindowList := DisableTaskWindows(0);
  195. try
  196. { Note: DisableTaskWindows doesn't disable invisible windows.
  197. MB_TASKMODAL will ensure that Application.Handle gets disabled too. }
  198. Result := MessageBox(0, Text, Caption, Flags or MB_TASKMODAL);
  199. finally
  200. EnableTaskWindows(WindowList);
  201. SetActiveWindow(ActiveWindow);
  202. end;
  203. Exit;
  204. end;
  205. Result := Application.MessageBox(Text, Caption, Flags);
  206. finally
  207. TriggerMessageBoxCallbackFunc(Flags, True);
  208. end;
  209. end;
  210. function MsgBoxP(const Text, Caption: PChar; const Typ: TMsgBoxType;
  211. const Buttons: Cardinal): Integer;
  212. const
  213. IconFlags: array[TMsgBoxType] of Cardinal =
  214. (MB_ICONINFORMATION, MB_ICONQUESTION, MB_ICONEXCLAMATION, MB_ICONSTOP);
  215. begin
  216. Result := AppMessageBox(Text, GetMessageBoxCaption(Caption, Typ), Buttons or IconFlags[Typ]);
  217. end;
  218. function MsgBox(const Text, Caption: String; const Typ: TMsgBoxType;
  219. const Buttons: Cardinal): Integer;
  220. begin
  221. Result := MsgBoxP(PChar(Text), PChar(Caption), Typ, Buttons);
  222. end;
  223. function MsgBoxFmt(const Text: String; const Args: array of const;
  224. const Caption: String; const Typ: TMsgBoxType; const Buttons: Cardinal): Integer;
  225. begin
  226. Result := MsgBox(Format(Text, Args), Caption, Typ, Buttons);
  227. end;
  228. function ReactivateTopWindowEnumProc(Wnd: HWND; LParam: LPARAM): BOOL; stdcall;
  229. begin
  230. { Stop if we encounter the application window; don't consider it or any
  231. windows below it }
  232. if Wnd = Application.Handle then
  233. Result := False
  234. else
  235. if IsWindowVisible(Wnd) and IsWindowEnabled(Wnd) and
  236. (GetWindowLong(Wnd, GWL_EXSTYLE) and (WS_EX_TOPMOST or WS_EX_TOOLWINDOW) = 0) then begin
  237. SetActiveWindow(Wnd);
  238. Result := False;
  239. end
  240. else
  241. Result := True;
  242. end;
  243. procedure ReactivateTopWindow;
  244. { If the application window is active, reactivates the top window owned by the
  245. current thread. Tool windows and windows that are invisible, disabled, or
  246. topmost are not considered. }
  247. begin
  248. if GetActiveWindow = Application.Handle then
  249. EnumThreadWindows(GetCurrentThreadId, @ReactivateTopWindowEnumProc, 0);
  250. end;
  251. procedure FreeCaptions; far;
  252. var
  253. T: TMsgBoxType;
  254. begin
  255. for T := Low(T) to High(T) do begin
  256. StrDispose(MessageBoxCaptions[T]);
  257. MessageBoxCaptions[T] := nil;
  258. end;
  259. end;
  260. { TWindowDisabler }
  261. const
  262. WindowDisablerWndClassName = 'TWindowDisabler-Window';
  263. var
  264. WindowDisablerWndClassAtom: TAtom;
  265. function WindowDisablerWndProc(Wnd: HWND; Msg: UINT; WParam: WPARAM;
  266. LParam: LPARAM): LRESULT; stdcall;
  267. begin
  268. if Msg = WM_CLOSE then
  269. { If the fallback window becomes focused (e.g. by Alt+Tabbing onto it) and
  270. Alt+F4 is pressed, we must not pass the message to DefWindowProc because
  271. it would destroy the window }
  272. Result := 0
  273. else
  274. Result := DefWindowProc(Wnd, Msg, WParam, LParam);
  275. end;
  276. constructor TWindowDisabler.Create;
  277. const
  278. WndClass: TWndClass = (
  279. style: 0;
  280. lpfnWndProc: @WindowDisablerWndProc;
  281. cbClsExtra: 0;
  282. cbWndExtra: 0;
  283. hInstance: 0;
  284. hIcon: 0;
  285. hCursor: 0;
  286. hbrBackground: COLOR_WINDOW + 1;
  287. lpszMenuName: nil;
  288. lpszClassName: WindowDisablerWndClassName);
  289. begin
  290. inherited Create;
  291. FPreviousActiveWnd := GetActiveWindow;
  292. FPreviousFocusWnd := GetFocus;
  293. FWindowList := DisableTaskWindows(0);
  294. { Create the "fallback" window.
  295. When a child process hides its last window, Windows will try to activate
  296. the top-most enabled window on the desktop. If all of our windows were
  297. disabled, it would end up bringing some other application to the
  298. foreground. This gives Windows an enabled window to re-activate, which
  299. is invisible to the user. }
  300. if WindowDisablerWndClassAtom = 0 then
  301. WindowDisablerWndClassAtom := Windows.RegisterClass(WndClass);
  302. if WindowDisablerWndClassAtom <> 0 then begin
  303. { Create an invisible owner window for the fallback window so that it
  304. doesn't display a taskbar button. (We can't just give it the
  305. WS_EX_TOOLWINDOW style because Windows skips tool windows when searching
  306. for a new window to activate.) }
  307. FOwnerWnd := CreateWindowEx(0, WindowDisablerWndClassName, '',
  308. WS_POPUP or WS_DISABLED, 0, 0, 0, 0, HWND_DESKTOP, 0, HInstance, nil);
  309. if FOwnerWnd <> 0 then begin
  310. FFallbackWnd := CreateWindowEx(0, WindowDisablerWndClassName,
  311. PChar(Application.Title), WS_POPUP, 0, 0, 0, 0, FOwnerWnd, 0,
  312. HInstance, nil);
  313. if FFallbackWnd <> 0 then
  314. ShowWindow(FFallbackWnd, SW_SHOWNA);
  315. end;
  316. end;
  317. { Take the focus away from whatever has it. While you can't click controls
  318. inside a disabled window, keystrokes will still reach the focused control
  319. (e.g. you can press Space to re-click a focused button). }
  320. SetFocus(0);
  321. end;
  322. destructor TWindowDisabler.Destroy;
  323. begin
  324. EnableTaskWindows(FWindowList);
  325. { Re-activate the previous window. But don't do this if GetActiveWindow
  326. returns zero, because that means another application is in the foreground
  327. (possibly a child process spawned by us that is still running). }
  328. if GetActiveWindow <> 0 then begin
  329. if FPreviousActiveWnd <> 0 then
  330. SetActiveWindow(FPreviousActiveWnd);
  331. { If the active window never changed, then the above SetActiveWindow call
  332. won't have an effect. Explicitly restore the focus. }
  333. if FPreviousFocusWnd <> 0 then
  334. SetFocus(FPreviousFocusWnd);
  335. end;
  336. if FOwnerWnd <> 0 then
  337. DestroyWindow(FOwnerWnd); { will destroy FFallbackWnd too }
  338. inherited;
  339. end;
  340. initialization
  341. finalization
  342. FreeCaptions;
  343. end.