bccombobox.pas 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075
  1. // SPDX-License-Identifier: LGPL-3.0-linking-exception
  2. unit BCComboBox;
  3. {$mode delphi}
  4. interface
  5. uses
  6. {$ifdef WINDOWS}Windows,{$endif} Classes, SysUtils, LResources, Forms, Controls, ExtCtrls, Graphics, Dialogs, BCButton,
  7. StdCtrls, BCTypes, BCBaseCtrls, BGRABitmap, BGRABitmapTypes, LMessages, LCLType;
  8. type
  9. { TBCComboBox }
  10. TBCComboBox = class(TBCStyleCustomControl)
  11. private
  12. FButton: TBCButton;
  13. FCanvasScaleMode: TBCCanvasScaleMode;
  14. FDropDownBorderSize: integer;
  15. FDropDownCount: integer;
  16. FDropDownColor: TColor;
  17. FDropDownFontColor: TColor;
  18. FDropDownFontHighlight: TColor;
  19. FDropDownHighlight: TColor;
  20. FDropDownOnSameForm: boolean;
  21. FFocusBorderColor: TColor;
  22. FFocusBorderOpacity: byte;
  23. FItems: TStringList;
  24. FItemIndex: integer;
  25. FForm: TForm;
  26. FPanel: TPanel;
  27. FDropDownHideDate: TDateTime;
  28. FHoverItem: integer;
  29. FItemHeight: integer;
  30. FListBox: TListBox;
  31. FItemPadding: integer;
  32. FDropDownBorderColor: TColor;
  33. FOnDrawItem: TDrawItemEvent;
  34. FOnDrawSelectedItem: TOnAfterRenderBCButton;
  35. FOnChange: TNotifyEvent;
  36. FOnDropDown: TNotifyEvent;
  37. FDrawingDropDown: boolean;
  38. FTimerCheckFormHide: TTimer;
  39. FQueryDropDownHide: boolean;
  40. procedure ButtonClick(Sender: TObject);
  41. procedure FormDeactivate(Sender: TObject);
  42. procedure PanelExit(Sender: TObject);
  43. procedure FormHide(Sender: TObject);
  44. function GetArrowFlip: boolean;
  45. function GetComboCanvas: TCanvas;
  46. function GetArrowSize: integer;
  47. function GetArrowWidth: integer;
  48. function GetButtonHint: TTranslateString;
  49. function GetButtonShowHint: Boolean;
  50. function GetGlobalOpacity: byte;
  51. function GetItemText: string;
  52. function GetDropDownColor: TColor;
  53. function GetItemIndex: integer;
  54. function GetItems: TStrings;
  55. function GetMemoryUsage: TBCButtonMemoryUsage;
  56. function GetOnDrawSelectedItem: TOnAfterRenderBCButton;
  57. function GetRounding: TBCRounding;
  58. function GetStateClicked: TBCButtonState;
  59. function GetStateHover: TBCButtonState;
  60. function GetStateNormal: TBCButtonState;
  61. function GetStaticButton: boolean;
  62. procedure ListBoxKeyDown(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
  63. procedure ListBoxMouseUp({%H-}Sender: TObject; {%H-}Button: TMouseButton;
  64. {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);
  65. procedure ListBoxMouseLeave(Sender: TObject);
  66. procedure ListBoxMouseMove(Sender: TObject; {%H-}Shift: TShiftState; X,
  67. Y: Integer);
  68. procedure ListBoxSelectionChange(Sender: TObject; {%H-}User: boolean);
  69. procedure ListBoxDrawItem(Control: TWinControl; Index: Integer;
  70. ARect: TRect; State: TOwnerDrawState);
  71. procedure OnAfterRenderButton(Sender: TObject; const ABGRA: TBGRABitmap;
  72. AState: TBCButtonState; ARect: TRect);
  73. procedure OnTimerCheckFormHide(Sender: TObject);
  74. procedure SetArrowFlip(AValue: boolean);
  75. procedure SetArrowSize(AValue: integer);
  76. procedure SetArrowWidth(AValue: integer);
  77. procedure SetButtonHint(const AValue: TTranslateString);
  78. procedure SetButtonShowHint(AValue: Boolean);
  79. procedure SetCanvasScaleMode(AValue: TBCCanvasScaleMode);
  80. procedure SetDropDownColor(AValue: TColor);
  81. procedure SetGlobalOpacity(AValue: byte);
  82. procedure SetItemIndex(AValue: integer);
  83. procedure SetItems(AValue: TStrings);
  84. procedure SetMemoryUsage(AValue: TBCButtonMemoryUsage);
  85. procedure SetOnDrawSelectedItem(AValue: TOnAfterRenderBCButton);
  86. procedure SetRounding(AValue: TBCRounding);
  87. procedure SetStateClicked(AValue: TBCButtonState);
  88. procedure SetStateHover(AValue: TBCButtonState);
  89. procedure SetStateNormal(AValue: TBCButtonState);
  90. procedure SetStaticButton(AValue: boolean);
  91. function GetOnButtonMouseDown: TMouseEvent;
  92. function GetOnButtonMouseEnter: TNotifyEvent;
  93. function GetOnButtonMouseLeave: TNotifyEvent;
  94. function GetOnButtonMouseMove: TMouseMoveEvent;
  95. function GetOnButtonMouseUp: TMouseEvent;
  96. function GetOnButtonMouseWheel: TMouseWheelEvent;
  97. function GetOnButtonMouseWheelDown: TMouseWheelUpDownEvent;
  98. function GetOnButtonMouseWheelUp: TMouseWheelUpDownEvent;
  99. procedure SetOnButtonMouseDown(AValue: TMouseEvent);
  100. procedure SetOnButtonMouseEnter(AValue: TNotifyEvent);
  101. procedure SetOnButtonMouseLeave(AValue: TNotifyEvent);
  102. procedure SetOnButtonMouseMove(AValue: TMouseMoveEvent);
  103. procedure SetOnButtonMouseUp(AValue: TMouseEvent);
  104. procedure SetOnButtonMouseWheel(AValue: TMouseWheelEvent);
  105. procedure SetOnButtonMouseWheelDown(AValue: TMouseWheelUpDownEvent);
  106. procedure SetOnButtonMouseWheelUp(AValue: TMouseWheelUpDownEvent);
  107. protected
  108. function GetStyleExtension: String; override;
  109. procedure WMSetFocus(var {%H-}Message: {$IFDEF FPC}TLMSetFocus{$ELSE}TWMSetFocus{$ENDIF}); message {$IFDEF FPC}LM_SETFOCUS{$ELSE}WM_SETFOCUS{$ENDIF};
  110. procedure WMKillFocus(var Message: {$IFDEF FPC}TLMKillFocus{$ELSE}TWMKillFocus{$ENDIF}); message {$IFDEF FPC}LM_KILLFOCUS{$ELSE}WM_KILLFOCUS{$ENDIF};
  111. procedure UpdateFocus(AFocused: boolean);
  112. procedure KeyDown(var Key: Word; {%H-}Shift: TShiftState); override;
  113. procedure UTF8KeyPress(var UTF8Key: TUTF8Char); override;
  114. procedure CreateDropDown;
  115. procedure FreeDropDown;
  116. function CloseDropDown: boolean;
  117. procedure PrepareListBoxForDropDown;
  118. procedure AutosizeListBox;
  119. procedure AdaptDropDownContainerSize;
  120. function GetListBox: TListBox;
  121. procedure UpdateButtonCanvasScaleMode;
  122. public
  123. constructor Create(AOwner: TComponent); override;
  124. destructor Destroy; override;
  125. { Assign the properties from Source to this instance }
  126. procedure Assign(Source: TPersistent); override;
  127. procedure Clear;
  128. property HoverItem: integer read FHoverItem;
  129. property Button: TBCButton read FButton write FButton;
  130. property ListBox: TListBox read GetListBox;
  131. property Text: string read GetItemText;
  132. published
  133. property Align;
  134. property Anchors;
  135. property BorderSpacing;
  136. property Canvas: TCanvas read GetComboCanvas;
  137. property CanvasScaleMode: TBCCanvasScaleMode read FCanvasScaleMode write SetCanvasScaleMode default csmAuto;
  138. property Hint: TTranslateString read GetButtonHint write SetButtonHint;
  139. property Items: TStrings read GetItems write SetItems;
  140. property ItemIndex: integer read GetItemIndex write SetItemIndex;
  141. property ItemHeight: integer read FItemHeight write FItemHeight default 0;
  142. property ArrowSize: integer read GetArrowSize write SetArrowSize;
  143. property ArrowWidth: integer read GetArrowWidth write SetArrowWidth;
  144. property ArrowFlip: boolean read GetArrowFlip write SetArrowFlip default false;
  145. property FocusBorderColor: TColor read FFocusBorderColor write FFocusBorderColor default clBlack;
  146. property FocusBorderOpacity: byte read FFocusBorderOpacity write FFocusBorderOpacity default 0;
  147. property DropDownBorderColor: TColor read FDropDownBorderColor write FDropDownBorderColor default clWindowText;
  148. property DropDownBorderSize: integer read FDropDownBorderSize write FDropDownBorderSize default 1;
  149. property DropDownColor: TColor read GetDropDownColor write SetDropDownColor default clWindow;
  150. property DropDownFontColor: TColor read FDropDownFontColor write FDropDownFontColor default clWindowText;
  151. property DropDownCount: integer read FDropDownCount write FDropDownCount default 8;
  152. property DropDownHighlight: TColor read FDropDownHighlight write FDropDownHighlight default clHighlight;
  153. property DropDownFontHighlight: TColor read FDropDownFontHighlight write FDropDownFontHighlight default clHighlightText;
  154. property DropDownOnSameForm: boolean read FDropDownOnSameForm write FDropDownOnSameForm default False;
  155. property GlobalOpacity: byte read GetGlobalOpacity write SetGlobalOpacity;
  156. property MemoryUsage: TBCButtonMemoryUsage read GetMemoryUsage write SetMemoryUsage;
  157. property Rounding: TBCRounding read GetRounding write SetRounding;
  158. property ShowHint: Boolean read GetButtonShowHint write SetButtonShowHint default False;
  159. property StateClicked: TBCButtonState read GetStateClicked write SetStateClicked;
  160. property StateHover: TBCButtonState read GetStateHover write SetStateHover;
  161. property StateNormal: TBCButtonState read GetStateNormal write SetStateNormal;
  162. property StaticButton: boolean read GetStaticButton write SetStaticButton;
  163. property OnDropDown: TNotifyEvent read FOnDropDown write FOnDropDown;
  164. property OnDrawItem: TDrawItemEvent read FOnDrawItem write FOnDrawItem;
  165. property OnDrawSelectedItem: TOnAfterRenderBCButton read GetOnDrawSelectedItem write SetOnDrawSelectedItem;
  166. property OnChange: TNotifyEvent read FOnChange write FOnChange;
  167. property OnMouseDown: TMouseEvent read GetOnButtonMouseDown write SetOnButtonMouseDown;
  168. property OnMouseMove: TMouseMoveEvent read GetOnButtonMouseMove write SetOnButtonMouseMove;
  169. property OnMouseUp: TMouseEvent read GetOnButtonMouseUp write SetOnButtonMouseUp;
  170. property OnMouseEnter: TNotifyEvent read GetOnButtonMouseEnter write SetOnButtonMouseEnter;
  171. property OnMouseLeave: TNotifyEvent read GetOnButtonMouseLeave write SetOnButtonMouseLeave;
  172. property OnMouseWheel: TMouseWheelEvent read GetOnButtonMouseWheel write SetOnButtonMouseWheel;
  173. property OnMouseWheelDown: TMouseWheelUpDownEvent read GetOnButtonMouseWheelDown write SetOnButtonMouseWheelDown;
  174. property OnMouseWheelUp: TMouseWheelUpDownEvent read GetOnButtonMouseWheelUp write SetOnButtonMouseWheelUp;
  175. property TabStop;
  176. property TabOrder;
  177. end;
  178. procedure Register;
  179. implementation
  180. uses math, PropEdits, BGRAText;
  181. procedure Register;
  182. begin
  183. RegisterComponents('BGRA Controls', [TBCComboBox]);
  184. end;
  185. { TBCComboBox }
  186. procedure TBCComboBox.ButtonClick(Sender: TObject);
  187. const MinDelayReopen = 500/(1000*60*60*24);
  188. var
  189. p: TPoint;
  190. f: TCustomForm;
  191. monitor: TMonitor;
  192. begin
  193. CreateDropDown;
  194. if not CloseDropDown and (Now > FDropDownHideDate+MinDelayReopen) then
  195. begin
  196. if DropDownOnSameForm then
  197. begin
  198. f := GetParentForm(self, False);
  199. if Assigned(f) then
  200. begin
  201. PrepareListBoxForDropDown;
  202. p := ControlToScreen(Point(FButton.Left, FButton.Top + FButton.Height));
  203. p := f.ScreenToClient(p);
  204. FPanel.Parent := f;
  205. FPanel.Left := p.X;
  206. FPanel.Top := p.Y;
  207. FPanel.Color := FDropDownBorderColor;
  208. AdaptDropDownContainerSize;
  209. if FPanel.Top + FPanel.Height > f.ClientHeight then
  210. FPanel.Top := FPanel.Top - FPanel.Height - Self.Height;
  211. if Assigned(FOnDropDown) then FOnDropDown(self);
  212. FQueryDropDownHide := false;
  213. FTimerCheckFormHide.Enabled:= true;
  214. FPanel.Visible := true;
  215. if FPanel.CanSetFocus then
  216. FPanel.SetFocus;
  217. if FListBox.CanSetFocus then
  218. FListBox.SetFocus;
  219. end;
  220. end else
  221. begin
  222. PrepareListBoxForDropDown;
  223. p := ControlToScreen(Point(FButton.Left, FButton.Top + FButton.Height));
  224. FForm.Left := p.X;
  225. FForm.Top := p.Y;
  226. FForm.Color := FDropDownBorderColor;
  227. AdaptDropDownContainerSize;
  228. monitor := Screen.MonitorFromPoint(p);
  229. if FForm.Top + FForm.Height > monitor.WorkareaRect.Bottom then
  230. FForm.Top := FForm.Top - FForm.Height - Self.Height;
  231. if Assigned(FOnDropDown) then FOnDropDown(self);
  232. FQueryDropDownHide := false;
  233. FTimerCheckFormHide.Enabled:= true;
  234. {$IFDEF DARWIN}
  235. f := GetParentForm(self, False);
  236. if fsModal in f.FormState then FForm.ShowModal else
  237. {$ENDIF}
  238. begin
  239. FForm.Visible := True;
  240. if FListBox.CanSetFocus then
  241. FListBox.SetFocus;
  242. end;
  243. end;
  244. end;
  245. end;
  246. procedure TBCComboBox.FormDeactivate(Sender: TObject);
  247. begin
  248. FQueryDropDownHide := true;
  249. end;
  250. procedure TBCComboBox.PanelExit(Sender: TObject);
  251. begin
  252. FQueryDropDownHide := true;
  253. end;
  254. procedure TBCComboBox.FormHide(Sender: TObject);
  255. begin
  256. FDropDownHideDate := Now;
  257. end;
  258. function TBCComboBox.GetArrowFlip: boolean;
  259. begin
  260. result := Button.FlipArrow;
  261. end;
  262. function TBCComboBox.GetComboCanvas: TCanvas;
  263. begin
  264. if FDrawingDropDown then
  265. result := ListBox.Canvas
  266. else
  267. result := inherited Canvas;
  268. end;
  269. function TBCComboBox.GetArrowSize: integer;
  270. begin
  271. result := Button.DropDownArrowSize;
  272. end;
  273. function TBCComboBox.GetArrowWidth: integer;
  274. begin
  275. result := Button.DropDownWidth;
  276. end;
  277. function TBCComboBox.GetButtonHint: TTranslateString;
  278. begin
  279. result := FButton.Hint;
  280. end;
  281. function TBCComboBox.GetButtonShowHint: Boolean;
  282. begin
  283. result := FButton.ShowHint;
  284. end;
  285. function TBCComboBox.GetGlobalOpacity: byte;
  286. begin
  287. result := Button.GlobalOpacity;
  288. end;
  289. function TBCComboBox.GetItemText: string;
  290. begin
  291. if ItemIndex<>-1 then
  292. result := Items[ItemIndex]
  293. else
  294. result := '';
  295. end;
  296. function TBCComboBox.GetDropDownColor: TColor;
  297. begin
  298. if Assigned(FListBox) then
  299. result := FListBox.Color
  300. else result := FDropDownColor;
  301. end;
  302. function TBCComboBox.GetItemIndex: integer;
  303. begin
  304. if Assigned(FListBox) then
  305. result := FListBox.ItemIndex
  306. else
  307. begin
  308. if FItemIndex >= Items.Count then
  309. FItemIndex := -1;
  310. result := FItemIndex;
  311. end;
  312. end;
  313. function TBCComboBox.GetItems: TStrings;
  314. begin
  315. if Assigned(FListBox) then
  316. Result := FListBox.Items
  317. else Result := FItems;
  318. end;
  319. function TBCComboBox.GetMemoryUsage: TBCButtonMemoryUsage;
  320. begin
  321. result := Button.MemoryUsage;
  322. end;
  323. function TBCComboBox.GetOnDrawSelectedItem: TOnAfterRenderBCButton;
  324. begin
  325. result := FOnDrawSelectedItem;
  326. end;
  327. function TBCComboBox.GetRounding: TBCRounding;
  328. begin
  329. result := Button.Rounding;
  330. end;
  331. function TBCComboBox.GetStateClicked: TBCButtonState;
  332. begin
  333. result := Button.StateClicked;
  334. end;
  335. function TBCComboBox.GetStateHover: TBCButtonState;
  336. begin
  337. result := Button.StateHover;
  338. end;
  339. function TBCComboBox.GetStateNormal: TBCButtonState;
  340. begin
  341. result := Button.StateNormal;
  342. end;
  343. function TBCComboBox.GetStaticButton: boolean;
  344. begin
  345. result := Button.StaticButton;
  346. end;
  347. procedure TBCComboBox.ListBoxKeyDown(Sender: TObject; var Key: Word;
  348. Shift: TShiftState);
  349. begin
  350. if (Key = VK_RETURN) or (Key = VK_ESCAPE) then
  351. begin
  352. ButtonClick(nil);
  353. Key := 0;
  354. end;
  355. end;
  356. procedure TBCComboBox.ListBoxMouseUp(Sender: TObject; Button: TMouseButton;
  357. Shift: TShiftState; X, Y: Integer);
  358. begin
  359. CloseDropDown;
  360. end;
  361. procedure TBCComboBox.ListBoxMouseLeave(Sender: TObject);
  362. begin
  363. FHoverItem := -1;
  364. FListBox.Repaint;
  365. end;
  366. procedure TBCComboBox.ListBoxMouseMove(Sender: TObject; Shift: TShiftState; X,
  367. Y: Integer);
  368. var
  369. TempItem: integer;
  370. begin
  371. TempItem := FListBox.ItemAtPos(Point(x, y), True);
  372. if TempItem <> FHoverItem then
  373. begin
  374. FHoverItem := TempItem;
  375. if (FHoverItem<>-1) and ([ssLeft,ssRight]*Shift <> []) then
  376. FListBox.ItemIndex := FHoverItem;
  377. FListBox.Repaint;
  378. end;
  379. end;
  380. procedure TBCComboBox.ListBoxSelectionChange(Sender: TObject; User: boolean);
  381. begin
  382. Button.Caption := GetItemText;
  383. if User and Assigned(FOnChange) then FOnChange(self);
  384. {$IFDEF WINDOWS}
  385. // ensure redrawing of all items
  386. (Sender as TListBox).Invalidate;
  387. {$ENDIF}
  388. end;
  389. procedure TBCComboBox.ListBoxDrawItem(Control: TWinControl; Index: Integer;
  390. ARect: TRect; State: TOwnerDrawState);
  391. var
  392. aCanvas: TCanvas;
  393. padding: integer;
  394. r: TRect;
  395. begin
  396. if Index = 0 then
  397. begin
  398. padding := max(0, ARect.Height - TListBox(Control).ItemHeight);
  399. if padding <> FItemPadding then
  400. begin
  401. // next time, use a better adjustment
  402. FItemPadding := padding;
  403. end;
  404. end;
  405. if Assigned(FOnDrawItem) then
  406. begin
  407. FDrawingDropDown := true;
  408. Exclude(State, odSelected);
  409. if Index = HoverItem then Include(State, odSelected);
  410. if Index = ItemIndex then Include(State, odChecked);
  411. try
  412. r := ARect;
  413. {$IFDEF DARWIN}
  414. // on MacOS the vertical scrollbar is over the content
  415. Dec(r.Right, 8);
  416. {$ENDIF}
  417. FOnDrawItem(Control, Index, r, State);
  418. finally
  419. FDrawingDropDown := false;
  420. end;
  421. exit;
  422. end;
  423. aCanvas := TListBox(Control).Canvas;
  424. aCanvas.Pen.Style := psClear;
  425. {$IFDEF DARWIN}
  426. // paint top and bottom margin on MacOS
  427. aCanvas.Brush.Color := DropDownColor;
  428. if Index = 0 then
  429. begin
  430. r := ARect;
  431. r.Bottom := r.Top;
  432. dec(r.Top, 10);
  433. aCanvas.FillRect(r);
  434. end;
  435. if Index = TListBox(Control).Count-1 then
  436. begin
  437. r := ARect;
  438. r.Top := r.Bottom;
  439. inc(r.Bottom, 10);
  440. aCanvas.FillRect(r);
  441. end;
  442. {$ENDIF}
  443. if Index = HoverItem then
  444. begin
  445. aCanvas.Brush.Color := DropDownHighlight;
  446. aCanvas.Font.Color := DropDownFontHighlight;
  447. end
  448. else
  449. begin
  450. aCanvas.Brush.Color := DropDownColor;
  451. aCanvas.Font.Color := DropDownFontColor;
  452. end;
  453. aCanvas.FillRect(ARect);
  454. aCanvas.TextRect(ARect, ARect.Left+4, ARect.Top +
  455. (ARect.Height - aCanvas.GetTextHeight(Items[Index])) div 2,
  456. Items[Index]);
  457. end;
  458. procedure TBCComboBox.OnAfterRenderButton(Sender: TObject;
  459. const ABGRA: TBGRABitmap; AState: TBCButtonState; ARect: TRect);
  460. var
  461. FocusMargin: integer;
  462. begin
  463. if Assigned(FOnDrawSelectedItem) then
  464. FOnDrawSelectedItem(self, ABGRA, AState, ARect);
  465. if Focused then
  466. begin
  467. FocusMargin := round(2 * FButton.CanvasScale);
  468. ABGRA.RoundRectAntialias(
  469. ARect.Left + FocusMargin,
  470. ARect.Top + FocusMargin,
  471. ARect.Right - FocusMargin - 1,
  472. ARect.Bottom - FocusMargin - 1,
  473. Max(0, FButton.Rounding.RoundX - FocusMargin),
  474. Max(0, FButton.Rounding.RoundY - FocusMargin),
  475. ColorToBGRA(FFocusBorderColor, FFocusBorderOpacity),
  476. FButton.CanvasScale);
  477. end;
  478. end;
  479. procedure TBCComboBox.OnTimerCheckFormHide(Sender: TObject);
  480. {$ifdef WINDOWS}
  481. function IsDropDownOnTop: boolean;
  482. begin
  483. result := Assigned(FForm) and (GetForegroundWindow = FForm.Handle);
  484. end;
  485. {$endif}
  486. procedure DoClose;
  487. begin
  488. CloseDropDown;
  489. FQueryDropDownHide := false;
  490. FTimerCheckFormHide.Enabled := false;
  491. end;
  492. begin
  493. {$IFNDEF LCLgtk3}
  494. if not Application.Active then FQueryDropDownHide:= true;
  495. {$ENDIF}
  496. if not FQueryDropDownHide then exit;
  497. if Assigned(FPanel) then DoClose;
  498. {$IFDEF WINDOWS}
  499. If Assigned(FForm) and FForm.Visible then DoClose;
  500. {$ELSE}
  501. If Assigned(FForm) and not FForm.Active then DoClose;
  502. {$ENDIF}
  503. end;
  504. procedure TBCComboBox.SetArrowFlip(AValue: boolean);
  505. begin
  506. Button.FlipArrow:= AValue;
  507. end;
  508. procedure TBCComboBox.SetArrowSize(AValue: integer);
  509. begin
  510. Button.DropDownArrowSize:= AValue;
  511. end;
  512. procedure TBCComboBox.SetArrowWidth(AValue: integer);
  513. begin
  514. Button.DropDownWidth:= AValue;
  515. end;
  516. procedure TBCComboBox.SetButtonHint(const AValue: TTranslateString);
  517. begin
  518. FButton.Hint := AValue;
  519. end;
  520. procedure TBCComboBox.SetButtonShowHint(AValue: Boolean);
  521. begin
  522. FButton.ShowHint := AValue;
  523. end;
  524. procedure TBCComboBox.SetCanvasScaleMode(AValue: TBCCanvasScaleMode);
  525. begin
  526. if FCanvasScaleMode=AValue then Exit;
  527. FCanvasScaleMode:=AValue;
  528. UpdateButtonCanvasScaleMode;
  529. end;
  530. procedure TBCComboBox.SetDropDownColor(AValue: TColor);
  531. begin
  532. if Assigned(FListBox) then
  533. FListBox.Color := AValue
  534. else FDropDownColor:= AValue;
  535. end;
  536. procedure TBCComboBox.SetGlobalOpacity(AValue: byte);
  537. begin
  538. Button.GlobalOpacity := AValue;
  539. end;
  540. procedure TBCComboBox.SetItemIndex(AValue: integer);
  541. begin
  542. if Assigned(FListBox) then
  543. FListBox.ItemIndex := AValue
  544. else
  545. begin
  546. if AValue <> FItemIndex then
  547. begin
  548. FItemIndex := AValue;
  549. Button.Caption := GetItemText;
  550. end;
  551. end;
  552. end;
  553. procedure TBCComboBox.SetItems(AValue: TStrings);
  554. begin
  555. if Assigned(FListBox) then
  556. FListBox.Items.Assign(AValue)
  557. else FItems.Assign(AValue);
  558. end;
  559. procedure TBCComboBox.SetMemoryUsage(AValue: TBCButtonMemoryUsage);
  560. begin
  561. Button.MemoryUsage := AValue;
  562. end;
  563. procedure TBCComboBox.SetOnDrawSelectedItem(AValue: TOnAfterRenderBCButton);
  564. begin
  565. if @FOnDrawSelectedItem = @AValue then Exit;
  566. FOnDrawSelectedItem:= AValue;
  567. FButton.ShowCaption := not Assigned(AValue);
  568. UpdateButtonCanvasScaleMode;
  569. end;
  570. procedure TBCComboBox.SetRounding(AValue: TBCRounding);
  571. begin
  572. Button.Rounding := AValue;
  573. end;
  574. procedure TBCComboBox.SetStateClicked(AValue: TBCButtonState);
  575. begin
  576. Button.StateClicked := AValue;
  577. end;
  578. procedure TBCComboBox.SetStateHover(AValue: TBCButtonState);
  579. begin
  580. Button.StateHover := AValue;
  581. end;
  582. procedure TBCComboBox.SetStateNormal(AValue: TBCButtonState);
  583. begin
  584. Button.StateNormal := AValue;
  585. end;
  586. procedure TBCComboBox.SetStaticButton(AValue: boolean);
  587. begin
  588. Button.StaticButton:= AValue;
  589. end;
  590. function TBCComboBox.GetOnButtonMouseDown: TMouseEvent;
  591. begin
  592. result := FButton.OnMouseDown;
  593. end;
  594. function TBCComboBox.GetOnButtonMouseEnter: TNotifyEvent;
  595. begin
  596. result := FButton.OnMouseEnter;
  597. end;
  598. function TBCComboBox.GetOnButtonMouseLeave: TNotifyEvent;
  599. begin
  600. result := FButton.OnMouseLeave;
  601. end;
  602. function TBCComboBox.GetOnButtonMouseMove: TMouseMoveEvent;
  603. begin
  604. result := FButton.OnMouseMove;
  605. end;
  606. function TBCComboBox.GetOnButtonMouseUp: TMouseEvent;
  607. begin
  608. result := FButton.OnMouseUp;
  609. end;
  610. function TBCComboBox.GetOnButtonMouseWheel: TMouseWheelEvent;
  611. begin
  612. result := FButton.OnMouseWheel;
  613. end;
  614. function TBCComboBox.GetOnButtonMouseWheelDown: TMouseWheelUpDownEvent;
  615. begin
  616. result := FButton.OnMouseWheelDown;
  617. end;
  618. function TBCComboBox.GetOnButtonMouseWheelUp: TMouseWheelUpDownEvent;
  619. begin
  620. result := FButton.OnMouseWheelUp;
  621. end;
  622. procedure TBCComboBox.SetOnButtonMouseDown(AValue: TMouseEvent);
  623. begin
  624. FButton.OnMouseDown := AValue;
  625. end;
  626. procedure TBCComboBox.SetOnButtonMouseEnter(AValue: TNotifyEvent);
  627. begin
  628. FButton.OnMouseEnter := AValue;
  629. end;
  630. procedure TBCComboBox.SetOnButtonMouseLeave(AValue: TNotifyEvent);
  631. begin
  632. FButton.OnMouseLeave := AValue;
  633. end;
  634. procedure TBCComboBox.SetOnButtonMouseMove(AValue: TMouseMoveEvent);
  635. begin
  636. FButton.OnMouseMove := AValue;
  637. end;
  638. procedure TBCComboBox.SetOnButtonMouseUp(AValue: TMouseEvent);
  639. begin
  640. FButton.OnMouseUp := AValue;
  641. end;
  642. procedure TBCComboBox.SetOnButtonMouseWheel(AValue: TMouseWheelEvent);
  643. begin
  644. FButton.OnMouseWheel := AValue;
  645. end;
  646. procedure TBCComboBox.SetOnButtonMouseWheelDown(AValue: TMouseWheelUpDownEvent);
  647. begin
  648. FButton.OnMouseWheelDown := AValue;
  649. end;
  650. procedure TBCComboBox.SetOnButtonMouseWheelUp(AValue: TMouseWheelUpDownEvent);
  651. begin
  652. FButton.OnMouseWheelUp := AValue;
  653. end;
  654. function TBCComboBox.GetStyleExtension: String;
  655. begin
  656. result := 'bccombo';
  657. end;
  658. procedure TBCComboBox.WMSetFocus(var Message: TLMSetFocus);
  659. begin
  660. UpdateFocus(True);
  661. end;
  662. procedure TBCComboBox.WMKillFocus(var Message: TLMKillFocus);
  663. begin
  664. if Message.FocusedWnd <> Handle then
  665. UpdateFocus(False);
  666. end;
  667. procedure TBCComboBox.UpdateFocus(AFocused: boolean);
  668. var
  669. lForm: TCustomForm;
  670. begin
  671. lForm := GetParentForm(Self);
  672. if lForm = nil then Exit;
  673. {$IFDEF FPC}//#
  674. if AFocused then
  675. ActiveDefaultControlChanged(lForm.ActiveControl)
  676. else
  677. ActiveDefaultControlChanged(nil);
  678. {$ENDIF}
  679. FButton.UpdateControl;
  680. Invalidate;
  681. end;
  682. procedure TBCComboBox.KeyDown(var Key: Word; Shift: TShiftState);
  683. begin
  684. if Key = VK_RETURN then
  685. begin
  686. ButtonClick(nil);
  687. Key := 0;
  688. end
  689. else if Key = VK_DOWN then
  690. begin
  691. if ItemIndex + 1 < Items.Count then
  692. begin
  693. ItemIndex := ItemIndex + 1;
  694. Button.Caption := GetItemText;
  695. if Assigned(FOnChange) then
  696. FOnChange(Self);
  697. end;
  698. Key := 0;
  699. end
  700. else if Key = VK_UP then
  701. begin
  702. if ItemIndex - 1 >= 0 then
  703. begin
  704. ItemIndex := ItemIndex - 1;
  705. Button.Caption := GetItemText;
  706. if Assigned(FOnChange) then
  707. FOnChange(Self);
  708. end;
  709. Key := 0;
  710. end;
  711. end;
  712. procedure TBCComboBox.UTF8KeyPress(var UTF8Key: TUTF8Char);
  713. var
  714. i: integer;
  715. begin
  716. for i:=0 to Items.Count-1 do
  717. begin
  718. if (Items[i] <> '') and Items[i].ToLower.StartsWith(LowerCase(UTF8Key)) then
  719. begin
  720. if ItemIndex <> i then
  721. begin
  722. ItemIndex := i;
  723. Button.Caption := GetItemText;
  724. if Assigned(FOnChange) then
  725. FOnChange(Self);
  726. break;
  727. end;
  728. end;
  729. end;
  730. end;
  731. procedure TBCComboBox.CreateDropDown;
  732. begin
  733. {$IFDEF LINUX}
  734. // ensure correct window placement on Linux
  735. if Assigned(FForm) and not FForm.Visible then FreeDropDown;
  736. {$ENDIF}
  737. if (FForm = nil) and not DropDownOnSameForm then
  738. begin
  739. FForm := TForm.Create(Self);
  740. FForm.Visible := False;
  741. FForm.ShowInTaskBar:= stNever;
  742. FForm.BorderStyle := bsNone;
  743. FForm.OnDeactivate:= FormDeactivate;
  744. FForm.OnHide:=FormHide;
  745. FForm.FormStyle := fsStayOnTop;
  746. end else
  747. if Assigned(FForm) and DropDownOnSameForm then
  748. begin
  749. If Assigned(FListBox) and (FListBox.Parent = FForm) then FListBox.Parent := nil;
  750. FreeAndNil(FForm);
  751. end;
  752. if (FPanel = nil) and DropDownOnSameForm then
  753. begin
  754. FPanel := TPanel.Create(Self);
  755. FPanel.Visible := False;
  756. FPanel.BevelInner := bvNone;
  757. FPanel.BevelOuter := bvNone;
  758. end else
  759. if Assigned(FPanel) and not DropDownOnSameForm then
  760. begin
  761. If Assigned(FListBox) and (FListBox.Parent = FPanel) then FListBox.Parent := nil;
  762. FreeAndNil(FPanel);
  763. end;
  764. if FListBox = nil then
  765. begin
  766. FListBox := TListBox.Create(self);
  767. FListBox.BorderStyle := bsNone;
  768. FListBox.OnMouseLeave:=ListBoxMouseLeave;
  769. FListBox.OnMouseMove:=ListBoxMouseMove;
  770. FListBox.OnMouseUp:= ListBoxMouseUp;
  771. FListBox.Style := lbOwnerDrawFixed;
  772. FListBox.OnDrawItem:= ListBoxDrawItem;
  773. FListBox.Options := []; // do not draw focus rect
  774. FListBox.OnKeyDown:=ListBoxKeyDown;
  775. if Assigned(FItems) then
  776. begin
  777. FListBox.Items.Assign(FItems);
  778. FreeAndNil(FItems);
  779. end;
  780. FListBox.ItemIndex := FItemIndex;
  781. FListBox.Color := FDropDownColor;
  782. FListBox.OnSelectionChange := ListBoxSelectionChange;
  783. end;
  784. if DropDownOnSameForm then
  785. begin
  786. FListBox.Parent := FPanel;
  787. FListBox.OnExit:= PanelExit;
  788. end
  789. else
  790. begin
  791. FListBox.Parent := FForm;
  792. FListBox.OnExit:= nil;
  793. end;
  794. end;
  795. procedure TBCComboBox.FreeDropDown;
  796. begin
  797. if Assigned(FListBox) then
  798. begin
  799. if FListBox.LCLRefCount > 0 then exit;
  800. if FItems = nil then
  801. FItems := TStringList.Create;
  802. FItems.Assign(FListBox.Items);
  803. FItemIndex := FListBox.ItemIndex;
  804. FDropDownColor:= FListBox.Color;
  805. FreeAndNil(FListBox);
  806. end;
  807. FreeAndNil(FForm);
  808. FreeAndNil(FPanel);
  809. end;
  810. function TBCComboBox.CloseDropDown: boolean;
  811. begin
  812. if Assigned(FForm) and FForm.Visible then
  813. begin
  814. FForm.Close;
  815. result := true;
  816. end
  817. else if Assigned(FPanel) and FPanel.Visible then
  818. begin
  819. FPanel.Hide;
  820. FDropDownHideDate := Now;
  821. result := true;
  822. end else
  823. begin
  824. result := false;
  825. end;
  826. FQueryDropDownHide := true;
  827. end;
  828. procedure TBCComboBox.PrepareListBoxForDropDown;
  829. var
  830. h: Integer;
  831. begin
  832. FListBox.Font.Name := Button.StateNormal.FontEx.Name;
  833. FListBox.Font.Style := Button.StateNormal.FontEx.Style;
  834. FListBox.Font.Height := FontEmHeightSign*Button.StateNormal.FontEx.Height;
  835. if Assigned(FOnDrawItem) and (FItemHeight <> 0) then
  836. h := FItemHeight else h := self.Canvas.GetTextHeight('Hg');
  837. {$IF defined(LCLgtk2)}
  838. inc(h,2);
  839. {$ELSEIF defined(LCLgtk3)}
  840. inc(h,4);
  841. {$ELSE}
  842. inc(h,6); // default
  843. {$ENDIF}
  844. FListBox.ItemHeight := h;
  845. AutosizeListBox;
  846. end;
  847. procedure TBCComboBox.AutosizeListBox;
  848. var
  849. s: TSize;
  850. begin
  851. s := TSize.Create(FButton.Width,
  852. (FListBox.ItemHeight + FItemPadding)*min(Items.Count, FDropDownCount)
  853. + 2*FDropDownBorderSize);
  854. {$IFDEF DARWIN}
  855. // on MacOS there is a top and bottom margin of both 10
  856. if Items.Count <= FDropDownCount then
  857. inc(s.cy, 20)
  858. else
  859. // if overflow, keep only either top or bottom margin
  860. inc(s.cy, 10);
  861. {$ENDIF}
  862. FListBox.SetBounds(FDropDownBorderSize,FDropDownBorderSize,
  863. s.cx - 2*FDropDownBorderSize,
  864. s.cy - 2*FDropDownBorderSize);
  865. end;
  866. procedure TBCComboBox.AdaptDropDownContainerSize;
  867. var w, h: integer;
  868. begin
  869. if not Assigned(FListBox) then exit;
  870. w := FListBox.Width + 2*FDropDownBorderSize;
  871. h := FListBox.Height + 2*FDropDownBorderSize;
  872. if Assigned(FPanel) then
  873. begin
  874. FPanel.ClientWidth := w;
  875. FPanel.ClientHeight := h;
  876. end;
  877. if Assigned(FForm) {$IFDEF LCLgtk2}and not FForm.HandleAllocated{$ENDIF} then
  878. begin
  879. FForm.SetBounds(FForm.Left, FForm.Top,
  880. w + FForm.Width - FForm.ClientWidth,
  881. h + FForm.Height - FForm.ClientHeight);
  882. end;
  883. end;
  884. function TBCComboBox.GetListBox: TListBox;
  885. begin
  886. CreateDropDown;
  887. result := FListBox;
  888. end;
  889. procedure TBCComboBox.UpdateButtonCanvasScaleMode;
  890. begin
  891. if (CanvasScaleMode = csmFullResolution) or
  892. ((CanvasScaleMode = csmAuto) and not Assigned(FOnDrawSelectedItem)) then
  893. FButton.CanvasScaleMode:= csmFullResolution
  894. else FButton.CanvasScaleMode:= csmScaleBitmap;
  895. end;
  896. constructor TBCComboBox.Create(AOwner: TComponent);
  897. begin
  898. inherited Create(AOwner);
  899. FButton := TBCButton.Create(Self);
  900. FButton.Align := alClient;
  901. FButton.Parent := Self;
  902. FButton.OnClick := ButtonClick;
  903. FButton.DropDownArrow := True;
  904. FButton.OnAfterRenderBCButton := OnAfterRenderButton;
  905. FFocusBorderColor := clBlack;
  906. FFocusBorderOpacity := 0;
  907. UpdateButtonCanvasScaleMode;
  908. FItems := TStringList.Create;
  909. FHoverItem := -1;
  910. FItemIndex := -1;
  911. DropDownBorderSize := 1;
  912. DropDownColor := clWindow;
  913. DropDownBorderColor := clWindowText;
  914. DropDownCount := 8;
  915. DropDownFontColor := clWindowText;
  916. DropDownHighlight := clHighlight;
  917. DropDownFontHighlight := clHighlightText;
  918. FTimerCheckFormHide := TTimer.Create(self);
  919. {$IFDEF LCLgtk3}
  920. FTimerCheckFormHide.Enabled := false;
  921. {$ENDIF}
  922. FTimerCheckFormHide.Interval:= 30;
  923. FTimerCheckFormHide.OnTimer:= OnTimerCheckFormHide;
  924. {$IFDEF WINDOWS}
  925. FItemPadding:= 0;
  926. {$ELSE}
  927. {$IFDEF LCLgtk2}
  928. FItemPadding:= 4;
  929. {$ELSE}
  930. FItemPadding:= 0; // default
  931. {$ENDIF}
  932. {$ENDIF}
  933. end;
  934. destructor TBCComboBox.Destroy;
  935. begin
  936. FreeAndNil(FItems);
  937. inherited Destroy;
  938. end;
  939. procedure TBCComboBox.Assign(Source: TPersistent);
  940. var
  941. src: TBCComboBox;
  942. begin
  943. if Source is TBCComboBox then
  944. begin
  945. src := TBCComboBox(Source);
  946. Button.Assign(src.Button);
  947. Items.Assign(src.Items);
  948. ItemIndex := src.ItemIndex;
  949. DropDownBorderColor := src.DropDownBorderColor;
  950. DropDownBorderSize := src.DropDownBorderSize;
  951. DropDownColor := src.DropDownColor;
  952. DropDownFontColor := src.DropDownFontColor;
  953. DropDownCount := src.DropDownCount;
  954. DropDownHighlight := src.DropDownHighlight;
  955. DropDownFontHighlight := src.DropDownFontHighlight;
  956. end else
  957. inherited Assign(Source);
  958. end;
  959. procedure TBCComboBox.Clear;
  960. begin
  961. Items.Clear;
  962. end;
  963. end.