PasTools.pas 31 KB

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