From a24f33ad93edfbb48efbd1ed67e2fb342dfe16eb Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Mon, 3 Dec 2012 19:11:35 -0500 Subject: [PATCH 01/17] Add .travis.yml for Travis-CI support --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..8d0eefbb8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: node_js +node_js: + - 0.8 +before_install: + - jdk_switcher use openjdk6 +install: ./bootstrap.sh --noprompt --directory ./narwhal +script: jake test +env: PATH=/home/travis/builds/ahankinson/cappuccino/narwhal/bin:$PATH CAPP_BUILD=/home/travis/builds/ahankinson/cappuccino/Build NARWHAL_ENGINE=rhino \ No newline at end of file From f5bb8abfe0368d7de3d1ee32b9f8dc83ef5193c8 Mon Sep 17 00:00:00 2001 From: Andrew Hankinson Date: Mon, 3 Dec 2012 19:24:14 -0500 Subject: [PATCH 02/17] Updating paths for integration into mainline cappuccino --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8d0eefbb8..2a34c8f25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ before_install: - jdk_switcher use openjdk6 install: ./bootstrap.sh --noprompt --directory ./narwhal script: jake test -env: PATH=/home/travis/builds/ahankinson/cappuccino/narwhal/bin:$PATH CAPP_BUILD=/home/travis/builds/ahankinson/cappuccino/Build NARWHAL_ENGINE=rhino \ No newline at end of file +env: PATH=/home/travis/builds/cappuccino/cappuccino/narwhal/bin:$PATH CAPP_BUILD=/home/travis/builds/cappuccino/cappuccino/Build NARWHAL_ENGINE=rhino \ No newline at end of file From e2f19c30f167b0c2812da907297af49f624cfbb3 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sat, 15 Dec 2012 01:03:52 +0000 Subject: [PATCH 03/17] Whitespace. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2a34c8f25..cb27662b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: node_js node_js: - 0.8 -before_install: +before_install: - jdk_switcher use openjdk6 install: ./bootstrap.sh --noprompt --directory ./narwhal script: jake test -env: PATH=/home/travis/builds/cappuccino/cappuccino/narwhal/bin:$PATH CAPP_BUILD=/home/travis/builds/cappuccino/cappuccino/Build NARWHAL_ENGINE=rhino \ No newline at end of file +env: PATH=/home/travis/builds/cappuccino/cappuccino/narwhal/bin:$PATH CAPP_BUILD=/home/travis/builds/cappuccino/cappuccino/Build NARWHAL_ENGINE=rhino From 7b2464a740d3f506fb79e5f78d0ba8ff45184aab Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sat, 15 Dec 2012 01:12:29 +0000 Subject: [PATCH 04/17] Fix tab view test to work after 2758454. Now that we use layoutSubviews in CPTabView we have to make sure it's called in a unit test scenario. --- Tests/AppKit/CPTabViewTest.j | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/Tests/AppKit/CPTabViewTest.j b/Tests/AppKit/CPTabViewTest.j index 8515240b9..fdde18b7e 100644 --- a/Tests/AppKit/CPTabViewTest.j +++ b/Tests/AppKit/CPTabViewTest.j @@ -28,15 +28,15 @@ - (void)setUp { _tabView = [[CPTabView alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]; - - _tabItem1 = [[CPTabViewItem alloc] initWithIdentifier:@"id1"]; + + _tabItem1 = [[CPTabViewItem alloc] initWithIdentifier:@"id1"]; [_tabItem1 setLabel:@"Item A"]; [_tabItem1 setView:[[CPView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]] _tabItem2 = [[CPTabViewItem alloc] initWithIdentifier:@"id2"]; [_tabItem2 setLabel:@"Item B"]; [_tabItem2 setView:[[CPView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]] - + [_tabView addTabViewItem:_tabItem1]; [_tabView addTabViewItem:_tabItem2]; } @@ -44,35 +44,41 @@ - (void)testCreate { [self assertNotNull:_tabView]; - + } - (void)testMiddle { var tabs = [_tabView tabs]; - [self assert:([_tabView frameSize].width / 2) equals:CPRectGetMidX([tabs frame])]; + [self assert:([_tabView frameSize].width / 2) equals:CGRectGetMidX([tabs frame])]; } - (void)testMiddleAfterMoveFrame { var tabs = [_tabView tabs]; - [_tabView setFrame:CPRectMake(10, 100, 1000, 200)]; - [self assert:([_tabView frameSize].width / 2) equals:CPRectGetMidX([tabs frame])]; + [_tabView setFrame:CGRectMake(10, 100, 1000, 200)]; + // Perform this manually for the sake of the unit test. + [_tabView layoutIfNeeded]; + [self assert:([_tabView frameSize].width / 2) equals:CGRectGetMidX([tabs frame])]; } - (void)testMiddleAfterMoveBound { var tabs = [_tabView tabs]; - [_tabView setBounds:CPRectMake(100, 100, 20, 300)]; - [self assert:([tabs boundsSize].width / 2) equals:CPRectGetMidX([tabs bounds])]; + [_tabView setBounds:CGRectMake(100, 100, 20, 300)]; + // Perform this manually for the sake of the unit test. + [_tabView layoutIfNeeded]; + [self assert:([tabs boundsSize].width / 2) equals:CGRectGetMidX([tabs bounds])]; } - (void)testBoxHeight { var box = [_tabView box], tabs = [_tabView tabs]; - - [_tabView setFrame:CPRectMake(0, 0, 800, 800)]; + + [_tabView setFrame:CGRectMake(0, 0, 800, 800)]; + // Perform this manually for the sake of the unit test. + [_tabView layoutIfNeeded]; [self assert:[box frameSize].height equals:800 - [tabs frameSize].height / 2]; } @@ -87,4 +93,4 @@ [self assertNull:[_tabItem1 tabView]]; } -@end \ No newline at end of file +@end From 7070fe9b2eeeba064ebaa7919c82e0ea02328282 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sat, 15 Dec 2012 01:31:28 +0000 Subject: [PATCH 05/17] --quiet and --verbose boostrap.sh options. Without this change, bootstrap.sh outputs a great deal of often superfluous information, like precisely which files are unzipped. Also, curl and wget display progress information by default. With this change, by default unzipping is quiet, unless --verbose is specified. curl and wget still display progress information, but --quiet can be specified to prevent this. --- bootstrap.sh | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index dd4d6948f..65eff8a08 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -144,6 +144,7 @@ github_ref="v0.9.6-RC2" noprompt="" install_capp="" install_method="zip" +verbosity=1 while [ $# -gt 0 ]; do case "$1" in @@ -153,6 +154,8 @@ while [ $# -gt 0 ]; do --clone-http) install_method="clone --http";; --github-user) github_user="$2"; shift;; --github-ref) github_ref="$2"; shift;; + -q | --quiet) verbosity=$[verbosity - 1]; shift;; + -v | --verbose) verbosity=$[verbosity + 1]; shift;; *) cat >&2 <<-EOT usage: ./bootstrap.sh [OPTIONS] @@ -162,6 +165,8 @@ usage: ./bootstrap.sh [OPTIONS] --clone-http: Do "git clone http://" instead of downloading zips. --github-user [USER]: Github user (default: $github_user). --github-ref [REF]: Use another git ref (default: $github_ref). + -q | --quiet: Output less logging. + -v | --verbose: Output more logging. EOT exit 1;; esac @@ -174,7 +179,8 @@ github_path="$github_user/cappuccino-base" # The purpose of bootstrap is to install Cappuccino. install_cappuccino="yes" -sed "s/\[\[ CAPPUCCINO_VERSION \]\]/$github_ref/" < 0 )); then + sed "s/\[\[ CAPPUCCINO_VERSION \]\]/$github_ref/" < /dev/null && echo curl -L -o || echo wget --no-check-certificate -O) "$tmp_zip" "$zip_ball" + curl_quiet_arg="" + wget_quiet_arg="" + if (( $verbosity < 1 )); then curl_quiet_arg="--silent"; wget_quiet_arg="--no-verbose"; fi + $(which curl &> /dev/null && echo curl $curl_quiet_arg -L -o || echo $wget_quiet_arg --no-check-certificate -O) "$tmp_zip" "$zip_ball" check_and_exit echo "Installing Cappuccino base..." - unzip "$tmp_zip" -d "$install_directory" + + quiet_arg="" + if (( $verbosity < 2 )); then quiet_arg="-q"; fi + unzip $quiet_arg "$tmp_zip" -d "$install_directory" check_and_exit rm "$tmp_zip" check_and_exit From 1ebdd8ed8e7df0cd7571c56975ed961e4be40952 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sat, 15 Dec 2012 01:32:06 +0000 Subject: [PATCH 06/17] Try to make travis output more to the point. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cb27662b6..841ce09f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,6 @@ node_js: - 0.8 before_install: - jdk_switcher use openjdk6 -install: ./bootstrap.sh --noprompt --directory ./narwhal +install: ./bootstrap.sh --quiet --noprompt --directory ./narwhal script: jake test env: PATH=/home/travis/builds/cappuccino/cappuccino/narwhal/bin:$PATH CAPP_BUILD=/home/travis/builds/cappuccino/cappuccino/Build NARWHAL_ENGINE=rhino From d82da5d57150e6528b687e1d5183a970d6e77c7f Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sat, 15 Dec 2012 01:36:05 +0000 Subject: [PATCH 07/17] Fix 7070fe9b2e. --- bootstrap.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index 65eff8a08..7044cffbe 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -154,8 +154,8 @@ while [ $# -gt 0 ]; do --clone-http) install_method="clone --http";; --github-user) github_user="$2"; shift;; --github-ref) github_ref="$2"; shift;; - -q | --quiet) verbosity=$[verbosity - 1]; shift;; - -v | --verbose) verbosity=$[verbosity + 1]; shift;; + -q|--quiet) verbosity=$[verbosity - 1];; + -v|--verbose) verbosity=$[verbosity + 1];; *) cat >&2 <<-EOT usage: ./bootstrap.sh [OPTIONS] From 252b8f5ca5231c29bbe2a3c71561fabad3f81749 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sat, 15 Dec 2012 11:48:15 +0000 Subject: [PATCH 08/17] Fixed: [CPApp windows] contained nil. Before this fix, CPApp tried to add nil to its internal list of windows, even that nil should not be added to a CPArray. Second, that nil was actually returned by [CPApp windows] rather than an expected list of only CPWindow instances. This fix makes [CPApp windows] return only actual windows, replaces the internal _windows[0] placeholder with CPNull, and ensures this CPNull is never actually exposed outside of the class. --- AppKit/CPApplication.j | 13 +++++++++---- Tests/AppKit/CPApplicationTest.j | 12 +++++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/AppKit/CPApplication.j b/AppKit/CPApplication.j index 63b1691a3..e0fd5261f 100644 --- a/AppKit/CPApplication.j +++ b/AppKit/CPApplication.j @@ -20,6 +20,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#import "../Foundation/CPRange.h" + @import @import "CPCompatibility.j" @@ -139,9 +141,7 @@ CPRunContinuesResponse = -1002; { _eventListeners = []; - _windows = []; - - [_windows addObject:nil]; + _windows = [[CPNull null]]; } return self; @@ -655,6 +655,10 @@ CPRunContinuesResponse = -1002; */ - (CPWindow)windowWithWindowNumber:(int)aWindowNumber { + // Never allow _windows[0] to be returned - it's an internal CPNull placeholder. + if (!aWindowNumber) + return nil; + return _windows[aWindowNumber]; } @@ -663,7 +667,8 @@ CPRunContinuesResponse = -1002; */ - (CPArray)windows { - return _windows; + // Return all windows, but not the CPNull placeholder in _windows[0]. + return [_windows subarrayWithRange:_CPMakeRange(1, [_windows count] - 1)]; } /*! diff --git a/Tests/AppKit/CPApplicationTest.j b/Tests/AppKit/CPApplicationTest.j index f3ceefb57..7ab0de490 100644 --- a/Tests/AppKit/CPApplicationTest.j +++ b/Tests/AppKit/CPApplicationTest.j @@ -1,7 +1,7 @@ @import @import -globalResults = []; +var globalResults = []; @implementation MyAppDelegate : CPObject { @@ -77,6 +77,12 @@ globalResults = []; globalResults = [] } +- (void)tearDown +{ + // This is the only way to clear the global window list between tests. You'd normally never do this. + CPApp = nil; +} + - (void)receiveNotification:(CPNotification)aNote { globalResults.push(aNote); @@ -182,12 +188,12 @@ globalResults = []; - (void)testWindows { - [self assert:@"My Great Window" equals:[[[app windows] objectAtIndex:1] title]]; + [self assert:[[aWindow title]] equals:[[app windows] valueForKey:@"title"]]; } - (void)testWindowWithWindowNumber { - [self assert:@"My Great Window" equals:[[app windowWithWindowNumber:1] title]]; + [self assert:@"My Great Window" equals:[[app windowWithWindowNumber:[aWindow windowNumber]] title]]; } - (void)testReplyToApplicationShouldTerminate From 848c893b9e9e0a5b2d8d38c8db4492cd26f3a71f Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sat, 15 Dec 2012 11:50:47 +0000 Subject: [PATCH 09/17] Fixed: CPApplicationTest setArguments. Without this change, CPApplicationTest's manipulation of window.location.hash was ignored by CPApplication in recent versions, since CPApp in a unit test normally does not have access to the window object. This change makes it so that CPApp does try to access the window object, even in a unit test scenario (non PLATFORM(DOM)). --- AppKit/CPApplication.j | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AppKit/CPApplication.j b/AppKit/CPApplication.j index e0fd5261f..8743ecee4 100644 --- a/AppKit/CPApplication.j +++ b/AppKit/CPApplication.j @@ -1047,9 +1047,9 @@ CPRunContinuesResponse = -1002; if (!args || args.length == 0) { _args = []; -#if PLATFORM(DOM) - window.location.hash = @"#"; -#endif + // Don't use if PLATFORM(DOM) here - the unit test fakes window.location so we should play along. + if (window && window.location) + window.location.hash = @"#"; return; } @@ -1064,9 +1064,9 @@ CPRunContinuesResponse = -1002; var hash = [toEncode componentsJoinedByString:@"/"]; -#if PLATFORM(DOM) - window.location.hash = @"#" + hash; -#endif + // Don't use if PLATFORM(DOM) here - the unit test fakes window.location so we should play along. + if (window && window.location) + window.location.hash = @"#" + hash; } - (void)_reloadArguments From a202cfbbe9c4a5e902ab09e0135a64c439116bad Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Sat, 15 Dec 2012 12:36:00 +0000 Subject: [PATCH 10/17] Skip check-missing-imports in standard jake test to make the load on travis-ci lighter. "detect-missing-imports" takes a long, long time to run. Since it only emits warnings it can't affect the result of the continuous integration builds. Until it can be made a little faster it's probably best to run it by hand with `jake check-missing-imports`. --- Jakefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Jakefile b/Jakefile index ebf8f9e34..09f91bd23 100644 --- a/Jakefile +++ b/Jakefile @@ -263,8 +263,8 @@ task ("demos", function() rm_rf(demosDir); FILE.mkdirs(demosDir); - OS.system("curl -L http://github.com/280north/cappuccino-demos/zipball/master > "+zipQuoted); - OS.system("(cd "+demosQuoted+" && unzip "+zipQuoted+" -d demos)"); + OS.system("curl -L http://github.com/280north/cappuccino-demos/zipball/master > " + zipQuoted); + OS.system("(cd " + demosQuoted + " && unzip " + zipQuoted + " -d demos)"); require("objective-j"); @@ -311,8 +311,8 @@ task ("demos", function() cp_r(FILE.join($STARTER_DOWNLOAD_APPLICATION, "Frameworks"), FILE.join(demo.path(), "Frameworks")); rm_rf(FILE.join(demo.path(), "Frameworks", "Debug")); - var outputPath = demo.name().replace(/\s/g, "-")+".zip"; - OS.system("cd "+OS.enquote(FILE.dirname(demo.path()))+" && zip -ry -8 "+OS.enquote(outputPath)+" "+OS.enquote(FILE.basename(demo.path()))); + var outputPath = demo.name().replace(/\s/g, "-") + ".zip"; + OS.system("cd " + OS.enquote(FILE.dirname(demo.path()))+" && zip -ry -8 " + OS.enquote(outputPath) + " " + OS.enquote(FILE.basename(demo.path()))); // remove the frameworks rm_rf(FILE.join(demo.path(), "Frameworks")); @@ -321,7 +321,7 @@ task ("demos", function() // Testing -task("test", ["CommonJS", "test-only", "check-missing-imports"]); +task("test", ["CommonJS", "test-only"]); task("test-only", function() { From cea120945ca832400a22ae94153ed5c77f03adc3 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Tue, 18 Dec 2012 00:19:04 +0000 Subject: [PATCH 11/17] Fixed: CPAlert sheet crash. Without this fix, CPAlert windows would not finish closing before sending the `alertDidEnd:returnCode:` delegate message. This would lead to a crash if the delegate method tried to open a new sheet. This fix makes sure to fully order out the alert sheet before sending the delegate message. --- AppKit/CPAlert.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AppKit/CPAlert.j b/AppKit/CPAlert.j index 6ed8e1d73..8c5f689b4 100644 --- a/AppKit/CPAlert.j +++ b/AppKit/CPAlert.j @@ -633,8 +633,8 @@ CPCriticalAlertStyle = 2; { if ([_window isSheet]) { - [CPApp endSheet:_window returnCode:[aSender tag]]; [_window orderOut:nil]; + [CPApp endSheet:_window returnCode:[aSender tag]]; } else { From 3775c3fb2e4148824692029cd5e46d6e8fb2eddf Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Tue, 18 Dec 2012 00:22:19 +0000 Subject: [PATCH 12/17] Fixed: CPAlert was a CPView. Before this fix, CPAlert subclassed CPView, causing an API mismatch with Cocoa and exposing unexpected methods in the public API which made no sense for a CPAlert such as autoresizing masks. It also lead to CPAlert mistakingly shadowing _window from its CPView superclass. This fix makes CPAlert inherit from CPObject as it should. The theming, which needs to be applied to a view, was moved to an internal view. --- AppKit/CPAlert.j | 140 ++++++++++++++---------- AppKit/Themes/Aristo/ThemeDescriptors.j | 4 +- 2 files changed, 82 insertions(+), 62 deletions(-) diff --git a/AppKit/CPAlert.j b/AppKit/CPAlert.j index 8c5f689b4..76b4c64b3 100644 --- a/AppKit/CPAlert.j +++ b/AppKit/CPAlert.j @@ -75,32 +75,33 @@ CPCriticalAlertStyle = 2; representing the first button added to the alert which appears on the right, 1 representing the next button to the left and so on) */ -@implementation CPAlert : CPView +@implementation CPAlert : CPObject { - BOOL _showHelp @accessors(property=showsHelp); - BOOL _showSuppressionButton @accessors(property=showsSuppressionButton); + BOOL _showHelp @accessors(property=showsHelp); + BOOL _showSuppressionButton @accessors(property=showsSuppressionButton); - CPAlertStyle _alertStyle @accessors(property=alertStyle); - CPString _title @accessors(property=title); - CPView _accessoryView @accessors(property=accessoryView); - CPImage _icon @accessors(property=icon); + CPAlertStyle _alertStyle @accessors(property=alertStyle); + CPString _title @accessors(property=title); + CPView _accessoryView @accessors(property=accessoryView); + CPImage _icon @accessors(property=icon); - CPArray _buttons @accessors(property=buttons,readonly); - CPCheckBox _suppressionButton @accessors(property=suppressionButton,readonly); + CPArray _buttons @accessors(property=buttons,readonly); + CPCheckBox _suppressionButton @accessors(property=suppressionButton,readonly); - id _delegate @accessors(property=delegate); - id _modalDelegate; - SEL _didEndSelector; + id _delegate @accessors(property=delegate); + id _modalDelegate; + SEL _didEndSelector; - CPWindow _window @accessors(property=window,readonly); - int _defaultWindowStyle; + _CPAlertThemeView _themeView @accessors(property=themeView, readonly); + CPWindow _window @accessors(property=window,readonly); + int _defaultWindowStyle; - CPImageView _alertImageView; - CPTextField _informativeLabel; - CPTextField _messageLabel; - CPButton _alertHelpButton; + CPImageView _alertImageView; + CPTextField _informativeLabel; + CPTextField _messageLabel; + CPButton _alertHelpButton; - BOOL _needsLayout; + BOOL _needsLayout; } #pragma mark Creating Alerts @@ -164,6 +165,7 @@ CPCriticalAlertStyle = 2; _showHelp = NO; _needsLayout = YES; _defaultWindowStyle = CPTitledWindowMask; + _themeView = [_CPAlertThemeView new]; _messageLabel = [CPTextField labelWithTitle:@"Alert"]; _alertImageView = [[CPImageView alloc] init]; @@ -180,6 +182,11 @@ CPCriticalAlertStyle = 2; #pragma mark Accessors +- (CPTheme)theme +{ + return [_themeView theme]; +} + /*! set the theme to use @@ -197,9 +204,20 @@ CPCriticalAlertStyle = 2; _window = nil; // will be regenerated at next layout _needsLayout = YES; - [super setTheme:aTheme]; + [_themeView setTheme:aTheme]; } +- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName +{ + [_themeView setValue:aValue forThemeAttribute:aName]; +} + +- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName inState:(CPThemeState)aState +{ + [_themeView setValue:aValue forThemeAttribute:aName inState:aState]; +} + + /*! @deprecated */ - (void)setWindowStyle:(int)aStyle @@ -334,16 +352,16 @@ CPCriticalAlertStyle = 2; */ - (void)_layoutMessageView { - var inset = [self currentValueForThemeAttribute:@"content-inset"], + var inset = [_themeView currentValueForThemeAttribute:@"content-inset"], sizeWithFontCorrection = 6.0, messageLabelWidth, messageLabelTextSize; - [_messageLabel setTextColor:[self currentValueForThemeAttribute:@"message-text-color"]]; - [_messageLabel setFont:[self currentValueForThemeAttribute:@"message-text-font"]]; - [_messageLabel setTextShadowColor:[self currentValueForThemeAttribute:@"message-text-shadow-color"]]; - [_messageLabel setTextShadowOffset:[self currentValueForThemeAttribute:@"message-text-shadow-offset"]]; - [_messageLabel setAlignment:[self currentValueForThemeAttribute:@"message-text-alignment"]]; + [_messageLabel setTextColor:[_themeView currentValueForThemeAttribute:@"message-text-color"]]; + [_messageLabel setFont:[_themeView currentValueForThemeAttribute:@"message-text-font"]]; + [_messageLabel setTextShadowColor:[_themeView currentValueForThemeAttribute:@"message-text-shadow-color"]]; + [_messageLabel setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"message-text-shadow-offset"]]; + [_messageLabel setAlignment:[_themeView currentValueForThemeAttribute:@"message-text-alignment"]]; [_messageLabel setLineBreakMode:CPLineBreakByWordWrapping]; messageLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right; @@ -357,18 +375,18 @@ CPCriticalAlertStyle = 2; */ - (void)_layoutInformativeView { - var inset = [self currentValueForThemeAttribute:@"content-inset"], - defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"], + var inset = [_themeView currentValueForThemeAttribute:@"content-inset"], + defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"], sizeWithFontCorrection = 6.0, informativeLabelWidth, informativeLabelOriginY, informativeLabelTextSize; - [_informativeLabel setTextColor:[self currentValueForThemeAttribute:@"informative-text-color"]]; - [_informativeLabel setFont:[self currentValueForThemeAttribute:@"informative-text-font"]]; - [_informativeLabel setTextShadowColor:[self currentValueForThemeAttribute:@"informative-text-shadow-color"]]; - [_informativeLabel setTextShadowOffset:[self currentValueForThemeAttribute:@"informative-text-shadow-offset"]]; - [_informativeLabel setAlignment:[self currentValueForThemeAttribute:@"informative-text-alignment"]]; + [_informativeLabel setTextColor:[_themeView currentValueForThemeAttribute:@"informative-text-color"]]; + [_informativeLabel setFont:[_themeView currentValueForThemeAttribute:@"informative-text-font"]]; + [_informativeLabel setTextShadowColor:[_themeView currentValueForThemeAttribute:@"informative-text-shadow-color"]]; + [_informativeLabel setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"informative-text-shadow-offset"]]; + [_informativeLabel setAlignment:[_themeView currentValueForThemeAttribute:@"informative-text-alignment"]]; [_informativeLabel setLineBreakMode:CPLineBreakByWordWrapping]; informativeLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right; @@ -386,8 +404,8 @@ CPCriticalAlertStyle = 2; if (!_accessoryView) return; - var inset = [self currentValueForThemeAttribute:@"content-inset"], - defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"], + var inset = [_themeView currentValueForThemeAttribute:@"content-inset"], + defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"], accessoryViewWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right, accessoryViewOriginY = CGRectGetMaxY([_informativeLabel frame]) + defaultElementsMargin; @@ -403,16 +421,16 @@ CPCriticalAlertStyle = 2; if (!_showSuppressionButton) return; - var inset = [self currentValueForThemeAttribute:@"content-inset"], - suppressionViewXOffset = [self currentValueForThemeAttribute:@"suppression-button-x-offset"], - suppressionViewYOffset = [self currentValueForThemeAttribute:@"suppression-button-y-offset"], - defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"], + var inset = [_themeView currentValueForThemeAttribute:@"content-inset"], + suppressionViewXOffset = [_themeView currentValueForThemeAttribute:@"suppression-button-x-offset"], + suppressionViewYOffset = [_themeView currentValueForThemeAttribute:@"suppression-button-y-offset"], + defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"], suppressionButtonViewOriginY = CGRectGetMaxY([(_accessoryView || _informativeLabel) frame]) + defaultElementsMargin + suppressionViewYOffset; - [_suppressionButton setTextColor:[self currentValueForThemeAttribute:@"suppression-button-text-color"]]; - [_suppressionButton setFont:[self currentValueForThemeAttribute:@"suppression-button-text-font"]]; - [_suppressionButton setTextShadowColor:[self currentValueForThemeAttribute:@"suppression-button-text-shadow-color"]]; - [_suppressionButton setTextShadowOffset:[self currentValueForThemeAttribute:@"suppression-button-text-shadow-offset"]]; + [_suppressionButton setTextColor:[_themeView currentValueForThemeAttribute:@"suppression-button-text-color"]]; + [_suppressionButton setFont:[_themeView currentValueForThemeAttribute:@"suppression-button-text-font"]]; + [_suppressionButton setTextShadowColor:[_themeView currentValueForThemeAttribute:@"suppression-button-text-shadow-color"]]; + [_suppressionButton setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"suppression-button-text-shadow-offset"]]; [_suppressionButton sizeToFit]; [_suppressionButton setFrameOrigin:CGPointMake(inset.left + suppressionViewXOffset, suppressionButtonViewOriginY)]; @@ -424,12 +442,12 @@ CPCriticalAlertStyle = 2; */ - (CGSize)_layoutButtonsFromView:(CPView)lastView { - var inset = [self currentValueForThemeAttribute:@"content-inset"], - minimumSize = [self currentValueForThemeAttribute:@"size"], - buttonOffset = [self currentValueForThemeAttribute:@"button-offset"], - helpLeftOffset = [self currentValueForThemeAttribute:@"help-image-left-offset"], + var inset = [_themeView currentValueForThemeAttribute:@"content-inset"], + minimumSize = [_themeView currentValueForThemeAttribute:@"size"], + buttonOffset = [_themeView currentValueForThemeAttribute:@"button-offset"], + helpLeftOffset = [_themeView currentValueForThemeAttribute:@"help-image-left-offset"], aRepresentativeButton = [_buttons objectAtIndex:0], - defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"], + defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"], panelSize = [[_window contentView] frame].size, buttonsOriginY, offsetX; @@ -461,8 +479,8 @@ CPCriticalAlertStyle = 2; if (_showHelp) { - var helpImage = [self currentValueForThemeAttribute:@"help-image"], - helpImagePressed = [self currentValueForThemeAttribute:@"help-image-pressed"], + var helpImage = [_themeView currentValueForThemeAttribute:@"help-image"], + helpImagePressed = [_themeView currentValueForThemeAttribute:@"help-image-pressed"], helpImageSize = helpImage ? [helpImage size] : CGSizeMakeZero(), helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY, helpImageSize.width, helpImageSize.height); @@ -487,7 +505,7 @@ CPCriticalAlertStyle = 2; if (!_window) [self _createWindowWithStyle:nil]; - var iconOffset = [self currentValueForThemeAttribute:@"image-offset"], + var iconOffset = [_themeView currentValueForThemeAttribute:@"image-offset"], theImage = _icon, finalSize; @@ -495,13 +513,13 @@ CPCriticalAlertStyle = 2; switch (_alertStyle) { case CPWarningAlertStyle: - theImage = [self currentValueForThemeAttribute:@"warning-image"]; + theImage = [_themeView currentValueForThemeAttribute:@"warning-image"]; break; case CPInformationalAlertStyle: - theImage = [self currentValueForThemeAttribute:@"information-image"]; + theImage = [_themeView currentValueForThemeAttribute:@"information-image"]; break; case CPCriticalAlertStyle: - theImage = [self currentValueForThemeAttribute:@"error-image"]; + theImage = [_themeView currentValueForThemeAttribute:@"error-image"]; break; } @@ -592,7 +610,7 @@ CPCriticalAlertStyle = 2; - (void)_createWindowWithStyle:(int)forceStyle { var frame = CGRectMakeZero(); - frame.size = [self currentValueForThemeAttribute:@"size"]; + frame.size = [_themeView currentValueForThemeAttribute:@"size"]; _window = [[CPWindow alloc] initWithContentRect:frame styleMask:forceStyle || _defaultWindowStyle]; [_window setLevel:CPStatusWindowLevel]; @@ -633,8 +651,8 @@ CPCriticalAlertStyle = 2; { if ([_window isSheet]) { - [_window orderOut:nil]; [CPApp endSheet:_window returnCode:[aSender tag]]; + [_window orderOut:nil]; } else { @@ -650,17 +668,19 @@ CPCriticalAlertStyle = 2; */ - (void)_alertDidEnd:(CPWindow)aWindow returnCode:(int)returnCode contextInfo:(id)contextInfo { - if ([_delegate respondsToSelector:@selector(alertDidEnd:returnCode:)]) - [_delegate alertDidEnd:self returnCode:returnCode]; - if (_didEndSelector) objj_msgSend(_modalDelegate, _didEndSelector, self, returnCode, contextInfo); _modalDelegate = nil; _didEndSelector = nil; + + if ([_delegate respondsToSelector:@selector(alertDidEnd:returnCode:)]) + [_delegate alertDidEnd:self returnCode:returnCode]; } -#pragma mark Theme Attributes +@end + +@implementation _CPAlertThemeView : CPView + (CPString)defaultThemeClass { diff --git a/AppKit/Themes/Aristo/ThemeDescriptors.j b/AppKit/Themes/Aristo/ThemeDescriptors.j index 4eaa51738..c57c063c8 100755 --- a/AppKit/Themes/Aristo/ThemeDescriptors.j +++ b/AppKit/Themes/Aristo/ThemeDescriptors.j @@ -1832,7 +1832,7 @@ var themedButtonValues = nil, [self registerThemeValues:themedAlertValues forView:alert]; - return alert; + return [alert themeView]; } + (CPStepper)themedStepper @@ -2095,7 +2095,7 @@ var themedButtonValues = nil, [self registerThemeValues:hudSpecificValues forView:alert inherit:themedAlertValues]; - return alert; + return [alert themeView]; } @end From 2a11bdb7ef3bb155bbf8f31457843a0ca6d5a3ff Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Tue, 18 Dec 2012 00:34:14 +0000 Subject: [PATCH 13/17] Fixup: previous fix undid penultimate fix. --- AppKit/CPAlert.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AppKit/CPAlert.j b/AppKit/CPAlert.j index 76b4c64b3..8a4186e47 100644 --- a/AppKit/CPAlert.j +++ b/AppKit/CPAlert.j @@ -651,8 +651,8 @@ CPCriticalAlertStyle = 2; { if ([_window isSheet]) { - [CPApp endSheet:_window returnCode:[aSender tag]]; [_window orderOut:nil]; + [CPApp endSheet:_window returnCode:[aSender tag]]; } else { From 9705f1fe289d5a5b1ddd9fb4353ee63311b51e68 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Fri, 21 Dec 2012 11:05:59 +0000 Subject: [PATCH 14/17] Commit message guidelines. --- CONTRIBUTING.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b78b2f6c5..7f75c0173 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,6 +29,48 @@ Now you can go to GitHub and [make a pull request](https://help.github.com/artic Some things are difficult to unit test in a way that's worthwhile. For instance a change to the mouse interaction of a `CPButton`. In this case a manual test is fine instead. Find the closest related manual test in `Tests/Manual` and add whatever it takes to easily reproduce the patched behavior, ideally with some labels describing how someone running the manual test should exercise it. +## Commit Messages + +The style and format of your commit messages are very important to the health of the project. A good commit message helps not only users reading the release notes, but also your fellow Cappuccino developers as they review `git log` or `git blame` to figure out what you were doing. + +This is the preferred format of commit messages for complex changes: + + [New:] + + [OPTIONAL DESCRIPTION ABOUT WHAT THIS THING DOES.] + +Or for fixes: + + Fixed: + + BEFORE + + AFTER + +**Example:** + + nib2cib support for table view background colour. + +**Example:** + + Fixed: CPAlert was a CPView. + + Before this fix, CPAlert subclassed CPView, causing an API mismatch with Cocoa and exposing unexpected methods in the public API which made no sense for a CPAlert such as autoresizing masks. It also lead to CPAlert mistakingly shadowing _window from its CPView superclass. + + This fix makes CPAlert inherit from CPObject as it should. The theming, which needs to be applied to a view, was moved to an internal view. + +The summary line concisely says what was fixed or improved by the commit. If it's an improvement or something new it optionally starts with "New:". If it's a bug fix it starts with "Fixed:". + +For a 'new' change, the rest of the summary line answers the question *"What is new and where?"*. Optionally the summary can be followed with more detail about what's new and why it's great. + +If the summary starts with "Fixed:" the rest of the summary line answers the question *"What was fixed?"*. E.g. "Fixed: the blargh had too many blurghs." + +(Note it does NOT answer the question *"what was done to fix it?"* E.g. **WRONG:** "Fixed: removed 2 blurghs from the blargh." That would imply that your very change was what was fixed by itself, causing an infinite loop in the universe and ending the world as we know it.) + +The rest of the fix message is in a before / after form. The first part describes what the situation was before the fix and says why the fix was needed. The after part describes what change was made to rectify the situation and any implementation notes that might be useful to know for people reviewing it in the future. + +Naturally small or unimportant commits have less rigid requirements. If you can comfortably and easily describe the whole change in the summary only, go ahead. Maybe you fixed a tiny typo or did a minor refactoring. + ## Cappuccino Coding Style Guidelines ### Trailing whitespace From 8f8f1a0224581bcbe90f0bc28ee7aa534397fc77 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Fri, 21 Dec 2012 15:00:20 +0000 Subject: [PATCH 15/17] Fixed: token field autocomplete menu remaining after window closed. Without this change, closing the window containing a token field showing an autocomplete menu would result in a detached autocomplete menu hanging around. This fix checks for window close notifications and makes sure to dismiss the menu if the window closes. --- AppKit/CPView.j | 5 +++++ AppKit/_CPAutocompleteMenu.j | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/AppKit/CPView.j b/AppKit/CPView.j index a1b1d42ba..ce63921db 100644 --- a/AppKit/CPView.j +++ b/AppKit/CPView.j @@ -651,6 +651,9 @@ var CPViewFlags = { }, if (_window === aWindow) return; + // Unlike in Cocoa you can observe the window of a view. + [self willChangeValueForKey:@"window"]; + [[self window] _dirtyKeyViewLoop]; // Clear out first responder if we're the first responder and leaving. @@ -678,6 +681,8 @@ var CPViewFlags = { }, [self viewDidMoveToWindow]; [[self window] _dirtyKeyViewLoop]; + + [self didChangeValueForKey:@"window"]; } /*! diff --git a/AppKit/_CPAutocompleteMenu.j b/AppKit/_CPAutocompleteMenu.j index 2aaafd81f..e5254de15 100644 --- a/AppKit/_CPAutocompleteMenu.j +++ b/AppKit/_CPAutocompleteMenu.j @@ -38,6 +38,7 @@ var _CPAutocompleteMenuMaximumHeight = 307; float widestItemWidth; CPWindow _menuWindow; + CPWindow _parentWindow @accessors(property=parentWindow); CPScrollView scrollView; CPTableView tableView; @@ -48,7 +49,8 @@ var _CPAutocompleteMenuMaximumHeight = 307; { if (self = [super init]) { - textField = aTextField; + [self bind:@"parentWindow" toObject:self withKeyPath:@"textField.window" options:nil]; + [self setTextField:aTextField]; _menuWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0, 0, 100, 100) styleMask:CPBorderlessWindowMask]; @@ -258,6 +260,25 @@ var _CPAutocompleteMenuMaximumHeight = 307; [textField _complete:self]; } +- (void)setParentWindow:(CPWindow)aWindow +{ + if (aWindow === _parentWindow) + return; + + if (_parentWindow) + [[CPNotificationCenter defaultCenter] removeObserver:self]; + + _parentWindow = aWindow; + + if (_parentWindow) + [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(parentWindowWillClose:) name:CPWindowWillCloseNotification object:_parentWindow]; +} + +- (void)parentWindowWillClose:(CPNotification)aNotification +{ + [self _hideCompletions]; +} + @end From 588da9addb59ad897feaf81c15a6505220f42e3c Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Fri, 21 Dec 2012 16:44:35 +0000 Subject: [PATCH 16/17] Fixed: token field access of DOM in non-DOM apps. Without this fix, the internal [tokenField _editorValue] would check the DOM input element even if !PLATFORM(DOM), which could potentially crash unit tests and future non DOM implementations. The fix returns an empty string for non-DOM platforms. --- AppKit/CPTokenField.j | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AppKit/CPTokenField.j b/AppKit/CPTokenField.j index e333e3a59..cc19b24e5 100755 --- a/AppKit/CPTokenField.j +++ b/AppKit/CPTokenField.j @@ -706,7 +706,11 @@ var CPScrollDestinationNone = 0, { if (![self hasThemeState:CPThemeStateEditing]) return @""; +#if PLATFORM(DOM) return [self _inputElement].value; +#else + return @""; +#endif } - (void)moveUp:(id)sender From b7031f2f77ee077b1a66b3eb77d99444c32e7cff Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Fri, 21 Dec 2012 16:53:21 +0000 Subject: [PATCH 17/17] Unit test token field autocomplete window close. --- AppKit/CPWindow/CPWindow.j | 4 +++ AppKit/_CPAutocompleteMenu.j | 2 +- Tests/AppKit/CPTokenFieldTest.j | 49 +++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/AppKit/CPWindow/CPWindow.j b/AppKit/CPWindow/CPWindow.j index ffd7de7e6..82185385a 100644 --- a/AppKit/CPWindow/CPWindow.j +++ b/AppKit/CPWindow/CPWindow.j @@ -901,6 +901,8 @@ CPTexturedBackgroundWindowMask [_platformWindow orderFront:self]; [_platformWindow order:CPWindowAbove window:self relativeTo:nil]; +#else + _isVisible = YES; #endif if (!CPApp._keyWindow) @@ -946,6 +948,8 @@ CPTexturedBackgroundWindowMask #if PLATFORM(DOM) [_platformWindow order:CPWindowOut window:self relativeTo:nil]; +#else + _isVisible = NO; #endif [self _updateMainAndKeyWindows]; diff --git a/AppKit/_CPAutocompleteMenu.j b/AppKit/_CPAutocompleteMenu.j index e5254de15..3a658d308 100644 --- a/AppKit/_CPAutocompleteMenu.j +++ b/AppKit/_CPAutocompleteMenu.j @@ -187,7 +187,7 @@ var _CPAutocompleteMenuMaximumHeight = 307; { var indexOfSelectedItem = [self indexOfSelectedItem]; - [self setContentArray:[textField _completionsForSubstring:[textField _inputElement].value indexOfToken:0 indexOfSelectedItem:indexOfSelectedItem]]; + [self setContentArray:[textField _completionsForSubstring:[textField _editorValue] indexOfToken:0 indexOfSelectedItem:indexOfSelectedItem]]; if (![contentArray count]) return; diff --git a/Tests/AppKit/CPTokenFieldTest.j b/Tests/AppKit/CPTokenFieldTest.j index 14e5440f7..76d593ea4 100644 --- a/Tests/AppKit/CPTokenFieldTest.j +++ b/Tests/AppKit/CPTokenFieldTest.j @@ -28,4 +28,53 @@ [[aWindow contentView] addSubview:tokenField]; } +- (void)testCloseParentWindow +{ + var aWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0) styleMask:CPTitledWindowMask], + tokenField = [[CPTokenField alloc] initWithFrame:CGRectMake(10, 10, 100, 28)]; + + [[aWindow contentView] addSubview:tokenField]; + [tokenField setEnabled:YES]; + [tokenField setEditable:YES]; + + [aWindow makeKeyAndOrderFront:nil]; + + // Start autocomplete. + var tokenDelegate = [TokenFieldDelegate new]; + [tokenDelegate setCompletions:[@"Tokyo", @"Toronto", @"Gothenburg", @"London"]]; + [tokenField setDelegate:tokenDelegate]; + + [tokenField setStringValue:@"To"]; + // Start autocomplete programmatically. + [aWindow makeFirstResponder:tokenField]; + [[tokenField _autocompleteMenu] _showCompletions:nil]; + + // Verify we're now autocompleting. + [self assertTrue:[[tokenField _autocompleteMenu]._menuWindow isVisible] message:@"autocomplete visible"]; + + [aWindow close]; + [self assertFalse:[[tokenField _autocompleteMenu]._menuWindow isVisible] message:@"autocomplete not visible when token field window closes"]; +} + +@end + +@implementation TokenFieldDelegate : CPObject +{ + CPArray completions @accessors; +} + +- (CPArray)tokenField:(CPTokenField)aTokenField completionsForSubstring:(CPString)substring indexOfToken:(int)tokenIndex indexOfSelectedItem:(int)selectedIndex +{ + var r = []; + + if (!substring) + return completions; + + for (var i = 0; i < completions.length; i++) + if (completions[i].toLowerCase().indexOf(completions.toLowerCase()) == 0) + r.push(completions[i]); + + return r; +} + @end