IDE.HelperFunc.pas 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936
  1. unit IDE.HelperFunc;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Additional Compiler IDE functions
  8. }
  9. interface
  10. uses
  11. Windows,
  12. Classes, Forms, Dialogs, Menus, Controls, StdCtrls, Graphics,
  13. ScintEdit, IDE.IDEScintEdit, ModernColors;
  14. const
  15. MRUListMaxCount = 10;
  16. type
  17. TMRUItemCompareProc = function(const S1, S2: String): Integer;
  18. TAddLinesPrefix = (alpNone, alpTimestamp, alpCountdown);
  19. TKeyMappingType = (kmtDelphi, kmtVisualStudio);
  20. procedure InitFormFont(Form: TForm);
  21. procedure SetControlWindowTheme(const WinControl: TWinControl; const Dark: Boolean);
  22. procedure InitFormThemeInit(const Theme: TTheme);
  23. function InitFormTheme(const Form: TForm): Boolean;
  24. function InitFormThemeGetBkColor(const WindowColor: Boolean): TColor;
  25. function InitFormThemeIsDark: Boolean;
  26. function GetDisplayFilename(const Filename: String): String;
  27. function GetFileTitle(const Filename: String): String;
  28. function GetCleanFileNameOfFile(const Filename: String): String;
  29. function GetLastWriteTimeOfFile(const Filename: String;
  30. LastWriteTime: PFileTime): Boolean;
  31. procedure AddFileToRecentDocs(const Filename: String);
  32. function GenerateGuid: String;
  33. function ISPPInstalled: Boolean;
  34. function IsISPPBuiltins(const Filename: String): Boolean;
  35. function WindowsVersionAtLeast(const AMajor, AMinor: Byte; const ABuild: Word = 0): Boolean;
  36. function IsWindows10: Boolean;
  37. function IsWindows11: Boolean;
  38. function GetDefaultThemeType: TThemeType;
  39. function GetDefaultKeyMappingType: TKeyMappingType;
  40. function GetDefaultMemoKeyMappingType: TIDEScintKeyMappingType;
  41. procedure LaunchFileOrURL(const AFilename: String; const AParameters: String = '');
  42. procedure OpenDonateSite;
  43. procedure OpenMailingListSite;
  44. procedure ClearMRUList(const MRUList: TStringList; const Section: String);
  45. procedure ReadMRUList(const MRUList: TStringList; const Section, Ident: String);
  46. procedure ModifyMRUList(const MRUList: TStringList; const Section, Ident: String;
  47. const AItem: String; const AddNewItem: Boolean; CompareProc: TMRUItemCompareProc);
  48. procedure LoadKnownIncludedAndHiddenFiles(const AFilename: String; const IncludedFiles, HiddenFiles: TStringList);
  49. procedure SaveKnownIncludedAndHiddenFiles(const AFilename: String; const IncludedFiles, HiddenFiles: TStringList);
  50. procedure DeleteKnownIncludedAndHiddenFiles(const AFilename: String);
  51. procedure LoadBreakPointLines(const AFilename: String; const BreakPointLines: TStringList);
  52. procedure SaveBreakPointLines(const AFilename: String; const BreakPointLines: TStringList);
  53. procedure DeleteBreakPointLines(const AFilename: String);
  54. function NewShortCutToText(const ShortCut: TShortCut): String;
  55. procedure SetFakeShortCutText(const MenuItem: TMenuItem; const S: String);
  56. procedure SetFakeShortCut(const MenuItem: TMenuItem; const Key: Word;
  57. const Shift: TShiftState); overload;
  58. procedure SetFakeShortCut(const MenuItem: TMenuItem; const ShortCut: TShortCut); overload;
  59. procedure SaveTextToFile(const Filename: String;
  60. const S: String; const SaveEncoding: TSaveEncoding);
  61. procedure AddLines(const ListBox: TListBox; const S: String; const AObject: TObject; const LineBreaks: Boolean; const Prefix: TAddLinesPrefix; const PrefixParam: Cardinal);
  62. procedure SetLowPriority(ALowPriority: Boolean; var SavePriorityClass: DWORD);
  63. procedure SetHelpFileDark(const Dark: Boolean);
  64. function GetHelpFile: String;
  65. function FindOptionsToSearchOptions(const FindOptions: TFindOptions;
  66. const RegEx: Boolean): TScintFindOptions; overload;
  67. function FindOptionsToSearchOptions(const MatchCase: Boolean;
  68. const RegEx: Boolean): TScintFindOptions; overload;
  69. function RegExToReplaceMode(const RegEx: Boolean): TScintReplaceMode;
  70. procedure StartAddRemovePrograms;
  71. function GetSourcePath(const AFilename: String): String;
  72. function ReadScriptLines(const ALines: TStringList; const ReadFromFile: Boolean;
  73. const ReadFromFileFilename: String; const NotReadFromFileMemo: TScintEdit): Integer;
  74. function CreateBitmapInfo(const Width, Height, BitCount: Integer): TBitmapInfo;
  75. function GetPreferredMemoFont: String;
  76. function DoubleAmp(const S: String): String;
  77. function HighContrastActive: Boolean;
  78. implementation
  79. uses
  80. ActiveX, ShlObj, ShellApi, CommDlg, SysUtils, IOUtils, StrUtils, ExtCtrls,
  81. Messages, DwmApi, Consts,
  82. Shared.CommonFunc, Shared.CommonFunc.Vcl, PathFunc, Shared.FileClass, NewUxTheme, NewNotebook,
  83. IDE.MainForm, IDE.Messages, Shared.ConfigIniFile;
  84. procedure InitFormFont(Form: TForm);
  85. var
  86. FontName: String;
  87. Metrics: TNonClientMetrics;
  88. begin
  89. begin
  90. Metrics.cbSize := SizeOf(Metrics);
  91. if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SizeOf(Metrics),
  92. @Metrics, 0) then
  93. FontName := Metrics.lfMessageFont.lfFaceName;
  94. { Only allow fonts that we know will fit the text correctly }
  95. if not SameText(FontName, 'Microsoft Sans Serif') and
  96. not SameText(FontName, 'Segoe UI') then
  97. FontName := 'Tahoma';
  98. end;
  99. Form.Font.Name := FontName;
  100. Form.Font.Size := 8;
  101. end;
  102. procedure SetControlWindowTheme(const WinControl: TWinControl; const Dark: Boolean);
  103. { Can be used for memos and listboxes to switch them to (or from) a native dark scrollbar }
  104. begin
  105. if UseThemes then begin
  106. WinControl.StyleName := 'Windows';
  107. if Dark then
  108. SetWindowTheme(WinControl.Handle, 'DarkMode_Explorer', nil)
  109. else
  110. SetWindowTheme(WinControl.Handle, nil, nil);
  111. end;
  112. end;
  113. var
  114. FormTheme: TTheme;
  115. procedure InitFormThemeInit(const Theme: TTheme);
  116. begin
  117. FormTheme := Theme;
  118. end;
  119. function InitFormTheme(const Form: TForm): Boolean;
  120. {$IF CompilerVersion >= 36.0}
  121. procedure HideGroupBoxFrames(const ParentControl: TWinControl);
  122. begin
  123. for var I := 0 to ParentControl.ControlCount-1 do begin
  124. const Control = ParentControl.Controls[I];
  125. if Control is TGroupBox then
  126. (Control as TGroupBox).ShowFrame := False;
  127. if Control is TWinControl then
  128. HideGroupBoxFrames(Control as TWinControl);
  129. end;
  130. end;
  131. {$ENDIF}
  132. { Assumes forms other then MainForm call this function only once during creation, and assumes they
  133. don't need any styling if the theme is non dark. Always styles MainForm. Returns True if it did
  134. style, False otherwise. }
  135. begin
  136. Result := (Form = MainForm) or FormTheme.Dark;
  137. if Result then begin
  138. Form.Color := InitFormThemeGetBkColor(Form = MainForm); { Prevents some flicker, but not all }
  139. { Based on https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes
  140. Unlike this article we check for Windows 10 Version 2004 because that's the first version
  141. that introduced DWMWA_USE_IMMERSIVE_DARK_MODE as 20 (the now documented value) instead of 19 }
  142. if WindowsVersionAtLeast(10, 0, 19041) then begin
  143. Form.StyleElements := Form.StyleElements - [seBorder];
  144. const DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
  145. var value: BOOL := FormTheme.Dark;
  146. DwmSetWindowAttribute(Form.Handle, DWMWA_USE_IMMERSIVE_DARK_MODE, @value, SizeOf(value));
  147. end;
  148. end;
  149. {$IF CompilerVersion >= 36.0}
  150. HideGroupBoxFrames(Form);
  151. {$ENDIF}
  152. end;
  153. function InitFormThemeGetBkColor(const WindowColor: Boolean): TColor;
  154. begin
  155. if WindowColor then begin
  156. Result := FormTheme.Colors[tcBack]; { This is white if not dark mode }
  157. if Result = clWhite then
  158. Result := GetSysColor(COLOR_WINDOW); { For high contrast themes }
  159. end else
  160. Result := FormTheme.Colors[tcToolBack]; { This is gray/btnface if not dark mode }
  161. end;
  162. function InitFormThemeIsDark: Boolean;
  163. begin
  164. Result := FormTheme.Dark;
  165. end;
  166. function GetDisplayFilename(const Filename: String): String;
  167. var
  168. Buf: array[0..MAX_PATH-1] of Char;
  169. begin
  170. if CommDlg.GetFileTitle(PChar(Filename), Buf, SizeOf(Buf) div SizeOf(Buf[0])) = 0 then
  171. Result := Buf
  172. else
  173. Result := Filename;
  174. end;
  175. function GetFileTitle(const Filename: String): String;
  176. begin
  177. if Filename = '' then
  178. Result := 'Untitled'
  179. else
  180. Result := Filename;
  181. end;
  182. function GetCleanFileNameOfFile(const Filename: String): String;
  183. begin
  184. var Files := TDirectory.GetFiles(PathExtractDir(Filename), PathExtractName(Filename));
  185. if Length(Files) = 1 then
  186. Result := Files[0]
  187. else
  188. Result := Filename;
  189. end;
  190. function GetLastWriteTimeOfFile(const Filename: String;
  191. LastWriteTime: PFileTime): Boolean;
  192. var
  193. H: THandle;
  194. begin
  195. H := CreateFile(PChar(Filename), 0, FILE_SHARE_READ or FILE_SHARE_WRITE,
  196. nil, OPEN_EXISTING, 0, 0);
  197. if H <> INVALID_HANDLE_VALUE then begin
  198. Result := GetFileTime(H, nil, nil, LastWriteTime);
  199. CloseHandle(H);
  200. end
  201. else
  202. Result := False;
  203. end;
  204. procedure AddFileToRecentDocs(const Filename: String);
  205. { Notifies the shell that a document has been opened. This will
  206. add the file to the Recent section of the app's Jump List.
  207. It is only necessary to call this function when the shell is unaware that
  208. a file is being opened. Files opened through Explorer or common dialogs get
  209. added to the Jump List automatically. }
  210. begin
  211. SHAddToRecentDocs(SHARD_PATHW, PChar(Filename));
  212. end;
  213. function GenerateGuid: String;
  214. var
  215. Guid: TGUID;
  216. P: PWideChar;
  217. begin
  218. if CoCreateGuid(Guid) <> S_OK then
  219. raise Exception.Create('CoCreateGuid failed');
  220. if StringFromCLSID(Guid, P) <> S_OK then
  221. raise Exception.Create('StringFromCLSID failed');
  222. try
  223. Result := P;
  224. finally
  225. CoTaskMemFree(P);
  226. end;
  227. end;
  228. function ISPPInstalled: Boolean;
  229. begin
  230. Result := NewFileExists(PathExtractPath(NewParamStr(0)) + 'ISPP.dll');
  231. end;
  232. function IsISPPBuiltins(const Filename: String): Boolean;
  233. begin
  234. Result := PathCompare(PathExtractName(Filename), 'ISPPBuiltins.iss') = 0;
  235. end;
  236. var
  237. WindowsVersion: Cardinal;
  238. function WindowsVersionAtLeast(const AMajor, AMinor: Byte; const ABuild: Word): Boolean;
  239. begin
  240. Result := WindowsVersion >= Cardinal((AMajor shl 24) or (AMinor shl 16) or ABuild);
  241. end;
  242. function IsWindows10: Boolean;
  243. begin
  244. Result := WindowsVersionAtLeast(10, 0);
  245. end;
  246. function IsWindows11: Boolean;
  247. begin
  248. Result := WindowsVersionAtLeast(10, 0, 22000);
  249. end;
  250. function GetDefaultThemeType: TThemeType;
  251. var
  252. K: HKEY;
  253. Size, AppsUseLightTheme: DWORD;
  254. begin
  255. Result := ttModernLight;
  256. if IsWindows10 and (RegOpenKeyExView(rvDefault, HKEY_CURRENT_USER, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize', 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS) then begin
  257. Size := SizeOf(AppsUseLightTheme);
  258. if (RegQueryValueEx(K, 'AppsUseLightTheme', nil, nil, @AppsUseLightTheme, @Size) = ERROR_SUCCESS) and (AppsUseLightTheme = 0) then
  259. Result := ttModernDark;
  260. RegCloseKey(K);
  261. end;
  262. end;
  263. function GetDefaultKeyMappingType: TKeyMappingType;
  264. begin
  265. Result := kmtDelphi;
  266. end;
  267. function GetDefaultMemoKeyMappingType: TIDEScintKeyMappingType;
  268. begin
  269. Result := kmtDefault;
  270. end;
  271. procedure LaunchFileOrURL(const AFilename: String; const AParameters: String = '');
  272. begin
  273. { SEE_MASK_FLAG_NO_UI isn't used, so error dialogs are possible }
  274. const OwnerWnd = GetOwnerWndForMessageBox;
  275. const WindowList = DisableTaskWindows(OwnerWnd);
  276. try
  277. const Dir = GetSystemDir;
  278. var Info: TShellExecuteInfo;
  279. FillChar(Info, SizeOf(Info), 0);
  280. Info.cbSize := SizeOf(Info);
  281. Info.fMask := SEE_MASK_NOASYNC;
  282. Info.Wnd := OwnerWnd;
  283. Info.lpVerb := 'open';
  284. Info.lpFile := PChar(AFilename);
  285. Info.lpParameters := PChar(AParameters);
  286. Info.lpDirectory := PChar(Dir);
  287. Info.nShow := SW_SHOWNORMAL;
  288. ShellExecuteEx(@Info);
  289. finally
  290. EnableTaskWindows(WindowList);
  291. end;
  292. end;
  293. procedure OpenDonateSite;
  294. begin
  295. LaunchFileOrURL('https://jrsoftware.org/isdonate.php');
  296. end;
  297. procedure OpenMailingListSite;
  298. begin
  299. LaunchFileOrURL('https://jrsoftware.org/ismail.php');
  300. end;
  301. procedure ClearMRUList(const MRUList: TStringList; const Section: String);
  302. var
  303. Ini: TConfigIniFile;
  304. begin
  305. Ini := TConfigIniFile.Create;
  306. try
  307. MRUList.Clear;
  308. Ini.EraseSection(Section);
  309. finally
  310. Ini.Free;
  311. end;
  312. end;
  313. procedure ReadMRUList(const MRUList: TStringList; const Section, Ident: String);
  314. { Loads a list of MRU items from the registry }
  315. var
  316. Ini: TConfigIniFile;
  317. I: Integer;
  318. S: String;
  319. begin
  320. Ini := TConfigIniFile.Create;
  321. try
  322. MRUList.Clear;
  323. for I := 0 to MRUListMaxCount-1 do begin
  324. S := Ini.ReadString(Section, Ident + IntToStr(I), '');
  325. if S <> '' then MRUList.Add(S);
  326. end;
  327. finally
  328. Ini.Free;
  329. end;
  330. end;
  331. procedure ModifyMRUList(const MRUList: TStringList; const Section, Ident: String;
  332. const AItem: String; const AddNewItem: Boolean; CompareProc: TMRUItemCompareProc);
  333. var
  334. I: Integer;
  335. Ini: TConfigIniFile;
  336. S: String;
  337. begin
  338. I := 0;
  339. while I < MRUList.Count do begin
  340. if CompareProc(MRUList[I], AItem) = 0 then
  341. MRUList.Delete(I)
  342. else
  343. Inc(I);
  344. end;
  345. if AddNewItem then
  346. MRUList.Insert(0, AItem);
  347. while MRUList.Count > MRUListMaxCount do
  348. MRUList.Delete(MRUList.Count-1);
  349. { Save new MRU items }
  350. Ini := TConfigIniFile.Create;
  351. try
  352. { MRU list }
  353. for I := 0 to MRUListMaxCount-1 do begin
  354. if I < MRUList.Count then
  355. S := MRUList[I]
  356. else
  357. S := '';
  358. Ini.WriteString(Section, Ident + IntToStr(I), S);
  359. end;
  360. finally
  361. Ini.Free;
  362. end;
  363. end;
  364. procedure LoadConfigIniList(const AIni: TConfigIniFile; const ASection, AIdent: String;
  365. const AList: TStringList; const ADelimiter: Char);
  366. begin
  367. if ASection = '' then
  368. raise Exception.Create('ASection must be set');
  369. var OldDelimiter := AList.Delimiter;
  370. AList.Delimiter := ADelimiter;
  371. try
  372. AList.DelimitedText := AIni.ReadString(ASection, AIdent, '');
  373. finally
  374. AList.Delimiter := OldDelimiter;
  375. end;
  376. end;
  377. procedure DeleteConfigIniList(const AIni: TConfigIniFile; const ASection, AIdent: String);
  378. begin
  379. if ASection = '' then
  380. raise Exception.Create('ASection must be set');
  381. AIni.DeleteKey(ASection, AIdent);
  382. end;
  383. procedure SaveConfigIniList(const AIni: TConfigIniFile; const ASection, AIdent: String;
  384. const AList: TStringList; const ADelimiter: Char);
  385. begin
  386. if AList.Count = 0 then begin
  387. DeleteConfigIniList(AIni, ASection, AIdent);
  388. Exit;
  389. end;
  390. if ASection = '' then
  391. raise Exception.Create('ASection must be set');
  392. var OldDelimiter := AList.Delimiter;
  393. AList.Delimiter := ADelimiter;
  394. try
  395. AIni.WriteString(ASection, AIdent, AList.DelimitedText);
  396. finally
  397. AList.Delimiter := OldDelimiter;
  398. end;
  399. end;
  400. procedure LoadKnownIncludedAndHiddenFiles(const AFilename: String; const IncludedFiles, HiddenFiles: TStringList);
  401. begin
  402. var Ini := TConfigIniFile.Create;
  403. try
  404. LoadConfigIniList(Ini, 'IncludedFilesHistory', AFilename, IncludedFiles, '*');
  405. LoadConfigIniList(Ini, 'HiddenFilesHistory', AFilename, HiddenFiles, '*');
  406. finally
  407. Ini.Free;
  408. end;
  409. end;
  410. procedure SaveKnownIncludedAndHiddenFiles(const AFilename: String; const IncludedFiles, HiddenFiles: TStringList);
  411. begin
  412. var Ini := TConfigIniFile.Create;
  413. try
  414. SaveConfigIniList(Ini, 'IncludedFilesHistory', AFilename, IncludedFiles, '*');
  415. SaveConfigIniList(Ini, 'HiddenFilesHistory', AFilename, HiddenFiles, '*');
  416. finally
  417. Ini.Free;
  418. end;
  419. end;
  420. procedure DeleteKnownIncludedAndHiddenFiles(const AFilename: String);
  421. begin
  422. var Ini := TConfigIniFile.Create;
  423. try
  424. DeleteConfigIniList(Ini, 'IncludedFilesHistory', AFilename);
  425. DeleteConfigIniList(Ini, 'HiddenFilesHistory', AFilename);
  426. finally
  427. Ini.Free;
  428. end;
  429. end;
  430. procedure LoadBreakPointLines(const AFilename: String; const BreakPointLines: TStringList);
  431. begin
  432. var Ini := TConfigIniFile.Create;
  433. try
  434. LoadConfigIniList(Ini, 'BreakPointLines', AFilename, BreakPointLines, ',');
  435. finally
  436. Ini.Free;
  437. end;
  438. end;
  439. procedure SaveBreakPointLines(const AFilename: String; const BreakPointLines: TStringList);
  440. begin
  441. var Ini := TConfigIniFile.Create;
  442. try
  443. SaveConfigIniList(Ini, 'BreakPointLines', AFilename, BreakPointLines, ',');
  444. finally
  445. Ini.Free;
  446. end;
  447. end;
  448. procedure DeleteBreakPointLines(const AFilename: String);
  449. begin
  450. var Ini := TConfigIniFile.Create;
  451. try
  452. DeleteConfigIniList(Ini, 'BreakPointLines', AFilename);
  453. finally
  454. Ini.Free;
  455. end;
  456. end;
  457. function NewShortCutToText(const ShortCut: TShortCut): String;
  458. { This function is better than Delphi's ShortCutToText function because it works
  459. for dead keys. A dead key is a key which waits for the user to press another
  460. key so it can be combined. For example `+e=è. Pressing space after a dead key
  461. produces the dead key char itself. For example `+space=`. }
  462. const
  463. { List of chars ShortCutToText knows about and doesn't rely on Win32's
  464. GetKeyNameText for, taken from Vcl.Menus.pas }
  465. OKKeys = [$08, $09, $0D, $1B, $20..$28, $2D..$2E, $30..$39, $41..$5A, $70..$87];
  466. begin
  467. Result := '';
  468. var Key := LoByte(Word(ShortCut));
  469. if not (Key in OKKeys) then begin
  470. { ShortCutToText will use Win32's GetKeyNameText for this key and if it's
  471. a dead key this gives long names like 'ACCENT CIRCONFLEXE' instead of a
  472. short name like '^'. Long names are not what we want so handle these dead
  473. keys ourselves and use ToUnicode instead of GetKeyNameText to find the
  474. short name. For non-dead keys we always call ShortCutToText even if
  475. ToUnicode might work as well. }
  476. var ScanCode := MapVirtualKey(Key, MAPVK_VK_TO_VSC);
  477. if ScanCode <> 0 then begin
  478. var KeyboardState: TKeyboardState;
  479. GetKeyboardState(KeyboardState);
  480. const TempSize = 64; { Same as Vcl.Touch.Keyboard.pas }
  481. var TempStr: String;
  482. SetLength(TempStr, TempSize);
  483. ZeroMemory(@TempStr[1], TempSize * SizeOf(Char));
  484. var Size := ToUnicode(Key, ScanCode, KeyboardState, @TempStr[1], TempSize, 0);
  485. if Size = -1 then begin
  486. { This was a dead key, now stored in TempStr. Add space to get the dead
  487. key char itself. }
  488. ScanCode := MapVirtualKey(VK_SPACE, MAPVK_VK_TO_VSC);
  489. if ScanCode <> 0 then begin
  490. Size := ToUnicode(VK_SPACE, ScanCode, KeyboardState, @TempStr[1], TempSize, 0);
  491. if Size = 1 then begin
  492. var Name := TempStr[1];
  493. if ShortCut and scShift <> 0 then Result := Result + SmkcShift;
  494. if ShortCut and scCtrl <> 0 then Result := Result + SmkcCtrl;
  495. if ShortCut and scAlt <> 0 then Result := Result + SmkcAlt;
  496. Result := Result + Name;
  497. end;
  498. end;
  499. end;
  500. end else begin
  501. { This virtual key has no scan code meaning it's impossible to enter with
  502. the current keyboard layout (for example French AZERTY + VK_OEM_MINUS).
  503. We can just exit because calling ShortCutToText is pointless. }
  504. Exit;
  505. end;
  506. end;
  507. if Result = '' then
  508. Result := ShortCutToText(ShortCut);
  509. { Example CompForm test code:
  510. SetFakeShortCut(HDonate, ShortCut(VK_OEM_1, []));
  511. SetFakeShortCut(HShortcutsDoc, ShortCut(VK_OEM_PLUS, []));
  512. SetFakeShortCut(HDoc, ShortCut(VK_OEM_COMMA, []));
  513. SetFakeShortCut(HExamples, ShortCut(VK_OEM_MINUS, []));
  514. SetFakeShortCut(HFaq, ShortCut(VK_OEM_PERIOD, []));
  515. SetFakeShortCut(HMailingList, ShortCut(VK_OEM_2, []));
  516. SetFakeShortCut(HWhatsNew, ShortCut(VK_OEM_3, []));
  517. SetFakeShortCut(HWebsite, ShortCut(VK_OEM_4, []));
  518. SetFakeShortCut(HISPPDoc, ShortCut(VK_OEM_5, []));
  519. SetFakeShortCut(HAbout, ShortCut(VK_OEM_6, []));
  520. SetFakeShortCut(TAddRemovePrograms, ShortCut(VK_OEM_7, []));
  521. Without our dead key handling this produces for example:
  522. -US International + VK_OEM_3: "GRAVE"
  523. -French AZERTY + VK_OEM_7: "ACCENT CIRCONFLEXE"
  524. To add a keyboard layout follow the instructions at
  525. https://www.thewindowsclub.com/add-or-remove-keyboard-layout-in-windows-11
  526. and then switch to the language using the task bar's language bar.
  527. Also see https://code.visualstudio.com/docs/getstarted/keybindings#_keyboard-layouts }
  528. end;
  529. procedure SetFakeShortCutText(const MenuItem: TMenuItem; const S: String);
  530. begin
  531. var Caption := MenuItem.Caption;
  532. var P := Pos(#9, Caption);
  533. if P <> 0 then
  534. Delete(Caption, P, MaxInt);
  535. if S <> '' then
  536. MenuItem.Caption := Caption + #9 + S
  537. else
  538. MenuItem.Caption := Caption;
  539. end;
  540. procedure SetFakeShortCut(const MenuItem: TMenuItem; const Key: Word;
  541. const Shift: TShiftState);
  542. begin
  543. SetFakeShortCut(MenuItem, ShortCut(Key, Shift));
  544. end;
  545. procedure SetFakeShortCut(const MenuItem: TMenuItem; const ShortCut: TShortCut);
  546. begin
  547. SetFakeShortCutText(MenuItem, NewShortCutToText(ShortCut));
  548. end;
  549. procedure SaveTextToFile(const Filename: String;
  550. const S: String; const SaveEncoding: TSaveEncoding);
  551. var
  552. AnsiMode: Boolean;
  553. AnsiStr: AnsiString;
  554. F: TTextFileWriter;
  555. begin
  556. AnsiMode := False;
  557. if SaveEncoding = seAuto then begin
  558. AnsiStr := AnsiString(S);
  559. if S = String(AnsiStr) then
  560. AnsiMode := True;
  561. end;
  562. F := TTextFileWriter.Create(Filename, fdCreateAlways, faWrite, fsNone);
  563. try
  564. if AnsiMode then
  565. F.WriteAnsi(AnsiStr)
  566. else begin
  567. F.UTF8WithoutBOM := SaveEncoding <> seUTF8WithBOM;
  568. F.Write(S);
  569. end;
  570. finally
  571. F.Free;
  572. end;
  573. end;
  574. procedure AddLines(const ListBox: TListBox; const S: String; const AObject: TObject; const LineBreaks: Boolean; const Prefix: TAddLinesPrefix; const PrefixParam: Cardinal);
  575. var
  576. ST: TSystemTime;
  577. LineNumber: Cardinal;
  578. procedure AddLine(S: String);
  579. var
  580. TimestampPrefixTab: Boolean;
  581. DC: HDC;
  582. Size: TSize;
  583. begin
  584. TimestampPrefixTab := False;
  585. case Prefix of
  586. alpTimestamp:
  587. begin
  588. if LineNumber = 0 then begin
  589. { Don't forget about ListBox's DrawItem if you change the format of the following timestamp. }
  590. Insert(Format('[%.2u%s%.2u%s%.2u%s%.3u] ', [ST.wHour, FormatSettings.TimeSeparator,
  591. ST.wMinute, FormatSettings.TimeSeparator, ST.wSecond, FormatSettings.DecimalSeparator,
  592. ST.wMilliseconds]), S, 1);
  593. end else begin
  594. Insert(#9, S, 1); { Not actually painted - just for Ctrl+C }
  595. TimestampPrefixTab := True;
  596. end;
  597. end;
  598. alpCountdown:
  599. begin
  600. Insert(Format('[%.2d] ', [PrefixParam-LineNumber]), S, 1);
  601. end;
  602. end;
  603. try
  604. ListBox.TopIndex := ListBox.Items.AddObject(S, AObject);
  605. except
  606. on EOutOfResources do begin
  607. ListBox.Clear;
  608. SendMessage(ListBox.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
  609. ListBox.Items.Add(SCompilerStatusReset);
  610. ListBox.TopIndex := ListBox.Items.AddObject(S, AObject);
  611. end;
  612. end;
  613. DC := GetDC(0);
  614. try
  615. SelectObject(DC, ListBox.Font.Handle);
  616. if TimestampPrefixTab then
  617. GetTextExtentPoint(DC, PChar(S)+1, Length(S)-1, Size)
  618. else
  619. GetTextExtentPoint(DC, PChar(S), Length(S), Size);
  620. finally
  621. ReleaseDC(0, DC);
  622. end;
  623. Inc(Size.cx, 5);
  624. if TimestampPrefixTab then
  625. Inc(Size.cx, PrefixParam);
  626. if Size.cx > SendMessage(ListBox.Handle, LB_GETHORIZONTALEXTENT, 0, 0) then
  627. SendMessage(ListBox.Handle, LB_SETHORIZONTALEXTENT, Size.cx, 0);
  628. Inc(LineNumber);
  629. end;
  630. var
  631. LineStart, I: Integer;
  632. LastWasCR: Boolean;
  633. begin
  634. GetLocalTime(ST);
  635. if LineBreaks then begin
  636. LineNumber := 0;
  637. LineStart := 1;
  638. LastWasCR := False;
  639. { Call AddLine for each line. CR, LF, and CRLF line breaks are supported. }
  640. for I := 1 to Length(S) do begin
  641. if S[I] = #13 then begin
  642. AddLine(Copy(S, LineStart, I - LineStart));
  643. LineStart := I + 1;
  644. LastWasCR := True;
  645. end
  646. else begin
  647. if S[I] = #10 then begin
  648. if not LastWasCR then
  649. AddLine(Copy(S, LineStart, I - LineStart));
  650. LineStart := I + 1;
  651. end;
  652. LastWasCR := False;
  653. end;
  654. end;
  655. AddLine(Copy(S, LineStart, Maxint));
  656. end else
  657. AddLine(S);
  658. end;
  659. procedure SetLowPriority(ALowPriority: Boolean; var SavePriorityClass: DWORD);
  660. begin
  661. if ALowPriority then begin
  662. { Save current priority and change to 'low' }
  663. if SavePriorityClass = 0 then
  664. SavePriorityClass := GetPriorityClass(GetCurrentProcess);
  665. SetPriorityClass(GetCurrentProcess, IDLE_PRIORITY_CLASS);
  666. end
  667. else begin
  668. { Restore original priority }
  669. if SavePriorityClass <> 0 then begin
  670. SetPriorityClass(GetCurrentProcess, SavePriorityClass);
  671. SavePriorityClass := 0;
  672. end;
  673. end;
  674. end;
  675. var
  676. HelpFileDark: Boolean;
  677. procedure SetHelpFileDark(const Dark: Boolean);
  678. begin
  679. HelpFileDark := Dark;
  680. end;
  681. function GetHelpFile: String;
  682. begin
  683. Result := Format('%sisetup%s.chm', [PathExtractPath(NewParamStr(0)) {$IFDEF DEBUG} + '..\..\Files\' {$ENDIF}, IfThen(HelpFileDark, '-dark', '')]);
  684. end;
  685. function FindOptionsToSearchOptions(const FindOptions: TFindOptions;
  686. const RegEx: Boolean): TScintFindOptions;
  687. begin
  688. Result := [];
  689. if frMatchCase in FindOptions then
  690. Include(Result, sfoMatchCase);
  691. if frWholeWord in FindOptions then
  692. Include(Result, sfoWholeWord);
  693. if RegEx then
  694. Include(Result, sfoRegEx);
  695. end;
  696. function FindOptionsToSearchOptions(const MatchCase: Boolean;
  697. const RegEx: Boolean): TScintFindOptions; overload;
  698. begin
  699. Result := [];
  700. if MatchCase then
  701. Include(Result, sfoMatchCase);
  702. if RegEx then
  703. Include(Result, sfoRegEx);
  704. end;
  705. function RegExToReplaceMode(const RegEx: Boolean): TScintReplaceMode;
  706. begin
  707. if RegEx then
  708. Result := srmRegEx
  709. else
  710. Result := srmMinimal;
  711. end;
  712. procedure StartAddRemovePrograms;
  713. var
  714. Dir: String;
  715. Wow64DisableWow64FsRedirectionFunc: function(var OldValue: Pointer): BOOL; stdcall;
  716. Wow64RevertWow64FsRedirectionFunc: function(OldValue: Pointer): BOOL; stdcall;
  717. RedirDisabled: Boolean;
  718. RedirOldValue: Pointer;
  719. StartupInfo: TStartupInfo;
  720. ProcessInfo: TProcessInformation;
  721. begin
  722. Dir := GetSystemDir;
  723. FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  724. StartupInfo.cb := SizeOf(StartupInfo);
  725. { Have to disable file system redirection because the 32-bit version of
  726. appwiz.cpl is buggy on XP x64 RC2 -- it doesn't show any Change/Remove
  727. buttons on 64-bit MSI entries, and it doesn't list non-MSI 64-bit apps
  728. at all. }
  729. Wow64DisableWow64FsRedirectionFunc := GetProcAddress(GetModuleHandle(kernel32),
  730. 'Wow64DisableWow64FsRedirection');
  731. Wow64RevertWow64FsRedirectionFunc := GetProcAddress(GetModuleHandle(kernel32),
  732. 'Wow64RevertWow64FsRedirection');
  733. RedirDisabled := Assigned(Wow64DisableWow64FsRedirectionFunc) and
  734. Assigned(Wow64RevertWow64FsRedirectionFunc) and
  735. Wow64DisableWow64FsRedirectionFunc(RedirOldValue);
  736. try
  737. Win32Check(CreateProcess(nil, PChar('"' + AddBackslash(Dir) + 'control.exe" appwiz.cpl'),
  738. nil, nil, False, 0, nil, PChar(Dir), StartupInfo, ProcessInfo));
  739. finally
  740. if RedirDisabled then
  741. Wow64RevertWow64FsRedirectionFunc(RedirOldValue);
  742. end;
  743. CloseHandle(ProcessInfo.hProcess);
  744. CloseHandle(ProcessInfo.hThread);
  745. end;
  746. function GetSourcePath(const AFilename: String): String;
  747. begin
  748. if AFilename <> '' then
  749. Result := PathExtractPath(AFilename)
  750. else begin
  751. { If the script was not saved, default to My Documents }
  752. Result := GetShellFolderPath(CSIDL_PERSONAL);
  753. if Result = '' then
  754. raise Exception.Create('GetShellFolderPath failed');
  755. end;
  756. end;
  757. function ReadScriptLines(const ALines: TStringList; const ReadFromFile: Boolean;
  758. const ReadFromFileFilename: String; const NotReadFromFileMemo: TScintEdit): Integer;
  759. function ContainsNullChar(const S: String): Boolean;
  760. var
  761. I: Integer;
  762. begin
  763. Result := False;
  764. for I := 1 to Length(S) do
  765. if S[I] = #0 then begin
  766. Result := True;
  767. Break;
  768. end;
  769. end;
  770. var
  771. F: TTextFileReader;
  772. I: Integer;
  773. begin
  774. if ReadFromFile then begin
  775. F := TTextFileReader.Create(ReadFromFileFilename, fdOpenExisting, faRead, fsRead);
  776. try
  777. while not F.Eof do
  778. ALines.Add(F.ReadLine);
  779. finally
  780. F.Free;
  781. end;
  782. end
  783. else begin
  784. ALines.Capacity := NotReadFromFileMemo.Lines.Count;
  785. ALines.Assign(NotReadFromFileMemo.Lines);
  786. end;
  787. { Check for null characters }
  788. for I := 0 to ALines.Count-1 do begin
  789. if ContainsNullChar(ALines[I]) then begin
  790. Result := I;
  791. Exit;
  792. end;
  793. end;
  794. Result := -1;
  795. end;
  796. function CreateBitmapInfo(const Width, Height, BitCount: Integer): TBitmapInfo;
  797. begin
  798. ZeroMemory(@Result, SizeOf(Result));
  799. Result.bmiHeader.biSize := SizeOf(Result.bmiHeader);
  800. Result.bmiHeader.biWidth := Width;
  801. Result.bmiHeader.biHeight := Height;
  802. Result.bmiHeader.biPlanes := 1;
  803. Result.bmiHeader.biBitCount := BitCount;
  804. Result.bmiHeader.biCompression := BI_RGB;
  805. end;
  806. var
  807. PreferredMemoFont: String;
  808. function GetPreferredMemoFont: String;
  809. begin
  810. Result := PreferredMemoFont;
  811. end;
  812. function DoubleAmp(const S: String): String;
  813. var
  814. I: Integer;
  815. begin
  816. Result := S;
  817. I := 1;
  818. while I <= Length(Result) do begin
  819. if Result[I] = '&' then begin
  820. Inc(I);
  821. Insert('&', Result, I);
  822. Inc(I);
  823. end
  824. else
  825. Inc(I, PathCharLength(S, I));
  826. end;
  827. end;
  828. function HighContrastActive: Boolean;
  829. begin
  830. var HighContrast: THighContrast;
  831. HighContrast.cbSize := SizeOf(HighContrast);
  832. Result := False;
  833. if SystemParametersInfo(SPI_GETHIGHCONTRAST, HighContrast.cbSize, @HighContrast, 0) then
  834. Result := (HighContrast.dwFlags and HCF_HIGHCONTRASTON) <> 0;
  835. end;
  836. initialization
  837. var OSVersionInfo: TOSVersionInfo;
  838. OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo);
  839. GetVersionEx(OSVersionInfo);
  840. WindowsVersion := (Byte(OSVersionInfo.dwMajorVersion) shl 24) or (Byte(OSVersionInfo.dwMinorVersion) shl 16) or Word(OSVersionInfo.dwBuildNumber);
  841. PreferredMemoFont := 'Consolas';
  842. if not FontExists(PreferredMemoFont) then
  843. PreferredMemoFont := 'Courier New';
  844. end.