diff --git a/Tests/Manual/ThemeKitchenSinkA3/AppController.j b/Tests/Manual/ThemeKitchenSinkA3/AppController.j index 70372607c..b27662634 100644 --- a/Tests/Manual/ThemeKitchenSinkA3/AppController.j +++ b/Tests/Manual/ThemeKitchenSinkA3/AppController.j @@ -3,6 +3,7 @@ * KitchenSink in Code * * Created by Daniel Böhringer 2026. + * Modified for TabView and SplitView (Table + Text) support. */ @import @@ -10,24 +11,23 @@ // -------------------------------------------------------------------------------- // KitchenSinkWindowController -// Manages a single window instance, its specific style (HUD/Standard), and content state. +// Manages a single window instance with Tabs: Controls & Table/Text Split // -------------------------------------------------------------------------------- @implementation KitchenSinkWindowController : CPWindowController { - BOOL _isHUD; - BOOL _areControlsEnabled; + BOOL _isHUD; + BOOL _areControlsEnabled; + CPArrayController _arrayController; } - (id)initWithContentRect:(CGRect)aRect isHUD:(BOOL)isHUD enabled:(BOOL)isEnabled { - // 1. Determine Style Mask var styleMask = CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask | CPResizableWindowMask; if (isHUD) styleMask |= CPHUDBackgroundWindowMask; - // 2. Create Window var theWindow = [[CPWindow alloc] initWithContentRect:aRect styleMask:styleMask]; self = [super initWithWindow:theWindow]; @@ -40,18 +40,28 @@ var title = (isHUD ? @"HUD Theme" : @"Aristo3 Theme") + (isEnabled ? @" (Enabled)" : @" (Disabled)"); [theWindow setTitle:title]; - // 3. Setup Toolbar var toolbar = [[CPToolbar alloc] initWithIdentifier:@"KitchenSinkToolbar" + (isHUD ? @"HUD" : @"Aqua")]; [toolbar setDelegate:self]; [toolbar setVisible:YES]; [theWindow setToolbar:toolbar]; - // 4. Build UI + _arrayController = [[CPArrayController alloc] init]; + + var contentData = [ + [CPDictionary dictionaryWithObjectsAndKeys:@"Cat", @"animal", 4, @"legs"], + [CPDictionary dictionaryWithObjectsAndKeys:@"Duck", @"animal", 2, @"legs"], + [CPDictionary dictionaryWithObjectsAndKeys:@"Centipede", @"animal", 100, @"legs"], + [CPDictionary dictionaryWithObjectsAndKeys:@"Spider", @"animal", 8, @"legs"], + [CPDictionary dictionaryWithObjectsAndKeys:@"Snake", @"animal", 0, @"legs"] + ]; + + [_arrayController setContent:contentData]; + [_arrayController setEditable:YES]; + [self _buildInterface]; - // 5. Apply Enabled State if (!isEnabled) - [self _disableAllControls]; + [self _disableControlsInView:[theWindow contentView]]; } return self; @@ -61,170 +71,279 @@ { var window = [self window], contentView = [window contentView], - col1X = 20.0, + bounds = [contentView bounds]; + + var tabView = [[CPTabView alloc] initWithFrame:bounds]; + [tabView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + + // --- TAB 1: Controls --- + var item1 = [[CPTabViewItem alloc] initWithIdentifier:@"Controls"]; + [item1 setLabel:@"Controls"]; + + var controlsView = [[CPView alloc] initWithFrame:[tabView bounds]]; + [self _buildControlsTab:controlsView]; + [item1 setView:controlsView]; + [tabView addTabViewItem:item1]; + + // --- TAB 2: Table & Text Split --- + var item2 = [[CPTabViewItem alloc] initWithIdentifier:@"Table"]; + [item2 setLabel:@"Data Split"]; // Renamed slightly to reflect content + + var tableViewWrapper = [[CPView alloc] initWithFrame:[tabView bounds]]; + [self _buildTableTab:tableViewWrapper]; + [item2 setView:tableViewWrapper]; + [tabView addTabViewItem:item2]; + + [contentView addSubview:tabView]; +} + +- (void)_buildControlsTab:(CPView)containerView +{ + var col1X = 20.0, col2X = 200.0, startY = 20.0, gapY = 35.0, width = 150.0; // --- COLUMN 1 --- - - // Push Button var pushButton = [[CPButton alloc] initWithFrame:CGRectMake(col1X, startY, width, 24)]; [pushButton setTitle:@"Push Button"]; - [contentView addSubview:pushButton]; + [containerView addSubview:pushButton]; - // Gradient Button var gradientButton = [[CPButton alloc] initWithFrame:CGRectMake(col1X, startY + gapY, width, 24)]; [gradientButton setTitle:@"Gradient Button"]; - [contentView addSubview:gradientButton]; + [containerView addSubview:gradientButton]; - // Round Rect Button var roundRectButton = [[CPButton alloc] initWithFrame:CGRectMake(col1X, startY + (gapY * 2), width, 24)]; [roundRectButton setTitle:@"Round Rect Button"]; [roundRectButton setBezelStyle:CPRoundedBezelStyle]; - [contentView addSubview:roundRectButton]; + [containerView addSubview:roundRectButton]; - // Placeholder TextField var placeholderField = [[CPTextField alloc] initWithFrame:CGRectMake(col1X, startY + (gapY * 3), width, 24)]; [placeholderField setEditable:YES]; [placeholderField setBezeled:YES]; [placeholderField setPlaceholderString:@"Placeholder"]; - [contentView addSubview:placeholderField]; + [containerView addSubview:placeholderField]; - // Normal TextField var textField = [[CPTextField alloc] initWithFrame:CGRectMake(col1X, startY + (gapY * 4), width, 25)]; [textField setEditable:YES]; [textField setBezeled:YES]; [textField setStringValue:@"Text Field"]; - [contentView addSubview:textField]; + [containerView addSubview:textField]; - // Search Field var searchField = [[CPSearchField alloc] initWithFrame:CGRectMake(col1X, startY + (gapY * 5), width, 25)]; [searchField setPlaceholderString:@"Search..."]; - [contentView addSubview:searchField]; + [containerView addSubview:searchField]; - // Token Field var tokenField = [[CPTokenField alloc] initWithFrame:CGRectMake(col1X, startY + (gapY * 6), width, 25)]; [tokenField setObjectValue:["Token", "Field"]]; - [contentView addSubview:tokenField]; + [containerView addSubview:tokenField]; - // Combo Box var comboBox = [[CPComboBox alloc] initWithFrame:CGRectMake(col1X, startY + (gapY * 7), width, 25)]; [comboBox setPlaceholderString:@"Combo Box"]; [comboBox addItemsWithObjectValues:["Alpha", "Beta", "Gamma"]]; - [contentView addSubview:comboBox]; + [containerView addSubview:comboBox]; - // Slider var bottomSlider = [[CPSlider alloc] initWithFrame:CGRectMake(col1X, startY + (gapY * 8.5), width, 25)]; - [contentView addSubview:bottomSlider]; - + [containerView addSubview:bottomSlider]; // --- COLUMN 2 --- - - // Date Picker var datePicker = [[CPDatePicker alloc] initWithFrame:CGRectMake(col2X, startY, 175, 28)]; [datePicker setDatePickerStyle:CPTextFieldAndStepperDatePickerStyle]; [datePicker setDateValue:[CPDate date]]; - [contentView addSubview:datePicker]; + [containerView addSubview:datePicker]; - // Checkboxes (Grouped visually) var cbY = startY + gapY; var cbOn = [CPCheckBox checkBoxWithTitle:@"On"]; [cbOn setFrameOrigin:CGPointMake(col2X, cbY)]; [cbOn setState:CPOnState]; - [contentView addSubview:cbOn]; + [containerView addSubview:cbOn]; var cbOff = [CPCheckBox checkBoxWithTitle:@"Off"]; [cbOff setFrameOrigin:CGPointMake(col2X + 50, cbY)]; [cbOff setState:CPOffState]; - [contentView addSubview:cbOff]; + [containerView addSubview:cbOff]; var cbBoth = [CPCheckBox checkBoxWithTitle:@"Mixed"]; [cbBoth setFrameOrigin:CGPointMake(col2X + 100, cbY)]; [cbBoth setState:CPMixedState]; - [contentView addSubview:cbBoth]; + [containerView addSubview:cbBoth]; - // Pop Up Button var popUp = [[CPPopUpButton alloc] initWithFrame:CGRectMake(col2X, startY + (gapY * 2), 150, 24) pullsDown:NO]; [popUp addItemWithTitle:@"Item 1"]; [popUp addItemWithTitle:@"Item 2"]; - [contentView addSubview:popUp]; + [containerView addSubview:popUp]; - // Segmented Control - var seg = [[CPSegmentedControl alloc] initWithFrame:CGRectMake(col2X, startY + (gapY * 3), 160, 24)]; - [seg setSegmentCount:3]; - [seg setLabel:@"One" forSegment:0]; - [seg setLabel:@"Two" forSegment:1]; - [seg setLabel:@"Three" forSegment:2]; - [seg setWidth:50 forSegment:0]; - [seg setSelectedSegment:0]; - [contentView addSubview:seg]; + // Stepper & Progress Bar + var swapY = startY + (gapY * 3); + var stepper = [[CPStepper alloc] initWithFrame:CGRectMake(col2X, swapY + 3, 19, 27)]; + [stepper setValueWraps:NO]; + [stepper setAutorepeat:YES]; + [stepper setMinValue:0]; + [stepper setMaxValue:100]; + [stepper setDoubleValue:33]; + [containerView addSubview:stepper]; + + var detProgress = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(col2X + 25, swapY + 5.5, 125, 16)]; + [detProgress setStyle:CPProgressIndicatorBarStyle]; + [detProgress setIndeterminate:NO]; + [detProgress setMinValue:0]; + [detProgress setMaxValue:100]; + [detProgress bind:CPValueBinding toObject:stepper withKeyPath:@"doubleValue" options:nil]; + [containerView addSubview:detProgress]; - // Radio Buttons var radioY = startY + (gapY * 4); var radio1 = [CPRadio radioWithTitle:@"Radio A"]; [radio1 setFrameOrigin:CGPointMake(col2X, radioY)]; [radio1 setState:CPOnState]; - [contentView addSubview:radio1]; + [containerView addSubview:radio1]; var radio2 = [CPRadio radioWithTitle:@"Radio B"]; [radio2 setFrameOrigin:CGPointMake(col2X, radioY + 22)]; - [contentView addSubview:radio2]; - - // Link radios target + [containerView addSubview:radio2]; [radio1 setTarget:self]; [radio1 setAction:@selector(dummyAction:)]; [radio2 setTarget:self]; [radio2 setAction:@selector(dummyAction:)]; - // Level Indicator var levelInd = [[CPLevelIndicator alloc] initWithFrame:CGRectMake(col2X, startY + (gapY * 5.5), 150, 18)]; [levelInd setMaxValue:5]; [levelInd setDoubleValue:3]; [levelInd setLevelIndicatorStyle:CPDiscreteCapacityLevelIndicatorStyle]; - [contentView addSubview:levelInd]; + [containerView addSubview:levelInd]; - // Tick Slider var tickSlider = [[CPSlider alloc] initWithFrame:CGRectMake(col2X, startY + (gapY * 6.5), 110, 24)]; - //[tickSlider setNumberOfTickMarks:5]; - [contentView addSubview:tickSlider]; + [containerView addSubview:tickSlider]; - // Vertical Slider var vSlider = [[CPSlider alloc] initWithFrame:CGRectMake(col2X + 130, startY + (gapY * 6.5), 24, 70)]; - [contentView addSubview:vSlider]; + [containerView addSubview:vSlider]; - // Circular Slider var knob = [[CPSlider alloc] initWithFrame:CGRectMake(col2X, startY + (gapY * 7.5), 32, 32)]; [knob setSliderType:CPCircularSlider]; - [contentView addSubview:knob]; + [containerView addSubview:knob]; - // Indeterminate Progress Bar - // Placed to the right of the knob var progressBar = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(col2X + 40, startY + (gapY * 7.5) + 8, 80, 16)]; [progressBar setStyle:CPProgressIndicatorBarStyle]; [progressBar setIndeterminate:YES]; [progressBar startAnimation:self]; - [contentView addSubview:progressBar]; + [containerView addSubview:progressBar]; } -- (void)_disableAllControls +- (void)_buildTableTab:(CPView)containerView { - var contentView = [[self window] contentView], - subviews = [contentView subviews], + var bounds = [containerView bounds]; + var bottomBarHeight = 32.0; + + // 1. Calculate Frame for Split View (Everything above the button bar) + var splitFrame = CGRectMake(0, 0, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - bottomBarHeight); + + // 2. Create the Split View + var splitView = [[CPSplitView alloc] initWithFrame:splitFrame]; + [splitView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [splitView setVertical:NO]; // NO = Horizontal Dividers = Vertical Stacking + + // --- TOP PANE: Table View --- + + var tableScroll = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(bounds), CGRectGetHeight(splitFrame) / 2.0)]; + [tableScroll setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [tableScroll setAutohidesScrollers:YES]; + + var tableView = [[CPTableView alloc] initWithFrame:[tableScroll bounds]]; + [tableView setUsesAlternatingRowBackgroundColors:YES]; + [tableView setCornerView:nil]; + + // Bindings + [tableView bind:CPSelectionIndexesBinding toObject:_arrayController withKeyPath:@"selectionIndexes" options:nil]; + [tableView bind:@"sortDescriptors" toObject:_arrayController withKeyPath:@"sortDescriptors" options:nil]; + + // Col 1: Animal + var colAnimal = [[CPTableColumn alloc] initWithIdentifier:@"animal"]; + [[colAnimal headerView] setStringValue:@"Animal"]; + [colAnimal setWidth:150]; + var animalCell = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; + [animalCell setFont:[CPFont systemFontOfSize:12.0]]; + [colAnimal setDataView:animalCell]; + [tableView addTableColumn:colAnimal]; + [colAnimal bind:CPValueBinding toObject:_arrayController withKeyPath:@"arrangedObjects.animal" options:nil]; + + // Col 2: Legs + var colLegs = [[CPTableColumn alloc] initWithIdentifier:@"legs"]; + [[colLegs headerView] setStringValue:@"Legs"]; + [colLegs setWidth:100]; + var legsCell = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; + [legsCell setFont:[CPFont systemFontOfSize:12.0]]; + [colLegs setDataView:legsCell]; + [tableView addTableColumn:colLegs]; + [colLegs bind:CPValueBinding toObject:_arrayController withKeyPath:@"arrangedObjects.legs" options:nil]; + + [tableScroll setDocumentView:tableView]; + + // --- BOTTOM PANE: Text View --- + + var textScroll = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(bounds), CGRectGetHeight(splitFrame) / 2.0)]; + [textScroll setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [textScroll setAutohidesScrollers:YES]; + + var textView = [[CPTextView alloc] initWithFrame:[textScroll bounds]]; + [textView setEditable:YES]; + [textView setString:@"Select an item in the table above...\n\n(This is a CPTextView inside a CPScrollView inside a CPSplitView)"]; + [textView setFont:[CPFont fontWithName:@"Courier" size:13.0]]; + + [textScroll setDocumentView:textView]; + + // --- ASSEMBLE SPLIT VIEW --- + // Note: The order of addSubview determines top vs bottom + [splitView addSubview:tableScroll]; + [splitView addSubview:textScroll]; + + [containerView addSubview:splitView]; + + // 3. Button Bar (Remains at bottom, outside split view) + var buttonBar = [[CPButtonBar alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(bounds) - bottomBarHeight, CGRectGetWidth(bounds), bottomBarHeight)]; + [buttonBar setAutoresizingMask:CPViewWidthSizable | CPViewMinYMargin]; + + if ([buttonBar respondsToSelector:@selector(setHasResizeControl:)]) + [buttonBar setHasResizeControl:NO]; + else if ([buttonBar respondsToSelector:@selector(setAutomaticResizeControl:)]) + [buttonBar setAutomaticResizeControl:NO]; + + var plusBtn = [CPButtonBar plusButton]; + [plusBtn setTarget:_arrayController]; + [plusBtn setAction:@selector(add:)]; + [plusBtn setEnabled:YES]; + + var minusBtn = [CPButtonBar minusButton]; + [minusBtn setTarget:_arrayController]; + [minusBtn setAction:@selector(remove:)]; + [minusBtn bind:CPEnabledBinding toObject:_arrayController withKeyPath:@"canRemove" options:nil]; + + [buttonBar setButtons:[plusBtn, minusBtn]]; + [containerView addSubview:buttonBar]; +} + +- (void)_disableControlsInView:(id)aView +{ + var subviews = [aView subviews], count = [subviews count]; for (var i = 0; i < count; i++) { var view = subviews[i]; + + // Skip text views to keep them readable even if window is 'disabled' (optional preference) + // or disable them too. Here we stick to the requested behavior of disabling controls. if ([view respondsToSelector:@selector(setEnabled:)]) - { [view setEnabled:NO]; - } + + // Specifically for CPTextView which might rely on setEditable for "enabling" + if ([view isKindOfClass:[CPTextView class]]) + [view setEditable:NO]; + + if ([[view subviews] count] > 0) + [self _disableControlsInView:view]; } } - (void)dummyAction:(id)sender { - // Placeholder action for controls } // --- TOOLBAR DELEGATE --- @@ -247,11 +366,8 @@ { [item setLabel:@"Colors"]; [item setPaletteLabel:@"Colors"]; - - // Use system provided color panel images [item setImage:[CPImage imageNamed:CPImageNameColorPanel]]; [item setAlternateImage:[CPImage imageNamed:CPImageNameColorPanelHighlighted]]; - [item setTarget:self]; [item setAction:@selector(orderFrontColorPanel:)]; } @@ -262,11 +378,6 @@ @end -// -------------------------------------------------------------------------------- -// AppController -// Orchestrates the creation of the 4 windows. -// -------------------------------------------------------------------------------- - @implementation AppController : CPObject { CPArray windows; @@ -276,35 +387,23 @@ { windows = []; - var winWidth = 400.0, - winHeight = 380.0, + var winWidth = 430.0, + winHeight = 500.0, // Increased height slightly to accommodate the split view better padding = 20.0; - // 1. Top Left: Normal, Enabled - var wc1 = [[KitchenSinkWindowController alloc] initWithContentRect:CGRectMake(50, 50, winWidth, winHeight) - isHUD:NO - enabled:YES]; + var wc1 = [[KitchenSinkWindowController alloc] initWithContentRect:CGRectMake(50, 50, winWidth, winHeight) isHUD:NO enabled:YES]; [wc1 showWindow:self]; [windows addObject:wc1]; - // 2. Top Right: Normal, Disabled - var wc2 = [[KitchenSinkWindowController alloc] initWithContentRect:CGRectMake(50 + winWidth + padding, 50, winWidth, winHeight) - isHUD:NO - enabled:NO]; + var wc2 = [[KitchenSinkWindowController alloc] initWithContentRect:CGRectMake(50 + winWidth + padding, 50, winWidth, winHeight) isHUD:NO enabled:NO]; [wc2 showWindow:self]; [windows addObject:wc2]; - // 3. Bottom Left: HUD, Enabled - var wc3 = [[KitchenSinkWindowController alloc] initWithContentRect:CGRectMake(50, 50 + winHeight + padding + 30, winWidth, winHeight) - isHUD:YES - enabled:YES]; + var wc3 = [[KitchenSinkWindowController alloc] initWithContentRect:CGRectMake(50, 50 + winHeight + padding + 30, winWidth, winHeight) isHUD:YES enabled:YES]; [wc3 showWindow:self]; [windows addObject:wc3]; - // 4. Bottom Right: HUD, Disabled - var wc4 = [[KitchenSinkWindowController alloc] initWithContentRect:CGRectMake(50 + winWidth + padding, 50 + winHeight + padding + 30, winWidth, winHeight) - isHUD:YES - enabled:NO]; + var wc4 = [[KitchenSinkWindowController alloc] initWithContentRect:CGRectMake(50 + winWidth + padding, 50 + winHeight + padding + 30, winWidth, winHeight) isHUD:YES enabled:NO]; [wc4 showWindow:self]; [windows addObject:wc4];