PathLabel.pas 27 KB

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