IDE.HelperFunc.pas 29 KB

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