Skip to content

Commit 49490fd

Browse files
fix(installer): scrollable blocking-process dialog
MsgBox grew off-screen with long process lists so Yes/No were unreachable and X re-prompted forever. Use a fixed-height form with a scrollable list; X/Escape maps to No. Closes #1400 Co-authored-by: Cursor
1 parent f7cb9b4 commit 49490fd

1 file changed

Lines changed: 127 additions & 17 deletions

File tree

‎installer/setup.iss‎

Lines changed: 127 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3409,12 +3409,118 @@ begin
34093409
Result := ProcessList <> '';
34103410
end;
34113411
3412+
{ Scrollable process prompt. Buttons stay on-screen even with long lists.
3413+
Returns IDYES / IDNO / IDCANCEL (Cancel = "check again").
3414+
Form X / Escape returns IDNO so the user is never trapped in a re-prompt loop. }
3415+
function PromptBlockingProcessList(
3416+
const DialogTitle, IntroText, ProcessList, Footnote: String;
3417+
const YesCaption, NoCaption, CheckAgainCaption: String
3418+
): Integer;
3419+
var
3420+
Form: TSetupForm;
3421+
IntroLabel: TNewStaticText;
3422+
ListMemo: TNewMemo;
3423+
FootLabel: TNewStaticText;
3424+
YesButton: TNewButton;
3425+
NoButton: TNewButton;
3426+
CheckAgainButton: TNewButton;
3427+
ButtonTop: Integer;
3428+
ButtonWidth: Integer;
3429+
Gap: Integer;
3430+
ContentWidth: Integer;
3431+
ModalResult: Integer;
3432+
begin
3433+
Form := CreateCustomForm(ScaleX(520), ScaleY(420), False, False);
3434+
try
3435+
Form.Caption := DialogTitle;
3436+
3437+
ContentWidth := Form.ClientWidth - ScaleX(24);
3438+
3439+
IntroLabel := TNewStaticText.Create(Form);
3440+
IntroLabel.Parent := Form;
3441+
IntroLabel.Left := ScaleX(12);
3442+
IntroLabel.Top := ScaleY(12);
3443+
IntroLabel.Width := ContentWidth;
3444+
IntroLabel.AutoSize := False;
3445+
IntroLabel.WordWrap := True;
3446+
IntroLabel.Caption := IntroText;
3447+
IntroLabel.Height := ScaleY(48);
3448+
3449+
FootLabel := TNewStaticText.Create(Form);
3450+
FootLabel.Parent := Form;
3451+
FootLabel.Left := ScaleX(12);
3452+
FootLabel.Width := ContentWidth;
3453+
FootLabel.AutoSize := False;
3454+
FootLabel.WordWrap := True;
3455+
FootLabel.Caption := Footnote;
3456+
FootLabel.Height := ScaleY(52);
3457+
FootLabel.Top := Form.ClientHeight - ScaleY(96);
3458+
3459+
ButtonWidth := ScaleX(120);
3460+
Gap := ScaleX(8);
3461+
ButtonTop := Form.ClientHeight - ScaleY(40);
3462+
3463+
YesButton := TNewButton.Create(Form);
3464+
YesButton.Parent := Form;
3465+
YesButton.Caption := YesCaption;
3466+
YesButton.ModalResult := mrYes;
3467+
YesButton.Default := True;
3468+
YesButton.Width := ButtonWidth;
3469+
YesButton.Height := ScaleY(24);
3470+
YesButton.Left := Form.ClientWidth - ScaleX(12) - (ButtonWidth * 3) - (Gap * 2);
3471+
YesButton.Top := ButtonTop;
3472+
3473+
NoButton := TNewButton.Create(Form);
3474+
NoButton.Parent := Form;
3475+
NoButton.Caption := NoCaption;
3476+
NoButton.ModalResult := mrNo;
3477+
NoButton.Cancel := True; { Escape / title-bar X → No (escape hatch) }
3478+
NoButton.Width := ButtonWidth;
3479+
NoButton.Height := ScaleY(24);
3480+
NoButton.Left := YesButton.Left + ButtonWidth + Gap;
3481+
NoButton.Top := ButtonTop;
3482+
3483+
CheckAgainButton := TNewButton.Create(Form);
3484+
CheckAgainButton.Parent := Form;
3485+
CheckAgainButton.Caption := CheckAgainCaption;
3486+
CheckAgainButton.ModalResult := mrRetry;
3487+
CheckAgainButton.Width := ButtonWidth;
3488+
CheckAgainButton.Height := ScaleY(24);
3489+
CheckAgainButton.Left := NoButton.Left + ButtonWidth + Gap;
3490+
CheckAgainButton.Top := ButtonTop;
3491+
3492+
ListMemo := TNewMemo.Create(Form);
3493+
ListMemo.Parent := Form;
3494+
ListMemo.Left := ScaleX(12);
3495+
ListMemo.Top := IntroLabel.Top + IntroLabel.Height + ScaleY(8);
3496+
ListMemo.Width := ContentWidth;
3497+
ListMemo.Height := FootLabel.Top - ListMemo.Top - ScaleY(8);
3498+
ListMemo.ReadOnly := True;
3499+
ListMemo.ScrollBars := ssVertical;
3500+
ListMemo.WantReturns := True;
3501+
ListMemo.Text := ProcessList;
3502+
3503+
Form.FlipAndCenterIfNeeded(False, WizardForm, False);
3504+
ModalResult := Form.ShowModal();
3505+
finally
3506+
Form.Free();
3507+
end;
3508+
3509+
case ModalResult of
3510+
mrYes:
3511+
Result := IDYES;
3512+
mrRetry:
3513+
Result := IDCANCEL; { keep prior "check again" loop semantics }
3514+
else
3515+
Result := IDNO; { mrNo, mrCancel, X, Escape }
3516+
end;
3517+
end;
3518+
34123519
{ Returns True when install should continue (possibly after closing blockers). }
34133520
function ConfirmAndCloseBlockingInstallProcesses(): Boolean;
34143521
var
34153522
ProcessList: String;
34163523
Response: Integer;
3417-
MessageText: String;
34183524
begin
34193525
Result := True;
34203526
@@ -3433,15 +3539,17 @@ begin
34333539
if not ScanBlockingInstallProcesses(ProcessList) then
34343540
Break;
34353541
3436-
MessageText :=
3437-
'These Node.js processes are still running and can block shim updates:' + #13#10 + #13#10 +
3438-
ProcessList + #13#10 + #13#10 +
3439-
'(Includes any node/npm/npx/corepack/pnpm/yarn process on this PC, plus NVM install folders.)' + #13#10 + #13#10 +
3542+
Response := PromptBlockingProcessList(
3543+
'{#Name}',
3544+
'These Node.js processes are still running and can block shim updates:',
3545+
ProcessList,
34403546
'Yes = close them and continue' + #13#10 +
3441-
'No = continue without closing (shim rebuild may time out; you will need nvm reshim)' + #13#10 +
3442-
'Cancel = check again';
3443-
3444-
Response := MsgBox(MessageText, mbConfirmation, MB_YESNOCANCEL);
3547+
'No = continue without closing (shim rebuild may time out; run nvm reshim later)' + #13#10 +
3548+
'Check again = rescan after you close apps manually',
3549+
'&Yes',
3550+
'&No',
3551+
'Check &again'
3552+
);
34453553
34463554
if Response = IDYES then
34473555
begin
@@ -3998,7 +4106,6 @@ var
39984106
DataRoot: String;
39994107
ProcessList: String;
40004108
Response: Integer;
4001-
MessageText: String;
40024109
begin
40034110
Result := True;
40044111
@@ -4020,14 +4127,17 @@ begin
40204127
if not ScanBlockingUninstallProcesses(AppRoot, DataRoot, ProcessList) then
40214128
Break;
40224129
4023-
MessageText :=
4024-
'Close these NVM processes before uninstall can continue:' + #13#10 + #13#10 +
4025-
ProcessList + #13#10 + #13#10 +
4130+
Response := PromptBlockingProcessList(
4131+
'{#Name} Uninstall',
4132+
'Close these NVM processes before uninstall can continue:',
4133+
ProcessList,
40264134
'Yes = close them and continue' + #13#10 +
4027-
'No = cancel uninstall' + #13#10 +
4028-
'Cancel = check again';
4029-
4030-
Response := MsgBox(MessageText, mbConfirmation, MB_YESNOCANCEL);
4135+
'No = cancel uninstall' + #13#10 +
4136+
'Check again = rescan after you close apps manually',
4137+
'&Yes',
4138+
'&No',
4139+
'Check &again'
4140+
);
40314141
40324142
if Response = IDNO then
40334143
begin

0 commit comments

Comments
 (0)