PasTools.pas 29 KB

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