IDE.HelperFunc.pas 28 KB

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