PathLabel.pas 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. unit PathLabel;
  2. interface
  3. {$WARN UNIT_PLATFORM OFF}
  4. uses
  5. Messages, StdCtrls, Controls, Classes, Forms, Windows, Graphics;
  6. type
  7. TCustomPathLabel = class;
  8. TPathLabelGetStatusEvent = procedure(Sender: TCustomPathLabel; var Active: Boolean) of object;
  9. TPathLabelPathClickEvent = procedure(Sender: TCustomPathLabel; Path: string) of object;
  10. TCustomPathLabel = class(TCustomLabel)
  11. private
  12. FColors: array[0..5] of TColor;
  13. FIndentHorizontal: Integer;
  14. FIndentVertical: Integer;
  15. FUnixPath: Boolean;
  16. FOnGetStatus: TPathLabelGetStatusEvent;
  17. FOnPathClick: TPathLabelPathClickEvent;
  18. FOnMaskClick: TNotifyEvent;
  19. FDisplayPath: string;
  20. FDisplayHotTrack: string;
  21. FDisplayMask: string;
  22. FHotTrack: Boolean;
  23. FMouseInView: Boolean;
  24. FIsActive: Boolean;
  25. FIsEnabled: Boolean;
  26. FMask: string;
  27. FAutoSizeVertical: Boolean;
  28. procedure CMHintShow(var Message: TMessage); message CM_HINTSHOW;
  29. procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
  30. procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
  31. function GetColors(Index: Integer): TColor;
  32. procedure SetColors(Index: Integer; Value: TColor);
  33. procedure SetIndentHorizontal(AIndent: Integer);
  34. procedure SetIndentVertical(AIndent: Integer);
  35. procedure SetUnixPath(AUnixPath: Boolean);
  36. procedure SetMask(Value: string);
  37. procedure SetAutoSizeVertical(Value: Boolean);
  38. procedure SetFocusControl(Value: TWinControl);
  39. function GetFocusControl: TWinControl;
  40. function CalculateAutoHotTrackColor(C: TColor): TColor;
  41. procedure CalculateAutoHotTrackColors;
  42. function CalculateAutoHotTrackColorComponent(C: Byte; Bright: Boolean): Byte;
  43. function UseHotTrack: Boolean;
  44. protected
  45. procedure AdjustBounds; override;
  46. procedure Click; override;
  47. procedure DoDrawTextIntern(var Rect: TRect; Flags: Longint; S: string);
  48. procedure DoDrawText(var Rect: TRect; Flags: Longint); override;
  49. procedure Notification(AComponent: TComponent;
  50. Operation: TOperation); override;
  51. procedure Paint; override;
  52. function IsActive: Boolean;
  53. function TrackingActive: Boolean;
  54. function HotTrackPath: string;
  55. function GetSeparator: Char;
  56. procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override;
  57. procedure DoPathClick(Path: string); virtual;
  58. procedure DoMaskClick;
  59. procedure DblClick; override;
  60. procedure DoContextPopup(MousePos: TPoint; var Handled: Boolean); override;
  61. procedure CMStyleChanged(var Message: TMessage); message CM_STYLECHANGED;
  62. public
  63. constructor Create(AnOwner: TComponent); override;
  64. procedure UpdateStatus;
  65. function UseRightToLeftAlignment: Boolean; override;
  66. property ActiveColor: TColor index 1 read GetColors write SetColors
  67. default clActiveCaption;
  68. property ActiveTextColor: TColor index 3 read GetColors write SetColors
  69. default clCaptionText;
  70. property UnixPath: Boolean read FUnixPath write SetUnixPath default False;
  71. property IndentHorizontal: Integer read FIndentHorizontal
  72. write SetIndentHorizontal default 5;
  73. property IndentVertical: Integer read FIndentVertical
  74. write SetIndentVertical default 1;
  75. property InactiveColor: TColor index 0 read GetColors write SetColors
  76. default clInactiveCaption;
  77. property InactiveTextColor: TColor index 2 read GetColors write SetColors
  78. default clInactiveCaptionText;
  79. property OnGetStatus: TPathLabelGetStatusEvent read FOnGetStatus write FOnGetStatus;
  80. property OnPathClick: TPathLabelPathClickEvent read FOnPathClick write FOnPathClick;
  81. property OnMaskClick: TNotifyEvent read FOnMaskClick write FOnMaskClick;
  82. property HotTrack: Boolean read FHotTrack write FHotTrack default False;
  83. property Mask: string read FMask write SetMask;
  84. property AutoSizeVertical: Boolean read FAutoSizeVertical write SetAutoSizeVertical default False;
  85. property FocusControl: TWinControl read GetFocusControl write SetFocusControl;
  86. property Caption;
  87. property Hint stored False;
  88. property Align default alTop;
  89. end;
  90. type
  91. TPathLabel = class(TCustomPathLabel)
  92. published
  93. property ActiveColor;
  94. property ActiveTextColor;
  95. property UnixPath;
  96. property IndentHorizontal;
  97. property IndentVertical;
  98. property InactiveColor;
  99. property InactiveTextColor;
  100. property AutoSizeVertical;
  101. property HotTrack;
  102. property OnGetStatus;
  103. property OnPathClick;
  104. property OnMaskClick;
  105. property Align;
  106. property Alignment;
  107. property Anchors;
  108. property AutoSize;
  109. property BiDiMode;
  110. property Constraints;
  111. property DragCursor;
  112. property DragKind;
  113. property DragMode;
  114. property Enabled;
  115. property Font;
  116. property ParentBiDiMode;
  117. property ParentFont;
  118. property PopupMenu;
  119. property Transparent;
  120. property Visible;
  121. property OnClick;
  122. property OnDblClick;
  123. property OnDragDrop;
  124. property OnDragOver;
  125. property OnEndDock;
  126. property OnEndDrag;
  127. property OnMouseDown;
  128. property OnMouseMove;
  129. property OnMouseUp;
  130. property OnStartDock;
  131. property OnStartDrag;
  132. end;
  133. procedure Register;
  134. implementation
  135. uses
  136. { SysUtils must overload deprecated FileCtrl (implements MinimizeName) }
  137. FileCtrl, SysUtils, Math, PasTools;
  138. const
  139. // magic value
  140. HotTrackMask: string = #1;
  141. procedure Register;
  142. begin
  143. RegisterComponents('Martin', [TPathLabel]);
  144. end;
  145. { TCustomPathLabel }
  146. constructor TCustomPathLabel.Create(AnOwner: TComponent);
  147. begin
  148. inherited Create(AnOwner);
  149. WordWrap := False;
  150. Align := alTop;
  151. ShowAccelChar := False;
  152. FIndentHorizontal := 5;
  153. FIndentVertical := 1;
  154. FUnixPath := False;
  155. FHotTrack := False;
  156. FColors[0] := clInactiveCaption;
  157. FColors[1] := clActiveCaption;
  158. FColors[2] := clInactiveCaptionText;
  159. FColors[3] := clCaptionText;
  160. FIsEnabled := True;
  161. CalculateAutoHotTrackColors;
  162. end;
  163. procedure TCustomPathLabel.CMHintShow(var Message: TMessage);
  164. begin
  165. with TCMHintShow(Message).HintInfo^ do
  166. begin
  167. HintPos.X := ClientOrigin.X + IndentHorizontal;
  168. HintPos.Y := ClientOrigin.Y + IndentVertical;
  169. if UseHotTrack then Inc(HintPos.Y, Height);
  170. end;
  171. end; { CMHintShow }
  172. procedure TCustomPathLabel.Click;
  173. var
  174. HotPath: string;
  175. RemainingPath: string;
  176. begin
  177. if FIsEnabled then
  178. begin
  179. HotPath := HotTrackPath;
  180. if HotPath <> '' then
  181. begin
  182. if (Copy(HotPath, Length(HotPath), 1) = GetSeparator) and
  183. (HotPath <> GetSeparator) then
  184. SetLength(HotPath, Length(HotPath) - 1);
  185. if HotPath = HotTrackMask then DoMaskClick
  186. else
  187. if FDisplayPath = Caption then DoPathClick(HotPath)
  188. else
  189. begin
  190. // Displayed path is shortened.
  191. // The below is based on knowledge of MinimizeName algorithm
  192. RemainingPath := Copy(FDisplayPath, Length(HotPath) + 1,
  193. Length(FDisplayPath) - Length(HotPath));
  194. // Contrary to stripping separator from HotPath above, we strip it even from bare /,
  195. // as the Caption does not include it either
  196. if Copy(RemainingPath, Length(RemainingPath), 1) = GetSeparator then
  197. SetLength(RemainingPath, Length(RemainingPath) - 1);
  198. // Part from ellipsis (inclusive)
  199. if RemainingPath = Copy(Caption, Length(Caption) - Length(RemainingPath) + 1,
  200. Length(RemainingPath)) then
  201. begin
  202. DoPathClick(Copy(Caption, 1, Length(Caption) - Length(RemainingPath)));
  203. end
  204. else
  205. // Part before ellipsis
  206. if HotPath = Copy(Caption, 1, Length(HotPath)) then
  207. begin
  208. DoPathClick(HotPath);
  209. end
  210. else Assert(False);
  211. end;
  212. end;
  213. if Assigned(FocusControl) then FocusControl.SetFocus;
  214. inherited;
  215. end;
  216. end; { Click }
  217. procedure TCustomPathLabel.SetUnixPath(AUnixPath: Boolean);
  218. begin
  219. if FUnixPath <> AUnixPath then
  220. begin
  221. FUnixPath := AUnixPath;
  222. AdjustBounds;
  223. Invalidate;
  224. end;
  225. end;
  226. procedure TCustomPathLabel.SetMask(Value: string);
  227. begin
  228. if FMask <> Value then
  229. begin
  230. FMask := Value;
  231. AdjustBounds;
  232. Invalidate;
  233. end;
  234. end;
  235. procedure TCustomPathLabel.SetColors(Index: integer; Value: TColor);
  236. begin
  237. Assert(Index in [0..3]);
  238. if FColors[Index] <> Value then
  239. begin
  240. FColors[Index] := Value;
  241. CalculateAutoHotTrackColors;
  242. UpdateStatus;
  243. end;
  244. end; { SetColors }
  245. // taken from PngImageListEditor
  246. const
  247. WeightR: single = 0.764706;
  248. WeightG: single = 1.52941;
  249. WeightB: single = 0.254902;
  250. function ColorDistance(C1, C2: Integer): Single;
  251. var
  252. DR, DG, DB: Integer;
  253. begin
  254. DR := (C1 and $FF) - (C2 and $FF);
  255. Result := Sqr(DR * WeightR);
  256. DG := (C1 shr 8 and $FF) - (C2 shr 8 and $FF);
  257. Result := Result + Sqr(DG * WeightG);
  258. DB := (C1 shr 16) - (C2 shr 16);
  259. Result := Result + Sqr(DB * WeightB);
  260. Result := Sqrt(Result);
  261. end;
  262. function GetAdjustedThreshold(BkgndIntensity, Threshold: Single): Single;
  263. begin
  264. if BkgndIntensity < 220 then
  265. Result := (2 - BkgndIntensity / 220) * Threshold
  266. else
  267. Result := Threshold;
  268. end;
  269. function IsContrastEnough(AColor, ABkgndColor: Integer; DoAdjustThreshold: Boolean; Threshold: Single): Boolean;
  270. begin
  271. if DoAdjustThreshold then
  272. Threshold := GetAdjustedThreshold(ColorDistance(ABkgndColor, $000000),
  273. Threshold);
  274. Result := ColorDistance(ABkgndColor, AColor) > Threshold;
  275. end;
  276. procedure AdjustContrast(var AColor: Integer; ABkgndColor: Integer; Threshold: Single);
  277. var
  278. X, Y, Z: Single;
  279. R, G, B: Single;
  280. RR, GG, BB: Integer;
  281. I1, I2, S, Q, W: Single;
  282. DoInvert: Boolean;
  283. begin
  284. I1 := ColorDistance(AColor, $000000);
  285. I2 := ColorDistance(ABkgndColor, $000000);
  286. Threshold := GetAdjustedThreshold(I2, Threshold);
  287. if I1 > I2 then
  288. DoInvert := I2 < 442 - Threshold
  289. else
  290. DoInvert := I2 < Threshold;
  291. X := (ABkgndColor and $FF) * WeightR;
  292. Y := (ABkgndColor shr 8 and $FF) * WeightG;
  293. Z := (ABkgndColor shr 16) * WeightB;
  294. R := (AColor and $FF) * WeightR;
  295. G := (AColor shr 8 and $FF) * WeightG;
  296. B := (AColor shr 16) * WeightB;
  297. if DoInvert then begin
  298. R := 195 - R;
  299. G := 390 - G;
  300. B := 65 - B;
  301. X := 195 - X;
  302. Y := 390 - Y;
  303. Z := 65 - Z;
  304. end;
  305. S := Sqrt(Sqr(B) + Sqr(G) + Sqr(R));
  306. if S < 0.01 then
  307. S := 0.01;
  308. Q := (R * X + G * Y + B * Z) / S;
  309. X := Q / S * R - X;
  310. Y := Q / S * G - Y;
  311. Z := Q / S * B - Z;
  312. W := Sqrt(Sqr(Threshold) - Sqr(X) - Sqr(Y) - Sqr(Z));
  313. R := (Q - W) * R / S;
  314. G := (Q - W) * G / S;
  315. B := (Q - W) * B / S;
  316. if DoInvert then begin
  317. R := 195 - R;
  318. G := 390 - G;
  319. B := 65 - B;
  320. end;
  321. if R < 0 then
  322. R := 0
  323. else if R > 195 then
  324. R := 195;
  325. if G < 0 then
  326. G := 0
  327. else if G > 390 then
  328. G := 390;
  329. if B < 0 then
  330. B := 0
  331. else if B > 65 then
  332. B := 65;
  333. RR := Trunc(R * (1 / WeightR) + 0.5);
  334. GG := Trunc(G * (1 / WeightG) + 0.5);
  335. BB := Trunc(B * (1 / WeightB) + 0.5);
  336. if RR > $FF then
  337. RR := $FF
  338. else if RR < 0 then
  339. RR := 0;
  340. if GG > $FF then
  341. GG := $FF
  342. else if GG < 0 then
  343. GG := 0;
  344. if BB > $FF then
  345. BB := $FF
  346. else if BB < 0 then
  347. BB := 0;
  348. AColor := (BB and $FF) shl 16 or (GG and $FF) shl 8 or (RR and $FF);
  349. end;
  350. procedure SetContrast(var Color: TColor; BkgndColor: TColor; Threshold: Integer);
  351. var
  352. T: Single;
  353. begin
  354. if Color < 0 then
  355. Color := GetSysColor(Color and $FF);
  356. if BkgndColor < 0 then
  357. BkgndColor := GetSysColor(BkgndColor and $FF);
  358. T := Threshold;
  359. if not IsContrastEnough(Color, BkgndColor, True, T) then
  360. AdjustContrast(Integer(Color), BkgndColor, T);
  361. end;
  362. function TCustomPathLabel.CalculateAutoHotTrackColorComponent(C: Byte; Bright: Boolean): Byte;
  363. var
  364. Delta: Byte;
  365. begin
  366. Delta := Max(Round(C * 0.3), 80);
  367. if Bright then
  368. Result := Byte(Max(Integer(C) - Delta, 0))
  369. else
  370. Result := Byte(Min(C + Delta, 255));
  371. end;
  372. function TCustomPathLabel.CalculateAutoHotTrackColor(C: TColor): TColor;
  373. var
  374. R, G, B: Byte;
  375. Bright: Boolean;
  376. begin
  377. C := ColorToRGB(C);
  378. R := GetRValue(C);
  379. G := GetGValue(C);
  380. B := GetBValue(C);
  381. Bright := (R + G + B) > (256 / 2 * 3);
  382. R := CalculateAutoHotTrackColorComponent(R, Bright);
  383. G := CalculateAutoHotTrackColorComponent(G, Bright);
  384. B := CalculateAutoHotTrackColorComponent(B, Bright);
  385. Result := RGB(R, G, B);
  386. end;
  387. procedure TCustomPathLabel.CalculateAutoHotTrackColors;
  388. begin
  389. FColors[4] := CalculateAutoHotTrackColor(FColors[2]);
  390. SetContrast(FColors[4], FColors[0], 50);
  391. FColors[5] := CalculateAutoHotTrackColor(FColors[3]);
  392. SetContrast(FColors[5], FColors[1], 50);
  393. end;
  394. procedure TCustomPathLabel.SetIndentHorizontal(AIndent: Integer);
  395. begin
  396. if FIndentHorizontal <> AIndent then
  397. begin
  398. FIndentHorizontal := AIndent;
  399. AdjustBounds;
  400. Invalidate;
  401. end;
  402. end;
  403. procedure TCustomPathLabel.SetIndentVertical(AIndent: Integer);
  404. begin
  405. if FIndentVertical <> AIndent then
  406. begin
  407. FIndentVertical := AIndent;
  408. AdjustBounds;
  409. Invalidate;
  410. end;
  411. end;
  412. procedure TCustomPathLabel.SetAutoSizeVertical(Value: Boolean);
  413. begin
  414. if FAutoSizeVertical <> Value then
  415. begin
  416. FAutoSizeVertical := Value;
  417. AdjustBounds;
  418. Invalidate;
  419. end;
  420. end;
  421. function TCustomPathLabel.GetFocusControl: TWinControl;
  422. begin
  423. Result := inherited FocusControl;
  424. end;
  425. procedure TCustomPathLabel.SetFocusControl(Value: TWinControl);
  426. begin
  427. if FocusControl <> Value then
  428. begin
  429. inherited FocusControl := Value;
  430. UpdateStatus;
  431. end;
  432. end;
  433. procedure TCustomPathLabel.DoDrawTextIntern(var Rect: TRect; Flags: Longint; S: string);
  434. var
  435. i: Integer;
  436. Width: Integer;
  437. WidthMask: Integer;
  438. WidthPath: Integer;
  439. HotTrackOffset: Integer;
  440. HotTrackBottom: Integer;
  441. Separator: string;
  442. Str: string;
  443. HotTrackColor: TColor;
  444. VirtualMask: string;
  445. IsEmptyMask: Boolean;
  446. IsVirtualMask: Boolean;
  447. begin
  448. if (Flags and DT_CALCRECT <> 0) and ((S = '') or ShowAccelChar and
  449. (S[1] = '&') and (S[2] = #0)) then S := S + ' ';
  450. if not ShowAccelChar then Flags := Flags or DT_NOPREFIX;
  451. // have to apply DrawTextBiDiModeFlags if we ever deal with dibi
  452. Canvas.Font := Font;
  453. Width := (Rect.Right - Rect.Left);
  454. FDisplayPath := S;
  455. VirtualMask := Mask;
  456. IsVirtualMask := False;
  457. IsEmptyMask := (VirtualMask = '');
  458. if IsEmptyMask and HotTrack then
  459. begin
  460. VirtualMask := '*.*';
  461. IsVirtualMask := not FMouseInView;
  462. end;
  463. if VirtualMask <> '' then
  464. begin
  465. if (Length(FDisplayPath) > 0) and (FDisplayPath[Length(FDisplayPath)] <> GetSeparator) then
  466. Separator := GetSeparator;
  467. FDisplayPath := FDisplayPath + Separator;
  468. S := S + Separator;
  469. WidthMask := Canvas.TextWidth(VirtualMask);
  470. if WidthMask > Width div 3 then
  471. WidthPath := Width - (Width div 3)
  472. else
  473. WidthPath := Width - WidthMask;
  474. end
  475. else
  476. begin
  477. WidthMask := 0;
  478. WidthPath := Width;
  479. end;
  480. if FUnixPath then
  481. for i := 1 to Length(FDisplayPath) do
  482. begin
  483. case FDisplayPath[i] of
  484. '/': FDisplayPath[i] := '\';
  485. '\': FDisplayPath[i] := '/';
  486. end;
  487. end;
  488. FDisplayPath := MinimizeName(FDisplayPath, Canvas, WidthPath);
  489. if FUnixPath then
  490. for i := 1 to Length(FDisplayPath) do
  491. begin
  492. case FDisplayPath[i] of
  493. '\': FDisplayPath[i] := '/';
  494. '/': FDisplayPath[i] := '\';
  495. end;
  496. end;
  497. WidthPath := Canvas.TextWidth(FDisplayPath);
  498. if VirtualMask <> '' then
  499. begin
  500. if WidthMask > Width - WidthPath then
  501. begin
  502. VirtualMask := VirtualMask + '...';
  503. repeat
  504. Delete(VirtualMask, Length(VirtualMask) - 3, 1);
  505. WidthMask := Canvas.TextWidth(VirtualMask);
  506. until (WidthMask <= Width - WidthPath) or (Length(VirtualMask) = 3);
  507. end;
  508. end;
  509. Str := FDisplayPath;
  510. if not IsVirtualMask then
  511. begin
  512. Str := Str + VirtualMask;
  513. FDisplayMask := VirtualMask;
  514. end
  515. else
  516. begin
  517. FDisplayMask := '';
  518. end;
  519. ShowHint :=
  520. (FDisplayPath <> S) or
  521. ((not IsEmptyMask) and (FDisplayMask <> Mask)) or
  522. (WidthPath + WidthMask > Width);
  523. if not ShowHint then Hint := ''
  524. else Hint := S + Mask;
  525. if not FIsEnabled then
  526. begin
  527. Canvas.Font.Color := clBtnShadow;
  528. DrawText(Canvas.Handle, PChar(Str), Length(Str), Rect, Flags);
  529. end
  530. else
  531. begin
  532. FDisplayHotTrack := HotTrackPath;
  533. HotTrackColor := TColor(0); // shut up
  534. if FDisplayHotTrack <> '' then
  535. begin
  536. if TrackingActive then
  537. begin
  538. HotTrackColor := FColors[4 + Integer(FIsActive)]
  539. end
  540. else
  541. begin
  542. // We do not have a path label with hot-track and not tracking-active,
  543. // so this is untested branch
  544. Assert(False);
  545. // As if it were active
  546. HotTrackColor := FColors[2 + 1];
  547. end;
  548. end;
  549. if (FDisplayHotTrack <> '') and (FDisplayHotTrack <> HotTrackMask) then
  550. begin
  551. Canvas.Font.Color := HotTrackColor;
  552. DrawText(Canvas.Handle, PChar(FDisplayHotTrack), Length(FDisplayHotTrack), Rect, Flags);
  553. HotTrackOffset := Canvas.TextWidth(FDisplayHotTrack);
  554. Inc(Rect.Left, HotTrackOffset);
  555. Delete(Str, 1, Length(FDisplayHotTrack));
  556. HotTrackBottom := Rect.Bottom;
  557. end
  558. else
  559. begin
  560. HotTrackOffset := 0;
  561. HotTrackBottom := 0;
  562. end;
  563. if TrackingActive then
  564. Canvas.Font.Color := FColors[2 + Integer(FIsActive)]
  565. else
  566. Canvas.Font.Color := clWindowText;
  567. if FDisplayHotTrack = HotTrackMask then
  568. begin
  569. DrawText(Canvas.Handle, PChar(FDisplayPath), Length(FDisplayPath), Rect, Flags);
  570. HotTrackOffset := Canvas.TextWidth(FDisplayPath);
  571. HotTrackBottom := Rect.Bottom;
  572. Inc(Rect.Left, HotTrackOffset);
  573. Canvas.Font.Color := HotTrackColor;
  574. DrawText(Canvas.Handle, PChar(VirtualMask), Length(VirtualMask), Rect, Flags);
  575. end
  576. else
  577. begin
  578. DrawText(Canvas.Handle, PChar(Str), Length(Str), Rect, Flags);
  579. end;
  580. Dec(Rect.Left, HotTrackOffset);
  581. Rect.Bottom := Max(Rect.Bottom, HotTrackBottom);
  582. end;
  583. end;
  584. procedure TCustomPathLabel.DoDrawText(var Rect: TRect; Flags: Longint);
  585. begin
  586. DoDrawTextIntern(Rect, Flags, Caption);
  587. end;
  588. function TCustomPathLabel.GetSeparator: Char;
  589. begin
  590. if FUnixPath then Result := '/'
  591. else Result := '\';
  592. end;
  593. function TCustomPathLabel.HotTrackPath: string;
  594. var
  595. P: TPoint;
  596. DelimPos: Integer;
  597. Len: Integer;
  598. Path: string;
  599. begin
  600. Result := '';
  601. if UseHotTrack and FMouseInView and (FDisplayPath <> '') then
  602. begin
  603. P := ScreenToClient(Mouse.CursorPos);
  604. Len := P.X - FIndentHorizontal;
  605. if Len >= 0 then
  606. begin
  607. if Len < Canvas.TextWidth(FDisplayPath) then
  608. begin
  609. Result := '';
  610. Path := FDisplayPath;
  611. repeat
  612. Assert(Path <> '');
  613. if (not FUnixPath) and (Result = '') and IsUncPath(Path) then
  614. begin
  615. Result := ExtractFileDrive(Path);
  616. if Copy(Path, Length(Result) + 1, 1) = GetSeparator then
  617. begin
  618. Result := Result + GetSeparator;
  619. end;
  620. Delete(Path, 1, Length(Result));
  621. end
  622. else
  623. begin
  624. DelimPos := Pos(GetSeparator, Path);
  625. if DelimPos > 0 then
  626. begin
  627. Result := Result + Copy(Path, 1, DelimPos);
  628. Delete(Path, 1, DelimPos);
  629. end
  630. else
  631. begin
  632. Result := Result + Path;
  633. Path := '';
  634. end;
  635. end;
  636. until (Canvas.TextWidth(Result) >= Len) or (Path = '');
  637. end
  638. else
  639. if Len < Canvas.TextWidth(FDisplayPath + FDisplayMask) then
  640. begin
  641. Result := HotTrackMask;
  642. end;
  643. end;
  644. end;
  645. end;
  646. function TCustomPathLabel.GetColors(Index: Integer): TColor;
  647. begin
  648. Assert(Index in [0..5]);
  649. Result := FColors[Index];
  650. end; { GetColors }
  651. function TCustomPathLabel.UseRightToLeftAlignment: Boolean;
  652. begin
  653. // Not sure how to properly deal with RTL atm.
  654. // See also a comment on DrawTextBiDiModeFlags in DoDrawTextIntern
  655. Result := False;
  656. end;
  657. procedure TCustomPathLabel.Paint;
  658. const
  659. Alignments: array[TAlignment] of Word = (DT_LEFT, DT_RIGHT, DT_CENTER);
  660. WordWraps: array[Boolean] of Word = (0, DT_WORDBREAK);
  661. var
  662. Rect, CalcRect: TRect;
  663. DrawStyle: Longint;
  664. begin
  665. with Canvas do
  666. begin
  667. if not Transparent then
  668. begin
  669. Brush.Color := Self.Color;
  670. Brush.Style := bsSolid;
  671. FillRect(ClientRect);
  672. end;
  673. Brush.Style := bsClear;
  674. Rect := ClientRect;
  675. // DoDrawText takes care of BiDi alignments
  676. DrawStyle := DT_EXPANDTABS or WordWraps[WordWrap] or Alignments[Alignment];
  677. // MP
  678. Rect.Left := Rect.Left + FIndentHorizontal;
  679. Rect.Right := Rect.Right - FIndentHorizontal;
  680. Rect.Top := Rect.Top + FIndentVertical;
  681. Rect.Bottom := Rect.Bottom - FIndentVertical;
  682. // Calculate vertical layout
  683. if Layout <> tlTop then
  684. begin
  685. CalcRect := Rect;
  686. DoDrawText(CalcRect, DrawStyle or DT_CALCRECT);
  687. if Layout = tlBottom then OffsetRect(Rect, 0, Height - CalcRect.Bottom)
  688. else OffsetRect(Rect, 0, (Height - CalcRect.Bottom) div 2);
  689. end;
  690. DoDrawText(Rect, DrawStyle);
  691. end;
  692. end;
  693. procedure TCustomPathLabel.AdjustBounds;
  694. const
  695. WordWraps: array[Boolean] of Word = (0, DT_WORDBREAK);
  696. var
  697. DC: HDC;
  698. X: Integer;
  699. Rect: TRect;
  700. AAlignment: TAlignment;
  701. AWidth: Integer;
  702. AHeight: Integer;
  703. S: string;
  704. begin
  705. if not (csReading in ComponentState) and
  706. (AutoSize or AutoSizeVertical) then
  707. begin
  708. Rect := ClientRect;
  709. DC := GetDC(0);
  710. Canvas.Handle := DC;
  711. S := Caption;
  712. if S = '' then S := 'SomeTextForSizing';
  713. DoDrawTextIntern(Rect, (DT_EXPANDTABS or DT_CALCRECT) or WordWraps[WordWrap], S);
  714. Canvas.Handle := 0;
  715. ReleaseDC(0, DC);
  716. X := Left;
  717. AAlignment := Alignment;
  718. if UseRightToLeftAlignment then ChangeBiDiModeAlignment(AAlignment);
  719. if AAlignment = taRightJustify then Inc(X, Width - 2*FIndentHorizontal + Rect.Right);
  720. if AutoSize then AWidth := 2*FIndentHorizontal + Rect.Right
  721. else AWidth := Width;
  722. if AutoSize or AutoSizeVertical then AHeight := 2*FIndentVertical + Rect.Bottom
  723. else AHeight := Height;
  724. SetBounds(X, Top, AWidth, AHeight);
  725. end;
  726. end;
  727. function TCustomPathLabel.IsActive: Boolean;
  728. begin
  729. if csDestroying in ComponentState then Result := False
  730. else
  731. begin
  732. Result := Assigned(FocusControl) and FocusControl.Focused;
  733. if Assigned(OnGetStatus) then
  734. OnGetStatus(Self, Result);
  735. end;
  736. end;
  737. function TCustomPathLabel.TrackingActive: Boolean;
  738. begin
  739. Result := Assigned(FocusControl) or Assigned(OnGetStatus);
  740. end;
  741. procedure TCustomPathLabel.UpdateStatus;
  742. var
  743. NewIsActive: Boolean;
  744. NewIsEnabled: Boolean;
  745. NewColor: TColor;
  746. begin
  747. if TrackingActive then
  748. begin
  749. NewIsActive := IsActive;
  750. NewIsEnabled :=
  751. Enabled and
  752. ((not Assigned(FocusControl)) or FocusControl.Enabled);
  753. NewColor := FColors[Integer(NewIsActive)];
  754. if (NewIsActive <> FIsActive) or
  755. (NewIsEnabled <> FIsEnabled) or
  756. (NewColor <> Color) then
  757. begin
  758. FIsActive := NewIsActive;
  759. FIsEnabled := NewIsEnabled;
  760. Color := NewColor;
  761. Invalidate;
  762. end;
  763. end
  764. else
  765. begin
  766. Color := clBtnFace;
  767. end;
  768. end; { UpdateStatus }
  769. procedure TCustomPathLabel.Notification(AComponent: TComponent;
  770. Operation: TOperation);
  771. var
  772. NeedUpdate: Boolean;
  773. begin
  774. NeedUpdate :=
  775. (Operation = opRemove) and (AComponent = FocusControl);
  776. inherited;
  777. if NeedUpdate then UpdateStatus;
  778. end; { Notification }
  779. function TCustomPathLabel.UseHotTrack: Boolean;
  780. begin
  781. Result := HotTrack and FIsEnabled;
  782. end;
  783. procedure TCustomPathLabel.MouseMove(Shift: TShiftState; X: Integer; Y: Integer);
  784. begin
  785. inherited;
  786. if FMouseInView and UseHotTrack and (FDisplayHotTrack <> HotTrackPath) then
  787. begin
  788. Invalidate;
  789. end;
  790. end;
  791. procedure TCustomPathLabel.DoPathClick(Path: string);
  792. begin
  793. if Assigned(OnPathClick) then
  794. OnPathClick(Self, Path);
  795. end;
  796. procedure TCustomPathLabel.DoMaskClick;
  797. begin
  798. if Assigned(OnMaskClick) then
  799. OnMaskClick(Self);
  800. end;
  801. procedure TCustomPathLabel.DblClick;
  802. begin
  803. if FIsEnabled then
  804. begin
  805. inherited;
  806. end;
  807. end;
  808. procedure TCustomPathLabel.DoContextPopup(MousePos: TPoint; var Handled: Boolean);
  809. begin
  810. if FIsEnabled then
  811. begin
  812. inherited;
  813. end
  814. else
  815. begin
  816. Handled := True;
  817. end;
  818. end;
  819. procedure TCustomPathLabel.CMMouseEnter(var Message: TMessage);
  820. begin
  821. inherited;
  822. FMouseInView := True;
  823. Invalidate;
  824. end;
  825. procedure TCustomPathLabel.CMMouseLeave(var Message: TMessage);
  826. begin
  827. FMouseInView := False;
  828. Invalidate;
  829. inherited;
  830. end;
  831. procedure TCustomPathLabel.CMStyleChanged(var Message: TMessage);
  832. var
  833. WasNotTransparent: Boolean;
  834. begin
  835. // WORKAROUND for what seems like a bug in TCustomLabel.CMStyleChanged that unconditionally sets Transparent := True,
  836. // when styling is enabled
  837. WasNotTransparent := not Transparent;
  838. inherited;
  839. if WasNotTransparent then
  840. begin
  841. Transparent := False;
  842. Invalidate;
  843. end;
  844. end;
  845. end.