Skip to content

Conversation

@hurnell
Copy link

@hurnell hurnell commented Sep 25, 2025

PolyModalAlert

A modal alert designed to do almost everything NSAlert can, with full UI flexibility.

Available options:

  • Multiple buttons
  • Multiple checkboxes
  • Combo box / dropdown menu
  • Text field
  • Configurable width

Usage

alert = iterm2.PolyModalAlert(_title, _subtitle, _windowId)

# alert.set_width(500) # 300 is the default

alert.add_button("button one")
alert.add_button("button two")
alert.add_button("button three")

alert.add_combobox_item("combobox item 1")
alert.add_combobox_item("combobox item 2 - default", 1)
alert.add_combobox_item("combobox item 3")


alert.add_checkbox_item('checkbox 1')
alert.add_checkbox_item('checkbox 2')
alert.add_checkbox_item('checkbox 3 - checked by default', 1)

alert.add_text_field('placeholder', 'default_text_field_value')

return await alert.async_run(connection)

return values

{
     'button': 'button three', 
     'tf_text': 'text you entered', 
     'combo': 'combobox item 1',
     'checks': ['checkbox 1’, 'checkbox 2']
}

@gnachman
Copy link
Owner

Sorry for the delay in getting to this. I've been swamped dealing with the release of 3.6.

NSUInteger idx = 0;
for (id checkbox in checkboxes) {
NSButton *currentCheckbox = [[NSButton alloc] initWithFrame:NSMakeRect(59, vertOffset, 82, 32)];
[currentCheckbox setButtonType:NSButtonTypeSwitch];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is overwritten on line 268

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line 268 removed

for (id checkbox in checkboxes) {
NSButton *currentCheckbox = [[NSButton alloc] initWithFrame:NSMakeRect(59, vertOffset, 82, 32)];
[currentCheckbox setButtonType:NSButtonTypeSwitch];
[currentCheckbox setTitle: checkbox];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: no space after a colon (here and in many other places)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated formatting and code style

[currentCheckbox setTitle: checkbox];
NSUInteger st = [[checkbox_defaults objectAtIndex: idx] intValue];
[currentCheckbox setState: st];
[currentCheckbox setButtonType:NSButtonTypeMomentaryLight]; //Set what type button You want
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove these comments

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed


@implementation iTermGetPolyModalAlertBuiltInFunction

+ (NSArray *) getCompletion:(NSMutableArray * ) items :(NSModalResponse ) tag :(NSString *) textFieldText :(NSArray *) buttons :(NSArray *) combobox_items {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nits: remove excess spaces in various places. Look at how method signatures look in existing code for guidance.

Use comboboxItems instead of combobox_items.

When possible, use lightweight generics for NSArray (e.g., NSArray<NSString *> *)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaces cleaned with ⌃⌥⇧I

changed to comboboxItems.

changed to eg

buttons:(NSArray<NSString *> *)buttons
checkboxDefaults:(NSArray<NSNumber *> *)checkboxDefaults


+ (NSArray *) getCompletion:(NSMutableArray * ) items :(NSModalResponse ) tag :(NSString *) textFieldText :(NSArray *) buttons :(NSArray *) combobox_items {
NSMutableArray *checkboxArray ;
if([items[1] isEqual: @"checkbox"]){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nits: should be if ([items[i] isEqual:@"checkbox"])

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to
if ([items[1] isEqual:@"checkbox"]) excess whitespace removed

NSUInteger st = [[checkbox_defaults objectAtIndex: idx] intValue];
[currentCheckbox setState: st];
[currentCheckbox setButtonType:NSButtonTypeMomentaryLight]; //Set what type button You want
[currentCheckbox setBezelStyle:NSBezelStylePush]; //Set what style You want
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to change the bezel style for a standard checkbox.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

[currentCheckbox setTarget:self];
[checkboxViews addObject: currentCheckbox];
[addedViews addObject: currentCheckbox];
idx ++;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No space after idx

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

NSMutableArray *checkboxViews = [[NSMutableArray alloc] initWithCapacity: [checkboxes count]];
NSUInteger idx = 0;
for (id checkbox in checkboxes) {
NSButton *currentCheckbox = [[NSButton alloc] initWithFrame:NSMakeRect(59, vertOffset, 82, 32)];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define a file-scope constant for the magic numbers 59. 82 and 32 are unncessary; since this uses auto layout, I expect the system will ignore the initial sizes values.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NSButton *currentCheckbox = [[NSButton alloc] initWithFrame:NSZeroRect];

"""Adds an array of checkbox items to the end of the list of
checkboxes."""
idx = 0
for item_text in items:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use zip(item_text, defaults) to simplify this loop

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert len(items) == len(defaults), "items and defaults must have the same length."
for item_text, default in zip(items, defaults):
    self.__checkbox_defaults.append(default)
    self.__checkboxes.append(item_text)

self.__combobox_default = item_text

def add_text_field(self, placeholder: str, default: str):
"""add a text field to the alert """
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: Use proper capitalization and punctuation

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""Add a text field to the alert."""

@hurnell hurnell requested a review from gnachman October 27, 2025 13:32

@dataclass
class PolyModalResult:
"""Dedicate object to return for PolyModalAlert """
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: should be "Dedicated"


+ (NSDictionary *)polyModalCompletion:(PolyModalItems *)polyModalItems
tag:(NSModalResponse)tag {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the values are all strings? If so use lightweight generics:

NSMutableDictionary<NSString *, NSString *> *result = [NSMutableDictionary dictionary];

And also the return type for the method should be NSDictionary<NSString *, NSString *> *

alert.informativeText = subtitle;
PolyModalItems *polyModalItems = [[PolyModalItems alloc] init];
polyModalItems.buttons = buttons;
for (NSString *buttonTitle in buttons) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure buttonTitle is a string since the type checker isn't able to do that for you (it only ensures that buttons is an array). See line 57 for an example.

alertWidth:(NSNumber *)alertWidth
windowID:(NSString *)windowID
completion:(iTermBuiltInFunctionCompletionBlock)completion {
NSAlert *alert = [[NSAlert alloc] init];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a check that checkboxes and checkboxDefaults have the same number of values. If they don't, call the completion block with an error and return early. See iTermArrayCountBuiltInFunction for an example of returning errors.

currentCheckbox.buttonType = NSButtonTypeSwitch;
currentCheckbox.title = checkboxTitle;
currentCheckbox.state = [[checkboxDefaults objectAtIndex:idx] integerValue];
currentCheckbox.target = self;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this. Since there is no action, clicking the button won't invoke a method.

NSWindow *window =
[[[iTermController sharedInstance] terminalWithGuid:windowID] window];
NSModalResponse responseTag;
if (window) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please verify that this lays out correctly in both macOS 26 and macOS 15. Typically we must call [alert layout] before running it. If you don't have a macOS 15 system to test with I can do this for you, just lmk

textField.stringValue = textFieldParams[1];
textField.placeholderString = textFieldParams[0];
[stackViews addObject:textField];
polyModalItems.textField = textField;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When there is a text field, you probably want to make it first responder. Look at showAlertWithTextFieldAccessoryWithTitle:subtitle:placeholder:defaultValue:windowID:completion: for how to do this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants