PathLabel.pas 26 KB

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