diff --git a/AppKit/Themes/Aristo3/ThemeDescriptors.j b/AppKit/Themes/Aristo3/ThemeDescriptors.j index ef4453eba..c9e861973 100644 --- a/AppKit/Themes/Aristo3/ThemeDescriptors.j +++ b/AppKit/Themes/Aristo3/ThemeDescriptors.j @@ -8269,14 +8269,26 @@ var themedButtonValues = nil, + (CPRuleEditor)themedRuleEditor { var ruleEditor = [[CPRuleEditor alloc] initWithFrame:CGRectMake(0.0, 0.0, 400.0, 300.0)], + + // --- Standard Colors --- backgroundColors = [[CPColor whiteColor], [CPColor colorWithRed:235 / 255 green:239 / 255 blue:252 / 255 alpha:1]], selectedActiveRowColor = [CPColor colorWithHexString:@"5f83b9"], selectedInactiveRowColor = [CPColor colorWithWhite:0.83 alpha:1], sliceTopBorderColor = [CPColor colorWithWhite:0.9 alpha:1.0], sliceBottomBorderColor = [CPColor colorWithWhite:0.729412 alpha:1.0], sliceLastBottomBorderColor = [CPColor colorWithWhite:0.6 alpha:1.0], + fontColor = [CPColor colorWithWhite:150 / 255 alpha:1], - // SVGs for buttons + // --- HUD Colors --- + // Alternating rows: Transparent and very faint white + hudBackgroundColors = [[CPColor clearColor], [CPColor colorWithWhite:1.0 alpha:0.05]], + // Selection: Semi-transparent white + hudSelectedRowColor = [CPColor colorWithWhite:1.0 alpha:0.2], + // Borders: Very faint white lines + hudSliceBorderColor = [CPColor colorWithWhite:1.0 alpha:0.1], + hudFontColor = [CPColor whiteColor], + + // --- Standard Images --- buttonAddImage = [CPImage imageWithCSSDictionary:@{ "-webkit-mask-image": svgPlus, "mask-image": svgPlus, @@ -8301,10 +8313,34 @@ var themedButtonValues = nil, "mask-position": "center" } size:CGSizeMake(12, 12)], - fontColor = [CPColor colorWithWhite:150 / 255 alpha:1], + // --- HUD Images (White Icons) --- + buttonAddImageHUD = [CPImage imageWithCSSDictionary:@{ + "-webkit-mask-image": svgPlus, + "mask-image": svgPlus, + "background-color": @"#FFFFFF", // White + "-webkit-mask-size": "contain", + "mask-size": "contain", + "-webkit-mask-repeat": "no-repeat", + "mask-repeat": "no-repeat", + "-webkit-mask-position": "center", + "mask-position": "center" + } size:CGSizeMake(12, 12)], + + buttonRemoveImageHUD = [CPImage imageWithCSSDictionary:@{ + "-webkit-mask-image": svgMinus, + "mask-image": svgMinus, + "background-color": @"#FFFFFF", // White + "-webkit-mask-size": "contain", + "mask-size": "contain", + "-webkit-mask-repeat": "no-repeat", + "mask-repeat": "no-repeat", + "-webkit-mask-position": "center", + "mask-position": "center" + } size:CGSizeMake(12, 12)], ruleEditorThemedValues = [ + // --- Standard Theme --- [@"alternating-row-colors", backgroundColors], [@"selected-color", selectedActiveRowColor, CPThemeStateNormal], [@"selected-color", selectedInactiveRowColor, CPThemeStateDisabled], @@ -8315,7 +8351,24 @@ var themedButtonValues = nil, [@"font-color", fontColor], [@"add-image", buttonAddImage, CPThemeStateNormal], [@"remove-image", buttonRemoveImage, CPThemeStateNormal], - [@"vertical-alignment", CPCenterVerticalTextAlignment] + [@"vertical-alignment", CPCenterVerticalTextAlignment], + + // --- HUD Mappings --- + [@"alternating-row-colors", hudBackgroundColors, CPThemeStateHUD], + [@"selected-color", hudSelectedRowColor, [CPThemeStateHUD, CPThemeStateNormal]], + [@"selected-color", hudSelectedRowColor, [CPThemeStateHUD, CPThemeStateDisabled]], + + // HUD Borders + [@"slice-top-border-color", hudSliceBorderColor, CPThemeStateHUD], + [@"slice-bottom-border-color", hudSliceBorderColor, CPThemeStateHUD], + [@"slice-last-bottom-border-color", hudSliceBorderColor, CPThemeStateHUD], + + // HUD Font + [@"font-color", hudFontColor, CPThemeStateHUD], + + // HUD Icons + [@"add-image", buttonAddImageHUD, [CPThemeStateHUD, CPThemeStateNormal]], + [@"remove-image", buttonRemoveImageHUD, [CPThemeStateHUD, CPThemeStateNormal]] ]; [self registerThemeValues:ruleEditorThemedValues forView:ruleEditor]; diff --git a/Tests/Manual/ThemeKitchenSinkA3/AppController.j b/Tests/Manual/ThemeKitchenSinkA3/AppController.j index 8a1729a90..44ec56cfe 100644 --- a/Tests/Manual/ThemeKitchenSinkA3/AppController.j +++ b/Tests/Manual/ThemeKitchenSinkA3/AppController.j @@ -4,7 +4,7 @@ * * Created by Daniel Böhringer 2026. * Modified for TabView, SplitView, Control Sizes support & Grouped Boxes. - * Update: Fixed Button Bezel Styles (Push, Gradient, Round Rect). + * Update: Fixed Table Editing (Column Editable Property). */ @import @@ -12,7 +12,6 @@ // -------------------------------------------------------------------------------- // KitchenSinkWindowController -// Manages a single window instance with Tabs: Controls, Table/Text Split, & Sizes // -------------------------------------------------------------------------------- @implementation KitchenSinkWindowController : CPWindowController @@ -20,6 +19,11 @@ BOOL _isHUD; BOOL _areControlsEnabled; CPArrayController _arrayController; + + // Rule Editor References + CPRuleEditor _ruleEditor; + RuleDelegate _ruleDelegate; + CPTextField _predicateField; } - (id)initWithContentRect:(CGRect)aRect isHUD:(BOOL)isHUD enabled:(BOOL)isEnabled @@ -84,12 +88,11 @@ var controlsView = [[CPView alloc] initWithFrame:[tabView bounds]]; [self _buildControlsTab:controlsView isHUD:isHUD]; [item1 setView:controlsView]; - [item1 setLabel:@"Controls"]; // Redundant but safe [tabView addTabViewItem:item1]; - // --- TAB 2: Table & Text Split --- + // --- TAB 2: Table & Text Split (Now with Rule Editor) --- var item2 = [[CPTabViewItem alloc] initWithIdentifier:@"Table"]; - [item2 setLabel:@"Data Split"]; + [item2 setLabel:@"Data & Rules"]; var tableViewWrapper = [[CPView alloc] initWithFrame:[tabView bounds]]; [self _buildTableTab:tableViewWrapper isHUD:isHUD]; @@ -113,159 +116,198 @@ // Layout Constants var boxMargin = 15.0, boxWidth = 190.0, - boxHeight = 420.0, - - innerX = 15.0, // X-offset inside the box - startY = 10.0, // Y-offset inside the box - gapY = 35.0, + innerX = 15.0, + startY = 10.0, + gapY = 35.0, // Uniform vertical spacing controlWidth = 150.0; // ------------------------------------------------------ // LEFT BOX: Standard Controls // ------------------------------------------------------ - var leftBox = [[CPBox alloc] initWithFrame:CGRectMake(boxMargin, 15.0, boxWidth, boxHeight)]; + var leftBox = [[CPBox alloc] initWithFrame:CGRectMake(boxMargin, 15.0, boxWidth, 100)]; // Height set later [leftBox setTitle:@"Standard Controls"]; - [leftBox setAutoresizingMask:CPViewMaxXMargin | CPViewHeightSizable]; + [leftBox setAutoresizingMask:CPViewMaxXMargin | CPViewMinYMargin]; [containerView addSubview:leftBox]; - // Note: We add subviews to [leftBox contentView] var leftContent = [leftBox contentView]; + var currentY = startY; - // 1. Push Button (Standard Rounded) - var pushButton = [[CPButton alloc] initWithFrame:CGRectMake(innerX, startY, controlWidth, 24)]; + // 1. Push Button + var pushButton = [[CPButton alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 24)]; [pushButton setTitle:@"Push Button"]; [pushButton setBezelStyle:CPRoundedBezelStyle]; [leftContent addSubview:pushButton]; + currentY += gapY; - // 2. Gradient Button (Small Square) - var gradientButton = [[CPButton alloc] initWithFrame:CGRectMake(innerX, startY + gapY, controlWidth, 24)]; + // 2. Gradient Button + var gradientButton = [[CPButton alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 24)]; [gradientButton setTitle:@"Gradient Button"]; [gradientButton setBezelStyle:CPSmallSquareBezelStyle]; [leftContent addSubview:gradientButton]; + currentY += gapY; // 3. Round Rect Button - var roundRectButton = [[CPButton alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 2), controlWidth, 24)]; + var roundRectButton = [[CPButton alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 24)]; [roundRectButton setTitle:@"Round Rect Button"]; [roundRectButton setBezelStyle:CPRoundRectBezelStyle]; [leftContent addSubview:roundRectButton]; + currentY += gapY; - var placeholderField = [[CPTextField alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 3), controlWidth, 24)]; + // 4. Placeholder + var placeholderField = [[CPTextField alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 24)]; [placeholderField setEditable:YES]; [placeholderField setBezeled:YES]; [placeholderField setPlaceholderString:@"Placeholder"]; [leftContent addSubview:placeholderField]; + currentY += gapY; - var textField = [[CPTextField alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 4), controlWidth, 25)]; + // 5. Text Field + var textField = [[CPTextField alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 25)]; [textField setEditable:YES]; [textField setBezeled:YES]; [textField setStringValue:@"Text Field"]; [leftContent addSubview:textField]; + currentY += gapY; - var searchField = [[CPSearchField alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 5), controlWidth, 25)]; + // 6. Search Field + var searchField = [[CPSearchField alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 25)]; [searchField setPlaceholderString:@"Search..."]; [leftContent addSubview:searchField]; + currentY += gapY; - var tokenField = [[CPTokenField alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 6), controlWidth, 25)]; + // 7. Token Field + var tokenField = [[CPTokenField alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 25)]; [tokenField setObjectValue:["Token", "Field"]]; [leftContent addSubview:tokenField]; + currentY += gapY; - var comboBox = [[CPComboBox alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 7), controlWidth, 25)]; + // 8. Combo Box + var comboBox = [[CPComboBox alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 25)]; [comboBox setPlaceholderString:@"Combo Box"]; [comboBox addItemsWithObjectValues:["Alpha", "Beta", "Gamma"]]; [leftContent addSubview:comboBox]; + currentY += gapY; - // --- NEW PULL DOWN MENU --- - var pullDown = [[CPPopUpButton alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 8), controlWidth, 25) pullsDown:YES]; - [pullDown addItemWithTitle:@"Pull Down Menu"]; // The first item is the title/cover + // 9. Pull Down Menu + var pullDown = [[CPPopUpButton alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 25) pullsDown:YES]; + [pullDown addItemWithTitle:@"Pull Down Menu"]; [pullDown addItemWithTitle:@"Action A"]; [pullDown addItemWithTitle:@"Action B"]; [pullDown addItemWithTitle:@"Action C"]; [leftContent addSubview:pullDown]; + currentY += gapY; - // Shifted slider down to 9.5 to make room for Pull Down - var bottomSlider = [[CPSlider alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 9.5), controlWidth, 25)]; - [leftContent addSubview:bottomSlider]; + // 10. Standard PopUp Button + var popUp = [[CPPopUpButton alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, 24) pullsDown:NO]; + [popUp addItemWithTitle:@"PopUp Item 1"]; + [popUp addItemWithTitle:@"PopUp Item 2"]; + [leftContent addSubview:popUp]; + currentY += gapY; + + // Resize Left Box to fit + [leftBox setFrameSize:CGSizeMake(boxWidth, currentY + 15.0)]; // ------------------------------------------------------ // RIGHT BOX: Advanced Controls // ------------------------------------------------------ - // Calculate X position for the second box var rightBoxX = boxMargin + boxWidth + 15.0; - var rightBoxWidth = 205.0; // Slightly wider for DatePicker/Steppers + var rightBoxWidth = 205.0; - var rightBox = [[CPBox alloc] initWithFrame:CGRectMake(rightBoxX, 15.0, rightBoxWidth, boxHeight)]; + var rightBox = [[CPBox alloc] initWithFrame:CGRectMake(rightBoxX, 15.0, rightBoxWidth, 100)]; [rightBox setTitle:@"Advanced Controls"]; - [rightBox setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [rightBox setAutoresizingMask:CPViewWidthSizable | CPViewMinYMargin]; [containerView addSubview:rightBox]; var rightContent = [rightBox contentView]; + currentY = startY; // Reset Y - var datePicker = [[CPDatePicker alloc] initWithFrame:CGRectMake(innerX, startY, 175, 28)]; + // 1. Date Picker + var datePicker = [[CPDatePicker alloc] initWithFrame:CGRectMake(innerX, currentY, 175, 28)]; [datePicker setDatePickerStyle:CPTextFieldAndStepperDatePickerStyle]; [datePicker setDateValue:[CPDate date]]; [rightContent addSubview:datePicker]; + currentY += gapY; - var cbY = startY + gapY; + // 2. Checkboxes var cbOn = [CPCheckBox checkBoxWithTitle:@"On"]; - [cbOn setFrameOrigin:CGPointMake(innerX, cbY)]; + [cbOn setFrameOrigin:CGPointMake(innerX, currentY)]; [cbOn setState:CPOnState]; [rightContent addSubview:cbOn]; var cbOff = [CPCheckBox checkBoxWithTitle:@"Off"]; - [cbOff setFrameOrigin:CGPointMake(innerX + 50, cbY)]; + [cbOff setFrameOrigin:CGPointMake(innerX + 50, currentY)]; [cbOff setState:CPOffState]; [rightContent addSubview:cbOff]; var cbBoth = [CPCheckBox checkBoxWithTitle:@"Mixed"]; - [cbBoth setFrameOrigin:CGPointMake(innerX + 100, cbY)]; + [cbBoth setFrameOrigin:CGPointMake(innerX + 100, currentY)]; [cbBoth setState:CPMixedState]; [rightContent addSubview:cbBoth]; + currentY += gapY; - var popUp = [[CPPopUpButton alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 2), 150, 24) pullsDown:NO]; - [popUp addItemWithTitle:@"Item 1"]; - [popUp addItemWithTitle:@"Item 2"]; - [rightContent addSubview:popUp]; + // 3. Spinners (Indeterminate & Determinate/Circular) + // A. Indeterminate Spinner + var spinner = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(innerX + 50, currentY - 4, 32, 32)]; + [spinner setStyle:CPProgressIndicatorSpinningStyle]; + [spinner setIndeterminate:YES]; + [spinner setControlSize:CPRegularControlSize]; + [spinner startAnimation:self]; + [rightContent addSubview:spinner]; - // Stepper & Progress Bar - var swapY = startY + (gapY * 3); - var stepper = [[CPStepper alloc] initWithFrame:CGRectMake(innerX, swapY + 3, 19, 27)]; + // B. Circular Progress Bar (Determinate Spinner) + var circProg = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(innerX + 90, currentY - 4, 32, 32)]; + [circProg setStyle:CPProgressIndicatorSpinningStyle]; + [circProg setIndeterminate:NO]; // Determinate makes it a circular progress bar usually + [circProg setControlSize:CPRegularControlSize]; + [circProg setDoubleValue:65.0]; + [circProg setMaxValue:100.0]; + [rightContent addSubview:circProg]; + + currentY += gapY; + + // 4. Stepper & Progress Bar + var stepper = [[CPStepper alloc] initWithFrame:CGRectMake(innerX, currentY + 3, 19, 27)]; [stepper setValueWraps:NO]; [stepper setAutorepeat:YES]; [stepper setMinValue:0]; [stepper setMaxValue:100]; - [stepper setDoubleValue:33]; + [stepper setDoubleValue:65]; [rightContent addSubview:stepper]; - var detProgress = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(innerX + 25, swapY + 5.5, 125, 16)]; + // Bind Circular Progress to Stepper + [circProg bind:CPValueBinding toObject:stepper withKeyPath:@"doubleValue" options:nil]; + + var detProgress = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(innerX + 25, currentY + 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]; [rightContent addSubview:detProgress]; + currentY += gapY; - var radioY = startY + (gapY * 4); + // 5. Radio Buttons var radio1 = [CPRadio radioWithTitle:@"Radio A"]; - [radio1 setFrameOrigin:CGPointMake(innerX, radioY)]; + [radio1 setFrameOrigin:CGPointMake(innerX, currentY)]; [radio1 setState:CPOnState]; [rightContent addSubview:radio1]; var radio2 = [CPRadio radioWithTitle:@"Radio B"]; - [radio2 setFrameOrigin:CGPointMake(innerX, radioY + 22)]; + [radio2 setFrameOrigin:CGPointMake(innerX, currentY + 22)]; [rightContent addSubview:radio2]; [radio1 setTarget:self]; [radio1 setAction:@selector(dummyAction:)]; [radio2 setTarget:self]; [radio2 setAction:@selector(dummyAction:)]; + currentY += gapY * 1.5; // Extra space for radios - // Level Indicator - var levelY = startY + (gapY * 5.5); - var levelInd = [[CPLevelIndicator alloc] initWithFrame:CGRectMake(innerX, levelY, 130, 18)]; + // 6. Level Indicator + var levelInd = [[CPLevelIndicator alloc] initWithFrame:CGRectMake(innerX, currentY, 130, 18)]; [levelInd setMaxValue:5]; [levelInd setDoubleValue:3]; [levelInd setLevelIndicatorStyle:CPDiscreteCapacityLevelIndicatorStyle]; [rightContent addSubview:levelInd]; - var levelStepper = [[CPStepper alloc] initWithFrame:CGRectMake(innerX + 135, levelY - 2, 19, 24)]; + var levelStepper = [[CPStepper alloc] initWithFrame:CGRectMake(innerX + 135, currentY - 2, 19, 24)]; [levelStepper setMinValue:0]; [levelStepper setMaxValue:5]; [levelStepper setDoubleValue:3]; @@ -273,38 +315,85 @@ [rightContent addSubview:levelStepper]; [levelInd bind:CPValueBinding toObject:levelStepper withKeyPath:@"doubleValue" options:nil]; + currentY += gapY; - var tickSlider = [[CPSlider alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 6.5), 110, 24)]; + // 7. Sliders + var tickSlider = [[CPSlider alloc] initWithFrame:CGRectMake(innerX, currentY, 110, 24)]; [rightContent addSubview:tickSlider]; - var vSlider = [[CPSlider alloc] initWithFrame:CGRectMake(innerX + 130, startY + (gapY * 6.5), 24, 70)]; + var vSlider = [[CPSlider alloc] initWithFrame:CGRectMake(innerX + 130, currentY, 24, 70)]; [rightContent addSubview:vSlider]; - - var knob = [[CPSlider alloc] initWithFrame:CGRectMake(innerX, startY + (gapY * 7.5), 32, 32)]; + currentY += gapY; + + // 8. Knob & Indeterminate Bar + var knob = [[CPSlider alloc] initWithFrame:CGRectMake(innerX, currentY, 32, 32)]; [knob setSliderType:CPCircularSlider]; [rightContent addSubview:knob]; - var progressBar = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(innerX + 40, startY + (gapY * 7.5) + 8, 80, 16)]; + var progressBar = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(innerX + 40, currentY + 8, 80, 16)]; [progressBar setStyle:CPProgressIndicatorBarStyle]; [progressBar setIndeterminate:YES]; [progressBar startAnimation:self]; [rightContent addSubview:progressBar]; + + currentY += gapY; + + // Resize Right Box to fit + [rightBox setFrameSize:CGSizeMake(rightBoxWidth, currentY + 15.0)]; } - (void)_buildTableTab:(CPView)containerView isHUD:(BOOL)isHUD { var bounds = [containerView bounds]; var bottomBarHeight = 32.0; - - // Split View Frame - var splitFrame = CGRectMake(0, 0, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - bottomBarHeight); + var ruleEditorHeight = 140.0; - var splitView = [[CPSplitView alloc] initWithFrame:splitFrame]; + // --- RULE EDITOR SECTION (Top) --- + var ruleContainer = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(bounds), ruleEditorHeight)]; + [ruleContainer setAutohidesScrollers:YES]; + [ruleContainer setBorderType:CPBezelBorder]; + + _ruleEditor = [[CPRuleEditor alloc] initWithFrame:CGRectMake(0,0, CGRectGetWidth(bounds), ruleEditorHeight)]; + [_ruleEditor setRowHeight:25.0]; + [_ruleEditor setFormattingStringsFilename:@"Rules"]; // Not used without resources, but good practice + [_ruleEditor setCanRemoveAllRows:YES]; + + // Create and attach delegate + _ruleDelegate = [[RuleDelegate alloc] init]; + [_ruleEditor setDelegate:_ruleDelegate]; + + // Add an initial row + [_ruleEditor addRow:self]; + + [ruleContainer setDocumentView:_ruleEditor]; + [containerView addSubview:ruleContainer]; + + // Predicate Label (To show output) + var predLabel = [CPTextField labelWithTitle:@"Predicate:"]; + [predLabel setFrame:CGRectMake(10, ruleEditorHeight - 30, 60, 20)]; + //[containerView addSubview:predLabel]; // Optional + + _predicateField = [[CPTextField alloc] initWithFrame:CGRectMake(10, ruleEditorHeight + 5, CGRectGetWidth(bounds) - 20, 20)]; + [_predicateField setEditable:NO]; + [_predicateField setFont:[CPFont systemFontOfSize:11.0]]; + [_predicateField setTextColor:[CPColor grayColor]]; + [_predicateField setStringValue:@"(Predicate will appear here)"]; + [containerView addSubview:_predicateField]; + + // Hook up notification for changes to update text + [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(ruleEditorRowsDidChange:) name:CPRuleEditorRowsDidChangeNotification object:_ruleEditor]; + + + // --- SPLIT VIEW SECTION (Bottom) --- + var splitY = ruleEditorHeight + 30.0; + var splitHeight = CGRectGetHeight(bounds) - splitY - bottomBarHeight; + + var splitView = [[CPSplitView alloc] initWithFrame:CGRectMake(0, splitY, CGRectGetWidth(bounds), splitHeight)]; [splitView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; [splitView setVertical:NO]; - // --- TOP PANE: Table View --- - var tableScroll = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(bounds), CGRectGetHeight(splitFrame) / 2.0)]; + // Top Pane: Table + var tableScroll = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(bounds), splitHeight / 2.0)]; [tableScroll setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; [tableScroll setAutohidesScrollers:YES]; @@ -315,34 +404,40 @@ [tableView bind:CPSelectionIndexesBinding toObject:_arrayController withKeyPath:@"selectionIndexes" options:nil]; [tableView bind:@"sortDescriptors" toObject:_arrayController withKeyPath:@"sortDescriptors" options:nil]; + // Column 1: Animal var colAnimal = [[CPTableColumn alloc] initWithIdentifier:@"animal"]; [[colAnimal headerView] setStringValue:@"Animal"]; [colAnimal setWidth:150]; + [colAnimal setEditable:YES]; // <--- CRITICAL FOR EDITING + var animalCell = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; - [animalCell setFont:[CPFont systemFontOfSize:12.0]]; + [animalCell setEditable:YES]; [colAnimal setDataView:animalCell]; [tableView addTableColumn:colAnimal]; [colAnimal bind:CPValueBinding toObject:_arrayController withKeyPath:@"arrangedObjects.animal" options:nil]; + // Column 2: Legs var colLegs = [[CPTableColumn alloc] initWithIdentifier:@"legs"]; [[colLegs headerView] setStringValue:@"Legs"]; [colLegs setWidth:100]; + [colLegs setEditable:YES]; // <--- CRITICAL FOR EDITING + var legsCell = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; - [legsCell setFont:[CPFont systemFontOfSize:12.0]]; + [legsCell setEditable:YES]; [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)]; + // Bottom Pane: Text + var textScroll = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(bounds), splitHeight / 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 setString:@"1. Use the Rule Editor above to build a predicate.\n2. Edit table cells directly.\n3. Add/Remove rows below."]; [textView setFont:[CPFont fontWithName:@"Courier" size:13.0]]; if (isHUD) @@ -360,7 +455,8 @@ [splitView setThemeState:CPThemeStateHUD]; [tableScroll setThemeState:CPThemeStateHUD]; [textScroll setThemeState:CPThemeStateHUD]; - [tableView setThemeState:CPThemeStateHUD]; // Also apply to table view for alternating row colors + [tableView setThemeState:CPThemeStateHUD]; + [ruleContainer setThemeState:CPThemeStateHUD]; } [containerView addSubview:splitView]; @@ -389,6 +485,15 @@ [containerView addSubview:buttonBar]; } +- (void)ruleEditorRowsDidChange:(CPNotification)note +{ + var predicate = [_ruleEditor predicate]; + if (predicate) + [_predicateField setStringValue:[predicate predicateFormat]]; + else + [_predicateField setStringValue:@"(Incomplete Predicate)"]; +} + // --- SIZES TAB BUILDER --- - (void)_buildSizesTab:(CPView)containerView isHUD:(BOOL)isHUD { @@ -408,7 +513,6 @@ [label setFrameOrigin:CGPointMake(xPos, y)]; [label setFont:[CPFont systemFontOfSize:13.0]]; [parentView addSubview:label]; - y += 35.0; // 2. PopUp Button @@ -417,7 +521,6 @@ [popUp addItemWithTitle:@"Item 2"]; [popUp setControlSize:aSize]; [parentView addSubview:popUp]; - y += rowHeight; // 3. Text Field @@ -427,25 +530,20 @@ [tf setEditable:YES]; [tf setControlSize:aSize]; [parentView addSubview:tf]; - y += rowHeight; // 4. Stepper var stepper = [[CPStepper alloc] initWithFrame:CGRectMake(xPos, y, 13, 23)]; [stepper setControlSize:aSize]; [parentView addSubview:stepper]; - y += rowHeight; // 5. Date Picker var dpWidth = width + (aSize == CPRegularControlSize ? 20 : 15); - var dp = [[CPDatePicker alloc] initWithFrame:CGRectMake(xPos, y, dpWidth, 28)]; [dp setControlSize:aSize]; [dp setDatePickerStyle:CPTextFieldAndStepperDatePickerStyle]; - // --- Show ONLY Year, Month, and Day (No Time) --- [dp setDatePickerElements:CPYearMonthDayDatePickerElementFlag]; - [dp setDateValue:[CPDate date]]; [parentView addSubview:dp]; y += rowHeight; @@ -456,7 +554,6 @@ [cb setControlSize:aSize]; [cb sizeToFit]; [parentView addSubview:cb]; - y += rowHeight; // 7. Standard Button @@ -464,7 +561,6 @@ [btn setTitle:@"Button"]; [btn setControlSize:aSize]; [parentView addSubview:btn]; - y += rowHeight; // 8. Textured Button @@ -473,7 +569,6 @@ [texBtn setBezelStyle:CPTexturedSquareBezelStyle]; [texBtn setControlSize:aSize]; [parentView addSubview:texBtn]; - y += rowHeight; // 9. Round Textured Button @@ -482,7 +577,6 @@ [roundTexBtn setBezelStyle:CPTexturedRoundedBezelStyle]; [roundTexBtn setControlSize:aSize]; [parentView addSubview:roundTexBtn]; - y += rowHeight; // 10. Radio Buttons @@ -492,7 +586,6 @@ [rad1 setState:CPOnState]; [rad1 sizeToFit]; [parentView addSubview:rad1]; - y += 24.0; var rad2 = [CPRadio radioWithTitle:@"Radio"]; @@ -500,7 +593,6 @@ [rad2 setControlSize:aSize]; [rad2 sizeToFit]; [parentView addSubview:rad2]; - y += rowHeight; // 11. Small Bottom PopUp @@ -518,15 +610,10 @@ for (var i = 0; i < count; i++) { var view = subviews[i]; - - // Skip CPBox so we can disable its children, but keep the box itself "enabled" (visible) - // Or simply recursively go down. if ([view respondsToSelector:@selector(setEnabled:)]) [view setEnabled:NO]; - if ([view isKindOfClass:[CPTextView class]]) [view setEditable:NO]; - if ([[view subviews] count] > 0) [self _disableControlsInView:view]; } @@ -537,21 +624,17 @@ } // --- TOOLBAR DELEGATE --- - - (CPArray)toolbarAllowedItemIdentifiers:(CPToolbar)aToolbar { return [@"ColorsItem", CPToolbarFlexibleSpaceItemIdentifier]; } - - (CPArray)toolbarDefaultItemIdentifiers:(CPToolbar)aToolbar { return [CPToolbarFlexibleSpaceItemIdentifier, @"ColorsItem"]; } - - (CPToolbarItem)toolbar:(CPToolbar)aToolbar itemForItemIdentifier:(CPString)anItemIdentifier willBeInsertedIntoToolbar:(BOOL)flag { var item = [[CPToolbarItem alloc] initWithItemIdentifier:anItemIdentifier]; - if (anItemIdentifier == @"ColorsItem") { [item setLabel:@"Colors"]; @@ -561,13 +644,83 @@ [item setTarget:self]; [item setAction:@selector(orderFrontColorPanel:)]; } - return item; } @end +// -------------------------------------------------------------------------------- +// RuleDelegate +// Implements the logic for the CPRuleEditor +// -------------------------------------------------------------------------------- + +@implementation RuleDelegate : CPObject +{ +} + +- (int)ruleEditor:(CPRuleEditor)editor numberOfChildrenForCriterion:(id)criterion withRowType:(CPRuleEditorRowType)rowType +{ + // Root Level: 2 options (Animal, Legs) + if (criterion == nil) + return 2; + + // Second Level: Operators + if (criterion == @"animal") + return 2; // contains, is + if (criterion == @"legs") + return 3; // =, >, < + + // Third Level: The Value input field (Leaf node) + return 0; +} + +- (id)ruleEditor:(CPRuleEditor)editor child:(int)index forCriterion:(id)criterion withRowType:(CPRuleEditorRowType)rowType +{ + if (criterion == nil) + { + if (index == 0) return @"animal"; + return @"legs"; + } + + if (criterion == @"animal") + { + if (index == 0) return @"contains"; + return @"is"; // like (Like) + } + + if (criterion == @"legs") + { + if (index == 0) return @">"; + if (index == 1) return @"<"; + return @"=="; + } + + return nil; +} + +- (id)ruleEditor:(CPRuleEditor)editor displayValueForCriterion:(id)criterion inRow:(int)row +{ + if (criterion == @"animal") return @"Animal Name"; + if (criterion == @"legs") return @"Leg Count"; + + if (criterion == @"contains") return @"contains"; + if (criterion == @"is") return @"is"; + + if (criterion == @">") return @"is greater than"; + if (criterion == @"<") return @"is less than"; + if (criterion == @"==") return @"is equal to"; + + return criterion; +} + +@end + + +// -------------------------------------------------------------------------------- +// AppController +// -------------------------------------------------------------------------------- + @implementation AppController : CPObject { CPArray windows;