IDE.HelperFunc.pas 29 KB

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