CompFunc.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. unit CompFunc;
  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. {$I VERSION.INC}
  10. interface
  11. uses
  12. Windows,
  13. Classes, Forms, Dialogs, Menus, StdCtrls,
  14. ScintEdit, CompScintEdit, ModernColors;
  15. const
  16. MRUListMaxCount = 10;
  17. type
  18. TMRUItemCompareProc = function(const S1, S2: String): Integer;
  19. TAddLinesPrefix = (alpNone, alpTimestamp, alpCountdown);
  20. procedure InitFormFont(Form: TForm);
  21. function GetDisplayFilename(const Filename: String): String;
  22. function GetFileTitle(const Filename: String): String;
  23. function GetLastWriteTimeOfFile(const Filename: String;
  24. LastWriteTime: PFileTime): Boolean;
  25. procedure AddFileToRecentDocs(const Filename: String);
  26. function GenerateGuid: String;
  27. function ISPPInstalled: Boolean;
  28. function IsISPPBuiltins(const Filename: String): Boolean;
  29. function ISCryptInstalled: Boolean;
  30. function GetDefaultThemeType: TThemeType;
  31. procedure OpenDonateSite;
  32. procedure OpenMailingListSite;
  33. procedure ReadMRUList(const MRUList: TStringList; const Section, Ident: String);
  34. procedure ModifyMRUList(const MRUList: TStringList; const Section, Ident: String;
  35. const AItem: String; const AddNewItem: Boolean; CompareProc: TMRUItemCompareProc);
  36. procedure LoadKnownIncludedAndHiddenFiles(const AFilename: String; const IncludedFiles, HiddenFiles: TStringList);
  37. procedure SaveKnownIncludedAndHiddenFiles(const AFilename: String; const IncludedFiles, HiddenFiles: TStringList);
  38. procedure DeleteKnownIncludedFiles(const AFilename: String);
  39. procedure SetFakeShortCutText(const MenuItem: TMenuItem; const S: String);
  40. procedure SetFakeShortCut(const MenuItem: TMenuItem; const Key: Word;
  41. const Shift: TShiftState);
  42. procedure SaveTextToFile(const Filename: String;
  43. const S: String; const SaveEncoding: TSaveEncoding);
  44. procedure AddLines(const ListBox: TListBox; const S: String; const AObject: TObject; const LineBreaks: Boolean; const Prefix: TAddLinesPrefix; const PrefixParam: Cardinal);
  45. procedure SetLowPriority(ALowPriority: Boolean; var SavePriorityClass: DWORD);
  46. function GetHelpFile: String;
  47. function FindOptionsToSearchOptions(const FindOptions: TFindOptions): TScintFindOptions;
  48. procedure StartAddRemovePrograms;
  49. function GetSourcePath(const AFilename: String): String;
  50. function ReadScriptLines(const ALines: TStringList; const ReadFromFile: Boolean;
  51. const ReadFromFileFilename: String; const NotReadFromFileMemo: TScintEdit): Integer;
  52. implementation
  53. uses
  54. ActiveX, ShlObj, ShellApi, CommDlg, SysUtils,
  55. Messages,
  56. CmnFunc2, PathFunc, FileClass,
  57. CompMsgs, CompTypes;
  58. procedure InitFormFont(Form: TForm);
  59. var
  60. FontName: String;
  61. Metrics: TNonClientMetrics;
  62. begin
  63. begin
  64. Metrics.cbSize := SizeOf(Metrics);
  65. if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SizeOf(Metrics),
  66. @Metrics, 0) then
  67. FontName := Metrics.lfMessageFont.lfFaceName;
  68. { Only allow fonts that we know will fit the text correctly }
  69. if not SameText(FontName, 'Microsoft Sans Serif') and
  70. not SameText(FontName, 'Segoe UI') then
  71. FontName := 'Tahoma';
  72. end;
  73. Form.Font.Name := FontName;
  74. Form.Font.Size := 8;
  75. end;
  76. function GetDisplayFilename(const Filename: String): String;
  77. var
  78. Buf: array[0..MAX_PATH-1] of Char;
  79. begin
  80. if CommDlg.GetFileTitle(PChar(Filename), Buf, SizeOf(Buf) div SizeOf(Buf[0])) = 0 then
  81. Result := Buf
  82. else
  83. Result := Filename;
  84. end;
  85. function GetFileTitle(const Filename: String): String;
  86. begin
  87. if Filename = '' then
  88. Result := 'Untitled'
  89. else
  90. Result := Filename;
  91. end;
  92. function GetLastWriteTimeOfFile(const Filename: String;
  93. LastWriteTime: PFileTime): Boolean;
  94. var
  95. H: THandle;
  96. begin
  97. H := CreateFile(PChar(Filename), 0, FILE_SHARE_READ or FILE_SHARE_WRITE,
  98. nil, OPEN_EXISTING, 0, 0);
  99. if H <> INVALID_HANDLE_VALUE then begin
  100. Result := GetFileTime(H, nil, nil, LastWriteTime);
  101. CloseHandle(H);
  102. end
  103. else
  104. Result := False;
  105. end;
  106. procedure AddFileToRecentDocs(const Filename: String);
  107. { Notifies the shell that a document has been opened. This will
  108. add the file to the Recent section of the app's Jump List.
  109. It is only necessary to call this function when the shell is unaware that
  110. a file is being opened. Files opened through Explorer or common dialogs get
  111. added to the Jump List automatically. }
  112. begin
  113. SHAddToRecentDocs(SHARD_PATHW, PChar(Filename));
  114. end;
  115. function GenerateGuid: String;
  116. var
  117. Guid: TGUID;
  118. P: PWideChar;
  119. begin
  120. if CoCreateGuid(Guid) <> S_OK then
  121. raise Exception.Create('CoCreateGuid failed');
  122. if StringFromCLSID(Guid, P) <> S_OK then
  123. raise Exception.Create('StringFromCLSID failed');
  124. try
  125. Result := P;
  126. finally
  127. CoTaskMemFree(P);
  128. end;
  129. end;
  130. function ISPPInstalled: Boolean;
  131. begin
  132. Result := NewFileExists(PathExtractPath(NewParamStr(0)) + 'ISPP.dll');
  133. end;
  134. function IsISPPBuiltins(const Filename: String): Boolean;
  135. begin
  136. Result := PathCompare(PathExtractName(Filename), 'ISPPBuiltins.iss') = 0;
  137. end;
  138. function ISCryptInstalled: Boolean;
  139. begin
  140. Result := NewFileExists(PathExtractPath(NewParamStr(0)) + 'iscrypt.dll');
  141. end;
  142. function GetDefaultThemeType: TThemeType;
  143. var
  144. K: HKEY;
  145. Size, AppsUseLightTheme: DWORD;
  146. begin
  147. Result := ttModernLight;
  148. if (Win32MajorVersion >= 10) and (RegOpenKeyExView(rvDefault, HKEY_CURRENT_USER, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize', 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS) then begin
  149. Size := SizeOf(AppsUseLightTheme);
  150. if (RegQueryValueEx(K, 'AppsUseLightTheme', nil, nil, @AppsUseLightTheme, @Size) = ERROR_SUCCESS) and (AppsUseLightTheme = 0) then
  151. Result := ttModernDark;
  152. RegCloseKey(K);
  153. end;
  154. end;
  155. procedure OpenDonateSite;
  156. begin
  157. ShellExecute(Application.Handle, 'open', 'https://jrsoftware.org/isdonate.php', nil,
  158. nil, SW_SHOW);
  159. end;
  160. procedure OpenMailingListSite;
  161. begin
  162. ShellExecute(Application.Handle, 'open', 'https://jrsoftware.org/ismail.php', nil,
  163. nil, SW_SHOW);
  164. end;
  165. procedure ReadMRUList(const MRUList: TStringList; const Section, Ident: String);
  166. { Loads a list of MRU items from the registry }
  167. var
  168. Ini: TConfigIniFile;
  169. I: Integer;
  170. S: String;
  171. begin
  172. Ini := TConfigIniFile.Create;
  173. try
  174. MRUList.Clear;
  175. for I := 0 to MRUListMaxCount-1 do begin
  176. S := Ini.ReadString(Section, Ident + IntToStr(I), '');
  177. if S <> '' then MRUList.Add(S);
  178. end;
  179. finally
  180. Ini.Free;
  181. end;
  182. end;
  183. procedure ModifyMRUList(const MRUList: TStringList; const Section, Ident: String;
  184. const AItem: String; const AddNewItem: Boolean; CompareProc: TMRUItemCompareProc);
  185. var
  186. I: Integer;
  187. Ini: TConfigIniFile;
  188. S: String;
  189. begin
  190. I := 0;
  191. while I < MRUList.Count do begin
  192. if CompareProc(MRUList[I], AItem) = 0 then
  193. MRUList.Delete(I)
  194. else
  195. Inc(I);
  196. end;
  197. if AddNewItem then
  198. MRUList.Insert(0, AItem);
  199. while MRUList.Count > MRUListMaxCount do
  200. MRUList.Delete(MRUList.Count-1);
  201. { Save new MRU items }
  202. Ini := TConfigIniFile.Create;
  203. try
  204. { MRU list }
  205. for I := 0 to MRUListMaxCount-1 do begin
  206. if I < MRUList.Count then
  207. S := MRUList[I]
  208. else
  209. S := '';
  210. Ini.WriteString(Section, Ident + IntToStr(I), S);
  211. end;
  212. finally
  213. Ini.Free;
  214. end;
  215. end;
  216. procedure LoadKnownIncludedAndHiddenFiles(const AFilename: String; const IncludedFiles, HiddenFiles: TStringList);
  217. begin
  218. var OldIncludedFilesDelimiter := IncludedFiles.Delimiter;
  219. var OldHiddenFilesDelimiter := HiddenFiles.Delimiter;
  220. var Ini := TConfigIniFile.Create;
  221. try
  222. IncludedFiles.Delimiter := '*';
  223. IncludedFiles.DelimitedText := Ini.ReadString('IncludedFilesHistory', AFilename, '');
  224. HiddenFiles.Delimiter := '*';
  225. HiddenFiles.DelimitedText := Ini.ReadString('HiddenFilesHistory', AFilename, '');
  226. finally
  227. Ini.Free;
  228. IncludedFiles.Delimiter := OldIncludedFilesDelimiter;
  229. HiddenFiles.Delimiter := OldHiddenFilesDelimiter;
  230. end;
  231. end;
  232. procedure SaveKnownIncludedAndHiddenFiles(const AFilename: String; const IncludedFiles, HiddenFiles: TStringList);
  233. begin
  234. if IncludedFiles.Count = 0 then begin
  235. DeleteKnownIncludedFiles(AFilename);
  236. Exit;
  237. end;
  238. if AFilename = '' then
  239. raise Exception.Create('AFilename must be set');
  240. var OldIncludedFilesDelimiter := IncludedFiles.Delimiter;
  241. var OldHiddenFilesDelimiter := HiddenFiles.Delimiter;
  242. var Ini := TConfigIniFile.Create;
  243. try
  244. IncludedFiles.Delimiter := '*';
  245. Ini.WriteString('IncludedFilesHistory', AFilename, IncludedFiles.DelimitedText);
  246. HiddenFiles.Delimiter := '*';
  247. Ini.WriteString('HiddenFilesHistory', AFilename, HiddenFiles.DelimitedText);
  248. finally
  249. Ini.Free;
  250. IncludedFiles.Delimiter := OldIncludedFilesDelimiter;
  251. HiddenFiles.Delimiter := OldHiddenFilesDelimiter;
  252. end;
  253. end;
  254. procedure DeleteKnownIncludedFiles(const AFilename: String);
  255. var
  256. Ini: TConfigIniFile;
  257. begin
  258. if AFilename = '' then
  259. raise Exception.Create('AFilename must be set');
  260. Ini := TConfigIniFile.Create;
  261. try
  262. Ini.DeleteKey('IncludedFilesHistory', AFilename);
  263. Ini.DeleteKey('HiddenFilesHistory', AFilename);
  264. finally
  265. Ini.Free;
  266. end;
  267. end;
  268. procedure SetFakeShortCutText(const MenuItem: TMenuItem; const S: String);
  269. begin
  270. MenuItem.Caption := MenuItem.Caption + #9 + S;
  271. end;
  272. procedure SetFakeShortCut(const MenuItem: TMenuItem; const Key: Word;
  273. const Shift: TShiftState);
  274. begin
  275. SetFakeShortCutText(MenuItem, ShortCutToText(ShortCut(Key, Shift)));
  276. end;
  277. procedure SaveTextToFile(const Filename: String;
  278. const S: String; const SaveEncoding: TSaveEncoding);
  279. var
  280. AnsiMode: Boolean;
  281. AnsiStr: AnsiString;
  282. F: TTextFileWriter;
  283. begin
  284. AnsiMode := False;
  285. if SaveEncoding = seAuto then begin
  286. AnsiStr := AnsiString(S);
  287. if S = String(AnsiStr) then
  288. AnsiMode := True;
  289. end;
  290. F := TTextFileWriter.Create(Filename, fdCreateAlways, faWrite, fsNone);
  291. try
  292. if AnsiMode then
  293. F.WriteAnsi(AnsiStr)
  294. else begin
  295. F.UTF8NoPreamble := SaveEncoding = seUTF8NoPreamble;
  296. F.Write(S);
  297. end;
  298. finally
  299. F.Free;
  300. end;
  301. end;
  302. procedure AddLines(const ListBox: TListBox; const S: String; const AObject: TObject; const LineBreaks: Boolean; const Prefix: TAddLinesPrefix; const PrefixParam: Cardinal);
  303. var
  304. ST: TSystemTime;
  305. LineNumber: Cardinal;
  306. procedure AddLine(S: String);
  307. var
  308. TimestampPrefixTab: Boolean;
  309. DC: HDC;
  310. Size: TSize;
  311. begin
  312. TimestampPrefixTab := False;
  313. case Prefix of
  314. alpTimestamp:
  315. begin
  316. if LineNumber = 0 then begin
  317. { Don't forget about ListBox's DrawItem if you change the format of the following timestamp. }
  318. Insert(Format('[%.2u%s%.2u%s%.2u%s%.3u] ', [ST.wHour, FormatSettings.TimeSeparator,
  319. ST.wMinute, FormatSettings.TimeSeparator, ST.wSecond, FormatSettings.DecimalSeparator,
  320. ST.wMilliseconds]), S, 1);
  321. end else begin
  322. Insert(#9, S, 1); { Not actually painted - just for Ctrl+C }
  323. TimestampPrefixTab := True;
  324. end;
  325. end;
  326. alpCountdown:
  327. begin
  328. Insert(Format('[%.2d] ', [PrefixParam-LineNumber]), S, 1);
  329. end;
  330. end;
  331. try
  332. ListBox.TopIndex := ListBox.Items.AddObject(S, AObject);
  333. except
  334. on EOutOfResources do begin
  335. ListBox.Clear;
  336. SendMessage(ListBox.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
  337. ListBox.Items.Add(SCompilerStatusReset);
  338. ListBox.TopIndex := ListBox.Items.Add(S);
  339. end;
  340. end;
  341. DC := GetDC(0);
  342. try
  343. SelectObject(DC, ListBox.Font.Handle);
  344. if TimestampPrefixTab then
  345. GetTextExtentPoint(DC, PChar(S)+1, Length(S)-1, Size)
  346. else
  347. GetTextExtentPoint(DC, PChar(S), Length(S), Size);
  348. finally
  349. ReleaseDC(0, DC);
  350. end;
  351. Inc(Size.cx, 5);
  352. if TimestampPrefixTab then
  353. Inc(Size.cx, PrefixParam);
  354. if Size.cx > SendMessage(ListBox.Handle, LB_GETHORIZONTALEXTENT, 0, 0) then
  355. SendMessage(ListBox.Handle, LB_SETHORIZONTALEXTENT, Size.cx, 0);
  356. Inc(LineNumber);
  357. end;
  358. var
  359. LineStart, I: Integer;
  360. LastWasCR: Boolean;
  361. begin
  362. GetLocalTime(ST);
  363. if LineBreaks then begin
  364. LineNumber := 0;
  365. LineStart := 1;
  366. LastWasCR := False;
  367. { Call AddLine for each line. CR, LF, and CRLF line breaks are supported. }
  368. for I := 1 to Length(S) do begin
  369. if S[I] = #13 then begin
  370. AddLine(Copy(S, LineStart, I - LineStart));
  371. LineStart := I + 1;
  372. LastWasCR := True;
  373. end
  374. else begin
  375. if S[I] = #10 then begin
  376. if not LastWasCR then
  377. AddLine(Copy(S, LineStart, I - LineStart));
  378. LineStart := I + 1;
  379. end;
  380. LastWasCR := False;
  381. end;
  382. end;
  383. AddLine(Copy(S, LineStart, Maxint));
  384. end else
  385. AddLine(S);
  386. end;
  387. procedure SetLowPriority(ALowPriority: Boolean; var SavePriorityClass: DWORD);
  388. begin
  389. if ALowPriority then begin
  390. { Save current priority and change to 'low' }
  391. if SavePriorityClass = 0 then
  392. SavePriorityClass := GetPriorityClass(GetCurrentProcess);
  393. SetPriorityClass(GetCurrentProcess, IDLE_PRIORITY_CLASS);
  394. end
  395. else begin
  396. { Restore original priority }
  397. if SavePriorityClass <> 0 then begin
  398. SetPriorityClass(GetCurrentProcess, SavePriorityClass);
  399. SavePriorityClass := 0;
  400. end;
  401. end;
  402. end;
  403. function GetHelpFile: String;
  404. begin
  405. Result := PathExtractPath(NewParamStr(0)) + 'isetup.chm';
  406. end;
  407. function FindOptionsToSearchOptions(const FindOptions: TFindOptions): TScintFindOptions;
  408. begin
  409. Result := [];
  410. if frMatchCase in FindOptions then
  411. Include(Result, sfoMatchCase);
  412. if frWholeWord in FindOptions then
  413. Include(Result, sfoWholeWord);
  414. end;
  415. procedure StartAddRemovePrograms;
  416. var
  417. Dir: String;
  418. Wow64DisableWow64FsRedirectionFunc: function(var OldValue: Pointer): BOOL; stdcall;
  419. Wow64RevertWow64FsRedirectionFunc: function(OldValue: Pointer): BOOL; stdcall;
  420. RedirDisabled: Boolean;
  421. RedirOldValue: Pointer;
  422. StartupInfo: TStartupInfo;
  423. ProcessInfo: TProcessInformation;
  424. begin
  425. Dir := GetSystemDir;
  426. FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  427. StartupInfo.cb := SizeOf(StartupInfo);
  428. { Have to disable file system redirection because the 32-bit version of
  429. appwiz.cpl is buggy on XP x64 RC2 -- it doesn't show any Change/Remove
  430. buttons on 64-bit MSI entries, and it doesn't list non-MSI 64-bit apps
  431. at all. }
  432. Wow64DisableWow64FsRedirectionFunc := GetProcAddress(GetModuleHandle(kernel32),
  433. 'Wow64DisableWow64FsRedirection');
  434. Wow64RevertWow64FsRedirectionFunc := GetProcAddress(GetModuleHandle(kernel32),
  435. 'Wow64RevertWow64FsRedirection');
  436. RedirDisabled := Assigned(Wow64DisableWow64FsRedirectionFunc) and
  437. Assigned(Wow64RevertWow64FsRedirectionFunc) and
  438. Wow64DisableWow64FsRedirectionFunc(RedirOldValue);
  439. try
  440. Win32Check(CreateProcess(nil, PChar('"' + AddBackslash(Dir) + 'control.exe" appwiz.cpl'),
  441. nil, nil, False, 0, nil, PChar(Dir), StartupInfo, ProcessInfo));
  442. finally
  443. if RedirDisabled then
  444. Wow64RevertWow64FsRedirectionFunc(RedirOldValue);
  445. end;
  446. CloseHandle(ProcessInfo.hProcess);
  447. CloseHandle(ProcessInfo.hThread);
  448. end;
  449. function GetSourcePath(const AFilename: String): String;
  450. begin
  451. if AFilename <> '' then
  452. Result := PathExtractPath(AFilename)
  453. else begin
  454. { If the script was not saved, default to My Documents }
  455. Result := GetShellFolderPath(CSIDL_PERSONAL);
  456. if Result = '' then
  457. raise Exception.Create('GetShellFolderPath failed');
  458. end;
  459. end;
  460. function ReadScriptLines(const ALines: TStringList; const ReadFromFile: Boolean;
  461. const ReadFromFileFilename: String; const NotReadFromFileMemo: TScintEdit): Integer;
  462. function ContainsNullChar(const S: String): Boolean;
  463. var
  464. I: Integer;
  465. begin
  466. Result := False;
  467. for I := 1 to Length(S) do
  468. if S[I] = #0 then begin
  469. Result := True;
  470. Break;
  471. end;
  472. end;
  473. var
  474. F: TTextFileReader;
  475. I: Integer;
  476. begin
  477. if ReadFromFile then begin
  478. F := TTextFileReader.Create(ReadFromFileFilename, fdOpenExisting, faRead, fsRead);
  479. try
  480. while not F.Eof do
  481. ALines.Add(F.ReadLine);
  482. finally
  483. F.Free;
  484. end;
  485. end
  486. else begin
  487. ALines.Capacity := NotReadFromFileMemo.Lines.Count;
  488. ALines.Assign(NotReadFromFileMemo.Lines);
  489. end;
  490. { Check for null characters }
  491. for I := 0 to ALines.Count-1 do begin
  492. if ContainsNullChar(ALines[I]) then begin
  493. Result := I;
  494. Exit;
  495. end;
  496. end;
  497. Result := -1;
  498. end;
  499. end.