Compare commits

..
1 Commits
Author SHA1 Message Date
David Richardson 048c7e269c Update README to reflect status as tombstone branch ‘legacy-1.4.0’
Change README file extension from .markdown to .md for universal editor support.
2026-08-04 14:38:15 -06:00
1032 changed files with 158448 additions and 69042 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [24.x]
node-version: [20.x, 21.x, 22.x, 23.x, 24.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
@@ -26,8 +26,6 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
- run: echo "${PWD}/dist/cappuccino/bin" >> $GITHUB_PATH
- run: echo "${PWD}/dist/objective-j/bin" >> $GITHUB_PATH
-2
View File
@@ -26,5 +26,3 @@ node_modules
/dist/cappuccino/package.json
/dist/cappuccino/lib
/dist/cappuccino/bin
Tests/Manual/.Frameworks
/Tests/Manual/index.html
+32 -82
View File
@@ -418,25 +418,20 @@ var bottomHeight = 71;
*/
- (void)addButtonWithTitle:(CPString)aTitle
{
var count = [_buttons count],
var bounds = [[_window contentView] bounds],
count = [_buttons count],
button = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
[button setTitle:aTitle];
[button setTag:count];
[button setTarget:self];
[button setAction:@selector(_takeReturnCodeFrom:)];
[button setBezelStyle:CPSmallSquareBezelStyle];
// Only add subview if the window has been created.
// Otherwise, _createWindowWithStyle will handle adding the buttons from the _buttons array later.
if (_window)
[[_window contentView] addSubview:button];
[[_window contentView] addSubview:button];
if (count == 0)
{
[button setKeyEquivalent:CPCarriageReturnCharacter];
[button setBezelStyle:CPRoundedBezelStyle];
}
else if ([aTitle lowercaseString] === @"cancel")
[button setKeyEquivalent:CPEscapeFunctionKey];
@@ -535,9 +530,6 @@ var bottomHeight = 71;
[[_window contentView] addSubview:_suppressionButton];
}
/*!
@ignore
*/
/*!
@ignore
*/
@@ -547,17 +539,25 @@ var bottomHeight = 71;
minimumSize = [_themeView currentValueForThemeAttribute:@"size"],
buttonOffset = [_themeView currentValueForThemeAttribute:@"button-offset"],
helpLeftOffset = [_themeView currentValueForThemeAttribute:@"help-image-left-offset"],
aRepresentativeButton = [_buttons objectAtIndex:0],
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
panelSize = [[_window contentView] frame].size,
buttonsOriginY,
buttonMarginY,
buttonMarginX,
theme = [self theme],
offsetX;
var isHUD = (_defaultWindowStyle & CPHUDBackgroundWindowMask) || (theme === [CPTheme defaultHudTheme]);
[aRepresentativeButton setTheme:[self theme]];
[aRepresentativeButton sizeToFit];
panelSize.height = CGRectGetMaxY([lastView frame]) + defaultElementsMargin + [aRepresentativeButton frameSize].height;
if (panelSize.height < minimumSize.height)
panelSize.height = minimumSize.height;
buttonsOriginY = panelSize.height - [aRepresentativeButton frameSize].height + buttonOffset;
offsetX = panelSize.width - inset.right;
// 1. Determine Margins (Moved up so we can use them in height calculation)
switch ([_window styleMask])
{
case _CPModalWindowMask:
@@ -571,61 +571,27 @@ var bottomHeight = 71;
break;
}
// 2. Prepare buttons and get row height
var maxButtonHeight = 0.0;
for (var i = 0; i < [_buttons count]; i++)
{
var btn = _buttons[i];
[btn sizeToFit];
if (isHUD)
[btn setThemeState:CPThemeStateHUD];
else
[btn unsetThemeState:CPThemeStateHUD];
maxButtonHeight = MAX(maxButtonHeight, CGRectGetHeight([btn frame]));
}
// 3. Calculate Content Height
var lastViewMaxY = CGRectGetMaxY([lastView frame]);
// Use bottomHeight (71) to reserve space for the footer
var requiredContentHeight = lastViewMaxY + bottomHeight;
var finalContentSize = CGSizeMake(
[[_window contentView] frame].size.width,
MAX(requiredContentHeight, minimumSize.height)
);
// 4. Position Buttons
// Calculate the top Y coordinate to vertically center the button row within the bottomHeight area
// Center Y of footer = Height - (bottomHeight / 2.0)
// Top Y of button = Center Y - (maxButtonHeight / 2.0)
buttonsOriginY = finalContentSize.height - ((bottomHeight + maxButtonHeight) / 2.0) - buttonMarginY;
offsetX = finalContentSize.width - inset.right;
// Loop and set frames
for (var i = [_buttons count] - 1; i >= 0 ; i--)
{
var button = _buttons[i],
buttonFrame = [button frame],
var button = _buttons[i];
[button setTheme:[self theme]];
[button sizeToFit];
var buttonFrame = [button frame],
width = MAX(80.0, CGRectGetWidth(buttonFrame)),
height = CGRectGetHeight(buttonFrame),
yOffset = FLOOR((maxButtonHeight - height) / 2.0);
height = CGRectGetHeight(buttonFrame);
offsetX -= width;
[button setFrame:CGRectMake(offsetX + buttonMarginX, buttonsOriginY + buttonMarginY + yOffset, width, height)];
[button setFrame:CGRectMake(offsetX + buttonMarginX, buttonsOriginY + buttonMarginY, width, height)];
offsetX -= 10;
}
// Position Help Button if needed
if (_showHelp)
{
var helpImage = [_themeView currentValueForThemeAttribute:@"help-image"],
helpImagePressed = [_themeView currentValueForThemeAttribute:@"help-image-pressed"],
helpImageSize = helpImage ? [helpImage size] : CGSizeMakeZero(),
helpYOffset = FLOOR((maxButtonHeight - helpImageSize.height) / 2.0),
helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY + buttonMarginY + helpYOffset, helpImageSize.width, helpImageSize.height);
helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY, helpImageSize.width, helpImageSize.height);
[_alertHelpButton setImage:helpImage];
[_alertHelpButton setAlternateImage:helpImagePressed];
@@ -633,7 +599,8 @@ var bottomHeight = 71;
[_alertHelpButton setFrame:helpFrame];
}
return finalContentSize;
panelSize.height += [aRepresentativeButton frameSize].height + inset.bottom + buttonOffset;
return panelSize;
}
/*!
@@ -647,14 +614,9 @@ var bottomHeight = 71;
if (!_window)
[self _createWindowWithStyle:nil];
// Ensure the theme view knows if we are in HUD mode so it picks up the right specificities
if ((_defaultWindowStyle & CPHUDBackgroundWindowMask) || ([self theme] === [CPTheme defaultHudTheme]))
[_themeView setThemeState:CPThemeStateHUD];
else
[_themeView unsetThemeState:CPThemeStateHUD];
var iconOffset = [_themeView currentValueForThemeAttribute:@"image-offset"],
theImage = _icon;
theImage = _icon,
finalSize;
if (!theImage)
switch (_alertStyle)
@@ -686,17 +648,11 @@ var bottomHeight = 71;
else if (_accessoryView)
lastView = _accessoryView;
// 1. Get the size needed for the *content* (text, buttons, padding)
var finalContentSize = [self _layoutButtonsFromView:lastView];
// 2. Convert Content Size -> Frame Size
// This accounts for the Title Bar and borders automatically.
var contentRect = CGRectMake(0.0, 0.0, finalContentSize.width, finalContentSize.height);
var frameRect = [[_window class] frameRectForContentRect:contentRect styleMask:[_window styleMask]];
// 3. Apply the calculated Frame Size
[_window setFrameSize:frameRect.size];
finalSize = [self _layoutButtonsFromView:lastView];
if ([_window styleMask] & CPDocModalWindowMask)
finalSize.height -= 26; // adjust the absence of title bar
[_window setFrameSize:finalSize];
[_window center];
if ([_window styleMask] & _CPModalWindowMask || [_window styleMask] & CPHUDBackgroundWindowMask)
@@ -796,12 +752,6 @@ var bottomHeight = 71;
var frame = CGRectMakeZero();
frame.size = [_themeView currentValueForThemeAttribute:@"size"];
// Propagate CPHUDBackgroundWindowMask from _defaultWindowStyle to forceStyle.
// This ensures that even if we force CPDocModalWindowMask (for sheets),
// the window still knows it should be a HUD.
if (_defaultWindowStyle & CPHUDBackgroundWindowMask)
forceStyle |= CPHUDBackgroundWindowMask;
_window = [[CPPanel alloc] initWithContentRect:frame styleMask:forceStyle || _defaultWindowStyle];
[_window setLevel:CPStatusWindowLevel];
[_window setPlatformWindow:[[CPApp keyWindow] platformWindow]];
+2 -75
View File
@@ -122,10 +122,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
CPPanel _aboutPanel;
CPThemeBlend _themeBlend @accessors(property=themeBlend);
// OS behavior
CPApplicationOSBehavior _OSBehavior;
BOOL _simulatesWindows;
}
/*!
@@ -158,9 +154,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
_eventListenerInsertionIndex = 0;
_windows = [[CPNull null]];
_OSBehavior = CPApplicationLegacyOSBehavior;
_simulatesWindows = NO;
}
return self;
@@ -1267,45 +1260,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
+ (CPString)defaultThemeName
{
return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo3");
}
// See CPApplication_Constants.j for comments
- (void)setOSBehavior:(CPApplicationOSBehavior)anOSBehavior
{
// Verify if provided OS behavior is valid
if ([[CPApplicationOSBehaviors allKeysForObject:anOSBehavior] count] == 0)
{
CPLog.warn("CPApplication setOSBehavior: invalid CPApplicationOSBehavior (received "+anOSBehavior+"). Ignored.");
return;
}
_OSBehavior = anOSBehavior;
[[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationOSBehaviorDidChangeNotification object:CPApp userInfo:nil];
}
- (CPApplicationOSBehavior)OSBehavior
{
return _OSBehavior;
}
- (BOOL)shouldMimicWindows
{
return (_OSBehavior == CPApplicationFollowOSBehavior) && (CPBrowserIsOperatingSystem(CPWindowsOperatingSystem) || _simulatesWindows);
}
- (void)setSimulatesWindows:(BOOL)shouldSimulateWindows
{
if (_simulatesWindows === shouldSimulateWindows)
return;
_simulatesWindows = shouldSimulateWindows;
}
- (BOOL)simulatesWindows
{
return _simulatesWindows;
return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo2");
}
@end
@@ -1420,7 +1375,7 @@ var _CPAppBootstrapperActions = nil;
var defaultThemeName = [CPApplication defaultThemeName],
themeURL = nil;
if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2" || defaultThemeName === @"Aristo3")
if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2")
themeURL = [[CPBundle bundleForClass:[CPApplication class]] pathForResource:defaultThemeName + @".blend"];
else
themeURL = [[CPBundle mainBundle] pathForResource:defaultThemeName + @".blend"];
@@ -1436,34 +1391,6 @@ var _CPAppBootstrapperActions = nil;
[[CPApplication sharedApplication] setThemeBlend:aThemeBlend];
[CPTheme setDefaultTheme:[CPTheme themeNamed:[CPApplication defaultThemeName]]];
// Search in the Info.plist if the special CPApplicationSimulateWindowsOS flag is set (for testing)
[CPApp setSimulatesWindows:!![[CPBundle mainBundle] objectForInfoDictionaryKey:"CPApplicationSimulateWindowsOS"]];
// Before loading the main CIB, try to find if a CPApplicationOSBehavior is specified in the Info.plist or in the user defaults
// (with user defaults precedence). Value stored must be a string representing the name of the OS behavior.
var plistOSBehavior = [[CPBundle mainBundle] objectForInfoDictionaryKey:"CPApplicationOSBehavior"],
userOSBehavior = [[CPUserDefaults standardUserDefaults] objectForKey:@"CPApplicationOSBehavior"];
if (userOSBehavior)
{
var osBehavior = [CPApplicationOSBehaviors objectForKey:userOSBehavior];
if (osBehavior)
[CPApp setOSBehavior:osBehavior];
else
CPLog.warn("Invalid CPApplicationOSBehavior specified in user defaults (found:"+userOSBehavior+"). Ignored.");
}
else if (plistOSBehavior)
{
var osBehavior = [CPApplicationOSBehaviors objectForKey:plistOSBehavior];
if (osBehavior)
[CPApp setOSBehavior:osBehavior];
else
CPLog.warn("Invalid CPApplicationOSBehavior specified in Info.plist (found:"+plistOSBehavior+"). Ignored.");
}
[self performActions];
}
-12
View File
@@ -39,15 +39,3 @@ CPTerminateLater = -1; // not currently supported
CPRunStoppedResponse = -1000;
CPRunAbortedResponse = -1001;
CPRunContinuesResponse = -1002;
// Should the application follow Cappuccino UX-UI (which is OSX like) or OS UX-UI (mainly Windows) ?
// See explanation on https://github.com/cappuccino/cappuccino/wiki/CPApplicationSelectedOSBehavior
@typedef CPApplicationOSBehavior
CPApplicationLegacyOSBehavior = 1;
CPApplicationFollowOSBehavior = 2;
CPApplicationOSBehaviorDidChangeNotification = @"CPApplicationOSBehaviorDidChangeNotification";
CPApplicationOSBehaviors = @{
@"CPApplicationLegacyOSBehavior": CPApplicationLegacyOSBehavior,
@"CPApplicationFollowOSBehavior": CPApplicationFollowOSBehavior
};
+2 -2
View File
@@ -285,7 +285,7 @@ CPBelowBottom = 6;
// MARK: borderColor
- (CPColor)borderColor
{
return [self currentValueForThemeAttribute:@"border-color"];
return [self valueForThemeAttribute:@"border-color"];
}
- (void)setBorderColor:(CPColor)color
@@ -785,7 +785,7 @@ CPBelowBottom = 6;
[_boxView setFrame:CGRectMake(0,2,bounds.size.width,1)];
}
[_boxView setBackgroundColor:[self currentValueForThemeAttribute:@"border-color"]];
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"border-color"]];
return;
}
+1 -1
View File
@@ -153,7 +153,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
_prototypeView = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_prototypeView setVerticalAlignment:CPCenterVerticalTextAlignment];
[_prototypeView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateSelectedDataView.and(CPThemeStateTableDataView)];
[_prototypeView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateSelectedDataView];
[_prototypeView setLineBreakMode:CPLineBreakByTruncatingTail];
_horizontalScrollView = [[CPScrollView alloc] initWithFrame:[self bounds]];
+4 -10
View File
@@ -98,8 +98,7 @@ var CPButtonBezelStyleStateMap = @{
CPRegularSquareBezelStyle: CPButtonStateBezelStyleRegularSquare,
CPTexturedSquareBezelStyle: CPButtonStateBezelStyleTextured,
CPDisclosureBezelStyle: CPButtonStateBezelStyleDisclosure,
CPRoundedDisclosureBezelStyle: CPButtonStateBezelStyleRoundedDisclosure,
CPHUDBezelStyle: CPThemeStateHUD
CPRoundedDisclosureBezelStyle: CPButtonStateBezelStyleRoundedDisclosure
};
/// @cond IGNORE
@@ -708,17 +707,12 @@ CPButtonImageOffset = 3.0;
// Note : We have to split content and image visual states as, for example, radio buttons don't follow push buttons behavior
- (CPThemeState)_contentVisualState
{
var visualState = [self themeState] || CPThemeStateNormal,
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state],
buttonIsOn = (currentState !== CPOffState);
// Define masks that imply the background changes color (Blue/Gray)
// If the background changes, we usually want the text to turn White (Highlighted state).
var highlightMask = CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask;
// Only add CPThemeStateHighlighted if the button is configured to highlight visually (Background change)
if ((_isHighlighted && (_highlightsBy & highlightMask)) ||
(((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn))
// If the button is pushed (_isHighlighted), always add the highlighted state
if (_isHighlighted || (((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn))
visualState = visualState.and(CPThemeStateHighlighted);
else
visualState = visualState.without(CPThemeStateHighlighted);
+168 -1688
View File
File diff suppressed because it is too large Load Diff
+19 -9
View File
@@ -91,7 +91,7 @@ var CPComboBoxTextSubview = @"text",
{
return @{
@"popup-button-size": CGSizeMake(21.0, 29.0),
@"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0)
@"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0),
};
}
@@ -533,7 +533,7 @@ var CPComboBoxTextSubview = @"text",
CPComboBoxFocusRingWidth = inset.bottom;
}
[_listDelegate popUpRelativeToRect:[self bounds] view:self offset:CPComboBoxFocusRingWidth - 1];
[_listDelegate popUpRelativeToRect:[self _borderFrame] view:self offset:CPComboBoxFocusRingWidth - 1];
[self _selectMatchingItem];
}
@@ -570,13 +570,6 @@ var CPComboBoxTextSubview = @"text",
_selectedStringValue = selectedStringValue;
[self setStringValue:_selectedStringValue];
[self _updatePlaceholderState];
#if PLATFORM(DOM)
[self _setCSSStyleForInputElement];
#endif
[self _reverseSetBinding];
return YES;
@@ -982,6 +975,23 @@ var CPComboBoxTextSubview = @"text",
}
}
/*!
Calculate the frame in base coordinates that will nestle just below the visible border of the text field.
@ignore
*/
- (CGRect)_borderFrame
{
var inset = [self currentValueForThemeAttribute:@"border-inset"],
frame = [self bounds];
frame.origin.x += inset.left;
frame.origin.y += inset.top;
frame.size.width -= inset.left + inset.right;
frame.size.height -= inset.top + inset.bottom;
return frame;
}
/* @ignore */
- (void)_popUpButtonWasClicked
{
+1 -5
View File
@@ -48,10 +48,9 @@
CPRegularControlSize = 0;
CPSmallControlSize = 1;
CPMiniControlSize = 2;
CPLargeControlSize = 3; // Since MacOS 11, there's a new control size "Large"
// To get the theme state corresponding to a control size, use CPControlSizeThemeStates[controlSize]
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini, CPThemeStateControlSizeLarge];
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini];
@typedef CPLineBreakMode
CPLineBreakByWordWrapping = 0;
@@ -249,9 +248,6 @@ var CPControlBlackColor = [CPColor blackColor];
case CPMiniControlSize:
return CPThemeStateControlSizeMini;
case CPLargeControlSize:
return CPThemeStateControlSizeLarge;
case CPRegularControlSize:
default:
+318 -178
View File
@@ -24,11 +24,14 @@
@import <Foundation/CPKeyedUnarchiver.j>
@import "CPView.j"
@import "CPTextField.j"
@import "CPImageView.j"
@import "CPImage.j"
@import "CALayer.j"
@class _CPCibCustomResource
@class CPDatePicker
@class HandImageLayer
@class HoursLayer
@class HandLayer
@global CPHourMinuteSecondDatePickerElementFlag
@global CPTextFieldAndStepperDatePickerStyle
@@ -44,19 +47,15 @@ _CPDatePickerClockSeconds = 3;
@implementation _CPDatePickerClock : CPControl
{
BOOL _isEnabled;
// Pure DOM Views
CPImageView _hourHandView;
CPImageView _minuteHandView;
CPImageView _secondHandView;
CPImageView _middleHandView;
CPArray _hourLabels;
HoursLayer _rootLayer;
HandLayer _hourHandLayer;
HandLayer _minuteHandLayer;
HandLayer _secondHandLayer;
CALayer _middleHandLayer;
CPDatePicker _datePicker;
CPTextField _PMAMTextField;
CPDatePicker _datePicker;
CPView _currentHandView;
CALayer _currentHandLayer;
_CPDatePickerClockHand _currentHand;
CPInteger _currentRepresentedValue;
float _currentValueShift;
@@ -68,14 +67,10 @@ _CPDatePickerClockSeconds = 3;
CPInteger _representedSeconds;
BOOL _representedHourIsPM;
// Angles for Hit-Testing
float _hourAngle;
float _minuteAngle;
float _secondAngle;
CPInteger _datePickerElements @accessors(getter=datePickerElements);
CPInteger _datePickerElements @accessors(getter=datePickerElements);
}
// MARK: -
// MARK: Init methods
@@ -86,53 +81,69 @@ _CPDatePickerClockSeconds = 3;
_datePicker = aDatePicker;
_datePickerElements = [_datePicker datePickerElements];
_trackingHand = NO;
_isEnabled = YES;
// 1. Initialize AM/PM Label
_PMAMTextField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_PMAMTextField setAlignment:CPCenterTextAlignment];
[_PMAMTextField setVerticalAlignment:CPCenterVerticalTextAlignment];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[self addSubview:_PMAMTextField];
// 2. Initialize Number Labels (1 to 12)
_hourLabels = [CPArray array];
var middleHandSize = [_datePicker valueForThemeAttribute:@"middle-hand-size"],
minuteHandSize = [_datePicker valueForThemeAttribute:@"minute-hand-size"],
hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"],
secondHandSize = [_datePicker valueForThemeAttribute:@"second-hand-size"];
for (var i = 1; i <= 12; i++)
{
var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[label setStringValue:String(i)];
[label setAlignment:CPCenterTextAlignment];
[label setVerticalAlignment:CPCenterVerticalTextAlignment];
[self addSubview:label];
[_hourLabels addObject:label];
}
// We use layer to make the rotation possible
_hourHandLayer = [[HandLayer alloc] initWithSize:hourHandSize];
[_hourHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_hourHandLayer setAnchorPoint:CGPointMakeZero()];
[_hourHandLayer setPosition:CGPointMake(0.0, 0.0)];
// 3. Initialize Hand Views
_hourHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_minuteHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_secondHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_middleHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_minuteHandLayer = [[HandLayer alloc] initWithSize:minuteHandSize];
[_minuteHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_minuteHandLayer setAnchorPoint:CGPointMakeZero()];
[_minuteHandLayer setPosition:CGPointMake(0.0, 0.0)];
// Ensure images stretch accurately across the bounds of the image view
[_hourHandView setImageScaling:CPImageScaleAxesIndependently];
[_minuteHandView setImageScaling:CPImageScaleAxesIndependently];
[_secondHandView setImageScaling:CPImageScaleAxesIndependently];
[_middleHandView setImageScaling:CPImageScaleAxesIndependently];
_secondHandLayer = [[HandLayer alloc] initWithSize:secondHandSize];
[_secondHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_secondHandLayer setAnchorPoint:CGPointMakeZero()];
[_secondHandLayer setPosition:CGPointMake(0.0, 0.0)];
// 4. Add subviews in correct Z-Order
[self addSubview:_hourHandView];
[self addSubview:_minuteHandView];
_middleHandLayer = [[HandLayer alloc] initWithSize:middleHandSize];
[_middleHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_middleHandLayer setAnchorPoint:CGPointMakeZero()];
[_middleHandLayer setPosition:CGPointMake(0.0, 0.0)];
_rootLayer = [[HoursLayer alloc] init];
[self setWantsLayer:YES];
[self setLayer:_rootLayer];
[self _initHands];
[_rootLayer addSublayer:_hourHandLayer];
[_rootLayer addSublayer:_minuteHandLayer];
if ([_datePicker valueForThemeAttribute:@"clock-second-hand-over"])
{
[self addSubview:_middleHandView];
[self addSubview:_secondHandView];
[_rootLayer addSublayer:_middleHandLayer];
[_rootLayer addSublayer:_secondHandLayer];
}
else
{
[self addSubview:_secondHandView];
[self addSubview:_middleHandView];
[_rootLayer addSublayer:_secondHandLayer];
[_rootLayer addSublayer:_middleHandLayer];
}
[_rootLayer setDrawsHours:[_datePicker valueForThemeAttribute:@"clock-draws-hours"]];
[_rootLayer setNeedsDisplay];
}
return self;
@@ -140,58 +151,60 @@ _CPDatePickerClockSeconds = 3;
- (void)_initHands
{
// FIX: Using 'duplicate' prevents CPImageViews from stealing the DOM element from each other!
[_middleHandView setImage:[[_datePicker currentValueForThemeAttribute:@"middle-hand-image"] duplicate]];
[_hourHandView setImage:[[_datePicker currentValueForThemeAttribute:@"hour-hand-image"] duplicate]];
[_minuteHandView setImage:[[_datePicker currentValueForThemeAttribute:@"minute-hand-image"] duplicate]];
[_secondHandView setImage:[[_datePicker currentValueForThemeAttribute:@"second-hand-image"] duplicate]];
var middleHandImage = [_datePicker currentValueForThemeAttribute:@"middle-hand-image"],
hourHandImage = [_datePicker currentValueForThemeAttribute:@"hour-hand-image"],
minuteHandImage = [_datePicker currentValueForThemeAttribute:@"minute-hand-image"],
secondHandImage = [_datePicker currentValueForThemeAttribute:@"second-hand-image"];
var font = [_datePicker currentValueForThemeAttribute:@"clock-font"],
textColor = [_datePicker currentValueForThemeAttribute:@"clock-text-color"],
shadowCol = [_datePicker currentValueForThemeAttribute:@"clock-text-shadow-color"],
shadowOff = [_datePicker currentValueForThemeAttribute:@"clock-text-shadow-offset"];
// If hand images are true CPImage, we have to duplicate them to avoid
// the multiple delegates bug when multiple clocks are displayed
if (font)
[_PMAMTextField setFont:font];
if ([middleHandImage isKindOfClass:[CPImage class]])
middleHandImage = [middleHandImage duplicate];
if (textColor)
[_PMAMTextField setTextColor:textColor];
if ([hourHandImage isKindOfClass:[CPImage class]])
hourHandImage = [hourHandImage duplicate];
if (shadowCol)
[_PMAMTextField setTextShadowColor:shadowCol];
if ([minuteHandImage isKindOfClass:[CPImage class]])
minuteHandImage = [minuteHandImage duplicate];
if (shadowOff)
[_PMAMTextField setTextShadowOffset:shadowOff];
if ([secondHandImage isKindOfClass:[CPImage class]])
secondHandImage = [secondHandImage duplicate];
var hoursFont = [_datePicker currentValueForThemeAttribute:@"clock-hours-font"],
hoursColor = [_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"],
drawsHours = [_datePicker currentValueForThemeAttribute:@"clock-draws-hours"];
[_middleHandLayer setImage:middleHandImage];
[_hourHandLayer setImage:hourHandImage];
[_minuteHandLayer setImage:minuteHandImage];
[_secondHandLayer setImage:secondHandImage];
for (var i = 0; i < 12; i++)
{
var label = _hourLabels[i];
[label setHidden:!drawsHours];
if (drawsHours) {
if (hoursFont) [label setFont:hoursFont];
if (hoursColor) [label setTextColor:hoursColor];
[label sizeToFit];
}
}
[_hourHandLayer setNeedsDisplay];
[_middleHandLayer setNeedsDisplay];
[_secondHandLayer setNeedsDisplay];
[_minuteHandLayer setNeedsDisplay];
[_rootLayer setFont:[_datePicker currentValueForThemeAttribute:@"clock-hours-font"]];
[_rootLayer setTextColor:[_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"]];
[_rootLayer setRadius:[_datePicker currentValueForThemeAttribute:@"clock-hours-radius"]];
[_rootLayer setNeedsDisplay];
}
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
{
_datePickerElements = aDatePickerElements;
[_secondHandView setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
// Check if we have to display the hand second
// FIXME: Don't know why but next line will cause theme compilation to fail...
// Workaround: added "if PLATFORM(DOM)"
#if PLATFORM(DOM)
[_secondHandLayer setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
#endif
}
// MARK: Layout methods
- (void)layoutSubviews
{
[self _initHands];
// While tracking a hand, we don't want the whole thing to be relayouted at each mouse movement
if (_trackingHand)
return;
@@ -206,65 +219,31 @@ _CPDatePickerClockSeconds = 3;
_representedSeconds = dateValue.getSeconds();
_representedHourIsPM = (_representedHours > 11);
// Hours are expressed in 24 hours format, we need 12 hours format
_representedHours -= (_representedHourIsPM ? 12 : 0);
var bounds = [self bounds],
centerX = bounds.size.width / 2.0,
centerY = bounds.size.height / 2.0;
[_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"];
[_PMAMTextField sizeToFit];
[_PMAMTextField setFrameOrigin:CGPointMake(centerX - [_PMAMTextField frameSize].width / 2.0, centerY + 15.0)];
if ([_datePicker currentValueForThemeAttribute:@"clock-draws-hours"])
{
var radius = [_datePicker currentValueForThemeAttribute:@"clock-hours-radius"] || 50.0;
for (var i = 0, angle = 60.0; i < 12; i++, angle -= 30.0)
{
var label = _hourLabels[i],
size = [label frameSize],
x = centerX + radius * COS(angle * RADIANS) - size.width / 2.0,
y = centerY - radius * SIN(angle * RADIANS) - size.height / 2.0;
[label setFrameOrigin:CGPointMake(x, y)];
}
}
var centerView = function(view, size) {[view setFrame:CGRectMake(centerX - size.width / 2.0, centerY - size.height / 2.0, size.width, size.height)];
};
var hSize = [_datePicker currentValueForThemeAttribute:@"hour-hand-size"] || CGSizeMake(4, 64),
mSize = [_datePicker currentValueForThemeAttribute:@"minute-hand-size"] || CGSizeMake(4, 96),
sSize = [_datePicker currentValueForThemeAttribute:@"second-hand-size"] || CGSizeMake(4, 96),
midSize = [_datePicker currentValueForThemeAttribute:@"middle-hand-size"] || CGSizeMake(8, 8);
centerView(_hourHandView, hSize);
centerView(_minuteHandView, mSize);
centerView(_secondHandView, sSize);
centerView(_middleHandView, midSize);
[self _updateHands];
}
// Applies Pure CSS Transforms to rotate the elements natively in the browser
- (void)_rotateView:(CPView)view byAngle:(float)radians
{
#if PLATFORM(DOM)
var style = view._DOMElement.style;
style[CPBrowserStyleProperty("transformOrigin")] = "50% 50%";
style[CPBrowserStyleProperty("transform")] = "rotate(" + radians + "rad)";
#endif
// FIXME: Workaround. Seems that CALayer doesn't redraw without an event
[CALayer runLoopUpdateLayers];
}
- (void)_updateHands
{
_hourAngle = (360.0 * (_representedHours + _representedMinutes / 60.0) / 12.0) * RADIANS;
_minuteAngle = (360.0 * (_representedMinutes + _representedSeconds / 60.0) / 60.0) * RADIANS;
_secondAngle = (360.0 * _representedSeconds / 60.0) * RADIANS;
var bounds = [self bounds];
[self _rotateView:_hourHandView byAngle:_hourAngle];
[self _rotateView:_minuteHandView byAngle:_minuteAngle];
[self _rotateView:_secondHandView byAngle:_secondAngle];
[_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"];
[_PMAMTextField sizeToFit];
[_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)];
[_hourHandLayer setRotationRadians:(360 * (_representedHours + _representedMinutes / 60) / 12) * RADIANS];
[_minuteHandLayer setRotationRadians:(360 * (_representedMinutes + _representedSeconds / 60) / 60) * RADIANS];
[_secondHandLayer setRotationRadians:(360 * _representedSeconds / 60) * RADIANS];
[_hourHandLayer setNeedsDisplay];
[_minuteHandLayer setNeedsDisplay];
[_secondHandLayer setNeedsDisplay];
// [_middleHandLayer setNeedsDisplay];
}
// MARK: Accessors
@@ -278,40 +257,12 @@ _CPDatePickerClockSeconds = 3;
_isEnabled = shouldEnable;
[self _initHands];
[self setNeedsLayout];
}
// MARK: Mouse actions
// Since we rotate using Pure CSS, Cappuccino's `convertPoint:` doesn't know about it.
// So we use standard Trigonometry to perfectly hit-test the rotated hands!
- (BOOL)_hitTestHandWithSize:(CGSize)size angle:(float)radians atPoint:(CGPoint)aPoint
{
var bounds = [self bounds],
centerX = bounds.size.width / 2.0,
centerY = bounds.size.height / 2.0;
// 1. Move point to center
var tx = aPoint.x - centerX,
ty = aPoint.y - centerY;
// 2. Rotate point backwards by the angle of the hand
var cosA = COS(-radians),
sinA = SIN(-radians),
rx = tx * cosA - ty * sinA,
ry = tx * sinA + ty * cosA;
// 3. Test if point is within the unrotated hand's rectangle
// The visual needle is in the top half of the hand's box (y from -h/2 to 0)
var w2 = size.width / 2.0,
h2 = size.height / 2.0;
if (rx >= -w2 && rx <= w2 && ry >= -h2 && ry <= 0)
return YES;
return NO;
}
- (void)mouseDown:(CPEvent)anEvent
{
if (!_isEnabled)
@@ -319,29 +270,25 @@ _CPDatePickerClockSeconds = 3;
var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil];
var sSize = [_datePicker currentValueForThemeAttribute:@"second-hand-size"] || CGSizeMake(4, 96),
mSize = [_datePicker currentValueForThemeAttribute:@"minute-hand-size"] || CGSizeMake(4, 96),
hSize = [_datePicker currentValueForThemeAttribute:@"hour-hand-size"] || CGSizeMake(4, 64);
if (![_secondHandView isHidden] && [self _hitTestHandWithSize:sSize angle:_secondAngle atPoint:currentLocation])
if ([_secondHandLayer handIsHitAtPoint:currentLocation])
{
_currentHandView = _secondHandView;
_currentHandLayer = _secondHandLayer;
_currentHand = _CPDatePickerClockSeconds;
_currentRepresentedValue = _representedSeconds;
_currentValueShift = 0;
_numberOfUnits = 60;
}
else if (![_minuteHandView isHidden] && [self _hitTestHandWithSize:mSize angle:_minuteAngle atPoint:currentLocation])
else if ([_minuteHandLayer handIsHitAtPoint:currentLocation])
{
_currentHandView = _minuteHandView;
_currentHandLayer = _minuteHandLayer;
_currentHand = _CPDatePickerClockMinutes;
_currentRepresentedValue = _representedMinutes;
_currentValueShift = _representedSeconds / 60;
_numberOfUnits = 60;
}
else if (![_hourHandView isHidden] && [self _hitTestHandWithSize:hSize angle:_hourAngle atPoint:currentLocation])
else if ([_hourHandLayer handIsHitAtPoint:currentLocation])
{
_currentHandView = _hourHandView;
_currentHandLayer = _hourHandLayer;
_currentHand = _CPDatePickerClockHours;
_currentRepresentedValue = _representedHours;
_currentValueShift = _representedMinutes / 60;
@@ -349,14 +296,14 @@ _CPDatePickerClockSeconds = 3;
}
else
{
_currentHandView = nil;
_currentHandLayer = nil;
_currentHand = CPNotFound;
_currentRepresentedValue = CPNotFound;
_currentValueShift = CPNotFound;
_numberOfUnits = CPNotFound;
}
if (_currentHandView)
if (_currentHandLayer)
[self trackMouse:anEvent];
}
@@ -368,12 +315,13 @@ _CPDatePickerClockSeconds = 3;
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
_trackingHand = YES;
return YES;
}
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
{
var dx = aPoint.x - _bounds.size.width / 2,
var dx = aPoint.x -_bounds.size.width / 2,
dy = _bounds.size.height / 2 - aPoint.y,
angle = (PI_2 - ATAN2(dy,dx) + PI2) % PI2,
value = ROUND(angle * _numberOfUnits / PI2 - _currentValueShift) % _numberOfUnits;
@@ -395,10 +343,14 @@ _CPDatePickerClockSeconds = 3;
_representedHourIsPM = !_representedHourIsPM;
if (movedForward && !_representedHourIsPM)
// Day++
dateValue.setDate(dateValue.getDate() + 1);
else if (movedBackward && _representedHourIsPM)
// Day--
dateValue.setDate(dateValue.getDate() - 1);
}
dateValue.setHours(value + (_representedHourIsPM ? 12 : 0));
break;
@@ -432,26 +384,29 @@ _CPDatePickerClockSeconds = 3;
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
// We have to adapt represented values
_representedHours = dateValue.getHours();
_representedMinutes = dateValue.getMinutes();
_representedSeconds = dateValue.getSeconds();
_representedHourIsPM = (_representedHours > 11);
// Hours are expressed in 24 hours format, we need 12 hours format
_representedHours -= (_representedHourIsPM ? 12 : 0);
switch (_currentHand) {
case _CPDatePickerClockHours:
_currentRepresentedValue = _representedHours;
break;
case _CPDatePickerClockMinutes:
_currentRepresentedValue = _representedMinutes;
break;
case _CPDatePickerClockSeconds:
_currentRepresentedValue = _representedSeconds;
break;
@@ -469,3 +424,188 @@ _CPDatePickerClockSeconds = 3;
}
@end
// MARK: -
@implementation HandLayer : CALayer
{
CPImage _image;
HandImageLayer _imageLayer;
float _rotationRadians;
}
// MARK: Init methods
- (id)initWithSize:(CGSize)aSize
{
if (self = [super init])
{
_imageLayer = [HandImageLayer layer];
_rotationRadians = 0;
[_imageLayer setDelegate:self];
[_imageLayer setBounds:CGRectMake(0.0, 0.0, aSize.width, aSize.height)];
[self addSublayer:_imageLayer];
}
return self;
}
// MARK: Setter Getter methods
/*!
Set the bounds of the layer. The imageLayer will be at the center of this bounds.
*/
- (void)setBounds:(CGRect)aRect
{
[super setBounds:aRect];
[_imageLayer setPosition:CGPointMake(CGRectGetMidX(aRect), CGRectGetMidY(aRect))];
}
- (void)setImage:(CPImage)anImage
{
if (_image === anImage)
return;
if ([anImage isKindOfClass:[_CPCibCustomResource class]])
_image = [anImage imageFromCoder:nil];
else
_image = anImage;
[_imageLayer setNeedsDisplay];
}
- (void)setRotationRadians:(float)radians
{
if (_rotationRadians === radians)
return;
_rotationRadians = radians;
[_imageLayer setAffineTransform:CGAffineTransformScale(
CGAffineTransformMakeRotation(_rotationRadians),
1.0, 1.0)];
}
- (void)imageDidLoad:(CPImage)anImage
{
[_imageLayer setNeedsDisplay];
}
- (void)drawLayer:(CALayer)aLayer inContext:(CGContext)aContext
{
if ([_image loadStatus] != CPImageLoadStatusCompleted)
[_image setDelegate:self];
else
CGContextDrawImage(aContext, [aLayer bounds], _image);
}
- (BOOL)handIsHitAtPoint:(CGPoint)aPoint
{
return (!_isHidden && [_imageLayer hitTest:aPoint] === _imageLayer);
}
- (void)setNeedsDisplay
{
[super setNeedsDisplay];
[_imageLayer setNeedsDisplay];
}
@end
// MARK: -
@implementation HandImageLayer : CALayer
{
CGRect _handBounds;
}
// We have to adapt hitTest so it only takes the hand into account (that's the top half of the image layer)
// We are also sure there's no sublayers
- (CALayer)hitTest:(CGPoint)aPoint
{
if (_isHidden)
return nil;
var point = CGPointApplyAffineTransform(aPoint, _transformToLayer);
return CGRectContainsPoint(_handBounds, point) ? self : nil;
}
- (void)setBounds:(CGRect)aBounds
{
if (CGRectEqualToRect(_bounds, aBounds))
return;
_handBounds = CGRectMakeCopy(aBounds);
_handBounds.size.height = _handBounds.size.height / 2;
[super setBounds:aBounds];
}
@end
// MARK: -
@implementation HoursLayer : CALayer
{
BOOL _drawsHours;
CPFont _font @accessors(property=font);
CPColor _textColor @accessors(property=textColor);
float _radius @accessors(property=radius);
}
- (id)init
{
if (self = [super init])
{
_drawsHours = NO;
}
return self;
}
- (void)setDrawsHours:(BOOL)shouldDrawHours
{
shouldDrawHours = !!shouldDrawHours;
if (_drawsHours === shouldDrawHours)
return;
_drawsHours = shouldDrawHours;
[self setNeedsDisplay];
}
- (void)drawInContext:(CGContext)aContext
{
[super drawInContext:aContext];
if (_drawsHours)
{
var bounds = [self bounds],
centerX = bounds.size.width / 2,
centerY = bounds.size.height / 2;
CGContextSelectFont(aContext, _font);
CGContextSetFillColor(aContext, _textColor);
aContext.textBaseline = @"middle";
aContext.textAlign = @"center";
for (var i = 1, angle = 60.0, x, y; i < 13; i++, angle -= 30.0)
{
x = centerX + _radius * COS(angle * RADIANS);
y = centerY - _radius * SIN(angle * RADIANS);
aContext.fillText(i, x, y);
}
}
}
@end
+1 -1
View File
@@ -220,7 +220,7 @@ following:
if (systemFontStyle)
{
// Yes, so install it in the DOM Style element
document.getElementsByTagName("STYLE")[0].innerHTML += "\n" + [aTheme setCSSResourcesPath:systemFontStyle];
document.getElementsByTagName("STYLE")[0].innerHTML += "\n" + [aTheme setCSSResourcesPathInString:systemFontStyle];
}
}
-8
View File
@@ -237,8 +237,6 @@ var CPImageViewEmptyPlaceholderImage = nil;
if (_hasShadow)
{
[self setClipsToBounds:NO];
_shadowView = [[CPShadowView alloc] initWithFrame:[self bounds]];
[self addSubview:_shadowView];
@@ -458,12 +456,6 @@ var CPImageViewEmptyPlaceholderImage = nil;
styleNode:_cssStyleNode
previousState:@ref(_cssStylePreviousState)];
}
if ([image isCSSBased])
{
_DOMImageElement.style.width = _DOMImageElement.width + 'px';
_DOMImageElement.style.height = _DOMImageElement.height + 'px';
}
#endif
_imageRect = CGRectMake(x, y, width, height);
+22 -26
View File
@@ -88,61 +88,43 @@ CPRatingLevelIndicatorStyle = 3;
- (void)layoutSubviews
{
// 1. Calculate the Theme State
// We explicitly check the window style mask to see if we are in a HUD.
// This allows us to pass CPThemeStateHUD to the theme system, even if the control
// itself isn't explicitly set to HUD, inheriting the style from the window.
var themeState = [self themeState];
if ([[self window] styleMask] & CPHUDBackgroundWindowMask)
themeState = themeState.and(CPThemeStateHUD);
// 2. Layout the Bezel
var bezelView = [self layoutEphemeralSubviewNamed:"bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color" inState:themeState]];
// TODO Make themable.
[bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color"]];
var segmentCount = _maxValue - _minValue;
if (segmentCount <= 0)
return;
// 3. Determine the Color based on Value and Thresholds
// We pass 'themeState' here. If it contains CPThemeStateHUD, the ThemeDescriptor
// will return the monochrome color for normal/warning/critical.
// If not, it returns Green/Yellow/Red.
var filledColor = [self valueForThemeAttribute:@"color-normal" inState:themeState],
var filledColor = [self valueForThemeAttribute:@"color-normal"],
value = [self doubleValue];
if (_warningValue < _criticalValue)
{
// Standard ascending scale (e.g. Volume)
if (value >= _criticalValue)
filledColor = [self valueForThemeAttribute:@"color-critical" inState:themeState];
filledColor = [self valueForThemeAttribute:@"color-critical"];
else if (value >= _warningValue)
filledColor = [self valueForThemeAttribute:@"color-warning" inState:themeState];
filledColor = [self valueForThemeAttribute:@"color-warning"];
}
else
{
// Descending scale (e.g. Battery Life)
if (value <= _criticalValue)
filledColor = [self valueForThemeAttribute:@"color-critical" inState:themeState];
filledColor = [self valueForThemeAttribute:@"color-critical"];
else if (value <= _warningValue)
filledColor = [self valueForThemeAttribute:@"color-warning" inState:themeState];
filledColor = [self valueForThemeAttribute:@"color-warning"];
}
var emptyColor = [self valueForThemeAttribute:@"color-empty" inState:themeState];
// 4. Paint Segments
for (var i = 0; i < segmentCount; i++)
{
var segmentView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:bezelView];
[segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : emptyColor];
[segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : [self valueForThemeAttribute:@"color-empty"]];
}
}
@@ -323,6 +305,20 @@ CPRatingLevelIndicatorStyle = 3;
[self setNeedsLayout];
}
/*
- (CPTickMarkPosition)tickMarkPosition;
- (void)setTickMarkPosition:(CPTickMarkPosition)position;
- (int)numberOfTickMarks;
- (void)setNumberOfTickMarks:(int)count;
- (int)numberOfMajorTickMarks;
- (void)setNumberOfMajorTickMarks:(int)count;
- (double)tickMarkValueAtIndex:(int)index;
- (CGRect)rectOfTickMarkAtIndex:(int)index;
*/
@end
var CPLevelIndicatorStyleKey = "CPLevelIndicatorStyleKey",
-44
View File
@@ -274,8 +274,6 @@ var _CPMenuBarVisible = NO,
_autoenablesItems = YES;
_showsStateColumn = YES;
_themeState = CPThemeStateNormal;
[self setMinimumWidth:0];
}
@@ -288,37 +286,6 @@ var _CPMenuBarVisible = NO,
return [self initWithTitle:@""];
}
// Managing Theme States (HUD Support)
- (void)setThemeState:(CPThemeState)aState
{
if ([self hasThemeState:aState])
return;
_themeState = _themeState.and(aState);
// Propagate to the view if the menu is currently visible
if (_menuWindow)
[[_menuWindow _menuView] setThemeState:_themeState];
}
- (void)unsetThemeState:(CPThemeState)aState
{
if (![self hasThemeState:aState])
return;
_themeState = _themeState.without(aState);
// Propagate to the view if the menu is currently visible
if (_menuWindow)
[[_menuWindow _menuView] setThemeState:_themeState];
}
- (CPThemeState)themeState
{
return _themeState;
}
// Setting Up Menu Commands
/*!
Inserts a menu item at the specified index.
@@ -831,10 +798,6 @@ var _CPMenuBarVisible = NO,
// Create the window for our menu.
var menuWindow = [_CPMenuWindow menuWindowWithMenu:self font:[self font]];
// This pushes the state (e.g., CPThemeStateHUD) to the actual view that renders the menu
if (_themeState)
[[menuWindow _menuView] setThemeState:_themeState];
[menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
if (anItem)
@@ -925,10 +888,6 @@ var _CPMenuBarVisible = NO,
var theWindow = [aView window],
menuWindow = [_CPMenuWindow menuWindowWithMenu:aMenu font:aFont];
// --- APPLY THEME STATE FROM MENU OBJECT TO VIEW ---
if ([aMenu respondsToSelector:@selector(themeState)])
[[menuWindow _menuView] setThemeState:[aMenu themeState]];
[menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
var constraintRect = [CPMenu _constraintRectForView:aView],
@@ -1369,9 +1328,6 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
_autoenablesItems = ![aCoder containsValueForKey:CPMenuAutoEnablesItemsKey] || [aCoder decodeBoolForKey:CPMenuAutoEnablesItemsKey];
// Ensure theme state is initialized to avoid undefined issues.
_themeState = CPThemeStateNormal;
[self setMinimumWidth:0];
}
+15 -62
View File
@@ -62,9 +62,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
// Do this so that coordinates will be accurate.
[menuWindow setFrameOrigin:CGPointMakeZero()];
// we need to reset the HUD state as this may be a recycled instance with HUD rund on non-HUD
[[menuWindow _windowView] unsetThemeState:CPThemeStateHUD];
[[menuWindow _menuView] unsetThemeState:CPThemeStateHUD];
}
else
menuWindow = [[_CPMenuWindow alloc] init];
@@ -111,33 +108,23 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
[contentView addSubview:_menuClipView];
_moreAboveView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
[_moreAboveView setImage:[_menuView valueForThemeAttribute:@"menu-window-more-above-image"]];
[_moreAboveView setFrameSize:[[_menuView valueForThemeAttribute:@"menu-window-more-above-image"] size]];
[contentView addSubview:_moreAboveView];
_moreBelowView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
[contentView addSubview:_moreBelowView];
// Initial setup using default attributes
[self updateScrollArrows];
[_moreBelowView setImage:[_menuView valueForThemeAttribute:@"menu-window-more-below-image"]];
[_moreBelowView setFrameSize:[[_menuView valueForThemeAttribute:@"menu-window-more-below-image"] size]];
[contentView addSubview:_moreBelowView];
}
return self;
}
- (void)updateScrollArrows
{
if (!_menuView)
return;
var aboveImage = [_menuView currentValueForThemeAttribute:@"menu-window-more-above-image"],
belowImage = [_menuView currentValueForThemeAttribute:@"menu-window-more-below-image"];
[_moreAboveView setImage:aboveImage];
[_moreAboveView setFrameSize:[aboveImage size]];
[_moreBelowView setImage:belowImage];
[_moreBelowView setFrameSize:[belowImage size]];
}
+ (float)_standardLeftMargin
{
return [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-window-margin-inset" forClass:_CPMenuView].left;
@@ -175,12 +162,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
- (void)setBackgroundStyle:(_CPMenuWindowBackgroundStyle)aBackgroundStyle
{
var color = [_menuView currentValueForThemeAttribute:@"menu-window-pop-up-background-style-color"];
if (!color)
color = [[self class] backgroundColorForBackgroundStyle:aBackgroundStyle];
[self setBackgroundColor:color];
[self setBackgroundColor:[[self class] backgroundColorForBackgroundStyle:aBackgroundStyle]];
}
- (void)setMenu:(CPMenu)aMenu
@@ -255,29 +237,16 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
// If we are a submenu and we are being displayed off the right of the screen,
// we should try and display on the left of our supermenu.
var supermenu = [[self menu] supermenu];
if (supermenu)
if (supermenu && (CGRectGetMaxX(_unconstrainedFrame) > CGRectGetMaxX(_constraintRect)))
{
if (CGRectGetMaxX(_unconstrainedFrame) > CGRectGetMaxX(_constraintRect))
var supermenuWindow = supermenu._menuWindow;
if (supermenuWindow)
{
var supermenuWindow = supermenu._menuWindow;
if (supermenuWindow)
{
var supermenuFrame = [supermenuWindow frame];
_unconstrainedFrame.origin.x = CGRectGetMinX(supermenuFrame) - CGRectGetWidth(_unconstrainedFrame);
}
var supermenuFrame = [supermenuWindow frame];
_unconstrainedFrame.origin.x = CGRectGetMinX(supermenuFrame) - CGRectGetWidth(_unconstrainedFrame);
}
// Shift true submenus vertically to fit within the screen if they extend past the bottom
if (supermenu !== [CPApp mainMenu] && CGRectGetMaxY(_unconstrainedFrame) > CGRectGetMaxY(_constraintRect))
_unconstrainedFrame.origin.y -= CGRectGetMaxY(_unconstrainedFrame) - CGRectGetMaxY(_constraintRect);
}
// Ensure no menu starts above the visible screen area, preventing it from incorrectly
// starting scrolled with a top arrow.
if (CGRectGetMinY(_unconstrainedFrame) < CGRectGetMinY(_constraintRect))
_unconstrainedFrame.origin.y = CGRectGetMinY(_constraintRect);
var constrainedFrame = CGRectIntersection(_unconstrainedFrame, _constraintRect),
marginInset = [_menuView valueForThemeAttribute:@"menu-window-margin-inset"],
scrollIndicatorHeight = [_menuView valueForThemeAttribute:@"menu-window-scroll-indicator-height"];
@@ -299,8 +268,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
[super setFrame:constrainedFrame display:shouldDisplay animate:shouldAnimate];
// This needs to happen before changing the frame.
// Base the view origin on our modified _unconstrainedFrame instead of aFrame
var menuViewOrigin = CGPointMake(CGRectGetMinX(_unconstrainedFrame) + marginInset.left, CGRectGetMinY(_unconstrainedFrame) + marginInset.top),
var menuViewOrigin = CGPointMake(CGRectGetMinX(aFrame) + marginInset.left, CGRectGetMinY(aFrame) + marginInset.top),
moreAbove = menuViewOrigin.y < CGRectGetMinY(constrainedFrame) + marginInset.top,
moreBelow = menuViewOrigin.y + CGRectGetHeight([_menuView frame]) > CGRectGetMaxY(constrainedFrame) - marginInset.bottom,
@@ -534,21 +502,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
};
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[_menuItemViews makeObjectsPerformSelector:@selector(setThemeState:) withObject:aState];
if ([[self window] respondsToSelector:@selector(updateScrollArrows)])
[[self window] updateScrollArrows];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[_menuItemViews makeObjectsPerformSelector:@selector(unsetThemeState:) withObject:aState];
}
- (unsigned)numberOfUnhiddenItems
{
return _visibleMenuItemInfos.length;
+14 -44
View File
@@ -145,26 +145,6 @@
return self;
}
- (void)setThemeState:(CPThemeState)aState
{
var oldState = [self themeState];
[super setThemeState:aState];
// If the state changed (e.g. adding HUD), we must re-run update
// to fetch the new text color defined for that state.
if (oldState !== [self themeState])
[self update];
}
- (void)unsetThemeState:(CPThemeState)aState
{
var oldState = [self themeState];
[super unsetThemeState:aState];
if (oldState !== [self themeState])
[self update];
}
- (CPColor)textColor
{
if (![_menuItem isEnabled])
@@ -173,7 +153,7 @@
if (_highlighted)
return [CPColor whiteColor];
return [self currentValueForThemeAttribute:@"menu-item-text-color"];
return [self valueForThemeAttribute:@"menu-item-text-color"];
}
- (CPColor)textShadowColor
@@ -184,7 +164,7 @@
if (_highlighted)
return nil;
return [self currentValueForThemeAttribute:@"menu-item-text-shadow-color"];
return [self valueForThemeAttribute:@"menu-item-text-shadow-color"];
}
- (void)setFont:(CPFont)aFont
@@ -198,11 +178,6 @@
return _font || [_menuItem font] || [CPFont systemFontOfSize:CPFontCurrentSystemSize];
}
// override needed to cancel out the standard HUD propagation
- (void)viewDidMoveToWindow
{
}
// FIXME: update is called 2 times at each display. Find why and fix.
- (void)update
{
@@ -213,8 +188,6 @@
// When possible, use specific vertical margin/offset value based on font size (which could have been set by control size)
correspondingControlSize = [myFont controlSizeCorrespondingToFontSize],
controlSizeState = CPControlSizeThemeStates[correspondingControlSize],
queryState = [self themeState] ? [self themeState].and(controlSizeState) : controlSizeState,
verticalMargin = [self valueForThemeAttribute:@"vertical-margin" inState:CPControlSizeThemeStates[correspondingControlSize]],
verticalOffset = [self valueForThemeAttribute:@"vertical-offset" inState:CPControlSizeThemeStates[correspondingControlSize]];
@@ -226,15 +199,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:queryState] || [_menuItem onStateImage]];
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPOffState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:queryState] || [_menuItem offStateImage]];
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPMixedState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:queryState] || [_menuItem mixedStateImage]];
[_stateView setImage:[_menuItem mixedStateImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
default:
@@ -353,10 +326,7 @@
_highlighted = shouldHighlight;
var correspondingControlSize = [[self font] controlSizeCorrespondingToFontSize],
// Construct the query state including the view's current theme state (e.g. HUD)
controlSizeState = CPControlSizeThemeStates[correspondingControlSize],
queryState = [self themeState] ? [self themeState].and(controlSizeState) : controlSizeState;
var correspondingControlSize = [[self font] controlSizeCorrespondingToFontSize];
[_imageAndTextView setTextColor:[self textColor]];
[_keyEquivalentView setTextColor:[self textColor]];
@@ -369,7 +339,7 @@
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:queryState]];
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
else
[_submenuIndicatorView setColor:[self textColor]];
}
@@ -379,7 +349,7 @@
[_imageAndTextView setImage:[_menuItem image]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:queryState]];
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
else
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
}
@@ -391,15 +361,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:queryState]];
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPOffState:
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:queryState]];
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPMixedState:
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:queryState]];
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
default:
@@ -411,15 +381,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:queryState]];
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPOffState:
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:queryState]];
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPMixedState:
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:queryState]];
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
default:
-22
View File
@@ -83,28 +83,6 @@
return self;
}
// override needed to cancel out the standard HUD propagation
- (void)viewDidMoveToWindow
{
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
// Propagate state to the actual content view (StandardView, Separator, etc.)
if ([_view respondsToSelector:@selector(setThemeState:)])
[_view setThemeState:aState];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
if ([_view respondsToSelector:@selector(unsetThemeState:)])
[_view unsetThemeState:aState];
}
- (CGSize)minSize
{
return _minSize;
+50 -565
View File
@@ -23,7 +23,6 @@
@import "CPButton.j"
@import "CPTableColumn.j"
@import "CPTableView.j"
@import "CPViewAnimation.j"
@global CPApp
@@ -286,12 +285,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
CPArray _pendingItemToClean;
CPArray _itemAddedDuringLastLoading;
CPIndexSet _animatingDisclosureRows;
BOOL _animates @accessors(property=animates);
CPViewAnimation _rowsAnimation;
CPArray _animationGhosts;
}
- (id)initWithFrame:(CGRect)aFrame
@@ -322,9 +315,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
[super setDelegate:self];
[self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
_animatingDisclosureRows = [CPIndexSet indexSet];
_animates = YES;
}
return self;
@@ -335,22 +325,12 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
_BlockDeselectView = function(view, row, column)
{
[view unsetThemeState:CPThemeStateSelectedDataView];
// Ensure we clear the focus state so the text goes back to normal color
[view unsetThemeState:CPThemeStateFirstResponder];
[_disclosureControlsForRows[row] unsetThemeState:CPThemeStateSelected];
};
_BlockSelectView = function(view, row, column)
{
[view setThemeState:CPThemeStateSelectedDataView];
// If the table is focused, apply the state that turns text white immediately
if ([self _isFocused])
[view setThemeState:CPThemeStateFirstResponder];
else
[view unsetThemeState:CPThemeStateFirstResponder];
[_disclosureControlsForRows[row] setThemeState:CPThemeStateSelected];
};
}
@@ -502,60 +482,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
return itemInfo.shouldShowOutlineDisclosureControl;
}
- (void)_noteDisclosureAnimationWillStartForRow:(CPInteger)aRow
{
[_animatingDisclosureRows addIndex:aRow];
// We do not need to setNeedsDisplay here because the button itself
// will trigger a redraw when its state changes or when the animation setup occurs.
}
- (void)_noteDisclosureAnimationDidStopForRow:(CPInteger)aRow
{
[_animatingDisclosureRows removeIndex:aRow];
// Explicitly find the button for this row and force it to redraw.
// The button was returning early from drawRect during the animation.
// Now that the flag is cleared, we must tell it to paint again.
if (_disclosureControlsForRows && aRow < _disclosureControlsForRows.length)
{
var button = _disclosureControlsForRows[aRow];
if (button)
[button display];
}
}
- (BOOL)_isRowAnimatingDisclosure:(CPInteger)aRow
{
return [_animatingDisclosureRows containsIndex:aRow];
}
- (CPInteger)rowForView:(CPView)aView
{
// 1. Try the standard lookup (for normal cells)
var row = [super rowForView:aView];
if (row !== CPNotFound)
return row;
// 2. If not found, check if it is a disclosure button
if ([aView isKindOfClass:[CPDisclosureButton class]])
{
// Check the list of active disclosure buttons
// _disclosureControlsForRows is a native JS array, so we use indexOf
var index = _disclosureControlsForRows.indexOf(aView);
if (index > -1)
return index;
// 3. Check if it is an animation clone (from the fix in the previous step)
// The clone is not in the array, but it carries the row index.
if (aView._animatingRowIndex !== undefined && aView._animatingRowIndex !== null)
return aView._animatingRowIndex;
}
return CPNotFound;
}
/*!
Used to find if an item is already expanded.
@@ -695,9 +621,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
*/
- (void)expandItem:(id)anItem expandChildren:(BOOL)shouldExpandChildren
{
var oldRowCount = [self numberOfRows],
parentIndex = [self rowForItem:anItem];
if ([self _delegateRespondsToShouldExpandItem])
if ([_outlineViewDelegate outlineView:self shouldExpandItem:anItem] == NO)
return;
@@ -740,7 +663,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
if (rowCountDelta)
{
var selection = [self selectedRowIndexes],
expandIndex = parentIndex + 1;
expandIndex = [self rowForItem:anItem] + 1;
if ([selection intersectsIndexesInRange:CPMakeRange(expandIndex, _itemsForRows.length)])
{
@@ -767,17 +690,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
if (r === CPOutlineViewCoalesceSelectionNotificationStateDid)
[self _noteSelectionDidChange];
}
// FIX: Animation logic moved to the end to match Version 1 behavior.
// This ensures views are ready and handles both single and recursive expansion.
var newRowCount = [self numberOfRows],
addedCount = newRowCount - oldRowCount;
if (_animates && addedCount > 0)
{
[self layoutSubviews]; // Force views to spawn so we can animate them
[self _animateExpandFromIndex:parentIndex addedCount:addedCount];
}
}
/*!
@@ -790,30 +702,29 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
if (!anItem)
return;
var collapseTopIndex = [self rowForItem:anItem];
// FIX 1: Safety check to prevent "jumping".
// If the parent row isn't found or invisible, we cannot animate.
if (collapseTopIndex === CPNotFound || collapseTopIndex < 0)
return;
if ([self _delegateRespondsToShouldCollapseItem])
if ([_outlineViewDelegate outlineView:self shouldCollapseItem:anItem] == NO)
return;
var itemInfo = _itemInfosForItems[[anItem UID]];
if (!itemInfo || !itemInfo.isExpanded)
if (!itemInfo)
return;
if (!itemInfo.isExpanded)
return;
// Don't spam notifications.
_coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
[self _noteItemWillCollapse:anItem];
var topLevel = [self levelForRow:collapseTopIndex],
// Update selections:
// * Deselect items inside the collapsed item.
// * Shift row selections below the collapsed item so that the same logical items remain selected.
var collapseTopIndex = [self rowForItem:anItem],
topLevel = [self levelForRow:collapseTopIndex],
collapseEndIndex = collapseTopIndex;
// Calculate how many items are being removed
while (collapseEndIndex + 1 < _itemsForRows.length && [self levelForRow:collapseEndIndex + 1] > topLevel)
collapseEndIndex++;
@@ -827,288 +738,31 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
{
[self _noteSelectionIsChanging];
[selection removeIndexesInRange:collapseRange];
[self _setSelectedRowIndexes:selection];
[self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
}
// Shift any selected rows below upwards.
if ([selection intersectsIndexesInRange:CPMakeRange(collapseEndIndex + 1, _itemsForRows.length)])
{
[self _noteSelectionIsChanging];
[selection shiftIndexesStartingAtIndex:collapseEndIndex + 1 by:-collapseRange.length];
[self _setSelectedRowIndexes:selection];
[self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
}
}
// Capture visual ghosts BEFORE the data is removed
var ghosts = nil;
if (_animates && collapseRange.length > 0)
ghosts = [self _createGhostsForRowsFrom:collapseRange.location to:CPMaxRange(collapseRange) - 1];
// Collapse the model
itemInfo.isExpanded = NO;
[self reloadItem:anItem reloadChildren:YES];
[self _noteItemDidCollapse:anItem];
// Trigger Animation
if (_animates && collapseRange.length > 0)
{
[self layoutSubviews];
[self _animateCollapseFromIndex:collapseTopIndex removedCount:collapseRange.length ghosts:ghosts];
}
// Send selection notifications only after the items have loaded so that
// the new selection is consistent with the actual rows for any observers.
var r = _coalesceSelectionNotificationState;
_coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
if (r === CPOutlineViewCoalesceSelectionNotificationStateDid)
[self _noteSelectionDidChange];
}
- (void)_animateCollapseFromIndex:(CPInteger)parentIndex removedCount:(CPInteger)removedCount ghosts:(CPArray)ghosts
{
// Ensure we don't animate if parent is invalid
if (parentIndex < 0)
return;
if (_rowsAnimation && [_rowsAnimation isAnimating])
[_rowsAnimation stopAnimation];
var viewAnimations = [],
// The Y position the ghosts will slide INTO (the parent row's Y position)
parentY = [self frameOfDataViewAtColumn:0 row:parentIndex].origin.y;
// Calculate shift offset based on ghost height or row height
var shiftOffset = 0;
if (ghosts && ghosts.length > 0)
{
var firstGhostFrame = [ghosts[0] frame],
maxY = CGRectGetMaxY(firstGhostFrame);
for(var i = 1; i < ghosts.length; i++)
maxY = MAX(maxY, CGRectGetMaxY([ghosts[i] frame]));
shiftOffset = maxY - firstGhostFrame.origin.y;
}
else
{
shiftOffset = removedCount * [self rowHeight];
}
// 1. Ghost views slide up into parent and fade out
if (ghosts)
{
for (var i = 0; i < ghosts.length; i++)
{
var ghost = ghosts[i],
startFrame = [ghost frame],
targetFrame = CGRectMake(startFrame.origin.x, parentY, startFrame.size.width, startFrame.size.height);
// FIX 3: Removed [self addSubview:positioned:relativeTo:]
// Ghosts remain at the top of the view stack (where they were created)
// ensuring they are visible and don't get hidden behind the background.
[viewAnimations addObject:@{
CPViewAnimationTargetKey: ghost,
CPViewAnimationStartFrameKey: startFrame,
CPViewAnimationEndFrameKey: targetFrame,
CPViewAnimationEffectKey: CPViewAnimationFadeOutEffect
}];
}
}
// 2. Siblings (rows below the collapsed group) slide UP to fill the gap
var columns = [self tableColumns],
colIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, columns.length)],
startIndex = parentIndex + 1,
rowCount = [self numberOfRows];
// FIX 4: Only animate rows that actually exist after the parent
if (startIndex < rowCount)
{
var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startIndex, rowCount - startIndex)];
[self _enumerateViewsInRows:rowIndexes columns:colIndexes usingBlock:function(view, row, column, stop) {
var targetFrame = [view frame];
// Siblings start lower (at target Y + shift) and move UP to target Y
var startFrame = CGRectMake(targetFrame.origin.x, targetFrame.origin.y + shiftOffset, targetFrame.size.width, targetFrame.size.height);
[view setFrame:startFrame];
[viewAnimations addObject:@{
CPViewAnimationTargetKey: view,
CPViewAnimationStartFrameKey: startFrame,
CPViewAnimationEndFrameKey: targetFrame
}];
}];
// Also animate disclosure triangles for the siblings
var disclosureRows = [];
[rowIndexes getIndexes:disclosureRows maxCount:-1 inIndexRange:nil];
for (var i = 0; i < disclosureRows.length; i++)
{
var btn = _disclosureControlsForRows[disclosureRows[i]];
if (btn)
{
var targetBtnFrame = [btn frame],
startBtnFrame = CGRectMake(targetBtnFrame.origin.x, targetBtnFrame.origin.y + shiftOffset, targetBtnFrame.size.width, targetBtnFrame.size.height);
[btn setFrame:startBtnFrame];
[viewAnimations addObject:@{
CPViewAnimationTargetKey: btn,
CPViewAnimationStartFrameKey: startBtnFrame,
CPViewAnimationEndFrameKey: targetBtnFrame
}];
}
}
}
[self _runAnimation:viewAnimations withGhosts:ghosts];
}
// MARK: - CPRuleEditor Style Animations
- (CPArray)_createGhostsForRowsFrom:(CPInteger)startRow to:(CPInteger)endRow
{
var ghosts = [];
var columns = [self tableColumns];
var rowIndexes =[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startRow, endRow - startRow + 1)];
var colIndexes =[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, columns.length)];[self _enumerateViewsInRows:rowIndexes columns:colIndexes usingBlock:function(view, row, column, stop) {
var ghost = [[CPView alloc] initWithFrame:[view frame]];
if (view._DOMElement)
ghost._DOMElement.innerHTML = view._DOMElement.innerHTML;
[self addSubview:ghost];
[ghosts addObject:ghost];
}];
var disclosureRows = [];[rowIndexes getIndexes:disclosureRows maxCount:-1 inIndexRange:nil];
for (var i = 0; i < disclosureRows.length; i++)
{
var btn = _disclosureControlsForRows[disclosureRows[i]];
if (btn)
{
var ghostBtn = [[CPView alloc] initWithFrame:[btn frame]];
if (btn._DOMElement)
ghostBtn._DOMElement.innerHTML = btn._DOMElement.innerHTML;[self addSubview:ghostBtn];
[ghosts addObject:ghostBtn];
}
}
return ghosts;
}
- (void)_animateExpandFromIndex:(CPInteger)parentIndex addedCount:(CPInteger)addedCount
{
if (_rowsAnimation && [_rowsAnimation isAnimating])
[_rowsAnimation stopAnimation];
var viewAnimations = [];
var columns = [self tableColumns];
var colIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, columns.length)];
var startIndex = parentIndex + 1;
var rowIndexes =[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startIndex, [self numberOfRows] - startIndex)];
var parentY = parentIndex >= 0 ? [self frameOfDataViewAtColumn:0 row:parentIndex].origin.y : 0;
// Safely calculate the shift gap even with variable row heights
var shiftOffset = 0;
if (addedCount > 0 && (startIndex + addedCount) < [self numberOfRows])
{
var firstNewTarget = [self frameOfDataViewAtColumn:0 row:startIndex].origin.y;
var firstDisplacedTarget = [self frameOfDataViewAtColumn:0 row:startIndex + addedCount].origin.y;
shiftOffset = firstDisplacedTarget - firstNewTarget;
}
else
shiftOffset = addedCount * [self rowHeight];[self _enumerateViewsInRows:rowIndexes columns:colIndexes usingBlock:function(view, row, column, stop) {
var isNewRow = (row < startIndex + addedCount);
var targetFrame = [view frame];
var startFrame;
var animDict = @{ CPViewAnimationTargetKey: view, CPViewAnimationEndFrameKey: targetFrame };
if (isNewRow)
{
startFrame = CGRectMake(targetFrame.origin.x, parentY, targetFrame.size.width, targetFrame.size.height);
[animDict setObject:CPViewAnimationFadeInEffect forKey:CPViewAnimationEffectKey];[view setAlphaValue:0.0];
}
else
startFrame = CGRectMake(targetFrame.origin.x, targetFrame.origin.y - shiftOffset, targetFrame.size.width, targetFrame.size.height);
[view setFrame:startFrame];[animDict setObject:startFrame forKey:CPViewAnimationStartFrameKey];
[viewAnimations addObject:animDict];
}];
var disclosureRows = [];
[rowIndexes getIndexes:disclosureRows maxCount:-1 inIndexRange:nil];
for (var i = 0; i < disclosureRows.length; i++)
{
var r = disclosureRows[i];
var btn = _disclosureControlsForRows[r];
if (btn)
{
var isNewRow = (r < startIndex + addedCount);
var targetBtnFrame = [btn frame];
var startBtnFrame;
var animBtnDict = @{ CPViewAnimationTargetKey: btn, CPViewAnimationEndFrameKey: targetBtnFrame };
if (isNewRow)
{
startBtnFrame = CGRectMake(targetBtnFrame.origin.x, parentY, targetBtnFrame.size.width, targetBtnFrame.size.height);[animBtnDict setObject:CPViewAnimationFadeInEffect forKey:CPViewAnimationEffectKey];[btn setAlphaValue:0.0];
}
else
startBtnFrame = CGRectMake(targetBtnFrame.origin.x, targetBtnFrame.origin.y - shiftOffset, targetBtnFrame.size.width, targetBtnFrame.size.height);
[btn setFrame:startBtnFrame];[animBtnDict setObject:startBtnFrame forKey:CPViewAnimationStartFrameKey];
[viewAnimations addObject:animBtnDict];
}
}
[self _runAnimation:viewAnimations withGhosts:nil];
}
- (void)_runAnimation:(CPArray)viewAnimations withGhosts:(CPArray)ghosts
{
// Clean up interrupted animations
if (_animationGhosts)
{
for (var i = 0; i < _animationGhosts.length; i++)
[_animationGhosts[i] removeFromSuperview];
}
_animationGhosts = ghosts;
if (viewAnimations.length > 0)
{
_rowsAnimation = [[CPViewAnimation alloc] initWithViewAnimations:viewAnimations];
[_rowsAnimation setDuration:0.25];[_rowsAnimation setAnimationCurve:CPAnimationEaseInOut];
[_rowsAnimation setDelegate:self];
[_rowsAnimation startAnimation];
}
else if (ghosts)
{
for (var i = 0; i < ghosts.length; i++)
[ghosts[i] removeFromSuperview];
_animationGhosts = nil;
}
}
- (void)animationDidEnd:(CPViewAnimation)animation
{
var animations = [animation viewAnimations];
for (var i = 0; i < animations.length; i++)
{
var view = animations[i][CPViewAnimationTargetKey];
if (!_animationGhosts || ![_animationGhosts containsObject:view])[view setAlphaValue:1.0];
}
if (_animationGhosts)
{
for (var i = 0; i < _animationGhosts.length; i++)
[_animationGhosts[i] removeFromSuperview];
_animationGhosts = nil;
}
[self setNeedsLayout];
}
/*!
Reloads the data for an item.
@@ -1132,11 +786,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
*/
- (void)reloadItem:(id)anItem reloadChildren:(BOOL)shouldReloadChildren
{
if (_rowsAnimation && [_rowsAnimation isAnimating])
{
[_rowsAnimation stopAnimation];
[self animationDidEnd:_rowsAnimation];
}
_pendingItemToClean = [];
_itemAddedDuringLastLoading = [];
@@ -2580,15 +2229,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
@implementation CPDisclosureButton : CPButton
{
float _angle;
// Animation state tracking
BOOL _isAnimatingClone;
CPInteger _animatingRowIndex;
CPOutlineView _parentOutlineView;
DOMElement _arrowElement;
CPString _lastColorCSS;
float _angle;
}
- (id)initWithFrame:(CGRect)aFrame
@@ -2596,220 +2237,66 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
self = [super initWithFrame:aFrame];
if (self)
{
[self setBordered:NO];
[self setWantsLayer:YES];
[self setHighlightsBy:0];
[self _setupDisclosureButton];
}
return self;
}
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
[self _setupDisclosureButton];
}
return self;
}
- (void)_setupDisclosureButton
{
_isAnimatingClone = NO; // Default for real buttons
#if PLATFORM(DOM)
// Add a dedicated DOM element for the SVG arrow
_arrowElement = document.createElement("div");
_arrowElement.style.position = "absolute";
_arrowElement.style.top = "0px";
_arrowElement.style.left = "0px";
_arrowElement.style.width = "100%";
_arrowElement.style.height = "100%";
_arrowElement.style.backgroundPosition = "center center";
_arrowElement.style.backgroundRepeat = "no-repeat";
// 24px maps the 24x24 viewBox to exact pixels, making the 10x5 path scale perfectly
_arrowElement.style.backgroundSize = "24px 24px";
_arrowElement.style.pointerEvents = "none";
self._DOMElement.appendChild(_arrowElement);
#endif
}
- (void)setAngle:(float)anAngle
{
_angle = anAngle;
// Rotate the DOM element directly using standard CSS transforms
var deg = _angle * (180.0 / Math.PI);
#if PLATFORM(DOM)
_arrowElement.style.transform = "rotate(" + deg + "deg)";
#endif
[self display];
}
- (float)angle
{
return _angle;
}
- (void)setState:(CPInteger)aState
{
[super setState:aState];
// Using setAngle immediately triggers the transform
if ([self state] === CPOnState)
[self setAngle:0.0];
_angle = 0.0;
else
[self setAngle:-PI_2];
}
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)point mouseIsUp:(BOOL)mouseIsUp
{
var bounds = [self bounds];
if (CGRectContainsPoint(bounds, point))
{
var outlineView = [self superview],
row = [outlineView rowForView:self];
if ([outlineView isKindOfClass:[CPOutlineView class]] && row !== CPNotFound)
{
// 1. Tell OutlineView to suppress drawing of the real button
[outlineView _noteDisclosureAnimationWillStartForRow:row];
// 2. Create the clone
var clone = [[CPDisclosureButton alloc] initWithFrame:[self frame]];
[clone setWantsLayer:YES];
[clone setThemeState:[self themeState]];
[clone unsetThemeState:CPThemeStateHighlighted];
[clone setHighlighted:NO];
[clone setAngle:_angle];
[clone setHitTests:NO];
// 3. Mark this as a clone so drawRect knows to draw it
clone._isAnimatingClone = YES;
clone._animatingRowIndex = row;
clone._parentOutlineView = outlineView;[outlineView addSubview:clone positioned:CPWindowAbove relativeTo:self];
// 4. Calculate angles
var isCurrentlyExpanded = ([self state] === CPOnState),
targetAngle = isCurrentlyExpanded ? -PI_2 : 0.0;
// 5. Setup Animation
var anim = [CABasicAnimation animationWithKeyPath:@"angle"];
[anim setFromValue:_angle];
[anim setToValue:targetAngle];
[anim setDuration:0.25];
[anim setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[clone layer] setDelegate:clone];
[anim setDelegate:clone];
[[clone layer] addAnimation:anim forKey:@"angle"];
// Set final angle on clone to avoid flicker at end
[clone setAngle:targetAngle];
}
}
[super stopTracking:lastPoint at:point mouseIsUp:mouseIsUp];
}
- (void)animationDidStop:(CAAnimation)anim finished:(BOOL)flag
{
// This runs on the CLONE
if (_parentOutlineView)
[_parentOutlineView _noteDisclosureAnimationDidStopForRow:_animatingRowIndex];
[self removeFromSuperview];
_angle = -PI_2;
}
- (void)drawRect:(CGRect)aRect
{
var bounds = [self bounds],
context = [[CPGraphicsContext currentContext] graphicsPort],
width = CGRectGetWidth(bounds),
height = CGRectGetHeight(bounds);
#if PLATFORM(DOM)
if (_arrowElement && _arrowElement.parentNode !== self._DOMElement)
self._DOMElement.appendChild(_arrowElement);
#endif
CGContextBeginPath(context);
// If I am NOT the clone, I must check if my row is animating.
// If it is animating, the clone is drawing on top of me, so I should be invisible.
if (!_isAnimatingClone)
if (_angle)
{
var outlineView = [self superview];
if ([outlineView isKindOfClass:[CPOutlineView class]])
{
var row = [outlineView rowForView:self];
if (row !== CPNotFound && [outlineView _isRowAnimatingDisclosure:row])
{
#if PLATFORM(DOM)
_arrowElement.style.display = "none";
#endif
return; // Suppress the real button
}
}
var centre = CGPointMake(FLOOR(width / 2.0), FLOOR(height / 2.0));
CGContextTranslateCTM(context, centre.x, centre.y);
CGContextRotateCTM(context, _angle);
CGContextTranslateCTM(context, -centre.x, -centre.y);
}
_arrowElement.style.display = "block";
var isSelected = [self hasThemeState:CPThemeStateSelected],
isHighlighted = [self hasThemeState:CPThemeStateHighlighted],
isKeyWindow = [self hasThemeState:CPThemeStateKeyWindow],
isHUD = [self window] && ([[self window] styleMask] & CPHUDBackgroundWindowMask),
triangleColor = nil;
// Center, but crisp.
CGContextTranslateCTM(context, FLOOR((width - 9.0) / 2.0), FLOOR((height - 8.0) / 2.0));
// Establish the required color based on the state/outline selection
if (isHUD)
{
triangleColor = isSelected ? [CPColor blackColor] : [CPColor whiteColor];
CGContextMoveToPoint(context, 0.0, 0.0);
CGContextAddLineToPoint(context, 9.0, 0.0);
CGContextAddLineToPoint(context, 4.5, 8.0);
CGContextClosePath(context);
if (isHighlighted)
triangleColor = [triangleColor colorWithAlphaComponent:0.5];
}
else
{
if (isSelected)
triangleColor = isKeyWindow ? [CPColor whiteColor] : [CPColor blackColor];
else triangleColor = [CPColor colorWithCalibratedWhite:0.45 alpha: 1.0];
CGContextSetFillColor(context,
colorForDisclosureTriangle([self hasThemeState:CPThemeStateSelected],
[self hasThemeState:CPThemeStateHighlighted]));
CGContextFillPath(context);
if (isHighlighted)
{
if (isSelected && isKeyWindow)
triangleColor = [CPColor colorWithCalibratedWhite:0.9 alpha: 1.0];
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0.0, 0.0);
CGContextAddLineToPoint(context, 4.5, 8.0);
else if (isSelected && !isKeyWindow)
triangleColor = [CPColor colorWithCalibratedWhite:0.2 alpha: 1.0];
else
triangleColor = [CPColor colorWithCalibratedWhite:0.25 alpha: 1.0];
}
}
if (_angle === 0.0)
CGContextAddLineToPoint(context, 9.0, 0.0);
var colorCSS = [triangleColor cssString];
// Convert and inject the updated SVG only when the required color has changed
if (_lastColorCSS !== colorCSS)
{
_lastColorCSS = colorCSS;
var svgString = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7 10L12 15L17 10H7Z" fill="' + colorCSS + '"/></svg>';
var b64 = window.btoa(svgString);
#if PLATFORM(DOM)
_arrowElement.style.backgroundImage = "url('data:image/svg+xml;charset=utf-8;base64," + b64 + "')";
#endif
}
CGContextSetStrokeColor(context, [CPColor colorWithCalibratedWhite:1.0 alpha: 0.7]);
CGContextStrokePath(context);
}
@end
var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey",
CPOutlineViewOutlineTableColumnKey = @"CPOutlineViewOutlineTableColumnKey",
CPOutlineViewDataSourceKey = @"CPOutlineViewDataSourceKey",
@@ -2843,8 +2330,6 @@ var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey"
[super setDelegate:self];
[self _updateIsViewBased];
_animatingDisclosureRows = [CPIndexSet indexSet];
}
return self;
-8
View File
@@ -79,8 +79,6 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
if (self)
{
[self setBezelStyle:CPRoundedBezelStyle];
[self selectItemAtIndex:CPNotFound];
_preferredEdge = CPMaxYEdge;
@@ -691,12 +689,6 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
// FIXME: setFont: should set the font on the menu.
[menu setFont:[self font]];
// Propagate the HUD theme state to the menu
if ([self hasThemeState:CPThemeStateHUD])
[menu setThemeState:CPThemeStateHUD];
else
[menu unsetThemeState:CPThemeStateHUD];
if ([self pullsDown])
{
var positionedItem = nil,
+33 -121
View File
@@ -43,6 +43,7 @@ CPProgressIndicatorSpinningStyle = 1;
*/
CPProgressIndicatorHUDBarStyle = 2;
var CPProgressIndicatorSpinningStyleColors = [];
/*!
@ingroup appkit
@@ -70,43 +71,6 @@ CPProgressIndicatorHUDBarStyle = 2;
BOOL _isDisplayedWhenStopped;
}
// Inject CSS Keyframes for spinning animation (Standard + WebKit)
+ (void)initialize
{
if (self !== [CPProgressIndicator class])
return;
#if PLATFORM(DOM)
if (document.getElementById("cp-progress-indicator-style"))
return;
var style = document.createElement("style");
style.id = "cp-progress-indicator-style";
style.type = "text/css";
// We define two animations:
// 1. cp-progress-indicator-spin: Rotates 360 degrees (for spinners)
// 2. cp-progress-indicator-bar-slide: Moves background-position (for striped bars)
var css =
"@keyframes cp-progress-indicator-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } " +
"@-webkit-keyframes cp-progress-indicator-spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } } " +
"@keyframes cp-progress-indicator-bar-slide { 0% { background-position: 0 0; } 100% { background-position: 30px 0; } } " +
"@-webkit-keyframes cp-progress-indicator-bar-slide { 0% { background-position: 0 0; } 100% { background-position: 30px 0; } }";
if (style.styleSheet)
style.styleSheet.cssText = css;
else
style.appendChild(document.createTextNode(css));
var head = document.getElementsByTagName("head")[0];
if (head)
head.appendChild(style);
#endif
}
+ (CPString)defaultThemeClass
{
return @"progress-indicator";
@@ -119,19 +83,12 @@ CPProgressIndicatorHUDBarStyle = 2;
@"bar-color": [CPNull null],
@"default-height": 20,
@"bezel-color": [CPNull null],
// CSS Spinner Attributes
@"spinner-color": [CPColor grayColor],
@"spinner-track-color": [CPColor colorWithWhite:0.9 alpha:1.0],
@"spinner-line-width": 3.0,
@"circular-border-color": [CPNull null],
@"circular-border-size": 1,
@"circular-color": [CPNull null],
@"spinning-mini-gif": [CPNull null],
@"spinning-small-gif": [CPNull null],
@"spinning-regular-gif": [CPNull null]
@"spinning-regular-gif": [CPNull null],
@"circular-border-color": [CPNull null],
@"circular-border-size": 1,
@"circular-color": [CPNull null]
};
}
@@ -181,7 +138,6 @@ CPProgressIndicatorHUDBarStyle = 2;
_isAnimating = YES;
[self _hideOrDisplay];
[self setNeedsLayout]; // Trigger layout to update CSS animation state
}
/*!
@@ -193,7 +149,6 @@ CPProgressIndicatorHUDBarStyle = 2;
_isAnimating = NO;
[self _hideOrDisplay];
[self setNeedsLayout]; // Trigger layout to remove CSS animation state
}
/*!
@@ -278,7 +233,7 @@ CPProgressIndicatorHUDBarStyle = 2;
_controlSize = aControlSize;
[self setNeedsLayout];
[self updateBackgroundColor];
}
/*!
@@ -330,7 +285,7 @@ CPProgressIndicatorHUDBarStyle = 2;
_indeterminate = indeterminate;
[self setNeedsLayout];
[self updateBackgroundColor];
}
/*!
@@ -352,7 +307,9 @@ CPProgressIndicatorHUDBarStyle = 2;
_style = aStyle;
[self setNeedsLayout];
[self setTheme:(_style === CPProgressIndicatorHUDBarStyle) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]];
[self updateBackgroundColor];
}
/*!
@@ -361,14 +318,7 @@ CPProgressIndicatorHUDBarStyle = 2;
- (void)sizeToFit
{
if (_style == CPProgressIndicatorSpinningStyle)
{
var size = 32.0;
if (_controlSize === CPMiniControlSize) size = 16.0;
else if (_controlSize === CPSmallControlSize) size = 24.0;
else if (_controlSize === CPRegularControlSize) size = 32.0;
[self setFrameSize:CGSizeMake(size, size)];
}
[self setFrameSize:[[CPProgressIndicatorSpinningStyleColors[_controlSize] patternImage] size]];
else
[self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), [self valueForThemeAttribute:@"default-height"])];
}
@@ -431,7 +381,6 @@ CPProgressIndicatorHUDBarStyle = 2;
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
{
// Handle the standard Bar View
if (aViewName === @"bar-view" && _style !== CPProgressIndicatorSpinningStyle)
{
var width = CGRectGetWidth([self bounds]),
@@ -445,78 +394,41 @@ CPProgressIndicatorHUDBarStyle = 2;
return CGRectMake(0, 0, barWidth, [self valueForThemeAttribute:@"default-height"]);
}
// Handle the Spinning View (CSS Spinner)
if (aViewName === @"spinner-view" && _style == CPProgressIndicatorSpinningStyle && _indeterminate)
return nil;
}
/* @ignore */
- (void)updateBackgroundColor
{
if ([CPProgressIndicatorSpinningStyleColors count] === 0)
{
return [self bounds];
CPProgressIndicatorSpinningStyleColors[CPMiniControlSize] = [self valueForThemeAttribute:@"spinning-mini-gif"];
CPProgressIndicatorSpinningStyleColors[CPSmallControlSize] = [self valueForThemeAttribute:@"spinning-small-gif"];
CPProgressIndicatorSpinningStyleColors[CPRegularControlSize] = [self valueForThemeAttribute:@"spinning-regular-gif"];
}
// Return nil for views that shouldn't appear in the current style
return nil;
[self setNeedsLayout];
}
- (void)layoutSubviews
{
if (YES)//_isBezeled)
{
// === SPINNING STYLE ===
if (_style == CPProgressIndicatorSpinningStyle)
{
// If indeterminate, use CSS spinner
if (_indeterminate)
{
var spinnerView = [self layoutEphemeralSubviewNamed:"spinner-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:nil];
// Ensure other views are hidden
[self layoutEphemeralSubviewNamed:"bar-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil];
if (!_indeterminate)
return;
// Configure CSS on the subview, NOT self._DOMElement, to avoid transform conflicts
var domEl = spinnerView._DOMElement,
spinnerColor = [self currentValueForThemeAttribute:@"spinner-color"],
trackColor = [self currentValueForThemeAttribute:@"spinner-track-color"],
lineWidth = [self currentValueForThemeAttribute:@"spinner-line-width"],
widthStr = lineWidth + "px";
// This will cause the bar view to go away due to having a nil rect when _style == CPProgressIndicatorSpinningStyle.
[self layoutEphemeralSubviewNamed:"bar-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
domEl.style.boxSizing = "border-box";
domEl.style.borderRadius = "50%";
domEl.style.borderStyle = "solid";
domEl.style.borderWidth = widthStr;
domEl.style.borderColor = [trackColor cssString];
domEl.style.borderTopColor = [spinnerColor cssString];
// Animation logic
if (_isAnimating)
{
var anim = "cp-progress-indicator-spin 1s linear infinite";
domEl.style.animation = anim;
domEl.style.WebkitAnimation = anim;
}
else
{
domEl.style.animation = "none";
domEl.style.WebkitAnimation = "none";
}
// Ensure main view is transparent
[self setBackgroundColor:nil];
}
else
{
// Determinate Spinner (Pie Chart drawn in drawRect)
// Hide CSS spinner
[self layoutEphemeralSubviewNamed:"spinner-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil];
[self setBackgroundColor:nil];
}
[self setBackgroundColor:CPProgressIndicatorSpinningStyleColors[_controlSize]];
}
// === BAR STYLE ===
else
{
// Hide spinner
[self layoutEphemeralSubviewNamed:"spinner-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil];
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
var barView = [self layoutEphemeralSubviewNamed:"bar-view"
@@ -535,8 +447,6 @@ CPProgressIndicatorHUDBarStyle = 2;
- (void)drawRect:(CGRect)aRect
{
// Handle determinate state for Spinning style (Pie chart progress) via CoreGraphics.
// If indeterminate, the CSS animation (spinner-view) takes over and we draw nothing.
if (_style == CPProgressIndicatorSpinningStyle && !_indeterminate)
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
@@ -602,7 +512,7 @@ CPProgressIndicatorHUDBarStyle = 2;
_isDisplayedWhenStoppedSet = [aCoder decodeObjectForKey:@"_isDisplayedWhenStoppedSet"];
_isDisplayedWhenStopped = [aCoder decodeObjectForKey:@"_isDisplayedWhenStopped"];
[self setNeedsLayout];
[self updateBackgroundColor];
}
return self;
@@ -610,6 +520,8 @@ CPProgressIndicatorHUDBarStyle = 2;
- (void)encodeWithCoder:(CPCoder)aCoder
{
// Don't encode the background colour. It can be recreated based on the flags
// and if encoded causes hardcoded image paths in the cib while just wasting space.
var backgroundColor = [self backgroundColor];
[self setBackgroundColor:nil];
[super encodeWithCoder:aCoder];
+12 -24
View File
@@ -1957,63 +1957,57 @@ TODO: implement
- (CPArray)_backgroundColors
{
return [self currentValueForThemeAttribute:@"alternating-row-colors"];
return [self valueForThemeAttribute:@"alternating-row-colors"];
}
- (CPColor)_selectedRowColor
{
return [self currentValueForThemeAttribute:@"selected-color"];
return [self valueForThemeAttribute:@"selected-color"];
}
- (CPColor)_sliceTopBorderColor
{
return [self currentValueForThemeAttribute:@"slice-top-border-color"];
return [self valueForThemeAttribute:@"slice-top-border-color"];
}
- (CPColor)_sliceBottomBorderColor
{
return [self currentValueForThemeAttribute:@"slice-bottom-border-color"];
return [self valueForThemeAttribute:@"slice-bottom-border-color"];
}
- (CPColor)_sliceLastBottomBorderColor
{
return [self currentValueForThemeAttribute:@"slice-last-bottom-border-color"];
return [self valueForThemeAttribute:@"slice-last-bottom-border-color"];
}
- (CPFont)font
{
return [self currentValueForThemeAttribute:@"font"];
return [self valueForThemeAttribute:@"font"];
}
- (CPColor)_fontColor
{
return [self currentValueForThemeAttribute:@"font-color"];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[_slices makeObjectsPerformSelector:@selector(setThemeState:) withObject:aState];
return [self valueForThemeAttribute:@"font-color"];
}
- (CPImage)_imageAdd
{
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateNormal.and([self themeState])];
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateNormal];
}
- (CPImage)_imageAddHighlighted
{
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateHighlighted.and([self themeState])];
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateHighlighted];
}
- (CPImage)_imageRemove
{
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateNormal.and([self themeState])];
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateNormal];
}
- (CPImage)_imageRemoveHighlighted
{
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateHighlighted.and([self themeState])];
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateHighlighted];
}
- (CPVerticalTextAlignment)_verticalAlignment
@@ -2225,13 +2219,7 @@ TODO: implement
return;
var point = [self convertPoint:[event locationInWindow] fromView:nil],
index = FLOOR(MAX(0, point.y) / _sliceHeight);
// Check bounds before accessing the array to prevent CPRangeException
if (index >= [_slices count])
return;
var view = [_slices objectAtIndex:FLOOR(MAX(0, point.y) / _sliceHeight)];
view = [_slices objectAtIndex:FLOOR(MAX(0, point.y) / _sliceHeight)];
if ([self _dragShouldBeginFromMouseDown:view])
[self _performDragForSlice:view withEvent:event];
+23 -67
View File
@@ -49,13 +49,6 @@
return self;
}
- (void)setRowIndex:(int)anIndex
{
_rowIndex = anIndex;
[self _updateBackgroundColor];
[self setNeedsDisplay:YES];
}
- (void)_setSelected:(BOOL)select
{
if (select == _selected)
@@ -64,47 +57,6 @@
var selector = select ? @selector(setThemeState:) : @selector(unsetThemeState:);
[[self subviews] makeObjectsPerformSelector:selector withObject:CPThemeStateSelectedDataView];
_selected = select;
[self _updateBackgroundColor];
[self setNeedsDisplay:YES];
}
- (void)_updateBackgroundColor
{
var color = nil;
if ([self _isSelected])
{
color = [_ruleEditor _selectedRowColor];
}
else
{
var colors = [_ruleEditor _backgroundColors],
count = [colors count];
if (count > 0)
color = [colors objectAtIndex:(_rowIndex % count)];
}
[self setBackgroundColor:color];
}
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
[self _updateBackgroundColor];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[self _updateBackgroundColor];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self _updateBackgroundColor];
}
- (void)drawRect:(CGRect)rect
@@ -114,31 +66,35 @@
maxX = CGRectGetWidth(bounds),
maxY = CGRectGetHeight(bounds);
// Note: Background is now handled by setBackgroundColor in _updateBackgroundColor
// to support CSS-based colors and transparency correctly.
// Draw background
if ([self _isSelected])
_backgroundColor = [_ruleEditor _selectedRowColor];
else
{
var colors = [_ruleEditor _backgroundColors],
count = [colors count];
_backgroundColor = [colors objectAtIndex:(_rowIndex % count)];
}
CGContextSetFillColor(context, _backgroundColor);
CGContextFillRect(context, rect);
// Draw Top Border
var topColor = [_ruleEditor _sliceTopBorderColor];
if (topColor)
{
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, maxX, 0);
CGContextSetStrokeColor(context, topColor);
CGContextStrokePath(context);
}
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, maxX, 0);
CGContextSetStrokeColor(context, [_ruleEditor _sliceTopBorderColor]);
CGContextStrokePath(context);
// Draw Bottom Border
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, maxY);
CGContextAddLineToPoint(context, maxX, maxY);
var bottomColor = (_rowIndex == [_ruleEditor _lastRow]) ? [_ruleEditor _sliceLastBottomBorderColor] : [_ruleEditor _sliceBottomBorderColor];
if (bottomColor)
{
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, maxY);
CGContextAddLineToPoint(context, maxX, maxY);
CGContextSetStrokeColor(context, bottomColor);
CGContextStrokePath(context);
}
CGContextSetStrokeColor(context, bottomColor);
CGContextStrokePath(context);
}
- (void)mouseDown:(CPEvent)theEvent
@@ -67,23 +67,6 @@
[self setAutoresizingMask:CPViewWidthSizable];
}
- (void)_setSelected:(BOOL)isSelected
{
[super _setSelected:isSelected];
[self _updateButtonImages];
}
- (void)_updateButtonImages
{
var rowState = [self themeState];
if ([self _isSelected])
rowState = rowState.and(CPThemeStateSelected);
[_addButton setImage:[_ruleEditor valueForThemeAttribute:@"add-image" inState:rowState]];
[_subtractButton setImage:[_ruleEditor valueForThemeAttribute:@"remove-image" inState:rowState]];
}
- (CPButton)_createRowButton
{
var button = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
@@ -106,8 +89,6 @@
var button = [self _createRowButton];
[button setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
// Initial setup, _updateButtonImages will be called later to set correct state-based images
[button setValue:[_ruleEditor _imageAdd] forThemeAttribute:@"image" inState:CPThemeStateNormal];
[button setValue:[_ruleEditor _imageAddHighlighted] forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
@@ -122,8 +103,6 @@
var button = [self _createRowButton];
[button setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
// Initial setup
[button setValue:[_ruleEditor _imageRemove] forThemeAttribute:@"image" inState:CPThemeStateNormal];
[button setValue:[_ruleEditor _imageRemoveHighlighted] forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
@@ -546,23 +525,9 @@
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
[self _updateButtonImages];
[self layoutSubviews];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[self _updateButtonImages];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self _updateButtonImages];
}
- (void)_addObservers
{
if (_isObserving)
+17 -78
View File
@@ -103,12 +103,6 @@ var CPScrollViewWillStartLiveScrollNotification = @"CPScrollViewWillStartLiveScr
var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
CPScrollerStyleGlobalChangeNotification = @"CPScrollerStyleGlobalChangeNotification";
var CPScrollViewBorderSuffixes = @[@"no-border", @"line-border", @"bezel-border", @"groove-border"];
// _CPScrollViews will hold all created CPScrollView's in order to propagate changes
// of scroller global style
var _CPScrollViews;
/*!
@ingroup appkit
@class CPScrollView
@@ -173,8 +167,6 @@ var _CPScrollViews;
CPScrollerStyleGlobal = _isBrowserUsingOverlayScrollers() ? CPScrollerStyleOverlay : CPScrollerStyleLegacy
else
CPScrollerStyleGlobal = globalValue;
_CPScrollViews = @[];
}
+ (CPString)defaultThemeClass
@@ -186,15 +178,7 @@ var _CPScrollViews;
{
return @{
@"bottom-corner-color": [CPColor whiteColor],
@"border-color": [CPColor blackColor],
@"content-inset-no-border": CGInsetMake(0, 0, 0, 0),
@"content-inset-line-border": CGInsetMake(1, 1, 1, 1),
@"content-inset-bezel-border": CGInsetMake(1, 1, 1, 1),
@"content-inset-groove-border": CGInsetMake(2, 2, 2, 2),
@"background-color-no-border": [CPNull null],
@"background-color-line-border": [CPNull null],
@"background-color-bezel-border": [CPNull null],
@"background-color-groove-border": [CPNull null]
@"border-color": [CPColor blackColor]
};
}
@@ -263,35 +247,19 @@ var _CPScrollViews;
+ (CGRect)_insetBounds:(CGRect)bounds borderType:(CPBorderType)borderType
{
// First, we have to check if we are compiling a theme or running an application because if working on a theme,
// we can't use theme attributes to determine the inset ! This would be a kind of circular reference...
var compilingATheme = [[[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPApplicationDelegateClass"] isEqualToString:@"BKShowcaseController"];
if (compilingATheme)
return bounds;
var contentInset = [[CPTheme defaultTheme] valueForAttributeWithName:@"content-inset-"+CPScrollViewBorderSuffixes[borderType] forClass:CPScrollView];
// As this is a class method, we don't have object attributes, so if the theme doesn't declare content insets, we don't automatically get default value.
// Get it by hand.
if (!contentInset)
contentInset = [[self themeAttributes] objectForKey:@"content-inset-"+CPScrollViewBorderSuffixes[borderType]];
switch (borderType)
{
case CPNoBorder:
case CPLineBorder:
case CPBezelBorder:
return CGRectInsetByInset(bounds, contentInset);
return CGRectInset(bounds, 1.0, 1.0);
case CPGrooveBorder:
// FIXME: Do something better with this
bounds = CGRectInsetByInset(bounds, contentInset);
bounds = CGRectInset(bounds, 2.0, 2.0);
++bounds.origin.y;
--bounds.size.height;
return bounds;
case CPNoBorder:
default:
return bounds;
}
@@ -313,10 +281,7 @@ var _CPScrollViews;
+ (void)setGlobalScrollerStyle:(CPScrollerStyle)aStyle
{
CPScrollerStyleGlobal = aStyle;
// We propagate the new scroller global style to all existing CPScrollView's
for (var i = 0, count = [_CPScrollViews count]; i < count; i++)
[_CPScrollViews[i] setScrollerStyle:CPScrollerStyleGlobal];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollerStyleGlobalChangeNotification object:nil];
}
@@ -358,8 +323,6 @@ var _CPScrollViews;
_delegate = nil;
_scrollTimer = nil;
_implementedDelegateMethods = 0;
[_CPScrollViews addObject:self];
}
return self;
@@ -1453,9 +1416,7 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_scrollerStyle === CPScrollerStyleLegacy)
{
var bottomCornerFrame = [self _bottomCornerViewFrame];
[[self bottomCornerView] setFrame:bottomCornerFrame];
[[self bottomCornerView] setHidden:CGRectIsEmpty(bottomCornerFrame)];
[[self bottomCornerView] setFrame:[self _bottomCornerViewFrame]];
[[self bottomCornerView] setBackgroundColor:[self currentValueForThemeAttribute:@"bottom-corner-color"]];
}
@@ -1533,6 +1494,7 @@ Notifies the delegate when the scroll view has finished scrolling.
// MARK: -
// MARK: Overrides
- (void)_removeObservers
{
if (!_isObserving)
@@ -1550,6 +1512,9 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_isObserving)
return;
//Make sure to have the last global style for the scroller
[self _didReceiveDefaultStyleChange:nil];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(_didReceiveDefaultStyleChange:)
name:CPScrollerStyleGlobalChangeNotification
@@ -1558,11 +1523,13 @@ Notifies the delegate when the scroll view has finished scrolling.
[super _addObservers];
}
- (void)drawRect:(CGRect)aRect
{
[super drawRect:aRect];
if ([self isCSSBased] || (_borderType == CPNoBorder))
if (_borderType == CPNoBorder)
return;
var strokeRect = [self bounds],
@@ -1767,37 +1734,6 @@ Notifies the delegate when the scroll view has finished scrolling.
// MARK: -
@implementation CPScrollView (CSSTheming)
- (void)layoutSubviews
{
if (![self isCSSBased])
return;
if (_borderType !== CPNoBorder)
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color-"+CPScrollViewBorderSuffixes[_borderType]]];
if (_scrollerStyle === CPScrollerStyleLegacy)
[[self bottomCornerView] setBackgroundColor:[self currentValueForThemeAttribute:@"bottom-corner-color"]];
}
- (BOOL)isCSSBased
{
return [[self theme] isCSSBased];
}
- (void)refreshDisplay
{
if ([self isCSSBased])
[self setNeedsLayout:YES];
else
[self setNeedsDisplay:YES];
}
@end
#pragma mark -
@implementation CPScrollView (FirstResponder)
// Those 4 next methods are needed to (un)set CPThemeStateFirstResponder based on content view
@@ -1914,7 +1850,10 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
_scrollerStyle = [aCoder decodeObjectForKey:CPScrollViewScrollerStyleKey] || CPScrollerStyleGlobal;
_scrollerKnobStyle = [aCoder decodeObjectForKey:CPScrollViewScrollerKnobStyleKey] || CPScrollerKnobStyleDefault;
[_CPScrollViews addObject:self];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(_didReceiveDefaultStyleChange:)
name:CPScrollerStyleGlobalChangeNotification
object:nil];
}
return self;
+4 -21
View File
@@ -23,11 +23,12 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPAnimation.j"
@import "CPControl.j"
@import "CPViewAnimation.j"
@import "CPWindow_Constants.j"
@import "CPViewAnimation.j"
@global CPApp
@@ -258,7 +259,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
*/
- (void)setKnobProportion:(float)aProportion
{
if (!CPIsNumeric(aProportion))
if (!_IS_NUMERIC(aProportion))
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric, was: "+aProportion];
_knobProportion = MIN(1.0, MAX(0.0001, aProportion));
@@ -320,25 +321,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
if (![self hasThemeState:CPThemeStateSelected] && ![self hasThemeState:CPThemeStateScrollViewLegacy])
return CPScrollerNoPart;
// Fetch the visual rect for the knob
var bounds = [self bounds],
knobRect = [self rectForPart:CPScrollerKnob],
hitKnobRect = CGRectMake(CGRectGetMinX(knobRect), CGRectGetMinY(knobRect), CGRectGetWidth(knobRect), CGRectGetHeight(knobRect));
// Expand the hit test rect to span the entire track on the minor axis
// so dragging works smoothly even if the user clicks slightly off-center.
if ([self isVertical])
{
hitKnobRect.origin.x = 0;
hitKnobRect.size.width = CGRectGetWidth(bounds);
}
else
{
hitKnobRect.origin.y = 0;
hitKnobRect.size.height = CGRectGetHeight(bounds);
}
if (CGRectContainsPoint(hitKnobRect, aPoint))
if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
return CPScrollerKnob;
if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementPage], aPoint))
+2 -9
View File
@@ -104,8 +104,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
_sendsWholeSearchString = NO;
_sendsSearchStringImmediately = NO;
_recentsAutosaveName = nil;
[self setPlaceholderString:@"Search"];
[self _init];
}
@@ -124,13 +122,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[self setContinuous:YES];
var bounds = [self bounds],
cancelButton = nil,
searchButton = nil;
#if PLATFORM(DOM)
cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]];
searchButton = [[CPButton alloc] initWithFrame:[self searchButtonRectForBounds:bounds]];
#endif
cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]],
searchButton = [[CPButton alloc] initWithFrame:[self searchButtonRectForBounds:bounds]];
[self setCancelButton:cancelButton];
[self resetCancelButton];
+149 -278
View File
@@ -71,7 +71,7 @@ CPSegmentSwitchTrackingMomentary = 2;
@"right-segment-bezel-color": [CPNull null],
@"center-segment-bezel-color": [CPNull null],
@"divider-bezel-color": [CPNull null],
@"divider-thickness": 1.0
@"divider-thickness": 1.0,
};
}
@@ -316,7 +316,7 @@ CPSegmentSwitchTrackingMomentary = 2;
// Working with Individual Segments
/*!
Sets the width of the specified segment.
@param aWidth the new width for the segment (0 for automatic width)
@param aWidth the new width for the segment
@param aSegment the segment to set the width for
@throws CPRangeException if \c aSegment is out of bounds
*/
@@ -420,14 +420,7 @@ CPSegmentSwitchTrackingMomentary = 2;
[segment setSelected:isSelected];
if (_themeStates[aSegment] == undefined)
_themeStates[aSegment] = CPThemeStateNormal;
// Update state using .and() / .without() to preserve existing states (like Disabled)
if (isSelected)
_themeStates[aSegment] = _themeStates[aSegment].and(CPThemeStateSelected);
else
_themeStates[aSegment] = _themeStates[aSegment].without(CPThemeStateSelected);
_themeStates[aSegment] = isSelected ? CPThemeStateSelected : CPThemeStateNormal;
// We need to do some cleanup if we only allow one selection.
if (isSelected)
@@ -439,10 +432,7 @@ CPSegmentSwitchTrackingMomentary = 2;
if (_trackingMode == CPSegmentSwitchTrackingSelectOne && oldSelectedSegment != aSegment && oldSelectedSegment != -1 && oldSelectedSegment < _segments.length)
{
[_segments[oldSelectedSegment] setSelected:NO];
// Update old segment using .without() to preserve other states
// Previously: _themeStates[oldSelectedSegment] = CPThemeStateNormal;
_themeStates[oldSelectedSegment] = _themeStates[oldSelectedSegment].without(CPThemeStateSelected);
_themeStates[oldSelectedSegment] = CPThemeStateNormal;
[self drawSegmentBezel:oldSelectedSegment highlight:NO];
}
@@ -540,24 +530,12 @@ CPSegmentSwitchTrackingMomentary = 2;
- (float)_leftOffsetForSegment:(unsigned)segment
{
if ([[self actualTheme] isCSSBased])
{
// CSS styling
if (segment == 0)
return 0;
if (segment == 0)
return [self currentValueForThemeAttribute:@"bezel-inset"].left;
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) - 2; // FIXME: -2 for collapse of borders
}
else
{
// Legacy styling
if (segment == 0)
return [self currentValueForThemeAttribute:@"bezel-inset"].left;
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) + thickness;
}
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) + thickness;
}
- (unsigned)_indexOfLastSegment
@@ -591,28 +569,19 @@ CPSegmentSwitchTrackingMomentary = 2;
}
else if (aName.indexOf("segment-bezel") === 0)
{
if ([[self actualTheme] isCSSBased])
{
var segment = parseInt(aName.substring("segment-bezel-".length), 10);
return [self bezelFrameForSegment:segment];
}
else
{
var segment = parseInt(aName.substring("segment-bezel-".length), 10),
var segment = parseInt(aName.substring("segment-bezel-".length), 10),
frame = CGRectCreateCopy([self frameForSegment:segment]);
if (segment === 0)
{
frame.origin.x += contentInset.left;
frame.size.width -= contentInset.left;
}
if (segment === [self segmentCount] - 1)
frame.size.width = CGRectGetWidth([self bounds]) - contentInset.right - frame.origin.x;
return frame;
if (segment === 0)
{
frame.origin.x += contentInset.left;
frame.size.width -= contentInset.left;
}
if (segment === [self segmentCount] - 1)
frame.size.width = CGRectGetWidth([self bounds]) - contentInset.right - frame.origin.x;
return frame;
}
else if (aName.indexOf("divider-bezel") === 0)
{
@@ -651,85 +620,48 @@ CPSegmentSwitchTrackingMomentary = 2;
if ([self segmentCount] <= 0)
return;
// Check for HUD state globally
var isHUD = [self hasThemeState:CPThemeStateHUD];
var themeState = _themeStates[0],
isDisabled = [self hasThemeState:CPThemeStateDisabled],
isControlSizeSmall = [self hasThemeState:CPThemeStateControlSizeSmall],
isControlSizeMini = [self hasThemeState:CPThemeStateControlSizeMini];
if ([[self actualTheme] isCSSBased])
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
var leftCapColor = [self valueForThemeAttribute:@"left-segment-bezel-color"
inState:themeState],
leftBezelView = [self layoutEphemeralSubviewNamed:@"left-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[leftBezelView setBackgroundColor:leftCapColor];
var themeState = _themeStates[_themeStates.length - 1];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
var rightCapColor = [self valueForThemeAttribute:@"right-segment-bezel-color"
inState:themeState],
rightBezelView = [self layoutEphemeralSubviewNamed:@"right-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[rightBezelView setBackgroundColor:rightCapColor];
for (var i = 0, count = _themeStates.length; i < count; i++)
{
// CSS Styling
var isDisabled = [self hasThemeState:CPThemeStateDisabled],
isControlSizeSmall = [self hasThemeState:CPThemeStateControlSizeSmall],
isControlSizeMini = [self hasThemeState:CPThemeStateControlSizeMini],
isKeyWindow = [self hasThemeState:CPThemeStateKeyWindow];
for (var i = 0, count = _themeStates.length; i < count; i++)
{
var themeState = _themeStates[i];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
// Apply HUD state to lookup
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
themeState = isKeyWindow ? themeState.and(CPThemeStateKeyWindow) : themeState;
var bezelColor,
segment = _segments[i],
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil],
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:"segment-bezel-" + i];
if (i == 0)
bezelColor = [self valueForThemeAttribute:@"left-segment-bezel-color" inState:themeState];
else if (i < count - 1)
bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color" inState:themeState];
else
bezelColor = [self valueForThemeAttribute:@"right-segment-bezel-color" inState:themeState];
[bezelView setBackgroundColor:bezelColor];
// Trick : Put selected segments over unselected ones to automaticaly manage borders
#if PLATFORM(DOM)
contentView._DOMElement.style.zIndex = ([segment selected] ? 1 : 0);
bezelView._DOMElement.style.zIndex = ([segment selected] ? 1 : 0);
#endif
[contentView setText:[segment label]];
[contentView setImage:[segment image]];
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:([segment enabled] ? themeState : themeState.and(CPThemeStateDisabled))]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
if ([segment image] && [segment label])
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
else if ([segment image])
[contentView setImagePosition:CPImageOnly];
}
}
else
{
// Legacy (Canvas) Styling
var themeState = _themeStates[0],
isDisabled = [self hasThemeState:CPThemeStateDisabled],
isControlSizeSmall = [self hasThemeState:CPThemeStateControlSizeSmall],
isControlSizeMini = [self hasThemeState:CPThemeStateControlSizeMini];
var themeState = _themeStates[i];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
@@ -738,114 +670,59 @@ CPSegmentSwitchTrackingMomentary = 2;
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Left Cap
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
var bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color"
inState:themeState],
var leftCapColor = [self valueForThemeAttribute:@"left-segment-bezel-color"
inState:themeState],
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
leftBezelView = [self layoutEphemeralSubviewNamed:@"left-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[bezelView setBackgroundColor:bezelColor];
[leftBezelView setBackgroundColor:leftCapColor];
// layout image/title views
var segment = _segments[i],
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"segment-bezel-" + i];
var themeState = _themeStates[_themeStates.length - 1];
[contentView setText:[segment label]];
[contentView setImage:[segment image]];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:themeState]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
if ([segment image] && [segment label])
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
else if ([segment image])
[contentView setImagePosition:CPImageOnly];
if (i == count - 1)
continue;
var borderState = _themeStates[i].and(_themeStates[i + 1]);
borderState = isDisabled ? borderState.and(CPThemeStateDisabled) : borderState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
borderState = borderState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
borderState = borderState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Right Cap
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
var rightCapColor = [self valueForThemeAttribute:@"right-segment-bezel-color"
inState:themeState],
rightBezelView = [self layoutEphemeralSubviewNamed:@"right-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[rightBezelView setBackgroundColor:rightCapColor];
for (var i = 0, count = _themeStates.length; i < count; i++)
{
var themeState = _themeStates[i];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Center Segments
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
var bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color"
inState:themeState],
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[bezelView setBackgroundColor:bezelColor];
// layout image/title views
var segment = _segments[i],
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"segment-bezel-" + i];
[contentView setText:[segment label]];
[contentView setImage:[segment image]];
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:themeState]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
if ([segment image] && [segment label])
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
else if ([segment image])
[contentView setImagePosition:CPImageOnly];
if (i == count - 1)
continue;
var borderState = _themeStates[i].and(_themeStates[i + 1]);
borderState = isDisabled ? borderState.and(CPThemeStateDisabled) : borderState;
if (isControlSizeSmall)
borderState = borderState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
borderState = borderState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Dividers
if (isHUD)
borderState = borderState.and(CPThemeStateHUD);
var borderColor = [self valueForThemeAttribute:@"divider-bezel-color"
inState:borderState],
var borderColor = [self valueForThemeAttribute:@"divider-bezel-color"
inState:borderState],
borderView = [self layoutEphemeralSubviewNamed:"divider-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[borderView setBackgroundColor:borderColor];
}
[borderView setBackgroundColor:borderColor];
}
}
@@ -926,10 +803,8 @@ CPSegmentSwitchTrackingMomentary = 2;
label = [segment label],
image = [segment image];
contentInsetWidth += ([[self actualTheme] isCSSBased] && ((aSegment == 0) || (aSegment == [self _indexOfLastSegment])) ? [self valueForThemeAttribute:@"divider-thickness" inState:themeState] : 0);
// add 1 pixel to account for possible fractional pixels at right edge
width = CEIL((label ? [label sizeWithFont:[self font]].width + 1: 4.0) + (image ? [image size].width : 0) + contentInsetWidth);
width = (label ? [label sizeWithFont:[self font]].width + 1 : 4.0) + (image ? [image size].width : 0) + contentInsetWidth;
}
return CGRectMake(left, top, width, height);
@@ -937,14 +812,6 @@ CPSegmentSwitchTrackingMomentary = 2;
- (CGRect)contentFrameForSegment:(unsigned)aSegment
{
if ([[self actualTheme] isCSSBased])
{
var bezelFrame = [self bezelFrameForSegment:aSegment],
contentInset = [self currentValueForThemeAttribute:@"content-inset"];
return CGRectInsetByInset(bezelFrame, contentInset);
}
var height = [self currentValueForThemeAttribute:@"min-size"].height,
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
width = CGRectGetWidth([self frameForSegment:aSegment]),
@@ -1007,64 +874,68 @@ CPSegmentSwitchTrackingMomentary = 2;
var type = [anEvent type],
location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
if (type == CPLeftMouseUp)
switch (type)
{
if (_trackingSegment == -1)
case CPLeftMouseUp:
if (_trackingSegment === CPNotFound)
return;
if (_trackingSegment === [self testSegment:location])
{
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
{
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
// With ANY, _selectedSegment means last pressed.
_selectedSegment = _trackingSegment;
}
else
[self setSelected:YES forSegment:_trackingSegment];
[self sendAction:[self action] to:[self target]];
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
{
[self setSelected:NO forSegment:_trackingSegment];
_selectedSegment = CPNotFound;
}
}
[self drawSegmentBezel:_trackingSegment highlight:NO];
_trackingSegment = CPNotFound;
return;
if (_trackingSegment === [self testSegment:location])
{
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
case CPLeftMouseDown:
var trackingSegment = [self testSegment:location];
if (trackingSegment > CPNotFound && [self isEnabledForSegment:trackingSegment])
{
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
// With ANY, _selectedSegment means last pressed.
_selectedSegment = _trackingSegment;
_trackingHighlighted = YES;
_trackingSegment = trackingSegment;
[self drawSegmentBezel:_trackingSegment highlight:YES];
}
else
[self setSelected:YES forSegment:_trackingSegment];
[self sendAction:[self action] to:[self target]];
break;
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
case CPLeftMouseDragged:
if (_trackingSegment === CPNotFound)
return;
var highlighted = [self testSegment:location] === _trackingSegment;
if (highlighted != _trackingHighlighted)
{
[self setSelected:NO forSegment:_trackingSegment];
_trackingHighlighted = highlighted;
_selectedSegment = CPNotFound;
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
}
}
[self drawSegmentBezel:_trackingSegment highlight:NO];
_trackingSegment = -1;
return;
}
if (type == CPLeftMouseDown)
{
var trackingSegment = [self testSegment:location];
if (trackingSegment > -1 && [self isEnabledForSegment:trackingSegment])
{
_trackingHighlighted = YES;
_trackingSegment = trackingSegment;
[self drawSegmentBezel:_trackingSegment highlight:YES];
}
}
else if (type == CPLeftMouseDragged)
{
if (_trackingSegment == -1)
return;
var highlighted = [self testSegment:location] === _trackingSegment;
if (highlighted != _trackingHighlighted)
{
_trackingHighlighted = highlighted;
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
}
break;
}
[CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
+1 -1
View File
@@ -102,7 +102,6 @@ CPThemeStateShadowViewHeavy = CPThemeState("shadowview-style-heavy");
[self setWeight:CPLightShadow];
[self setHitTests:NO];
[self setClipsToBounds:NO];
}
return self;
@@ -170,6 +169,7 @@ CPThemeStateShadowViewHeavy = CPThemeState("shadowview-style-heavy");
- (void)layoutSubviews
{
[super layoutSubviews];
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
}
+7 -11
View File
@@ -23,11 +23,13 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPButtonBar.j"
@import "CPCursor.j"
@import "CPImage.j"
@import "CPTrackingArea.j"
@import "CPView.j"
@import "CPCursor.j"
@import "CPTrackingArea.j"
@class CPUserDefaults
@global CPApp
@@ -822,9 +824,6 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
- (void)layoutSubviews
{
// Retrieve the current color based on the current theme state (Standard vs HUD)
var currentDividerColor = [self dividerColor];
for (var i = 0, count = _arrangedSubviews.length, position = 0, origin; i < count; i++)
{
origin = CGPointMakeZero();
@@ -839,9 +838,6 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
origin[_originComponent] = position;
position += [_dividerSubviews[i] frame].size[_sizeComponent];
// Apply the color to the specific divider view
[_dividerSubviews[i] setBackgroundColor:currentDividerColor];
[_dividerSubviews[i] setFrameOrigin:origin];
}
}
@@ -1175,7 +1171,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
// Silently ignore bad positions which could result from odd delegate responses. We don't want these
// bad results to go into the system and cause havoc with frame sizes as the split view tries to resize
// its subviews.
if (CPIsNumeric(proposedPosition))
if (_IS_NUMERIC(proposedPosition))
position = proposedPosition;
var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex],
@@ -1185,10 +1181,10 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
proposedActualMin = [self _sendDelegateSplitViewConstrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex],
proposedActualMax = [self _sendDelegateSplitViewConstrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex];
if (CPIsNumeric(proposedActualMin))
if (_IS_NUMERIC(proposedActualMin))
actualMin = proposedActualMin;
if (CPIsNumeric(proposedActualMax))
if (_IS_NUMERIC(proposedActualMax))
actualMax = proposedActualMax;
var viewA = _arrangedSubviews[dividerIndex],
+2
View File
@@ -20,6 +20,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPView.j"
// Gravity Areas
+7 -67
View File
@@ -175,53 +175,12 @@
[super setFrame:frame];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
// Force a layout update because the internal buttons (_buttonUp and _buttonDown)
// rely on layoutSubviews to receive the new theme attributes (like HUD colors).
[self setNeedsLayout];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self setNeedsLayout];
}
/*! @ignore */
- (void)layoutSubviews
{
var controlSizeThemeState = [self _controlSizeThemeState],
aFrame = [self frame],
isHUD = [self hasThemeState:CPThemeStateHUD],
// 1. Prepare Lookup States (To fetch the correct image/color from the Stepper's theme)
normalLookupStates = [controlSizeThemeState, CPThemeStateBordered],
disabledLookupStates = [controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled],
highlightedLookupStates = [controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted],
// 2. Prepare Target States (To tell the child buttons when to use this image)
normalTargetStates = [CPThemeStateBordered, CPButtonStateBezelStyleRoundRect],
disabledTargetStates = [CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect],
highlightedTargetStates = [CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect];
// If we are in HUD mode, add CPThemeStateHUD to both lookup and target arrays
if (isHUD)
{
// Lookup: Ask theme for "HUD" version of the stepper arrows
normalLookupStates.push(CPThemeStateHUD);
disabledLookupStates.push(CPThemeStateHUD);
highlightedLookupStates.push(CPThemeStateHUD);
// Target: Tell the child buttons "Use this when you are in HUD state"
normalTargetStates.push(CPThemeStateHUD);
disabledTargetStates.push(CPThemeStateHUD);
highlightedTargetStates.push(CPThemeStateHUD);
}
var upSize = [self valueForThemeAttribute:@"up-button-size" inState:controlSizeThemeState],
upSize = [self valueForThemeAttribute:@"up-button-size" inState:controlSizeThemeState],
downSize = [self valueForThemeAttribute:@"down-button-size" inState:controlSizeThemeState],
upFrame = CGRectMake(0, 0, upSize.width, upSize.height),
downFrame = CGRectMake(0, upSize.height, downSize.width, downSize.height);
@@ -229,31 +188,12 @@
[_buttonUp setFrame:upFrame];
[_buttonDown setFrame:downFrame];
// Apply Up Button Attributes
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:normalLookupStates]
forThemeAttribute:@"bezel-color"
inStates:normalTargetStates];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:disabledLookupStates]
forThemeAttribute:@"bezel-color"
inStates:disabledTargetStates];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:highlightedLookupStates]
forThemeAttribute:@"bezel-color"
inStates:highlightedTargetStates];
// Apply Down Button Attributes
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:normalLookupStates]
forThemeAttribute:@"bezel-color"
inStates:normalTargetStates];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:disabledLookupStates]
forThemeAttribute:@"bezel-color"
inStates:disabledTargetStates];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:highlightedLookupStates]
forThemeAttribute:@"bezel-color"
inStates:highlightedTargetStates];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPButtonStateBezelStyleRoundRect]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPButtonStateBezelStyleRoundRect]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect]];
}
- (void)_sizeToFit
+1 -1
View File
@@ -117,7 +117,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
var height = [_tabs valueForThemeAttribute:@"min-size"].height;
[_tabs setFrameSize:CGSizeMake(0, height)];
_box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]];
_box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]];
[_box setTabView:self];
[_box setContentInset:[self currentValueForThemeAttribute:@"box-content-inset"]];
[_box setContentViewMargins:CGSizeMakeZero()];
+23 -231
View File
@@ -25,10 +25,6 @@
@import "CPCursor.j"
@import "_CPImageAndTextView.j"
@import "CPTrackingArea.j"
@import "CPAnimationContext.j"
@import "CPViewAnimator.j"
@import "CPScrollView.j"
@import <Foundation/CPGeometry.j>
@class CPTableView
@@ -54,16 +50,10 @@
@"text-color": [CPNull null],
@"font": [CPNull null],
@"text-shadow-color": [CPNull null],
@"text-shadow-offset": CGSizeMakeZero(),
@"dont-draw-separator": NO
@"text-shadow-offset": CGSizeMakeZero()
};
}
- (BOOL)acceptsFirstMouse:(CPEvent)anEvent
{
return YES;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
@@ -76,22 +66,18 @@
- (void)_init
{
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
var inset = [self valueForThemeAttribute:@"text-inset"];
_textField = [[_CPImageAndTextView alloc] initWithFrame:
CGRectMake(inset.left, inset.top, CGRectGetWidth([self bounds]) - (inset.left + inset.right), CGRectGetHeight([self bounds]) - (inset.top + inset.bottom))];
CGRectMake(5.0, 0.0, CGRectGetWidth([self bounds]) - 10.0, CGRectGetHeight([self bounds]))];
[_textField setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_textField setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode"]];
[_textField setTextColor:[self valueForThemeAttribute:@"text-color"]];
[_textField setFont:[self valueForThemeAttribute:@"font"]];
[_textField setAlignment:[self valueForThemeAttribute:@"text-alignment"]];
[_textField setLineBreakMode:CPLineBreakByTruncatingTail];
[_textField setTextColor:[CPColor colorWithRed:51.0 / 255.0 green:51.0 / 255.0 blue:51.0 / 255.0 alpha:1.0]];
[_textField setFont:[CPFont boldSystemFontOfSize:12.0]];
[_textField setAlignment:CPLeftTextAlignment];
[_textField setVerticalAlignment:CPCenterVerticalTextAlignment];
[_textField setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color"]];
[_textField setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset"]];
[_textField setTextShadowColor:[CPColor whiteColor]];
[_textField setTextShadowOffset:CGSizeMake(0,1)];
[self addSubview:_textField];
}
@@ -202,9 +188,6 @@
- (void)drawRect:(CGRect)aRect
{
if ([self valueForThemeAttribute:@"dont-draw-separator"])
return;
var bounds = [self bounds];
if (!CGRectIntersectsRect(aRect, bounds))
@@ -240,8 +223,7 @@ var _CPTableColumnHeaderViewStringValueKey = @"_CPTableColumnHeaderViewStringVal
[self _init];
[self _setIndicatorImage:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewImageKey]];
[self setStringValue:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewStringValueKey]];
// FIXME: pourquoi dans actif, font=null ?
// [self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
[self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
}
return self;
@@ -275,13 +257,11 @@ var CPTableHeaderViewResizeZone = 3.0,
BOOL _isResizing;
BOOL _isDragging;
BOOL _isAnimating;
BOOL _canDragColumn;
CPView _columnDragView;
CPView _columnDragHeaderView;
CPView _columnDragClipView;
CPScrollView _columnDragScrollView;
float _columnOldWidth;
@@ -298,9 +278,7 @@ var CPTableHeaderViewResizeZone = 3.0,
return @{
@"background-color": [CPNull null],
@"divider-color": [CPColor grayColor],
@"divider-thickness": 1.0,
@"swap-animation": [CPNull null],
@"return-animation": [CPNull null]
@"divider-thickness": 1.0
};
}
@@ -362,24 +340,7 @@ var CPTableHeaderViewResizeZone = 3.0,
- (CPInteger)columnAtPoint:(CGPoint)aPoint
{
var tableView = [self tableView],
tableColumns = [tableView tableColumns],
count = [tableColumns count],
bounds = [self bounds],
// Create a point that keeps the X position but forces Y to be safely
// in the middle of the header view.
constrainedPoint = CGPointMake(aPoint.x, CGRectGetMidY(bounds));
// Iterate through columns to find which one contains the constrained X coordinate
for (var i = 0; i < count; i++)
{
// headerRectOfColumn: is a utility method defined in CPTableHeaderView
// that handles the coordinate conversion from the table view relative to the header.
if (CGRectContainsPoint([self headerRectOfColumn:i], constrainedPoint))
return i;
}
return -1;
return [_tableView columnAtPoint:aPoint];
}
- (CGRect)headerRectOfColumn:(CPInteger)aColumnIndex
@@ -404,8 +365,6 @@ var CPTableHeaderViewResizeZone = 3.0,
- (void)layoutSubviews
{
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
var tableColumns = [_tableView tableColumns],
count = [tableColumns count];
@@ -483,14 +442,6 @@ var CPTableHeaderViewResizeZone = 3.0,
}
else if (_isDragging)
{
// First, we have to avoid a running condition where user stops dragging while a swap animation is running
if (_isAnimating)
{
[CPTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(_retry_mouseUp:) userInfo:theEvent repeats:NO];
return;
}
[self _stopDraggingTableColumn:_activeColumn];
}
else if (_activeColumn != -1)
@@ -507,11 +458,6 @@ var CPTableHeaderViewResizeZone = 3.0,
_activeColumn = -1;
}
- (void)_retry_mouseUp:(CPTimer)aTimer
{
[self mouseUp:[aTimer userInfo]];
}
@end
@implementation CPTableHeaderView (CPTrackingArea)
@@ -616,21 +562,7 @@ var CPTableHeaderViewResizeZone = 3.0,
pressure:[theEvent pressure]];
[self autoscroll:constrainedEvent];
var contentView = [_tableView superview],
boundsOriginBefore = [contentView boundsOrigin];
[_tableView autoscroll:constrainedEvent];
var boundsOriginAfter = [contentView boundsOrigin],
deltaX = boundsOriginAfter.x - boundsOriginBefore.x;
if (_isDragging)
{
var dragContentView = [_columnDragScrollView contentView],
dragContentBoundsOrigin = [dragContentView boundsOrigin];
[dragContentView setBoundsOrigin:CGPointMake(dragContentBoundsOrigin.x + deltaX, dragContentBoundsOrigin.y)];
}
}
- (CGRect)_headerRectOfLastVisibleColumn
@@ -652,91 +584,16 @@ var CPTableHeaderViewResizeZone = 3.0,
- (CGPoint)_constrainDragPoint:(CGPoint)aPoint
{
// This effectively clamps the value between the minimum and maximum
var tableFrame = [_tableView frame],
dragFrame = [_columnDragView frame],
maxX = tableFrame.size.width - dragFrame.size.width,
point = CGPointMake(MAX(MIN(aPoint.x, maxX),0), aPoint.y);
var visibleRect = [_tableView visibleRect],
lastColumnRect = [self _headerRectOfLastVisibleColumn],
activeColumnRect = [self headerRectOfColumn:_activeColumn],
maxX = CGRectGetMaxX(lastColumnRect) - CGRectGetWidth(activeColumnRect) - CGRectGetMinX(visibleRect),
point = CGPointMake(MAX(MIN(aPoint.x, maxX), -CGRectGetMinX(visibleRect)), aPoint.y);
return point;
}
- (void)_moveColumn:(CPInteger)aFromIndex toColumn:(CPInteger)aToIndex
{
if (_isAnimating)
return;
var swapAnimation = [self currentValueForThemeAttribute:@"swap-animation"];
if (swapAnimation)
{
_isAnimating = YES;
// There's a theme defined animation function, just use it
objj_eval("("+swapAnimation+")")(self, aFromIndex, aToIndex, _columnDragClipView, _columnDragView);
// var animatedColumn = [[_tableView tableColumns] objectAtIndex:aToIndex],
// animatedHeader = [animatedColumn headerView],
// animatedHeaderOrigin = [animatedHeader frameOrigin],
//
// destinationX,
// draggedHeader = [[[_tableView tableColumns] objectAtIndex:aFromIndex] headerView],
//
// scrollView = [self enclosingScrollView],
// animatedView = [_tableView _animationViewForColumn:aToIndex],
// animatedOrigin = [animatedView frameOrigin];
//
// [_columnDragClipView addSubview:animatedView positioned:CPWindowBelow relativeTo:_columnDragView];
//
// [[animatedHeader subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:YES];
// [animatedHeader setThemeState:CPThemeStateVertical];
//
// if (aFromIndex < aToIndex)
// destinationX = CGRectGetMinX([_tableView rectOfColumn:aFromIndex]);
// else
// destinationX = animatedOrigin.x + CGRectGetWidth([_tableView rectOfColumn:aFromIndex]);
//
// [CPAnimationContext beginGrouping];
//
// var context = [CPAnimationContext currentContext];
//
// [context setDuration:0.15];
// [context setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
// [context setCompletionHandler:function() {
// [animatedView removeFromSuperview];
//
// [self _finalize_moveColumn:aFromIndex toColumn:aToIndex];
//
// [animatedHeader unsetThemeState:CPThemeStateVertical];
// [[animatedHeader subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:NO];
//
// if ([animatedView isSelected])
// {
// [animatedHeader setThemeState:CPThemeStateSelected];
//
// // We have to reselect the animated column
// [[_tableView selectedColumnIndexes] addIndex:aFromIndex];
// }
//
// // Reload animated column
// var columnVisRect = CGRectIntersection([_tableView rectOfColumn:aFromIndex], [_tableView visibleRect]),
// rowsIndexes = [CPIndexSet indexSetWithIndexesInRange:[_tableView rowsInRect:columnVisRect]],
// columnsIndexes = [CPIndexSet indexSetWithIndex:aFromIndex];
//
// [_tableView _loadDataViewsInRows:rowsIndexes columns:columnsIndexes];
// [_tableView _layoutViewsForRowIndexes:rowsIndexes columnIndexes:columnsIndexes];
//
// [_tableView._tableDrawView displayRect:columnVisRect];
// }];
//
// [[animatedView animator] setFrameOrigin:CGPointMake(destinationX, animatedOrigin.y)];
//
// [CPAnimationContext endGrouping];
}
else
[self _finalize_moveColumn:aFromIndex toColumn:aToIndex];
}
- (void)_finalize_moveColumn:(CPInteger)aFromIndex toColumn:(CPInteger)aToIndex
{
[_tableView moveColumn:aFromIndex toColumn:aToIndex];
_activeColumn = aToIndex;
@@ -745,8 +602,6 @@ var CPTableHeaderViewResizeZone = 3.0,
[_tableView _setDraggedColumn:_activeColumn];
[self setNeedsDisplay:YES];
_isAnimating = NO;
}
- (BOOL)isDragging
@@ -763,30 +618,17 @@ var CPTableHeaderViewResizeZone = 3.0,
// Create a new clip view for the drag view that clips to the header + visible content
var headerHeight = CGRectGetHeight([self frame]),
scrollView = [self enclosingScrollView],
contentFrame = [[scrollView contentView] frame],
contentBounds = [[scrollView contentView] bounds];
contentFrame = [[scrollView contentView] frame];
contentFrame.origin.y -= headerHeight;
contentFrame.size.height += headerHeight;
_columnDragScrollView = [[CPScrollView alloc] initWithFrame:contentFrame];
[_columnDragScrollView setHasHorizontalScroller:NO];
[_columnDragScrollView setHasVerticalScroller:NO];
[_columnDragScrollView setBorderType:CPNoBorder];
var tableFrame = [_tableView frame],
clipFrame = CGRectMake(0, 0, tableFrame.size.width, contentFrame.size.height);
_columnDragClipView = [[CPView alloc] initWithFrame:clipFrame];
_columnDragClipView = [[CPView alloc] initWithFrame:contentFrame];
[_columnDragClipView addSubview:_columnDragView];
[_columnDragScrollView setDocumentView:_columnDragClipView];
[[_columnDragScrollView contentView] setBoundsOrigin:CGPointMake(contentBounds.origin.x, 0)];
// Insert the clip view above the table header (and content)
[scrollView addSubview:_columnDragScrollView positioned:CPWindowAbove relativeTo:self];
[scrollView addSubview:_columnDragClipView positioned:CPWindowAbove relativeTo:self];
// Hide the underlying column header subviews, we just want to draw the chrome
var headerView = [[[_tableView tableColumns] objectAtIndex:aColumnIndex] headerView];
@@ -796,9 +638,6 @@ var CPTableHeaderViewResizeZone = 3.0,
// The underlying column header shows normal state
[headerView unsetThemeStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
// FIXME: Just a little hack to get a special background (using an unused theme state)
[headerView setThemeState:CPThemeStateVertical];
// Keep track of the location within the column header where the original mousedown occurred
_columnDragHeaderView = [_columnDragView viewWithTag:CPTableHeaderViewDragColumnHeaderTag];
@@ -853,43 +692,10 @@ var CPTableHeaderViewResizeZone = 3.0,
}
- (void)_stopDraggingTableColumn:(CPInteger)aColumnIndex
{
var returnAnimation = [self currentValueForThemeAttribute:@"return-animation"];
if (returnAnimation)
{
_isAnimating = YES;
// There's a theme defined animation function, just use it
objj_eval("("+returnAnimation+")")(self, aColumnIndex, _columnDragView);
// var animatedColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
// animatedHeader = [animatedColumn headerView],
// animatedHeaderOrigin = [animatedHeader frameOrigin];
//
// [CPAnimationContext beginGrouping];
//
// var context = [CPAnimationContext currentContext];
//
// [context setDuration:0.15];
// [context setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
// [context setCompletionHandler:function() {
//
// [self _finalize_stopDraggingTableColumn:aColumnIndex];
// }];
//
// [[_columnDragView animator] setFrameOrigin:CGPointMake(animatedHeaderOrigin.x, 0)];
//
// [CPAnimationContext endGrouping];
}
else
[self _finalize_stopDraggingTableColumn:aColumnIndex];
}
- (void)_finalize_stopDraggingTableColumn:(CPInteger)aColumnIndex
{
_isDragging = NO;
[_columnDragClipView removeFromSuperview];
[_tableView _setDraggedColumn:-1];
var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
@@ -897,30 +703,16 @@ var CPTableHeaderViewResizeZone = 3.0,
[[headerView subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:NO];
// Restore headerView background
[headerView unsetThemeState:CPThemeStateVertical];
if (_tableView._draggedColumnIsSelected)
[headerView setThemeState:CPThemeStateSelected];
// Reload animated column
var columnVisRect = CGRectIntersection([_tableView rectOfColumn:aColumnIndex], [_tableView visibleRect]),
rowsIndexes = [CPIndexSet indexSetWithIndexesInRange:[_tableView rowsInRect:columnVisRect]],
columnsIndexes = [CPIndexSet indexSetWithIndex:aColumnIndex];
[_tableView _reloadDataViews];
[[_tableView headerView] setNeedsLayout];
[_tableView _loadDataViewsInRows:rowsIndexes columns:columnsIndexes];
[_tableView _layoutViewsForRowIndexes:rowsIndexes columnIndexes:columnsIndexes];
[_tableView _updateDataViewsFocusState];
[_tableView._tableDrawView displayRect:columnVisRect];
[[CPCursor arrowCursor] set]; // FIXME: retirer ?
[[CPCursor arrowCursor] set];
[self updateTrackingAreas];
[_columnDragScrollView removeFromSuperview];
[_tableView _sendDelegateDidDragTableColumn:tableColumn];
_isAnimating = NO;
}
- (BOOL)_shouldResizeTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
+465 -460
View File
File diff suppressed because it is too large Load Diff
+12 -16
View File
@@ -771,27 +771,33 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"],
left = CGRectGetMinX(contentRect);
// If the browser has a built in left padding, compensate for it. We need the input text to be exactly on top of the original text.
if (CPFeatureIsCompatible(CPInput1PxLeftPadding))
left -= 1;
switch (verticalAlign)
{
case CPTopVerticalTextAlignment:
var topPoint = CEIL(CGRectGetMinY(contentRect)) + "px";
var topPoint = CGRectGetMinY(contentRect) + "px";
break;
case CPCenterVerticalTextAlignment:
var topPoint = CEIL((CGRectGetMidY(contentRect) - (lineHeight / 2))) + "px";
var topPoint = (CGRectGetMidY(contentRect) - (lineHeight / 2)) + "px";
break;
case CPBottomVerticalTextAlignment:
var topPoint = CEIL((CGRectGetMaxY(contentRect) - lineHeight)) + "px";
var topPoint = (CGRectGetMaxY(contentRect) - lineHeight) + "px";
break;
default:
var topPoint = CEIL(CGRectGetMinY(contentRect)) + "px";
var topPoint = CGRectGetMinY(contentRect) + "px";
break;
}
// Use currentValueForThemeAttribute to respect all current states (HUD, Placeholder, Editing, etc.)
element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
if ([self hasThemeState:CPTextFieldStatePlaceholder])
element.style.color = [[self valueForThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder] cssString];
else
element.style.color = [[self valueForThemeAttribute:@"text-color" inState:CPThemeStateEditing] cssString];
switch ([self alignment])
{
@@ -1295,16 +1301,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[super textDidChange:note];
}
- (void)validateEditing
{
#if PLATFORM(DOM)
var element = [self _inputElement];
if (element)
[self _setStringValue:element.value isNewValue:YES errorDescription:nil];
#endif
}
- (void)textDidBeginEditing:(CPNotification)note
{
//this looks to prevent false propagation of notifications for other objects
+1 -5
View File
@@ -870,11 +870,6 @@ var kDelegateRespondsTo_textShouldBeginEditing
- (void)drawInsertionPointInRect:(CGRect)aRect color:(CPColor)aColor turnedOn:(BOOL)flag
{
[_caret setRect:aRect];
#if PLATFORM(DOM)
_caret._caretDOM.style.backgroundColor = [_insertionPointColor cssString] || "black";
#endif
[_caret setVisibility:flag stop:NO];
}
@@ -3029,6 +3024,7 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey",
style.padding = "0px";
style.margin = "0px";
style.whiteSpace = "pre";
style.backgroundColor = "black";
_caretDOM.style.width = "1px";
_caretDOM.style.zIndex = 10001;
_textView = aView;
+1 -3
View File
@@ -814,12 +814,10 @@ CPThemeStateKeyWindow = CPThemeState("keyWindow");
CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular");
CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall");
CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
CPThemeStateControlSizeLarge = CPThemeState("controlSizeLarge");
CPThemeStateAlternateState = CPThemeState("alternate");
CPThemeStateComposedControl = CPThemeState("composed");
CPThemeStateWindowsPlatform = CPThemeState("windowsPlatform");
CPThemeStateNormalString = String(CPThemeStateNormal);
CPThemeStateHUD = CPThemeState("hud");
@implementation _CPThemeAttribute : CPObject
+2 -34
View File
@@ -334,9 +334,8 @@ var CPViewHighDPIDrawingEnabled = YES;
{
return @{
@"css-based": NO,
@"nib2cib-adjustment-frame": [CPNull null],
@"direct-nib2cib-adjustment": NO,
@"dynamic-set": [CPNull null]
@"dynamic-set": [CPNull null],
@"nib2cib-adjustment-frame": CGRectMakeZero()
};
}
@@ -929,13 +928,6 @@ var CPViewHighDPIDrawingEnabled = YES;
*/
- (void)viewDidMoveToWindow
{
var window = [self window];
// If the view is inside a window with the HUD style mask, turn on the HUD state
if (window && ([window styleMask] & CPHUDBackgroundWindowMask))
[self setThemeState:CPThemeStateHUD];
else
[self unsetThemeState:CPThemeStateHUD];
}
/*!
@@ -4179,27 +4171,3 @@ var _CPViewGetTransform = function(/*CPView*/ fromView, /*CPView */ toView)
return transform;
};
@implementation CPView (ThemingAdditions)
- (void)_setThemeStateRecursively:(ThemeState)aState
{
[_subviews makeObjectsPerformSelector:@selector(_setThemeStateRecursively:) withObject:aState];
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view setThemeState:aState];
}];
}
- (void)_unsetThemeStateRecursively:(ThemeState)aState
{
[_subviews makeObjectsPerformSelector:@selector(_unsetThemeStateRecursively:) withObject:aState];
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view unsetThemeState:aState];
}];
}
@end
+11 -34
View File
@@ -28,8 +28,6 @@ CPViewAnimationTargetKey = @"CPViewAnimationTargetKey";
CPViewAnimationStartFrameKey = @"CPViewAnimationStartFrameKey";
CPViewAnimationEndFrameKey = @"CPViewAnimationEndFrameKey";
CPViewAnimationEffectKey = @"CPViewAnimationEffectKey";
CPViewAnimationStartOpacityKey = @"CPViewAnimationStartOpacityKey";
CPViewAnimationEndOpacityKey = @"CPViewAnimationEndOpacityKey";
CPViewAnimationFadeInEffect = @"CPViewAnimationFadeInEffect";
CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
@@ -134,24 +132,12 @@ CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
[view setFrame:intermediateFrame];
// Check for explicit Opacity keys first
var startOpacity = [dictionary objectForKey:CPViewAnimationStartOpacityKey],
endOpacity = [dictionary objectForKey:CPViewAnimationEndOpacityKey];
if (startOpacity != nil && endOpacity != nil)
{
// Interpolate alpha
[view setAlphaValue:startOpacity + (endOpacity - startOpacity) * value];
}
else
{
// Fallback to legacy Effect keys
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0 * value];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:1.0 + ( 0.0 - 1.0 ) * value];
}
// Update the view's alpha value
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0 * value];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:1.0 + ( 0.0 - 1.0 ) * value];
if (progress === 1.0)
[self _targetView:view setHidden:CGRectIsEmpty(endFrame) || [view alphaValue] === 0.0];
@@ -169,20 +155,11 @@ CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
[view setFrame:endFrame];
var endOpacity = [dictionary objectForKey:CPViewAnimationEndOpacityKey];
if (endOpacity != nil)
{
[view setAlphaValue:endOpacity];
}
else
{
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:0.0];
}
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:0.0];
[self _targetView:view setHidden:CGRectIsEmpty(endFrame) || [view alphaValue] === 0.0];
}
-7
View File
@@ -979,9 +979,6 @@ CPTexturedBackgroundWindowMask
- (void)orderFront:(id)aSender
{
[self orderWindow:CPWindowAbove relativeTo:0];
if (_styleMask & CPHUDBackgroundWindowMask)
[_contentView _setThemeStateRecursively:CPThemeStateHUD];
}
- (void)_orderFront
@@ -2542,10 +2539,6 @@ CPTexturedBackgroundWindowMask
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillCloseNotification object:self];
// Give a chance to the _windowView to do things before closing (for example, remove an observer)
if ([_windowView respondsToSelector:@selector(_close)])
[_windowView _close];
[_parentWindow removeChildWindow:self];
[self _orderOutRecursively:NO];
[self _detachFromChildrenClosing:!_parentWindow];
@@ -39,22 +39,6 @@
};
}
- (id)initWithFrame:(CGRect)aFrame styleMask:(unsigned)aStyleMask
{
self = [super initWithFrame:aFrame styleMask:aStyleMask];
if (self)
{
// Check if the mask contains the HUD flag
if (aStyleMask & CPHUDBackgroundWindowMask)
{
[self setBackgroundColor:[CPColor blackColor]];
}
}
return self;
}
- (void)setShowsResizeIndicator:(BOOL)shouldShowResizeIndicator
{
// We don't ever want to show the resize indicator.
+55 -331
View File
@@ -22,7 +22,6 @@
@import "CPButton.j"
@import "_CPTitleableWindowView.j"
@import "CPApplication_Constants.j"
@class _CPDocModalWindowView
@@ -44,11 +43,7 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
+ (CPDictionary)themeAttributes
{
return @{
@"close-active-image-button": [CPNull null],
@"minimize-active-image-button": [CPNull null],
@"zoom-active-image-button": [CPNull null],
};
return @{};
}
- (id)initWithFrame:(CGRect)aFrame windowView:(_CPWindowView)parentView
@@ -76,26 +71,19 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
bounds = [self bounds],
bezelHeadColor = [[CPTheme defaultTheme] valueForAttributeWithName:_isSheet ? @"bezel-head-sheet-color" : @"bezel-head-color" inState:[_parentView themeState] forClass:_CPStandardWindowView];
// Apply the border/background color to self (the container) so borders are drawn on the outside
[self setBackgroundColor:bezelHeadColor];
[_gradientView setFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), gradientHeight)];
[_gradientView setBackgroundColor:bezelHeadColor];
// Set x=0.0 to avoid 1px gap. Width is reduced by 2.0 to fit inside the 1px borders on each side.
[_gradientView setFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds) - 2.0, gradientHeight)];
// Set x=0.0 to avoid 1px gap. Width is reduced by 2.0.
[_solidView setFrame:CGRectMake(0.0, gradientHeight, CGRectGetWidth(bounds) - 2.0, CGRectGetHeight(bounds) - gradientHeight)];
[_solidView setFrame:CGRectMake(0.0, gradientHeight, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - gradientHeight)];
[_solidView setBackgroundColor:[[CPTheme defaultTheme] valueForAttributeWithName:@"solid-color" forClass:_CPStandardWindowView]];
}
- (void)resizeSubviewsWithOldSize:(CGSize)aSize
{
var bounds = [self bounds],
width = CGRectGetWidth(bounds) - 2.0; // Account for border inset
var bounds = [self bounds];
if (width < 0) width = 0;
[_gradientView setFrameSize:CGSizeMake(width, [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
[_solidView setFrameSize:CGSizeMake(width, CGRectGetHeight(bounds) - [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
[_gradientView setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
[_solidView setFrameSize:CGSizeMake(CGRectGetWidth(bounds), CGRectGetHeight(bounds) - [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
}
@end
@@ -109,16 +97,9 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
CPButton _closeButton;
CPButton _minimizeButton;
CPButton _zoomButton;
BOOL _isDocumentEdited;
BOOL _isSheet;
CPTrackingArea _closeButtonTrackingArea;
CPTrackingArea _minimizeButtonTrackingArea;
CPTrackingArea _zoomButtonTrackingArea;
int _buttonsWidth;
}
+ (CPString)defaultThemeClass
@@ -142,8 +123,6 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
@"close-image-highlighted-button": [CPNull null],
@"unsaved-image-button": [CPNull null],
@"unsaved-image-highlighted-button": [CPNull null],
@"zoom-image-button": [CPNull null],
@"zoom-image-highlighted-button": [CPNull null]
};
}
@@ -175,7 +154,7 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
var theClass = [self class],
bounds = [self bounds];
_headView = [[_CPTexturedWindowHeadView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), _titleBarHeight) windowView:self];
_headView = [[_CPTexturedWindowHeadView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), [self valueForThemeAttribute:@"title-bar-height"]) windowView:self];
[_headView setAutoresizingMask:CPViewWidthSizable];
[_headView setHitTests:NO];
@@ -202,50 +181,30 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
if (_styleMask & CPClosableWindowMask)
{
_closeButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
_closeButton = [[CPButton alloc] initWithFrame:CGRectMake(8.0, 8.0, 16.0, 16.0)];
[_closeButton setButtonType:CPMomentaryChangeButton];
[_closeButton setBordered:NO];
[self _updateCloseButton];
[self addSubview:_closeButton];
}
if (_styleMask & CPMiniaturizableWindowMask)
if (_styleMask & CPMiniaturizableWindowMask && ![CPPlatform isBrowser])
{
_minimizeButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
_minimizeButton = [[CPButton alloc] initWithFrame:CGRectMake(27.0, 7.0, 16.0, 16.0)];
[_minimizeButton setButtonType:CPMomentaryChangeButton];
[_minimizeButton setBordered:NO];
[self addSubview:_minimizeButton];
}
if (_styleMask & CPResizableWindowMask)
{
_zoomButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
[_zoomButton setButtonType:CPMomentaryChangeButton];
[_zoomButton setBordered:NO];
[self addSubview:_zoomButton];
}
[self _updateWindowButtons:YES];
[self tile];
// Observe CPApplicationOSBehaviorDidChangeNotification
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_osBehaviorDidChange:) name:CPApplicationOSBehaviorDidChangeNotification object:CPApp];
}
return self;
}
// This will be called by CPWindow -close so the observer can be removed
- (void)_close
{
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPApplicationOSBehaviorDidChangeNotification object:CPApp];
}
- (void)viewDidMoveToWindow
{
[_closeButton setTarget:[self window]];
@@ -253,14 +212,11 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_minimizeButton setTarget:[self window]];
[_minimizeButton setAction:@selector(performMiniaturize:)];
[_zoomButton setTarget:[self window]];
[_zoomButton setAction:@selector(performZoom:)];
}
- (CGSize)toolbarOffset
{
return CGSizeMake(0.0, _titleBarHeight);
return CGSizeMake(0.0, [self valueForThemeAttribute:@"title-bar-height"]);
}
- (void)tile
@@ -280,7 +236,6 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_headView setFrameSize:CGSizeMake(width, headHeight)];
// Set divider to full width to prevent gaps/holes at the sides.
[_dividerView setFrame:CGRectMake(0.0, headHeight, width, _CPStandardWindowViewDividerViewHeight)];
var dividerMinY = 0,
@@ -291,179 +246,52 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_bodyView setFrame:CGRectMake(0.0, dividerMinY, width, CGRectGetHeight(bounds) - dividerMinY)];
[_titleField setFrame:CGRectMake(_buttonsWidth, 0, width - _buttonsWidth * 2.0, _titleBarHeight)];
var leftOffset = 8;
if (_closeButton)
leftOffset += 19.0;
if (_minimizeButton)
leftOffset += 19.0;
[_titleField setFrame:CGRectMake(leftOffset, 0, width - leftOffset * 2.0, [self valueForThemeAttribute:@"title-bar-height"])];
var contentFrame = [_bodyView frame];
[[theWindow contentView] setFrame:CGRectInset(contentFrame, 1.0, 1.0)];
}
- (void)_updateWindowButtons:(BOOL)shouldRefreshLayout
/*
- (void)setAnimatingToolbar:(BOOL)isAnimatingToolbar
{
if (shouldRefreshLayout)
[super setAnimatingToolbar:isAnimatingToolbar];
if ([self isAnimatingToolbar])
{
var mimicWindows = [CPApp shouldMimicWindows];
[[self toolbarView] setAutoresizingMask:CPViewHeightSizable];
if (mimicWindows)
[self setThemeState:CPThemeStateWindowsPlatform];
else
[self unsetThemeState:CPThemeStateWindowsPlatform];
[_headView setAutoresizingMask:CPViewHeightSizable];
[_dividerView setAutoresizingMask:CPViewMinYMargin];
[_bodyView setAutoresizingMask:CPViewMinYMargin];
// Remember that on Cappuccino, buttons are (left to right) : close - minimize - zoom
// and that on Windows, (also left to right) : minimize - zoom - close
var offset,
mask,
closeThemeOrigin = CGPointMakeCopy([self currentValueForThemeAttribute:@"close-image-origin"] || CGPointMakeZero()),
minimizeThemeOrigin = CGPointMakeCopy([self currentValueForThemeAttribute:@"minimize-image-origin"] || CGPointMakeZero()),
zoomThemeOrigin = CGPointMakeCopy([self currentValueForThemeAttribute:@"zoom-image-origin"] || CGPointMakeZero()),
closeThemeSize = CGSizeMakeZero(),
minimizeThemeSize = CGSizeMakeZero(),
zoomThemeSize = CGSizeMakeZero(),
delta1,
delta2;
// For retro-compatibility
if (![self isCSSBased])
{
if (mimicWindows)
{
// There's no zoom button in Aristo2
closeThemeOrigin = CGPointMake(-24.0, 8.0);
minimizeThemeOrigin = CGPointMake(-43.0, 8.0);
// FIXME: if someone designs a zoom button in Aristo2 one day, use those values
// closeThemeOrigin = CGPointMake(-24.0, 8.0);
// minimizeThemeOrigin = CGPointMake(-62.0, 8.0);
// zoomThemeOrigin = CGPointMake(-43.0, 8.0);
}
else
{
closeThemeOrigin = CGPointMake(8.0, 8.0);
minimizeThemeOrigin = CGPointMake(27.0, 8.0);
zoomThemeOrigin = CGPointMake(46.0, 8.0);
}
}
if (mimicWindows)
{
offset = [self bounds].size.width;
mask = CPViewMinXMargin;
delta1 = zoomThemeOrigin.x - closeThemeOrigin.x;
delta2 = minimizeThemeOrigin.x - zoomThemeOrigin.x;
}
else
{
offset = 0;
mask = CPViewMaxXMargin;
delta1 = minimizeThemeOrigin.x - closeThemeOrigin.x;
delta2 = zoomThemeOrigin.x - minimizeThemeOrigin.x;
}
_buttonsWidth = 0;
if (_styleMask & CPClosableWindowMask)
{
closeThemeSize = [self currentValueForThemeAttribute:@"close-image-size"];
// For retro-compatibility:
if (!closeThemeSize)
closeThemeSize = CGSizeMake(16.0, 16.0);
[_closeButton setFrame:CGRectMake(closeThemeOrigin.x + offset, closeThemeOrigin.y, closeThemeSize.width, closeThemeSize.height)];
[_closeButton setAutoresizingMask:mask];
_buttonsWidth = ABS(closeThemeOrigin.x) + (mimicWindows ? 0 : closeThemeSize.width);
}
else
{
minimizeThemeOrigin.x -= delta1;
zoomThemeOrigin.x -= delta1;
}
if (mimicWindows)
{
if (_styleMask & CPResizableWindowMask)
{
zoomThemeSize = [self currentValueForThemeAttribute:@"zoom-image-size"];
// For retro-compatibility:
if (!zoomThemeSize)
zoomThemeSize = CGSizeMake(16.0, 16.0);
[_zoomButton setFrame:CGRectMake(zoomThemeOrigin.x + offset, zoomThemeOrigin.y, zoomThemeSize.width, zoomThemeSize.height)];
[_zoomButton setAutoresizingMask:mask];
_buttonsWidth = ABS(zoomThemeOrigin.x);
}
else
minimizeThemeOrigin.x -= delta2;
if (_styleMask & CPMiniaturizableWindowMask)
{
minimizeThemeSize = [self currentValueForThemeAttribute:@"minimize-image-size"];
// For retro-compatibility:
if (!minimizeThemeSize)
minimizeThemeSize = CGSizeMake(16.0, 16.0);
[_minimizeButton setFrame:CGRectMake(minimizeThemeOrigin.x + offset, minimizeThemeOrigin.y, minimizeThemeSize.width, minimizeThemeSize.height)];
[_minimizeButton setAutoresizingMask:mask];
_buttonsWidth = ABS(minimizeThemeOrigin.x);
}
if (_buttonsWidth > 0)
_buttonsWidth += ABS(closeThemeOrigin.x) - closeThemeSize.width;
}
else // not win
{
if (_styleMask & CPMiniaturizableWindowMask)
{
minimizeThemeSize = [self currentValueForThemeAttribute:@"minimize-image-size"];
// For retro-compatibility:
if (!minimizeThemeSize)
minimizeThemeSize = CGSizeMake(16.0, 16.0);
[_minimizeButton setFrame:CGRectMake(minimizeThemeOrigin.x + offset, minimizeThemeOrigin.y, minimizeThemeSize.width, minimizeThemeSize.height)];
[_minimizeButton setAutoresizingMask:mask];
_buttonsWidth = minimizeThemeOrigin.x + minimizeThemeSize.width;
}
else
zoomThemeOrigin.x -= delta2;
if (_styleMask & CPResizableWindowMask)
{
zoomThemeSize = [self currentValueForThemeAttribute:@"zoom-image-size"];
// For retro-compatibility:
if (!zoomThemeSize)
zoomThemeSize = CGSizeMake(16.0, 16.0);
[_zoomButton setFrame:CGRectMake(zoomThemeOrigin.x + offset, zoomThemeOrigin.y, zoomThemeSize.width, zoomThemeSize.height)];
[_zoomButton setAutoresizingMask:mask];
_buttonsWidth = zoomThemeOrigin.x + zoomThemeSize.width;
}
if (_buttonsWidth > 0)
_buttonsWidth += closeThemeOrigin.x;
}
[self updateTrackingAreas];
[[[self window] contentView] setAutoresizingMask:CPViewNotSizable];
}
else
{
[[self toolbarView] setAutoresizingMask:CPViewWidthSizable];
[self _updateCloseButton];
[_headView setAutoresizingMask:CPViewWidthSizable];
[_dividerView setAutoresizingMask:CPViewWidthSizable];
[_bodyView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_minimizeButton setImage:[self currentValueForThemeAttribute:@"minimize-image-button"]];
[_minimizeButton setAlternateImage:[self currentValueForThemeAttribute:@"minimize-image-highlighted-button"]];
[_zoomButton setImage:[self currentValueForThemeAttribute:@"zoom-image-button"]];
[_zoomButton setAlternateImage:[self currentValueForThemeAttribute:@"zoom-image-highlighted-button"]];
[[[self window] contentView] setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
}
}
*/
- (void)_updateCloseButton
{
[_closeButton setFrameSize:[self valueForThemeAttribute:@"close-image-size"]];
[_closeButton setFrameOrigin:[self valueForThemeAttribute:@"close-image-origin"]];
if (_isDocumentEdited)
{
[_closeButton setImage:[self currentValueForThemeAttribute:@"unsaved-image-button"]];
@@ -476,15 +304,9 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
}
}
- (void)_osBehaviorDidChange:(id)application
{
[self _updateWindowButtons:YES];
}
- (void)setDocumentEdited:(BOOL)isEdited
{
_isDocumentEdited = isEdited;
[self _updateCloseButton];
}
@@ -515,10 +337,9 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_dividerView setHidden:enable];
}
[_closeButton setHidden:enable];
[_closeButton setHidden:enable];
[_minimizeButton setHidden:enable];
[_zoomButton setHidden:enable];
[_titleField setHidden:enable];
[_titleField setHidden:enable];
[[self window] setMovable:!enable];
@@ -560,35 +381,28 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
- (void)layoutSubviews
{
var width = [self bounds].size.width,
mimicWindows = [CPApp shouldMimicWindows];
var bounds = [self bounds];
[super layoutSubviews];
[self _updateCloseButton];
if (_closeButton || _minimizeButton || _zoomButton)
[self _updateWindowButtons:NO];
[_minimizeButton setImage:[self valueForThemeAttribute:@"minimize-image-button"]];
[_minimizeButton setAlternateImage:[self valueForThemeAttribute:@"minimize-image-highlighted-button"]];
[_dividerView setBackgroundColor:[self valueForThemeAttribute:@"divider-color"]];
[_bodyView setBackgroundColor:[self valueForThemeAttribute:@"body-color"]];
[_headView setNeedsLayout];
if (width - 2 * _buttonsWidth < _minimumTitleFieldSize)
{
if (width - _buttonsWidth - _titleMargin < _minimumTitleFieldSize)
[_titleField setFrame:CGRectMake((mimicWindows ? _titleMargin : _buttonsWidth), 0, width - _buttonsWidth - _titleMargin, _titleBarHeight)];
else
[_titleField setFrame:CGRectMake((mimicWindows ? width - _buttonsWidth - _minimumTitleFieldSize : _buttonsWidth), 0, _minimumTitleFieldSize, _titleBarHeight)];
}
}
- (CGSize)_minimumResizeSize
{
// The minimum width is such that the close/minimize/zoom button(s) would always be visible.
// We give the same margin to the right of the button(s) as there is to the left.
var size = CGSizeMakeCopy([super _minimumResizeSize]);
// The minimum width is such that the close button would always be visible.
// We give the same margin to the right of the button as there is to the left.
var size = [super _minimumResizeSize],
closeSize = [self valueForThemeAttribute:@"close-image-size"],
closeOrigin = [self valueForThemeAttribute:@"close-image-origin"];
size.width = _buttonsWidth;
size.width = closeSize.width + (closeOrigin.x * 2);
size.height += _CPStandardWindowViewDividerViewHeight;
return size;
@@ -599,94 +413,4 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
return [_bodyView frame].origin.y;
}
#pragma mark -
#pragma mark Hover management for buttons
- (void)updateTrackingAreas
{
if (_closeButtonTrackingArea)
[_closeButton removeTrackingArea:_closeButtonTrackingArea];
if (_minimizeButtonTrackingArea)
[_minimizeButton removeTrackingArea:_minimizeButtonTrackingArea];
if (_zoomButtonTrackingArea)
[_zoomButton removeTrackingArea:_zoomButtonTrackingArea];
if (_closeButton)
{
_closeButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveAlways | CPTrackingInVisibleRect
owner:self
userInfo:_closeButton];
[_closeButton addTrackingArea:_closeButtonTrackingArea];
}
if (_minimizeButton)
{
_minimizeButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveAlways | CPTrackingInVisibleRect
owner:self
userInfo:_minimizeButton];
[_minimizeButton addTrackingArea:_minimizeButtonTrackingArea];
}
if (_zoomButton)
{
_zoomButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveAlways | CPTrackingInVisibleRect
owner:self
userInfo:_zoomButton];
[_zoomButton addTrackingArea:_zoomButtonTrackingArea];
}
[super updateTrackingAreas];
}
- (void)mouseEntered:(CPEvent)anEvent
{
var triggeredButton = [[anEvent trackingArea] userInfo],
state = CPThemeStateHovered;
if ([CPApp shouldMimicWindows])
state = state.and(CPThemeStateWindowsPlatform);
if (triggeredButton === _closeButton)
{
if (_isDocumentEdited)
[_closeButton setImage:[self valueForThemeAttribute:@"unsaved-image-button" inState:state]];
else
[_closeButton setImage:[self valueForThemeAttribute:@"close-image-button" inState:state]];
}
else if (triggeredButton === _minimizeButton)
{
[_minimizeButton setImage:[self valueForThemeAttribute:@"minimize-image-button" inState:state]];
}
else if (triggeredButton === _zoomButton)
{
[_zoomButton setImage:[self valueForThemeAttribute:@"zoom-image-button" inState:state]];
}
}
- (void)mouseExited:(CPEvent)anEvent
{
var triggeredButton = [[anEvent trackingArea] userInfo];
if (triggeredButton === _closeButton)
{
if (_isDocumentEdited)
[_closeButton setImage:[self currentValueForThemeAttribute:@"unsaved-image-button"]];
else
[_closeButton setImage:[self currentValueForThemeAttribute:@"close-image-button"]];
}
else if (triggeredButton === _minimizeButton)
{
[_minimizeButton setImage:[self currentValueForThemeAttribute:@"minimize-image-button"]];
}
else if (triggeredButton === _zoomButton)
{
[_zoomButton setImage:[self currentValueForThemeAttribute:@"zoom-image-button"]];
}
}
@end
+4 -13
View File
@@ -27,9 +27,6 @@
@implementation _CPTitleableWindowView : _CPWindowView
{
CPTextField _titleField;
int _minimumTitleFieldSize;
int _titleBarHeight;
int _titleMargin;
}
+ (int)titleBarHeight
@@ -50,7 +47,7 @@
+ (CGRect)frameRectForContentRect:(CGRect)aContentRect
{
var frameRect = [super frameRectForContentRect:aContentRect],
var frameRect = CGRectMakeCopy(aContentRect),
titleBarHeight = [self titleBarHeight];
frameRect.origin.y -= titleBarHeight;
@@ -65,19 +62,15 @@
if (self)
{
// We cache some values for optimization
_titleBarHeight = [[self class] titleBarHeight];
_titleMargin = [self currentValueForThemeAttribute:@"title-margin"];
_titleField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_titleField setHitTests:NO];
[_titleField setStringValue:@"Untitled"];
[_titleField sizeToFit];
[_titleField setAutoresizingMask:CPViewWidthSizable];
[self setTitle:@""];
[_titleField setStringValue:@""];
[_titleField setFrame:CGRectMake(_titleMargin, 3.0, CGRectGetWidth([self bounds]) - 2 * _titleMargin, CGRectGetHeight([_titleField frame]))];
[_titleField setFrame:CGRectMake(20.0, 3.0, CGRectGetWidth([self bounds]) - 40.0, CGRectGetHeight([_titleField frame]))];
[self addSubview:_titleField];
@@ -90,8 +83,6 @@
- (void)setTitle:(CPString)aTitle
{
[_titleField setStringValue:aTitle];
_minimumTitleFieldSize = [_titleField _minimumFrameSize].width;
}
- (void)tile
@@ -104,7 +95,7 @@
// The vertical alignment of the title is set by the theme, so just give it all available space. By default
// the title will vertically centre within.
[_titleField setFrame:CGRectMake(_titleMargin, 0, width - 2 * _titleMargin, _titleBarHeight)];
[_titleField setFrame:CGRectMake(20.0, 0, width - 40.0, [[self class] titleBarHeight])];
}
- (void)layoutSubviews
+40 -33
View File
@@ -25,8 +25,8 @@
@implementation _CPToolTipWindowView : _CPWindowView
{
BOOL _mouseDownPressed @accessors(getter=isMouseDownPressed, setter=setMouseDownPressed:);
unsigned _gravity @accessors(property=gravity);
BOOL _mouseDownPressed @accessors(getter=isMouseDownPressed, setter=setMouseDownPressed:);
unsigned _gravity @accessors(property=gravity);
}
@@ -41,30 +41,21 @@
+ (CPDictionary)themeAttributes
{
return @{
// 1. The DOM-based CSS rendering attributes.
// _CPWindowView will natively apply this to the outer window bounds.
@"bezel-color":[CPColor colorWithCSSDictionary:@{
@"background-color": @"#FFFFCA",
@"border": @"1px solid #B0B0B0",
@"border-radius": @"2px",
@"box-sizing": @"border-box",
@"box-shadow": @"0px 1px 3px rgba(0,0,0,0.25)"
}],
@"color": [CPColor blackColor],
// 2. Legacy attributes zeroed out to satisfy the build process/theme inheritance
@"background-color": [CPColor clearColor],
@"stroke-color": [CPColor clearColor],
@"stroke-width": 0.0,
@"border-radius": 0.0
};
@"stroke-color": [CPColor colorWithHexString:@"E3E3E3"],
@"background-color": [CPColor colorWithHexString:@"FFFFCA"],
@"border-radius": 2.0,
@"stroke-width": 1.0,
@"color": [CPColor blackColor],
};
}
/*! compute the contentView frame from a given window frame
@param aFrameRect the window frame
*/
+ (CGRect)contentRectForFrameRect:(CGRect)aFrameRect
{
var contentRect = [super contentRectForFrameRect:aFrameRect];
// This pushes the text inwards so it doesn't touch the outer CSS border
contentRect.origin.x += 3;
contentRect.origin.y += 3;
contentRect.size.width -= 6;
@@ -73,34 +64,50 @@
return contentRect;
}
/*! compute the window frame from a given contentView frame
@param aContentRect the contentView frame
*/
+ (CGRect)frameRectForContentRect:(CGRect)aContentRect
{
var aFrameRect = CGRectMakeCopy(aContentRect);
aFrameRect.origin.x -= 3;
aFrameRect.origin.y -= 3;
aFrameRect.size.width += 9;
aFrameRect.size.height += 9;
aFrameRect.size.width += 6;
aFrameRect.size.height += 6;
return aFrameRect;
}
// MARK: -
// MARK: drawing
- (void)layoutSubviews
{
[super layoutSubviews];
// Apply the CSS dictionary to the standard CPView subview (contentView).
// This bypasses the _CPWindowView canvas interceptor and applies directly to the DOM.
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
}
- (void)drawRect:(CGRect)aRect
{
// Intentionally empty to disable legacy Canvas drawing.
[super drawRect:aRect];
var context = [[CPGraphicsContext currentContext] graphicsPort],
radius = [self currentValueForThemeAttribute:@"border-radius"],
strokeWidth = [self currentValueForThemeAttribute:@"stroke-width"],
strokeColor = [self currentValueForThemeAttribute:@"stroke-color"],
bgColor = [self currentValueForThemeAttribute:@"background-color"];
CGContextSetStrokeColor(context, strokeColor);
CGContextSetFillColor(context, bgColor);
CGContextSetLineWidth(context, strokeWidth);
aRect.origin.x += strokeWidth;
aRect.origin.y += strokeWidth;
aRect.size.width -= strokeWidth * 2;
aRect.size.height -= strokeWidth * 2;
var path = CGPathWithRoundedRectangleInRect(aRect, radius, radius, YES, YES, YES, YES);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGContextAddPath(context, path);
CGContextFillPath(context);
}
@end
-3
View File
@@ -61,9 +61,6 @@ task ("Theme", [$BUILD_CJS_CAPPUCCINO_APPKIT], function()
utilsFile.cp_r(path.join($BUILD_DIR, $CONFIGURATION, 'Aristo2.blend'), path.join($BUILD_PATH, 'Resources', 'Aristo2.blend'));
utilsFile.cp_r(path.join($BUILD_DIR, $CONFIGURATION, 'Aristo2.blend'), path.join($BUILD_CJS_CAPPUCCINO_APPKIT, "Resources", "Aristo2.blend"));
utilsFile.cp_r(path.join($BUILD_DIR, $CONFIGURATION, 'Aristo3.blend'), path.join($BUILD_PATH, 'Resources', 'Aristo3.blend'));
utilsFile.cp_r(path.join($BUILD_DIR, $CONFIGURATION, 'Aristo3.blend'), path.join($BUILD_CJS_CAPPUCCINO_APPKIT, "Resources", "Aristo3.blend"));
});
task ("build", ["AppKit", $BUILD_CJS_CAPPUCCINO_APPKIT, "Theme"]);
-163
View File
@@ -1,163 +0,0 @@
/*
* Aristo3Colors.j
* AppKit
*
* Created by Didier Korthoudt
* Copyright 2018 <didier.korthoudt@uliege.be>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
A3CPColorActiveText = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.85];
A3CPColorActiveTextHighlighted = [CPColor colorWithRed:255 green:255 blue:255 alpha:0.90];
A3CPColorInactiveText = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.30];
A3CPColorInactiveWhiteText = [CPColor colorWithRed: 255 green:255 blue:255 alpha:0.6];
A3CPColorDefaultText = [CPColor colorWithHexString:@"FFFFFF"];
A3CPColorActiveText65 = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.65];
A3CPColorBlack50 = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.50];
A3CPColorBlack85 = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.85];
A3CPColorBlack25 = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.25];
A3CPColorActiveBorder = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.20];
A3ColorActiveBorder = @"rgba(0,0,0,0.20)";
A3CPColorInactiveBorder = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.10];
A3ColorInactiveBorder = @"rgba(0,0,0,0.10)";
A3ColorInactiveDarkBorder = @"rgba(0,0,0,0.25)";
A3ColorNotKeyDarkBorder = @"rgba(0,0,0,0.85)";
A3ColorTransparent = @"rgba(0,0,0,0)";
A3ColorLightBackground = @"rgba(255,255,255,0.5)";
A3ColorBorderLight = @"rgb(230,230,230)";
A3ColorBorderMedium = @"rgb(213,213,213)";
A3ColorBorderDark = @"rgb(205,205,205)";
A3ColorBorderBlue = @"rgb(23,128,251)";
A3CPColorBorderBlue = [CPColor colorWithHexString:@"1780FB"];
A3CPColorBorderBlueInactive = [CPColor colorWithHexString:@"DCDCDC"];
A3ColorBorderBlueLight = @"rgb(150,200,250)";
A3ColorBorderBlueHighlighted = @"rgb(0,103,216)";
A3ColorBackground = @"rgb(236,236,236)";
A3ColorBackgroundInactive = @"rgb(240,240,240)";
A3ColorBackgroundHighlighted = @"rgb(231,231,231)";
A3ColorBackgroundWhite = @"rgb(255,255,255)";
A3ColorBackgroundDark = @"rgb(159,159,159)";
A3ColorBackgroundDarkened = @"rgba(0,0,0,0.15)";
A3ColorBackgroundLightlyDarkened = @"rgba(0,0,0,0.05)";
A3ColorButtonBackgroundHighlighted = @"rgb(191,191,191)";
A3ColorBackground90 = @"rgba(236,236,236,0.90)";
A3ColorBackground50 = @"rgba(236,236,236,0.50)";
A3ColorButtonBackgroundHighlighted80= @"rgba(191,191,191,0.80)";
A3ColorButtonBackgroundHighlighted50= @"rgba(191,191,191,0.50)";
A3ColorBackgroundDark35 = @"rgba(159,159,159,0.35)";
A3ColorBackgroundBlack50 = @"rgba(0,0,0,0.50)";
A3ColorBackgroundBlack35 = @"rgba(0,0,0,0.35)";
A3ColorBackgroundBlack20 = @"rgba(0,0,0,0.20)";
A3ColorBackgroundBlack14 = @"rgba(0,0,0,0.14)";
A3ColorBorderRed = @"rgb(192,26,25)";
A3ColorBorderRedLight = @"rgb(239,102,103)";
A3ColorBorderRedHighlighted = @"rgb(144,19,19)";
// Windows
A3ColorWindowHeadActive = @"rgb(216,216,216)";
A3ColorWindowHeadInactive = @"rgb(246,246,246)";
A3ColorWindowButtonClose = @"rgb(252,96,92)";
A3ColorWindowButtonCloseDark = @"rgb(223,72,69)";
A3ColorWindowButtonCloseLight = @"rgb(254,176,174)";
A3ColorWindowButtonMin = @"rgb(253,188,64)";
A3ColorWindowButtonMinDark = @"rgb(222,160,52)";
A3ColorWindowButtonMinLight = @"rgb(254,222,160)";
A3ColorWindowButtonZoom = @"rgb(52,200,74)";
A3ColorWindowButtonZoomDark = @"rgb(40,171,53)";
A3ColorWindowButtonZoomLight = @"rgb(154,227,164)";
A3ColorWindowButtonUnsaved = @"rgba(50,50,50,0.65)";
A3ColorWindowButtonUnsavedLight = @"rgb(100,100,100)";
A3ColorWindowButtonBackground = @"rgb(128,128,128)";
A3ColorWindowButtonBackgroundLight = @"rgb(192,192,192)";
A3ColorWindowButtonBackgroundDark = @"rgb(96,96,96)";
A3ColorWindowBorder = @"rgb(189,189,189)";
// Menus
A3ColorMenuLightBackground = @"rgb(246,246,246)";
A3ColorMenuBackground = @"rgb(206,206,206)";
A3ColorMenuCheckmark = @"rgba(0,0,0,0.85)";
A3ColorMenuBorder = @"rgba(0,0,0,0.20)";
// Textfields
A3ColorTextfieldActiveBorder = @"rgba(0,0,0,0.25)";
A3ColorTextfieldInactiveBorder = @"rgba(0,0,0,0.20)";
// Tables
A3CPColorTableRow = [CPColor whiteColor];
A3CPColorTableAlternateRow = [CPColor colorWithRed:245.0/255.0 green:245.0/255.0 blue:245.0/255.0 alpha:1.0];
A3CPColorTableDivider = [CPColor colorWithRed:214.0/255.0 green:214.0/255.0 blue:214.0/255.0 alpha:1.0];
A3ColorTableDivider = @"rgb(214,214,214)";
A3ColorTableHeaderSeparator = @"rgb(229,229,229)";
A3CPColorTableHeaderText = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.40];
A3CPColorSelectedTableHeaderText = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.70];
A3ColorTableColumnHeaderPressed = @"rgba(0,0,0,0.10)";
// Scrollers
A3ColorScrollerDark = @"rgba(0,0,0,0.50)";
A3ColorScrollerLight = @"rgba(255,255,255,0.5)";
A3ColorScrollerLegacy = @"rgba(0,0,0,0.20)";
A3ColorScrollerBackground = @"rgb(250,250,250)";
A3ColorScrollerBorder = @"rgb(232,232,232)";
// Sliders
A3ColorCircularSliderKnob = @"rgba(0,0,0,0.5)";
A3ColorSliderDisabledKnob = @"rgb(251,251,251)";
A3ColorSliderDisabledTrack = @"rgb(140,140,140)";
// Steppers
A3ColorStepperArrow = @"rgba(0,0,0,0.65)";
A3ColorHighlightedStepperArrow = @"rgba(255,255,255,1)";
// Split views
A3ColorSplitPaneDividerBackground = @"rgb(253,253,253)";
A3ColorSplitPaneDividerBorder = @"rgb(213,213,213)";
// Calendar
A3ColorCalendarButtons = @"rgb(50,50,50)";
A3ColorCalendarHighlightedButtons = @"rgb(165,165,165)";
A3CPColorCalendarDark = [CPColor colorWithRed:0 green:0 blue:0 alpha:0.35];
A3ColorCalendarDark = @"rgba(0,0,0,0.35)";
A3ColorCalendarActive = A3ColorBorderBlue;
A3ColorCalendarActiveNotKey = @"rgba(0,0,0,0.15)";
A3CPColorCalendarTitle = [CPColor colorWithRed:65/255 green:65/255 blue:65/255 alpha:1];
A3CPColorCalendarTile = A3CPColorActiveText; // [CPColor colorWithRed:40/255 green:40/255 blue:40/255 alpha:1];
A3CPColorCalendarCurrentDayTile = A3CPColorBorderBlue;
A3CPColorCalendarOutOfRangeTile = A3CPColorInactiveText;
A3CPColorCalendarSelectedTile = [CPColor whiteColor];
A3ColorCalendarBackground = @"rgb(255,255,255)";
// Disclosure triangle
A3ColorDisclosure = @"rgb(140,140,140)";
A3ColorDisclosureDisabled = @"rgb(191,191,191)";
A3ColorDisclosurePushed = @"rgb(115,115,115)";
// Square buttons
A3ColorSquareButtonBackground = @"rgb(248,248,248)";
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CPSystemFontFace</key>
<string>SFNSText, Helvetica Neue</string>
<key>CPApplicationDelegateClass</key>
<string>BKShowcaseController</string>
<key>CPBundleName</key>
<string>Aristo3</string>
<key>BKLearnMoreURL</key>
<string>http://cappuccino.org/aristo</string>
<key>BKLearnMoreButtonTitle</key>
<string>Aristo Home Page</string>
<key>CPPrincipalClass</key>
<string>CPApplication</string>
<key>CPDefaultTheme</key>
<string>Aristo3</string>
<key>CPSystemFontSize</key>
<string>13</string>
</dict>
</plist>
-40
View File
@@ -1,40 +0,0 @@
require("../../../common.jake");
const path = require("path");
var callback;
function callbackFunction(blendtask) {
callback(blendtask);
}
$BUILD_CJS_BLENDTASK = path.join($BUILD_CJS_CAPPUCCINO, "lib", "cappuccino", "jake", "blendtask.j");
var promise = new Promise((resolve, reject) => {
callback = function(BLEND_TASK) {
exports.BlendTask = BLEND_TASK.BlendTask;
exports.blend = BLEND_TASK.blend;
defineBlendTask().then(() => {
resolve();
delete exports.jakePromise;
});
}
});
require("../../../CommonJS/lib/cappuccino/jake.js").initilize(callbackFunction);
async function defineBlendTask() {
await exports.blend ("Aristo3.blend", function(aristoTask)
{
aristoTask.setBuildIntermediatesPath(path.join($BUILD_DIR, "Aristo3.build", $CONFIGURATION))
aristoTask.setBuildPath(path.join($BUILD_DIR, $CONFIGURATION));
aristoTask.setThemeDescriptors(new FileList("ThemeDescriptors.j"));
aristoTask.setIdentifier("com.280n.blend.Aristo3");
aristoTask.setResources(new FileList("Resources/*"));
});
task ("build", ["Aristo3.blend"]);
}
exports.jakePromise = promise;
-28
View File
@@ -1,28 +0,0 @@
Copyright (c) 2015, Alcatel-Lucent Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of monolithe nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-150
View File
@@ -1,150 +0,0 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<!--
index-debug.html
null
Created by You on December 12, 2012
Copyright 2012, Your Company. All rights reserved.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="apple-touch-icon" href="Resources/icon.png" />
<link rel="apple-touch-startup-image" href="Resources/default.png" />
<title>Aristo 3</title>
<script type="text/javascript">
OBJJ_MAIN_FILE = "main.j";
OBJJ_INCLUDE_PATHS = ["Frameworks/Debug", "Frameworks", "SomethingElse"];
OBJJ_COMPILER_FLAGS = ["IncludeDebugSymbols", "IncludeTypeSignatures", "SourceMap", "InlineMsgSend"];
</script>
<script type="text/javascript" src="Frameworks/Debug/Objective-J/Objective-J.js" charset="UTF-8"></script>
<script type="text/javascript">
objj_msgSend_reset();
// DEBUG OPTIONS:
// Uncomment to enable printing of backtraces on exceptions:
//objj_msgSend_decorate(objj_backtrace_decorator);
// Uncomment to supress exceptions that take place inside a message
//objj_msgSend_decorate(objj_supress_exceptions_decorator)
// Uncomment to enable runtime type checking:
//objj_msgSend_decorate(objj_typecheck_decorator);
// Uncomment (along with both above) to print backtraces on type check errors:
//objj_typecheck_prints_backtrace = true;
// Uncomment to disable the default logger (CPLogConsole if window.console exists, CPLogPopup otherwise):
//CPLogUnregister(CPLogDefault);
// Uncomment to enable a specific logger:
//CPLogRegister(CPLogConsole);
//CPLogRegister(CPLogPopup);
// Tag view DOM elements with a "data-cappuccino-view" attribute that contains
// the class name of the view that created them. Comment this or set to false to disable.
appkit_tag_dom_elements = true;
</script>
<style type="text/css">
body{margin:0; padding:0;}
#container {position: absolute; top:50%; left:50%;}
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
@font-face {
font-family: 'SFNSText';
src: local(".SFNSText-Light"), url("Resources/fonts/SFNSText-Light.woff") format("woff");
font-weight: 300
}
@font-face {
font-family: 'SFNSText';
src: local(".SFNSText-Medium"), url("Resources/fonts/SFNSText-Medium.woff") format("woff");
font-weight: 500
}
@font-face {
font-family: 'SFNSDisplay';
src: local(".SFNSDisplay-Light"), url("Resources/fonts/SFNSDisplay-Light.woff") format("woff");
font-weight: 300
}
@font-face {
font-family: 'SFNSDisplay';
src: local(".SFNSDisplay-Medium"), url("Resources/fonts/SFNSDisplay-Medium.woff") format("woff");
font-weight: 500
}
@font-face {
font-family: 'SFNSText';
src: local(".SFNSText"), url("Resources/fonts/SFNSText-Regular.woff") format("woff");
font-weight: 400
}
@font-face {
font-family: 'SFNSText';
src: local(".SFNSText-Bold"), url("Resources/fonts/SFNSText-Bold.woff") format("woff");
font-weight: 600
}
@font-face {
font-family: 'SFNSDisplay';
src: local(".SFNSDisplay"), url("Resources/fonts/SFNSDisplay-Regular.woff") format("woff");
font-weight: 400
}
@font-face {
font-family: 'SFNSDisplay';
src: local(".SFNSDisplay-Bold"), url("Resources/fonts/SFNSDisplay-Bold.woff") format("woff");
font-weight: 600
}
</style>
<!--[if lt IE 7]>
<STYLE type="text/css">
#container { position: relative; top: 50%; }
#content { position: relative;}
</STYLE>
<![endif]-->
</head>
<body style="">
<div id="cappuccino-body">
<div id="loadingcontainer" style="background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
<script type="text/javascript">
document.write("<div id='container'><p id='content'>" +
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
"Loading Aristo 3...</p></div>");
</script>
<noscript>
<div id="container">
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino.org/noscript">Show me how to enable JavaScript</a></p>
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
</ul>
</div>
</div>
</noscript>
</div>
</div>
</body>
</html>
-77
View File
@@ -1,77 +0,0 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<!--
index.html
null
Created by You on December 12, 2012
Copyright 2012, Your Company. All rights reserved.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="apple-touch-icon" href="Resources/icon.png" />
<link rel="apple-touch-startup-image" href="Resources/default.png" />
<title>Aristo 2</title>
<script type="text/javascript">
OBJJ_MAIN_FILE = "main.j";
</script>
<script type="text/javascript" src="Frameworks/Objective-J/Objective-J.js" charset="UTF-8"></script>
<style type="text/css">
body{margin:0; padding:0;}
#container {position: absolute; top:50%; left:50%;}
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
</style>
<!--[if lt IE 7]>
<STYLE type="text/css">
#container { position: relative; top: 50%; }
#content { position: relative;}
</STYLE>
<![endif]-->
</head>
<body style="">
<div id="cappuccino-body">
<div id="loadingcontainer" style="background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
<script type="text/javascript">
document.write("<div id='container'><p id='content'>" +
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
"Loading Aristo 3...</p></div>");
</script>
<noscript>
<div id="container">
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino.org/noscript">Show me how to enable JavaScript</a></p>
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
</ul>
</div>
</div>
</noscript>
</div>
</div>
</body>
</html>
-19
View File
@@ -1,19 +0,0 @@
/*
* main.j
* null
*
* Created by You on December 12, 2012
* Copyright 2012, Your Company. All rights reserved.
*/
@import <Foundation/Foundation.j>
@import <AppKit/AppKit.j>
@import <BlendKit/BlendKit.j>
@import "ThemeDescriptors.j"
function main(args, namedArgs)
{
CPApplicationMain(args, namedArgs);
}
+1 -1
View File
@@ -1,3 +1,3 @@
require("../../common.jake");
subtasks(["BlendKit", "CommonJS", "Aristo", "Aristo2", "Aristo3"], ["build", "clean", "clobber"]);
subtasks(["BlendKit", "CommonJS", "Aristo", "Aristo2"], ["build", "clean", "clobber"]);
-3
View File
@@ -44,9 +44,6 @@
var context = [[CPGraphicsContext currentContext] graphicsPort],
color = [self currentValueForThemeAttribute:@"divider-color"];
if (!color)
return;
CGContextSetLineWidth(context, 1);
CGContextSetStrokeColor(context, [self currentValueForThemeAttribute:@"divider-color"]);
+3 -36
View File
@@ -248,10 +248,6 @@ var ListColumnIdentifier = @"1";
[[_panel contentView] addSubview:_scrollView];
[_panel setInitialFirstResponder:_tableView];
// Ensure table is transparent in HUD mode so Panel background shows through
if ([_dataSource respondsToSelector:@selector(hasThemeState:)] && [_dataSource hasThemeState:CPThemeStateHUD])
[_tableView setBackgroundColor:[CPColor clearColor]];
if ([_dataSource numberOfItemsInList:self] > 0)
[_tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
else
@@ -268,36 +264,12 @@ var ListColumnIdentifier = @"1";
{
var panel = [[_CPPopUpPanel alloc] initWithContentRect:aFrame styleMask:CPBorderlessWindowMask];
// HUD Theme Styling
if ([_dataSource respondsToSelector:@selector(hasThemeState:)] && [_dataSource hasThemeState:CPThemeStateHUD])
{
// hack to set the HUD background mask to the panel but using the standard WindowView class (not the HUD one).
panel._styleMask |= CPHUDBackgroundWindowMask;
// Create the custom HUD background with dark fill, light border, and shadow
var hudBackgroundColor = [CPColor colorWithCSSDictionary:@{
@"background-color": @"rgba(30, 30, 30, 0.95)",
@"border-color": @"rgba(255, 255, 255, 0.3)",
@"border-style": @"solid",
@"border-width": @"1px",
@"border-radius": @"6px",
@"box-shadow": @"0 5px 15px rgba(0,0,0,0.6)",
@"box-sizing": @"border-box"
}];
[panel setBackgroundColor:hudBackgroundColor];
}
else
{
// Standard Styling
[panel setHasShadow:YES];
[panel setShadowStyle:CPMenuWindowShadowStyle];
}
[panel setTitle:@""];
[panel setFloatingPanel:YES];
[panel setBecomesKeyOnlyIfNeeded:YES];
[panel setLevel:CPPopUpMenuWindowLevel];
[panel setHasShadow:YES];
[panel setShadowStyle:CPMenuWindowShadowStyle];
[panel setDelegate:self];
return panel;
@@ -341,12 +313,7 @@ var ListColumnIdentifier = @"1";
{
var scroll = [[CPScrollView alloc] initWithFrame:aFrame];
// Remove border for HUD to avoid double borders (Window Border + ScrollView Border)
if ([_dataSource respondsToSelector:@selector(hasThemeState:)] && [_dataSource hasThemeState:CPThemeStateHUD])
[scroll setBorderType:CPNoBorder];
else
[scroll setBorderType:CPLineBorder];
[scroll setBorderType:CPLineBorder];
[scroll setAutohidesScrollers:NO];
[scroll setHasVerticalScroller:YES];
[scroll setHasHorizontalScroller:NO];
+5 -20
View File
@@ -118,9 +118,13 @@ var _CPToolTipHeight = 24.0,
textFrameSizeSingleLine = [aText sizeWithFont:font],
textFrameSize = [aText sizeWithFont:font inWidth:(aToolTipSize.width)];
// this small adjustement fixes
// tooltips wrapping issues from fractional pixels.
textFrameSizeSingleLine.width += 1;
textFrameSize.width += 1;
// If the text fully fits within the maximum width, shrink to fit.
if (textFrameSizeSingleLine.width < aToolTipSize.width)
{
var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()],
@@ -133,14 +137,7 @@ var _CPToolTipHeight = 24.0,
if (textFrameSize.height < 100)
{
aToolTipSize.height = textFrameSize.height + 4;
// FIX: Snap to whole pixels
aToolTipSize.width = CEIL(aToolTipSize.width);
aToolTipSize.height = CEIL(aToolTipSize.height);
textFrameSize.width = CEIL(textFrameSize.width);
textFrameSize.height = CEIL(textFrameSize.height);
return[aToolTipSize, textFrameSize];
return [aToolTipSize, textFrameSize];
}
var newWidth = aToolTipSize.width + ((parseInt(textFrameSize.height - 100) / _CPToolTipHeight) * _CPToolTipHeight);
@@ -148,12 +145,6 @@ var _CPToolTipHeight = 24.0,
aToolTipSize.width = newWidth + 2;
aToolTipSize.height = textFrameSize.height + 4;
// FIX: Snap to whole pixels
aToolTipSize.width = CEIL(aToolTipSize.width);
aToolTipSize.height = CEIL(aToolTipSize.height);
textFrameSize.width = CEIL(textFrameSize.width);
textFrameSize.height = CEIL(textFrameSize.height);
return [aToolTipSize, textFrameSize];
}
@@ -209,12 +200,6 @@ var _CPToolTipHeight = 24.0,
[self setLevel:CPStatusWindowLevel];
[self setAlphaValue:0.9];
if ([_toolTipWindow styleMask] & CPHUDBackgroundWindowMask)
{
[_content setThemeState:CPThemeStateHUD];
[_windowView setThemeState:CPThemeStateHUD];
}
[_windowView setNeedsDisplay:YES];
}
+16 -48
View File
@@ -43,6 +43,14 @@ var concat = Array.prototype.concat,
join = Array.prototype.join,
push = Array.prototype.push;
#define FORWARD_TO_CONCRETE_CLASS()\
if (self === _CPSharedPlaceholderArray)\
{\
arguments[0] = [_CPJavaScriptArray alloc];\
return objj_msgSend.apply(this, arguments);\
}\
return [super init];
/*!
@class CPArray
@brief A mutable array backed by a JavaScript Array.
@@ -125,14 +133,7 @@ var concat = Array.prototype.concat,
*/
- (id)init
{
// Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS()
// for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray.
if (self === _CPSharedPlaceholderArray)
{
arguments[0] = [_CPJavaScriptArray alloc];
return objj_msgSend.apply(this, arguments);
}
return [super init];
FORWARD_TO_CONCRETE_CLASS();
}
// Creating an Array
@@ -143,14 +144,7 @@ var concat = Array.prototype.concat,
*/
- (id)initWithArray:(CPArray)anArray
{
// Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS()
// for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray.
if (self === _CPSharedPlaceholderArray)
{
arguments[0] = [_CPJavaScriptArray alloc];
return objj_msgSend.apply(this, arguments);
}
return [super init];
FORWARD_TO_CONCRETE_CLASS();
}
/*!
@@ -163,14 +157,7 @@ var concat = Array.prototype.concat,
*/
- (id)initWithArray:(CPArray)anArray copyItems:(BOOL)shouldCopyItems
{
// Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS()
// for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray.
if (self === _CPSharedPlaceholderArray)
{
arguments[0] = [_CPJavaScriptArray alloc];
return objj_msgSend.apply(this, arguments);
}
return [super init];
FORWARD_TO_CONCRETE_CLASS();
}
/*!
@@ -178,14 +165,7 @@ var concat = Array.prototype.concat,
*/
- (id)initWithObjects:(id)anObject, ...
{
// Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS()
// for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray.
if (self === _CPSharedPlaceholderArray)
{
arguments[0] = [_CPJavaScriptArray alloc];
return objj_msgSend.apply(this, arguments);
}
return [super init];
FORWARD_TO_CONCRETE_CLASS();
}
/*!
@@ -196,27 +176,13 @@ var concat = Array.prototype.concat,
*/
- (id)initWithObjects:(CPArray)objects count:(CPUInteger)aCount
{
// Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS()
// for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray.
if (self === _CPSharedPlaceholderArray)
{
arguments[0] = [_CPJavaScriptArray alloc];
return objj_msgSend.apply(this, arguments);
}
return [super init];
FORWARD_TO_CONCRETE_CLASS();
}
// FIXME: This should be defined in CPMutableArray, not here.
- (id)initWithCapacity:(CPUInteger)aCapacity
{
// Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS()
// for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray.
if (self === _CPSharedPlaceholderArray)
{
arguments[0] = [_CPJavaScriptArray alloc];
return objj_msgSend.apply(this, arguments);
}
return [super init];
FORWARD_TO_CONCRETE_CLASS();
}
// Querying an array
@@ -1091,3 +1057,5 @@ var _CPSharedPlaceholderArray = nil;
}
@end
//@import "_CPJavaScriptArray.j"
+16 -19
View File
@@ -109,31 +109,28 @@ var CPDateReferenceDate = new Date(Date.UTC(2001, 0, 1, 0, 0, 0, 0));
}
/*!
Returns a CPDate initialized with a date and time specified by the given
string in international date format YYYY-MM-DD HH:MM:SS ±HHMM (e.g.
2009-11-17 17:52:04 +0000).
The offset is taken verbatim from the string; the result does not depend
on the host's local time zone or any CPTimeZone data. Callers needing an
offset derived from an actual IANA zone (e.g. accounting for DST) are
responsible for resolving it themselves via the browser's Intl API before
constructing the string.
Returns a CPDate initialized with a date and time specified by the given
string in international date format YYYY-MM-DD HH:MM:SS ±HHMM (e.g.
2009-11-17 17:52:04 +0000).
*/
- (id)initWithString:(CPString)description
{
var format = new RegExp("(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}) ([-+])(\\d{2})(\\d{2})"),
d = description.match(format);
var format = new RegExp("(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}) ([-+])(\\d{2})(\\d{2})"),
d = description.match(new RegExp(format));
if (!d || d.length != 10)
[CPException raise:CPInvalidArgumentException
reason:"initWithString: the string must be in YYYY-MM-DD HH:MM:SS ±HHMM format"];
if (!d || d.length != 10)
[CPException raise:CPInvalidArgumentException
reason:"initWithString: the string must be in YYYY-MM-DD HH:MM:SS ±HHMM format"];
var timeZoneOffsetMinutes = (Number(d[8]) * 60 + Number(d[9])) * (d[7] === '-' ? 1 : -1),
utcMillis = Date.UTC(Number(d[1]), Number(d[2]) - 1, Number(d[3]),
Number(d[4]), Number(d[5]), Number(d[6]));
var date = new Date(d[1], d[2] - 1, d[3]),
timeZoneOffset = (Number(d[8]) * 60 + Number(d[9])) * (d[7] === '-' ? 1 : -1);
self = new Date(utcMillis + timeZoneOffsetMinutes * 60 * 1000);
return self;
date.setHours(d[4]);
date.setMinutes(d[5]);
date.setSeconds(d[6]);
self = new Date(date.getTime() + (timeZoneOffset - date.getTimezoneOffset()) * 60 * 1000);
return self;
}
- (CPTimeInterval)timeIntervalSinceDate:(CPDate)anotherDate
Regular → Executable
+4 -9
View File
@@ -248,13 +248,8 @@ if (Error.prototype._userInfo !== null)
[CPException initialize];
// MARK: - Exception Utilities
function _CPMethodCallString(anObject, aSelector)
{
var prefix = class_isMetaClass(anObject.isa) ? "+" : "-";
return prefix + "[" + [anObject className] + " " + aSelector + "]: ";
}
#define METHOD_CALL_STRING()\
((class_isMetaClass(anObject.isa) ? "+" : "-") + "[" + [anObject className] + " " + aSelector + "]: ")
function _CPRaiseInvalidAbstractInvocation(anObject, aSelector)
{
@@ -264,13 +259,13 @@ function _CPRaiseInvalidAbstractInvocation(anObject, aSelector)
function _CPRaiseInvalidArgumentException(anObject, aSelector, aMessage)
{
[CPException raise:CPInvalidArgumentException
reason:_CPMethodCallString(anObject, aSelector) + aMessage];
reason:METHOD_CALL_STRING() + aMessage];
}
function _CPRaiseRangeException(anObject, aSelector, anIndex, aCount)
{
[CPException raise:CPRangeException
reason:_CPMethodCallString(anObject, aSelector) + "index (" + anIndex + ") beyond bounds (" + aCount + ")"];
reason:METHOD_CALL_STRING() + "index (" + anIndex + ") beyond bounds (" + aCount + ")"];
}
function _CPReportLenientDeprecation(/*Class*/ aClass, /*SEL*/ oldSelector, /*SEL*/ newSelector)
+3 -1
View File
@@ -20,6 +20,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Foundation.h"
@import "CPArray.j"
@import "CPObject.j"
@import "CPRange.j"
@@ -77,7 +79,7 @@
*/
- (id)initWithIndex:(CPInteger)anIndex
{
if (!CPIsNumeric(anIndex))
if (!_IS_NUMERIC(anIndex))
[CPException raise:CPInvalidArgumentException
reason:"Invalid index"];
-615
View File
@@ -1,615 +0,0 @@
/*
* CPLanguageModel.j
* Foundation
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
@import "CPObject.j"
@import "CPString.j"
@import "CPError.j"
@import "CPDictionary.j"
@import "CPBundle.j"
@import "CPUserDefaults.j"
// File-scoped fallback configuration parameters
var CPLanguageModelSessionFallbackServiceType = @"ollama",
CPLanguageModelSessionFallbackEndpoint = @"http://localhost:11434/api/generate",
CPLanguageModelSessionFallbackModel = @"gemma4:e4b",
CPLanguageModelSessionFallbackAPIKey = @"",
CPLanguageModelSessionFallbackAPIKeyUserDefaultKey = @"",
CPLanguageModelSessionEndorsesFallback = NO;
/*!
@ingroup foundation
@class CPSystemLanguageModel
CPSystemLanguageModel provides a standard query interface to inspect the
availability of client-side, on-device large language models (such as Gemma Nano on Chrome)
in the active web browser runtime.
*/
@implementation CPSystemLanguageModel : CPObject
var sharedInstance = nil;
/*!
Returns the singleton system language model monitor.
@return the default CPSystemLanguageModel instance
*/
+ (id)defaultModel
{
if (!sharedInstance)
sharedInstance = [[CPSystemLanguageModel alloc] init];
return sharedInstance;
}
/*!
Asynchronously queries the active browser environment to determine if on-device
language models are supported and readily available to execute prompts.
@param completionHandler a callback block executed with a boolean parameter (supported)
*/
- (void)supportsLocaleWithCompletionHandler:(Function)completionHandler
{
if (typeof window === "undefined" || !completionHandler)
{
if (completionHandler)
completionHandler(NO);
return;
}
(async function() {
var supported = false;
try {
if (window.ai && window.ai.languageModel) {
// Pass language options to align with the creation options
var options = {
expectedInputs: [{ type: "text", languages: ["en"] }],
expectedOutputs: [{ type: "text", languages: ["en"] }]
};
if (typeof window.ai.languageModel.availability === 'function') {
var avail = await window.ai.languageModel.availability(options);
supported = (avail === "readily" || avail === "available" || avail === "after-download");
} else if (typeof window.ai.languageModel.capabilities === 'function') {
var caps = await window.ai.languageModel.capabilities(options);
supported = (caps.available === "readily" || caps.available === "after-download");
} else {
supported = true;
}
}
else if (window.LanguageModel) {
supported = true;
}
} catch (e) {
supported = false;
}
completionHandler(supported);
})();
}
@end
/*!
@ingroup foundation
@class CPLanguageModelSession
CPLanguageModelSession manages an active session with a local on-device
language model. If the active browser does not support on-device models, the session
gracefully and transparently falls back to configured remote server endpoints.
@discussion
CPLanguageModelSession handles text generation prompts. If on-device AI
(like Gemma Nano) is supported by the browser, it is utilized directly.
Otherwise, or if CPLanguageModelSessionEndorsesFallback is configured to YES,
the session automatically falls back to configured network-based providers
(such as local Ollama, Groq, or OpenRouter).
Fallback configurations can be populated globally using the application's Info.plist
via the following keys:
<pre>
CPEndorseLanguageModelFallback - YES to bypass on-device models and force fallback
CPDefaultLanguageModelService - "ollama" | "groq" | "gemini" | "openrouter"
CPDefaultLanguageModelEndpoint - API Endpoint (e.g. Ollama URL)
CPDefaultLanguageModelModel - Model name string
CPDefaultLanguageModelAPIKeyUserDefaultKey - CPUserDefaults key containing the actual API token
CPDefaultLanguageModelAPIKey - Authentication token string (Unsecure direct fallback)
</pre>
*/
@implementation CPLanguageModelSession : CPObject
{
id _chromeSession @accessors(property=chromeSession);
CPString _instructions @accessors(property=instructions);
CPString _fallbackServiceType @accessors(property=fallbackServiceType);
CPString _fallbackEndpoint @accessors(property=fallbackEndpoint);
CPString _fallbackModel @accessors(property=fallbackModel);
CPString _fallbackAPIKey @accessors(property=fallbackAPIKey);
}
/*!
Initializes fallback defaults and "Endorsement" flags from the application's Info.plist.
*/
+ (void)initialize
{
if (self === [CPLanguageModelSession class])
{
var bundle = [CPBundle mainBundle];
CPLanguageModelSessionEndorsesFallback = !![bundle objectForInfoDictionaryKey:@"CPEndorseLanguageModelFallback"];
CPLanguageModelSessionFallbackServiceType = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelService"] || @"ollama";
CPLanguageModelSessionFallbackEndpoint = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelEndpoint"] || @"http://localhost:11434/api/generate";
CPLanguageModelSessionFallbackModel = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelModel"] || @"gemma4:e4b";
CPLanguageModelSessionFallbackAPIKey = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelAPIKey"] || @"";
CPLanguageModelSessionFallbackAPIKeyUserDefaultKey = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelAPIKeyUserDefaultKey"] || @"";
}
}
/*!
Configures whether the session should bypass native browser AI models and force fallback network routing.
@param shouldEndorse YES to bypass native AI; NO to prioritize native AI if available
*/
+ (void)setEndorsesFallback:(BOOL)shouldEndorse
{
CPLanguageModelSessionEndorsesFallback = shouldEndorse;
}
/*!
Indicates if the session bypasses native browser AI models.
@return YES if forced fallback is active; NO otherwise
*/
+ (BOOL)endorsesFallback
{
return CPLanguageModelSessionEndorsesFallback;
}
/*!
Configures fallback details dynamically, overriding any defaults loaded from Info.plist.
@param serviceType the service type (e.g. @"ollama", @"groq", @"gemini", @"openrouter")
@param endpoint the network target URL
@param model the model identifier
@param apiKey the API key string
*/
+ (void)setFallbackServiceType:(CPString)serviceType endpoint:(CPString)endpoint model:(CPString)model apiKey:(CPString)apiKey
{
CPLanguageModelSessionFallbackServiceType = serviceType;
CPLanguageModelSessionFallbackEndpoint = endpoint;
CPLanguageModelSessionFallbackModel = model;
CPLanguageModelSessionFallbackAPIKey = apiKey;
}
/*!
Configures the CPUserDefaults key used to dynamically look up the API key.
@param keyName the user defaults key name containing the actual credentials
*/
+ (void)setFallbackAPIKeyUserDefaultKey:(CPString)keyName
{
CPLanguageModelSessionFallbackAPIKeyUserDefaultKey = keyName;
}
/*!
Gets the CPUserDefaults key name used to dynamically look up the API key.
@return the user defaults key name
*/
+ (CPString)fallbackAPIKeyUserDefaultKey
{
return CPLanguageModelSessionFallbackAPIKeyUserDefaultKey;
}
/*!
Initializes a language model session with specific system instructions.
@param instructions the system instructions or context prompt
@return the initialized session
*/
- (id)initWithInstructions:(CPString)instructions
{
self = [super init];
if (self)
{
_instructions = instructions;
_chromeSession = nil;
_fallbackServiceType = nil;
_fallbackEndpoint = nil;
_fallbackModel = nil;
_fallbackAPIKey = nil;
}
return self;
}
/*!
Initializes a language model session with specific system instructions and an explicit programmatic API key.
@param instructions the system instructions or context prompt
@param apiKey the fallback API key to use specifically for this session
@return the initialized session
*/
- (id)initWithInstructions:(CPString)instructions apiKey:(CPString)apiKey
{
self = [self initWithInstructions:instructions];
if (self)
{
_fallbackAPIKey = apiKey;
}
return self;
}
/*!
Initializes a language model session with instructions and explicit fallback settings.
@param instructions the system instructions or context prompt
@param options dictionary containing custom fallback configuration (e.g. @{ @"serviceType": ..., @"apiKey": ... })
@return the initialized session
*/
- (id)initWithInstructions:(CPString)instructions fallbackOptions:(CPDictionary)options
{
self = [self initWithInstructions:instructions];
if (self)
{
if (options)
{
_fallbackServiceType = [options objectForKey:@"serviceType"];
_fallbackEndpoint = [options objectForKey:@"endpoint"];
_fallbackModel = [options objectForKey:@"model"];
_fallbackAPIKey = [options objectForKey:@"apiKey"];
}
}
return self;
}
/*!
Sends a query prompt to the language model session.
@param prompt the query text to analyze
@param completionHandler a callback receiving the response string or a CPError instance
*/
- (void)respondToPrompt:(CPString)prompt options:(id)options completionHandler:(Function)completionHandler
{
// If the developer forced fallback, bypass native browser execution
if (CPLanguageModelSessionEndorsesFallback)
{
[self _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler];
return;
}
if (_chromeSession)
{
[self _executePrompt:prompt options:options completionHandler:completionHandler];
return;
}
var instructions = [self instructions];
[CPLanguageModelSession _getChromeFactoryWithCompletionHandler:function(factory, error) {
if (error) {
[self _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler];
return;
}
// Add the required expected input and output parameters
var sessionOptions = {
expectedInputs: [{ type: "text", languages: ["en"] }],
expectedOutputs: [{ type: "text", languages: ["en"] }]
};
if (instructions) {
sessionOptions.systemPrompt = instructions;
}
factory.create(sessionOptions).then(function(session) {
[self setChromeSession:session];
[self _executePrompt:prompt options:options completionHandler:completionHandler];
}).catch(function(err) {
[self _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler];
});
}];
}
/*!
Sends a query prompt and streams the response chunk-by-chunk for live UI rendering.
@param prompt the query text to analyze
@param chunkHandler a callback block executed as text increments are received
@param completionHandler a final callback block executed when generation completes
*/
- (void)respondToPrompt:(CPString)prompt
onChunkReceived:(Function)chunkHandler
completed:(Function)completionHandler
{
if (CPLanguageModelSessionEndorsesFallback)
{
[self _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) {
if (!err && chunkHandler)
chunkHandler(res);
completionHandler(res, err);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
}];
return;
}
if (_chromeSession)
{
[self _executePromptStreaming:prompt onChunkReceived:chunkHandler completed:completionHandler];
return;
}
var instructions = [self instructions];
[CPLanguageModelSession _getChromeFactoryWithCompletionHandler:function(factory, error) {
if (error) {
[self _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) {
if (!err && chunkHandler)
chunkHandler(res);
completionHandler(res, err);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
}];
return;
}
// Add the required expected input and output parameters
var options = {
expectedInputs: [{ type: "text", languages: ["en"] }],
expectedOutputs: [{ type: "text", languages: ["en"] }]
};
if (instructions)
options.systemPrompt = instructions;
factory.create(options).then(function(session) {
[self setChromeSession:session];
[self _executePromptStreaming:prompt onChunkReceived:chunkHandler completed:completionHandler];
}).catch(function(err)
{
[self _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) {
if (!err && chunkHandler)
chunkHandler(res);
completionHandler(res, err);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
}];
});
}];
}
/*!
Closes the session and releases associated memory resources on-device.
*/
- (void)destroy
{
if (_chromeSession && typeof _chromeSession.destroy === "function")
{
_chromeSession.destroy();
_chromeSession = nil;
}
}
// MARK: - Private Helper Methods
+ (void)_getChromeFactoryWithCompletionHandler:(Function)completionHandler
{
if (typeof window === "undefined")
{
var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain" code:-1 userInfo:[CPDictionary dictionaryWithObject:@"Execution environment is not a browser window." forKey:CPLocalizedDescriptionKey]];
completionHandler(nil, cpError);
return;
}
if (window.ai && window.ai.languageModel)
completionHandler(window.ai.languageModel, nil);
else if (window.LanguageModel)
completionHandler(window.LanguageModel, nil);
else
completionHandler(nil, [CPError errorWithDomain:@"CPLanguageModelErrorDomain" code:0 userInfo:nil]);
}
- (void)_executePrompt:(CPString)prompt options:(id)options completionHandler:(Function)completionHandler
{
var promptPromise = options ? _chromeSession.prompt(prompt, options) : _chromeSession.prompt(prompt);
promptPromise.then(function(result) {
completionHandler(result, nil);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
}).catch(function(err) {
var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain"
code:2
userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]];
completionHandler(nil, cpError);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
});
}
- (CPString)_resolvedFallbackServiceType
{
return _fallbackServiceType || CPLanguageModelSessionFallbackServiceType;
}
- (CPString)_resolvedFallbackEndpoint
{
return _fallbackEndpoint || CPLanguageModelSessionFallbackEndpoint;
}
- (CPString)_resolvedFallbackModel
{
return _fallbackModel || CPLanguageModelSessionFallbackModel;
}
- (CPString)_resolvedFallbackAPIKey
{
// 1. Session instance explicit key has highest priority
if (_fallbackAPIKey)
return _fallbackAPIKey;
// 2. Class fallback API key set programmatically takes second priority
if (CPLanguageModelSessionFallbackAPIKey)
return CPLanguageModelSessionFallbackAPIKey;
// 3. Dynamic lookup from standard user defaults takes final priority
if (CPLanguageModelSessionFallbackAPIKeyUserDefaultKey)
{
var defaults = [CPUserDefaults standardUserDefaults],
apiKey = [defaults objectForKey:CPLanguageModelSessionFallbackAPIKeyUserDefaultKey];
if (apiKey)
return apiKey;
}
return @"";
}
- (void)_executeRemoteFallbackWithPrompt:(CPString)prompt options:(id)options completionHandler:(Function)completionHandler
{
var systemPrompt = [self instructions],
serviceType = [self _resolvedFallbackServiceType],
endpoint = [self _resolvedFallbackEndpoint],
model = [self _resolvedFallbackModel],
apiKey = [self _resolvedFallbackAPIKey];
var reqUrl = @"",
headers = { "Content-Type": "application/json" },
payload = {};
if (serviceType === @"groq")
{
reqUrl = "https://api.groq.com/openai/v1/chat/completions";
headers["Authorization"] = "Bearer " + apiKey;
payload = {
"model": model,
"messages": [
{ "role": "system", "content": systemPrompt },
{ "role": "user", "content": prompt }
],
"temperature": 0
};
}
else if (serviceType === @"gemini")
{
reqUrl = "https://generativelanguage.googleapis.com/v1beta/models/" + model + ":generateContent?key=" + apiKey;
payload = {
"contents": [
{ "parts": [{ "text": systemPrompt + "\n\n" + prompt }] }
],
"generationConfig": { "temperature": 0 }
};
}
else if (serviceType === @"openrouter")
{
reqUrl = "https://openrouter.ai/api/v1/chat/completions";
headers["Authorization"] = "Bearer " + apiKey;
payload = {
"model": model,
"messages": [
{ "role": "system", "content": systemPrompt },
{ "role": "user", "content": prompt }
],
"temperature": 0
};
}
else
{
reqUrl = endpoint || "http://localhost:11434/api/generate";
payload = {
"model": model,
"prompt": systemPrompt + "\n\n" + prompt,
"stream": false,
"options": { "temperature": 0 }
};
}
fetch(reqUrl, {
method: 'POST',
headers: headers,
body: JSON.stringify(payload)
})
.then(function(response) {
if (!response.ok) {
throw new Error("HTTP error! Status: " + response.status);
}
return response.json();
})
.then(function(data) {
var responseText = "";
if (serviceType === "groq" || serviceType === "openrouter") {
responseText = (data.choices && data.choices[0] && data.choices[0].message) ? data.choices[0].message.content : "";
} else if (serviceType === "gemini") {
responseText = (data.candidates && data.candidates[0] && data.candidates[0].content && data.candidates[0].content.parts) ? data.candidates[0].content.parts[0].text : "";
} else {
responseText = data.response || "";
}
completionHandler(responseText, nil);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
})
.catch(function(err) {
var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain"
code:4
userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]];
completionHandler(nil, cpError);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
});
}
- (void)_executePromptStreaming:(CPString)prompt
onChunkReceived:(Function)chunkHandler
completed:(Function)completionHandler
{
var stream;
try {
stream = _chromeSession.promptStreaming(prompt);
} catch (err) {
var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain"
code:3
userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]];
completionHandler(nil, cpError);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
return;
}
(async function() {
var lastChunk = "";
try {
for await (const chunk of stream) {
lastChunk = chunk;
if (chunkHandler) {
chunkHandler(chunk);
}
}
if (completionHandler)
{
completionHandler(lastChunk, nil);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
}
} catch (err) {
if (completionHandler) {
var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain"
code:2
userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]];
completionHandler(nil, cpError);
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update
}
}
})();
}
@end
+4 -18
View File
@@ -105,31 +105,17 @@
// MARK: Creating a Dictionary Representation
/*!
Returns a dictionary representation of the map table.
Note: This will only work correctly if all keys are strings.
Returns a dictionary representation of the map table.
Note: This will only work correctly if all keys are strings.
@return A CPDictionary containing the entries of the map table.
@return A CPDictionary containing the entries of the map table.
*/
- (CPDictionary)dictionaryRepresentation
{
var dictionary = [CPDictionary dictionary];
// TODO: Revert to ES6 destructuring in the loop declaration once the
// legacy compiler is retired. The legacy parser fails to recognize
// `var [key, value]` as a local scope declaration, causing the variables
// to leak to the global object and emitting false-positive "uninitialized
// global variable" warnings, which is unacceptable for CI hygiene.
//
// for (var [key, value] of _map.entries())
// {
// [dictionary setObject:value forKey:key];
// }
for (var entry of _map.entries())
for (var [key, value] of _map.entries())
{
var key = entry[0],
value = entry[1];
[dictionary setObject:value forKey:key];
}
+61 -42
View File
@@ -25,22 +25,27 @@
@import "CPObject.j"
@import "CPObjJRuntime.j"
// MODIFICATION: Added FIXME to highlight global mutable state anti-pattern.
// FIXME: Anti-pattern: Global Mutable State. This dictionary tracks UIDs for primitives
// and grows indefinitely in long-running processes, causing memory leaks.
const CPNumberUIDs = new CFMutableDictionary();
#define CAST_TO_INT(x) ((x) >= 0 ? Math.floor((x)) : Math.ceil((x)))
var CPNumberUIDs = new CFMutableDictionary();
/*!
@class CPNumber
@ingroup foundation
@brief A bridged object to native Javascript numbers.
*/
@class CPNumber
@ingroup foundation
@brief A bridged object to native Javascript numbers.
This class primarily exists for source compatibility. The JavaScript
\c Number type can be changed on the fly based on context,
so there is no need to call any of these methods.
In other words, native JavaScript numbers are bridged to CPNumber,
so you can use them interchangeably (including operators and methods).
*/
@implementation CPNumber : CPObject
+ (id)alloc
{
// MODIFICATION: Replaced 'var' with 'let' for block scoping.
let result = new Number();
var result = new Number();
result.isa = [self class];
return result;
}
@@ -105,7 +110,12 @@ const CPNumberUIDs = new CFMutableDictionary();
{
return anUnsignedLong;
}
/*
+ (id)numberWithUnsignedLongLong:(unsigned long long)anUnsignedLongLong
{
return anUnsignedLongLong;
}
*/
+ (id)numberWithUnsignedShort:(unsigned short)anUnsignedShort
{
return anUnsignedShort;
@@ -171,7 +181,12 @@ const CPNumberUIDs = new CFMutableDictionary();
{
return anUnsignedLong;
}
/*
- (id)initWithUnsignedLongLong:(unsigned long long)anUnsignedLongLong
{
return anUnsignedLongLong;
}
*/
- (id)initWithUnsignedShort:(unsigned short)anUnsignedShort
{
return anUnsignedShort;
@@ -179,8 +194,7 @@ const CPNumberUIDs = new CFMutableDictionary();
- (CPString)UID
{
// MODIFICATION: Replaced 'var' with 'let' for block scoping.
let UID = CPNumberUIDs.valueForKey(self);
var UID = CPNumberUIDs.valueForKey(self);
if (!UID)
{
@@ -193,13 +207,18 @@ const CPNumberUIDs = new CFMutableDictionary();
- (BOOL)boolValue
{
// MODIFICATION: Replaced conditional logic with double-not operator for strict boolean coercion.
return !!self;
// Ensure we return actual booleans.
return self ? true : false;
}
// MODIFICATION: Added FIXME to highlight unimplemented feature.
// FIXME: Unimplemented Feature. CPDecimal is not natively supported.
// This should either be removed or throw a proper CPInvalidArgumentException.
- (char)charValue
{
return String.fromCharCode(self);
}
/*
FIXME: Do we need this?
*/
- (CPDecimal)decimalValue
{
throw new Error("decimalValue: NOT YET IMPLEMENTED");
@@ -207,8 +226,10 @@ const CPNumberUIDs = new CFMutableDictionary();
- (CPString)descriptionWithLocale:(CPDictionary)aDictionary
{
// MODIFICATION: Removed hostile runtime Error throw. Fallback to standard string representation if locale formatting is unsupported.
return self.toString();
if (!aDictionary)
return self.toString();
throw new Error("descriptionWithLocale: NOT YET IMPLEMENTED");
}
- (CPString)description
@@ -234,32 +255,27 @@ const CPNumberUIDs = new CFMutableDictionary();
- (int)intValue
{
// MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc().
return Math.trunc(self);
return CAST_TO_INT(self);
}
- (int)integerValue
{
// MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc().
return Math.trunc(self);
return CAST_TO_INT(self);
}
- (long long)longLongValue
{
// MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc().
return Math.trunc(self);
return CAST_TO_INT(self);
}
- (long)longValue
{
// MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc().
return Math.trunc(self);
return CAST_TO_INT(self);
}
- (short)shortValue
{
// MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc().
return Math.trunc(self);
return CAST_TO_INT(self);
}
- (CPString)stringValue
@@ -275,22 +291,25 @@ const CPNumberUIDs = new CFMutableDictionary();
- (unsigned int)unsignedIntValue
{
// Despite the name this method does not make a negative value positive in Objective-C, so neither does it here.
// MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc().
return Math.trunc(self);
return CAST_TO_INT(self);
}
/*
- (unsigned long long)unsignedLongLongValue
{
if (typeof self == "boolean") return self ? 1 : 0;
return self;
}
*/
- (unsigned long)unsignedLongValue
{
// Despite the name this method does not make a negative value positive in Objective-C, so neither does it here.
// MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc().
return Math.trunc(self);
return CAST_TO_INT(self);
}
- (unsigned short)unsignedShortValue
{
// Despite the name this method does not make a negative value positive in Objective-C, so neither does it here.
// MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc().
return Math.trunc(self);
return CAST_TO_INT(self);
}
- (CPComparisonResult)compare:(CPNumber)aNumber
@@ -330,8 +349,8 @@ const CPNumberUIDs = new CFMutableDictionary();
if (Number.prototype.isa !== CPNumber)
{
Object.defineProperties(Number.prototype,
{
isa:
{
isa:
{
value: CPNumber,
enumerable: false,
@@ -342,8 +361,8 @@ if (Number.prototype.isa !== CPNumber)
if (Boolean.prototype.isa !== CPNumber)
{
Object.defineProperties(Boolean.prototype,
{
isa:
{
isa:
{
value: CPNumber,
enumerable: false,
+9 -28
View File
@@ -43,6 +43,8 @@ CPNumberFormatterRoundHalfUp = CPRoundPlain;
var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?');
#define SET_NEEDS_NUMBER_HANDLER_UPDATE() _numberHandler = nil
/*!
@ingroup foundation
@@ -230,19 +232,13 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?');
case CPNumberFormatterDecimalStyle:
_minimumFractionDigits = 0;
_maximumFractionDigits = 3;
// Invalidate the cached number handler.
// It rebuilds on next use.
// Replaces pre-processor directive, which is incompatible with the new compiler
_numberHandler = nil;
SET_NEEDS_NUMBER_HANDLER_UPDATE();
break;
case CPNumberFormatterCurrencyStyle:
_minimumFractionDigits = 2;
_maximumFractionDigits = 2;
// Invalidate the cached number handler.
// It rebuilds on next use.
// Replaces pre-processor directive, which is incompatible with the new compiler
_numberHandler = nil;
SET_NEEDS_NUMBER_HANDLER_UPDATE();
break;
}
}
@@ -250,46 +246,31 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?');
- (void)setRoundingMode:(CPNumberFormatterRoundingMode)aRoundingMode
{
_roundingMode = aRoundingMode;
// Invalidate the cached number handler.
// It rebuilds on next use.
// Replaces pre-processor directive, which is incompatible with the new compiler
_numberHandler = nil;
SET_NEEDS_NUMBER_HANDLER_UPDATE();
}
- (void)setMinimumFractionDigits:(CPUInteger)aNumber
{
_minimumFractionDigits = aNumber;
// Invalidate the cached number handler.
// It rebuilds on next use.
// Replaces pre-processor directive, which is incompatible with the new compiler
_numberHandler = nil;
SET_NEEDS_NUMBER_HANDLER_UPDATE();
}
- (void)setMaximumFractionDigits:(CPUInteger)aNumber
{
_maximumFractionDigits = aNumber;
// Invalidate the cached number handler.
// It rebuilds on next use.
// Replaces pre-processor directive, which is incompatible with the new compiler
_numberHandler = nil;
SET_NEEDS_NUMBER_HANDLER_UPDATE();
}
- (void)setMinimum:(CPUInteger)aNumber
{
_minimum = aNumber;
// Invalidate the cached number handler.
// It rebuilds on next use.
// Replaces pre-processor directive, which is incompatible with the new compiler
_numberHandler = nil;
SET_NEEDS_NUMBER_HANDLER_UPDATE();
}
- (void)setMaximum:(CPUInteger)aNumber
{
_maximum = aNumber;
// Invalidate the cached number handler.
// It rebuilds on next use.
// Replaces pre-processor directive, which is incompatible with the new compiler
_numberHandler = nil;
SET_NEEDS_NUMBER_HANDLER_UPDATE();
}
// MARK: Private
+370 -370
View File
@@ -23,7 +23,7 @@
*/
- (id)initWithCapacity:(unsigned)aCapacity
{
return [self init];
return [self init];
}
/*!
@@ -32,7 +32,7 @@
*/
+ (id)setWithCapacity:(CPUInteger)aCapacity
{
return [[self alloc] initWithCapacity:aCapacity];
return [[self alloc] initWithCapacity:aCapacity];
}
/*!
@@ -41,16 +41,16 @@
*/
- (void)filterUsingPredicate:(CPPredicate)aPredicate
{
var object,
objectEnumerator = [self objectEnumerator];
var object,
objectEnumerator = [self objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
{
if (![aPredicate evaluateWithObject:object])
{
[self removeObject:object];
}
}
while ((object = [objectEnumerator nextObject]) != nil)
{
if (![aPredicate evaluateWithObject:object])
{
[self removeObject:object];
}
}
}
/*!
@@ -59,7 +59,7 @@
*/
- (void)removeObject:(id)anObject
{
_CPRaiseInvalidAbstractInvocation(self, _cmd);
_CPRaiseInvalidAbstractInvocation(self, _cmd);
}
/*!
@@ -68,13 +68,13 @@
*/
- (void)removeObjectsInArray:(CPArray)anArray
{
var index = 0,
count = [anArray count];
var index = 0,
count = [anArray count];
for (; index < count; ++index)
{
[self removeObject:[anArray objectAtIndex:index]];
}
for (; index < count; ++index)
{
[self removeObject:[anArray objectAtIndex:index]];
}
}
/*!
@@ -82,13 +82,13 @@
*/
- (void)removeAllObjects
{
var object,
objectEnumerator = [self objectEnumerator];
var object,
objectEnumerator = [self objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
{
[self removeObject:object];
}
while ((object = [objectEnumerator nextObject]) != nil)
{
[self removeObject:object];
}
}
/*!
@@ -97,12 +97,12 @@
*/
- (void)addObjectsFromArray:(CPArray)objects
{
var count = [objects count];
var count = [objects count];
while (count--)
{
[self addObject:objects[count]];
}
while (count--)
{
[self addObject:objects[count]];
}
}
/*!
@@ -111,13 +111,13 @@
*/
- (void)unionSet:(CPSet)aSet
{
var object,
objectEnumerator = [aSet objectEnumerator];
var object,
objectEnumerator = [aSet objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
{
[self addObject:object];
}
while ((object = [objectEnumerator nextObject]) != nil)
{
[self addObject:object];
}
}
/*!
@@ -126,13 +126,13 @@
*/
- (void)minusSet:(CPSet)aSet
{
var object,
objectEnumerator = [aSet objectEnumerator];
var object,
objectEnumerator = [aSet objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
{
[self removeObject:object];
}
while ((object = [objectEnumerator nextObject]) != nil)
{
[self removeObject:object];
}
}
/*!
@@ -141,24 +141,24 @@
*/
- (void)intersectSet:(CPSet)aSet
{
var object,
objectEnumerator = [self objectEnumerator],
objectsToRemove = [];
var object,
objectEnumerator = [self objectEnumerator],
objectsToRemove = [];
while ((object = [objectEnumerator nextObject]) != nil)
{
if (![aSet containsObject:object])
{
objectsToRemove.push(object);
}
}
while ((object = [objectEnumerator nextObject]) != nil)
{
if (![aSet containsObject:object])
{
objectsToRemove.push(object);
}
}
var count = [objectsToRemove count];
var count = [objectsToRemove count];
while (count--)
{
[self removeObject:objectsToRemove[count]];
}
while (count--)
{
[self removeObject:objectsToRemove[count]];
}
}
/*!
@@ -167,8 +167,8 @@
*/
- (void)setSet:(CPSet)aSet
{
[self removeAllObjects];
[self unionSet:aSet];
[self removeAllObjects];
[self unionSet:aSet];
}
@end
@@ -180,69 +180,69 @@
- (id)valueForKeyPath:(CPString)aKeyPath
{
if (!aKeyPath)
{
[self valueForUndefinedKey:@"<empty path>"];
}
if (!aKeyPath)
{
[self valueForUndefinedKey:@"<empty path>"];
}
if (aKeyPath.charAt(0) === "@")
{
var dotIndex = aKeyPath.indexOf("."),
operator,
parameter;
if (aKeyPath.charAt(0) === "@")
{
var dotIndex = aKeyPath.indexOf("."),
operator,
parameter;
if (dotIndex !== -1)
{
operator = aKeyPath.substring(1, dotIndex);
parameter = aKeyPath.substring(dotIndex + 1);
}
else
{
operator = aKeyPath.substring(1);
}
if (dotIndex !== -1)
{
operator = aKeyPath.substring(1, dotIndex);
parameter = aKeyPath.substring(dotIndex + 1);
}
else
{
operator = aKeyPath.substring(1);
}
return [_CPCollectionKVCOperator performOperation:operator withCollection:self propertyPath:parameter];
}
else
{
var valuesForKeySet = [CPSet set],
containedObject,
containedObjectValue,
containedObjectEnumerator = [self objectEnumerator];
return [_CPCollectionKVCOperator performOperation:operator withCollection:self propertyPath:parameter];
}
else
{
var valuesForKeySet = [CPSet set],
containedObject,
containedObjectValue,
containedObjectEnumerator = [self objectEnumerator];
while ((containedObject = [containedObjectEnumerator nextObject]) != nil)
{
containedObjectValue = [containedObject valueForKeyPath:aKeyPath];
while ((containedObject = [containedObjectEnumerator nextObject]) != nil)
{
containedObjectValue = [containedObject valueForKeyPath:aKeyPath];
if (containedObjectValue == nil)
{
containedObjectValue = [CPNull null];
}
if (containedObjectValue == nil)
{
containedObjectValue = [CPNull null];
}
[valuesForKeySet addObject:containedObjectValue];
}
[valuesForKeySet addObject:containedObjectValue];
}
return valuesForKeySet;
}
return valuesForKeySet;
}
}
- (id)valueForKey:(CPString)aKey
{
// If the key starts with @, it is an operator path.
// If not, it is a property path that we want applied to all members.
// In either case, valueForKeyPath: handles both scenarios correctly.
return [self valueForKeyPath:aKey];
// If the key starts with @, it is an operator path.
// If not, it is a property path that we want applied to all members.
// In either case, valueForKeyPath: handles both scenarios correctly.
return [self valueForKeyPath:aKey];
}
- (void)setValue:(id)aValue forKey:(CPString)aKey
{
var containedObject,
containedObjectEnumerator = [self objectEnumerator];
var containedObject,
containedObjectEnumerator = [self objectEnumerator];
while ((containedObject = [containedObjectEnumerator nextObject]) != nil)
{
[containedObject setValue:aValue forKey:aKey];
}
while ((containedObject = [containedObjectEnumerator nextObject]) != nil)
{
[containedObject setValue:aValue forKey:aKey];
}
}
@end
@@ -254,22 +254,22 @@
- (id)mutableSetValueForKey:(id)aKey
{
return [[_CPKVCSet alloc] initWithKey:aKey forProxyObject:self];
return [[_CPKVCSet alloc] initWithKey:aKey forProxyObject:self];
}
- (id)mutableSetValueForKeyPath:(id)aKeyPath
{
var dotIndex = aKeyPath.indexOf(".");
var dotIndex = aKeyPath.indexOf(".");
if (dotIndex < 0)
{
return [self mutableSetValueForKey:aKeyPath];
}
if (dotIndex < 0)
{
return [self mutableSetValueForKey:aKeyPath];
}
var firstPart = aKeyPath.substring(0, dotIndex),
lastPart = aKeyPath.substring(dotIndex + 1);
var firstPart = aKeyPath.substring(0, dotIndex),
lastPart = aKeyPath.substring(dotIndex + 1);
return [[self valueForKeyPath:firstPart] mutableSetValueForKeyPath:lastPart];
return [[self valueForKeyPath:firstPart] mutableSetValueForKeyPath:lastPart];
}
@end
@@ -279,387 +279,387 @@
@implementation _CPKVCSet : CPMutableSet
{
id _proxyObject;
id _key;
id _proxyObject;
id _key;
SEL _accessSEL;
Function _access;
SEL _accessSEL;
Function _access;
SEL _setSEL;
Function _set;
SEL _setSEL;
Function _set;
SEL _countSEL;
Function _count;
SEL _countSEL;
Function _count;
SEL _enumeratorSEL;
Function _enumerator;
SEL _enumeratorSEL;
Function _enumerator;
SEL _memberSEL;
Function _member;
SEL _memberSEL;
Function _member;
SEL _addSEL;
Function _add;
SEL _addSEL;
Function _add;
SEL _addManySEL;
Function _addMany;
SEL _addManySEL;
Function _addMany;
SEL _removeSEL;
Function _remove;
SEL _removeSEL;
Function _remove;
SEL _removeManySEL;
Function _removeMany;
SEL _removeManySEL;
Function _removeMany;
SEL _intersectSEL;
Function _intersect;
SEL _intersectSEL;
Function _intersect;
}
+ (id)alloc
{
var set = [CPMutableSet set];
var set = [CPMutableSet set];
set.isa = self;
set.isa = self;
var ivars = class_copyIvarList(self),
count = ivars.length;
var ivars = class_copyIvarList(self),
count = ivars.length;
while (count--)
{
set[ivar_getName(ivars[count])] = nil;
}
while (count--)
{
set[ivar_getName(ivars[count])] = nil;
}
return set;
return set;
}
- (id)initWithKey:(id)aKey forProxyObject:(id)anObject
{
self = [super init];
self = [super init];
_key = aKey;
_proxyObject = anObject;
_key = aKey;
_proxyObject = anObject;
var capitalizedKey = _key.charAt(0).toUpperCase() + _key.substring(1);
var capitalizedKey = _key.charAt(0).toUpperCase() + _key.substring(1);
_accessSEL = sel_getName(_key);
if ([_proxyObject respondsToSelector:_accessSEL])
{
_access = [_proxyObject methodForSelector:_accessSEL];
}
_accessSEL = sel_getName(_key);
if ([_proxyObject respondsToSelector:_accessSEL])
{
_access = [_proxyObject methodForSelector:_accessSEL];
}
_setSEL = sel_getName(@"set" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_setSEL])
{
_set = [_proxyObject methodForSelector:_setSEL];
}
_setSEL = sel_getName(@"set" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_setSEL])
{
_set = [_proxyObject methodForSelector:_setSEL];
}
_countSEL = sel_getName(@"countOf" + capitalizedKey);
if ([_proxyObject respondsToSelector:_countSEL])
{
_count = [_proxyObject methodForSelector:_countSEL];
}
_countSEL = sel_getName(@"countOf" + capitalizedKey);
if ([_proxyObject respondsToSelector:_countSEL])
{
_count = [_proxyObject methodForSelector:_countSEL];
}
_enumeratorSEL = sel_getName(@"enumeratorOf" + capitalizedKey);
if ([_proxyObject respondsToSelector:_enumeratorSEL])
{
_enumerator = [_proxyObject methodForSelector:_enumeratorSEL];
}
_enumeratorSEL = sel_getName(@"enumeratorOf" + capitalizedKey);
if ([_proxyObject respondsToSelector:_enumeratorSEL])
{
_enumerator = [_proxyObject methodForSelector:_enumeratorSEL];
}
_memberSEL = sel_getName(@"memberOf" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_memberSEL])
{
_member = [_proxyObject methodForSelector:_memberSEL];
}
_memberSEL = sel_getName(@"memberOf" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_memberSEL])
{
_member = [_proxyObject methodForSelector:_memberSEL];
}
_addSEL = sel_getName(@"add" + capitalizedKey + "Object:");
if ([_proxyObject respondsToSelector:_addSEL])
{
_add = [_proxyObject methodForSelector:_addSEL];
}
_addSEL = sel_getName(@"add" + capitalizedKey + "Object:");
if ([_proxyObject respondsToSelector:_addSEL])
{
_add = [_proxyObject methodForSelector:_addSEL];
}
_addManySEL = sel_getName(@"add" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_addManySEL])
{
_addMany = [_proxyObject methodForSelector:_addManySEL];
}
_addManySEL = sel_getName(@"add" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_addManySEL])
{
_addMany = [_proxyObject methodForSelector:_addManySEL];
}
_removeSEL = sel_getName(@"remove" + capitalizedKey + "Object:");
if ([_proxyObject respondsToSelector:_removeSEL])
{
_remove = [_proxyObject methodForSelector:_removeSEL];
}
_removeSEL = sel_getName(@"remove" + capitalizedKey + "Object:");
if ([_proxyObject respondsToSelector:_removeSEL])
{
_remove = [_proxyObject methodForSelector:_removeSEL];
}
_removeManySEL = sel_getName(@"remove" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_removeManySEL])
{
_removeMany = [_proxyObject methodForSelector:_removeManySEL];
}
_removeManySEL = sel_getName(@"remove" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_removeManySEL])
{
_removeMany = [_proxyObject methodForSelector:_removeManySEL];
}
_intersectSEL = sel_getName(@"intersect" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_intersectSEL])
{
_intersect = [_proxyObject methodForSelector:_intersectSEL];
}
_intersectSEL = sel_getName(@"intersect" + capitalizedKey + ":");
if ([_proxyObject respondsToSelector:_intersectSEL])
{
_intersect = [_proxyObject methodForSelector:_intersectSEL];
}
return self;
return self;
}
- (id)_representedObject
{
if (_access)
{
return _access(_proxyObject, _accessSEL);
}
if (_access)
{
return _access(_proxyObject, _accessSEL);
}
return [_proxyObject valueForKey:_key];
return [_proxyObject valueForKey:_key];
}
- (void)_setRepresentedObject:(id)anObject
{
if (_set)
{
return _set(_proxyObject, _setSEL, anObject);
}
if (_set)
{
return _set(_proxyObject, _setSEL, anObject);
}
[_proxyObject setValue:anObject forKey:_key];
[_proxyObject setValue:anObject forKey:_key];
}
- (CPUInteger)count
{
if (_count)
{
return _count(_proxyObject, _countSEL);
}
if (_count)
{
return _count(_proxyObject, _countSEL);
}
return [[self _representedObject] count];
return [[self _representedObject] count];
}
- (CPEnumerator)objectEnumerator
{
if (_enumerator)
{
return _enumerator(_proxyObject, _enumeratorSEL);
}
if (_enumerator)
{
return _enumerator(_proxyObject, _enumeratorSEL);
}
return [[self _representedObject] objectEnumerator];
return [[self _representedObject] objectEnumerator];
}
- (id)member:(id)anObject
{
if (_member)
{
return _member(_proxyObject, _memberSEL, anObject);
}
if (_member)
{
return _member(_proxyObject, _memberSEL, anObject);
}
return [[self _representedObject] member:anObject];
return [[self _representedObject] member:anObject];
}
- (void)addObject:(id)anObject
{
if (_add)
{
_add(_proxyObject, _addSEL, anObject);
}
else if (_addMany)
{
var objectSet = [CPSet setWithObject:anObject];
_addMany(_proxyObject, _addManySEL, objectSet);
}
else
{
var target = [[self _representedObject] copy];
[target addObject:anObject];
[self _setRepresentedObject:target];
}
if (_add)
{
_add(_proxyObject, _addSEL, anObject);
}
else if (_addMany)
{
var objectSet = [CPSet setWithObject:anObject];
_addMany(_proxyObject, _addManySEL, objectSet);
}
else
{
var target = [[self _representedObject] copy];
[target addObject:anObject];
[self _setRepresentedObject:target];
}
}
- (void)addObjectsFromArray:(CPArray)objects
{
if (_addMany)
{
var objectSet = [CPSet setWithArray:objects];
_addMany(_proxyObject, _addManySEL, objectSet);
}
else if (_add)
{
var object,
objectEnumerator = [objects objectEnumerator];
if (_addMany)
{
var objectSet = [CPSet setWithArray:objects];
_addMany(_proxyObject, _addManySEL, objectSet);
}
else if (_add)
{
var object,
objectEnumerator = [objects objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
{
_add(_proxyObject, _addSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target addObjectsFromArray:objects];
[self _setRepresentedObject:target];
}
while ((object = [objectEnumerator nextObject]) != nil)
{
_add(_proxyObject, _addSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target addObjectsFromArray:objects];
[self _setRepresentedObject:target];
}
}
- (void)unionSet:(CPSet)aSet
{
if (_addMany)
{
_addMany(_proxyObject, _addManySEL, aSet);
}
else if (_add)
{
var object,
objectEnumerator = [aSet objectEnumerator];
if (_addMany)
{
_addMany(_proxyObject, _addManySEL, aSet);
}
else if (_add)
{
var object,
objectEnumerator = [aSet objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
{
_add(_proxyObject, _addSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target unionSet:aSet];
[self _setRepresentedObject:target];
}
while ((object = [objectEnumerator nextObject]) != nil)
{
_add(_proxyObject, _addSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target unionSet:aSet];
[self _setRepresentedObject:target];
}
}
- (void)removeObject:(id)anObject
{
if (_remove)
{
_remove(_proxyObject, _removeSEL, anObject);
}
else if (_removeMany)
{
var objectSet = [CPSet setWithObject:anObject];
_removeMany(_proxyObject, _removeManySEL, objectSet);
}
else
{
var target = [[self _representedObject] copy];
[target removeObject:anObject];
[self _setRepresentedObject:target];
}
if (_remove)
{
_remove(_proxyObject, _removeSEL, anObject);
}
else if (_removeMany)
{
var objectSet = [CPSet setWithObject:anObject];
_removeMany(_proxyObject, _removeManySEL, objectSet);
}
else
{
var target = [[self _representedObject] copy];
[target removeObject:anObject];
[self _setRepresentedObject:target];
}
}
- (void)minusSet:(CPSet)aSet
{
if (_removeMany)
{
_removeMany(_proxyObject, _removeManySEL, aSet);
}
else if (_remove)
{
var object,
objectEnumerator = [aSet objectEnumerator];
if (_removeMany)
{
_removeMany(_proxyObject, _removeManySEL, aSet);
}
else if (_remove)
{
var object,
objectEnumerator = [aSet objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
{
_remove(_proxyObject, _removeSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target minusSet:aSet];
[self _setRepresentedObject:target];
}
while ((object = [objectEnumerator nextObject]) != nil)
{
_remove(_proxyObject, _removeSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target minusSet:aSet];
[self _setRepresentedObject:target];
}
}
- (void)removeObjectsInArray:(CPArray)objects
{
if (_removeMany)
{
var objectSet = [CPSet setWithArray:objects];
_removeMany(_proxyObject, _removeManySEL, objectSet);
}
else if (_remove)
{
var object,
objectEnumerator = [objects objectEnumerator];
if (_removeMany)
{
var objectSet = [CPSet setWithArray:objects];
_removeMany(_proxyObject, _removeManySEL, objectSet);
}
else if (_remove)
{
var object,
objectEnumerator = [objects objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
{
_remove(_proxyObject, _removeSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target removeObjectsInArray:objects];
[self _setRepresentedObject:target];
}
while ((object = [objectEnumerator nextObject]) != nil)
{
_remove(_proxyObject, _removeSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target removeObjectsInArray:objects];
[self _setRepresentedObject:target];
}
}
- (void)removeAllObjects
{
if (_removeMany)
{
var allObjectsSet = [[self _representedObject] copy];
_removeMany(_proxyObject, _removeManySEL, allObjectsSet);
}
else if (_remove)
{
var object,
objectEnumerator = [[[self _representedObject] copy] objectEnumerator];
if (_removeMany)
{
var allObjectsSet = [[self _representedObject] copy];
_removeMany(_proxyObject, _removeManySEL, allObjectsSet);
}
else if (_remove)
{
var object,
objectEnumerator = [[[self _representedObject] copy] objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
{
_remove(_proxyObject, _removeSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target removeAllObjects];
[self _setRepresentedObject:target];
}
while ((object = [objectEnumerator nextObject]) != nil)
{
_remove(_proxyObject, _removeSEL, object);
}
}
else
{
var target = [[self _representedObject] copy];
[target removeAllObjects];
[self _setRepresentedObject:target];
}
}
- (void)intersectSet:(CPSet)aSet
{
if (_intersect)
{
_intersect(_proxyObject, _intersectSEL, aSet);
}
else
{
var target = [[self _representedObject] copy];
[target intersectSet:aSet];
[self _setRepresentedObject:target];
}
if (_intersect)
{
_intersect(_proxyObject, _intersectSEL, aSet);
}
else
{
var target = [[self _representedObject] copy];
[target intersectSet:aSet];
[self _setRepresentedObject:target];
}
}
- (void)setSet:(CPSet)set
{
[self _setRepresentedObject:set];
[self _setRepresentedObject:set];
}
- (CPArray)allObjects
{
return [[self _representedObject] allObjects];
return [[self _representedObject] allObjects];
}
- (id)anyObject
{
return [[self _representedObject] anyObject];
return [[self _representedObject] anyObject];
}
- (BOOL)containsObject:(id)anObject
{
return [[self _representedObject] containsObject:anObject];
return [[self _representedObject] containsObject:anObject];
}
- (BOOL)intersectsSet:(CPSet)aSet
{
return [[self _representedObject] intersectsSet:aSet];
return [[self _representedObject] intersectsSet:aSet];
}
- (BOOL)isEqualToSet:(CPSet)aSet
{
return [[self _representedObject] isEqualToSet:aSet];
return [[self _representedObject] isEqualToSet:aSet];
}
- (id)copy
{
return [[self _representedObject] copy];
return [[self _representedObject] copy];
}
@end
+110 -110
View File
@@ -34,263 +34,263 @@
+ (id)alloc
{
if (self === [CPSet class] || self === [CPMutableSet class])
return [_CPPlaceholderSet alloc];
if (self === [CPSet class] || self === [CPMutableSet class])
return [_CPPlaceholderSet alloc];
return [super alloc];
return [super alloc];
}
+ (id)set
{
return [[self alloc] init];
return [[self alloc] init];
}
+ (id)setWithArray:(CPArray)anArray
{
return [[self alloc] initWithArray:anArray];
return [[self alloc] initWithArray:anArray];
}
+ (id)setWithObject:(id)anObject
{
return [[self alloc] initWithObjects:anObject];
return [[self alloc] initWithObjects:anObject];
}
+ (id)setWithObjects:(id)objects count:(CPUInteger)count
{
return [[self alloc] initWithObjects:objects count:count];
return [[self alloc] initWithObjects:objects count:count];
}
+ (id)setWithObjects:(id)anObject, ...
{
var argumentsArray = Array.prototype.slice.apply(arguments);
var argumentsArray = Array.prototype.slice.apply(arguments);
argumentsArray[0] = [self alloc];
argumentsArray[1] = @selector(initWithObjects:);
argumentsArray[0] = [self alloc];
argumentsArray[1] = @selector(initWithObjects:);
return objj_msgSend.apply(this, argumentsArray);
return objj_msgSend.apply(this, argumentsArray);
}
+ (id)setWithSet:(CPSet)set
{
return [[self alloc] initWithSet:set];
return [[self alloc] initWithSet:set];
}
- (id)setByAddingObject:(id)anObject
{
return [[self class] setWithArray:[[self allObjects] arrayByAddingObject:anObject]];
return [[self class] setWithArray:[[self allObjects] arrayByAddingObject:anObject]];
}
- (id)setByAddingObjectsFromSet:(CPSet)aSet
{
return [self setByAddingObjectsFromArray:[aSet allObjects]];
return [self setByAddingObjectsFromArray:[aSet allObjects]];
}
- (id)setByAddingObjectsFromArray:(CPArray)anArray
{
return [[self class] setWithArray:[[self allObjects] arrayByAddingObjectsFromArray:anArray]];
return [[self class] setWithArray:[[self allObjects] arrayByAddingObjectsFromArray:anArray]];
}
- (id)init
{
return [self initWithObjects:nil count:0];
return [self initWithObjects:nil count:0];
}
- (id)initWithArray:(CPArray)anArray
{
return [self initWithObjects:anArray count:[anArray count]];
return [self initWithObjects:anArray count:[anArray count]];
}
- (id)initWithObjects:(id)anObject, ...
{
var index = 2,
count = arguments.length;
var index = 2,
count = arguments.length;
for (; index < count; ++index)
if (arguments[index] === nil)
break;
for (; index < count; ++index)
if (arguments[index] === nil)
break;
return [self initWithObjects:Array.prototype.slice.call(arguments, 2, index) count:index - 2];
return [self initWithObjects:Array.prototype.slice.call(arguments, 2, index) count:index - 2];
}
- (id)initWithObjects:(CPArray)objects count:(CPUInteger)aCount
{
if (self === _CPSharedPlaceholderSet)
return [[_CPConcreteMutableSet alloc] initWithObjects:objects count:aCount];
if (self === _CPSharedPlaceholderSet)
return [[_CPConcreteMutableSet alloc] initWithObjects:objects count:aCount];
return [super init];
return [super init];
}
- (id)initWithSet:(CPSet)aSet
{
return [self initWithArray:[aSet allObjects]];
return [self initWithArray:[aSet allObjects]];
}
- (id)initWithSet:(CPSet)aSet copyItems:(BOOL)shouldCopyItems
{
if (shouldCopyItems)
return [aSet valueForKey:@"copy"];
if (shouldCopyItems)
return [aSet valueForKey:@"copy"];
return [self initWithSet:aSet];
return [self initWithSet:aSet];
}
- (CPUInteger)count
{
_CPRaiseInvalidAbstractInvocation(self, _cmd);
_CPRaiseInvalidAbstractInvocation(self, _cmd);
}
- (CPArray)allObjects
{
var objects = [],
object,
objectEnumerator = [self objectEnumerator];
var objects = [],
object,
objectEnumerator = [self objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
objects.push(object);
while ((object = [objectEnumerator nextObject]) != nil)
objects.push(object);
return objects;
return objects;
}
- (id)anyObject
{
return [[self objectEnumerator] nextObject];
return [[self objectEnumerator] nextObject];
}
- (BOOL)containsObject:(id)anObject
{
return [self member:anObject] != nil;
return [self member:anObject] != nil;
}
- (CPSet)filteredSetUsingPredicate:(CPPredicate)aPredicate
{
var objects = [],
object,
objectEnumerator = [self objectEnumerator];
var objects = [],
object,
objectEnumerator = [self objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
if ([aPredicate evaluateWithObject:object])
objects.push(object);
while ((object = [objectEnumerator nextObject]) != nil)
if ([aPredicate evaluateWithObject:object])
objects.push(object);
return [[[self class] alloc] initWithArray:objects];
return [[[self class] alloc] initWithArray:objects];
}
- (void)makeObjectsPerformSelector:(SEL)aSelector
{
[self makeObjectsPerformSelector:aSelector withObjects:nil];
[self makeObjectsPerformSelector:aSelector withObjects:nil];
}
- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)anObject
{
[self makeObjectsPerformSelector:aSelector withObjects:[anObject]];
[self makeObjectsPerformSelector:aSelector withObjects:[anObject]];
}
- (void)makeObjectsPerformSelector:(SEL)aSelector withObjects:(CPArray)objects
{
var object,
objectEnumerator = [self objectEnumerator],
argumentsArray = [nil, aSelector].concat(objects || []);
var object,
objectEnumerator = [self objectEnumerator],
argumentsArray = [nil, aSelector].concat(objects || []);
while ((object = [objectEnumerator nextObject]) != nil)
{
argumentsArray[0] = object;
objj_msgSend.apply(this, argumentsArray);
}
while ((object = [objectEnumerator nextObject]) != nil)
{
argumentsArray[0] = object;
objj_msgSend.apply(this, argumentsArray);
}
}
- (id)member:(id)anObject
{
_CPRaiseInvalidAbstractInvocation(self, _cmd);
_CPRaiseInvalidAbstractInvocation(self, _cmd);
}
- (CPEnumerator)objectEnumerator
{
_CPRaiseInvalidAbstractInvocation(self, _cmd);
_CPRaiseInvalidAbstractInvocation(self, _cmd);
}
- (void)enumerateObjectsUsingBlock:(Function)aFunction
{
var object,
objectEnumerator = [self objectEnumerator],
shouldStop = NO;
var object,
objectEnumerator = [self objectEnumerator],
shouldStop = NO;
while (!shouldStop && (object = [objectEnumerator nextObject]) != nil) {
if (aFunction(object, @ref(shouldStop)) !== undefined) {
throw "DEPRECATED: The method enumerateObjectsUsingBlock: does not support returning a value in the block to stop the iteration.";
}
}
while (!shouldStop && (object = [objectEnumerator nextObject]) != nil) {
if (aFunction(object, @ref(shouldStop)) !== undefined) {
throw "DEPRECATED: The method enumerateObjectsUsingBlock: does not support returning a value in the block to stop the iteration.";
}
}
}
- (CPSet)objectsPassingTest:(Function)aFunction
{
var objects = [],
object = nil,
objectEnumerator = [self objectEnumerator];
var objects = [],
object = nil,
objectEnumerator = [self objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
if (aFunction(object))
objects.push(object);
while ((object = [objectEnumerator nextObject]) != nil)
if (aFunction(object))
objects.push(object);
return [[[self class] alloc] initWithArray:objects];
return [[[self class] alloc] initWithArray:objects];
}
- (BOOL)isSubsetOfSet:(CPSet)aSet
{
var object = nil,
objectEnumerator = [self objectEnumerator];
var object = nil,
objectEnumerator = [self objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
if (![aSet containsObject:object])
return NO;
while ((object = [objectEnumerator nextObject]) != nil)
if (![aSet containsObject:object])
return NO;
return YES;
return YES;
}
- (BOOL)intersectsSet:(CPSet)aSet
{
if (self === aSet)
return [self count] > 0;
if (self === aSet)
return [self count] > 0;
var object = nil,
objectEnumerator = [self objectEnumerator];
var object = nil,
objectEnumerator = [self objectEnumerator];
while ((object = [objectEnumerator nextObject]) != nil)
if ([aSet containsObject:object])
return YES;
while ((object = [objectEnumerator nextObject]) != nil)
if ([aSet containsObject:object])
return YES;
return NO;
return NO;
}
- (CPArray)sortedArrayUsingDescriptors:(CPArray)someSortDescriptors
{
return [[self allObjects] sortedArrayUsingDescriptors:someSortDescriptors];
return [[self allObjects] sortedArrayUsingDescriptors:someSortDescriptors];
}
- (BOOL)isEqualToSet:(CPSet)aSet
{
return [self isEqual:aSet];
return [self isEqual:aSet];
}
- (BOOL)isEqual:(CPSet)aSet
{
return self === aSet ||
[aSet isKindOfClass:[CPSet class]] &&
([self count] === [aSet count] &&
[aSet isSubsetOfSet:self]);
return self === aSet ||
[aSet isKindOfClass:[CPSet class]] &&
([self count] === [aSet count] &&
[aSet isSubsetOfSet:self]);
}
- (CPString)description
{
var string = "{(\n",
objects = [self allObjects],
index = 0,
count = [objects count];
var string = "{(\n",
objects = [self allObjects],
index = 0,
count = [objects count];
for (; index < count; ++index)
{
var object = objects[index];
string += "\t" + String(object).split('\n').join("\n\t") + "\n";
}
for (; index < count; ++index)
{
var object = objects[index];
string += "\t" + String(object).split('\n').join("\n\t") + "\n";
}
return string + ")}";
return string + ")}";
}
@end
@@ -302,12 +302,12 @@
- (id)copy
{
return [[self class] setWithSet:self];
return [[self class] setWithSet:self];
}
- (id)mutableCopy
{
return [self copy];
return [self copy];
}
@end
@@ -321,12 +321,12 @@ var CPSetObjectsKey = @"CPSetObjectsKey";
- (id)initWithCoder:(CPCoder)aCoder
{
return [self initWithArray:[aCoder decodeObjectForKey:CPSetObjectsKey]];
return [self initWithArray:[aCoder decodeObjectForKey:CPSetObjectsKey]];
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeObject:[self allObjects] forKey:CPSetObjectsKey];
[aCoder encodeObject:[self allObjects] forKey:CPSetObjectsKey];
}
@end
@@ -342,10 +342,10 @@ var _CPSharedPlaceholderSet = nil;
+ (id)alloc
{
if (!_CPSharedPlaceholderSet)
_CPSharedPlaceholderSet = [super alloc];
if (!_CPSharedPlaceholderSet)
_CPSharedPlaceholderSet = [super alloc];
return _CPSharedPlaceholderSet;
return _CPSharedPlaceholderSet;
}
@end
+89 -128
View File
@@ -47,25 +47,30 @@ var abbreviationDictionary,
function abbreviationForDate(date)
{
// First, ask Intl directly for the short time zone name (e.g. "PDT") of
// the runtime's local zone, which correctly reflects DST for this date.
// Replaces the previous date.toString() parenthesis-scraping and
// long-name-to-acronym regex guessing, which broke for locales and
// engines that don't format Date#toString the way that logic assumed.
try {
var parts = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).formatToParts(date),
tzPart = parts.filter(function (p) { return p.type === 'timeZoneName'; })[0];
// Strategy 1: Parse date.toString() as it's more reliable than toLocaleString.
// Format is usually: "Day Mon dd yyyy hh:mm:ss GMT+XXXX (Time Zone Name)"
var dateString = date.toString();
if (tzPart && [abbreviationDictionary objectForKey:tzPart.value])
return tzPart.value;
} catch (e) {
// Intl API not supported, or it failed. Fall through to the next attempt.
// Check for a long name within parentheses, e.g., (Pacific Daylight Time)
var longNameMatch = dateString.match(/\(([^)]+)\)/);
if (longNameMatch) {
var timeZoneComponent = longNameMatch[1];
// If the component is already a known abbreviation (e.g., "EST"), return it.
if ([abbreviationDictionary objectForKey:timeZoneComponent]) {
return timeZoneComponent;
}
// If it's a long name (e.g., "Eastern Daylight Time"), create an acronym.
if (timeZoneComponent.indexOf(' ') > -1) {
var generatedAbbr = timeZoneComponent.split(' ').map(function(word) { return word[0]; }).join('');
if ([abbreviationDictionary objectForKey:generatedAbbr]) {
return generatedAbbr;
}
}
}
// If that short name isn't one of our known abbreviations (e.g. it
// returned "GMT-04:00" for a zone with no common three/four-letter
// abbreviation), resolve the runtime's IANA zone and pick whichever
// known abbreviation for that zone matches the date's current UTC offset.
// Strategy 2: If string parsing fails (e.g., for "GMT-04:00"), use the modern and reliable Intl API.
try {
var ianaName = new Intl.DateTimeFormat().resolvedOptions().timeZone;
var currentOffset = -date.getTimezoneOffset(); // in minutes
@@ -93,22 +98,8 @@ function abbreviationForDate(date)
if (possibleAbbrs.length > 0) {
return possibleAbbrs[0];
}
// Neither attempt above found a match by name. Fall back to any
// known abbreviation whose stored offset matches the system's
// current UTC offset. Several abbreviations legitimately share an
// offset (GMT, UTC, and WET all correctly resolve to 0, for example),
// so this returns one of them rather than none.
var offsetKeys = [timeDifferenceFromUTC keyEnumerator],
offsetKey;
while (offsetKey = [offsetKeys nextObject]) {
if ([timeDifferenceFromUTC valueForKey:offsetKey] === currentOffset) {
return offsetKey;
}
}
} catch (e) {
// Intl API not supported, or it failed.
// Intl API not supported, or it failed. We cannot proceed with this strategy.
}
// Return nil if no valid abbreviation could be determined.
@@ -117,18 +108,28 @@ function abbreviationForDate(date)
function _abbreviationForNameAndDate(tzName, date)
{
// Determines the abbreviation for a given IANA name based on the provided
// date, which allows it to respect daylight saving time. Reads the short
// time zone name directly from Intl.formatToParts, rather than parsing
// a long name out of toLocaleString's locale-formatted output.
// This is a helper function based on the existing `abbreviationForDate`.
// It determines the abbreviation for a given IANA name based on the provided date,
// which allows it to respect daylight saving time.
try {
var parts = new Intl.DateTimeFormat('en-US', { timeZone: tzName, timeZoneName: 'short' }).formatToParts(date),
tzPart = parts.filter(function (p) { return p.type === 'timeZoneName'; })[0];
var options = {
timeZone: tzName,
timeZoneName: 'long'
};
// The 'en-US' locale provides a predictable format for parsing.
var dateString = date.toLocaleString('en-US', options);
return tzPart ? tzPart.value : nil;
// This regex is copied from the global 'abbreviationForDate' function.
// It strips the date and time, leaving the long time zone name.
var longTZName = dateString.replace(/^([0]?\d|[1][0-2])\/((?:[0]?|[1-2])\d|[3][0-1])\/([2][01]|[1][6-9])\d{2}(,?\s*([0]?\d|[1][0-2])(\:[0-5]\d){1,2})*\s*([aApP][mM]{0,2})?\s*/, "");
// Create the abbreviation from the long name (e.g., "Pacific Daylight Time" -> "PDT")
var abbreviation = longTZName.split(" ").map(function(l) { return l[0]}).join("");
return abbreviation;
} catch (e) {
// The tzName might be invalid for Intl.DateTimeFormat, which throws a
// RangeError. In this case, we can't determine the abbreviation.
// The tzName might be invalid for toLocaleString, which throws a RangeError.
// In this case, we can't determine the abbreviation.
return nil;
}
}
@@ -154,94 +155,56 @@ function _abbreviationForNameAndDate(tzName, date)
return;
knownTimeZoneNames = [
@"Africa/Addis_Ababa",
@"Africa/Harare",
@"Africa/Lagos",
@"America/Argentina/Buenos_Aires",
@"America/Bogota",
@"America/Chicago",
@"America/Denver",
@"America/Halifax",
@"America/Juneau",
@"America/Lima",
@"America/Los_Angeles",
@"America/New_York",
@"America/Santiago",
@"America/Sao_Paulo",
@"Asia/Bangkok",
@"Asia/Calcutta",
@"America/Juneau",
@"America/Argentina/Buenos_Aires",
@"America/Halifax",
@"Asia/Dhaka",
@"America/Sao_Paulo",
@"America/Sao_Paulo",
@"Europe/London",
@"Africa/Harare",
@"America/Chicago",
@"Europe/Paris",
@"Europe/Paris",
@"America/Santiago",
@"America/Santiago",
@"America/Bogota",
@"America/Chicago",
@"Africa/Addis_Ababa",
@"America/New_York",
@"Europe/Istanbul",
@"Europe/Istanbul",
@"America/New_York",
@"GMT",
@"Asia/Dubai",
@"Asia/Hong_Kong",
@"Asia/Jakarta",
@"Asia/Karachi",
@"Asia/Manila",
@"Asia/Seoul",
@"Asia/Singapore",
@"Asia/Tehran",
@"Asia/Tokyo",
@"Europe/Istanbul",
@"Europe/Lisbon",
@"Europe/London",
@"Europe/Moscow",
@"Europe/Paris",
@"GMT",
@"Pacific/Auckland",
@"Pacific/Honolulu",
@"Asia/Bangkok",
@"Asia/Tehran",
@"Asia/Calcutta",
@"Asia/Tokyo",
@"Asia/Seoul",
@"America/Denver",
@"Europe/Moscow",
@"Europe/Moscow",
@"America/Denver",
@"Pacific/Auckland",
@"Pacific/Auckland",
@"America/Los_Angeles",
@"America/Lima",
@"Asia/Manila",
@"Asia/Karachi",
@"America/Los_Angeles",
@"Asia/Singapore",
@"UTC",
@"Africa/Lagos",
@"Europe/Lisbon",
@"Europe/Lisbon",
@"Asia/Jakarta"
];
// Prefer the runtime's own IANA database, when it exposes one, over the
// hardcoded 48-city list above: it's the full current set, not a snapshot
// that will silently drift the way the hand-maintained tables above have.
if (typeof Intl !== "undefined" && typeof Intl.supportedValuesOf === "function")
{
try
{
var supportedZones = Intl.supportedValuesOf("timeZone");
if (supportedZones && supportedZones.length > 0)
{
var zones = [];
var hasGMT = false;
var hasUTC = false;
var count = supportedZones.length;
// Iterate using primitive property access.
// The array returned by Intl across the runtime bridge may lack
// standard Array prototypes (e.g., slice, indexOf). A standard loop
// ensures safe data extraction into a local array without triggering
// prototype resolution exceptions or relying on CPArray.
for (var i = 0; i < count; i++)
{
var zone = supportedZones[i];
zones[i] = zone;
if (zone === @"GMT")
hasGMT = true;
else if (zone === @"UTC")
hasUTC = true;
}
// Explicitly restore legacy aliases if the host engine omits them.
// Engines adhering strictly to canonical IANA identifiers omit "GMT"
// and "UTC". CPTimeZone's static dictionaries map these directly,
// requiring their presence to initialize localTimeZone in UTC environments.
if (!hasGMT)
zones[zones.length] = @"GMT";
if (!hasUTC)
zones[zones.length] = @"UTC";
knownTimeZoneNames = zones;
}
}
catch (e)
{
// Fall through, keep the hardcoded list above.
}
}
abbreviationDictionary = @{
@"ADT" : @"America/Halifax",
@"AKDT" : @"America/Juneau",
@@ -326,14 +289,12 @@ function _abbreviationForNameAndDate(tzName, date)
@"IST" : 330,
@"JST" : 540,
@"KST" : 540,
@"MDT" : -360,
@"MSD" : 240, // Stale: Russia abolished DST in 2014. No current offset
// is correct for a distinct "Moscow Summer Time"; left
// unfixed rather than fabricated. See CPTimeZone redesign.
@"MSK" : 180,
@"MDT" : -300,
@"MSD" : 240,
@"MSK" : 240,
@"MST" : -420,
@"NZDT" : 780,
@"NZST" : 720,
@"NZDT" : 900,
@"NZST" : 900,
@"PDT" : -420,
@"PET" : -300,
@"PHT" : 480,
@@ -341,10 +302,10 @@ function _abbreviationForNameAndDate(tzName, date)
@"PST" : -480,
@"SGT" : 480,
@"UTC" : 0,
@"WAT" : 60,
@"WAT" : -540,
@"WEST" : 60,
@"WET" : 0,
@"WIT" : 420
@"WIT" : 540
};
var englishLocalizedName = @{
+60 -68
View File
@@ -25,15 +25,14 @@
@import "CPObject.j"
@import "CPRunLoop.j"
// FIXME: Expose CPTimerDefaultTimeInterval via public API or eliminate the fallback behaviour.
const CPTimerDefaultTimeInterval = 0.1;
#define CPTimerDefaultTimeInterval 0.1
/*!
@class CPTimer
@ingroup foundation
@class CPTimer
@ingroup foundation
@brief A timer object that can send a message after the given time interval.
*/
@brief A timer object that can send a message after the given time interval.
*/
@implementation CPTimer : CPObject
{
CPTimeInterval _timeInterval;
@@ -47,11 +46,11 @@ const CPTimerDefaultTimeInterval = 0.1;
}
/*!
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
*/
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
*/
+ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat
{
const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat];
var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat];
[[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode];
@@ -59,11 +58,11 @@ const CPTimerDefaultTimeInterval = 0.1;
}
/*!
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
*/
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
*/
+ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat];
var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat];
[[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode];
@@ -71,11 +70,11 @@ const CPTimerDefaultTimeInterval = 0.1;
}
/*!
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
*/
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
*/
+ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat
{
const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat];
var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat];
[[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode];
@@ -83,32 +82,32 @@ const CPTimerDefaultTimeInterval = 0.1;
}
/*!
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
*/
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
*/
+ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat
{
return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat];
}
/*!
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
*/
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
*/
+ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat];
}
/*!
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
*/
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
*/
+ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat
{
return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat];
}
/*!
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
*/
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
*/
- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat
{
self = [super init];
@@ -126,11 +125,11 @@ const CPTimerDefaultTimeInterval = 0.1;
}
/*!
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
*/
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
*/
- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
const invocation = [CPInvocation invocationWithMethodSignature:1];
var invocation = [CPInvocation invocationWithMethodSignature:1];
[invocation setTarget:aTarget];
[invocation setSelector:aSelector];
@@ -145,8 +144,8 @@ const CPTimerDefaultTimeInterval = 0.1;
}
/*!
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
*/
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
*/
- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat
{
self = [super init];
@@ -164,32 +163,32 @@ const CPTimerDefaultTimeInterval = 0.1;
}
/*!
Returns the receivers time interval.
*/
Returns the receivers time interval.
*/
- (CPTimeInterval)timeInterval
{
return _timeInterval;
return _timeInterval;
}
/*!
Returns the date at which the receiver will fire.
*/
Returns the date at which the receiver will fire.
*/
- (CPDate)fireDate
{
return _fireDate;
return _fireDate;
}
/*!
Resets the receiver to fire next at a given date.
*/
Resets the receiver to fire next at a given date.
*/
- (void)setFireDate:(CPDate)aDate
{
_fireDate = aDate;
}
/*!
Causes the receivers message to be sent to its target.
*/
Causes the receivers message to be sent to its target.
*/
- (void)fire
{
if (!_isValid)
@@ -205,64 +204,56 @@ const CPTimerDefaultTimeInterval = 0.1;
if (_repeats)
_fireDate = [CPDate dateWithTimeIntervalSinceNow:_timeInterval];
else
[self invalidate];
}
/*!
Returns a Boolean value that indicates whether the receiver is currently valid.
*/
Returns a Boolean value that indicates whether the receiver is currently valid.
*/
- (BOOL)isValid
{
return _isValid;
return _isValid;
}
/*!
Stops the receiver from ever firing again and requests its removal from its CPRunLoop object.
*/
Stops the receiver from ever firing again and requests its removal from its CPRunLoop object.
*/
- (void)invalidate
{
_isValid = NO;
_userInfo = nil;
_invocation = nil;
_callback = nil;
_isValid = NO;
_userInfo = nil;
_invocation = nil;
_callback = nil;
}
/*!
Returns the receiver's userInfo object.
*/
Returns the receiver's userInfo object.
*/
- (id)userInfo
{
return _userInfo;
return _userInfo;
}
@end
// FIXME: Anti-pattern: Global DOM Override. This section invasively overrides global DOM timing
// functions (window.setTimeout, setInterval) to force external execution through CPRunLoop.
// This deep coupling creates unpredictable side effects for third-party libraries and should
// be replaced with a non-invasive run loop integration strategy.
let CPTimersTimeoutID = 1000;
var CPTimersTimeoutID = 1000,
CPTimersForTimeoutIDs = {};
// FIXME: Anti-pattern: Manual Global Tracking. Tracking bridged DOM timers in a global map like
// this is brittle and prone to memory leaks in long-running processes.
const CPTimersForTimeoutIDs = {};
const _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, functionArgs)
var _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, functionArgs)
{
const timeoutID = CPTimersTimeoutID++;
let theFunction = nil;
var timeoutID = CPTimersTimeoutID++,
theFunction = nil;
if (typeof codeOrFunction === "string")
{
// FIXME: Anti-pattern: Dynamic Evaluation. Evaluating string payloads via `new Function`
// is a strict Content Security Policy (CSP) violation.
theFunction = function()
{
new Function(codeOrFunction)();
if (!shouldRepeat)
delete CPTimersForTimeoutIDs[timeoutID];
CPTimersForTimeoutIDs[timeoutID] = nil;
}
}
else
@@ -275,7 +266,7 @@ const _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, funct
codeOrFunction.apply(window, functionArgs);
if (!shouldRepeat)
delete CPTimersForTimeoutIDs[timeoutID];
CPTimersForTimeoutIDs[timeoutID] = nil;
}
}
@@ -288,6 +279,7 @@ const _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, funct
};
// Avoid "TypeError: Result of expression 'window' [undefined] is not an object" when running unit tests.
// We can't use a regular PLATFORM(DOM) check because that platform constant is not defined in Foundation.
if (typeof(window) !== 'undefined')
{
window.setTimeout = function(codeOrFunction, aDelay)
@@ -297,12 +289,12 @@ if (typeof(window) !== 'undefined')
window.clearTimeout = function(aTimeoutID)
{
const timer = CPTimersForTimeoutIDs[aTimeoutID];
var timer = CPTimersForTimeoutIDs[aTimeoutID];
if (timer)
[timer invalidate];
delete CPTimersForTimeoutIDs[aTimeoutID];
CPTimersForTimeoutIDs[aTimeoutID] = nil;
};
window.setInterval = function(codeOrFunction, aDelay, functionArgs)
+14 -14
View File
@@ -161,14 +161,14 @@ var CPURLConnectionDelegate = nil;
Typical use can be like this:
- (async @action)doAction:(id)sender {
const { response, data, error } = await [CPURLConnection sendAsynchronousRequest:[CPURLRequest requestWithURL:@"http://cappuccino.dev"]];
if (error == nil) {
//do the stuff...
} else {
// Handle errors
}
}
- (async @action)doAction:(id)sender {
const { response, data, error } = await [CPURLConnection sendAsynchronousRequest:[CPURLRequest requestWithURL:@"http://cappuccino.dev"]];
if (error == nil) {
//do the stuff...
} else {
// Handle errors
}
}
*/
+ (async JSObject /* { response: CPURLResponse, data: CPData, error: CPError } */)sendAsynchronousRequest:(CPURLRequest)aRequest
{
@@ -218,15 +218,15 @@ var CPURLConnectionDelegate = nil;
- (void)_initWithRequest:(CPURLRequest)aRequest
{
_request = aRequest;
_request = aRequest;
_originalRequest = [aRequest copy];
_isCanceled = NO;
_isCanceled = NO;
var URL = [_request URL],
scheme = [URL scheme];
var URL = [_request URL],
scheme = [URL scheme];
// Browsers use "file:", Titanium uses "app:"
_isLocalFileConnection = scheme === "file" ||
// Browsers use "file:", Titanium uses "app:"
_isLocalFileConnection = scheme === "file" ||
((scheme === "http" || scheme === "https") &&
window.location &&
(window.location.protocol === "file:" || window.location.protocol === "app:"));
+2
View File
@@ -0,0 +1,2 @@
// By Christian C. Salvadó, http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric/1830844#1830844
#define _IS_NUMERIC(n) (!isNaN(parseFloat(n)) && isFinite(n))
-1
View File
@@ -21,7 +21,6 @@
*/
@import "_CGGeometry.j"
@import "_CPFoundationUtilities.j"
@import "CPArray.j"
@import "CPBundle.j"
@import "CPByteCountFormatter.j"
-88
View File
@@ -1,88 +0,0 @@
/*
* _CPFoundationUtilities.j
* Foundation
*
* Created by David Richardson.
* Copyright 2026, Cappuccino Project.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
//#define _IS_NUMERIC(n) (!isNaN(parseFloat(n)) && isFinite(n))
/*
Objective-J is a strict superset of JavaScript and compiles down to a shared global runtime scope.
This file (_CPFoundationUtilities.j) is the canonical place for otherwise "homeless" low-level
utilities, stateless helpers, and former C-style preprocessor macros that do not belong to a
specific class but are required across the framework.
While modern JavaScript ecosystems (e.g., ES6 modules, bundlers) treat the global scope as something
to be strictly avoided, Cappuccino's architecture predates these paradigms. It relies entirely on
global scope sharing for its runtime and toll-free bridging with native JavaScript, much like C and
Objective-C. Therefore, injecting `CP`-prefixed functions into the global scope is the intended design
pattern here, not an anti-pattern or "pollution."
----------------------------------------------------------------------------
New compiler does not provide a pre-processor.
The macro is expanded here to a concrete function.
The alternative is inlining at call site.
On a cold call, this provides a very minor performance advantage.
Conversely, it provides the Javascript engine fewer opportunities to optimize,
which is only done when a call site is invoked.
Additionally, it depends on individual maintainers to correctly implement the call every time.
The macro is expanded here precisely to maintain identical semantics.
Every current, popular browser engine optimizes a small, hot, monomorphic function like a numeric check almost immediately:
V8 (Chrome, Edge, Opera, Brave, Node) — tiered JIT (Ignition → Sparkplug → Maglev → TurboFan).
A function called this often gets promoted within tens of calls.
SpiderMonkey (Firefox) — Baseline Interpreter → Baseline JIT → Ion. Same pattern.
JavaScriptCore (Safari, all iOS browsers, since iOS forces WebKit) — LLInt → Baseline → DFG → FTL.
All three engines specialize aggressively on exactly this shape of code: a tiny, pure, argument-type-stable function with no side effects. It is close to the ideal case for JIT optimization — the compiler will likely inline the call at the machine-code level, which is the same outcome as hand-inlining the expression, achieved automatically.
There is no browser in current popular use — desktop or mobile — where this function call would remain a meaningful cost.
Additionally, modern hardware and Javascript engines are so much faster than in 2008, when Cappuccino was conceived,
that even a cold execution of this function is trivial.
The performance objection which originally required in-lining via a macro does not exist for current targets.
Javascript, Objective-J, C, and Objective-C all lack native namespacing.
'CP' is the canonical namespace prefix used throughout Cappuccino to address potential collisions.
It is reserved by convention.
*/
/*
Checks if a value is a valid, finite number.
This implements the legacy `_IS_NUMERIC` behavior exactly. It returns true for
numbers and strings that can be successfully parsed into a finite number (e.g., 42, "3.14"),
and false for NaN, Infinity, null, and purely non-numeric strings.
This specific logic (parseFloat + global isFinite) is deliberately preserved to prevent
regressions in code that historically relied on its lenient string parsing, rather than
using the stricter modern ES6 `Number.isFinite()`.
TODO: Modernize this check to use ES6 `Number.isFinite()`. This is currently deferred
to maintain strict semantic continuity during the Go/Lisette toolchain migration and
requires a full audit of all call sites to ensure string coercion is no longer expected.
@param n The value to evaluate.
@return {Boolean} YES if the value is numeric, NO otherwise.
*/
function CPIsNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
+10 -31
View File
@@ -3,31 +3,10 @@
# Cappuccino: Build Desktop-Class Web Applications
> **✨ Project Status: v1.5.0 Baseline & Upcoming v2.0.0 Toolchain**
>
> Cappuccino has been under continuous development since 2008 and is actively maintained. The v1.5.0 release establishes a baseline as we move to the new resolution-independent Aristo3 theme and Go-based toolchain.
> For users seeking a complication-free alternative who wish to avoid the Aristo3 work entirely, the legacy-1.4.0 branch provides an unambiguous freeze point. Please note, however, that this legacy branch is not guaranteed to receive any future bug fixes or improvements.
> Active development is now focused on the upcoming v2.0.0 release, which will transition the full toolchain to Golang and the platform-native binaries it produces, leaving Node.js and npm behind.
> **🚨 Aristo3 Theme: Community Testing Required**
>
> Aristo3 represents a non-trivial refactoring of AppKit UI classes. While unit tests pass, it must be tested against real-world applications before being merged into `main`. Community members testing against their own applications will accelerate the process. We are not concerned with minor visual breakages or cosmetic regressions at this stage; **the primary concern is structural failures.**
>
> The fully merged state has been pushed to the `aristo3` branch on the canonical repo.
>
> **How to help:**
> 1. Check out the `aristo3` branch: `git fetch origin && git checkout aristo3`
> 2. Build the frameworks and run your existing applications against this branch.
> 3. Open your browser's developer console and watch for:
> - Uncaught `CPException`s or JavaScript errors.
> - Infinite layout loops (browser freezing/tab crashing).
> - Broken responder chains or keyboard event handling.
> - KVO/Binding failures or `valueForThemeAttribute:` resolving to `nil` unexpectedly.
> - View hierarchy corruption (subviews disappearing or failing to clip).
>
> Please leave a 👍 on the [Aristo3 Pull Request](https://github.com/cappuccino/cappuccino/pull/3038) if your apps run without structural failures. If you encounter exceptions or crashes, please leave a comment with the stack trace. **A solid response of thumbs up is required before `main` can be merged. Our intention is to leave no community member behind.**
> **🛑 Legacy Branch: Node.js Tombstone**
> This branch is the final, unmaintained state of the pre-Aristo3 Node/npm era (tagged `v1.4.0`), remaining visually and mechanically compatible with the `1.3.1` npm release. Bug fixes and improvements from the main branch are included. It uses Aristo2 as its theme.
>
> While the main branch is intended to maintain backward theme and toolchain compatibility, no guarantees are made that this branch will be advanced in sync with it. This branch is intended solely as a frozen artifact for those requiring an extended transition period to Cappuccino 2.
## Why Use Cappuccino?
@@ -112,7 +91,7 @@ Pure JavaScript and Objective-J can be mixed and matched, even in the same file.
## Frequently Asked Questions (FAQ)
**Q: What are the advantages over React or Vue?**
**Q: What are the advantages over React or Vue?**
**A:** React and Vue are excellent libraries for building web UIs. Cappuccino is a comprehensive **framework** for building entire **applications**. It provides a fully integrated stack—including a mature UI library, event handling, and data management—designed for large-scale development.
Beyond this, Cappuccino provides a more integrated and powerful data-binding layer inspired directly by Cocoa, which dramatically reduces boilerplate code for complex UIs as you can see in this [example code](https://github.com/daboe01/UIBuilder/tree/master/public/Frontend) that uses these features:
@@ -122,19 +101,19 @@ Beyond this, Cappuccino provides a more integrated and powerful data-binding lay
* **Advanced Filtering with Predicates:** A table displaying thousands of items can be filtered simply by setting a predicate (a declarative filter rule, e.g., `lastName BEGINSWITH 'S'`) on its controller. The UI updates instantly. This eliminates tons of manual state management and filtering logic code.
* **Automatic Value Transformation:** Data can be easily formatted for display (e.g., dates, currency, booleans to "Yes/No") directly within the binding itself using value transformers, keeping model data pure and view logic minimal.
**Q: Can Cappuccino be used on Windows/Linux?**
**Q: Can Cappuccino be used on Windows/Linux?**
**A:** Yes. The development tools run on Node.js and are platform-independent. Applications can be developed on any OS and deployed on any web server.
**Q: Is Xcode required?**
**Q: Is Xcode required?**
**A:** No. Any code editor can be used. Xcode offers optional visual development tools for macOS users, but it is not a requirement.
**Q: Hasn't Apple moved on from Objective-C, making these APIs obsolete?**
**Q: Hasn't Apple moved on from Objective-C, making these APIs obsolete?**
**A:** While Swift is Apple's newer language, Objective-C and AppKit remain foundational, actively supported technologies used in many of Apple's flagship applications. Cappuccino leverages the stability and power of this time-tested API design, which is independent of Apple's future product roadmap.
**Q: How can custom HTML, CSS, or JavaScript libraries be integrated?**
**Q: How can custom HTML, CSS, or JavaScript libraries be integrated?**
**A:** Cappuccino abstracts away the DOM, but other web technologies can still be integrated. The `CPWebView` control allows arbitrary HTML/CSS/JS content to be embedded. Since Objective-J is a superset of JavaScript, JS libraries can be used and JS functions can be called directly from Objective-J code.
**Q: Does the LGPL license permit closed-source commercial applications?**
**Q: Does the LGPL license permit closed-source commercial applications?**
**A:** Yes. The LGPLv2 license allows proprietary, closed-source applications to be built and distributed using Cappuccino. Sharing of source code is only required for any modifications made **to the Cappuccino framework itself**. The application code remains proprietary.
---
+1 -7
View File
@@ -1125,14 +1125,8 @@
/*!
Test the speed of set an big array when the old was an empty.
Also test the speed when an empty array is set and the old is an big
Disabled: This method is a macro-benchmark lacking functional assertions.
Absolute wall-clock timings are non-deterministic across disparate CI environments
and pollute standard test output. Actionable performance tracking requires a
dedicated metrics harness. Retained only for ad-hoc local profiling.
*/
- (void)disabled_testPerformance
- (void)testPerformance
{
[self initControllerWithContentBinding];
+68 -68
View File
@@ -12,18 +12,18 @@
*/
@implementation _MockColorPicker : CPObject
{
CPArray _receivedColors;
CPView _view;
CPArray _receivedColors;
CPView _view;
}
- (id)init
{
if (self = [super init])
{
_receivedColors = [];
_view = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
}
return self;
if (self = [super init])
{
_receivedColors = [];
_view = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
}
return self;
}
- (void)setColor:(CPColor)aColor
@@ -33,32 +33,32 @@
- (CPArray)receivedColors
{
return _receivedColors;
return _receivedColors;
}
- (CPView)provideNewView:(BOOL)initial
{
return _view;
return _view;
}
@end
@implementation CPColorPanelTest : OJTestCase
{
CPColorPanel _panel;
CPColorPanel _panel;
}
- (void)setUp
{
// Get shared panel and ensure it's initialized
_panel = [CPColorPanel sharedColorPanel];
[_panel _loadContentsIfNecessary];
// Get shared panel and ensure it's initialized
_panel = [CPColorPanel sharedColorPanel];
[_panel _loadContentsIfNecessary];
}
- (void)tearDown
{
// Reset panel state between tests
[_panel setColor:[CPColor whiteColor]];
// Reset panel state between tests
[_panel setColor:[CPColor whiteColor]];
}
/*
@@ -67,10 +67,10 @@
*/
- (void)testSetColorUpdatesOpacitySlider
{
var initialColor = [CPColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5];[_panel setColor:initialColor];
var initialColor = [CPColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5];[_panel setColor:initialColor];
[self assert:0.5 equals:[_panel._opacitySlider floatValue]
message:"Opacity slider should match the color's alpha component"];
[self assert:0.5 equals:[_panel._opacitySlider floatValue]
message:"Opacity slider should match the color's alpha component"];
}
/*
@@ -78,19 +78,19 @@
*/
- (void)testOpacityChangeUpdatesColorAlpha
{
var initialColor = [CPColor colorWithRed:0.5 green:0.5 blue:1.0 alpha:1.0];
[_panel setColor:initialColor];[_panel._opacitySlider setFloatValue:0.3];
[_panel setOpacity:_panel._opacitySlider];
var initialColor = [CPColor colorWithRed:0.5 green:0.5 blue:1.0 alpha:1.0];
[_panel setColor:initialColor];[_panel._opacitySlider setFloatValue:0.3];
[_panel setOpacity:_panel._opacitySlider];
var newColor = [_panel color];
[self assert:0.3 equals:[newColor alphaComponent]
message:"Color's alpha should match slider value"];
var newColor = [_panel color];
[self assert:0.3 equals:[newColor alphaComponent]
message:"Color's alpha should match slider value"];
// RGB components should remain unchanged
var components = [newColor components];
[self assert:0.5 equals:components[0] message:"Red component should be unchanged"];
[self assert:0.5 equals:components[1] message:"Green component should be unchanged"];
[self assert:1.0 equals:components[2] message:"Blue component should be unchanged"];
// RGB components should remain unchanged
var components = [newColor components];
[self assert:0.5 equals:components[0] message:"Red component should be unchanged"];
[self assert:0.5 equals:components[1] message:"Green component should be unchanged"];
[self assert:1.0 equals:components[2] message:"Blue component should be unchanged"];
}
/*
@@ -99,20 +99,20 @@
*/
- (void)testActivePickerNotifiedOfColorChanges
{
var mockPicker = [[_MockColorPicker alloc] init];
_panel._activePicker = mockPicker;
var mockPicker = [[_MockColorPicker alloc] init];
_panel._activePicker = mockPicker;
var color1 = [CPColor redColor];
var color2 = [CPColor blueColor];
var color1 = [CPColor redColor];
var color2 = [CPColor blueColor];
[_panel setColor:color1];
[_panel setColor:color2];
[_panel setColor:color1];
[_panel setColor:color2];
var receivedColors = [mockPicker receivedColors];
[self assert:2 equals:[receivedColors count]
message:"Active picker should receive setColor for each color change"];
[self assert:color1 same:receivedColors[0]];
[self assert:color2 same:receivedColors[1]];
var receivedColors = [mockPicker receivedColors];
[self assert:2 equals:[receivedColors count]
message:"Active picker should receive setColor for each color change"];
[self assert:color1 same:receivedColors[0]];
[self assert:color2 same:receivedColors[1]];
}
/*
@@ -121,21 +121,21 @@
*/
- (void)testPickerNotifiedOnActivation
{
var testColor = [CPColor colorWithRed:0.5 green:0.5 blue:1.0 alpha:1.0];
[_panel setColor:testColor];
var testColor = [CPColor colorWithRed:0.5 green:0.5 blue:1.0 alpha:1.0];
[_panel setColor:testColor];
var mockPicker = [[_MockColorPicker alloc] init];
_panel._colorPickers = [mockPicker];
var mockPicker = [[_MockColorPicker alloc] init];
_panel._colorPickers = [mockPicker];
var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
[button setTag:0];
var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
[button setTag:0];
[_panel _setPicker:button];
[_panel _setPicker:button];
var receivedColors = [mockPicker receivedColors];
[self assert:1 equals:[receivedColors count]
message:"Picker should receive setColor when activated"];
[self assert:testColor same:receivedColors[0]];
var receivedColors = [mockPicker receivedColors];
[self assert:1 equals:[receivedColors count]
message:"Picker should receive setColor when activated"];
[self assert:testColor same:receivedColors[0]];
}
/*
@@ -143,11 +143,11 @@
*/
- (void)testOpacityMethodReturnsAlpha
{
var testColor = [CPColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.42];
[_panel setColor:testColor];
var testColor = [CPColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.42];
[_panel setColor:testColor];
[self assert:0.42 equals:[_panel opacity]
message:"opacity method should return color's alpha component"];
[self assert:0.42 equals:[_panel opacity]
message:"opacity method should return color's alpha component"];
}
/*
@@ -156,18 +156,18 @@
*/
- (void)testSetColorWithEqualColorDoesNothing
{
var testColor = [CPColor colorWithRed:1.0 green:0.5 blue:0.0 alpha:0.7];
[_panel setColor:testColor];
var testColor = [CPColor colorWithRed:1.0 green:0.5 blue:0.0 alpha:0.7];
[_panel setColor:testColor];
var mockPicker = [[_MockColorPicker alloc] init];
_panel._activePicker = mockPicker;
var mockPicker = [[_MockColorPicker alloc] init];
_panel._activePicker = mockPicker;
// Set same color again
[_panel setColor:testColor];
// Set same color again
[_panel setColor:testColor];
var receivedColors = [mockPicker receivedColors];
[self assert:0 equals:[receivedColors count]
message:"Setting equal color should not trigger picker update"];
var receivedColors = [mockPicker receivedColors];
[self assert:0 equals:[receivedColors count]
message:"Setting equal color should not trigger picker update"];
}
/*
@@ -175,10 +175,10 @@
*/
- (void)testColorMethodReturnsCurrentColor
{
var testColor = [CPColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:0.8];[_panel setColor:testColor];
var testColor = [CPColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:0.8];[_panel setColor:testColor];
[self assert:testColor same:[_panel color]
message:"color method should return current color"];
[self assert:testColor same:[_panel color]
message:"color method should return current color"];
}
@end
+18 -33
View File
@@ -317,6 +317,24 @@
[self assertFalse:hasScrolled];
}
-(void)testNotificationsRegistered
{
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)],
theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0)
styleMask:CPWindowNotSizable];
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:scrollView] equals:[] message:@"Notications registered for the scrollView in the notification center are wrong -1-"];
[[theWindow contentView] addSubview:scrollView];
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:scrollView] equals:[@"CPScrollerStyleGlobalChangeNotification", @"_CPWindowDidChangeFirstResponderNotification"] message:@"Notications registered for the scrollView in the notification center are wrong -2-"];
[[theWindow contentView] addSubview:scrollView];
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:scrollView] equals:[@"CPScrollerStyleGlobalChangeNotification", @"_CPWindowDidChangeFirstResponderNotification"] message:@"Notications registered for the scrollView in the notification center are wrong -3-"];
[scrollView removeFromSuperview];
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:scrollView] equals:[] message:@"Notications registered for the scrollView in the notification center are wrong -4-"];
}
- (void)testDocumentVisibleRect
{
var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0)
@@ -343,39 +361,6 @@
[self assertRect:CGRectMake(80, 80, 200, 200) equals:[aScrollView documentVisibleRect] message:@"documentVisibleRect is wrong in CPScrollView"];
}
- (void)testSetGlobalScrollerStyle
{
// In this test, we set global scroller style to legacy, we create a scroll view, change global scroller style to overlay BEFORE placing the
// scroll view in the view hierarchy, then we insert the scroll view in the view hierarchy and it should then have overlay as scroller style.
[CPScrollView setGlobalScrollerStyle:CPScrollerStyleLegacy];
var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0)
styleMask:CPWindowNotSizable],
windowView = [theWindow contentView],
aScrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)],
aDocumentView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[CPScrollView setGlobalScrollerStyle:CPScrollerStyleOverlay];
[aScrollView setDocumentView:aDocumentView];
[windowView addSubview:aScrollView];
[self assert:CPScrollerStyleOverlay equals:[aScrollView scrollerStyle] message:@"Global scroller style was not applied to this CPScrollView"];
// In this test, we have global scroller style set to overlay. We create a scroll view, set the scroller style to legacy BEFORE placing the
// scroll view in the view hierarchy, then insert it. It should have legacy as scroller style.
var anotherScrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)],
anotherDocumentView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[anotherScrollView setScrollerStyle:CPScrollerStyleLegacy];
[anotherScrollView setDocumentView:anotherDocumentView];
[windowView addSubview:anotherScrollView];
[self assert:CPScrollerStyleLegacy equals:[anotherScrollView scrollerStyle] message:@"Specific scroller style was overrided by global scroller style"];
}
- (void)assertPoint:(CGPoint)expected equals:(CGPoint)actual message:(CPString)message
{
[self assert:expected.x equals:actual.x message:@"X: " + message];
+2 -6
View File
@@ -528,7 +528,7 @@
{
[self assertTrue:[dataView hasThemeState:CPThemeStateTableDataView] message:"CPThemeStateTableDataView should be enabled"];
[self assertFalse:[dataView hasThemeState:CPThemeStateSelectedDataView] message:"CPThemeStateSelectedDataView should be disabled"];
//[self assertTrue:[dataView hasThemeState:CPThemeStateFirstResponder] message:"CPThemeStateFirstResponder should be enabled"];
[self assertTrue:[dataView hasThemeState:CPThemeStateFirstResponder] message:"CPThemeStateFirstResponder should be enabled"];
}
enumerateViewsInRowsCall++;
@@ -548,7 +548,7 @@
{
[self assertTrue:[dataView hasThemeState:CPThemeStateTableDataView] message:"CPThemeStateTableDataView should be enabled"];
[self assertFalse:[dataView hasThemeState:CPThemeStateSelectedDataView] message:"CPThemeStateSelectedDataView should be disabled"];
// [self assertTrue:[dataView hasThemeState:CPThemeStateFirstResponder] message:"CPThemeStateFirstResponder should be enabled"];
[self assertTrue:[dataView hasThemeState:CPThemeStateFirstResponder] message:"CPThemeStateFirstResponder should be enabled"];
}
}];
@@ -740,10 +740,6 @@
{
return acceptsFirstResponder;
}
- (BOOL)_isFocused
{
return [[self window] firstResponder] === self;
}
@end
+8 -143
View File
@@ -17,21 +17,22 @@
CPTreeController _treeController @accessors(property=treeController);
CPArray _contentArray @accessors(property=contentArray);
CPMutableArray _observedKeyPaths;
CPArray observations;
int aCount @accessors;
}
- (CPArray)makeTestTree
{
var engineering = [OrgNode nodeWithName:@"Engineering"],
marketing = [OrgNode nodeWithName:@"Marketing"];
marketing = [OrgNode nodeWithName:@"Marketing"];
var webTeam = [OrgNode nodeWithName:@"Web Team"],
backendTeam = [OrgNode nodeWithName:@"Backend Team"];
backendTeam = [OrgNode nodeWithName:@"Backend Team"];
[engineering setChildren:[CPMutableArray arrayWithObjects:webTeam, backendTeam]];
var dev1 = [OrgNode nodeWithName:@"Francisco"],
dev2 = [OrgNode nodeWithName:@"Ross"];
dev2 = [OrgNode nodeWithName:@"Ross"];
[webTeam setChildren:[CPMutableArray arrayWithObjects:dev1, dev2]];
@@ -42,23 +43,12 @@
{
[[CPApplication alloc] init];
_observedKeyPaths = [CPMutableArray array];
_contentArray = [self makeTestTree];
_treeController = [[CPTreeController alloc] init];
[_treeController setChildrenKeyPath:@"children"];
[_treeController setContent:[_contentArray copy]];
}
- (void)tearDown
{
_observedKeyPaths = nil;
}
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)aChange context:(id)aContext
{
[_observedKeyPaths addObject:aKeyPath];
}
- (void)testInitWithContent
{
[self assert:[_contentArray count] equals:[[_treeController contentArray] count]];
@@ -109,7 +99,7 @@
[controller insertObject:newDev atArrangedObjectIndexPath:insertPath];
var engineering = [[controller contentArray] objectAtIndex:0],
backendTeam = [[engineering children] objectAtIndex:1];
backendTeam = [[engineering children] objectAtIndex:1];
[self assert:1 equals:[[backendTeam children] count] message:@"Child should be added to the model object's children array"];
[self assert:@"Tom" equals:[[[backendTeam children] objectAtIndex:0] name]];
}
@@ -142,7 +132,7 @@
[controller removeObjectAtArrangedObjectIndexPath:path];
var engineering = [[controller contentArray] objectAtIndex:0],
webTeam = [[engineering children] objectAtIndex:0];
webTeam = [[engineering children] objectAtIndex:0];
[self assert:1 equals:[[webTeam children] count] message:@"Francisco should be removed, leaving only Ross"];
[self assert:@"Ross" equals:[[[webTeam children] objectAtIndex:0] name]];
@@ -186,8 +176,7 @@
// "Marketing" is now at index 0
[self assert:[CPIndexPath indexPathWithIndex:0] equals:[controller selectionIndexPath]];
// Test behavior when AvoidsEmptySelection is NO
[controller insertObject:[OrgNode nodeWithName:@"New Dept"] atArrangedObjectIndexPath:[CPIndexPath indexPathWithIndex:1]];
// Test behavior when AvoidsEmptySelection is NO[controller insertObject:[OrgNode nodeWithName:@"New Dept"] atArrangedObjectIndexPath:[CPIndexPath indexPathWithIndex:1]];
[controller setAvoidsEmptySelection:NO];
// Reselect "Marketing" at index 0
@@ -240,130 +229,6 @@
[self assert:@"Marketing" equals:[[selectedObjects objectAtIndex:0] name]];
}
/*
* New Tests for Selection Bindings, KVO, and UI Action Status
*/
- (void)testExposedBindings
{
var exposedBindings = [CPTreeController exposedBindings];
[self assertTrue:[exposedBindings containsObject:@"contentArray"] message:@"contentArray should be exposed"];
[self assertTrue:[exposedBindings containsObject:@"sortDescriptors"] message:@"sortDescriptors should be exposed"];
[self assertTrue:[exposedBindings containsObject:@"selectionIndexPaths"] message:@"selectionIndexPaths should be exposed"];
[self assertTrue:[exposedBindings containsObject:@"selectionIndexPath"] message:@"selectionIndexPath should be exposed"];
[self assertTrue:[exposedBindings containsObject:@"selectedObjects"] message:@"selectedObjects should be exposed"];
}
- (void)testDetailBindingToSelectionProxyUpdatesOnSelectionChange
{
var controller = [self treeController],
textField = [[CPTextField alloc] init];
[textField bind:@"value" toObject:controller withKeyPath:@"selection.name" options:nil];
// Select "Engineering" (index 0)
[controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:0]];
[self assert:@"Engineering" equals:[textField stringValue] message:@"Bound view should reflect root selection"];
// Select "Web Team" (index [0, 0])
var nestedPath = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0];
[controller setSelectionIndexPath:nestedPath];
[self assert:@"Web Team" equals:[textField stringValue] message:@"Bound view should reflect nested selection"];
// Select "Marketing" (index 1)
[controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:1]];
[self assert:@"Marketing" equals:[textField stringValue] message:@"Bound view should reflect changed selection"];
}
- (void)testDetailBindingUpdatesOnSetContent
{
var controller = [self treeController],
textField = [[CPTextField alloc] init];
[textField bind:@"value" toObject:controller withKeyPath:@"selection.name" options:nil];
// Select "Engineering"
[controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:0]];
[self assert:@"Engineering" equals:[textField stringValue]];
// Replace content
var newDept = [OrgNode nodeWithName:@"Design Dept"];
[controller setContent:[CPMutableArray arrayWithObject:newDept]];
// Detail binding should update to the preserved selection or new root
[self assert:@"Design Dept" equals:[textField stringValue] message:@"Detail binding must update when content changes"];
}
- (void)testSelectedObjectsKVOTriggeredOnContentChange
{
var controller = [self treeController];
[controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:0]];
[controller addObserver:self forKeyPath:@"selectedObjects" options:0 context:nil];
// Set new content
var newDept = [OrgNode nodeWithName:@"Operations"];
[controller setContent:[CPMutableArray arrayWithObject:newDept]];
[self assertTrue:[_observedKeyPaths containsObject:@"selectedObjects"] message:@"selectedObjects KVO notification must fire when content changes"];
[controller removeObserver:self forKeyPath:@"selectedObjects"];
}
- (void)testCanInsertDependsOnEditable
{
var controller = [self treeController];
[controller addObserver:self forKeyPath:@"canInsert" options:0 context:nil];
[controller setEditable:YES];
[self assertTrue:[controller canInsert]];
[_observedKeyPaths removeAllObjects];
[controller setEditable:NO];
[self assertFalse:[controller canInsert] message:@"canInsert should be NO when editable is NO"];
[self assertTrue:[_observedKeyPaths containsObject:@"canInsert"] message:@"canInsert KVO must fire when editable changes"];
[controller removeObserver:self forKeyPath:@"canInsert"];
}
- (void)testCanAddChildAndCanInsertChildDependOnEditableAndSelection
{
var controller = [self treeController];
[controller setEditable:YES];
[controller addObserver:self forKeyPath:@"canAddChild" options:0 context:nil];
[controller addObserver:self forKeyPath:@"canInsertChild" options:0 context:nil];
// Empty selection -> canAddChild/canInsertChild should be NO
[controller setSelectionIndexPaths:[CPArray array]];
[self assertFalse:[controller canAddChild]];
[self assertFalse:[controller canInsertChild]];
[_observedKeyPaths removeAllObjects];
// Select an item -> canAddChild/canInsertChild should become YES
[controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:0]];
[self assertTrue:[controller canAddChild]];
[self assertTrue:[controller canInsertChild]];
[self assertTrue:[_observedKeyPaths containsObject:@"canAddChild"] message:@"canAddChild KVO should fire on selection change"];
[self assertTrue:[_observedKeyPaths containsObject:@"canInsertChild"] message:@"canInsertChild KVO should fire on selection change"];
[_observedKeyPaths removeAllObjects];
// Set editable to NO -> canAddChild/canInsertChild should become NO
[controller setEditable:NO];
[self assertFalse:[controller canAddChild]];
[self assertFalse:[controller canInsertChild]];
[self assertTrue:[_observedKeyPaths containsObject:@"canAddChild"] message:@"canAddChild KVO should fire when editable changes"];
[self assertTrue:[_observedKeyPaths containsObject:@"canInsertChild"] message:@"canInsertChild KVO should fire when editable changes"];
[controller removeObserver:self forKeyPath:@"canAddChild"];
[controller removeObserver:self forKeyPath:@"canInsertChild"];
}
@end
/*
+10 -474
View File
@@ -1,495 +1,31 @@
@import <AppKit/CPTreeNode.j>
@import <Foundation/CPIndexPath.j>
@import <Foundation/CPSortDescriptor.j>
@import <Foundation/CPKeyedArchiver.j>
@implementation CPTreeNodeTest : OJTestCase
{
CPTreeNode root;
CPTreeNode child1;
CPTreeNode child2;
CPMutableArray _kvoRecordedChanges;
CPTreeNode treeNode;
CPTreeNode childNode;
}
- (void)setUp
{
// This will init the global var CPApp which are used internally in the AppKit
[[CPApplication alloc] init];
root = [CPTreeNode treeNodeWithRepresentedObject:@"root"];
child1 = [CPTreeNode treeNodeWithRepresentedObject:@"child1"];
child2 = [CPTreeNode treeNodeWithRepresentedObject:@"child2"];
treeNode = [CPTreeNode treeNodeWithRepresentedObject:nil];
_kvoRecordedChanges = [];
childNode = [CPTreeNode treeNodeWithRepresentedObject:nil];
[treeNode insertObject:childNode inChildNodesAtIndex:0];
}
/*
* Records each KVO notification delivered to this test instance.
* Used by the KVO-contract tests below.
*/
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)aChange context:(id)aContext
{
[_kvoRecordedChanges addObject:aChange];
}
// 1. creation and representedObject
- (void)testCreationAndRepresentedObject
{
[self assert:@"root" equals:[root representedObject]];
[self assertTrue:([root isKindOfClass:[CPTreeNode class]])];
}
// 2. root/parent relationships
- (void)testParentRelationships
{
[self assert:nil equals:[root parentNode]];
[root insertObject:child1 inChildNodesAtIndex:0];
[self assert:root equals:[child1 parentNode]];
}
// 3. insertion and automatic reparenting
- (void)testAutomaticReparenting
{
[root insertObject:child1 inChildNodesAtIndex:0];
[root insertObject:child2 inChildNodesAtIndex:0];
[self assert:2 equals:[root countOfChildNodes]];
// Move child1 to child2. It should be removed from root automatically.
[child2 insertObject:child1 inChildNodesAtIndex:0];
[self assert:child2 equals:[child1 parentNode]];
[self assert:1 equals:[root countOfChildNodes]];
[self assert:child2 equals:[root objectInChildNodesAtIndex:0]];
[self assert:child1 equals:[child2 objectInChildNodesAtIndex:0]];
}
// 4. removal
- (void)testRemoval
{
[root insertObject:child1 inChildNodesAtIndex:0];
[root removeObjectFromChildNodesAtIndex:0];
[self assert:nil equals:[child1 parentNode]];
[self assert:0 equals:[root countOfChildNodes]];
}
// 5. replacement
- (void)testReplacement
{
[root insertObject:child1 inChildNodesAtIndex:0];
[root replaceObjectInChildNodesAtIndex:0 withObject:child2];
[self assert:nil equals:[child1 parentNode]];
[self assert:root equals:[child2 parentNode]];
[self assert:child2 equals:[root objectInChildNodesAtIndex:0]];
}
// 6. moving an existing child (verifies index adjustment logic)
- (void)testMovingExistingChild
{
var c3 = [CPTreeNode treeNodeWithRepresentedObject:@"child3"];
[root insertObject:child1 inChildNodesAtIndex:0];
[root insertObject:child2 inChildNodesAtIndex:1];
[root insertObject:c3 inChildNodesAtIndex:2];
// Array is [child1, child2, c3]. Move child1 (index 0) to index 2.
[root insertObject:child1 inChildNodesAtIndex:2];
[self assert:child2 equals:[root objectInChildNodesAtIndex:0]];
[self assert:c3 equals:[root objectInChildNodesAtIndex:1]];
[self assert:child1 equals:[root objectInChildNodesAtIndex:2]];
// Move child1 (index 2) back to index 0.
[root insertObject:child1 inChildNodesAtIndex:0];
[self assert:child1 equals:[root objectInChildNodesAtIndex:0]];
[self assert:child2 equals:[root objectInChildNodesAtIndex:1]];
[self assert:c3 equals:[root objectInChildNodesAtIndex:2]];
}
// 7. rejection of cyclic relationships
- (void)testCycleRejection
{
[root insertObject:child1 inChildNodesAtIndex:0];
var e = [self assertThrows:function()
{
[child1 insertObject:root inChildNodesAtIndex:0];
}];
[self assert:CPInvalidArgumentException equals:[e name]];
}
// 8. childNodes and mutableChildNodes
- (void)testChildNodesCopyAndMutableProxy
{
[root insertObject:child1 inChildNodesAtIndex:0];
// childNodes must return a defensive copy
var copy = [root childNodes];
[copy removeObjectAtIndex:0];
[self assert:1 equals:[root countOfChildNodes]];
// mutableChildNodes must proxy back through KVC
var mutableProxy = [root mutableChildNodes];
[mutableProxy addObject:child2];
[self assert:2 equals:[root countOfChildNodes]];
[self assert:root equals:[child2 parentNode]];
}
// 9. index paths
- (void)testIndexPaths
{
[self assert:[CPIndexPath indexPathWithIndexes:[]] equals:[root indexPath]];
[root insertObject:child1 inChildNodesAtIndex:0];
[self assert:[CPIndexPath indexPathWithIndex:0] equals:[child1 indexPath]];
[child1 insertObject:child2 inChildNodesAtIndex:0];
[self assert:[CPIndexPath indexPathWithIndexes:[0, 0]] equals:[child2 indexPath]];
}
// 10. descendant lookup
- (void)testDescendantNodeAtIndexPath
{
[root insertObject:child1 inChildNodesAtIndex:0];
[child1 insertObject:child2 inChildNodesAtIndex:0];
var indexPath = [CPIndexPath indexPathWithIndex:0];
var path = [CPIndexPath indexPathWithIndexes:[0, 0]];
[self assert:child2 equals:[root descendantNodeAtIndexPath:path]];
[self assert:childNode equals:[treeNode descendantNodeAtIndexPath:indexPath]];
var invalidPath = [CPIndexPath indexPathWithIndexes:[1, 0]];
[self assert:nil equals:[root descendantNodeAtIndexPath:invalidPath]];
}
indexPath = [CPIndexPath indexPathWithIndex:1];
// 11. recursive sorting
- (void)testRecursiveSorting
{
var nodeA = [CPTreeNode treeNodeWithRepresentedObject:@"A"];
var nodeC = [CPTreeNode treeNodeWithRepresentedObject:@"C"];
var nodeB = [CPTreeNode treeNodeWithRepresentedObject:@"B"];
[root insertObject:nodeC inChildNodesAtIndex:0];
[root insertObject:nodeA inChildNodesAtIndex:1];
[root insertObject:nodeB inChildNodesAtIndex:2];
// Add children to nodeB to verify recursion
var childB2 = [CPTreeNode treeNodeWithRepresentedObject:@"B2"];
var childB1 = [CPTreeNode treeNodeWithRepresentedObject:@"B1"];
[nodeB insertObject:childB2 inChildNodesAtIndex:0];
[nodeB insertObject:childB1 inChildNodesAtIndex:1];
var sd = [[CPSortDescriptor alloc] initWithKey:@"representedObject" ascending:YES];
[root sortWithSortDescriptors:[sd] recursively:YES];
[self assert:nodeA equals:[root objectInChildNodesAtIndex:0]];
[self assert:nodeB equals:[root objectInChildNodesAtIndex:1]];
[self assert:nodeC equals:[root objectInChildNodesAtIndex:2]];
[self assert:childB1 equals:[nodeB objectInChildNodesAtIndex:0]];
[self assert:childB2 equals:[nodeB objectInChildNodesAtIndex:1]];
}
// 12. NS/Cocoa-style KVC mutation behavior (Strict validation)
- (void)testStrictChildValidation
{
var e = [self assertThrows:function()
{
[root insertObject:[CPObject new] inChildNodesAtIndex:0];
}];
[self assert:CPInvalidArgumentException equals:[e name]];
}
// 13. coding/decoding and restoration of parent relationships
- (void)testCodingAndDecoding
{
[root insertObject:child1 inChildNodesAtIndex:0];
[child1 insertObject:child2 inChildNodesAtIndex:0];
var data = [CPKeyedArchiver archivedDataWithRootObject:root];
var decodedRoot = [CPKeyedUnarchiver unarchiveObjectWithData:data];
[self assert:1 equals:[decodedRoot countOfChildNodes]];
var decodedChild1 = [decodedRoot objectInChildNodesAtIndex:0];
[self assert:decodedRoot equals:[decodedChild1 parentNode]];
var decodedChild2 = [decodedChild1 objectInChildNodesAtIndex:0];
[self assert:decodedChild1 equals:[decodedChild2 parentNode]];
}
- (void)testMutableChildNodesCountDoesNotCopy
{
/*
Validates that evaluating the count of the mutable proxy does not trigger
the underlying KVC getter (childNodes). The getter returns a defensive
copy. Invoking it for a simple count degrades an O(1) operation to O(N)
allocations.
*/
var spy = [[CPTreeNodeCountingSpy alloc] initWithRepresentedObject:@"spy"];
for (var i = 0; i < 50; i++)
[spy insertObject:[CPTreeNode treeNodeWithRepresentedObject:i] inChildNodesAtIndex:i];
[spy setChildNodesCallCount:0];
[[spy mutableChildNodes] count];
[self assert:0 equals:[spy childNodesCallCount]
message:"count via mutableChildNodes should not invoke the copying childNodes accessor"];
}
// 14. replacing at an index with a child already present at a different index (same parent)
- (void)testReplaceExistingChildSameParent
{
var c3 = [CPTreeNode treeNodeWithRepresentedObject:@"child3"];
[root insertObject:child1 inChildNodesAtIndex:0];
[root insertObject:child2 inChildNodesAtIndex:1];
[root insertObject:c3 inChildNodesAtIndex:2];
// Array is [child1, child2, c3]. Replace the object at index 2 (c3) with child1.
[root replaceObjectInChildNodesAtIndex:2 withObject:child1];
[self assert:2 equals:[root countOfChildNodes]];
[self assert:child2 equals:[root objectInChildNodesAtIndex:0]];
[self assert:child1 equals:[root objectInChildNodesAtIndex:1]];
[self assert:root equals:[child1 parentNode]];
[self assert:nil equals:[c3 parentNode]];
}
// 15. rejection of cyclic relationships via replacement
- (void)testReplaceCycleRejection
{
var c3 = [CPTreeNode treeNodeWithRepresentedObject:@"child3"];
[root insertObject:child1 inChildNodesAtIndex:0];
[child1 insertObject:child2 inChildNodesAtIndex:0];
[child2 insertObject:c3 inChildNodesAtIndex:0];
var e = [self assertThrows:function()
{
[child2 replaceObjectInChildNodesAtIndex:0 withObject:root];
}];
[self assert:CPInvalidArgumentException equals:[e name]];
}
// 16. KVO: same-parent move reports a paired removal and insertion on childNodes
//
// Disabled: structurally impossible with the current accessor-call approach.
// _CPKVOProxy coalesces nested willChange/didChange calls for the same key
// on the same object (see _sendNotificationsForKey:changeOptions:isBefore:
// in CPKeyValueObserving.j); the inner Removal bracket opened by
// removeObjectFromChildNodesAtIndex: is silently discarded inside the outer
// Insertion bracket already open on root. Fixing this requires firing a
// single CPKeyValueChangeReplacement instead of two nested accessor calls.
- (void)disabled_testKVONotificationsDuringSameParentMove
{
var c3 = [CPTreeNode treeNodeWithRepresentedObject:@"child3"];
[root insertObject:child1 inChildNodesAtIndex:0];
[root insertObject:child2 inChildNodesAtIndex:1];
[root insertObject:c3 inChildNodesAtIndex:2];
[root addObserver:self forKeyPath:@"childNodes" options:0 context:nil];
// Array is [child1, child2, c3]. Move child1 (index 0) to index 2.
[root insertObject:child1 inChildNodesAtIndex:2];
[root removeObserver:self forKeyPath:@"childNodes"];
var removals = 0,
insertions = 0,
count = [_kvoRecordedChanges count];
for (var i = 0; i < count; i++)
{
var kind = [[_kvoRecordedChanges objectAtIndex:i] objectForKey:CPKeyValueChangeKindKey];
if (kind === CPKeyValueChangeRemoval)
removals++;
else if (kind === CPKeyValueChangeInsertion)
insertions++;
}
[self assert:1 equals:removals
message:"a same-parent move must report a removal at the original position"];
[self assert:1 equals:insertions
message:"a same-parent move must report an insertion at the target position"];
}
// 17. KVO: cross-parent move reports a removal on the old parent and an insertion on the new parent
- (void)testKVONotificationsDuringCrossParentMove
{
[root insertObject:child1 inChildNodesAtIndex:0];
[root addObserver:self forKeyPath:@"childNodes" options:0 context:nil];
[child2 addObserver:self forKeyPath:@"childNodes" options:0 context:nil];
// child1 currently belongs to root. Move it under child2.
[child2 insertObject:child1 inChildNodesAtIndex:0];
[root removeObserver:self forKeyPath:@"childNodes"];
[child2 removeObserver:self forKeyPath:@"childNodes"];
var removals = 0,
insertions = 0,
count = [_kvoRecordedChanges count];
for (var i = 0; i < count; i++)
{
var kind = [[_kvoRecordedChanges objectAtIndex:i] objectForKey:CPKeyValueChangeKindKey];
if (kind === CPKeyValueChangeRemoval)
removals++;
else if (kind === CPKeyValueChangeInsertion)
insertions++;
}
[self assert:1 equals:removals
message:"the old parent's childNodes must report the removal"];
[self assert:1 equals:insertions
message:"the new parent's childNodes must report the insertion"];
}
// 18. KVO: reparenting notifies observers of parentNode
//
// Disabled: parentNode has no setParentNode:, so there is no selector for
// the KVO swizzler to instrument. Not a bug to fix incrementally; requires
// deciding whether parentNode becomes a real settable property.
- (void)disabled_testParentNodeNotifiesOnMove
{
[root insertObject:child1 inChildNodesAtIndex:0];
[child1 addObserver:self forKeyPath:@"parentNode" options:0 context:nil];
// child1 currently belongs to root. Move it under child2.
[child2 insertObject:child1 inChildNodesAtIndex:0];
[child1 removeObserver:self forKeyPath:@"parentNode"];
[self assert:1 equals:[_kvoRecordedChanges count]
message:"reparenting must notify observers of parentNode"];
}
// 19. mutableChildNodes proxy: remove and replace
- (void)testMutableChildNodesProxyRemoveAndReplace
{
[root insertObject:child1 inChildNodesAtIndex:0];
[root insertObject:child2 inChildNodesAtIndex:1];
var proxy = [root mutableChildNodes];
[proxy removeObjectAtIndex:0];
[self assert:1 equals:[root countOfChildNodes]];
[self assert:child2 equals:[root objectInChildNodesAtIndex:0]];
[self assert:nil equals:[child1 parentNode]];
var c3 = [CPTreeNode treeNodeWithRepresentedObject:@"child3"];
[proxy replaceObjectAtIndex:0 withObject:c3];
[self assert:c3 equals:[root objectInChildNodesAtIndex:0]];
[self assert:root equals:[c3 parentNode]];
[self assert:nil equals:[child2 parentNode]];
}
// 20. range validation on insertion
- (void)testInsertOutOfBoundsRaises
{
var e1 = [self assertThrows:function()
{
[root insertObject:child1 inChildNodesAtIndex:-1];
}];
[self assert:CPRangeException equals:[e1 name]];
var e2 = [self assertThrows:function()
{
[root insertObject:child1 inChildNodesAtIndex:1];
}];
[self assert:CPRangeException equals:[e2 name]];
}
// 21. non-recursive sort leaves descendants untouched
- (void)testSortNonRecursive
{
var nodeA = [CPTreeNode treeNodeWithRepresentedObject:@"A"];
var nodeC = [CPTreeNode treeNodeWithRepresentedObject:@"C"];
var nodeB = [CPTreeNode treeNodeWithRepresentedObject:@"B"];
[root insertObject:nodeC inChildNodesAtIndex:0];
[root insertObject:nodeA inChildNodesAtIndex:1];
[root insertObject:nodeB inChildNodesAtIndex:2];
var childB2 = [CPTreeNode treeNodeWithRepresentedObject:@"B2"];
var childB1 = [CPTreeNode treeNodeWithRepresentedObject:@"B1"];
[nodeB insertObject:childB2 inChildNodesAtIndex:0];
[nodeB insertObject:childB1 inChildNodesAtIndex:1];
var sd = [[CPSortDescriptor alloc] initWithKey:@"representedObject" ascending:YES];
[root sortWithSortDescriptors:[sd] recursively:NO];
[self assert:nodeA equals:[root objectInChildNodesAtIndex:0]];
[self assert:nodeB equals:[root objectInChildNodesAtIndex:1]];
[self assert:nodeC equals:[root objectInChildNodesAtIndex:2]];
// Descendants of nodeB must remain in insertion order; only the top level was sorted.
[self assert:childB2 equals:[nodeB objectInChildNodesAtIndex:0]];
[self assert:childB1 equals:[nodeB objectInChildNodesAtIndex:1]];
}
// 22. leaf status
- (void)testIsLeaf
{
[self assertTrue:[root isLeaf]];
[root insertObject:child1 inChildNodesAtIndex:0];
[self assertFalse:[root isLeaf]];
[self assertTrue:[child1 isLeaf]];
}
// 23. descendant lookup for nil and zero-length index paths
- (void)testDescendantNodeAtIndexPathDegenerateCases
{
[root insertObject:child1 inChildNodesAtIndex:0];
[self assert:root equals:[root descendantNodeAtIndexPath:nil]];
[self assert:root equals:[root descendantNodeAtIndexPath:[CPIndexPath indexPathWithIndexes:[]]]];
}
@end
@implementation CPTreeNodeCountingSpy : CPTreeNode
{
CPInteger _childNodesCallCount @accessors(property=childNodesCallCount);
}
- (id)initWithRepresentedObject:(id)anObject
{
self = [super initWithRepresentedObject:anObject];
if (self)
_childNodesCallCount = 0;
return self;
}
- (CPArray)childNodes
{
_childNodesCallCount++;
return [super childNodes];
[self assert:nil equals:[treeNode descendantNodeAtIndexPath:indexPath]];
}
@end
@@ -1,63 +1,63 @@
<?xml version='1.0' encoding='UTF-8'?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment version="1050" identifier="macosx" />
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117" />
<deployment version="1050" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="450" id="451" />
<outlet property="delegate" destination="450" id="451"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder" />
<customObject id="-3" userLabel="Application" customClass="NSObject" />
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<menu title="AMainMenu" systemMenu="main" id="29" userLabel="MainMenu">
<items>
<menuItem title="CPAnimationContextTest" id="56">
<menu key="submenu" title="CPAnimationContextTest" systemMenu="apple" id="57">
<menuItem title="NewApplication" id="56">
<menu key="submenu" title="NewApplication" systemMenu="apple" id="57">
<items>
<menuItem title="About CPAnimationContextTest" id="58">
<modifierMask key="keyEquivalentModifierMask" />
<menuItem title="About NewApplication" id="58">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-2" id="142" />
<action selector="orderFrontStandardAboutPanel:" target="-2" id="142"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="236">
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Preferences…" keyEquivalent="," id="129" userLabel="121" />
<menuItem title="Preferences…" keyEquivalent="," id="129" userLabel="121"/>
<menuItem isSeparatorItem="YES" id="143">
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Services" id="131">
<menu key="submenu" title="Services" systemMenu="services" id="130" />
<menu key="submenu" title="Services" systemMenu="services" id="130"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="144">
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Hide CPAnimationContextTest" keyEquivalent="h" id="134">
<menuItem title="Hide NewApplication" keyEquivalent="h" id="134">
<connections>
<action selector="hide:" target="-1" id="367" />
<action selector="hide:" target="-1" id="367"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="145">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="368" />
<action selector="hideOtherApplications:" target="-1" id="368"/>
</connections>
</menuItem>
<menuItem title="Show All" id="150">
<connections>
<action selector="unhideAllApplications:" target="-1" id="370" />
<action selector="unhideAllApplications:" target="-1" id="370"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="149">
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Quit CPAnimationContextTest" keyEquivalent="q" id="136" userLabel="1111">
<menuItem title="Quit NewApplication" keyEquivalent="q" id="136" userLabel="1111">
<connections>
<action selector="terminate:" target="-3" id="449" />
<action selector="terminate:" target="-3" id="449"/>
</connections>
</menuItem>
</items>
@@ -68,12 +68,12 @@
<items>
<menuItem title="New" keyEquivalent="n" id="82" userLabel="9">
<connections>
<action selector="newDocument:" target="-1" id="373" />
<action selector="newDocument:" target="-1" id="373"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="72">
<connections>
<action selector="openDocument:" target="-1" id="374" />
<action selector="openDocument:" target="-1" id="374"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="124">
@@ -81,49 +81,49 @@
<items>
<menuItem title="Clear Menu" id="126">
<connections>
<action selector="clearRecentDocuments:" target="-1" id="127" />
<action selector="clearRecentDocuments:" target="-1" id="127"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="79" userLabel="7">
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Close" keyEquivalent="w" id="73" userLabel="1">
<connections>
<action selector="performClose:" target="-1" id="193" />
<action selector="performClose:" target="-1" id="193"/>
</connections>
</menuItem>
<menuItem title="Save" keyEquivalent="s" id="75" userLabel="3">
<connections>
<action selector="saveDocument:" target="-1" id="362" />
<action selector="saveDocument:" target="-1" id="362"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="80" userLabel="8">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="saveDocumentAs:" target="-1" id="363" />
<action selector="saveDocumentAs:" target="-1" id="363"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" id="112" userLabel="10">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="revertDocumentToSaved:" target="-1" id="364" />
<action selector="revertDocumentToSaved:" target="-1" id="364"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="74" userLabel="2">
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Page Setup..." keyEquivalent="P" id="77" userLabel="5">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="-1" id="87" />
<action selector="runPageLayout:" target="-1" id="87"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="78" userLabel="6">
<connections>
<action selector="print:" target="-1" id="86" />
<action selector="print:" target="-1" id="86"/>
</connections>
</menuItem>
</items>
@@ -134,62 +134,62 @@
<items>
<menuItem title="Undo" keyEquivalent="z" id="207">
<connections>
<action selector="undo:" target="-1" id="223" />
<action selector="undo:" target="-1" id="223"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="215">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="redo:" target="-1" id="231" />
<action selector="redo:" target="-1" id="231"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="206">
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Cut" keyEquivalent="x" id="199">
<connections>
<action selector="cut:" target="-1" id="228" />
<action selector="cut:" target="-1" id="228"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="197">
<connections>
<action selector="copy:" target="-1" id="224" />
<action selector="copy:" target="-1" id="224"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="203">
<connections>
<action selector="paste:" target="-1" id="226" />
<action selector="paste:" target="-1" id="226"/>
</connections>
</menuItem>
<menuItem title="Delete" id="202">
<connections>
<action selector="delete:" target="-1" id="235" />
<action selector="delete:" target="-1" id="235"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="198">
<connections>
<action selector="selectAll:" target="-1" id="232" />
<action selector="selectAll:" target="-1" id="232"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="214">
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Find" id="218">
<menu key="submenu" title="Find" id="220">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="209">
<connections>
<action selector="performFindPanelAction:" target="-1" id="241" />
<action selector="performFindPanelAction:" target="-1" id="241"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="208" />
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="208"/>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="213">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="221" />
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="221"/>
<menuItem title="Jump to Selection" keyEquivalent="j" id="210">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="245" />
<action selector="centerSelectionInVisibleArea:" target="-1" id="245"/>
</connections>
</menuItem>
</items>
@@ -200,22 +200,22 @@
<items>
<menuItem title="Show Spelling…" keyEquivalent=":" id="204">
<connections>
<action selector="showGuessPanel:" target="-1" id="230" />
<action selector="showGuessPanel:" target="-1" id="230"/>
</connections>
</menuItem>
<menuItem title="Check Spelling" keyEquivalent=";" id="201">
<connections>
<action selector="checkSpelling:" target="-1" id="225" />
<action selector="checkSpelling:" target="-1" id="225"/>
</connections>
</menuItem>
<menuItem title="Check Spelling While Typing" id="219">
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="222" />
<action selector="toggleContinuousSpellChecking:" target="-1" id="222"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="346">
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="347" />
<action selector="toggleGrammarChecking:" target="-1" id="347"/>
</connections>
</menuItem>
</items>
@@ -226,18 +226,18 @@
<items>
<menuItem title="Smart Copy/Paste" tag="1" keyEquivalent="f" id="350">
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="355" />
<action selector="toggleSmartInsertDelete:" target="-1" id="355"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" tag="2" keyEquivalent="g" id="351">
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="356" />
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="356"/>
</connections>
</menuItem>
<menuItem title="Smart Links" tag="3" keyEquivalent="G" id="354">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="357" />
<action selector="toggleAutomaticLinkDetection:" target="-1" id="357"/>
</connections>
</menuItem>
</items>
@@ -248,12 +248,12 @@
<items>
<menuItem title="Start Speaking" id="196">
<connections>
<action selector="startSpeaking:" target="-1" id="233" />
<action selector="startSpeaking:" target="-1" id="233"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="195">
<connections>
<action selector="stopSpeaking:" target="-1" id="227" />
<action selector="stopSpeaking:" target="-1" id="227"/>
</connections>
</menuItem>
</items>
@@ -263,182 +263,182 @@
</menu>
</menuItem>
<menuItem title="Format" id="375">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="376">
<items>
<menuItem title="Font" id="377">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="388">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="389" />
<menuItem title="Bold" tag="2" keyEquivalent="b" id="390" />
<menuItem title="Italic" tag="1" keyEquivalent="i" id="391" />
<menuItem title="Show Fonts" keyEquivalent="t" id="389"/>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="390"/>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="391"/>
<menuItem title="Underline" keyEquivalent="u" id="392">
<connections>
<action selector="underline:" target="-1" id="432" />
<action selector="underline:" target="-1" id="432"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="393" />
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="394" />
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="395" />
<menuItem isSeparatorItem="YES" id="396" />
<menuItem isSeparatorItem="YES" id="393"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="394"/>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="395"/>
<menuItem isSeparatorItem="YES" id="396"/>
<menuItem title="Kern" id="397">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="415">
<items>
<menuItem title="Use Default" id="416">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="-1" id="438" />
<action selector="useStandardKerning:" target="-1" id="438"/>
</connections>
</menuItem>
<menuItem title="Use None" id="417">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="-1" id="441" />
<action selector="turnOffKerning:" target="-1" id="441"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="418">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="-1" id="431" />
<action selector="tightenKerning:" target="-1" id="431"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="419">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="-1" id="435" />
<action selector="loosenKerning:" target="-1" id="435"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligature" id="398">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligature" id="411">
<items>
<menuItem title="Use Default" id="412">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="-1" id="439" />
<action selector="useStandardLigatures:" target="-1" id="439"/>
</connections>
</menuItem>
<menuItem title="Use None" id="413">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="-1" id="440" />
<action selector="turnOffLigatures:" target="-1" id="440"/>
</connections>
</menuItem>
<menuItem title="Use All" id="414">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="-1" id="434" />
<action selector="useAllLigatures:" target="-1" id="434"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="399">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="405">
<items>
<menuItem title="Use Default" id="406">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="-1" id="437" />
<action selector="unscript:" target="-1" id="437"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="407">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="-1" id="430" />
<action selector="superscript:" target="-1" id="430"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="408">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="-1" id="429" />
<action selector="subscript:" target="-1" id="429"/>
</connections>
</menuItem>
<menuItem title="Raise" id="409">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="-1" id="426" />
<action selector="raiseBaseline:" target="-1" id="426"/>
</connections>
</menuItem>
<menuItem title="Lower" id="410">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="-1" id="427" />
<action selector="lowerBaseline:" target="-1" id="427"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="400" />
<menuItem isSeparatorItem="YES" id="400"/>
<menuItem title="Show Colors" keyEquivalent="C" id="401">
<connections>
<action selector="orderFrontColorPanel:" target="-1" id="433" />
<action selector="orderFrontColorPanel:" target="-1" id="433"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="402" />
<menuItem isSeparatorItem="YES" id="402"/>
<menuItem title="Copy Style" keyEquivalent="c" id="403">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="-1" id="428" />
<action selector="copyFont:" target="-1" id="428"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="404">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="-1" id="436" />
<action selector="pasteFont:" target="-1" id="436"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="378">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="379">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="380">
<connections>
<action selector="alignLeft:" target="-1" id="442" />
<action selector="alignLeft:" target="-1" id="442"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="381">
<connections>
<action selector="alignCenter:" target="-1" id="445" />
<action selector="alignCenter:" target="-1" id="445"/>
</connections>
</menuItem>
<menuItem title="Justify" id="382">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="-1" id="443" />
<action selector="alignJustified:" target="-1" id="443"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="383">
<connections>
<action selector="alignRight:" target="-1" id="447" />
<action selector="alignRight:" target="-1" id="447"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="384" />
<menuItem isSeparatorItem="YES" id="384"/>
<menuItem title="Show Ruler" id="385">
<modifierMask key="keyEquivalentModifierMask" />
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="-1" id="446" />
<action selector="toggleRuler:" target="-1" id="446"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="386">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="-1" id="444" />
<action selector="copyRuler:" target="-1" id="444"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="387">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="-1" id="448" />
<action selector="pasteRuler:" target="-1" id="448"/>
</connections>
</menuItem>
</items>
@@ -451,14 +451,14 @@
<menu key="submenu" title="View" id="296">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="297">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES" />
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="-1" id="366" />
<action selector="toggleToolbarShown:" target="-1" id="366"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="298">
<connections>
<action selector="runToolbarCustomizationPalette:" target="-1" id="365" />
<action selector="runToolbarCustomizationPalette:" target="-1" id="365"/>
</connections>
</menuItem>
</items>
@@ -469,20 +469,20 @@
<items>
<menuItem title="Minimize" keyEquivalent="m" id="23">
<connections>
<action selector="performMiniaturize:" target="-1" id="37" />
<action selector="performMiniaturize:" target="-1" id="37"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="239">
<connections>
<action selector="performZoom:" target="-1" id="240" />
<action selector="performZoom:" target="-1" id="240"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="92">
<modifierMask key="keyEquivalentModifierMask" command="YES" />
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Bring All to Front" id="5">
<connections>
<action selector="arrangeInFront:" target="-1" id="39" />
<action selector="arrangeInFront:" target="-1" id="39"/>
</connections>
</menuItem>
</items>
@@ -491,9 +491,9 @@
<menuItem title="Help" id="103" userLabel="1">
<menu key="submenu" title="Help" id="106" userLabel="2">
<items>
<menuItem title="CPAnimationContextTest Help" keyEquivalent="?" id="111">
<menuItem title="NewApplication Help" keyEquivalent="?" id="111">
<connections>
<action selector="showHelp:" target="-1" id="360" />
<action selector="showHelp:" target="-1" id="360"/>
</connections>
</menuItem>
</items>
@@ -502,76 +502,76 @@
</items>
</menu>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="371">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" />
<windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES" />
<rect key="contentRect" x="335" y="390" width="940" height="1040" />
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1028" />
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="335" y="390" width="940" height="1040"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1028"/>
<view key="contentView" id="372">
<rect key="frame" x="0.0" y="0.0" width="940" height="1040" />
<autoresizingMask key="autoresizingMask" />
<rect key="frame" x="0.0" y="0.0" width="940" height="1040"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button identifier="run" verticalHuggingPriority="750" id="462">
<rect key="frame" x="332" y="999" width="134" height="32" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="332" y="999" width="134" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Start Animation" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="463">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES" />
<font key="font" metaFont="system" />
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="test:" target="450" id="0vd-DD-jrm" />
<action selector="test:" target="450" id="0vd-DD-jrm"/>
</connections>
</button>
<customView identifier="draw" id="FJv-ci-zoT" customClass="DrawView">
<rect key="frame" x="29" y="769" width="148" height="115" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="29" y="769" width="148" height="115"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
<customView identifier="layout" id="Ff1-j8-ljn" customClass="CustomLayoutView">
<rect key="frame" x="29" y="637" width="148" height="115" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="29" y="637" width="148" height="115"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<customView identifier="vanilla" id="obD-Te-e4b" customClass="ColorView">
<rect key="frame" x="20" y="52" width="53" height="43" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="20" y="52" width="53" height="43"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
<customView identifier="vanilla" id="kng-GZ-UzT" customClass="ColorView">
<rect key="frame" x="86" y="52" width="53" height="43" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="86" y="52" width="53" height="43"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
<customView identifier="vanilla" id="Ofx-KZ-FgQ" customClass="ColorView">
<rect key="frame" x="48" y="6" width="53" height="43" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="48" y="6" width="53" height="43"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
</subviews>
</customView>
<customView identifier="drawLayout" id="dLm-xl-6jP" customClass="CustomLayoutDrawView">
<rect key="frame" x="29" y="499" width="148" height="115" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="29" y="499" width="148" height="115"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<customView identifier="vanilla" id="Zo1-nI-BE8" customClass="ColorView">
<rect key="frame" x="12" y="61" width="53" height="43" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="12" y="61" width="53" height="43"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
<customView identifier="vanilla" id="Ohs-DN-x4k" customClass="ColorView">
<rect key="frame" x="84" y="61" width="53" height="43" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="84" y="61" width="53" height="43"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
<customView identifier="vanilla" id="OGq-ro-Klu" customClass="ColorView">
<rect key="frame" x="48" y="10" width="53" height="43" />
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" />
<rect key="frame" x="48" y="10" width="53" height="43"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
</subviews>
</customView>
</subviews>
</view>
<point key="canvasLocation" x="643" y="761" />
<point key="canvasLocation" x="643" y="761"/>
</window>
<customObject id="450" customClass="AppController">
<connections>
<outlet property="drawView" destination="FJv-ci-zoT" id="HzW-FO-0Sa" />
<outlet property="layoutDrawView" destination="dLm-xl-6jP" id="6fs-7K-Eeq" />
<outlet property="layoutView" destination="Ff1-j8-ljn" id="ryQ-pg-Xmz" />
<outlet property="theWindow" destination="371" id="459" />
<outlet property="drawView" destination="FJv-ci-zoT" id="HzW-FO-0Sa"/>
<outlet property="layoutDrawView" destination="dLm-xl-6jP" id="6fs-7K-Eeq"/>
<outlet property="layoutView" destination="Ff1-j8-ljn" id="ryQ-pg-Xmz"/>
<outlet property="theWindow" destination="371" id="459"/>
</connections>
</customObject>
</objects>
</document>
</document>
+52 -67
View File
@@ -1,12 +1,3 @@
/*
Disabled: This unit suite is a legacy benchmark harness designed for side-by-side
micro-benchmarking of JavaScript array operations and sorting implementations.
It relies on non-deterministic random data, wall-clock timing comparisons, and
Node.js host APIs (fs/path), while providing no deterministic functional assertions
or performance SLAs. Retained strictly for historical context until the native
Go and Lisette toolchain transition is fully finalized.
*/
var fs = require("fs");
var path = require("path");
@@ -26,7 +17,7 @@ function shuffle(o)
};
var ELEMENTS = 100,
REPEATS = 10;
REPEATS = 10;
@implementation CPArrayPerformanceTest : OJTestCase
{
@@ -36,117 +27,111 @@ REPEATS = 10;
- (void)setUp
{
descriptors = [
[CPSortDescriptor sortDescriptorWithKey:"a" ascending:NO],
[CPSortDescriptor sortDescriptorWithKey:"b" ascending:YES]
];
[CPSortDescriptor sortDescriptorWithKey:"a" ascending:NO],
[CPSortDescriptor sortDescriptorWithKey:"b" ascending:YES]
];
}
// Included only to ensure an active test is present and avoid 'no tests' warnings.
- (void)testPlaceholder
{
[self assertTrue:YES message:"Placeholder test to maintain test runner compatibility."];
}
- (void)disabled_testAlmostSortedNumericUsingMergeSort
- (void)testAlmostSortedNumericUsingMergeSort
{
console.log();
CPLog.warn("\nNUMERIC ALMOST SORTED");
var a = [self makeUnsorted],
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingDescriptors:) withObject:descriptors];
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingDescriptors:) withObject:descriptors];
[self checkAlmostSorted:sorted];
}
- (void)disabled_testAlmostSortedNumericUsingNativeSort
- (void)testAlmostSortedNumericUsingNativeSort
{
var a = [self makeUnsorted],
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingDescriptors:) withObject:descriptors];
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingDescriptors:) withObject:descriptors];
[self checkAlmostSorted:sorted];
}
- (void)disabled_testRandomNumericUsingMergeSort
- (void)testRandomNumericUsingMergeSort
{
console.log();
CPLog.warn("\nNUMERIC RANDOM");
var a = [self makeRandomNumeric],
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingDescriptors:) withObject:descriptors];
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingDescriptors:) withObject:descriptors];
[self checkRandomSorted:sorted];
}
- (void)disabled_testRandomNumericUsingNativeSort
- (void)testRandomNumericUsingNativeSort
{
var a = [self makeRandomNumeric],
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingDescriptors:) withObject:descriptors];
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingDescriptors:) withObject:descriptors];
[self checkRandomSorted:sorted];
}
- (void)disabled_testRandomTextUsingMergeSort
- (void)testRandomTextUsingMergeSort
{
console.log();
CPLog.warn("\nTEXT RANDOM");
var a = [self makeRandomText],
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingDescriptors:) withObject:descriptors];
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingDescriptors:) withObject:descriptors];
[self checkRandomSorted:sorted];
}
- (void)disabled_testRandomTextUsingNativeSort
- (void)testRandomTextUsingNativeSort
{
var a = [self makeRandomText],
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingDescriptors:) withObject:descriptors];
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingDescriptors:) withObject:descriptors];
[self checkRandomSorted:sorted];
}
- (void)disabled_testAlmostSortedNumericUsingMergeSelectorSort
- (void)testAlmostSortedNumericUsingMergeSelectorSort
{
console.log();
CPLog.warn("\nNUMERIC ALMOST SORTED (SELECTOR)");
var a = [self makeUnsorted],
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
[self checkAlmostSorted:sorted];
}
- (void)disabled_testAlmostSortedNumericUsingNativeSelectorSort
- (void)testAlmostSortedNumericUsingNativeSelectorSort
{
var a = [self makeUnsorted],
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
[self checkAlmostSorted:sorted];
}
- (void)disabled_testRandomNumericUsingMergeSelectorSort
- (void)testRandomNumericUsingMergeSelectorSort
{
console.log();
CPLog.warn("\nNUMERIC RANDOM (SELECTOR)");
var a = [self makeRandomNumeric],
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
[self checkRandomSorted:sorted];
}
- (void)disabled_testRandomNumericUsingNativeSelectorSort
- (void)testRandomNumericUsingNativeSelectorSort
{
var a = [self makeRandomNumeric],
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
[self checkRandomSorted:sorted];
}
- (void)disabled_testRandomTextUsingMergeSelectorSort
- (void)testRandomTextUsingMergeSelectorSort
{
console.log();
CPLog.warn("\nTEXT RANDOM (SELECTOR)");
var a = [self makeRandomText],
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
[self checkRandomSorted:sorted];
}
- (void)disabled_testRandomTextUsingNativeSelectorSort
- (void)testRandomTextUsingNativeSelectorSort
{
var a = [self makeRandomText],
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)];
[self checkRandomSorted:sorted];
}
- (CPArray)sort:(CPArray)anArray usingSortSelector:(SEL)aSelector withObject:(id)anObject
{
var sorted,
start = (new Date).getTime();
start = (new Date).getTime();
for (var i = 0; i < REPEATS; ++i)
sorted = [anArray performSelector:aSelector withObject:anObject];
@@ -182,8 +167,8 @@ REPEATS = 10;
for (var i = 0; i < ELEMENTS; i++)
{
var s = [Sortable new],
n1 = ROUND(RAND() * ELEMENTS),
n2 = ROUND(RAND() * ELEMENTS);
n1 = ROUND(RAND() * ELEMENTS),
n2 = ROUND(RAND() * ELEMENTS);
[s setA:n1];
[s setB:n2];
@@ -196,9 +181,9 @@ REPEATS = 10;
- (CPArray)makeRandomText
{
var the_big_sort = fs.readFileSync(path.join(path.dirname(__filename), "the_big_sort.txt"), {encoding: 'utf-8'}),
words = the_big_sort.split(" ", ELEMENTS),
wordcount = words.length,
array = [];
words = the_big_sort.split(" ", ELEMENTS),
wordcount = words.length,
array = [];
for (var i = 0; i < wordcount - 1; i++)
{
@@ -242,17 +227,17 @@ REPEATS = 10;
}
}
- (void)disabled_testObjectsAtIndexesSpeed
- (void)testObjectsAtIndexesSpeed
{
REPEATS = 100;
var SIZE = 1000,
c = SIZE,
r = REPEATS,
rr = r,
location = 0,
array = [CPArray array],
indexes = [CPIndexSet indexSet];
c = SIZE,
r = REPEATS,
rr = r,
location = 0,
array = [CPArray array],
indexes = [CPIndexSet indexSet];
while (c--)
array.push("" + c);
@@ -266,8 +251,8 @@ REPEATS = 10;
}
var d = new Date(),
test1,
test2;
test1,
test2;
while (r--)
test1 = [array _previous_objectsAtIndexes:indexes];
var dd = new Date();
@@ -285,25 +270,25 @@ REPEATS = 10;
[self fail:"_CPJavaScriptArray -objectsAtIndexes: returns an wrong value"];
}
- (void)disabled_testRemoveObjectIdenticalTo
- (void)testRemoveObjectIdenticalTo
{
REPEATS = 200;
var SIZE = 33 * 6,
allThings = [],
testSources = [];
allThings = [],
testSources = [];
for (var c = 0; c < SIZE; c++)
allThings.push("" + c);
var someThings = [allThings subarrayWithRange:CPMakeRange(SIZE / 3, SIZE / 3)],
removeThings = [allThings subarrayWithRange:(CPMakeRange(0, 2 * SIZE / 3))];
removeThings = [allThings subarrayWithRange:(CPMakeRange(0, 2 * SIZE / 3))];
for (var r = 0; r < REPEATS * 2; r++)
testSources.push(shuffle(someThings));
var d = new Date(),
test1;
test1;
for (var r = 0; r < REPEATS; r++)
{
test1 = testSources.pop();
@@ -312,7 +297,7 @@ REPEATS = 10;
}
var dd = new Date(),
test2;
test2;
for (var r = 0; r < REPEATS; r++)
{
test2 = testSources.pop();
@@ -367,9 +352,9 @@ REPEATS = 10;
var count = [descriptors count];
self.sort(function(lhs, rhs)
{
{
var i = 0,
result = CPOrderedSame;
result = CPOrderedSame;
while (i < count)
if ((result = [descriptors[i++] compareObject:lhs withObject:rhs]) !== CPOrderedSame)
@@ -391,7 +376,7 @@ REPEATS = 10;
- (CPArray)_native_sortUsingSelector:(SEL)aSelector
{
self.sort(function(lhs, rhs)
{
{
return [lhs performSelector:aSelector withObject:rhs];
});
}
+11 -28
View File
@@ -82,26 +82,18 @@
- (void)test_objectAtIndex_
{
var arrayClass = [[self class] arrayClass],
array = [arrayClass array],
e;
array = [arrayClass array];
e = [self assertThrows:function () { [array objectAtIndex:-1] }];
[self assert:CPRangeException equals:[e name]];
e = [self assertThrows:function () { [array objectAtIndex:0] }];
[self assert:CPRangeException equals:[e name]];
[self assertThrows:function () { [array objectAtIndex:-1] }];
[self assertThrows:function () { [array objectAtIndex:0] }];
var array = [arrayClass arrayWithObjects:0, 1, 2];
e = [self assertThrows:function () { [array objectAtIndex:-1] }];
[self assert:CPRangeException equals:[e name]];
[self assertThrows:function () { [array objectAtIndex:-1] }];
[self assert:[array objectAtIndex:0] same:0];
[self assert:[array objectAtIndex:1] same:1];
[self assert:[array objectAtIndex:2] same:2];
e = [self assertThrows:function () { [array objectAtIndex:3] }];
[self assert:CPRangeException equals:[e name]];
[self assertThrows:function () { [array objectAtIndex:3] }];
}
- (void)test_objectsAtIndexes_
@@ -112,29 +104,20 @@
}
var arrayClass = [[self class] arrayClass],
array = [arrayClass array],
e;
array = [arrayClass array];
e = [self assertThrows:function () { [array objectsAtIndexes:rangeIndexes(0, 1)] }];
[self assert:CPRangeException equals:[e name]];
[self assertThrows:function () { [array objectsAtIndexes:rangeIndexes(0, 1)] }];
var array = [arrayClass arrayWithObjects:0, 1, 2];
[self assert:[array objectsAtIndexes:rangeIndexes(0, 1)] equals:[0]];
[self assert:[array objectsAtIndexes:rangeIndexes(0, 2)] equals:[0, 1]];
[self assert:[array objectsAtIndexes:rangeIndexes(0, 3)] equals:[0, 1, 2]];
e = [self assertThrows:function () { [array objectsAtIndexes:rangeIndexes(0, 4)] }];
[self assert:CPRangeException equals:[e name]];
[self assertThrows:function () { [array objectsAtIndexes:rangeIndexes(0, 4)] }];
[self assert:[array objectsAtIndexes:rangeIndexes(1, 1)] equals:[1]];
[self assert:[array objectsAtIndexes:rangeIndexes(1, 2)] equals:[1, 2]];
e = [self assertThrows:function () { [array objectsAtIndexes:rangeIndexes(1, 3)] }];
[self assert:CPRangeException equals:[e name]];
e = [self assertThrows:function () { [array objectsAtIndexes:rangeIndexes(3, 1)] }];
[self assert:CPRangeException equals:[e name]];
[self assertThrows:function () { [array objectsAtIndexes:rangeIndexes(1, 3)] }];
[self assertThrows:function () { [array objectsAtIndexes:rangeIndexes(3, 1)] }];
}
- (void)test_indexOfObject_
@@ -846,7 +829,7 @@
- (id)objectAtIndex:(CPUInteger)anIndex
{
if (anIndex < 0 || anIndex >= [self count])
[CPException raise:CPRangeException reason:"index (" + anIndex + ") beyond bounds (" + [self count] + ")"];
throw "range error";
return array[anIndex];
}
+1 -6
View File
@@ -490,11 +490,8 @@
var result = [CPMutableDictionary dictionary];
// Test basic for...of iteration
for (var entry of dict)
for (var [key, value] of dict)
{
var key = entry[0],
value = entry[1];
[result setObject:value forKey:key];
}
@@ -517,8 +514,6 @@
var iterations = 0;
for (var entry of emptyDict)
{
// Explicitly evaluate the bound variable to satisfy the static analyzer.
[entry self];
iterations++;
}
[self assert:0 equals:iterations message:@"for...of on an empty dictionary should not iterate"];
+1 -1
View File
@@ -578,7 +578,7 @@
- (id)objectAtIndex:(CPUInteger)anIndex
{
if (anIndex < 0 || anIndex >= [self count])
[CPException raise:CPRangeException reason:"index (" + anIndex + ") beyond bounds (" + [self count] + ")"];
throw "range error";
return array[anIndex];
}
-9
View File
@@ -428,19 +428,10 @@
// 3. Test on an empty set
/*
The bound variable must be explicitly read to satisfy the static analyzer.
Standard JavaScript idioms for unused variables, such as the `_` identifier
or the `void` operator, either fail linting or trigger AST collisions in the
legacy Node.js parser. Evaluating the variable via a standard Objective-J
message send resolves the warning while preserving parser stability.
*/
var emptySet = [CPSet set];
var iterations = 0;
for (var entry of emptySet)
{
[itemsSeen addObject:entry];
iterations++;
}
[self assert:0 equals:iterations message:@"for...of on an empty set should not iterate"];
+5 -78
View File
@@ -357,14 +357,11 @@
var expectedAbbreviationLA;
try {
// This logic mirrors the _abbreviationForNameAndDate helper in
// CPTimeZone.j: read the short time zone name directly from Intl,
// as an independent check that CPTimeZone's own use of the same
// API returns the same thing, not a duplicate of an algorithm.
var options = { timeZone: laTimeZoneName, timeZoneName: 'short' };
var parts = new Intl.DateTimeFormat('en-US', options).formatToParts(new Date());
var tzPart = parts.filter(function (p) { return p.type === 'timeZoneName'; })[0];
expectedAbbreviationLA = tzPart ? tzPart.value : nil;
// This logic mimics the _abbreviationForNameAndDate helper function in CPTimeZone.j
var options = { timeZone: laTimeZoneName, timeZoneName: 'long' };
var dateString = (new Date()).toLocaleString('en-US', options);
var longTZName = dateString.replace(/^([0]?\d|[1][0-2])\/((?:[0]?|[1-2])\d|[3][0-1])\/([2][01]|[1][6-9])\d{2}(,?\s*([0]?\d|[1][0-2])(\:[0-5]\d){1,2})*\s*([aApP][mM]{0,2})?\s*/, "");
expectedAbbreviationLA = longTZName.split(" ").map(function(l) { return l[0]}).join("");
} catch (e) {
[self fail:"Could not determine expected abbreviation for America/Los_Angeles"];
return;
@@ -388,74 +385,4 @@
[self assert:[timeZoneHNL abbreviation] equals:@"HST"];
}
// Pins the six timeDifferenceFromUTC entries corrected on CPTimeZoneTest-fix
// (MDT, MSK, NZDT, NZST, WAT, WIT) through the public API, so a regression
// back to any of the old wrong values is caught directly.
- (void)testCorrectedOffsets
{
[self assert:[[CPTimeZone timeZoneWithAbbreviation:@"MDT"] secondsFromGMT] equals:(-360 * 60)];
[self assert:[[CPTimeZone timeZoneWithAbbreviation:@"MSK"] secondsFromGMT] equals:(180 * 60)];
[self assert:[[CPTimeZone timeZoneWithAbbreviation:@"NZDT"] secondsFromGMT] equals:(780 * 60)];
[self assert:[[CPTimeZone timeZoneWithAbbreviation:@"NZST"] secondsFromGMT] equals:(720 * 60)];
[self assert:[[CPTimeZone timeZoneWithAbbreviation:@"WAT"] secondsFromGMT] equals:(60 * 60)];
[self assert:[[CPTimeZone timeZoneWithAbbreviation:@"WIT"] secondsFromGMT] equals:(420 * 60)];
}
// MSD ("Moscow Summer Time") has no correct current value: Russia abolished
// DST in 2014, so there is no live distinct summer offset for Moscow to
// assign. This pins the current, intentionally-unfixed value, so any future
// edit to it happens deliberately alongside CPTimeZone.j's comment and the
// tracked Intl redesign, not as a silent, unrelated drift.
- (void)testMSDKnownStaleValue
{
[self assert:[[CPTimeZone timeZoneWithAbbreviation:@"MSD"] secondsFromGMT] equals:(240 * 60)];
}
// knownTimeZoneNames should prefer the runtime's own IANA database over the
// old 48-city hardcoded list, on any engine that exposes
// Intl.supportedValuesOf. Skips itself where that API is unavailable, rather
// than asserting a specific engine capability.
- (void)testKnownTimeZoneNamesUsesIntlWhenAvailable
{
if (typeof Intl === "undefined" || typeof Intl.supportedValuesOf !== "function")
return;
var names = [CPTimeZone knownTimeZoneNames];
[self assertTrue:([names count] > 48)
message:"knownTimeZoneNames should use Intl.supportedValuesOf when available, not the small hardcoded fallback list"];
[self assertTrue:[names containsObject:@"Europe/Berlin"]
message:"a zone absent from the old hardcoded list should be present via Intl"];
}
// Disabled: the "fr" entry in localizedName is an empty dictionary, so any
// lookup for a French locale currently returns nil regardless of style. This
// is the same static, English-only table design as before the Intl
// migration; folding it in was deferred because CPTimeZoneNameStyleStandard/
// DaylightSaving need to force a specific state independent of the current
// date, which Intl.formatToParts(date) alone can't do for an arbitrary date.
// Tracked for the larger IANA-identity redesign, not fixed here.
- (void)disabled_testLocalizedNameFrenchLocale
{
var frenchLocale = [[CPLocale alloc] initWithLocaleIdentifier:@"fr_FR"],
timeZone = [CPTimeZone timeZoneWithAbbreviation:@"PST"];
[self assert:[timeZone localizedName:CPTimeZoneNameStyleStandard locale:frenchLocale] equals:@"Heure normale du Pacifique"];
}
// Disabled: timeZoneForSecondsFromGMT: does a linear scan of
// timeDifferenceFromUTC and returns the first matching key, with no defined
// tie-break when more than one abbreviation shares an offset. MDT and CST
// both now correctly resolve to -360; asking for that offset silently
// returns whichever CPDictionary happens to enumerate first, not a
// documented choice. The idealized contract is that an inherently ambiguous
// query should not silently guess one answer. No fix exists for this within
// the current flat abbreviation-to-offset table design.
- (void)disabled_testTimeZoneForSecondsFromGMTOffsetCollisionIsAmbiguous
{
var timeZone = [CPTimeZone timeZoneForSecondsFromGMT:(-360 * 60)];
[self assert:timeZone equals:nil];
}
@end
+94
View File
@@ -0,0 +1,94 @@
/*
* Jakefile
* ArrayController
*
* Created by Alexander Ljungberg on April 2, 2011.
* Copyright 2011, WireLoad 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 ("ArrayController", function(task)
{
task.setBuildIntermediatesPath(FILE.join("Build", "ArrayController.build", configuration));
task.setBuildPath(FILE.join("Build", configuration));
task.setProductName("ArrayController");
task.setIdentifier("com.yourcompany.ArrayController");
task.setVersion("1.0");
task.setAuthor("WireLoad");
task.setEmail("feedback @nospam@ yourcompany.com");
task.setSummary("ArrayController");
task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**")));
task.setResources(new FileList("Resources/**"));
task.setIndexFilePath("index.html");
task.setInfoPlistPath("Info.plist");
task.setNib2CibFlags("-R Resources/");
if (configuration === "Debug")
task.setCompilerFlags("-DDEBUG -g");
else
task.setCompilerFlags("-O");
});
task ("default", ["ArrayController"], 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", "ArrayController", "index.html")]);
});
task ("run-release", ["release"], function()
{
OS.system(["open", FILE.join("Build", "Release", "ArrayController", "index.html")]);
});
task ("deploy", ["release"], function()
{
FILE.mkdirs(FILE.join("Build", "Deployment", "ArrayController"));
OS.system(["press", "-f", FILE.join("Build", "Release", "ArrayController"), FILE.join("Build", "Deployment", "ArrayController")]);
printResults("Deployment")
});
task ("desktop", ["release"], function()
{
FILE.mkdirs(FILE.join("Build", "Desktop", "ArrayController"));
require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "ArrayController"), FILE.join("Build", "Desktop", "ArrayController", "ArrayController.app"));
printResults("Desktop")
});
task ("run-desktop", ["desktop"], function()
{
OS.system([FILE.join("Build", "Desktop", "ArrayController", "ArrayController.app", "Contents", "MacOS", "NativeHost"), "-i"]);
});
function printResults(configuration)
{
print("----------------------------");
print(configuration+" app built at path: "+FILE.join("Build", configuration, "ArrayController"));
print("----------------------------");
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,941 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1050</int>
<string key="IBDocument.SystemVersion">12C60</string>
<string key="IBDocument.InterfaceBuilderVersion">3084</string>
<string key="IBDocument.AppKitVersion">1187.34</string>
<string key="IBDocument.HIToolboxVersion">625.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">3084</string>
</object>
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSArrayController</string>
<string>NSButton</string>
<string>NSButtonCell</string>
<string>NSCustomObject</string>
<string>NSScrollView</string>
<string>NSScroller</string>
<string>NSTableHeaderView</string>
<string>NSTableView</string>
<string>NSTextField</string>
<string>NSTextFieldCell</string>
<string>NSView</string>
<string>NSWindowTemplate</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSCustomObject" id="1021">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSCustomObject" id="1014">
<string key="NSClassName">FirstResponder</string>
</object>
<object class="NSCustomObject" id="1050">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSWindowTemplate" id="972006081">
<int key="NSWindowStyleMask">7</int>
<int key="NSWindowBacking">2</int>
<string key="NSWindowRect">{{335, 445}, {480, 305}}</string>
<int key="NSWTFlags">1946157056</int>
<string key="NSWindowTitle">Window</string>
<string key="NSWindowClass">NSWindow</string>
<nil key="NSViewClass"/>
<nil key="NSUserInterfaceItemIdentifier"/>
<object class="NSView" key="NSWindowView" id="439893737">
<reference key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSScrollView" id="152379243">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSClipView" id="284793823">
<reference key="NSNextResponder" ref="152379243"/>
<int key="NSvFlags">2304</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSTableView" id="971713637">
<reference key="NSNextResponder" ref="284793823"/>
<int key="NSvFlags">256</int>
<string key="NSFrameSize">{443, 117}</string>
<reference key="NSSuperview" ref="284793823"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="362059487"/>
<bool key="NSEnabled">YES</bool>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
<bool key="NSControlAllowsExpansionToolTips">YES</bool>
<object class="NSTableHeaderView" key="NSHeaderView" id="1058581420">
<reference key="NSNextResponder" ref="791373261"/>
<int key="NSvFlags">256</int>
<string key="NSFrameSize">{443, 17}</string>
<reference key="NSSuperview" ref="791373261"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="284793823"/>
<reference key="NSTableView" ref="971713637"/>
</object>
<object class="_NSCornerView" key="NSCornerView">
<nil key="NSNextResponder"/>
<int key="NSvFlags">-2147483392</int>
<string key="NSFrame">{{224, 0}, {16, 17}}</string>
</object>
<object class="NSMutableArray" key="NSTableColumns">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<double key="NSIntercellSpacingWidth">3</double>
<double key="NSIntercellSpacingHeight">2</double>
<object class="NSColor" key="NSBackgroundColor" id="894569415">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
</object>
<object class="NSColor" key="NSGridColor">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">gridColor</string>
<object class="NSColor" key="NSColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC41AA</bytes>
</object>
</object>
<double key="NSRowHeight">17</double>
<int key="NSTvFlags">-566231040</int>
<reference key="NSDelegate"/>
<reference key="NSDataSource"/>
<int key="NSColumnAutoresizingStyle">5</int>
<int key="NSDraggingSourceMaskForLocal">15</int>
<int key="NSDraggingSourceMaskForNonLocal">0</int>
<bool key="NSAllowsTypeSelect">YES</bool>
<int key="NSTableViewDraggingDestinationStyle">0</int>
<int key="NSTableViewGroupRowStyle">1</int>
</object>
</object>
<string key="NSFrame">{{1, 17}, {443, 117}}</string>
<reference key="NSSuperview" ref="152379243"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="971713637"/>
<reference key="NSDocView" ref="971713637"/>
<object class="NSColor" key="NSBGColor" id="651906913">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlBackgroundColor</string>
<object class="NSColor" key="NSColor" id="279320231">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
</object>
</object>
<int key="NScvFlags">4</int>
</object>
<object class="NSScroller" id="362059487">
<reference key="NSNextResponder" ref="152379243"/>
<int key="NSvFlags">-2147483392</int>
<string key="NSFrame">{{224, 17}, {15, 102}}</string>
<reference key="NSSuperview" ref="152379243"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="1056492085"/>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
<reference key="NSTarget" ref="152379243"/>
<string key="NSAction">_doScroller:</string>
<double key="NSCurValue">1</double>
<double key="NSPercent">0.1947367936372757</double>
</object>
<object class="NSScroller" id="1056492085">
<reference key="NSNextResponder" ref="152379243"/>
<int key="NSvFlags">-2147483392</int>
<string key="NSFrame">{{1, 119}, {223, 15}}</string>
<reference key="NSSuperview" ref="152379243"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="351504327"/>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
<int key="NSsFlags">1</int>
<reference key="NSTarget" ref="152379243"/>
<string key="NSAction">_doScroller:</string>
<double key="NSPercent">0.57142859697341919</double>
</object>
<object class="NSClipView" id="791373261">
<reference key="NSNextResponder" ref="152379243"/>
<int key="NSvFlags">2304</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="1058581420"/>
</object>
<string key="NSFrame">{{1, 0}, {443, 17}}</string>
<reference key="NSSuperview" ref="152379243"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="1058581420"/>
<reference key="NSDocView" ref="1058581420"/>
<reference key="NSBGColor" ref="651906913"/>
<int key="NScvFlags">4</int>
</object>
</object>
<string key="NSFrame">{{15, 150}, {445, 135}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="791373261"/>
<int key="NSsFlags">133682</int>
<reference key="NSVScroller" ref="362059487"/>
<reference key="NSHScroller" ref="1056492085"/>
<reference key="NSContentView" ref="284793823"/>
<reference key="NSHeaderClipView" ref="791373261"/>
<bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
<double key="NSMinMagnification">0.25</double>
<double key="NSMaxMagnification">4</double>
<double key="NSMagnification">1</double>
</object>
<object class="NSButton" id="351504327">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{9, 110}, {40, 32}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="214480962"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="615437802">
<int key="NSCellFlags">67108864</int>
<int key="NSCellFlags2">134217728</int>
<string key="NSContents"/>
<object class="NSFont" key="NSSupport" id="856882769">
<string key="NSName">LucidaGrande</string>
<double key="NSSize">13</double>
<int key="NSfFlags">1044</int>
</object>
<reference key="NSControlView" ref="351504327"/>
<int key="NSButtonFlags">-2033958912</int>
<int key="NSButtonFlags2">129</int>
<object class="NSCustomResource" key="NSNormalImage">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSAddTemplate</string>
</object>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">200</int>
<int key="NSPeriodicInterval">25</int>
</object>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
</object>
<object class="NSButton" id="214480962">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{49, 110}, {41, 32}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="473445886"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="791512344">
<int key="NSCellFlags">67108864</int>
<int key="NSCellFlags2">134217728</int>
<string key="NSContents"/>
<reference key="NSSupport" ref="856882769"/>
<reference key="NSControlView" ref="214480962"/>
<int key="NSButtonFlags">-2033958912</int>
<int key="NSButtonFlags2">129</int>
<object class="NSCustomResource" key="NSNormalImage">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSRemoveTemplate</string>
</object>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">200</int>
<int key="NSPeriodicInterval">25</int>
</object>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
</object>
<object class="NSTextField" id="435057252">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{12, 77}, {155, 17}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="314396040"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="992079013">
<int key="NSCellFlags">68157504</int>
<int key="NSCellFlags2">71304192</int>
<string key="NSContents">Selected Name:</string>
<reference key="NSSupport" ref="856882769"/>
<reference key="NSControlView" ref="435057252"/>
<object class="NSColor" key="NSBackgroundColor" id="617443217">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlColor</string>
<reference key="NSColor" ref="279320231"/>
</object>
<object class="NSColor" key="NSTextColor" id="650615687">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlTextColor</string>
<object class="NSColor" key="NSColor" id="730639221">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
</object>
</object>
</object>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
</object>
<object class="NSTextField" id="794763058">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{66, 47}, {101, 17}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="563826159"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="349060415">
<int key="NSCellFlags">68157504</int>
<int key="NSCellFlags2">71304192</int>
<string key="NSContents">Selected Price:</string>
<reference key="NSSupport" ref="856882769"/>
<reference key="NSControlView" ref="794763058"/>
<reference key="NSBackgroundColor" ref="617443217"/>
<reference key="NSTextColor" ref="650615687"/>
</object>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
</object>
<object class="NSTextField" id="153336867">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{17, 20}, {150, 17}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="620159090"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="943890587">
<int key="NSCellFlags">68157504</int>
<int key="NSCellFlags2">71304192</int>
<string key="NSContents">Sum of Selected Prices:</string>
<reference key="NSSupport" ref="856882769"/>
<reference key="NSControlView" ref="153336867"/>
<reference key="NSBackgroundColor" ref="617443217"/>
<reference key="NSTextColor" ref="650615687"/>
</object>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
</object>
<object class="NSTextField" id="620159090">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{174, 20}, {292, 17}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="203129013">
<int key="NSCellFlags">68157504</int>
<int key="NSCellFlags2">272630784</int>
<string key="NSContents">Label</string>
<reference key="NSSupport" ref="856882769"/>
<reference key="NSControlView" ref="620159090"/>
<reference key="NSBackgroundColor" ref="617443217"/>
<reference key="NSTextColor" ref="650615687"/>
</object>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
</object>
<object class="NSTextField" id="314396040">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{174, 75}, {286, 22}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="794763058"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="66689853">
<int key="NSCellFlags">-1804599231</int>
<int key="NSCellFlags2">272630784</int>
<string key="NSContents"/>
<reference key="NSSupport" ref="856882769"/>
<reference key="NSControlView" ref="314396040"/>
<bool key="NSDrawsBackground">YES</bool>
<object class="NSColor" key="NSBackgroundColor" id="879609906">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">textBackgroundColor</string>
<reference key="NSColor" ref="894569415"/>
</object>
<object class="NSColor" key="NSTextColor" id="949633793">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">textColor</string>
<reference key="NSColor" ref="730639221"/>
</object>
</object>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
</object>
<object class="NSTextField" id="563826159">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{174, 45}, {286, 22}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="153336867"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="841860402">
<int key="NSCellFlags">-1804599231</int>
<int key="NSCellFlags2">272630784</int>
<string key="NSContents"/>
<reference key="NSSupport" ref="856882769"/>
<reference key="NSControlView" ref="563826159"/>
<bool key="NSDrawsBackground">YES</bool>
<reference key="NSBackgroundColor" ref="879609906"/>
<reference key="NSTextColor" ref="949633793"/>
</object>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
</object>
<object class="NSButton" id="473445886">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{90, 110}, {76, 32}}</string>
<reference key="NSSuperview" ref="439893737"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="435057252"/>
<string key="NSReuseIdentifierKey">_NS:9</string>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="120906924">
<int key="NSCellFlags">67108864</int>
<int key="NSCellFlags2">134217728</int>
<string key="NSContents">Insert</string>
<reference key="NSSupport" ref="856882769"/>
<string key="NSCellIdentifier">_NS:9</string>
<reference key="NSControlView" ref="473445886"/>
<int key="NSButtonFlags">-2038284288</int>
<int key="NSButtonFlags2">129</int>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">200</int>
<int key="NSPeriodicInterval">25</int>
</object>
<bool key="NSAllowsLogicalLayoutDirection">NO</bool>
</object>
</object>
<string key="NSFrameSize">{480, 305}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="152379243"/>
</object>
<string key="NSScreenRect">{{0, 0}, {2560, 1418}}</string>
<string key="NSMaxSize">{10000000000000, 10000000000000}</string>
<bool key="NSWindowIsRestorable">YES</bool>
</object>
<object class="NSCustomObject" id="635946545">
<string key="NSClassName">AppController</string>
</object>
<object class="NSArrayController" id="939887647">
<bool key="NSEditable">YES</bool>
<object class="_NSManagedProxy" key="_NSManagedProxy"/>
<bool key="NSAvoidsEmptySelection">YES</bool>
<bool key="NSPreservesSelection">YES</bool>
<bool key="NSSelectsInsertedObjects">YES</bool>
<bool key="NSFilterRestrictsInsertion">YES</bool>
<bool key="NSClearsFilterPredicateOnInsertion">YES</bool>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="1021"/>
<reference key="destination" ref="635946545"/>
</object>
<int key="connectionID">451</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">theWindow</string>
<reference key="source" ref="635946545"/>
<reference key="destination" ref="972006081"/>
</object>
<int key="connectionID">459</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">tableView</string>
<reference key="source" ref="635946545"/>
<reference key="destination" ref="971713637"/>
</object>
<int key="connectionID">483</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">totalCountField</string>
<reference key="source" ref="635946545"/>
<reference key="destination" ref="620159090"/>
</object>
<int key="connectionID">498</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">selectedNameField</string>
<reference key="source" ref="635946545"/>
<reference key="destination" ref="314396040"/>
</object>
<int key="connectionID">507</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">selectedPriceField</string>
<reference key="source" ref="635946545"/>
<reference key="destination" ref="563826159"/>
</object>
<int key="connectionID">508</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">arrayController</string>
<reference key="source" ref="635946545"/>
<reference key="destination" ref="939887647"/>
</object>
<int key="connectionID">512</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">add:</string>
<reference key="source" ref="939887647"/>
<reference key="destination" ref="351504327"/>
</object>
<int key="connectionID">513</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">remove:</string>
<reference key="source" ref="939887647"/>
<reference key="destination" ref="214480962"/>
</object>
<int key="connectionID">514</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">insert:</string>
<reference key="source" ref="939887647"/>
<reference key="destination" ref="473445886"/>
</object>
<int key="connectionID">515</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<object class="NSArray" key="object" id="1049">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="children" ref="1048"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="1021"/>
<reference key="parent" ref="1049"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="1014"/>
<reference key="parent" ref="1049"/>
<string key="objectName">First Responder</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-3</int>
<reference key="object" ref="1050"/>
<reference key="parent" ref="1049"/>
<string key="objectName">Application</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">371</int>
<reference key="object" ref="972006081"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="439893737"/>
</object>
<reference key="parent" ref="1049"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">372</int>
<reference key="object" ref="439893737"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="152379243"/>
<reference ref="435057252"/>
<reference ref="314396040"/>
<reference ref="794763058"/>
<reference ref="563826159"/>
<reference ref="153336867"/>
<reference ref="620159090"/>
<reference ref="351504327"/>
<reference ref="214480962"/>
<reference ref="473445886"/>
</object>
<reference key="parent" ref="972006081"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">450</int>
<reference key="object" ref="635946545"/>
<reference key="parent" ref="1049"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">460</int>
<reference key="object" ref="152379243"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="362059487"/>
<reference ref="1056492085"/>
<reference ref="971713637"/>
<reference ref="1058581420"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">461</int>
<reference key="object" ref="362059487"/>
<reference key="parent" ref="152379243"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">462</int>
<reference key="object" ref="1056492085"/>
<reference key="parent" ref="152379243"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">463</int>
<reference key="object" ref="971713637"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="parent" ref="152379243"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">464</int>
<reference key="object" ref="1058581420"/>
<reference key="parent" ref="152379243"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">469</int>
<reference key="object" ref="351504327"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="615437802"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">470</int>
<reference key="object" ref="615437802"/>
<reference key="parent" ref="351504327"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">471</int>
<reference key="object" ref="214480962"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="791512344"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">472</int>
<reference key="object" ref="791512344"/>
<reference key="parent" ref="214480962"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">484</int>
<reference key="object" ref="435057252"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="992079013"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">485</int>
<reference key="object" ref="992079013"/>
<reference key="parent" ref="435057252"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">488</int>
<reference key="object" ref="794763058"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="349060415"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">489</int>
<reference key="object" ref="349060415"/>
<reference key="parent" ref="794763058"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">492</int>
<reference key="object" ref="153336867"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="943890587"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">493</int>
<reference key="object" ref="943890587"/>
<reference key="parent" ref="153336867"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">494</int>
<reference key="object" ref="620159090"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="203129013"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">495</int>
<reference key="object" ref="203129013"/>
<reference key="parent" ref="620159090"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">499</int>
<reference key="object" ref="314396040"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="66689853"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">500</int>
<reference key="object" ref="66689853"/>
<reference key="parent" ref="314396040"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">501</int>
<reference key="object" ref="563826159"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="841860402"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">502</int>
<reference key="object" ref="841860402"/>
<reference key="parent" ref="563826159"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">509</int>
<reference key="object" ref="473445886"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="120906924"/>
</object>
<reference key="parent" ref="439893737"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">510</int>
<reference key="object" ref="120906924"/>
<reference key="parent" ref="473445886"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">511</int>
<reference key="object" ref="939887647"/>
<reference key="parent" ref="1049"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.IBPluginDependency</string>
<string>-2.IBPluginDependency</string>
<string>-3.IBPluginDependency</string>
<string>371.IBPluginDependency</string>
<string>371.IBWindowTemplateEditedContentRect</string>
<string>371.NSWindowTemplate.visibleAtLaunch</string>
<string>372.IBPluginDependency</string>
<string>450.IBPluginDependency</string>
<string>460.IBPluginDependency</string>
<string>461.IBPluginDependency</string>
<string>462.IBPluginDependency</string>
<string>463.IBPluginDependency</string>
<string>464.IBPluginDependency</string>
<string>469.IBPluginDependency</string>
<string>470.IBPluginDependency</string>
<string>471.IBPluginDependency</string>
<string>472.IBPluginDependency</string>
<string>484.IBPluginDependency</string>
<string>485.IBPluginDependency</string>
<string>488.IBPluginDependency</string>
<string>489.IBPluginDependency</string>
<string>492.IBPluginDependency</string>
<string>493.IBPluginDependency</string>
<string>494.IBPluginDependency</string>
<string>495.IBPluginDependency</string>
<string>499.IBPluginDependency</string>
<string>500.IBPluginDependency</string>
<string>501.IBPluginDependency</string>
<string>502.IBPluginDependency</string>
<string>509.IBPluginDependency</string>
<string>510.IBPluginDependency</string>
<string>511.IBPluginDependency</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{108, 156}, {480, 305}}</string>
<integer value="1"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="1049"/>
<reference key="dict.values" ref="1049"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="1049"/>
<reference key="dict.values" ref="1049"/>
</object>
<nil key="sourceID"/>
<int key="maxID">515</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">AppController</string>
<string key="superclassName">NSObject</string>
<object class="NSMutableDictionary" key="outlets">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>arrayController</string>
<string>selectedNameField</string>
<string>selectedPriceField</string>
<string>tableView</string>
<string>theWindow</string>
<string>totalCountField</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSArrayController</string>
<string>NSTextField</string>
<string>NSTextField</string>
<string>NSTableView</string>
<string>NSWindow</string>
<string>NSTextField</string>
</object>
</object>
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>arrayController</string>
<string>selectedNameField</string>
<string>selectedPriceField</string>
<string>tableView</string>
<string>theWindow</string>
<string>totalCountField</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBToOneOutletInfo">
<string key="name">arrayController</string>
<string key="candidateClassName">NSArrayController</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">selectedNameField</string>
<string key="candidateClassName">NSTextField</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">selectedPriceField</string>
<string key="candidateClassName">NSTextField</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">tableView</string>
<string key="candidateClassName">NSTableView</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">theWindow</string>
<string key="candidateClassName">NSWindow</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">totalCountField</string>
<string key="candidateClassName">NSTextField</string>
</object>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/AppController.h</string>
</object>
</object>
</object>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
<integer value="1050" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
<integer value="3000" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSAddTemplate</string>
<string>NSRemoveTemplate</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>{8, 8}</string>
<string>{8, 8}</string>
</object>
</object>
</data>
</archive>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Some files were not shown because too many files have changed in this diff Show More