PasTools.pas 32 KB

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