BidiCtrls.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. unit BidiCtrls;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Previously this unit had RTL-capable versions of standard controls
  8. But now standard controls are RTL-capable already, and there's not much code left here
  9. Define VCLSTYLES to include an improved version TButtonStyleHook.DrawButton for command
  10. link buttons
  11. }
  12. interface
  13. uses
  14. Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms, Dialogs,
  15. {$IFDEF VCLSTYLES} Vcl.Themes, {$ELSE} Themes, {$ENDIF}
  16. StdCtrls, ExtCtrls;
  17. type
  18. TNewEdit = class(TEdit);
  19. TNewMemo = class(TMemo);
  20. TNewComboBox = class(TComboBox);
  21. TNewListBox = class(TListBox);
  22. TNewButton = class(TButton)
  23. private
  24. class var FDontStyle: Boolean;
  25. class constructor Create;
  26. class destructor Destroy;
  27. protected
  28. procedure CreateParams(var Params: TCreateParams); override;
  29. public
  30. constructor Create(AOwner: TComponent); override;
  31. function AdjustHeightIfCommandLink: Integer;
  32. class property DontStyle: Boolean write FDontStyle;
  33. end;
  34. TNewButtonStyleHook = class(TButtonStyleHook)
  35. {$IFDEF VCLSTYLES}
  36. private
  37. class function DrawOrMeasureCommandLink(const Draw: Boolean;
  38. const Control: TNewButton; const LStyle: TCustomStyleServices; const FPressed: Boolean;
  39. const ACanvas: TCanvas; const AMouseInControl: Boolean): Integer;
  40. class function GetIdealHeightIfCommandLink(const Control: TNewButton; const Style: TCustomStyleServices): Integer;
  41. protected
  42. procedure DrawButton(ACanvas: TCanvas; AMouseInControl: Boolean); override;
  43. {$ENDIF}
  44. end;
  45. TNewCheckBox = class(TCheckBox);
  46. TNewRadioButton = class(TRadioButton);
  47. TNewLinkLabel = class(TLinkLabel)
  48. public
  49. function AdjustHeight: Integer;
  50. end;
  51. procedure Register;
  52. implementation
  53. uses
  54. CommCtrl, Types,
  55. BidiUtils;
  56. procedure Register;
  57. begin
  58. RegisterComponents('JR', [TNewEdit, TNewMemo, TNewComboBox, TNewListBox,
  59. TNewButton, TNewCheckBox, TNewRadioButton]);
  60. end;
  61. { TNewButton }
  62. class constructor TNewButton.Create;
  63. begin
  64. TCustomStyleEngine.RegisterStyleHook(TNewButton, TNewButtonStyleHook);
  65. end;
  66. constructor TNewButton.Create(AOwner: TComponent);
  67. begin
  68. inherited;
  69. if FDontStyle then
  70. StyleName := TStyleManager.SystemStyleName;
  71. end;
  72. procedure TNewButton.CreateParams(var Params: TCreateParams);
  73. begin
  74. inherited;
  75. if (Style = bsCommandLink) and IsRightToLeft then begin
  76. { Command link buttons need to have WS_EX_LAYOUTRTL enabled for full RTL, in addition to
  77. WS_EX_RTLREADING and WS_EX_LEFTSCROLLBAR, but not WS_EX_RIGHT. This can be confirmed by
  78. inspecting the style of a task dialog command link button. However, if VCL Styles is
  79. active, this should not be done since the style hook does not expect it at all. }
  80. if not IsCustomStyleActive then
  81. Params.ExStyle := Params.ExStyle or WS_EX_LAYOUTRTL;
  82. end;
  83. end;
  84. class destructor TNewButton.Destroy;
  85. begin
  86. TCustomStyleEngine.UnregisterStyleHook(TNewButton, TNewButtonStyleHook);
  87. end;
  88. function TNewButton.AdjustHeightIfCommandLink: Integer;
  89. begin
  90. Result := 0;
  91. if Style = bsCommandLink then begin
  92. var OldHeight := Height;
  93. {$IFDEF VCLSTYLES}
  94. var LStyle := StyleServices(Self);
  95. if not LStyle.Enabled or LStyle.IsSystemStyle then
  96. LStyle := nil;
  97. if LStyle <> nil then begin
  98. const IdealHeight = TNewButtonStyleHook.GetIdealHeightIfCommandLink(Self, LStyle);
  99. if IdealHeight <> 0 then begin
  100. Height := IdealHeight;
  101. Exit(Height- OldHeight);
  102. end;
  103. end;
  104. {$ENDIF}
  105. var IdealSize: TSize;
  106. IdealSize.cx := Width;
  107. IdealSize.cy := 0; { Not needed according to docs and tests, but clearing anyway }
  108. if SendMessage(Handle, BCM_GETIDEALSIZE, 0, LPARAM(@IdealSize)) <> 0 then begin
  109. Height := IdealSize.cy;
  110. Result := Height - OldHeight;
  111. end;
  112. end;
  113. end;
  114. { TNewLinkLabel }
  115. function TNewLinkLabel.AdjustHeight: Integer;
  116. begin
  117. var OldHeight := Height;
  118. var IdealSize: TSize;
  119. SendMessage(Handle, LM_GETIDEALSIZE, Width, LPARAM(@IdealSize));
  120. Height := IdealSize.cy;
  121. Result := Height - OldHeight;
  122. end;
  123. {$IFDEF VCLSTYLES}
  124. { TNewButtonStyleHook - same as Vcl.StdCtrls' TButtonStyleHook except that for command links it:
  125. -Adds support for measuring height
  126. -Fixes RTL support for CommandLinkHint
  127. -Actually flips the text and icons on RTL
  128. -Improves alignment of shield icons, especially at high dpi
  129. -Avoids drawing empty notes
  130. -Respects the font of the control
  131. -Properly centers glyphs vertically on the first text line
  132. For other button styles it just calls the original code, and the code for those styles is not copied here }
  133. class function TNewButtonStyleHook.DrawOrMeasureCommandLink(const Draw: Boolean;
  134. const Control: TNewButton; const LStyle: TCustomStyleServices; const FPressed: Boolean;
  135. const ACanvas: TCanvas; const AMouseInControl: Boolean): Integer;
  136. var
  137. Details: TThemedElementDetails;
  138. LParentRect, DrawRect, R, RSingleLine: TRect;
  139. LIsRightToLeft: Boolean;
  140. IL: BUTTON_IMAGELIST;
  141. IW, IH: Integer;
  142. TextFormat: TTextFormatFlags;
  143. ThemeTextColor: TColor;
  144. Buffer: string;
  145. BufferLength: Integer;
  146. ImgIndex: Integer;
  147. BCaption: String;
  148. IsDefault: Boolean;
  149. IsElevationRequired: Boolean;
  150. LPPI: Integer;
  151. begin
  152. const Handle = Control.Handle;
  153. LPPI := Control.CurrentPPI;
  154. LParentRect := Control.ClientRect;
  155. LIsRightToLeft := Control.IsRightToLeft;
  156. BCaption := Control.Caption;
  157. ImgIndex := 0;
  158. IsDefault := Control.Active;
  159. IsElevationRequired := CheckWin32Version(6, 0) and Control.ElevationRequired;
  160. if not Control.Enabled then
  161. begin
  162. Details := LStyle.GetElementDetails(tbPushButtonDisabled);
  163. ImgIndex := 3;
  164. end
  165. else
  166. if FPressed then
  167. begin
  168. Details := LStyle.GetElementDetails(tbPushButtonPressed);
  169. ImgIndex := 2;
  170. end
  171. else if AMouseInControl then
  172. begin
  173. Details := LStyle.GetElementDetails(tbPushButtonHot);
  174. ImgIndex := 1;
  175. end
  176. else if Control.Focused or IsDefault then
  177. begin
  178. Details := LStyle.GetElementDetails(tbPushButtonDefaulted);
  179. ImgIndex := 4;
  180. end
  181. else if Control.Enabled then
  182. Details := LStyle.GetElementDetails(tbPushButtonNormal);
  183. DrawRect := LParentRect;
  184. if Draw then
  185. LStyle.DrawElement(ACanvas.Handle, Details, DrawRect);
  186. IW := MulDiv(35, LPPI, Screen.DefaultPixelsPerInch);
  187. Inc(DrawRect.Left, IW);
  188. Inc(DrawRect.Top, 15);
  189. Inc(DrawRect.Left, 5);
  190. ACanvas.Font := Control.Font;
  191. R := DrawRect;
  192. TextFormat := TTextFormatFlags(Control.DrawTextBiDiModeFlags(DT_LEFT or DT_WORDBREAK or DT_CALCRECT));
  193. LStyle.DrawText(ACanvas.Handle, Details, BCaption, R, TextFormat, ACanvas.Font.Color); { R is used directly below for measuring, and later also for the note }
  194. Result := R.Bottom;
  195. if Draw then begin
  196. RSingleLine := DrawRect;
  197. TextFormat := TTextFormatFlags(Control.DrawTextBiDiModeFlags(DT_LEFT or DT_SINGLELINE or DT_CALCRECT));
  198. LStyle.DrawText(ACanvas.Handle, Details, BCaption, RSingleLine, TextFormat, ACanvas.Font.Color); { RSingleLine is used below for the glyphs }
  199. { Following does not use any DT_CALCRECT results }
  200. TextFormat := TTextFormatFlags(Control.DrawTextBiDiModeFlags(DT_LEFT or DT_WORDBREAK));
  201. if (seFont in Control.StyleElements) and LStyle.GetElementColor(Details, ecTextColor, ThemeTextColor) then
  202. ACanvas.Font.Color := ThemeTextColor;
  203. var R2 := DrawRect;
  204. FlipRect(R2, LParentRect, LIsRightToLeft);
  205. LStyle.DrawText(ACanvas.Handle, Details, BCaption, R2, TextFormat, ACanvas.Font.Color);
  206. end;
  207. SetLength(Buffer, Button_GetNoteLength(Handle) + 1);
  208. if Length(Buffer) > 1 then
  209. begin
  210. BufferLength := Length(Buffer);
  211. if Button_GetNote(Handle, PChar(Buffer), BufferLength) then
  212. begin
  213. Inc(DrawRect.Top, R.Height + 2); { R is the DT_CALCRECT result }
  214. ACanvas.Font.Height := MulDiv(ACanvas.Font.Height, 2, 3);
  215. R := DrawRect;
  216. TextFormat := TTextFormatFlags(Control.DrawTextBiDiModeFlags(DT_LEFT or DT_WORDBREAK or DT_CALCRECT));
  217. LStyle.DrawText(ACanvas.Handle, Details, Buffer, R, TextFormat, ACanvas.Font.Color); { R is used directly below for measuring }
  218. if R.Bottom > Result then
  219. Result := R.Bottom;
  220. if Draw then begin
  221. { Following does not use any DT_CALCRECT results }
  222. TextFormat := TTextFormatFlags(Control.DrawTextBiDiModeFlags(DT_LEFT or DT_WORDBREAK));
  223. FlipRect(DrawRect, LParentRect, LIsRightToLeft);
  224. LStyle.DrawText(ACanvas.Handle, Details, Buffer, DrawRect, TextFormat, ACanvas.Font.Color);
  225. end;
  226. end;
  227. end;
  228. Inc(Result, 15);
  229. if not Draw then
  230. Exit;
  231. if Button_GetImageList(handle, IL) and (IL.himl <> 0) and
  232. ImageList_GetIconSize(IL.himl, IW, IH) then
  233. begin
  234. R.Left := 2;
  235. R.Top := RSingleLine.Top + (RSingleLine.Height - IH) div 2;
  236. if IsElevationRequired then
  237. begin
  238. ImgIndex := 0;
  239. Inc(R.Left, MulDiv(8, LPPI, Screen.DefaultPixelsPerInch));
  240. end;
  241. R.Right := R.Left + IW;
  242. R.Bottom := R.Top + IH;
  243. if Draw then begin
  244. FlipRect(R, LParentRect, LIsRightToLeft);
  245. ImageList_Draw(IL.himl, ImgIndex, ACanvas.Handle, R.Left, R.Top, ILD_NORMAL);
  246. end;
  247. end else begin
  248. if not Control.Enabled then
  249. Details := LStyle.GetElementDetails(tbCommandLinkGlyphDisabled)
  250. else
  251. if FPressed then
  252. Details := LStyle.GetElementDetails(tbCommandLinkGlyphPressed)
  253. else if Control.Focused then
  254. Details := LStyle.GetElementDetails(tbCommandLinkGlyphDefaulted)
  255. else if AMouseInControl then
  256. Details := LStyle.GetElementDetails(tbCommandLinkGlyphHot)
  257. else
  258. Details := LStyle.GetElementDetails(tbCommandLinkGlyphNormal);
  259. DrawRect.Right := IW;
  260. DrawRect.Left := 3;
  261. DrawRect.Top := RSingleLine.Top + (RSingleLine.Height - IW) div 2;
  262. DrawRect.Bottom := DrawRect.Top + IW;
  263. if Draw then begin
  264. if LIsRightToLeft then begin
  265. FlipRect(DrawRect, LParentRect, True);
  266. var FlipBitmap := TBitmap.Create;
  267. try
  268. FlipBitmap.Width := DrawRect.Width;
  269. FlipBitmap.Height := DrawRect.Height;
  270. BitBlt(FlipBitmap.Canvas.Handle, 0, 0, DrawRect.Width, DrawRect.Height, ACanvas.Handle, DrawRect.Left, DrawRect.Top, SRCCOPY);
  271. LStyle.DrawElement(FlipBitmap.Canvas.Handle, Details, Rect(0, 0, DrawRect.Width, DrawRect.Height), nil, LPPI);
  272. StretchBlt(ACanvas.Handle, DrawRect.Left, DrawRect.Top, DrawRect.Width, DrawRect.Height,
  273. FlipBitmap.Canvas.Handle, FlipBitmap.Width-1, 0, -FlipBitmap.Width, FlipBitmap.Height, SRCCOPY);
  274. finally
  275. FlipBitmap.Free;
  276. end;
  277. end else
  278. LStyle.DrawElement(ACanvas.Handle, Details, DrawRect, nil, LPPI);
  279. end;
  280. end;
  281. end;
  282. class function TNewButtonStyleHook.GetIdealHeightIfCommandLink(const Control: TNewButton; const Style: TCustomStyleServices): Integer;
  283. begin
  284. const Canvas = TCanvas.Create;
  285. try
  286. Canvas.Handle := GetDC(0);
  287. try
  288. Result := DrawOrMeasureCommandLink(False, Control, Style, False, Canvas, False);
  289. finally
  290. ReleaseDC(0, Canvas.Handle);
  291. end;
  292. finally
  293. Canvas.Handle := 0;
  294. Canvas.Free;
  295. end;
  296. end;
  297. procedure TNewButtonStyleHook.DrawButton(ACanvas: TCanvas; AMouseInControl: Boolean);
  298. begin
  299. const LControlStyle = GetWindowLong(Handle, GWL_STYLE);
  300. if (LControlStyle and BS_COMMANDLINK) <> BS_COMMANDLINK then begin
  301. inherited;
  302. Exit;
  303. end;
  304. DrawOrMeasureCommandLink(True, TNewButton(Control), StyleServices, FPressed, ACanvas, AMouseInControl);
  305. end;
  306. {$ENDIF}
  307. end.