From 1984d441acea1f43d868e54c928862ece2b2e55c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:32:07 +0000 Subject: [PATCH 1/3] Initial plan From d794226823d67505f8501d373a52c709ae74fba7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:39:37 +0000 Subject: [PATCH 2/3] Redesign app settings dialog with modern tabbed UI and additional settings Co-authored-by: danaspiegel <6631+danaspiegel@users.noreply.github.com> --- Table Tool/AppDelegate.m | 7 +- Table Tool/Constants.h | 10 + Table Tool/Constants.m | 8 + Table Tool/Document.m | 16 +- Table Tool/TTPreferencesWindowController.m | 326 +++++++++++++++++++-- 5 files changed, 340 insertions(+), 27 deletions(-) diff --git a/Table Tool/AppDelegate.m b/Table Tool/AppDelegate.m index 4539fc0..d54eeca 100644 --- a/Table Tool/AppDelegate.m +++ b/Table Tool/AppDelegate.m @@ -19,7 +19,12 @@ @implementation AppDelegate + (void)initialize { [[NSUserDefaults standardUserDefaults] registerDefaults:@{ TTShowLineNumbersKey: @YES, - TTTableFontSizeKey: @13 + TTTableFontSizeKey: @13, + TTAlternatingRowColorsKey: @YES, + TTRowHeightKey: @20.0, + TTDefaultEncodingKey: @(NSUTF8StringEncoding), + TTDefaultColumnSeparatorKey: @",", + TTDefaultFirstRowAsHeaderKey: @NO }]; } diff --git a/Table Tool/Constants.h b/Table Tool/Constants.h index a084af5..cad6d0e 100644 --- a/Table Tool/Constants.h +++ b/Table Tool/Constants.h @@ -21,3 +21,13 @@ extern NSString *TTLineNumberColumnIdentifier; extern NSString *TTShowLineNumbersKey; extern NSString *TTTableFontNameKey; extern NSString *TTTableFontSizeKey; +extern NSString *TTAlternatingRowColorsKey; +extern NSString *TTRowHeightKey; +extern NSString *TTDefaultEncodingKey; +extern NSString *TTDefaultColumnSeparatorKey; +extern NSString *TTDefaultFirstRowAsHeaderKey; + +#pragma mark Table View + +extern const CGFloat TTMinRowHeight; +extern const CGFloat TTMaxRowHeight; diff --git a/Table Tool/Constants.m b/Table Tool/Constants.m index 5b39e66..37e6d64 100644 --- a/Table Tool/Constants.m +++ b/Table Tool/Constants.m @@ -21,3 +21,11 @@ NSString *TTShowLineNumbersKey = @"showLineNumbers"; NSString *TTTableFontNameKey = @"tableFontName"; NSString *TTTableFontSizeKey = @"tableFontSize"; +NSString *TTAlternatingRowColorsKey = @"alternatingRowColors"; +NSString *TTRowHeightKey = @"rowHeight"; +NSString *TTDefaultEncodingKey = @"defaultEncoding"; +NSString *TTDefaultColumnSeparatorKey = @"defaultColumnSeparator"; +NSString *TTDefaultFirstRowAsHeaderKey = @"defaultFirstRowAsHeader"; + +const CGFloat TTMinRowHeight = 14.0; +const CGFloat TTMaxRowHeight = 60.0; diff --git a/Table Tool/Document.m b/Table Tool/Document.m index 06a57ba..e1bf3ea 100644 --- a/Table Tool/Document.m +++ b/Table Tool/Document.m @@ -73,7 +73,7 @@ - (void)windowControllerDidLoadNib:(NSWindowController *)aController { [super windowControllerDidLoadNib:aController]; dataCell = [self.tableView.tableColumns.firstObject dataCell]; [self updateTableColumns]; - [self applyTableFont]; + [self applyTableAppearance]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) @@ -92,6 +92,12 @@ - (void)windowControllerDidLoadNib:(NSWindowController *)aController { if(newFile){ _maxColumnNumber = 3; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSInteger defaultEncoding = [defaults integerForKey:TTDefaultEncodingKey]; + if (defaultEncoding > 0) _csvConfig.encoding = (NSStringEncoding)defaultEncoding; + NSString *defaultSeparator = [defaults stringForKey:TTDefaultColumnSeparatorKey]; + if (defaultSeparator.length > 0) _csvConfig.columnSeparator = defaultSeparator; + _csvConfig.firstRowAsHeader = [defaults boolForKey:TTDefaultFirstRowAsHeaderKey]; [self updateTableColumns]; [_data addObject:[[NSMutableArray alloc]init]]; [self.tableView reloadData]; @@ -113,7 +119,7 @@ - (void)close { [super close]; } --(void)applyTableFont { +-(void)applyTableAppearance { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *fontName = [defaults stringForKey:TTTableFontNameKey]; CGFloat fontSize = [defaults doubleForKey:TTTableFontSizeKey]; @@ -126,11 +132,15 @@ -(void)applyTableFont { font = [NSFont systemFontOfSize:fontSize]; } [dataCell setFont:font]; + self.tableView.usesAlternatingRowBackgroundColors = [defaults boolForKey:TTAlternatingRowColorsKey]; + CGFloat rowHeight = [defaults doubleForKey:TTRowHeightKey]; + if (rowHeight < TTMinRowHeight) rowHeight = 20; + self.tableView.rowHeight = rowHeight; [self.tableView reloadData]; } -(void)userDefaultsDidChange:(NSNotification *)notification { - [self applyTableFont]; + [self applyTableAppearance]; } diff --git a/Table Tool/TTPreferencesWindowController.m b/Table Tool/TTPreferencesWindowController.m index 37b554c..d05e1be 100644 --- a/Table Tool/TTPreferencesWindowController.m +++ b/Table Tool/TTPreferencesWindowController.m @@ -7,11 +7,24 @@ #import "TTPreferencesWindowController.h" #import "Constants.h" +#import "CSVConfiguration.h" @interface TTPreferencesWindowController () +// Appearance tab @property (nonatomic, strong) NSButton *fontButton; @property (nonatomic, strong) NSStepper *fontSizeStepper; -@property (nonatomic, strong) NSTextField *fontSizeLabel; +@property (nonatomic, strong) NSTextField *fontSizeField; + +// General tab +@property (nonatomic, strong) NSButton *showLineNumbersCheckbox; +@property (nonatomic, strong) NSButton *alternatingRowColorsCheckbox; +@property (nonatomic, strong) NSStepper *rowHeightStepper; +@property (nonatomic, strong) NSTextField *rowHeightField; + +// CSV Defaults tab +@property (nonatomic, strong) NSPopUpButton *encodingPopup; +@property (nonatomic, strong) NSPopUpButton *separatorPopup; +@property (nonatomic, strong) NSButton *firstRowAsHeaderCheckbox; @end @implementation TTPreferencesWindowController @@ -26,11 +39,12 @@ + (instancetype)sharedController { } - (instancetype)init { - NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 300, 120) + NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 440, 300) styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable backing:NSBackingStoreBuffered defer:NO]; window.title = @"Preferences"; + [window center]; self = [super initWithWindow:window]; if (self) { [self buildUI]; @@ -41,20 +55,137 @@ - (instancetype)init { - (void)buildUI { NSView *contentView = self.window.contentView; - // "Table Font:" label - NSTextField *fontLabel = [NSTextField labelWithString:@"Table Font:"]; + NSTabView *tabView = [[NSTabView alloc] init]; + tabView.translatesAutoresizingMaskIntoConstraints = NO; + [contentView addSubview:tabView]; + + [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[tabView]-0-|" + options:0 metrics:nil + views:@{@"tabView": tabView}]]; + [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[tabView]-0-|" + options:0 metrics:nil + views:@{@"tabView": tabView}]]; + + NSTabViewItem *generalTab = [[NSTabViewItem alloc] initWithIdentifier:@"general"]; + generalTab.label = @"General"; + generalTab.view = [self buildGeneralTabView]; + [tabView addTabViewItem:generalTab]; + + NSTabViewItem *appearanceTab = [[NSTabViewItem alloc] initWithIdentifier:@"appearance"]; + appearanceTab.label = @"Appearance"; + appearanceTab.view = [self buildAppearanceTabView]; + [tabView addTabViewItem:appearanceTab]; + + NSTabViewItem *csvTab = [[NSTabViewItem alloc] initWithIdentifier:@"csv"]; + csvTab.label = @"CSV Defaults"; + csvTab.view = [self buildCSVDefaultsTabView]; + [tabView addTabViewItem:csvTab]; +} + +#pragma mark - General Tab + +- (NSView *)buildGeneralTabView { + NSView *view = [[NSView alloc] init]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + // Show line numbers checkbox + self.showLineNumbersCheckbox = [NSButton checkboxWithTitle:@"Show line numbers" + target:self + action:@selector(showLineNumbersChanged:)]; + self.showLineNumbersCheckbox.translatesAutoresizingMaskIntoConstraints = NO; + self.showLineNumbersCheckbox.state = [defaults boolForKey:TTShowLineNumbersKey] ? NSControlStateValueOn : NSControlStateValueOff; + [view addSubview:self.showLineNumbersCheckbox]; + + // Alternating row colors checkbox + self.alternatingRowColorsCheckbox = [NSButton checkboxWithTitle:@"Use alternating row colors" + target:self + action:@selector(alternatingRowColorsChanged:)]; + self.alternatingRowColorsCheckbox.translatesAutoresizingMaskIntoConstraints = NO; + self.alternatingRowColorsCheckbox.state = [defaults boolForKey:TTAlternatingRowColorsKey] ? NSControlStateValueOn : NSControlStateValueOff; + [view addSubview:self.alternatingRowColorsCheckbox]; + + // Row height label + NSTextField *rowHeightLabel = [NSTextField labelWithString:@"Row height:"]; + rowHeightLabel.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:rowHeightLabel]; + + // Row height text field + self.rowHeightField = [[NSTextField alloc] init]; + self.rowHeightField.translatesAutoresizingMaskIntoConstraints = NO; + self.rowHeightField.doubleValue = [defaults doubleForKey:TTRowHeightKey]; + self.rowHeightField.formatter = [self integerNumberFormatter]; + self.rowHeightField.target = self; + self.rowHeightField.action = @selector(rowHeightFieldChanged:); + [view addSubview:self.rowHeightField]; + + // Row height stepper + self.rowHeightStepper = [[NSStepper alloc] init]; + self.rowHeightStepper.translatesAutoresizingMaskIntoConstraints = NO; + self.rowHeightStepper.minValue = TTMinRowHeight; + self.rowHeightStepper.maxValue = TTMaxRowHeight; + self.rowHeightStepper.increment = 1; + self.rowHeightStepper.valueWraps = NO; + self.rowHeightStepper.doubleValue = [defaults doubleForKey:TTRowHeightKey]; + self.rowHeightStepper.target = self; + self.rowHeightStepper.action = @selector(rowHeightStepperChanged:); + [view addSubview:self.rowHeightStepper]; + + // Row height points label + NSTextField *ptsLabel = [NSTextField labelWithString:@"points"]; + ptsLabel.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:ptsLabel]; + + NSDictionary *views = @{ + @"lineNumbers": self.showLineNumbersCheckbox, + @"alternating": self.alternatingRowColorsCheckbox, + @"rowHeightLabel": rowHeightLabel, + @"rowHeightField": self.rowHeightField, + @"rowHeightStepper": self.rowHeightStepper, + @"ptsLabel": ptsLabel + }; + + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[lineNumbers]->=20-|" + options:0 metrics:nil views:views]]; + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[alternating]->=20-|" + options:0 metrics:nil views:views]]; + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[rowHeightLabel]-8-[rowHeightField(48)]-2-[rowHeightStepper]-8-[ptsLabel]->=20-|" + options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-24-[lineNumbers]-12-[alternating]-12-[rowHeightLabel]->=20-|" + options:0 metrics:nil views:views]]; + return view; +} + +#pragma mark - Appearance Tab + +- (NSView *)buildAppearanceTabView { + NSView *view = [[NSView alloc] init]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + // Font label + NSTextField *fontLabel = [NSTextField labelWithString:@"Table font:"]; fontLabel.translatesAutoresizingMaskIntoConstraints = NO; - [contentView addSubview:fontLabel]; + [view addSubview:fontLabel]; - // Font button showing current font name - self.fontButton = [NSButton buttonWithTitle:[self currentFontDisplayName] target:self action:@selector(fontButtonClicked:)]; + // Font button + self.fontButton = [NSButton buttonWithTitle:[self currentFontDisplayName] + target:self + action:@selector(fontButtonClicked:)]; self.fontButton.translatesAutoresizingMaskIntoConstraints = NO; - [contentView addSubview:self.fontButton]; + [view addSubview:self.fontButton]; - // "Size:" label + // Size label NSTextField *sizeLabel = [NSTextField labelWithString:@"Size:"]; sizeLabel.translatesAutoresizingMaskIntoConstraints = NO; - [contentView addSubview:sizeLabel]; + [view addSubview:sizeLabel]; + + // Font size text field + self.fontSizeField = [[NSTextField alloc] init]; + self.fontSizeField.translatesAutoresizingMaskIntoConstraints = NO; + self.fontSizeField.doubleValue = [defaults doubleForKey:TTTableFontSizeKey]; + self.fontSizeField.formatter = [self integerNumberFormatter]; + self.fontSizeField.target = self; + self.fontSizeField.action = @selector(fontSizeFieldChanged:); + [view addSubview:self.fontSizeField]; // Font size stepper self.fontSizeStepper = [[NSStepper alloc] init]; @@ -63,29 +194,127 @@ - (void)buildUI { self.fontSizeStepper.maxValue = 72; self.fontSizeStepper.increment = 1; self.fontSizeStepper.valueWraps = NO; - self.fontSizeStepper.doubleValue = [[NSUserDefaults standardUserDefaults] doubleForKey:TTTableFontSizeKey]; + self.fontSizeStepper.doubleValue = [defaults doubleForKey:TTTableFontSizeKey]; self.fontSizeStepper.target = self; self.fontSizeStepper.action = @selector(fontSizeStepperChanged:); - [contentView addSubview:self.fontSizeStepper]; + [view addSubview:self.fontSizeStepper]; - // Font size label showing current size - self.fontSizeLabel = [NSTextField labelWithString:[NSString stringWithFormat:@"%.0f", self.fontSizeStepper.doubleValue]]; - self.fontSizeLabel.translatesAutoresizingMaskIntoConstraints = NO; - self.fontSizeLabel.alignment = NSTextAlignmentRight; - [contentView addSubview:self.fontSizeLabel]; + // Points label + NSTextField *ptLabel = [NSTextField labelWithString:@"points"]; + ptLabel.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:ptLabel]; - // Layout NSDictionary *views = @{ @"fontLabel": fontLabel, @"fontButton": self.fontButton, @"sizeLabel": sizeLabel, + @"sizeField": self.fontSizeField, @"stepper": self.fontSizeStepper, - @"sizeValue": self.fontSizeLabel + @"ptLabel": ptLabel + }; + + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[fontLabel]-8-[fontButton]->=20-|" + options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[sizeLabel]-8-[sizeField(48)]-2-[stepper]-8-[ptLabel]->=20-|" + options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-24-[fontLabel]-16-[sizeLabel]->=20-|" + options:0 metrics:nil views:views]]; + return view; +} + +#pragma mark - CSV Defaults Tab + +- (NSView *)buildCSVDefaultsTabView { + NSView *view = [[NSView alloc] init]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + // Encoding label + NSTextField *encodingLabel = [NSTextField labelWithString:@"Default encoding:"]; + encodingLabel.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:encodingLabel]; + + // Encoding popup + self.encodingPopup = [[NSPopUpButton alloc] init]; + self.encodingPopup.translatesAutoresizingMaskIntoConstraints = NO; + NSInteger savedEncoding = [defaults integerForKey:TTDefaultEncodingKey]; + for (NSArray *encoding in [CSVConfiguration supportedEncodings]) { + [self.encodingPopup addItemWithTitle:encoding[0]]; + self.encodingPopup.lastItem.tag = [encoding[1] integerValue]; + if ([encoding[1] integerValue] == savedEncoding) { + [self.encodingPopup selectItem:self.encodingPopup.lastItem]; + } + } + self.encodingPopup.target = self; + self.encodingPopup.action = @selector(encodingChanged:); + [view addSubview:self.encodingPopup]; + + // Separator label + NSTextField *separatorLabel = [NSTextField labelWithString:@"Default separator:"]; + separatorLabel.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:separatorLabel]; + + // Separator popup + self.separatorPopup = [[NSPopUpButton alloc] init]; + self.separatorPopup.translatesAutoresizingMaskIntoConstraints = NO; + NSString *savedSeparator = [defaults stringForKey:TTDefaultColumnSeparatorKey] ?: @","; + NSDictionary *separators = [self separatorNameToValueMap]; + NSArray *separatorOrder = [self separatorDisplayOrder]; + for (NSString *name in separatorOrder) { + [self.separatorPopup addItemWithTitle:name]; + if ([separators[name] isEqualToString:savedSeparator]) { + [self.separatorPopup selectItem:self.separatorPopup.lastItem]; + } + } + self.separatorPopup.target = self; + self.separatorPopup.action = @selector(separatorChanged:); + [view addSubview:self.separatorPopup]; + + // First row as header checkbox + self.firstRowAsHeaderCheckbox = [NSButton checkboxWithTitle:@"Use first row as header by default" + target:self + action:@selector(firstRowAsHeaderChanged:)]; + self.firstRowAsHeaderCheckbox.translatesAutoresizingMaskIntoConstraints = NO; + self.firstRowAsHeaderCheckbox.state = [defaults boolForKey:TTDefaultFirstRowAsHeaderKey] ? NSControlStateValueOn : NSControlStateValueOff; + [view addSubview:self.firstRowAsHeaderCheckbox]; + + NSDictionary *views = @{ + @"encodingLabel": encodingLabel, + @"encodingPopup": self.encodingPopup, + @"separatorLabel": separatorLabel, + @"separatorPopup": self.separatorPopup, + @"firstRowAsHeader": self.firstRowAsHeaderCheckbox }; - [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-16-[fontLabel]-8-[fontButton]->=8-|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; - [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-16-[sizeLabel]-8-[sizeValue(40)]-4-[stepper]->=8-|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; - [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-24-[fontLabel]-16-[sizeLabel]->=8-|" options:0 metrics:nil views:views]]; + CGFloat labelWidth = 130; + [view addConstraint:[NSLayoutConstraint constraintWithItem:encodingLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:labelWidth]]; + [view addConstraint:[NSLayoutConstraint constraintWithItem:separatorLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:labelWidth]]; + + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[encodingLabel]-8-[encodingPopup]->=20-|" + options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[separatorLabel]-8-[separatorPopup]->=20-|" + options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[firstRowAsHeader]->=20-|" + options:0 metrics:nil views:views]]; + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-24-[encodingLabel]-12-[separatorLabel]-12-[firstRowAsHeader]->=20-|" + options:0 metrics:nil views:views]]; + return view; +} + +#pragma mark - Helpers + +- (NSDictionary *)separatorNameToValueMap { + return @{@"Comma ( , )": @",", @"Semicolon ( ; )": @";", @"Tab ( ⇥ )": @"\t", @"Pipe ( | )": @"|"}; +} + +- (NSArray *)separatorDisplayOrder { + return @[@"Comma ( , )", @"Semicolon ( ; )", @"Tab ( ⇥ )", @"Pipe ( | )"]; +} + +- (NSNumberFormatter *)integerNumberFormatter { + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + formatter.numberStyle = NSNumberFormatterDecimalStyle; + formatter.maximumFractionDigits = 0; + return formatter; } - (NSString *)currentFontDisplayName { @@ -97,6 +326,30 @@ - (NSString *)currentFontDisplayName { return [NSFont systemFontOfSize:13].displayName; } +#pragma mark - General Tab Actions + +- (void)showLineNumbersChanged:(NSButton *)sender { + [[NSUserDefaults standardUserDefaults] setBool:(sender.state == NSControlStateValueOn) forKey:TTShowLineNumbersKey]; +} + +- (void)alternatingRowColorsChanged:(NSButton *)sender { + [[NSUserDefaults standardUserDefaults] setBool:(sender.state == NSControlStateValueOn) forKey:TTAlternatingRowColorsKey]; +} + +- (void)rowHeightFieldChanged:(NSTextField *)sender { + double value = MAX(TTMinRowHeight, MIN(TTMaxRowHeight, sender.doubleValue)); + [[NSUserDefaults standardUserDefaults] setDouble:value forKey:TTRowHeightKey]; + self.rowHeightStepper.doubleValue = value; +} + +- (void)rowHeightStepperChanged:(NSStepper *)sender { + double value = sender.doubleValue; + [[NSUserDefaults standardUserDefaults] setDouble:value forKey:TTRowHeightKey]; + self.rowHeightField.doubleValue = value; +} + +#pragma mark - Appearance Tab Actions + - (void)fontButtonClicked:(id)sender { NSFontManager *fontManager = [NSFontManager sharedFontManager]; NSString *fontName = [[NSUserDefaults standardUserDefaults] stringForKey:TTTableFontNameKey]; @@ -121,13 +374,40 @@ - (void)changeFont:(id)sender { if (!oldFont) oldFont = [NSFont systemFontOfSize:size]; NSFont *newFont = [fontManager convertFont:oldFont]; [[NSUserDefaults standardUserDefaults] setObject:newFont.fontName forKey:TTTableFontNameKey]; + NSInteger newSize = (NSInteger)newFont.pointSize; + [[NSUserDefaults standardUserDefaults] setInteger:newSize forKey:TTTableFontSizeKey]; [self.fontButton setTitle:newFont.displayName]; + self.fontSizeField.integerValue = newSize; + self.fontSizeStepper.integerValue = newSize; +} + +- (void)fontSizeFieldChanged:(NSTextField *)sender { + NSInteger size = MAX(6, MIN(72, sender.integerValue)); + [[NSUserDefaults standardUserDefaults] setInteger:size forKey:TTTableFontSizeKey]; + self.fontSizeStepper.integerValue = size; } - (void)fontSizeStepperChanged:(NSStepper *)sender { NSInteger size = (NSInteger)sender.doubleValue; [[NSUserDefaults standardUserDefaults] setInteger:size forKey:TTTableFontSizeKey]; - self.fontSizeLabel.stringValue = [NSString stringWithFormat:@"%ld", (long)size]; + self.fontSizeField.integerValue = size; +} + +#pragma mark - CSV Defaults Tab Actions + +- (void)encodingChanged:(NSPopUpButton *)sender { + NSInteger encoding = sender.selectedItem.tag; + [[NSUserDefaults standardUserDefaults] setInteger:encoding forKey:TTDefaultEncodingKey]; +} + +- (void)separatorChanged:(NSPopUpButton *)sender { + NSDictionary *separators = [self separatorNameToValueMap]; + NSString *separator = separators[sender.selectedItem.title] ?: @","; + [[NSUserDefaults standardUserDefaults] setObject:separator forKey:TTDefaultColumnSeparatorKey]; +} + +- (void)firstRowAsHeaderChanged:(NSButton *)sender { + [[NSUserDefaults standardUserDefaults] setBool:(sender.state == NSControlStateValueOn) forKey:TTDefaultFirstRowAsHeaderKey]; } -(IBAction)showPreferences:(id)sender { From 31e2d1dae07fa9b94536d76674766d27dd198c31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:58:58 +0000 Subject: [PATCH 3/3] Modernize settings dialog: NSTabViewController toolbar + NSGridView form layout Co-authored-by: danaspiegel <6631+danaspiegel@users.noreply.github.com> --- Table Tool/TTPreferencesWindowController.m | 448 +++++++++++---------- 1 file changed, 237 insertions(+), 211 deletions(-) diff --git a/Table Tool/TTPreferencesWindowController.m b/Table Tool/TTPreferencesWindowController.m index d05e1be..f291a0f 100644 --- a/Table Tool/TTPreferencesWindowController.m +++ b/Table Tool/TTPreferencesWindowController.m @@ -9,22 +9,28 @@ #import "Constants.h" #import "CSVConfiguration.h" -@interface TTPreferencesWindowController () -// Appearance tab -@property (nonatomic, strong) NSButton *fontButton; -@property (nonatomic, strong) NSStepper *fontSizeStepper; -@property (nonatomic, strong) NSTextField *fontSizeField; +// Layout constants matching macOS HIG spacing guidelines +static const CGFloat kEdgeInset = 20.0; // margin around each pane +static const CGFloat kLabelColumnWidth = 140.0; // fixed width for right-aligned label column +static const CGFloat kColumnGap = 8.0; // gap between label and control columns +static const CGFloat kRowSpacing = 8.0; // vertical spacing between form rows +static const CGFloat kGroupSpacing = 8.0; // extra spacer row height between groups +static const CGFloat kNumericFieldWidth= 44.0; // width of numeric text fields -// General tab -@property (nonatomic, strong) NSButton *showLineNumbersCheckbox; -@property (nonatomic, strong) NSButton *alternatingRowColorsCheckbox; -@property (nonatomic, strong) NSStepper *rowHeightStepper; +@interface TTPreferencesWindowController () +// General pane +@property (nonatomic, strong) NSButton *showLineNumbersCheckbox; +@property (nonatomic, strong) NSButton *alternatingRowColorsCheckbox; +@property (nonatomic, strong) NSStepper *rowHeightStepper; @property (nonatomic, strong) NSTextField *rowHeightField; - -// CSV Defaults tab +// Appearance pane +@property (nonatomic, strong) NSButton *fontButton; +@property (nonatomic, strong) NSStepper *fontSizeStepper; +@property (nonatomic, strong) NSTextField *fontSizeField; +// CSV Defaults pane @property (nonatomic, strong) NSPopUpButton *encodingPopup; @property (nonatomic, strong) NSPopUpButton *separatorPopup; -@property (nonatomic, strong) NSButton *firstRowAsHeaderCheckbox; +@property (nonatomic, strong) NSButton *firstRowAsHeaderCheckbox; @end @implementation TTPreferencesWindowController @@ -39,203 +45,242 @@ + (instancetype)sharedController { } - (instancetype)init { - NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 440, 300) + // Initial content rect; NSTabViewController resizes the window per pane. + NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 480, 200) styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable backing:NSBackingStoreBuffered defer:NO]; - window.title = @"Preferences"; - [window center]; + window.title = @"Settings"; self = [super initWithWindow:window]; if (self) { [self buildUI]; + [window center]; } return self; } +// --------------------------------------------------------------------------- +#pragma mark - Window / Tab construction +// --------------------------------------------------------------------------- + - (void)buildUI { - NSView *contentView = self.window.contentView; - - NSTabView *tabView = [[NSTabView alloc] init]; - tabView.translatesAutoresizingMaskIntoConstraints = NO; - [contentView addSubview:tabView]; - - [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[tabView]-0-|" - options:0 metrics:nil - views:@{@"tabView": tabView}]]; - [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[tabView]-0-|" - options:0 metrics:nil - views:@{@"tabView": tabView}]]; - - NSTabViewItem *generalTab = [[NSTabViewItem alloc] initWithIdentifier:@"general"]; - generalTab.label = @"General"; - generalTab.view = [self buildGeneralTabView]; - [tabView addTabViewItem:generalTab]; - - NSTabViewItem *appearanceTab = [[NSTabViewItem alloc] initWithIdentifier:@"appearance"]; - appearanceTab.label = @"Appearance"; - appearanceTab.view = [self buildAppearanceTabView]; - [tabView addTabViewItem:appearanceTab]; - - NSTabViewItem *csvTab = [[NSTabViewItem alloc] initWithIdentifier:@"csv"]; - csvTab.label = @"CSV Defaults"; - csvTab.view = [self buildCSVDefaultsTabView]; - [tabView addTabViewItem:csvTab]; + // Modern macOS preferences style: toolbar across the top, one pane per icon. + // This matches the Safari / Mail "Settings" window pattern on macOS 13+. + NSTabViewController *tabVC = [[NSTabViewController alloc] init]; + tabVC.tabStyle = NSTabViewControllerStyleToolbar; + + [tabVC addTabViewItem:[self tabItemIdentifier:@"general" + label:@"General" + symbolName:@"gearshape" + fallbackName:NSImageNamePreferencesGeneral + pane:[self buildGeneralPane] + contentSize:NSMakeSize(480, 152)]]; + + [tabVC addTabViewItem:[self tabItemIdentifier:@"appearance" + label:@"Appearance" + symbolName:@"paintbrush" + fallbackName:NSImageNameColorPanel + pane:[self buildAppearancePane] + contentSize:NSMakeSize(480, 112)]]; + + [tabVC addTabViewItem:[self tabItemIdentifier:@"csv" + label:@"CSV Defaults" + symbolName:@"doc.text" + fallbackName:NSImageNameShareTemplate + pane:[self buildCSVDefaultsPane] + contentSize:NSMakeSize(480, 168)]]; + + self.window.contentViewController = tabVC; } -#pragma mark - General Tab +/// Builds one NSTabViewItem wired to a plain NSViewController whose view is +/// already fully laid out. preferredContentSize drives the window resize +/// animation when the user switches tabs. +- (NSTabViewItem *)tabItemIdentifier:(NSString *)identifier + label:(NSString *)label + symbolName:(NSString *)symbolName + fallbackName:(NSString *)fallbackName + pane:(NSView *)pane + contentSize:(NSSize)size { + NSViewController *vc = [[NSViewController alloc] init]; + vc.view = pane; + vc.preferredContentSize = size; + + NSImage *icon; + if (@available(macOS 11.0, *)) { + icon = [NSImage imageWithSystemSymbolName:symbolName + accessibilityDescription:label]; + } else { + icon = [NSImage imageNamed:fallbackName]; + } -- (NSView *)buildGeneralTabView { - NSView *view = [[NSView alloc] init]; - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSTabViewItem *item = [[NSTabViewItem alloc] initWithIdentifier:identifier]; + item.label = label; + item.image = icon; + item.viewController = vc; + return item; +} + +// --------------------------------------------------------------------------- +#pragma mark - Form-layout helpers +// --------------------------------------------------------------------------- + +/// Returns a two-column NSGridView configured for a standard macOS form: +/// column 0 — right-aligned labels, fixed kLabelColumnWidth wide +/// column 1 — controls, left-aligned +- (NSGridView *)newFormGrid { + NSGridView *grid = [NSGridView gridViewWithNumberOfColumns:2 rows:0]; + grid.translatesAutoresizingMaskIntoConstraints = NO; + grid.columnSpacing = kColumnGap; + grid.rowSpacing = kRowSpacing; + + NSGridColumn *labelCol = [grid columnAtIndex:0]; + labelCol.xPlacement = NSGridCellPlacementTrailing; // right-align within column + labelCol.width = kLabelColumnWidth; + return grid; +} + +/// Creates a non-editable right-aligned label suitable for the label column. +- (NSTextField *)formLabel:(NSString *)title { + NSTextField *label = [NSTextField labelWithString:title]; + label.alignment = NSTextAlignmentRight; + return label; +} + +/// Creates a right-aligned editable numeric text field with a fixed width. +- (NSTextField *)numericFieldWithValue:(double)value { + NSTextField *field = [[NSTextField alloc] init]; + field.translatesAutoresizingMaskIntoConstraints = NO; + field.alignment = NSTextAlignmentRight; + field.usesSingleLineMode = YES; + field.doubleValue = value; + field.formatter = [self integerNumberFormatter]; + [field.widthAnchor constraintEqualToConstant:kNumericFieldWidth].active = YES; + return field; +} + +/// Returns an NSStackView grouping a numeric field, its stepper, and a unit +/// label — the standard macOS stepper-with-field idiom. +- (NSStackView *)stepperRowWithField:(NSTextField *)field + stepper:(NSStepper *)stepper + unit:(NSString *)unitString { + stepper.translatesAutoresizingMaskIntoConstraints = NO; + NSTextField *unitLabel = [NSTextField labelWithString:unitString]; + NSStackView *stack = [NSStackView stackViewWithViews:@[field, stepper, unitLabel]]; + stack.spacing = 4; + stack.orientation = NSUserInterfaceLayoutOrientationHorizontal; + stack.alignment = NSLayoutAttributeCenterY; + return stack; +} + +/// Wraps an NSGridView in a container view padded by kEdgeInset on all sides. +/// Both bottom and trailing use ≥ so the container can be sized freely by +/// NSTabViewController while the grid stays anchored at the top-left. +- (NSView *)containerForGrid:(NSGridView *)grid { + NSView *container = [[NSView alloc] init]; + container.translatesAutoresizingMaskIntoConstraints = NO; + [container addSubview:grid]; + [NSLayoutConstraint activateConstraints:@[ + [grid.topAnchor constraintEqualToAnchor:container.topAnchor constant: kEdgeInset], + [grid.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant: kEdgeInset], + [container.trailingAnchor constraintGreaterThanOrEqualToAnchor:grid.trailingAnchor constant:kEdgeInset], + [container.bottomAnchor constraintGreaterThanOrEqualToAnchor:grid.bottomAnchor constant:kEdgeInset], + ]]; + return container; +} + +// --------------------------------------------------------------------------- +#pragma mark - Pane builders +// --------------------------------------------------------------------------- - // Show line numbers checkbox - self.showLineNumbersCheckbox = [NSButton checkboxWithTitle:@"Show line numbers" - target:self - action:@selector(showLineNumbersChanged:)]; - self.showLineNumbersCheckbox.translatesAutoresizingMaskIntoConstraints = NO; - self.showLineNumbersCheckbox.state = [defaults boolForKey:TTShowLineNumbersKey] ? NSControlStateValueOn : NSControlStateValueOff; - [view addSubview:self.showLineNumbersCheckbox]; - - // Alternating row colors checkbox - self.alternatingRowColorsCheckbox = [NSButton checkboxWithTitle:@"Use alternating row colors" - target:self - action:@selector(alternatingRowColorsChanged:)]; - self.alternatingRowColorsCheckbox.translatesAutoresizingMaskIntoConstraints = NO; - self.alternatingRowColorsCheckbox.state = [defaults boolForKey:TTAlternatingRowColorsKey] ? NSControlStateValueOn : NSControlStateValueOff; - [view addSubview:self.alternatingRowColorsCheckbox]; - - // Row height label - NSTextField *rowHeightLabel = [NSTextField labelWithString:@"Row height:"]; - rowHeightLabel.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:rowHeightLabel]; - - // Row height text field - self.rowHeightField = [[NSTextField alloc] init]; - self.rowHeightField.translatesAutoresizingMaskIntoConstraints = NO; - self.rowHeightField.doubleValue = [defaults doubleForKey:TTRowHeightKey]; - self.rowHeightField.formatter = [self integerNumberFormatter]; +- (NSView *)buildGeneralPane { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSGridView *grid = [self newFormGrid]; + + // Row 1: "Show row numbers" checkbox — no label in column 0 (checkbox is self-labelling) + self.showLineNumbersCheckbox = + [NSButton checkboxWithTitle:@"Show line numbers" + target:self + action:@selector(showLineNumbersChanged:)]; + self.showLineNumbersCheckbox.state = + [defaults boolForKey:TTShowLineNumbersKey] ? NSControlStateValueOn : NSControlStateValueOff; + [grid addRowWithViews:@[NSGridCell.emptyContentView, self.showLineNumbersCheckbox]]; + + // Row 2: "Alternate row colors" checkbox + self.alternatingRowColorsCheckbox = + [NSButton checkboxWithTitle:@"Use alternating row colors" + target:self + action:@selector(alternatingRowColorsChanged:)]; + self.alternatingRowColorsCheckbox.state = + [defaults boolForKey:TTAlternatingRowColorsKey] ? NSControlStateValueOn : NSControlStateValueOff; + [grid addRowWithViews:@[NSGridCell.emptyContentView, self.alternatingRowColorsCheckbox]]; + + // Thin spacer between the checkbox group and the numeric row + [grid addRowWithViews:@[NSGridCell.emptyContentView, NSGridCell.emptyContentView]]; + [grid rowAtIndex:2].height = kGroupSpacing; + + // Row 3: "Row height:" label + field + stepper + "points" + self.rowHeightField = [self numericFieldWithValue:[defaults doubleForKey:TTRowHeightKey]]; self.rowHeightField.target = self; self.rowHeightField.action = @selector(rowHeightFieldChanged:); - [view addSubview:self.rowHeightField]; - // Row height stepper self.rowHeightStepper = [[NSStepper alloc] init]; - self.rowHeightStepper.translatesAutoresizingMaskIntoConstraints = NO; - self.rowHeightStepper.minValue = TTMinRowHeight; - self.rowHeightStepper.maxValue = TTMaxRowHeight; - self.rowHeightStepper.increment = 1; - self.rowHeightStepper.valueWraps = NO; + self.rowHeightStepper.minValue = TTMinRowHeight; + self.rowHeightStepper.maxValue = TTMaxRowHeight; + self.rowHeightStepper.increment = 1; + self.rowHeightStepper.valueWraps = NO; self.rowHeightStepper.doubleValue = [defaults doubleForKey:TTRowHeightKey]; self.rowHeightStepper.target = self; self.rowHeightStepper.action = @selector(rowHeightStepperChanged:); - [view addSubview:self.rowHeightStepper]; - - // Row height points label - NSTextField *ptsLabel = [NSTextField labelWithString:@"points"]; - ptsLabel.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:ptsLabel]; - - NSDictionary *views = @{ - @"lineNumbers": self.showLineNumbersCheckbox, - @"alternating": self.alternatingRowColorsCheckbox, - @"rowHeightLabel": rowHeightLabel, - @"rowHeightField": self.rowHeightField, - @"rowHeightStepper": self.rowHeightStepper, - @"ptsLabel": ptsLabel - }; - - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[lineNumbers]->=20-|" - options:0 metrics:nil views:views]]; - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[alternating]->=20-|" - options:0 metrics:nil views:views]]; - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[rowHeightLabel]-8-[rowHeightField(48)]-2-[rowHeightStepper]-8-[ptsLabel]->=20-|" - options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-24-[lineNumbers]-12-[alternating]-12-[rowHeightLabel]->=20-|" - options:0 metrics:nil views:views]]; - return view; -} -#pragma mark - Appearance Tab + NSStackView *rowHeightCtrl = [self stepperRowWithField:self.rowHeightField + stepper:self.rowHeightStepper + unit:@"points"]; + [grid addRowWithViews:@[[self formLabel:@"Row height:"], rowHeightCtrl]]; -- (NSView *)buildAppearanceTabView { - NSView *view = [[NSView alloc] init]; - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + return [self containerForGrid:grid]; +} - // Font label - NSTextField *fontLabel = [NSTextField labelWithString:@"Table font:"]; - fontLabel.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:fontLabel]; +- (NSView *)buildAppearancePane { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSGridView *grid = [self newFormGrid]; - // Font button + // Row 1: "Font:" label + select button (opens Font Panel) self.fontButton = [NSButton buttonWithTitle:[self currentFontDisplayName] target:self action:@selector(fontButtonClicked:)]; - self.fontButton.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:self.fontButton]; - - // Size label - NSTextField *sizeLabel = [NSTextField labelWithString:@"Size:"]; - sizeLabel.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:sizeLabel]; - - // Font size text field - self.fontSizeField = [[NSTextField alloc] init]; - self.fontSizeField.translatesAutoresizingMaskIntoConstraints = NO; - self.fontSizeField.doubleValue = [defaults doubleForKey:TTTableFontSizeKey]; - self.fontSizeField.formatter = [self integerNumberFormatter]; + [grid addRowWithViews:@[[self formLabel:@"Font:"], self.fontButton]]; + + // Row 2: "Size:" label + field + stepper + "points" + self.fontSizeField = [self numericFieldWithValue:[defaults doubleForKey:TTTableFontSizeKey]]; self.fontSizeField.target = self; self.fontSizeField.action = @selector(fontSizeFieldChanged:); - [view addSubview:self.fontSizeField]; - // Font size stepper self.fontSizeStepper = [[NSStepper alloc] init]; - self.fontSizeStepper.translatesAutoresizingMaskIntoConstraints = NO; - self.fontSizeStepper.minValue = 6; - self.fontSizeStepper.maxValue = 72; - self.fontSizeStepper.increment = 1; - self.fontSizeStepper.valueWraps = NO; + self.fontSizeStepper.minValue = 6; + self.fontSizeStepper.maxValue = 72; + self.fontSizeStepper.increment = 1; + self.fontSizeStepper.valueWraps = NO; self.fontSizeStepper.doubleValue = [defaults doubleForKey:TTTableFontSizeKey]; self.fontSizeStepper.target = self; self.fontSizeStepper.action = @selector(fontSizeStepperChanged:); - [view addSubview:self.fontSizeStepper]; - - // Points label - NSTextField *ptLabel = [NSTextField labelWithString:@"points"]; - ptLabel.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:ptLabel]; - - NSDictionary *views = @{ - @"fontLabel": fontLabel, - @"fontButton": self.fontButton, - @"sizeLabel": sizeLabel, - @"sizeField": self.fontSizeField, - @"stepper": self.fontSizeStepper, - @"ptLabel": ptLabel - }; - - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[fontLabel]-8-[fontButton]->=20-|" - options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[sizeLabel]-8-[sizeField(48)]-2-[stepper]-8-[ptLabel]->=20-|" - options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-24-[fontLabel]-16-[sizeLabel]->=20-|" - options:0 metrics:nil views:views]]; - return view; -} -#pragma mark - CSV Defaults Tab + NSStackView *sizeCtrl = [self stepperRowWithField:self.fontSizeField + stepper:self.fontSizeStepper + unit:@"points"]; + [grid addRowWithViews:@[[self formLabel:@"Size:"], sizeCtrl]]; -- (NSView *)buildCSVDefaultsTabView { - NSView *view = [[NSView alloc] init]; - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + return [self containerForGrid:grid]; +} - // Encoding label - NSTextField *encodingLabel = [NSTextField labelWithString:@"Default encoding:"]; - encodingLabel.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:encodingLabel]; +- (NSView *)buildCSVDefaultsPane { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSGridView *grid = [self newFormGrid]; - // Encoding popup + // Row 1: "Encoding:" popup self.encodingPopup = [[NSPopUpButton alloc] init]; self.encodingPopup.translatesAutoresizingMaskIntoConstraints = NO; + [self.encodingPopup.widthAnchor constraintGreaterThanOrEqualToConstant:220].active = YES; NSInteger savedEncoding = [defaults integerForKey:TTDefaultEncodingKey]; for (NSArray *encoding in [CSVConfiguration supportedEncodings]) { [self.encodingPopup addItemWithTitle:encoding[0]]; @@ -246,61 +291,42 @@ - (NSView *)buildCSVDefaultsTabView { } self.encodingPopup.target = self; self.encodingPopup.action = @selector(encodingChanged:); - [view addSubview:self.encodingPopup]; + [grid addRowWithViews:@[[self formLabel:@"Encoding:"], self.encodingPopup]]; - // Separator label - NSTextField *separatorLabel = [NSTextField labelWithString:@"Default separator:"]; - separatorLabel.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:separatorLabel]; - - // Separator popup + // Row 2: "Separator:" popup self.separatorPopup = [[NSPopUpButton alloc] init]; self.separatorPopup.translatesAutoresizingMaskIntoConstraints = NO; - NSString *savedSeparator = [defaults stringForKey:TTDefaultColumnSeparatorKey] ?: @","; - NSDictionary *separators = [self separatorNameToValueMap]; - NSArray *separatorOrder = [self separatorDisplayOrder]; - for (NSString *name in separatorOrder) { + NSString *savedSep = [defaults stringForKey:TTDefaultColumnSeparatorKey] ?: @","; + NSDictionary *sepMap = [self separatorNameToValueMap]; + for (NSString *name in [self separatorDisplayOrder]) { [self.separatorPopup addItemWithTitle:name]; - if ([separators[name] isEqualToString:savedSeparator]) { + if ([sepMap[name] isEqualToString:savedSep]) { [self.separatorPopup selectItem:self.separatorPopup.lastItem]; } } self.separatorPopup.target = self; self.separatorPopup.action = @selector(separatorChanged:); - [view addSubview:self.separatorPopup]; - - // First row as header checkbox - self.firstRowAsHeaderCheckbox = [NSButton checkboxWithTitle:@"Use first row as header by default" - target:self - action:@selector(firstRowAsHeaderChanged:)]; - self.firstRowAsHeaderCheckbox.translatesAutoresizingMaskIntoConstraints = NO; - self.firstRowAsHeaderCheckbox.state = [defaults boolForKey:TTDefaultFirstRowAsHeaderKey] ? NSControlStateValueOn : NSControlStateValueOff; - [view addSubview:self.firstRowAsHeaderCheckbox]; - - NSDictionary *views = @{ - @"encodingLabel": encodingLabel, - @"encodingPopup": self.encodingPopup, - @"separatorLabel": separatorLabel, - @"separatorPopup": self.separatorPopup, - @"firstRowAsHeader": self.firstRowAsHeaderCheckbox - }; - - CGFloat labelWidth = 130; - [view addConstraint:[NSLayoutConstraint constraintWithItem:encodingLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:labelWidth]]; - [view addConstraint:[NSLayoutConstraint constraintWithItem:separatorLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:labelWidth]]; - - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[encodingLabel]-8-[encodingPopup]->=20-|" - options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[separatorLabel]-8-[separatorPopup]->=20-|" - options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]]; - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[firstRowAsHeader]->=20-|" - options:0 metrics:nil views:views]]; - [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-24-[encodingLabel]-12-[separatorLabel]-12-[firstRowAsHeader]->=20-|" - options:0 metrics:nil views:views]]; - return view; + [grid addRowWithViews:@[[self formLabel:@"Separator:"], self.separatorPopup]]; + + // Thin spacer before the checkbox + [grid addRowWithViews:@[NSGridCell.emptyContentView, NSGridCell.emptyContentView]]; + [grid rowAtIndex:2].height = kGroupSpacing; + + // Row 3: "Use first row as column headers" checkbox + self.firstRowAsHeaderCheckbox = + [NSButton checkboxWithTitle:@"Use first row as header by default" + target:self + action:@selector(firstRowAsHeaderChanged:)]; + self.firstRowAsHeaderCheckbox.state = + [defaults boolForKey:TTDefaultFirstRowAsHeaderKey] ? NSControlStateValueOn : NSControlStateValueOff; + [grid addRowWithViews:@[NSGridCell.emptyContentView, self.firstRowAsHeaderCheckbox]]; + + return [self containerForGrid:grid]; } +// --------------------------------------------------------------------------- #pragma mark - Helpers +// --------------------------------------------------------------------------- - (NSDictionary *)separatorNameToValueMap { return @{@"Comma ( , )": @",", @"Semicolon ( ; )": @";", @"Tab ( ⇥ )": @"\t", @"Pipe ( | )": @"|"}; @@ -312,7 +338,7 @@ - (NSArray *)separatorDisplayOrder { - (NSNumberFormatter *)integerNumberFormatter { NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; - formatter.numberStyle = NSNumberFormatterDecimalStyle; + formatter.numberStyle = NSNumberFormatterDecimalStyle; formatter.maximumFractionDigits = 0; return formatter; }