diff --git a/AppKit/CPButtonBar.j b/AppKit/CPButtonBar.j index 28b17538d..44aba3384 100644 --- a/AppKit/CPButtonBar.j +++ b/AppKit/CPButtonBar.j @@ -36,6 +36,7 @@ BOOL _hasResizeControl; BOOL _resizeControlIsLeftAligned; CPArray _buttons; + CPArray _rightButtons; } + (id)plusButton @@ -104,6 +105,7 @@ if (self) { _buttons = []; + _rightButtons = []; [self setNeedsLayout]; } @@ -157,6 +159,30 @@ return [CPArray arrayWithArray:_buttons]; } +- (void)setRightButtons:(CPArray)buttons +{ + for (var i = [_rightButtons count] - 1; i >= 0; i--) + { + [_rightButtons[i] removeFromSuperview]; + [_rightButtons[i] removeObserver:self forKeyPath:@"hidden"]; + } + + _rightButtons = [CPArray arrayWithArray:buttons]; + + for (var i = [_rightButtons count] - 1; i >= 0; i--) + { + [_rightButtons[i] addObserver:self forKeyPath:@"hidden" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:nil]; + [_rightButtons[i] setBordered:YES]; + } + + [self setNeedsLayout]; +} + +- (CPArray)rightButtons +{ + return [CPArray arrayWithArray:_rightButtons]; +} + - (void)setHasResizeControl:(BOOL)shouldHaveResizeControl { if (_hasResizeControl === shouldHaveResizeControl) @@ -243,46 +269,73 @@ } } - var currentButtonOffset = _resizeControlIsLeftAligned ? CGRectGetMaxX([self bounds]) + 1 : -1, - bounds = [self bounds], + var rightButtonsNotHidden = [CPArray arrayWithArray:_rightButtons], + rightCount = [rightButtonsNotHidden count]; + + while (rightCount--) + { + var button = rightButtonsNotHidden[rightCount]; + + if ([button isHidden]) + { + [button removeFromSuperview]; + [rightButtonsNotHidden removeObject:button]; + } + } + + var bounds = [self bounds], height = CGRectGetHeight(bounds) - 1, frameWidth = CGRectGetWidth(bounds), resizeRect = _hasResizeControl ? [self rectForEphemeralSubviewNamed:"resize-control-view"] : CGRectMakeZero(), resizeWidth = CGRectGetWidth(resizeRect), availableWidth = frameWidth - resizeWidth - 1; - for (var i = 0, count = [buttonsNotHidden count]; i < count; i++) + var currentLeftOffset = _resizeControlIsLeftAligned ? resizeWidth - 1 : -1, + currentRightOffset = _resizeControlIsLeftAligned ? CGRectGetMaxX(bounds) + 1 : CGRectGetMaxX(bounds) - resizeWidth + 1; + + var setupButton = function(button, isRightAligned) { - var button = buttonsNotHidden[i], - width = CGRectGetWidth([button frame]); + var width = CGRectGetWidth([button frame]); if (availableWidth > width) availableWidth -= width; else - break; + return NO; - if (_resizeControlIsLeftAligned) + if (isRightAligned) { - [button setFrame:CGRectMake(currentButtonOffset - width, 1, width, height)]; - currentButtonOffset -= width - 1; + [button setFrame:CGRectMake(currentRightOffset - width, 1, width, height)]; + currentRightOffset -= width - 1; } else - { - [button setFrame:CGRectMake(currentButtonOffset, 1, width, height)]; - currentButtonOffset += width - 1; + {[button setFrame:CGRectMake(currentLeftOffset, 1, width, height)]; + currentLeftOffset += width - 1; } [button setValue:normalColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateNormal, CPThemeStateBordered]]; - [button setValue:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered, ]]; + [button setValue:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered]]; [button setValue:disabledColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateDisabled, CPThemeStateBordered]]; [button setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateBordered]; - // FIXME shouldn't need this - [button setValue:normalColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateNormal, CPThemeStateBordered, CPPopUpButtonStatePullsDown]]; + // FIXME shouldn't need this[button setValue:normalColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateNormal, CPThemeStateBordered, CPPopUpButtonStatePullsDown]]; [button setValue:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered, CPPopUpButtonStatePullsDown]]; [button setValue:disabledColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateDisabled, CPThemeStateBordered, CPPopUpButtonStatePullsDown]]; [self addSubview:button]; + + return YES; + }; + + for (var i = 0, count = [buttonsNotHidden count]; i < count; i++) + { + if (!setupButton(buttonsNotHidden[i], _resizeControlIsLeftAligned)) + break; + } + + for (var i = 0, count = [rightButtonsNotHidden count]; i < count; i++) + { + if (!setupButton(rightButtonsNotHidden[i], YES)) + break; } if (_hasResizeControl) @@ -314,7 +367,8 @@ var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey", CPButtonBarResizeControlIsLeftAlignedKey = @"CPButtonBarResizeControlIsLeftAlignedKey", - CPButtonBarButtonsKey = @"CPButtonBarButtonsKey"; + CPButtonBarButtonsKey = @"CPButtonBarButtonsKey", + CPButtonBarRightButtonsKey = @"CPButtonBarRightButtonsKey"; @implementation CPButtonBar (CPCoding) @@ -325,6 +379,7 @@ var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey", [aCoder encodeBool:_hasResizeControl forKey:CPButtonBarHasResizeControlKey]; [aCoder encodeBool:_resizeControlIsLeftAligned forKey:CPButtonBarResizeControlIsLeftAlignedKey]; [aCoder encodeObject:_buttons forKey:CPButtonBarButtonsKey]; + [aCoder encodeObject:_rightButtons forKey:CPButtonBarRightButtonsKey]; } - (id)initWithCoder:(CPCoder)aCoder @@ -332,6 +387,7 @@ var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey", if (self = [super initWithCoder:aCoder]) { _buttons = [aCoder decodeObjectForKey:CPButtonBarButtonsKey] || []; + _rightButtons = [aCoder decodeObjectForKey:CPButtonBarRightButtonsKey] || []; _hasResizeControl = [aCoder decodeBoolForKey:CPButtonBarHasResizeControlKey]; _resizeControlIsLeftAligned = [aCoder decodeBoolForKey:CPButtonBarResizeControlIsLeftAlignedKey]; } diff --git a/Tests/Manual/CPButtonBarTest/AppController.j b/Tests/Manual/CPButtonBarTest/AppController.j new file mode 100644 index 000000000..20e1af7ae --- /dev/null +++ b/Tests/Manual/CPButtonBarTest/AppController.j @@ -0,0 +1,104 @@ +/* + * AppController.j + * CPButtonBar Right Buttons Manual Test + */ + +@import +@import +@import +@import + +@implementation AppController : CPObject +{ + CPButtonBar buttonBar; +} + +- (void)applicationDidFinishLaunching:(CPNotification)aNotification +{ + var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask], + contentView = [theWindow contentView]; + + var bounds = [contentView bounds]; + + // 1. Create the ButtonBar + buttonBar = [[CPButtonBar alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(bounds) - 28, CGRectGetWidth(bounds), 28)]; + [buttonBar setAutoresizingMask:CPViewWidthSizable | CPViewMinYMargin]; + + // Add resize control to ensure bounds calculations are correct + [buttonBar setHasResizeControl:YES]; + + // 2. Setup standard Left Buttons + var plusButton = [CPButtonBar plusButton], + minusButton = [CPButtonBar minusButton]; + + [buttonBar setButtons:[plusButton, minusButton]]; + + // 3. Setup the new Right Buttons (PR #745) + var actionButton = [CPButtonBar actionPopupButton], + customRightBtn = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 70, 24)]; + + [customRightBtn setTitle:@"Settings"]; + [buttonBar setRightButtons:[customRightBtn, actionButton]]; + + [contentView addSubview:buttonBar]; + + // --- Testing Controls --- + + // Button to test resizing alignment layout + var toggleAlignmentBtn = [CPButton buttonWithTitle:@"Toggle Resize Control Alignment"]; + [toggleAlignmentBtn setFrameOrigin:CGPointMake(20, 20)]; + [toggleAlignmentBtn sizeToFit]; + [toggleAlignmentBtn setTarget:self]; + [toggleAlignmentBtn setAction:@selector(toggleResizeAlignment:)]; + [contentView addSubview:toggleAlignmentBtn]; + + // Button to test the KVO "hidden" observer on rightButtons + var toggleVisibilityBtn = [CPButton buttonWithTitle:@"Toggle Right Button Visibility"]; + [toggleVisibilityBtn setFrameOrigin:CGPointMake(20, 60)]; + [toggleVisibilityBtn sizeToFit]; + [toggleVisibilityBtn setTarget:self]; + [toggleVisibilityBtn setAction:@selector(toggleRightVisibility:)]; + [contentView addSubview:toggleVisibilityBtn]; + + // Button to test CPCoding archiving/unarchiving of rightButtons + var testArchivingBtn = [CPButton buttonWithTitle:@"Test Archiving (CPCoding)"]; + [testArchivingBtn setFrameOrigin:CGPointMake(20, 100)]; + [testArchivingBtn sizeToFit]; + [testArchivingBtn setTarget:self]; + [testArchivingBtn setAction:@selector(testArchiving:)]; + [contentView addSubview:testArchivingBtn]; + + [theWindow orderFront:self]; +} + +- (void)toggleResizeAlignment:(id)sender +{ + // Flipping the resize control to the left shouldn't break the right-aligned buttons + [buttonBar setResizeControlIsLeftAligned:![buttonBar resizeControlIsLeftAligned]]; +} + +- (void)toggleRightVisibility:(id)sender +{ + var rightBtns = [buttonBar rightButtons]; + if ([rightBtns count] > 0) + { + var btn = rightBtns[0]; + [btn setHidden:![btn isHidden]]; + } +} + +- (void)testArchiving:(id)sender +{ + // Test the new CPButtonBarRightButtonsKey in CPCoding + var data = [CPKeyedArchiver archivedDataWithRootObject:buttonBar]; + var unarchivedBar = [CPKeyedUnarchiver unarchiveObjectWithData:data]; + + var bounds = [[buttonBar superview] bounds]; + + // Place the cloned bar directly above the original one + [unarchivedBar setFrame:CGRectMake(0, CGRectGetHeight(bounds) - 56, CGRectGetWidth(bounds), 28)]; + + [[buttonBar superview] addSubview:unarchivedBar]; +} + +@end diff --git a/Tests/Manual/CPButtonBarTest/Info.plist b/Tests/Manual/CPButtonBarTest/Info.plist new file mode 100644 index 000000000..e9a691d12 --- /dev/null +++ b/Tests/Manual/CPButtonBarTest/Info.plist @@ -0,0 +1,12 @@ + + + + + CPApplicationDelegateClass + AppController + CPBundleName + CPButtonBarTest + CPPrincipalClass + CPApplication + + diff --git a/Tests/Manual/CPButtonBarTest/Jakefile b/Tests/Manual/CPButtonBarTest/Jakefile new file mode 100644 index 000000000..542de14fd --- /dev/null +++ b/Tests/Manual/CPButtonBarTest/Jakefile @@ -0,0 +1,81 @@ +/* + * Jakefile + * CPBrowserTest + * + * Created by Ross Boucher on March 23, 2010. + * Copyright 2010, Your Company All rights reserved. + */ + +var ENV = require("system").env, + FILE = require("file"), + JAKE = require("jake"), + task = JAKE.task, + FileList = JAKE.FileList, + app = require("cappuccino/jake").app, + configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug", + OS = require("os"); + +app ("CPBrowserTest", function(task) +{ + task.setBuildIntermediatesPath(FILE.join("Build", "CPBrowserTest.build", configuration)); + task.setBuildPath(FILE.join("Build", configuration)); + + task.setProductName("CPBrowserTest"); + task.setIdentifier("com.yourcompany.CPBrowserTest"); + task.setVersion("1.0"); + task.setAuthor("Your Company"); + task.setEmail("feedback @nospam@ yourcompany.com"); + task.setSummary("CPBrowserTest"); + task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**"))); + task.setResources(new FileList("Resources/*")); + task.setIndexFilePath("index.html"); + task.setInfoPlistPath("Info.plist"); + + if (configuration === "Debug") + task.setCompilerFlags("-DDEBUG -g"); + else + task.setCompilerFlags("-O"); +}); + +function printResults(configuration) +{ + print("----------------------------"); + print(configuration+" app built at path: "+FILE.join("Build", configuration, "CPBrowserTest")); + print("----------------------------"); +} + +task ("default", ["CPBrowserTest"], function() +{ + printResults(configuration); +}); + +task ("build", ["default"]); + +task ("debug", function() +{ + ENV["CONFIGURATION"] = "Debug"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("release", function() +{ + ENV["CONFIGURATION"] = "Release"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("run", ["debug"], function() +{ + OS.system(["open", FILE.join("Build", "Debug", "CPBrowserTest", "index.html")]); +}); + +task ("run-release", ["release"], function() +{ + OS.system(["open", FILE.join("Build", "Release", "CPBrowserTest", "index.html")]); +}); + +task ("deploy", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Deployment", "CPBrowserTest")); + OS.system(["press", "-f", FILE.join("Build", "Release", "CPBrowserTest"), FILE.join("Build", "Deployment", "CPBrowserTest")]); + printResults("Deployment") +}); diff --git a/Tests/Manual/CPButtonBarTest/Resources/spinner.gif b/Tests/Manual/CPButtonBarTest/Resources/spinner.gif new file mode 100644 index 000000000..a5e705f6c Binary files /dev/null and b/Tests/Manual/CPButtonBarTest/Resources/spinner.gif differ diff --git a/Tests/Manual/CPButtonBarTest/index-debug.html b/Tests/Manual/CPButtonBarTest/index-debug.html new file mode 100644 index 000000000..a36b1d3b9 --- /dev/null +++ b/Tests/Manual/CPButtonBarTest/index-debug.html @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + __project.name__ + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/Manual/CPButtonBarTest/index.html b/Tests/Manual/CPButtonBarTest/index.html new file mode 100644 index 000000000..ac42c98a7 --- /dev/null +++ b/Tests/Manual/CPButtonBarTest/index.html @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + __project.name__ + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/Manual/CPButtonBarTest/main.j b/Tests/Manual/CPButtonBarTest/main.j new file mode 100644 index 000000000..b953537a0 --- /dev/null +++ b/Tests/Manual/CPButtonBarTest/main.j @@ -0,0 +1,18 @@ +/* + * AppController.j + * CPButtonBarTest + * + * Created by Ross Boucher on March 23, 2010. + * Copyright 2010, Your Company All rights reserved. + */ + +@import +@import + +@import "AppController.j" + + +function main(args, namedArgs) +{ + CPApplicationMain(args, namedArgs); +}