PasTools.pas 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  1. unit PasTools;
  2. interface
  3. uses
  4. Windows, Types, Classes, ComCtrls, ExtCtrls, Controls, Dialogs, Forms, Messages;
  5. function Construct(ComponentClass: TComponentClass; Owner: TComponent): TComponent;
  6. function IsVistaHard: Boolean;
  7. function IsVista: Boolean;
  8. // Prevent name conflict with C++ IsWin8.
  9. {$HPPEMIT '#define IsWin7 IsWin7Pas'}
  10. {$HPPEMIT END '#undef IsWin7'}
  11. function IsWin7: Boolean;
  12. // Prevent name conflict with C++ IsWin8.
  13. {$HPPEMIT '#define IsWin8 IsWin8Pas'}
  14. {$HPPEMIT END '#undef IsWin8'}
  15. function IsWin8: Boolean;
  16. // Prevent name conflict with C++ CutToChar.
  17. {$HPPEMIT '#define CutToChar CutToCharPas'}
  18. {$HPPEMIT END '#undef CutToChar'}
  19. function CutToChar(var Str: string; Ch: Char; Trim: Boolean): string;
  20. procedure FilterToFileTypes(Filter: string; FileTypes: TFileTypeItems);
  21. // Note that while we based our scaling on pixels-per-inch,
  22. // VCL actually scales based on font size
  23. const
  24. CM_DPICHANGED = WM_USER + $2000 + 10;
  25. WM_DPICHANGED_BEFOREPARENT = $02E2;
  26. WM_DPICHANGED_AFTERPARENT = $02E3;
  27. function HasSystemParametersInfoForPixelsPerInch: Boolean;
  28. function SystemParametersInfoForPixelsPerInch(
  29. uiAction, uiParam: UINT; pvParam: Pointer; fWinIni: UINT; dpi: UINT): BOOL;
  30. procedure GetFormScaleRatio(Form: TForm; var M, D: Integer);
  31. function GetMonitorFromControl(Control: TControl): TMonitor;
  32. function GetMonitorPixelsPerInch(Monitor: TMonitor): Integer;
  33. function GetControlPixelsPerInch(Control: TControl): Integer;
  34. function GetComponentPixelsPerInch(Component: TComponent): Integer;
  35. function LoadDimension(Dimension: Integer; PixelsPerInch: Integer; Control: TControl): Integer;
  36. function StrToDimensionDef(Str: string; PixelsPerInch: Integer; Control: TControl; Default: Integer): Integer;
  37. function SaveDimension(Dimension: Integer): Integer;
  38. function DimensionToDefaultPixelsPerInch(Dimension: Integer): Integer;
  39. function ScaleByPixelsPerInch(Dimension: Integer; Monitor: TMonitor): Integer; overload;
  40. function ScaleByPixelsPerInch(Dimension: Integer; Control: TControl): Integer; overload;
  41. function ScaleByPixelsPerInchFromSystem(Dimension: Integer; Control: TControl): Integer;
  42. function LoadPixelsPerInch(S: string; Control: TControl): Integer;
  43. function SavePixelsPerInch(Control: TControl): string;
  44. function SaveDefaultPixelsPerInch: string;
  45. function ScaleByTextHeight(Control: TControl; Dimension: Integer): Integer;
  46. function ScaleByTextHeightRunTime(Control: TControl; Dimension: Integer): Integer;
  47. function GetSystemMetricsForControl(Control: TControl; nIndex: Integer): Integer;
  48. type
  49. TImageListSize = (ilsSmall, ilsLarge);
  50. procedure NeedShellImageLists;
  51. function ShellImageListForControl(Control: TControl; Size: TImageListSize): TImageList;
  52. function ControlHasRecreationPersistenceData(Control: TControl): Boolean;
  53. function IsAppIconic: Boolean;
  54. procedure SetAppIconic(Value: Boolean);
  55. procedure SetAppMainForm(Value: TForm);
  56. procedure SetAppTerminated(Value: Boolean);
  57. procedure ForceColorChange(Control: TWinControl);
  58. function IsUncPath(Path: string): Boolean;
  59. function SupportsDarkMode: Boolean;
  60. procedure AllowDarkModeForWindow(Control: TWinControl; Allow: Boolean);
  61. type
  62. TApiPathEvent = function(Path: string): string;
  63. var
  64. OnApiPath: TApiPathEvent = nil;
  65. // Prevent name conflict with C++ ApiPath.
  66. // We would not want to call this implementation in any case anyway.
  67. {$HPPEMIT '#define ApiPath ApiPathPas'}
  68. {$HPPEMIT END '#undef ApiPath'}
  69. function ApiPath(Path: string): string;
  70. type
  71. TControlScrollBeforeUpdate = procedure(ObjectToValidate: TObject) of object;
  72. TControlScrollAfterUpdate = procedure of object;
  73. TCustomControlScrollOnDragOver = class
  74. private
  75. FOnBeforeUpdate: TControlScrollBeforeUpdate;
  76. FOnAfterUpdate: TControlScrollAfterUpdate;
  77. FDragOverTimer: TTimer;
  78. FControl: TControl;
  79. FDragOverTime: FILETIME;
  80. FLastVScrollTime: FILETIME;
  81. FVScrollCount: Integer;
  82. procedure DragOverTimer(Sender: TObject);
  83. procedure BeforeUpdate(ObjectToValidate: TObject);
  84. procedure AfterUpdate;
  85. public
  86. constructor Create(Control: TControl; ScheduleDragOver: Boolean);
  87. destructor Destroy; override;
  88. procedure StartDrag; virtual;
  89. procedure EndDrag; virtual;
  90. procedure DragOver(Point: TPoint); virtual; abstract;
  91. property OnBeforeUpdate: TControlScrollBeforeUpdate read FOnBeforeUpdate write FOnBeforeUpdate;
  92. property OnAfterUpdate: TControlScrollAfterUpdate read FOnAfterUpdate write FOnAfterUpdate;
  93. end;
  94. TTreeViewScrollOnDragOver = class(TCustomControlScrollOnDragOver)
  95. private
  96. FLastDragNode: TTreeNode;
  97. FLastHScrollTime: FILETIME;
  98. public
  99. procedure StartDrag; override;
  100. procedure DragOver(Point: TPoint); override;
  101. end;
  102. TListViewScrollOnDragOver = class(TCustomControlScrollOnDragOver)
  103. public
  104. procedure DragOver(Point: TPoint); override;
  105. end;
  106. TListBoxScrollOnDragOver = class(TCustomControlScrollOnDragOver)
  107. public
  108. procedure DragOver(Point: TPoint); override;
  109. end;
  110. implementation
  111. uses
  112. SysUtils, StdCtrls, Graphics, MultiMon, ShellAPI, Generics.Collections, CommCtrl, ImgList;
  113. const
  114. DDExpandDelay = 15000000;
  115. DDMaxSlowCount = 3;
  116. DDVScrollDelay = 2000000;
  117. DDHScrollDelay = 2000000;
  118. DDDragStartDelay = 500000;
  119. function Construct(ComponentClass: TComponentClass; Owner: TComponent): TComponent;
  120. begin
  121. Result := ComponentClass.Create(Owner);
  122. end;
  123. // detects vista, even in compatibility mode
  124. // (GetLocaleInfoEx is available since Vista only)
  125. function IsVistaHard: Boolean;
  126. begin
  127. Result := (GetProcAddress(GetModuleHandle(Kernel32), 'GetLocaleInfoEx') <> nil);
  128. end;
  129. function IsVista: Boolean;
  130. begin
  131. Result := CheckWin32Version(6, 0);
  132. end;
  133. function IsWin7: Boolean;
  134. begin
  135. Result := CheckWin32Version(6, 1);
  136. end;
  137. function IsWin8: Boolean;
  138. begin
  139. Result := CheckWin32Version(6, 2);
  140. end;
  141. function CutToChar(var Str: string; Ch: Char; Trim: Boolean): string;
  142. var
  143. P: Integer;
  144. begin
  145. P := Pos(Ch, Str);
  146. if P > 0 then
  147. begin
  148. Result := Copy(Str, 1, P-1);
  149. Delete(Str, 1, P);
  150. end
  151. else
  152. begin
  153. Result := Str;
  154. Str := '';
  155. end;
  156. if Trim then Result := SysUtils.Trim(Result);
  157. end;
  158. procedure FilterToFileTypes(Filter: string; FileTypes: TFileTypeItems);
  159. var
  160. Item: TFileTypeItem;
  161. begin
  162. while Filter <> '' do
  163. begin
  164. Item := FileTypes.Add();
  165. Item.DisplayName := CutToChar(Filter, '|', True);
  166. Item.FileMask := CutToChar(Filter, '|', True);
  167. end;
  168. end;
  169. type
  170. TGetDpiForMonitorFunc =
  171. function (hMonitor: HMONITOR; MonitorType: Integer; out DpiX, DpiY: Cardinal): HRESULT; stdcall;
  172. TGetSystemMetricsForDpiFunc =
  173. function (nIndex: Integer; Dpi: Cardinal): Integer; stdcall;
  174. TSystemParametersInfoForDpiFunc =
  175. function (uiAction, uiParam: UINT; pvParam: Pointer; fWinIni: UINT; dpi: UINT): BOOL; stdcall;
  176. const
  177. MDT_EFFECTIVE_DPI = 0;
  178. var
  179. GetDpiForMonitor: TGetDpiForMonitorFunc = nil;
  180. GetSystemMetricsForDpi: TGetSystemMetricsForDpiFunc = nil;
  181. SystemParametersInfoForDpi: TSystemParametersInfoForDpiFunc = nil;
  182. function HasSystemParametersInfoForPixelsPerInch: Boolean;
  183. begin
  184. Result := Assigned(SystemParametersInfoForDpi);
  185. end;
  186. function SystemParametersInfoForPixelsPerInch(
  187. uiAction, uiParam: UINT; pvParam: Pointer; fWinIni: UINT; dpi: UINT): BOOL;
  188. begin
  189. if HasSystemParametersInfoForPixelsPerInch then
  190. begin
  191. Result := SystemParametersInfoForDpi(uiAction, uiParam, pvParam, fWinIni, dpi);
  192. end
  193. else
  194. begin
  195. Result := SystemParametersInfo(uiAction, uiParam, pvParam, fWinIni);
  196. end;
  197. end;
  198. function GetMonitorPixelsPerInch(Monitor: TMonitor): Integer;
  199. var
  200. DpiX, DpiY: Cardinal;
  201. begin
  202. if Assigned(GetDpiForMonitor) and
  203. (GetDpiForMonitor(Monitor.Handle, MDT_EFFECTIVE_DPI, DpiX, DpiY) = S_OK) then
  204. begin
  205. Result := DpiX;
  206. end
  207. else
  208. begin
  209. Result := Screen.PixelsPerInch;
  210. end;
  211. end;
  212. function GetMonitorFromControl(Control: TControl): TMonitor;
  213. begin
  214. if Control.Parent <> nil then
  215. begin
  216. Result := GetMonitorFromControl(Control.Parent);
  217. end
  218. else
  219. if Control is TCustomForm then
  220. begin
  221. Result := TCustomForm(Control).Monitor;
  222. end
  223. else
  224. if (Control is TWinControl) and TWinControl(Control).HandleAllocated then
  225. begin
  226. Result := Screen.MonitorFromWindow(TWinControl(Control).Handle);
  227. end
  228. else
  229. begin
  230. Result := nil;
  231. end;
  232. end;
  233. function GetControlPixelsPerInch(Control: TControl): Integer;
  234. var
  235. Form: TCustomForm;
  236. Monitor: TMonitor;
  237. begin
  238. if Assigned(GetDpiForMonitor) then // optimization
  239. begin
  240. Form := GetParentForm(Control);
  241. if Assigned(Form) then
  242. begin
  243. // By default, scale according to what the form is so far rendered on.
  244. // If the monitor perceived DPI does not match its monitor DPI, it's because the WM_DPICHANGED is still pending.
  245. Result := TForm(Form).PixelsPerInch;
  246. end
  247. else
  248. begin
  249. Monitor := GetMonitorFromControl(Control);
  250. if Monitor = nil then
  251. begin
  252. Assert(False);
  253. Monitor := Screen.PrimaryMonitor;
  254. end;
  255. Result := GetMonitorPixelsPerInch(Monitor);
  256. end;
  257. end
  258. else
  259. begin
  260. Result := Screen.PixelsPerInch;
  261. end;
  262. end;
  263. function GetComponentPixelsPerInch(Component: TComponent): Integer;
  264. begin
  265. Result := GetControlPixelsPerInch(TControl(Component.Owner));
  266. end;
  267. function LoadDimension(Dimension: Integer; PixelsPerInch: Integer; Control: TControl): Integer;
  268. begin
  269. Result := MulDiv(Dimension, GetControlPixelsPerInch(Control), PixelsPerInch);
  270. end;
  271. function StrToDimensionDef(Str: string; PixelsPerInch: Integer; Control: TControl; Default: Integer): Integer;
  272. begin
  273. if TryStrToInt(Str, Result) then
  274. begin
  275. Result := LoadDimension(Result, PixelsPerInch, Control);
  276. end
  277. else
  278. begin
  279. Result := Default;
  280. end;
  281. end;
  282. function SaveDimension(Dimension: Integer): Integer;
  283. begin
  284. // noop
  285. Result := Dimension;
  286. end;
  287. function DimensionToDefaultPixelsPerInch(Dimension: Integer): Integer;
  288. begin
  289. Result := MulDiv(Dimension, USER_DEFAULT_SCREEN_DPI, Screen.PixelsPerInch);
  290. end;
  291. function ScaleByPixelsPerInch(Dimension: Integer; Monitor: TMonitor): Integer;
  292. begin
  293. Result := MulDiv(Dimension, GetMonitorPixelsPerInch(Monitor), USER_DEFAULT_SCREEN_DPI);
  294. end;
  295. function ScaleByPixelsPerInch(Dimension: Integer; Control: TControl): Integer;
  296. begin
  297. Result := MulDiv(Dimension, GetControlPixelsPerInch(Control), USER_DEFAULT_SCREEN_DPI);
  298. end;
  299. function ScaleByPixelsPerInchFromSystem(Dimension: Integer; Control: TControl): Integer;
  300. begin
  301. Result := MulDiv(Dimension, GetControlPixelsPerInch(Control), Screen.PixelsPerInch);
  302. end;
  303. function LoadPixelsPerInch(S: string; Control: TControl): Integer;
  304. begin
  305. // for backward compatibility with version that did not save the DPI,
  306. // make reasonable assumption that the configuration was saved with
  307. // the same DPI as we run now
  308. Result := StrToIntDef(S, GetControlPixelsPerInch(Control));
  309. end;
  310. function SavePixelsPerInch(Control: TControl): string;
  311. begin
  312. Result := IntToStr(GetControlPixelsPerInch(Control));
  313. end;
  314. function SaveDefaultPixelsPerInch: string;
  315. begin
  316. Result := IntToStr(USER_DEFAULT_SCREEN_DPI);
  317. end;
  318. // WORKAROUND
  319. // https://stackoverflow.com/q/9410485/850848
  320. type
  321. TFormHelper = class helper for TCustomForm
  322. public
  323. function RetrieveTextHeight: Integer;
  324. function CalculateTextHeight: Integer;
  325. end;
  326. function TFormHelper.RetrieveTextHeight: Integer;
  327. begin
  328. Result := Self.FTextHeight;
  329. end;
  330. function TFormHelper.CalculateTextHeight: Integer;
  331. begin
  332. Result := Self.GetTextHeight;
  333. end;
  334. function ScaleByTextHeightImpl(Control: TControl; Dimension: Integer; TextHeight: Integer): Integer;
  335. var
  336. Form: TCustomForm;
  337. NewTextHeight: Integer;
  338. begin
  339. // RTL_COPY (TCustomForm.ReadState)
  340. Form := ValidParentForm(Control);
  341. NewTextHeight := Form.CalculateTextHeight;
  342. if TextHeight <> NewTextHeight then
  343. begin
  344. Dimension := MulDiv(Dimension, NewTextHeight, TextHeight);
  345. end;
  346. Result := Dimension;
  347. end;
  348. const
  349. OurDesignTimeTextHeight = 13;
  350. function ScaleByTextHeight(Control: TControl; Dimension: Integer): Integer;
  351. var
  352. Form: TCustomForm;
  353. TextHeight: Integer;
  354. begin
  355. // RTL_COPY (TCustomForm.ReadState)
  356. Form := GetParentForm(Control);
  357. if Form = nil then
  358. begin
  359. // This should happen only for screen tip over dropped down menu
  360. Assert(Control.ClassName = 'TTBXPopupWindow');
  361. Result := ScaleByPixelsPerInch(Dimension, Control);
  362. end
  363. else
  364. begin
  365. TextHeight := Form.RetrieveTextHeight;
  366. // runtime form (such as TTBFloatingWindowParent)
  367. if TextHeight = 0 then
  368. begin
  369. Result := ScaleByTextHeightRunTime(Control, Dimension);
  370. end
  371. else
  372. begin
  373. // that's our design text-size, we do not expect any other value
  374. Assert(TextHeight = OurDesignTimeTextHeight);
  375. Result := ScaleByTextHeightImpl(Control, Dimension, TextHeight);
  376. end;
  377. end;
  378. end;
  379. procedure GetFormScaleRatio(Form: TForm; var M, D: Integer);
  380. begin
  381. M := Form.CalculateTextHeight;
  382. D := Form.RetrieveTextHeight;
  383. end;
  384. // this differs from ScaleByTextHeight only by enforcing
  385. // constant design-time text height
  386. function ScaleByTextHeightRunTime(Control: TControl; Dimension: Integer): Integer;
  387. begin
  388. Result := ScaleByTextHeightImpl(Control, Dimension, OurDesignTimeTextHeight);
  389. end;
  390. function GetSystemMetricsForControl(Control: TControl; nIndex: Integer): Integer;
  391. begin
  392. if Assigned(GetSystemMetricsForDpi) then
  393. begin
  394. Result := GetSystemMetricsForDpi(nIndex, GetControlPixelsPerInch(Control))
  395. end
  396. else
  397. begin
  398. Result := GetSystemMetrics(nIndex);
  399. end;
  400. end;
  401. var
  402. ShellImageLists: TDictionary<Integer, TImageList> = nil;
  403. procedure InitializeShellImageLists;
  404. type
  405. TSHGetImageList = function (iImageList: integer; const riid: TGUID; var ppv: Pointer): hResult; stdcall;
  406. const
  407. IID_IImageList: TGUID = '{46EB5926-582E-4017-9FDF-E8998DAA0950}';
  408. var
  409. Lib: THandle;
  410. ImageList: Integer;
  411. Handle: THandle;
  412. Height, Width: Integer;
  413. ShellImageList: TImageList;
  414. SHGetImageList: TSHGetImageList;
  415. HR: HRESULT;
  416. begin
  417. Lib := LoadLibrary('shell32');
  418. SHGetImageList := GetProcAddress(Lib, 'SHGetImageList');
  419. ShellImageLists := TDictionary<Integer, TImageList>.Create;
  420. for ImageList := 0 to SHIL_LAST do
  421. begin
  422. // VCL have declaration for SHGetImageList in ShellAPI, but it does not link
  423. HR := SHGetImageList(ImageList, IID_IImageList, Pointer(Handle));
  424. if (HR = S_OK) and
  425. ImageList_GetIconSize(Handle, Width, Height) then
  426. begin
  427. // We could use AddOrSetValue instead, but to be on a safe siz, we prefer e.g. SHIL_SMALL over SHIL_SYSSMALL,
  428. // while they actually can be the same
  429. if not ShellImageLists.ContainsKey(Width) then
  430. begin
  431. ShellImageList := TImageList.Create(Application);
  432. ShellImageList.Handle := Handle;
  433. ShellImageList.ShareImages := True;
  434. ShellImageList.DrawingStyle := dsTransparent;
  435. ShellImageLists.Add(Width, ShellImageList);
  436. end;
  437. end;
  438. end;
  439. end;
  440. procedure NeedShellImageLists;
  441. begin
  442. if ShellImageLists = nil then
  443. begin
  444. InitializeShellImageLists;
  445. end;
  446. end;
  447. function ShellImageListForControl(Control: TControl; Size: TImageListSize): TImageList;
  448. var
  449. ImageListPair: TPair<Integer, TImageList>;
  450. Width, ImageListWidth: Integer;
  451. Diff, BestDiff: Integer;
  452. begin
  453. // Delay load image lists, not to waste resources in console/scripting mode
  454. NeedShellImageLists;
  455. case Size of
  456. ilsSmall: Width := 16;
  457. ilsLarge: Width := 32;
  458. else Width := 0; Assert(False);
  459. end;
  460. Width := ScaleByPixelsPerInch(Width, Control);
  461. Result := nil;
  462. BestDiff := -1;
  463. for ImageListPair in ShellImageLists do
  464. begin
  465. ImageListWidth := ImageListPair.Key;
  466. if ImageListWidth <= Width then
  467. begin
  468. Diff := Width - ImageListWidth;
  469. end
  470. else
  471. begin
  472. // Prefer smaller images over larger, so for 150%, we use 100% images, not 200%
  473. Diff := ImageListWidth - Width + 1;
  474. end;
  475. if (BestDiff < 0) or (BestDiff > Diff) then
  476. begin
  477. BestDiff := Diff;
  478. Result := ImageListPair.Value;
  479. end;
  480. end;
  481. end;
  482. type
  483. TListViewHelper = class helper for TCustomListView
  484. public
  485. function HasMemStream: Boolean;
  486. end;
  487. function TListViewHelper.HasMemStream: Boolean;
  488. begin
  489. Result := Assigned(Self.FMemStream);
  490. end;
  491. type
  492. TTreeViewHelper = class helper for TCustomTreeView
  493. public
  494. function HasMemStream: Boolean;
  495. end;
  496. function TTreeViewHelper.HasMemStream: Boolean;
  497. begin
  498. Result := Assigned(Self.FMemStream);
  499. end;
  500. type
  501. TRichEditHelper = class helper for TCustomRichEdit
  502. public
  503. function HasMemStream: Boolean;
  504. end;
  505. function TRichEditHelper.HasMemStream: Boolean;
  506. begin
  507. Result := Assigned(Self.FMemStream);
  508. end;
  509. function ControlHasRecreationPersistenceData(Control: TControl): Boolean;
  510. begin
  511. // not implemented for this class as we do not use it as of now
  512. Assert(not (Control is TCustomComboBoxEx));
  513. Result :=
  514. ((Control is TCustomListView) and (Control as TCustomListView).HasMemStream) or
  515. ((Control is TCustomTreeView) and (Control as TCustomTreeView).HasMemStream) or
  516. ((Control is TCustomRichEdit) and (Control as TCustomRichEdit).HasMemStream);
  517. end;
  518. type
  519. TApplicationHelper = class helper for TApplication
  520. public
  521. function IsAppIconic: Boolean;
  522. procedure SetAppIconic(Value: Boolean);
  523. procedure SetMainForm(Value: TForm);
  524. procedure SetTerminated(Value: Boolean);
  525. end;
  526. function TApplicationHelper.IsAppIconic: Boolean;
  527. begin
  528. Result := Self.FAppIconic;
  529. end;
  530. procedure TApplicationHelper.SetAppIconic(Value: Boolean);
  531. begin
  532. Self.FAppIconic := Value;
  533. end;
  534. procedure TApplicationHelper.SetMainForm(Value: TForm);
  535. begin
  536. Self.FMainForm := Value;
  537. end;
  538. procedure TApplicationHelper.SetTerminated(Value: Boolean);
  539. begin
  540. Self.FTerminate := Value;
  541. end;
  542. function IsAppIconic: Boolean;
  543. begin
  544. Result := Application.IsAppIconic;
  545. end;
  546. procedure SetAppIconic(Value: Boolean);
  547. begin
  548. Application.SetAppIconic(Value);
  549. end;
  550. procedure SetAppMainForm(Value: TForm);
  551. begin
  552. Application.SetMainForm(Value);
  553. end;
  554. procedure SetAppTerminated(Value: Boolean);
  555. begin
  556. Application.SetTerminated(Value);
  557. end;
  558. function ApiPath(Path: string): string;
  559. begin
  560. Result := Path;
  561. if Assigned(OnApiPath) then
  562. begin
  563. Result := OnApiPath(Result);
  564. end;
  565. end;
  566. procedure ForceColorChange(Control: TWinControl);
  567. begin
  568. // particularly when changing color back to default (clWindow),
  569. // non-client area (border line) is not redrawn,
  570. // keeping previous color. force redraw here
  571. if Control.HandleAllocated then
  572. begin
  573. RedrawWindow(Control.Handle, nil, 0, RDW_INVALIDATE or RDW_FRAME);
  574. end;
  575. end;
  576. { TCustomControlScrollOnDragOver }
  577. constructor TCustomControlScrollOnDragOver.Create(Control: TControl;
  578. ScheduleDragOver: Boolean);
  579. begin
  580. FControl := Control;
  581. FOnBeforeUpdate := nil;
  582. FOnAfterUpdate := nil;
  583. if ScheduleDragOver then
  584. begin
  585. FDragOverTimer := TTimer.Create(Control);
  586. FDragOverTimer.Enabled := False;
  587. FDragOverTimer.Interval := 50;
  588. FDragOverTimer.OnTimer := DragOverTimer;
  589. end
  590. else FDragOverTimer := nil;
  591. end;
  592. destructor TCustomControlScrollOnDragOver.Destroy;
  593. begin
  594. FreeAndNil(FDragOverTimer);
  595. end;
  596. procedure TCustomControlScrollOnDragOver.DragOverTimer(Sender: TObject);
  597. var
  598. P: TPoint;
  599. begin
  600. P := FControl.ScreenToClient(Mouse.CursorPos);
  601. if (P.X >= 0) and (P.X < FControl.Width) and
  602. (P.Y >= 0) and (P.Y < FControl.Height) then
  603. begin
  604. DragOver(P);
  605. end;
  606. end;
  607. procedure TCustomControlScrollOnDragOver.StartDrag;
  608. begin
  609. GetSystemTimeAsFileTime(FDragOverTime);
  610. GetSystemTimeAsFileTime(FLastVScrollTime);
  611. FVScrollCount := 0;
  612. if Assigned(FDragOverTimer) then
  613. FDragOverTimer.Enabled := True;
  614. end;
  615. procedure TCustomControlScrollOnDragOver.EndDrag;
  616. begin
  617. if Assigned(FDragOverTimer) then
  618. FDragOverTimer.Enabled := False;
  619. end;
  620. type
  621. TPublicControl = class(TControl);
  622. procedure TCustomControlScrollOnDragOver.BeforeUpdate(ObjectToValidate: TObject);
  623. var
  624. DragImages: TDragImageList;
  625. begin
  626. if Assigned(FOnBeforeUpdate) then
  627. FOnBeforeUpdate(ObjectToValidate);
  628. DragImages := TPublicControl(FControl).GetDragImages;
  629. if Assigned(DragImages) then
  630. DragImages.HideDragImage;
  631. end;
  632. procedure TCustomControlScrollOnDragOver.AfterUpdate;
  633. var
  634. DragImages: TDragImageList;
  635. begin
  636. if Assigned(FOnAfterUpdate) then
  637. FOnAfterUpdate;
  638. DragImages := TPublicControl(FControl).GetDragImages;
  639. if Assigned(DragImages) then
  640. DragImages.ShowDragImage;
  641. end;
  642. procedure TTreeViewScrollOnDragOver.StartDrag;
  643. var
  644. KeyBoardState : TKeyBoardState;
  645. begin
  646. inherited;
  647. FLastDragNode := nil;
  648. if (GetKeyState(VK_SPACE) <> 0) and GetKeyboardState(KeyBoardState) then
  649. begin
  650. KeyBoardState[VK_SPACE] := 0;
  651. SetKeyBoardState(KeyBoardState);
  652. end;
  653. GetSystemTimeAsFileTime(FLastHScrollTime);
  654. end;
  655. { TTreeViewScrollOnDragOver }
  656. procedure TTreeViewScrollOnDragOver.DragOver(Point: TPoint);
  657. var
  658. TreeView: TCustomTreeView;
  659. NbPixels: Integer;
  660. KnowTime: TFileTime;
  661. Node: TTreeNode;
  662. TempTopItem: TTreeNode;
  663. ScrollInfo: TScrollInfo;
  664. KeyBoardState : TKeyBoardState;
  665. begin
  666. TreeView := (FControl as TCustomTreeView);
  667. Node := TreeView.GetNodeAt(Point.X, Point.Y);
  668. if Assigned(Node) then
  669. begin
  670. GetSystemTimeAsFileTime(KnowTime);
  671. if GetKeyState(VK_SPACE) = 0 then
  672. begin
  673. {Expand node after 2.5 seconds: }
  674. if not Assigned(FLastDragNode) or (FLastDragNode <> Node) then
  675. begin
  676. {not previous droptarget: start timer}
  677. GetSystemTimeAsFileTime(FDragOverTime);
  678. FLastDragNode := Node
  679. end
  680. else
  681. begin
  682. if ((Int64(KnowTime) - Int64(FDragOverTime)) > DDExpandDelay) then
  683. begin
  684. TempTopItem := TreeView.TopItem;
  685. BeforeUpdate(nil);
  686. Node.Expand(False);
  687. TreeView.TopItem := TempTopItem;
  688. TreeView.Update;
  689. AfterUpdate;
  690. FDragOverTime := KnowTime;
  691. end;
  692. end;
  693. end
  694. else
  695. begin
  696. {restart timer}
  697. GetSystemTimeAsFileTime(FDragOverTime);
  698. if GetKeyboardState(KeyBoardState) then
  699. begin
  700. KeyBoardState[VK_Space] := 0;
  701. SetKeyBoardState(KeyBoardState);
  702. end;
  703. TempTopItem := TreeView.TopItem;
  704. BeforeUpdate(Node);
  705. if Node.Expanded then
  706. begin
  707. if not TreeView.Selected.HasAsParent(Node) then
  708. Node.Collapse(False);
  709. end
  710. else Node.Expand(False);
  711. TreeView.TopItem := TempTopItem;
  712. TreeView.Update;
  713. AfterUpdate;
  714. end;
  715. NbPixels := Abs(TTreeView(FControl).Font.Height);
  716. {Vertical treescrolling:}
  717. if ((Int64(KnowTime) - Int64(FLastVScrollTime)) > DDVScrollDelay) or
  718. ((FVScrollCount > 3) and
  719. ((Int64(KnowTime) - Int64(FLastVScrollTime)) > (DDVScrollDelay Div 4))) then
  720. begin
  721. {Scroll tree up, if droptarget is topitem:}
  722. if Node = TreeView.TopItem then
  723. begin
  724. BeforeUpdate(nil);
  725. TreeView.Perform(WM_VSCROLL, SB_LINEUP, 0);
  726. AfterUpdate;
  727. GetSystemTimeAsFileTime(FLastVScrollTime);
  728. Inc(FVScrollCount);
  729. end
  730. else
  731. {Scroll tree down, if next visible item of droptarget is not visible:}
  732. begin
  733. if Point.Y + 3 * nbPixels > TreeView.Height then
  734. begin
  735. BeforeUpdate(nil);
  736. TreeView.Perform(WM_VSCROLL, SB_LINEDOWN, 0);
  737. AfterUpdate;
  738. GetSystemTimeAsFileTime(FLastVScrollTime);
  739. Inc(FVScrollCount);
  740. end
  741. else
  742. begin
  743. FVScrollCount := 0;
  744. end;
  745. end;
  746. end; {VScrollDelay}
  747. {Horizontal treescrolling:}
  748. {Scroll tree Left}
  749. if ((Int64(KnowTime) - Int64(FLastHScrollTime)) > DDHScrollDelay) then
  750. begin
  751. GetSystemTimeAsFileTime(FLastHScrollTime);
  752. ScrollInfo.cbSize := SizeOf(ScrollInfo);
  753. ScrollInfo.FMask := SIF_ALL;
  754. GetScrollInfo(TreeView.Handle, SB_HORZ, ScrollInfo);
  755. if ScrollInfo.nMin <> ScrollInfo.nMax then
  756. begin
  757. if Point.X < 50 then
  758. begin
  759. if Node.DisplayRect(True).Right + 50 < TreeView.Width then
  760. begin
  761. BeforeUpdate(nil);
  762. TreeView.Perform(WM_HSCROLL, SB_LINELEFT, 0);
  763. AfterUpdate;
  764. end;
  765. end
  766. else
  767. if Point.X > (TreeView.Width - 50) then
  768. begin
  769. if Node.DisplayRect(True).Left > 50 then
  770. begin
  771. BeforeUpdate(nil);
  772. TreeView.Perform(WM_HSCROLL, SB_LINERIGHT, 0);
  773. AfterUpdate;
  774. end;
  775. end;
  776. end;
  777. end;
  778. end;
  779. end;
  780. { TListViewScrollOnDragOver }
  781. procedure TListViewScrollOnDragOver.DragOver(Point: TPoint);
  782. var
  783. ListView: TCustomListView;
  784. KnowTime: TFileTime;
  785. NbPixels: Integer;
  786. WParam: LongInt;
  787. begin
  788. ListView := (FControl as TCustomListView);
  789. GetSystemTimeAsFileTime(KnowTime);
  790. NbPixels := Abs(TListView(ListView).Font.Height);
  791. {Vertical scrolling, if viewstyle = vsReport:}
  792. if (TListView(ListView).ViewStyle = vsReport) and Assigned(ListView.TopItem) and
  793. (((Int64(KnowTime) - Int64(FLastVScrollTime)) > DDVScrollDelay) or
  794. ((FVScrollCount > DDMaxSlowCount) and
  795. ((Int64(KnowTime) - Int64(FLastVScrollTime)) > (DDVScrollDelay div 4)))) then
  796. begin
  797. if (Point.Y - 3 * nbPixels <= 0) and (ListView.TopItem.Index > 0) then WParam := SB_LINEUP
  798. else
  799. if (Point.Y + 3 * nbPixels > ListView.Height) then WParam := SB_LINEDOWN
  800. else WParam := -1;
  801. if WParam >= 0 then
  802. begin
  803. BeforeUpdate(nil);
  804. ListView.Perform(WM_VSCROLL, WParam, 0);
  805. if FVScrollCount > DDMaxSlowCount then
  806. ListView.Perform(WM_VSCROLL, WParam, 0);
  807. if FVScrollCount > DDMaxSlowCount * 3 then
  808. ListView.Perform(WM_VSCROLL, WParam, 0);
  809. ListView.Update;
  810. AfterUpdate;
  811. GetSystemTimeAsFileTime(FLastVScrollTime);
  812. Inc(FVScrollCount);
  813. end
  814. else FVScrollCount := 0;
  815. end;
  816. end;
  817. { TListBoxScrollOnDragOver }
  818. procedure TListBoxScrollOnDragOver.DragOver(Point: TPoint);
  819. var
  820. ListBox: TListBox;
  821. KnowTime: TFileTime;
  822. NbPixels: Integer;
  823. WParam: LongInt;
  824. begin
  825. ListBox := (FControl as TListBox);
  826. GetSystemTimeAsFileTime(KnowTime);
  827. NbPixels := Abs(ListBox.Font.Height);
  828. {Vertical scrolling, if viewstyle = vsReport:}
  829. if (ListBox.Items.Count > 0) and
  830. (((Int64(KnowTime) - Int64(FLastVScrollTime)) > DDVScrollDelay) or
  831. ((FVScrollCount > DDMaxSlowCount) and
  832. ((Int64(KnowTime) - Int64(FLastVScrollTime)) > (DDVScrollDelay div 4)))) then
  833. begin
  834. if (Point.Y - 3 * nbPixels <= 0) and (ListBox.TopIndex > 0) then WParam := SB_LINEUP
  835. else
  836. if (Point.Y + 3 * nbPixels > ListBox.Height) then WParam := SB_LINEDOWN
  837. else WParam := -1;
  838. if WParam >= 0 then
  839. begin
  840. BeforeUpdate(nil);
  841. ListBox.Perform(WM_VSCROLL, WParam, 0);
  842. if FVScrollCount > DDMaxSlowCount then
  843. ListBox.Perform(WM_VSCROLL, WParam, 0);
  844. if FVScrollCount > DDMaxSlowCount * 3 then
  845. ListBox.Perform(WM_VSCROLL, WParam, 0);
  846. ListBox.Update;
  847. AfterUpdate;
  848. GetSystemTimeAsFileTime(FLastVScrollTime);
  849. Inc(FVScrollCount);
  850. end
  851. else FVScrollCount := 0;
  852. end;
  853. end;
  854. function IsUncPath(Path: string): Boolean;
  855. begin
  856. Result := (Copy(Path, 1, 2) = '\\') or (Copy(Path, 1, 2) = '//');
  857. end;
  858. var
  859. AllowDarkModeForWindowLoaded: Boolean = False;
  860. AAllowDarkModeForWindow: function(hWnd: HWND; Allow: BOOL): BOOL; stdcall;
  861. function SupportsDarkMode: Boolean;
  862. var
  863. OSVersionInfo: TOSVersionInfoEx;
  864. UxThemeLib: HMODULE;
  865. begin
  866. if not AllowDarkModeForWindowLoaded then
  867. begin
  868. OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo);
  869. if GetVersionEx(OSVersionInfo) and (OSVersionInfo.dwBuildNumber >= 17763) then
  870. begin
  871. UxThemeLib := GetModuleHandle('UxTheme');
  872. if UxThemeLib <> 0 then
  873. begin
  874. AAllowDarkModeForWindow := GetProcAddress(UxThemeLib, MakeIntResource(133));
  875. end;
  876. end;
  877. AllowDarkModeForWindowLoaded := True;
  878. end;
  879. Result := Assigned(AAllowDarkModeForWindow);
  880. end;
  881. procedure AllowDarkModeForWindow(Control: TWinControl; Allow: Boolean);
  882. begin
  883. Assert(Control.HandleAllocated);
  884. if SupportsDarkMode and Control.HandleAllocated then
  885. begin
  886. AAllowDarkModeForWindow(Control.Handle, Allow);
  887. end;
  888. end;
  889. var
  890. Lib: THandle;
  891. initialization
  892. Lib := LoadLibrary('shcore');
  893. if Lib <> 0 then
  894. begin
  895. GetDpiForMonitor := GetProcAddress(Lib, 'GetDpiForMonitor');
  896. end;
  897. Lib := LoadLibrary('user32');
  898. if Lib <> 0 then
  899. begin
  900. GetSystemMetricsForDpi := GetProcAddress(Lib, 'GetSystemMetricsForDpi');
  901. SystemParametersInfoForDpi := GetProcAddress(Lib, 'SystemParametersInfoForDpi');
  902. end;
  903. finalization
  904. // No need to release individual image lists as they are owned by Application object.
  905. FreeAndNil(ShellImageLists);
  906. end.