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
963 changed files with 157262 additions and 40418 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
-1
View File
@@ -93,7 +93,6 @@
@import "CPSlider.j"
@import "CPSound.j"
@import "CPSplitView.j"
@import "CPStackView.j"
@import "CPStepper.j"
@import "CPTableColumn.j"
@import "CPTableView.j"
-3
View File
@@ -23,7 +23,6 @@
@import "CPButton.j"
@import "CPTableColumn.j"
@import "CPTableView.j"
@import "CPTreeNode.j"
@global CPApp
@@ -2532,8 +2531,6 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
selector:@selector(outlineViewSelectionDidChange:)
name:CPOutlineViewSelectionDidChangeNotification
object:aSource];
return self;
}
+ (void)unbind:(CPString)aBinding forObject:(id)anObject
+3 -2
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));
+7 -5
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
@@ -1169,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],
@@ -1179,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],
+19 -134
View File
@@ -20,80 +20,9 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* PLACEHOLDER IMPLEMENTATION — READ BEFORE USE OR MODIFICATION.
*
* This class lays out views by direct, procedural arithmetic. It has no
* constraint solver. It cannot compress, expand, or negotiate space among
* views the way NSStackView does; it only places views at their existing
* frame size, in order, separated by fixed spacing.
*
* Known, accepted limitations:
* - `distribution` is stored but has no effect on layout. Fill,
* FillEqually, FillProportionally, and EqualSpacing are unimplemented.
* - Center-gravity views are clamped against the Leading edge only; if
* Leading + Center + Trailing content overflows the container, Center
* views can overlap Trailing views instead of compressing.
* - `visibilityPriority:forView:` only supports the two extreme values
* (MustHold / NotVisible). Intermediate priorities are accepted but
* have no defined effect.
* - No guarantee is made of correctness beyond what a single manual
* test (Tests/Manual/CPStackViewTest) exercises: orientation, the
* three gravity areas, alignment switching, spacing, and hidden-view
* detachment. Insertion, removal, custom spacing, and the CPCoding
* archive path are implemented but not verified by that test.
*
* This exists to give AppKit a working CPStackView symbol now, not to be
* a durable design. It is expected to be replaced by a constraint-solver
* based implementation (Kiwi.js) when time permits. Do not build on its
* internal layout algorithm as if it were a stable foundation.
*/
#include "../Foundation/Foundation.h"
@import "CPView.j"
@import <Foundation/CPMapTable.j>
// MARK: -
// MARK: Minimal local type definitions
//
// These types support this file only. They are not shared with the rest
// of AppKit. A future constraint-solver based Auto Layout engine will
// replace them. Numeric values match the equivalent Cocoa constants
// (NSUserInterfaceLayoutOrientation, NSLayoutAttribute) so that a later,
// solver-based CPLayoutAttribute can reuse these numbers without a
// renumbering pass.
@typedef CPUserInterfaceLayoutOrientation
CPUserInterfaceLayoutOrientationHorizontal = 0;
CPUserInterfaceLayoutOrientationVertical = 1;
@typedef CPLayoutAttribute
CPLayoutAttributeLeft = 1;
CPLayoutAttributeRight = 2;
CPLayoutAttributeTop = 3;
CPLayoutAttributeBottom = 4;
CPLayoutAttributeLeading = 5;
CPLayoutAttributeTrailing = 6;
CPLayoutAttributeWidth = 7;
CPLayoutAttributeHeight = 8;
CPLayoutAttributeCenterX = 9;
CPLayoutAttributeCenterY = 10;
@typedef CPEdgeInsets
/*!
Creates a CPEdgeInsets. Argument order matches Cocoa's NSEdgeInsetsMake
(top, left, bottom, right). Storage reuses the existing CGInset struct,
whose field order is (top, right, bottom, left).
*/
function CPEdgeInsetsMake(top, left, bottom, right)
{
return CGInsetMake(top, right, bottom, left);
}
function CPEdgeInsetsEqualToEdgeInsets(lhsInsets, rhsInsets)
{
return CGInsetEqualToInset(lhsInsets, rhsInsets);
}
// Gravity Areas
@typedef CPStackViewGravity
@@ -133,7 +62,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
CPUserInterfaceLayoutOrientation _orientation;
CPLayoutAttribute _alignment;
CPStackViewDistribution _distribution;
float _spacing;
CPEdgeInsets _edgeInsets;
@@ -173,7 +101,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
_orientation = CPUserInterfaceLayoutOrientationHorizontal;
_alignment = CPLayoutAttributeCenterY; // Default alignment
_distribution = CPStackViewDistributionGravityAreas;
_spacing = 8.0; // Default Cocoa spacing
_edgeInsets = CPEdgeInsetsMake(0, 0, 0, 0);
_detachesHiddenViews = YES;
@@ -244,27 +171,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
[self setNeedsLayout:YES];
}
/*!
The distribution mode for the stack view.
@note Not yet applied to layout. All views are laid out at their
existing frame size regardless of this value, pending the
constraint-solver based layout engine. The value is stored and
returned so client code can read back what was set.
*/
- (CPStackViewDistribution)distribution
{
return _distribution;
}
- (void)setDistribution:(CPStackViewDistribution)aDistribution
{
if (_distribution === aDistribution)
return;
_distribution = aDistribution;
[self setNeedsLayout:YES];
}
/*!
The minimum spacing, in points, between adjacent views in the stack view.
*/
@@ -329,21 +235,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
return _viewsLeading; // Leading or Top
}
/*!
Rebuilds _arrangedSubviews from the three gravity containers, in
Leading, Center, Trailing order. Call after any change to a gravity
container so _arrangedSubviews stays a correct, single source of truth
for ordering, rather than an incrementally and separately maintained
(and error-prone) copy.
*/
- (void)_rebuildArrangedSubviews
{
_arrangedSubviews = [[CPMutableArray alloc] init];
[_arrangedSubviews addObjectsFromArray:_viewsLeading];
[_arrangedSubviews addObjectsFromArray:_viewsCenter];
[_arrangedSubviews addObjectsFromArray:_viewsTrailing];
}
/*!
Adds a view to the end of the stack view gravity area.
*/
@@ -356,7 +247,7 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
[self removeView:aView];
[container addObject:aView];
[self _rebuildArrangedSubviews];
[_arrangedSubviews addObject:aView];
// Add as actual subview
if ([aView superview] !== self)
@@ -380,7 +271,7 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
else
[container insertObject:aView atIndex:index];
[self _rebuildArrangedSubviews];
[_arrangedSubviews addObject:aView];
if ([aView superview] !== self)
[self addSubview:aView];
@@ -395,9 +286,13 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
var container = [self _containerForGravity:gravity];
// Remove old views from superview
// Remove old views from arranged list and superview
for (var i = 0; i < [container count]; i++)
[container[i] removeFromSuperview];
{
var oldView = container[i];
[oldView removeFromSuperview];
[_arrangedSubviews removeObject:oldView];
}
[container removeAllObjects];
@@ -405,10 +300,10 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
var newView = views[i];
[container addObject:newView];
[_arrangedSubviews addObject:newView];
[self addSubview:newView];
}
[self _rebuildArrangedSubviews];
[self setNeedsLayout:YES];
}
@@ -423,7 +318,7 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
[_viewsLeading removeObject:aView];
[_viewsCenter removeObject:aView];
[_viewsTrailing removeObject:aView];
[self _rebuildArrangedSubviews];
[_arrangedSubviews removeObject:aView];
[aView removeFromSuperview];
@@ -638,13 +533,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
var limit = (dir === 1) ? count : -1;
var step = (dir === 1) ? 1 : -1;
// Spacing is applied as a gap *before* placing an element (except the
// first placed one), rather than trailing off the end after the last
// element. This keeps the returned cursor at the true content edge,
// with no phantom spacing past the final view.
var hasPlacedAny = false;
var pendingSpacing = 0;
for (; i !== limit; i += step)
{
var view = views[i];
@@ -652,9 +540,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
if (_detachesHiddenViews && [view isHidden])
continue;
if (hasPlacedAny)
cursor += (dir === 1) ? pendingSpacing : -pendingSpacing;
var viewFrame = [view frame];
var viewSizePrimary = isVert ? CGRectGetHeight(viewFrame) : CGRectGetWidth(viewFrame);
@@ -734,10 +619,11 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
if (dir === 1) {
originY = cursor;
cursor += sizeH;
cursor += sizeH + [self _spacingAfterView:view];
} else {
cursor -= sizeH;
originY = cursor;
cursor -= [self _spacingAfterView:view];
}
}
else
@@ -749,17 +635,15 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
if (dir === 1) {
originX = cursor;
cursor += sizeW;
cursor += sizeW + [self _spacingAfterView:view];
} else {
cursor -= sizeW;
originX = cursor;
cursor -= [self _spacingAfterView:view];
}
}
[view setFrame:CGRectMake(originX, originY, sizeW, sizeH)];
pendingSpacing = [self _spacingAfterView:view];
hasPlacedAny = true;
}
return cursor;
@@ -775,7 +659,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
_orientation = [aCoder decodeIntForKey:@"CPStackViewOrientation"];
_alignment = [aCoder decodeIntForKey:@"CPStackViewAlignment"];
_distribution = [aCoder decodeIntForKey:@"CPStackViewDistribution"];
_spacing = [aCoder decodeFloatForKey:@"CPStackViewSpacing"];
_edgeInsets = [aCoder decodeObjectForKey:@"CPStackViewEdgeInsets"]; // Assuming CPEdgeInsets supports obj coding or manual decode
if (!_edgeInsets) _edgeInsets = CPEdgeInsetsMake(0,0,0,0);
@@ -787,7 +670,10 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
_viewsTrailing = [aCoder decodeObjectForKey:@"CPStackViewViewsTrailing"] || [];
// Rebuild arranged subviews cache
[self _rebuildArrangedSubviews];
_arrangedSubviews = [[CPMutableArray alloc] init];
[_arrangedSubviews addObjectsFromArray:_viewsLeading];
[_arrangedSubviews addObjectsFromArray:_viewsCenter];
[_arrangedSubviews addObjectsFromArray:_viewsTrailing];
_customSpacings = [aCoder decodeObjectForKey:@"CPStackViewCustomSpacings"] || [[CPMapTable alloc] init];
_visibilityPriorities = [[CPMapTable alloc] init]; // usually not persisted
@@ -800,7 +686,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
[super encodeWithCoder:aCoder];
[aCoder encodeInt:_orientation forKey:@"CPStackViewOrientation"];
[aCoder encodeInt:_alignment forKey:@"CPStackViewAlignment"];
[aCoder encodeInt:_distribution forKey:@"CPStackViewDistribution"];
[aCoder encodeFloat:_spacing forKey:@"CPStackViewSpacing"];
[aCoder encodeObject:_edgeInsets forKey:@"CPStackViewEdgeInsets"];
[aCoder encodeBool:_detachesHiddenViews forKey:@"CPStackViewDetachesHiddenViews"];
-1
View File
@@ -36,7 +36,6 @@
@class CPLayoutManager
@class CPTextContainer
@class CPFontManager
@class _CPFontPanelPreviewView
/*
Collection indexes
+1 -1
View File
@@ -26,7 +26,7 @@
CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName";
@typedef CPTabStopType
// Define missing global tab stop type constants
CPLeftTabStopType = 0;
CPRightTabStopType = 1;
CPCenterTabStopType = 2;
+151 -112
View File
@@ -28,18 +28,17 @@
@import "CPMenu.j"
@import "CPMenuItem.j"
@typedef CPRulerOrientation
CPHorizontalRuler = 0;
CPVerticalRuler = 1;
CPRulerOrientationHorizontal = 0;
CPRulerOrientationVertical = 1;
// Orientations matching AppKit standards
// typedef enum CPRulerOrientation
CPHorizontalRuler = 0,
CPVerticalRuler = 1,
CPRulerOrientationHorizontal = 0,
CPRulerOrientationVertical = 1
@class CPRulerView;
@class CPScrollView;
@class CPTextTab;
// MARK: - CPRulerMarker
// MARK: - CPRulerMarker (Interactive Handles with Dynamic Alignment Icons)
@implementation CPRulerMarker : CPView
{
@@ -58,18 +57,13 @@ CPRulerOrientationVertical = 1;
_imageValue = anImageValue;
_representedObject = anObject;
// CRITICAL: Subviews must not intercept mouse events so CPRulerView receives all mouseDragged: events
[self setHitTests:NO];
_label = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 12, 12)];
[_label setFont:[CPFont systemFontOfSize:10.0]];
[_label setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
[_label setAlignment:CPCenterTextAlignment];
[_label setHitTests:NO];
[self addSubview:_label];
_customHandleView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[_customHandleView setHitTests:NO];
[self addSubview:_customHandleView];
[self updateMarkerIcon];
@@ -94,6 +88,8 @@ CPRulerOrientationVertical = 1;
[self updateMarkerIcon];
}
// Dynamically sets the Unicode triangle direction based on the alignment or indent type,
// or draws custom split-height grab handles for indentation controls.
- (void)updateMarkerIcon
{
var isIndentMarker = (_representedObject === @"CPFirstLineIndent" || _representedObject === @"CPHeadIndent");
@@ -105,19 +101,28 @@ CPRulerOrientationVertical = 1;
var frame = [self bounds];
[_customHandleView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
// Remove old internal rendering to update cleanly
[[_customHandleView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
var isFirstLine = (_representedObject === @"CPFirstLineIndent");
[_customHandleView setBackgroundColor:[CPColor colorWithWhite:0.45 alpha:1.0]];
var innerView = [[CPView alloc] initWithFrame:CGRectMake(1.0, 1.0, Math.max(1.0, frame.size.width - 2.0), Math.max(1.0, frame.size.height - 2.0))];
[innerView setHitTests:NO];
[innerView setBackgroundColor:isFirstLine ? [CPColor colorWithWhite:0.95 alpha:1.0] : [CPColor colorWithWhite:0.80 alpha:1.0]];
// Dark outline/border representation
[_customHandleView setBackgroundColor:[CPColor colorWithWhite:0.5 alpha:1.0]];
// Inner fill (top handle is lighter, bottom is slightly darker)
var innerView = [[CPView alloc] initWithFrame:CGRectMake(1.0, 1.0, frame.size.width - 2.0, frame.size.height - 2.0)];
if (isFirstLine)
[innerView setBackgroundColor:[CPColor colorWithWhite:0.92 alpha:1.0]];
else
[innerView setBackgroundColor:[CPColor colorWithWhite:0.80 alpha:1.0]];
[_customHandleView addSubview:innerView];
var gripLine = [[CPView alloc] initWithFrame:CGRectMake(Math.floor(frame.size.width / 2.0) - 1.0, 1.0, 1.0, Math.max(1.0, frame.size.height - 3.0))];
[gripLine setHitTests:NO];
[gripLine setBackgroundColor:[CPColor colorWithWhite:0.5 alpha:1.0]];
// Horizontal indicator line to visually guide drag interactions
var gripLine = [[CPView alloc] initWithFrame:CGRectMake(Math.floor(frame.size.width / 2.0) - 1.0, 2.0, 1.0, frame.size.height - 4.0)];
[gripLine setBackgroundColor:[CPColor colorWithWhite:0.6 alpha:1.0]];
[innerView addSubview:gripLine];
}
else
@@ -130,23 +135,33 @@ CPRulerOrientationVertical = 1;
{
var align = [_representedObject alignment];
if (align === CPLeftTextAlignment)
[_label setStringValue:@"▶"];
[_label setStringValue:@"▶"]; // Left-aligned points Right
else if (align === CPCenterTextAlignment)
[_label setStringValue:@"▼"];
[_label setStringValue:@"▼"]; // Center-aligned points Down
else if (align === CPRightTextAlignment)
[_label setStringValue:@"◀"];
[_label setStringValue:@"◀"]; // Right-aligned points Left
}
else if ([_representedObject isKindOfClass:[CPString class]])
{
if (_representedObject === @"CPTailIndent")
[_label setStringValue:@"⥘"]; // Solid downward triangle for tail indent
else
[_label setStringValue:@"⇡"]; // Fallback standard up marker
}
else
{
[_label setStringValue:@""];
[_label setStringValue:@""]; // Fallback standard up marker
}
}
}
// MARK: -
// MARK: Context Menu Support
- (CPMenu)menuForEvent:(CPEvent)anEvent
{
var menu = [[CPMenu alloc] initWithTitle:@"Marker Context Menu"];
// If the marker represents a standard tab stop, allow changing its type
if ([_representedObject isKindOfClass:[CPTextTab class]])
{
var itemLeft = [menu addItemWithTitle:@"Left Tab Stop" action:@selector(changeTypeToLeft:) keyEquivalent:@""],
@@ -165,11 +180,17 @@ CPRulerOrientationVertical = 1;
[menu addItem:[CPMenuItem separatorItem]];
}
// Determine the context-specific delete title
var deleteTitle = @"Delete Tab Stop";
if (_representedObject === @"CPFirstLineIndent")
deleteTitle = @"Reset 1st line indentation";
else if (_representedObject === @"CPHeadIndent")
deleteTitle = @"Reset head indentation";
if ([_representedObject isKindOfClass:[CPString class]])
{
if (_representedObject === @"CPFirstLineIndent")
deleteTitle = @"Delete 1st line indentation marker";
else if (_representedObject === @"CPHeadIndent")
deleteTitle = @"Delete head indentation marker";
else if (_representedObject === @"CPTailIndent")
deleteTitle = @"Delete tail indentation marker";
}
var itemDelete = [menu addItemWithTitle:deleteTitle action:@selector(deleteMarker:) keyEquivalent:@""];
[itemDelete setTarget:self];
@@ -177,51 +198,66 @@ CPRulerOrientationVertical = 1;
return menu;
}
- (void)changeTypeToLeft:(id)sender { [self _changeAlignment:CPLeftTextAlignment]; }
- (void)changeTypeToCenter:(id)sender { [self _changeAlignment:CPCenterTextAlignment]; }
- (void)changeTypeToRight:(id)sender { [self _changeAlignment:CPRightTextAlignment]; }
- (void)changeTypeToLeft:(id)sender
{
[self _changeAlignment:CPLeftTextAlignment];
}
- (void)changeTypeToCenter:(id)sender
{
[self _changeAlignment:CPCenterTextAlignment];
}
- (void)changeTypeToRight:(id)sender
{
[self _changeAlignment:CPRightTextAlignment];
}
- (void)_changeAlignment:(CPTextAlignment)alignment
{
if (![_representedObject isKindOfClass:[CPTextTab class]])
return;
var oldTab = _representedObject,
newTab = [[CPTextTab alloc] initWithType:alignment location:_imageValue];
var oldTab = _representedObject;
var newTab = [[CPTextTab alloc] initWithType:alignment location:_imageValue];
// Using setRepresentedObject: automatically updates the marker triangle direction
[self setRepresentedObject:newTab];
var client = [_rulerView clientView];
if (client && [client respondsToSelector:@selector(rulerView:didUpdateMarker:oldTab:)])
{
[client rulerView:_rulerView didUpdateMarker:self oldTab:oldTab];
}
}
- (void)deleteMarker:(id)sender
{
var client = [_rulerView clientView];
if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)])
{
[client rulerView:_rulerView didRemoveMarker:self];
}
[_rulerView removeMarker:self];
}
@end
// MARK: - CPRulerView
// MARK: - CPRulerView (Pure DOM + Interactive Engine)
@implementation CPRulerView : CPView
{
CPScrollView _scrollView @accessors(property=scrollView);
CPRulerOrientation _orientation @accessors(property=orientation);
CPView _clientView;
CPView _clientView @accessors(property=clientView);
float _ruleThickness @accessors(property=ruleThickness);
float _reservedThicknessForMarkers;
CPArray _markers;
CPRulerMarker _draggingMarker @accessors(getter=draggingMarker);
// Dragger variables
CPRulerMarker _draggingMarker;
CGPoint _dragStartPoint;
float _dragStartLocation;
}
@@ -249,16 +285,7 @@ CPRulerOrientationVertical = 1;
[self updateRuler];
}
- (CPView)clientView
{
return _clientView || [_scrollView documentView];
}
- (void)setClientView:(CPView)aView
{
_clientView = aView;
}
// Markers registration
- (void)addMarker:(CPRulerMarker)aMarker
{
if ([_markers containsObject:aMarker])
@@ -288,20 +315,14 @@ CPRulerOrientationVertical = 1;
[self addSubview:marker];
[self _positionMarker:marker];
}
[self updateRuler];
}
- (CPRulerMarker)_markerAtPoint:(CGPoint)aPoint
{
// Search in reverse order so indent paddles (added last) are prioritized over overlapping 0-location tabs
for (var i = [_markers count] - 1; i >= 0; i--)
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i],
frame = [marker frame];
// Expanded hit-box by 3px on all sides for easy grabbing
var hitFrame = CGRectMake(frame.origin.x - 3.0, frame.origin.y - 2.0, frame.size.width + 6.0, frame.size.height + 4.0);
if (CGRectContainsPoint(hitFrame, aPoint))
var marker = [_markers objectAtIndex:i];
if (CGRectContainsPoint([marker frame], aPoint))
return marker;
}
return nil;
@@ -317,48 +338,55 @@ CPRulerOrientationVertical = 1;
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]),
markerLocation = [aMarker imageValue],
rep = [aMarker representedObject];
markerLocation = [aMarker imageValue];
if (isHorizontal)
{
var x = markerLocation - scrollPoint.x - 6.0,
var x = markerLocation - scrollPoint.x - 6.0, // Center the 12px wide marker
y = rulerHeight - 11.0,
w = 12.0,
h = 12.0;
if (rep === @"CPFirstLineIndent")
// Align the First Line Indent (upper half) and Head Indent (lower half) controls
if ([aMarker representedObject] === @"CPFirstLineIndent")
{
y = 0.0;
h = Math.floor(rulerHeight / 2.0);
}
else if (rep === @"CPHeadIndent")
else if ([aMarker representedObject] === @"CPHeadIndent")
{
y = Math.floor(rulerHeight / 2.0);
h = rulerHeight - y - 1.0;
h = rulerHeight - y - 1.0; // Subtract 1px to stay cleanly above bottom border
}
else
{
if (x < -6.0) x = -6.0;
else if (x + 12.0 > rulerWidth) x = rulerWidth - 12.0;
// Keep normal horizontal markers within the bounds of the ruler to prevent clipping
if (x < 0.0)
x = 0.0;
else if (x + 12.0 > rulerWidth)
x = rulerWidth - 12.0;
}
[aMarker setFrame:CGRectMake(x, y, w, h)];
}
else
{
var x = rulerWidth - 11.0,
y = markerLocation - scrollPoint.y - 6.0;
if (y < 0.0) y = 0.0;
else if (y + 12.0 > rulerHeight) y = rulerHeight - 12.0;
// Keep vertical marker within the bounds of the ruler to prevent clipping
if (y < 0.0)
y = 0.0;
else if (y + 12.0 > rulerHeight)
y = rulerHeight - 12.0;
[aMarker setFrame:CGRectMake(x, y, 12.0, 12.0)];
}
}
// MARK: - Interaction Handlers
// MARK: -
// MARK: Interaction Handlers
- (void)mouseDown:(CPEvent)anEvent
{
@@ -370,6 +398,7 @@ CPRulerOrientationVertical = 1;
var rulerLocation = isHorizontal ? (localPoint.x + scrollPoint.x) : (localPoint.y + scrollPoint.y);
// 1. Check if clicked an existing marker
var clickedMarker = [self _markerAtPoint:localPoint];
if (clickedMarker)
{
@@ -377,22 +406,20 @@ CPRulerOrientationVertical = 1;
_dragStartPoint = localPoint;
_dragStartLocation = [_draggingMarker imageValue];
}
// 2. Otherwise, create a new marker dynamically where the user clicked
else
{
// Allow client view to constrain initial placement
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:willAddMarker:atLocation:)])
rulerLocation = [client rulerView:self willAddMarker:nil atLocation:rulerLocation];
var newMarker = [[CPRulerMarker alloc] initWithRulerView:self
markerLocation:rulerLocation
imageValue:rulerLocation
representedObject:nil];
[self addMarker:newMarker];
// Notify the client view (e.g., CPTextView) that a new marker was added
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:didAddMarker:)])
[client rulerView:self didAddMarker:newMarker];
_draggingMarker = newMarker;
_dragStartPoint = localPoint;
_dragStartLocation = rulerLocation;
@@ -409,35 +436,34 @@ CPRulerOrientationVertical = 1;
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
var delta = isHorizontal ? (localPoint.x - _dragStartPoint.x) : (localPoint.y - _dragStartPoint.y),
newLocation = Math.max(0.0, _dragStartLocation + delta);
newLocation = _dragStartLocation + delta;
// Constrain marker location to last possible position
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:willMoveMarker:toLocation:)])
newLocation = [client rulerView:self willMoveMarker:_draggingMarker toLocation:newLocation];
if (newLocation < 0) newLocation = 0;
[_draggingMarker setImageValue:newLocation];
[self _positionMarker:_draggingMarker];
var rep = [_draggingMarker representedObject],
isIndent = (rep === @"CPFirstLineIndent" || rep === @"CPHeadIndent" || rep === @"CPTailIndent");
var draggedOff = !isIndent && (isHorizontal
? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15));
// Smoothly redraw ruler and margin bounds on every drag step
[self updateRuler];
// Check if dragged off the ruler (more than 15px off the boundary)
var draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15);
if (draggedOff)
{
// Visual feedback: Dim the handle to 40% and turn the triangle icon gray
[_draggingMarker setAlphaValue:0.4];
[[_draggingMarker label] setTextColor:[CPColor grayColor]];
}
else
{
// Restore standard styling when dragged back into the active strip
[_draggingMarker setAlphaValue:1.0];
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
}
// Notify the CPTextView that the marker coordinates shifted
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:didMoveMarker:)])
[client rulerView:self didMoveMarker:_draggingMarker];
}
@@ -449,12 +475,10 @@ CPRulerOrientationVertical = 1;
var localPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil],
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
rep = [_draggingMarker representedObject],
isIndent = (rep === @"CPFirstLineIndent" || rep === @"CPHeadIndent" || rep === @"CPTailIndent");
var draggedOff = !isIndent && (isHorizontal
? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15));
// If dragged more than 15 pixels off the ruler, delete the marker
draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15);
if (draggedOff)
{
@@ -466,19 +490,22 @@ CPRulerOrientationVertical = 1;
}
else
{
// Ensure marker style is fully restored if not deleted
[_draggingMarker setAlphaValue:1.0];
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
}
_draggingMarker = nil;
[self updateRuler];
}
// MARK: - Layout Builder
// MARK: -
// MARK: DOM Layout Builder
- (void)updateRuler
{
// Wipe subviews to redraw the dynamic visible tick lines/numbers
[self setSubviews:@[]];
if (!_scrollView)
@@ -497,11 +524,12 @@ CPRulerOrientationVertical = 1;
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]);
// Draw solid horizontal bottom border (pure, razor-sharp CSS DOM view)
var bottomBorder = [[CPView alloc] initWithFrame:CGRectMake(0, rulerHeight - 1, rulerWidth, 1)];
[bottomBorder setHitTests:NO];
[bottomBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]];
[self addSubview:bottomBorder];
// Find indent markers to determine background highlight boundaries
var firstLineMarker = nil,
headMarker = nil;
for (var i = 0; i < [_markers count]; i++)
@@ -515,30 +543,31 @@ CPRulerOrientationVertical = 1;
var halfHeight = Math.floor(rulerHeight / 2.0);
// Draw First Line Indent background - top half (lighter gray)
if (firstLineMarker)
{
var firstLineX = [firstLineMarker imageValue] - scrollPoint.x;
if (firstLineX > 0)
{
var firstLineBg = [[CPView alloc] initWithFrame:CGRectMake(0, 0, firstLineX, halfHeight)];
[firstLineBg setHitTests:NO];
[firstLineBg setBackgroundColor:[CPColor colorWithWhite:0.93 alpha:1.0]];
[self addSubview:firstLineBg];
}
}
// Draw Head Indent background - bottom half (slightly darker gray)
if (headMarker)
{
var headX = [headMarker imageValue] - scrollPoint.x;
if (headX > 0)
{
var headBg = [[CPView alloc] initWithFrame:CGRectMake(0, halfHeight, headX, rulerHeight - halfHeight - 1.0)];
[headBg setHitTests:NO];
[headBg setBackgroundColor:[CPColor colorWithWhite:0.86 alpha:1.0]];
[self addSubview:headBg];
}
}
// Render ruler tick lines and labels on top of shaded areas
for (var val = start; val <= end; val += 10)
{
if (val < 0) continue;
@@ -548,21 +577,30 @@ CPRulerOrientationVertical = 1;
tickHeight = isMajor ? 8.0 : 4.0,
tickY = rulerHeight - tickHeight - 1.0;
// Tick mark CSS line view
var tick = [[CPView alloc] initWithFrame:CGRectMake(screenX, tickY, 1.0, tickHeight)];
[tick setHitTests:NO];
[tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]];
[self addSubview:tick];
// Unit label
if (isMajor)
{
var labelX = screenX - 20.0,
alignment = CPCenterTextAlignment;
if (labelX < 0.0) { labelX = Math.max(0.0, screenX); alignment = CPLeftTextAlignment; }
else if (labelX + 40.0 > rulerWidth) { labelX = rulerWidth - 40.0; alignment = CPRightTextAlignment; }
// Adjust label frame and alignment if it lands near left/right bounds
if (labelX < 0.0)
{
labelX = Math.max(0.0, screenX);
alignment = CPLeftTextAlignment;
}
else if (labelX + 40.0 > rulerWidth)
{
labelX = rulerWidth - 40.0;
alignment = CPRightTextAlignment;
}
var label = [[CPTextField alloc] initWithFrame:CGRectMake(labelX, 1.0, 40.0, 12.0)];
[label setHitTests:NO];
[label setStringValue:[CPString stringWithFormat:@"%d", val]];
[label setFont:[CPFont systemFontOfSize:8.0]];
[label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]];
@@ -619,11 +657,12 @@ CPRulerOrientationVertical = 1;
}
}
// Add back existing markers without re-creating them
// Reposition and display active markers
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i];
[self addSubview:marker];
if ([marker superview] !== self)
[self addSubview:marker];
[self _positionMarker:marker];
}
}
+96 -174
View File
@@ -2472,10 +2472,6 @@ Sets the selection to a range of characters in response to user action.
if (!ruler)
return;
// Do not rebuild markers if user is currently dragging one
if ([ruler draggingMarker])
return;
var selectedRange = [self selectedRange],
paragraphStyle = [CPParagraphStyle defaultParagraphStyle],
currentAttributes = _typingAttributes;
@@ -2484,10 +2480,10 @@ Sets the selection to a range of characters in response to user action.
if (textLength > 0)
{
var charIndex = selectedRange.location;
// Safety bounds checks for cursor placements
if (charIndex >= textLength)
charIndex = textLength - 1;
if (charIndex < 0)
charIndex = 0;
@@ -2501,6 +2497,7 @@ Sets the selection to a range of characters in response to user action.
tabStops = [paragraphStyle tabStops],
count = [tabStops count];
// A. Load existing tab stop markers onto the ruler
for (var i = 0; i < count; i++)
{
var tab = [tabStops objectAtIndex:i],
@@ -2511,6 +2508,7 @@ Sets the selection to a range of characters in response to user action.
[markers addObject:marker];
}
// B. Add Indentation Handles (First line indent & Head indent)
var firstLineMarker = [[CPRulerMarker alloc] initWithRulerView:ruler
markerLocation:[paragraphStyle firstLineHeadIndent]
imageValue:[paragraphStyle firstLineHeadIndent]
@@ -2526,6 +2524,7 @@ Sets the selection to a range of characters in response to user action.
[ruler setMarkers:markers];
}
// Local comparison function helper for tab sorting
var compareTabStops = function(obj1, obj2, context) {
if ([obj1 location] < [obj2 location]) return CPOrderedAscending;
if ([obj1 location] > [obj2 location]) return CPOrderedDescending;
@@ -2534,10 +2533,14 @@ var compareTabStops = function(obj1, obj2, context) {
- (void)rulerView:(CPRulerView)rulerView didAddMarker:(CPRulerMarker)marker
{
if (![self _didBeginEditing] || ![self shouldChangeTextInRange:_selectionRange replacementString:nil])
return;
var selectedRange = [self selectedRange],
paragraphStyle = [CPParagraphStyle defaultParagraphStyle],
currentAttributes = _typingAttributes;
// Retrieve active attributes at the cursor position if there is no selection
var textLength = [_textStorage length],
charIndex = selectedRange.location;
@@ -2545,7 +2548,6 @@ var compareTabStops = function(obj1, obj2, context) {
{
if (charIndex >= textLength)
charIndex = textLength - 1;
if (charIndex < 0)
charIndex = 0;
@@ -2555,32 +2557,48 @@ var compareTabStops = function(obj1, obj2, context) {
if ([currentAttributes objectForKey:CPParagraphStyleAttributeName])
paragraphStyle = [currentAttributes objectForKey:CPParagraphStyleAttributeName];
var mutableStyle = [paragraphStyle mutableCopy],
newTab = [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:[marker imageValue]],
var mutableStyle = [paragraphStyle mutableCopy];
// Create a new Left-aligned tab stop where the user clicked
var newTab = [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:[marker imageValue]],
tabs = [[mutableStyle tabStops] mutableCopy];
[tabs addObject:newTab];
// Sort tabs by location ascending
[tabs sortUsingFunction:compareTabStops context:nil];
[mutableStyle setTabStops:tabs];
[marker setRepresentedObject:newTab];
// Find the target text range to modify (selection or containing paragraph)
var targetRange = selectedRange;
if (targetRange.length === 0 && textLength > 0)
if (targetRange.length === 0)
targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph];
if (targetRange.length > 0)
{
[_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(targetRange)];
[_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(targetRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(targetRange)];
[_layoutManager textStorage:_textStorage
edited:0
range:CPMakeRangeCopy(targetRange)
changeInLength:0
invalidatedRange:CPMakeRangeCopy(targetRange)];
}
[_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName];
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
if (selectedRange.length === 0)
{
[_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName];
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
}
}
- (void)rulerView:(CPRulerView)rulerView didMoveMarker:(CPRulerMarker)marker
{
if (![self _didBeginEditing] || ![self shouldChangeTextInRange:_selectionRange replacementString:nil])
return;
var selectedRange = [self selectedRange],
paragraphStyle = [CPParagraphStyle defaultParagraphStyle],
currentAttributes = _typingAttributes;
@@ -2592,7 +2610,6 @@ var compareTabStops = function(obj1, obj2, context) {
{
if (charIndex >= textLength)
charIndex = textLength - 1;
if (charIndex < 0)
charIndex = 0;
@@ -2603,45 +2620,58 @@ var compareTabStops = function(obj1, obj2, context) {
paragraphStyle = [currentAttributes objectForKey:CPParagraphStyleAttributeName];
var mutableStyle = [paragraphStyle mutableCopy],
rep = [marker representedObject];
oldTab = [marker representedObject];
if (!rep)
if (!oldTab)
return;
// A. Handle standard tab stops
if ([rep isKindOfClass:[CPTextTab class]])
if ([oldTab isKindOfClass:[CPTextTab class]])
{
var newTab = [[CPTextTab alloc] initWithType:[rep alignment] location:[marker imageValue]],
var newTab = [[CPTextTab alloc] initWithType:[oldTab alignment] location:[marker imageValue]],
tabs = [[mutableStyle tabStops] mutableCopy];
[tabs removeObject:rep];
[tabs removeObject:oldTab];
[tabs addObject:newTab];
// Sort tabs by location ascending
[tabs sortUsingFunction:compareTabStops context:nil];
[mutableStyle setTabStops:tabs];
[marker setRepresentedObject:newTab];
}
// B. Handle Indentation Marker drags (First Line, Left, and Right indents)
else if ([oldTab isKindOfClass:[CPString class]])
{
if (oldTab === @"CPFirstLineIndent")
[mutableStyle setFirstLineHeadIndent:[marker imageValue]];
else if (oldTab === @"CPHeadIndent")
[mutableStyle setHeadIndent:[marker imageValue]];
else if (oldTab === @"CPTailIndent")
[mutableStyle setTailIndent:[marker imageValue]];
}
// B. Handle Indentation Marker drags directly by string literal
else if (rep === @"CPFirstLineIndent")
[mutableStyle setFirstLineHeadIndent:[marker imageValue]];
else if (rep === @"CPHeadIndent")
[mutableStyle setHeadIndent:[marker imageValue]];
else if (rep === @"CPTailIndent")
[mutableStyle setTailIndent:[marker imageValue]];
// Find the target text range to modify (selection or containing paragraph)
var targetRange = selectedRange;
if (targetRange.length === 0 && textLength > 0)
if (targetRange.length === 0)
targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph];
if (targetRange.length > 0)
{
[_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(targetRange)];
[_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(targetRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(targetRange)];
[_layoutManager textStorage:_textStorage
edited:0
range:CPMakeRangeCopy(targetRange)
changeInLength:0
invalidatedRange:CPMakeRangeCopy(targetRange)];
}
[_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName];
if (selectedRange.length === 0)
{
[_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName];
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
}
[_layoutManager _validateLayoutAndGlyphs];
[self sizeToFit];
@@ -2650,6 +2680,9 @@ var compareTabStops = function(obj1, obj2, context) {
- (void)rulerView:(CPRulerView)rulerView didRemoveMarker:(CPRulerMarker)marker
{
if (![self _didBeginEditing] || ![self shouldChangeTextInRange:_selectionRange replacementString:nil])
return;
var selectedRange = [self selectedRange],
paragraphStyle = [CPParagraphStyle defaultParagraphStyle],
currentAttributes = _typingAttributes;
@@ -2659,8 +2692,11 @@ var compareTabStops = function(obj1, obj2, context) {
if (textLength > 0)
{
if (charIndex >= textLength) charIndex = textLength - 1;
if (charIndex < 0) charIndex = 0;
if (charIndex >= textLength)
charIndex = textLength - 1;
if (charIndex < 0)
charIndex = 0;
currentAttributes = [_textStorage attributesAtIndex:charIndex effectiveRange:nil];
}
@@ -2675,20 +2711,31 @@ var compareTabStops = function(obj1, obj2, context) {
var tabs = [[mutableStyle tabStops] mutableCopy];
[tabs removeObject:oldTab];
[mutableStyle setTabStops:tabs];
// Find the target text range to modify (selection or containing paragraph)
var targetRange = selectedRange;
if (targetRange.length === 0 && textLength > 0)
if (targetRange.length === 0)
targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph];
if (targetRange.length > 0)
{
[_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(targetRange)];
[_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(targetRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(targetRange)];
[_layoutManager textStorage:_textStorage
edited:0
range:CPMakeRangeCopy(targetRange)
changeInLength:0
invalidatedRange:CPMakeRangeCopy(targetRange)];
}
[_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName];
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
if (selectedRange.length === 0)
{
[_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName];
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
}
}
- (void)rulerView:(CPRulerView)rulerView didUpdateMarker:(CPRulerMarker)marker oldTab:(id)oldTab
@@ -2712,11 +2759,19 @@ var compareTabStops = function(obj1, obj2, context) {
[tabs removeObject:oldTab];
[tabs addObject:newTab];
// Sort tabs ascending
[tabs sortUsingFunction:compareTabStops context:nil];
[mutableStyle setTabStops:tabs];
[_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(targetRange)];
[_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(targetRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(targetRange)];
[_layoutManager textStorage:_textStorage
edited:0
range:CPMakeRangeCopy(targetRange)
changeInLength:0
invalidatedRange:CPMakeRangeCopy(targetRange)];
if (selectedRange.length === 0)
{
@@ -2724,145 +2779,12 @@ var compareTabStops = function(obj1, obj2, context) {
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
}
// Force layouts and view canvas update
[_layoutManager _validateLayoutAndGlyphs];
[self sizeToFit];
[self setNeedsDisplay:YES];
}
- (float)rulerView:(CPRulerView)aRulerView willMoveMarker:(CPRulerMarker)aMarker toLocation:(float)proposedLocation
{
var rep = [aMarker representedObject];
// Indent markers can be dragged anywhere >= 0
if (rep === @"CPFirstLineIndent" || rep === @"CPHeadIndent" || rep === @"CPTailIndent")
return Math.max(0.0, proposedLocation);
var textLength = [_textStorage length];
if (textLength === 0)
return Math.max(0.0, proposedLocation);
var selectedRange = [self selectedRange],
targetRange = selectedRange;
if (targetRange.length === 0)
targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph];
if (targetRange.length === 0)
return Math.max(0.0, proposedLocation);
[_layoutManager _validateLayoutAndGlyphs];
var theString = [_textStorage string],
minLocation = 0.0,
containerWidth = [_textContainer containerSize].width,
maxLocation = containerWidth,
currentParagraphStyle = [_textStorage attribute:CPParagraphStyleAttributeName atIndex:targetRange.location effectiveRange:nil] || [CPParagraphStyle defaultParagraphStyle],
tabStops = [currentParagraphStyle tabStops] || [],
tabCount = [tabStops count];
// 1. Right boundary: Tail indent (if configured)
var tailIndent = [currentParagraphStyle tailIndent];
if (tailIndent > 0.0)
maxLocation = Math.min(maxLocation, tailIndent);
else if (tailIndent < 0.0)
maxLocation = Math.min(maxLocation, containerWidth + tailIndent);
// 2. Identify active tab stop index
var targetTabIndex = -1;
for (var i = 0; i < tabCount; i++)
{
var tab = [tabStops objectAtIndex:i];
if (tab === rep || ([tab location] === [rep location] && [tab alignment] === [rep alignment]))
{
targetTabIndex = i;
break;
}
}
// 3. Constrain to neighboring tab stops (Left and Right)
var safetySpacer = 2.0;
if (targetTabIndex !== -1)
{
// Left neighbor constraint (previous tab stop)
if (targetTabIndex > 0)
{
var prevTab = [tabStops objectAtIndex:targetTabIndex - 1];
minLocation = Math.max(minLocation, [prevTab location] + safetySpacer);
}
// Right neighbor constraint (next tab stop)
if (targetTabIndex + 1 < tabCount)
{
var nextTab = [tabStops objectAtIndex:targetTabIndex + 1];
maxLocation = Math.min(maxLocation, [nextTab location] - safetySpacer);
}
}
else
{
// For new markers being added between existing tab stops
for (var i = 0; i < tabCount; i++)
{
var tabLoc = [[tabStops objectAtIndex:i] location];
if (tabLoc < proposedLocation)
minLocation = Math.max(minLocation, tabLoc + safetySpacer);
else if (tabLoc > proposedLocation)
maxLocation = Math.min(maxLocation, tabLoc - safetySpacer);
}
}
// 4. Constrain to preceding text on active lines
var start = targetRange.location,
end = CPMaxRange(targetRange),
tabCountInLine = 0,
lineStart = start;
for (var i = start; i < end; i++)
{
var charCode = theString.charCodeAt(i);
if (charCode === 10 || charCode === 13)
{
tabCountInLine = 0;
lineStart = i + 1;
continue;
}
if (charCode === 9) // '\t'
{
if (tabCountInLine === targetTabIndex || targetTabIndex === -1)
{
var precedingX = 0.0;
if (i === lineStart)
{
var isFirstLine = (lineStart === 0 || theString.charCodeAt(lineStart - 1) === 10 || theString.charCodeAt(lineStart - 1) === 13);
precedingX = isFirstLine ? [currentParagraphStyle firstLineHeadIndent] : [currentParagraphStyle headIndent];
}
else
{
var glyphRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(i - 1, 1) inTextContainer:_textContainer];
precedingX = CGRectGetMaxX(glyphRect);
}
var textLimit = precedingX + safetySpacer;
if (textLimit > minLocation)
minLocation = textLimit;
}
tabCountInLine++;
}
}
if (minLocation > maxLocation)
minLocation = maxLocation;
// 5. Clamp proposedLocation within [minLocation, maxLocation]
return Math.min(maxLocation, Math.max(minLocation, proposedLocation));
}
- (float)rulerView:(CPRulerView)aRulerView willAddMarker:(CPRulerMarker)aMarker atLocation:(float)proposedLocation
{
return [self rulerView:aRulerView willMoveMarker:aMarker toLocation:proposedLocation];
}
@end
@implementation CPTextView (CPTextViewDelegate)
@@ -3166,8 +3088,8 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey",
{
var rect = [_textView._layoutManager boundingRectForGlyphRange:CPMakeRange(aLoc, 1) inTextContainer:_textView._textContainer];
if (aLoc >= [_textView._layoutManager numberOfCharacters])
rect.origin.x = CGRectGetMaxX(rect);
if (aLoc >= [_textView._layoutManager numberOfCharacters])
rect.origin.x = CGRectGetMaxX(rect);
[self setRect:rect];
}
+42 -24
View File
@@ -138,28 +138,32 @@ var CPSystemTypesetterFactory,
return [_layoutManager textContainers];
}
// Retrieves correct CPTextTab stop accounting for custom stops and default intervals
// Retrieves correct CPTextTab stop accounting for CPArray properties
- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction
{
var tabStops = [_currentParagraph tabStops],
defaultInterval = [_currentParagraph defaultTabInterval] || 28.0;
var tabStops = [_currentParagraph tabStops];
var l = tabStops ? [tabStops count] : 0;
if (!tabStops)
tabStops = [[CPParagraphStyle defaultParagraphStyle] tabStops];
// 1. If custom tab stops exist ahead of current position, use the first one encountered
if (l > 0)
var l = [tabStops count];
if (l === 0)
return nil;
// Find the first tab stop that is strictly greater than the current width
for (var i = 0; i < l; i++)
{
for (var i = 0; i < l; i++)
{
var tab = [tabStops objectAtIndex:i];
var tab = [tabStops objectAtIndex:i];
if ([tab location] > aWidth)
return tab;
}
if ([tab location] > aWidth)
return tab;
}
// 2. Otherwise (or when all custom tab stops are behind the text), advance to the next default interval
var nextLocation = (Math.floor(aWidth / defaultInterval) + 1) * defaultInterval;
// If aWidth exceeds the last tab stop, dynamically calculate the next
// tab location using the default tab interval.
var defaultInterval = [_currentParagraph defaultTabInterval] || 28.0;
var nextLocation = CEIL((aWidth + 1.0) / defaultInterval) * defaultInterval;
return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextLocation];
}
@@ -201,7 +205,7 @@ var CPSystemTypesetterFactory,
[_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange];
[_layoutManager _setAdvancements:advancements forGlyphRange:lineRange];
// fix the _lineFragments when fontsizes differ
//fix the _lineFragments when fontsizes differ
var l = _lineFragments.length;
for (var i = 0 ; i < l ; i++)
@@ -247,7 +251,7 @@ var CPSystemTypesetterFactory,
isTabStop = NO,
isAttachment = NO,
isWordWrapped = NO,
numberOfGlyphs = [_textStorage length],
numberOfGlyphs= [_textStorage length],
leading,
numLines = 0,
theString = [_textStorage string],
@@ -289,7 +293,7 @@ var CPSystemTypesetterFactory,
for (; numLines != maxNumLines && glyphIndex < numberOfGlyphs; glyphIndex++)
{
// check whether there is any change in the attributes from here on
// check whether there any change in the attributes from here on
if (!CPLocationInRange(glyphIndex, _attributesRange))
{
_currentAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:_attributesRange];
@@ -323,6 +327,15 @@ var CPSystemTypesetterFactory,
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
isFirstLineOfLayout = NO;
}
// Calculate the right wrapping margin based on tail indent
var tailIndent = [_currentParagraph tailIndent];
if (tailIndent > 0.0)
rightMargin = tailIndent;
else if (tailIndent < 0.0)
rightMargin = containerSizeWidth + tailIndent;
else
rightMargin = containerSizeWidth;
// Handle the layout's very first line indentation
if (isFirstLineOfLayout)
@@ -406,10 +419,10 @@ var CPSystemTypesetterFactory,
// We are processing characters, so we are no longer at the start of a physical line
isStartOfPhysicalLine = NO;
var currentCharCode = theString.charCodeAt(glyphIndex),
var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons
rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor;
switch (currentCharCode)
switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char.
{
case CPAttachmentCharacter:
{
@@ -441,8 +454,7 @@ var CPSystemTypesetterFactory,
}
case 9: // '\t'
{
// Measure against the actual text position before the tab stop
var nextTab = [self textTabForWidth:prevRangeWidth + lineOrigin.x writingDirection:0];
var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0];
isTabStop = YES;
@@ -489,7 +501,7 @@ var CPSystemTypesetterFactory,
}
else
{
rangeWidth = prevRangeWidth + 28.0; // standard fallback spacer
rangeWidth += 28.0; // standard fallback spacer
}
break;
}
@@ -499,6 +511,10 @@ var CPSystemTypesetterFactory,
wrapRange._height = _lineHeight;
wrapRange._base = _lineBase;
// Optimization: Start measuring from the next character to avoid O(n^2)
// string width calculation within a line since spaces do not carry ligatures or kerning.
// Only reset the measuring range if the next character is NOT another space.
// This prevents compounded subpixel rounding errors with contiguous spaces.
if (theString.charCodeAt(glyphIndex + 1) !== 32)
{
currentAnchor = rangeWidth;
@@ -528,7 +544,7 @@ var CPSystemTypesetterFactory,
isNewline = YES;
isWordWrapped = YES;
glyphIndex = CPMaxRange(lineRange) - 1;
glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character
}
if (isNewline || isTabStop || isAttachment)
@@ -560,6 +576,8 @@ var CPSystemTypesetterFactory,
containerSizeHeight = containerSize.height;
}
// If this is a soft wrap (isWordWrapped), next line gets headIndent.
// If it was a paragraph return, it gets firstLineHeadIndent.
isFirstLineOfParagraph = !isWordWrapped;
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
@@ -585,7 +603,7 @@ var CPSystemTypesetterFactory,
}
}
// Flush remaining characters
// this is to "flush" the remaining characters
if (lineRange.length)
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO];
+1 -1
View File
@@ -277,7 +277,7 @@ function _points2twips(a) { return (a) * 20.0; }
if (!num)
[colorDict setObject:num = [CPNumber numberWithInt:[colorDict count] + 1]
forKey:[color cssString]];
forKey:[color cssString]];
return [num intValue];
}
+1 -2
View File
@@ -19,8 +19,7 @@
*/
@import "CPView.j"
@class CPTextView;
@class CPTextContainer;
@import "CPTextView.j"
@import "CPTextField.j"
@import <Foundation/CPAttributedString.j>
+75 -94
View File
@@ -50,9 +50,6 @@
[self exposeBinding:@"contentArray"];
[self exposeBinding:@"sortDescriptors"];
[self exposeBinding:@"selectionIndexPaths"];
[self exposeBinding:@"selectionIndexPath"];
[self exposeBinding:@"selectedObjects"];
}
+ (CPSet)keyPathsForValuesAffectingContentArray
@@ -62,7 +59,7 @@
+ (CPSet)keyPathsForValuesAffectingArrangedObjects
{
return [CPSet setWithObjects:@"content", @"contentArray", @"sortDescriptors", @"childrenKeyPath"];
return [CPSet setWithObjects:@"content", @"sortDescriptors", @"childrenKeyPath"];
}
+ (CPSet)keyPathsForValuesAffectingSelectionIndexPath
@@ -72,27 +69,27 @@
+ (CPSet)keyPathsForValuesAffectingSelectedObjects
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"arrangedObjects"];
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingSelectedNodes
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"arrangedObjects"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsert
{
return [CPSet setWithObjects:@"editable"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsertChild
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"editable"];
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingCanAddChild
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"editable"];
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsert
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsertChild
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
- (id)init
@@ -118,8 +115,7 @@
}
- (void)prepareContent
{
[self _setContentArray:[CPArray arrayWithObject:[self newObject]]];
{[self _setContentArray:[CPArray arrayWithObject:[self newObject]]];
}
- (BOOL)preservesSelection { return _preservesSelection; }
@@ -148,8 +144,7 @@
- (void)setChildrenKeyPath:(CPString)aKeyPath
{
if (_childrenKeyPath === aKeyPath) return;
_childrenKeyPath = aKeyPath;
[self rearrangeObjects];
_childrenKeyPath = aKeyPath;[self rearrangeObjects];
}
- (CPString)countKeyPath { return _countKeyPath; }
@@ -171,21 +166,14 @@
if (![value isKindOfClass:[CPArray class]])
value = [CPArray arrayWithObject:value];
if (_contentObject === value)
return;
var oldSelectedObjects = nil,
oldSelectionIndexPaths = nil;
oldSelectionIndexPaths = nil;
if ([self preservesSelection])
oldSelectedObjects = [self selectedObjects];
else
oldSelectionIndexPaths = [self selectionIndexPaths];
[self _selectionWillChange];
[self willChangeValueForKey:@"content"];
[self willChangeValueForKey:@"contentArray"];
_contentObject = value;
[self _rearrangeObjects];
@@ -194,32 +182,23 @@
[self __setSelectedObjects:oldSelectedObjects];
else
[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
[self didChangeValueForKey:@"contentArray"];
[self didChangeValueForKey:@"content"];
[self _selectionDidChange];
}
- (void)_setContentArray:(id)anArray { [self setContent:anArray]; }
- (void)_setContentArray:(id)anArray {[self setContent:anArray]; }
- (id)contentArray { return _contentObject; }
- (id)arrangedObjects { return _arrangedObjects; }
- (void)rearrangeObjects
{
[self _selectionWillChange];
[self willChangeValueForKey:@"arrangedObjects"];
[self _rearrangeObjects];
[self didChangeValueForKey:@"arrangedObjects"];
[self _selectionDidChange];
}
- (void)_rearrangeObjects
{
var oldSelectedObjects = nil,
oldSelectionIndexPaths = nil;
oldSelectionIndexPaths = nil;
if ([self preservesSelection])
oldSelectedObjects = [self selectedObjects];
@@ -237,7 +216,7 @@
- (void)__rebuildArrangedObjectsTree
{
var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil],
contentArray = [self contentArray];
contentArray = [self contentArray];
if (contentArray && [contentArray count] > 0)
{
@@ -265,7 +244,7 @@
for (var i = 0; i < count; i++)
{
var obj = [sortedObjects objectAtIndex:i],
node = [[CPTreeNode alloc] initWithRepresentedObject:obj];
node = [[CPTreeNode alloc] initWithRepresentedObject:obj];
if (_childrenKeyPath)
{
@@ -299,7 +278,11 @@
- (BOOL)setSelectionIndexPaths:(CPArray)indexPaths
{
return [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO];
[self _selectionWillChange];
var result = [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO];
[self _selectionDidChange];
return result;
}
- (void)_ensureTreeNodesExistForIndexPaths:(CPArray)indexPaths
@@ -351,8 +334,7 @@
if (needsRebuild)
{
[childNodes removeAllObjects];
var newNodes = [self _buildTreeNodesForObjects:expectedChildObjects];
[childNodes addObjectsFromArray:newNodes];
var newNodes = [self _buildTreeNodesForObjects:expectedChildObjects];[childNodes addObjectsFromArray:newNodes];
}
}
@@ -384,13 +366,21 @@
if ([_selectionIndexPaths isEqualToArray:newPaths])
return NO;
[self _selectionWillChange];
[self willChangeValueForKey:@"selectionIndexPaths"];
_selectionIndexPaths = [newPaths copy];
var binderClass = [[self class] _binderClassForBinding:@"selectionIndexPaths"];
if (binderClass)
{
var binding = [binderClass getBinding:@"selectionIndexPaths" forObject:self];
if (binding)
[binding reverseSetValueFor:@"selectionIndexPaths"];
}
[self didChangeValueForKey:@"selectionIndexPaths"];
[self _selectionDidChange];
return YES;
}
@@ -398,7 +388,9 @@
- (BOOL)addSelectionIndexPaths:(CPArray)indexPaths
{
var newPaths = [_selectionIndexPaths mutableCopy];
[newPaths addObjectsFromArray:indexPaths];
return [self setSelectionIndexPaths:newPaths];
}
@@ -412,7 +404,7 @@
- (CPArray)selectedNodes
{
var nodes = [CPMutableArray array],
count = [_selectionIndexPaths count];
count = [_selectionIndexPaths count];
for (var i = 0; i < count; i++)
{
@@ -426,17 +418,13 @@
- (CPArray)selectedObjects
{
var objects = [CPMutableArray array],
nodes = [self selectedNodes],
count = [nodes count];
nodes = [self selectedNodes],
count = [nodes count];
for (var i = 0; i < count; i++)
{
var representedObject = [[nodes objectAtIndex:i] representedObject];
if (representedObject)
[objects addObject:representedObject];
}
[objects addObject:[[nodes objectAtIndex:i] representedObject]];
return [_CPObservableArray arrayWithArray:objects];
return objects;
}
- (BOOL)__setSelectedObjects:(CPArray)objects
@@ -474,22 +462,22 @@
}
- (BOOL)canInsert { return [self isEditable]; }
- (BOOL)canInsertChild { return [self isEditable] && [_selectionIndexPaths count] > 0; }
- (BOOL)canInsertChild { return [self isEditable] &&[_selectionIndexPaths count] > 0; }
- (BOOL)canAddChild { return [self canInsertChild]; }
- (void)add:(id)sender
{
if (![self canInsert]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
selectionPath = [self selectionIndexPath];
var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject],
selectionPath = [self selectionIndexPath];
if (!selectionPath)
selectionPath = [CPIndexPath indexPathWithIndex:[[[self arrangedObjects] childNodes] count]];
var length = [selectionPath length],
lastIndex = [selectionPath indexAtPosition:length - 1],
insertPath = [selectionPath indexPathByRemovingLastIndex];
lastIndex = [selectionPath indexAtPosition:length - 1],
insertPath = [selectionPath indexPathByRemovingLastIndex];
insertPath = [insertPath indexPathByAddingIndex:lastIndex + 1];
@@ -501,10 +489,10 @@
if (![self canAddChild])
return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]],
childCount = [[parentNode childNodes] count],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount];
var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]],
childCount = [[parentNode childNodes] count],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
@@ -513,8 +501,8 @@
{
if (![self canInsert]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0];
var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject],
indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0];
[self insertObject:newObject atArrangedObjectIndexPath:indexPath];
}
@@ -524,7 +512,7 @@
if (![self canInsertChild]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0];
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
@@ -536,7 +524,6 @@
- (void)insertObjects:(CPArray)objects atArrangedObjectIndexPaths:(CPArray)indexPaths
{
[self _selectionWillChange];
[self willChangeValueForKey:@"content"];
_disableSetContent = YES;
@@ -544,22 +531,21 @@
for (var i = 0; i < count; i++)
{
var object = [objects objectAtIndex:i],
path = [indexPaths objectAtIndex:i],
length = [path length];
path = [indexPaths objectAtIndex:i],
length = [path length];
if (length === 1)
{
[_contentObject insertObject:object atIndex:[path indexAtPosition:0]];
{[_contentObject insertObject:object atIndex:[path indexAtPosition:0]];
}
else
{
var parentPath = [path indexPathByRemovingLastIndex],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
if (parentNode)
{
var parentObj = [parentNode representedObject],
childIndex = [path indexAtPosition:length - 1];
childIndex = [path indexAtPosition:length - 1];
var children = [parentObj valueForKeyPath:_childrenKeyPath];
if (!children)
@@ -582,11 +568,9 @@
_disableSetContent = NO;
[self _rearrangeObjects];
if ([self selectsInsertedObjects])
[self setSelectionIndexPaths:indexPaths];
if ([self selectsInsertedObjects])[self setSelectionIndexPaths:indexPaths];
[self didChangeValueForKey:@"content"];
[self _selectionDidChange];
}
- (void)remove:(id)sender
@@ -601,17 +585,16 @@
- (void)removeObjectsAtArrangedObjectIndexPaths:(CPArray)indexPaths
{
[self _selectionWillChange];
[self willChangeValueForKey:@"content"];
_disableSetContent = YES;
var sortedPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)],
count = [sortedPaths count];
count = [sortedPaths count];
for (var i = count - 1; i >= 0; i--)
{
var path = [sortedPaths objectAtIndex:i],
length = [path length];
length = [path length];
if (length === 1)
{
@@ -620,15 +603,15 @@
else
{
var parentPath = [path indexPathByRemovingLastIndex],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
if (parentNode)
{
var parentObj = [parentNode representedObject],
childIndex = [path indexAtPosition:length - 1],
mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
childIndex = [path indexAtPosition:length - 1],
mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
if (mutableChildren && childIndex < [mutableChildren count])
if (mutableChildren && childIndex <[mutableChildren count])
[mutableChildren removeObjectAtIndex:childIndex];
}
}
@@ -641,7 +624,6 @@
_disableSetContent = NO;
[self _rearrangeObjects];
[self didChangeValueForKey:@"content"];
[self _selectionDidChange];
}
- (void)moveNode:(CPTreeNode)node toIndexPath:(CPIndexPath)indexPath
@@ -650,19 +632,18 @@
}
- (void)moveNodes:(CPArray)nodes toIndexPath:(CPIndexPath)startingIndexPath
{
[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."];
{[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."];
}
@end
var CPTreeControllerAvoidsEmptySelection = @"CPTreeControllerAvoidsEmptySelection",
CPTreeControllerPreservesSelection = @"CPTreeControllerPreservesSelection",
CPTreeControllerSelectsInsertedObjects = @"CPTreeControllerSelectsInsertedObjects",
CPTreeControllerAlwaysUsesMultipleValuesMarker = @"CPTreeControllerAlwaysUsesMultipleValuesMarker",
CPTreeControllerChildrenKeyPath = @"CPTreeControllerChildrenKeyPath",
CPTreeControllerCountKeyPath = @"CPTreeControllerCountKeyPath",
CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath";
CPTreeControllerPreservesSelection = @"CPTreeControllerPreservesSelection",
CPTreeControllerSelectsInsertedObjects = @"CPTreeControllerSelectsInsertedObjects",
CPTreeControllerAlwaysUsesMultipleValuesMarker = @"CPTreeControllerAlwaysUsesMultipleValuesMarker",
CPTreeControllerChildrenKeyPath = @"CPTreeControllerChildrenKeyPath",
CPTreeControllerCountKeyPath = @"CPTreeControllerCountKeyPath",
CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath";
@implementation CPTreeController (CPCoding)
+64 -299
View File
@@ -17,41 +17,17 @@
*
* 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
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
@import <Foundation/CPIndexPath.j>
@import <Foundation/CPArray.j>
/*
* CPTreeNode implements the NSTreeNode contract.
* The _childNodes array contains only CPTreeNode instances.
* The representedObject property contains the application data.
* The _parentNode and _childNodes properties maintain a strict bidirectional relationship.
* The KVC mutation methods are the public mechanism to change the tree structure.
*/
@implementation CPTreeNode : CPObject
{
/*
* KVO notifications on parentNode are never delivered: there is no
* setParentNode: for the swizzling machinery to intercept, so there is
* no selector to instrument, in any code path.
*
* KVO notifications on childNodes are reliable for
* insertObject:inChildNodesAtIndex:, including same-parent and
* cross-parent moves: all detach paths for that method route through
* the KVC accessors. replaceObjectInChildNodesAtIndex:withObject:
* still detaches a same-parent replacement node by mutating
* _childNodes directly, bypassing the KVC proxy for that step; an
* observer of that node's former parent's childNodes can miss that
* specific removal, or see it reported as the wrong kind of change.
* Do not rely on childNodes observation during a same-parent replace;
* rely only on the state after the call returns.
*/
id _representedObject @accessors(readonly, property=representedObject);
CPTreeNode _parentNode @accessors(readonly, property=parentNode);
id _representedObject @accessors(property=representedObject);
CPTreeNode _parentNode @accessors(property=parentNode);
CPMutableArray _childNodes;
}
@@ -73,107 +49,27 @@
return self;
}
/*
* Route plain init through the designated initializer.
* Without this override, [[CPTreeNode alloc] init] leaves _childNodes unset.
* The first mutation call then fails against an undefined array.
*/
- (id)init
{
return [self initWithRepresentedObject:nil];
}
/*
* Return YES if adding aTreeNode below self makes a cycle.
* This method walks the parent chain.
* The operation time is proportional to the tree depth.
*/
- (BOOL)_wouldCreateCycleWithNode:(CPTreeNode)aTreeNode
{
for (var node = self; node; node = node._parentNode)
{
if (node === aTreeNode)
return YES;
}
return NO;
}
/*
* Enforce the NSTreeNode abstraction boundary.
* All children must be CPTreeNode instances.
*/
- (void)_validateChildNode:(id)aTreeNode
{
if (![aTreeNode isKindOfClass:[CPTreeNode class]])
{
[CPException raise:CPInvalidArgumentException
reason:"CPTreeNode children must be CPTreeNode instances."];
}
}
/*
* Remove a child node by delegating to the public KVC accessor.
* Use this method for internal structural changes across a parent
* boundary, so an observer of this node's childNodes sees the removal.
*/
- (void)_removeChildNode:(CPTreeNode)aNode
{
var index = [_childNodes indexOfObjectIdenticalTo:aNode];
/*
* A caller reaches this method only when aNode.parentNode already equals
* self (see the two call sites below). If self._childNodes does not
* actually contain aNode at that point, the parent/child relationship
* is already broken. indexPath raises for this identical class of
* inconsistency; silently returning here would hide the same problem
* instead of surfacing it.
*/
if (index === CPNotFound)
{
[CPException raise:CPInternalInconsistencyException
reason:"CPTreeNode parent and child relationship is inconsistent."];
}
[self removeObjectFromChildNodesAtIndex:index];
}
- (CPIndexPath)indexPath
{
if (!_parentNode)
return [CPIndexPath indexPathWithIndexes:[]];
var indexes = [],
node = self;
while (node._parentNode)
// If we have a parent, calculate path based on parent's path + our index
if (_parentNode)
{
var parent = node._parentNode,
index = [parent._childNodes indexOfObjectIdenticalTo:node];
if (index === CPNotFound)
{
[CPException raise:CPInternalInconsistencyException
reason:"CPTreeNode parent and child relationship is inconsistent."];
}
[indexes addObject:index];
node = parent;
// Search the parent's child nodes, not our own!
var index = [[_parentNode childNodes] indexOfObjectIdenticalTo:self];
// If the parent is the root (and technically has no path itself in some implementations),
// we might get nil. Handle that gracefully.
var parentPath = [_parentNode indexPath];
if (parentPath)
return [parentPath indexPathByAddingIndex:index];
return [CPIndexPath indexPathWithIndex:index];
}
/*
* indexes was collected leaf-to-root. Build a second array in
* root-to-leaf order by walking indexes backward. CPArray has no
* -reverse selector; count/objectAtIndex:/addObject: are the verified,
* already-used-elsewhere primitives.
*/
var orderedIndexes = [],
count = [indexes count];
while (count--)
[orderedIndexes addObject:[indexes objectAtIndex:count]];
return [CPIndexPath indexPathWithIndexes:orderedIndexes];
// If we are the root, we don't have an index path in the context of a tree controller usually,
// or we are [] (empty path). Returning nil is acceptable for the absolute root.
return nil;
}
- (BOOL)isLeaf
@@ -183,10 +79,7 @@
- (CPArray)childNodes
{
/*
* Return a copy.
* This prevents external changes that bypass the KVC methods.
*/
// Return a copy to prevent external modification without KVC
return [_childNodes copy];
}
@@ -195,190 +88,78 @@
return [self mutableArrayValueForKey:@"childNodes"];
}
/*
* KVC compliance methods.
* The mutableArrayValueForKey: method uses these names.
*/
// MARK: - KVC Compliance Methods
- (void)insertObject:(CPTreeNode)aTreeNode inChildNodesAtIndex:(CPInteger)anIndex
{
var count = [_childNodes count];
if (anIndex < 0 || anIndex > count)
// Optional: Auto-detach from old parent if strictly moving nodes
if ([aTreeNode isKindOfClass:[CPTreeNode class]] && aTreeNode._parentNode)
{
[CPException raise:CPRangeException
reason:"index (" + anIndex + ") beyond bounds (0 .. " + count + ") for insertObject:inChildNodesAtIndex:"];
[[aTreeNode._parentNode mutableChildNodes] removeObjectIdenticalTo:aTreeNode];
}
[self _validateChildNode:aTreeNode];
// Direct ivar access is allowed here since we are inside the class implementation
if ([aTreeNode isKindOfClass:[CPTreeNode class]])
aTreeNode._parentNode = self;
if ([self _wouldCreateCycleWithNode:aTreeNode])
{
[CPException raise:CPInvalidArgumentException
reason:"Inserting a CPTreeNode beneath itself or one of its descendants makes a cycle."];
}
/*
* Detach the node from its old parent first.
* The code validated the index before this change.
*/
if (aTreeNode._parentNode)
{
if (aTreeNode._parentNode === self)
{
var originalIndex = [_childNodes indexOfObjectIdenticalTo:aTreeNode];
/*
* Route the detach through the KVC accessor, not direct array
* mutation, so an observer of childNodes sees the removal.
*/
[self removeObjectFromChildNodesAtIndex:originalIndex];
/*
* No index adjustment here. anIndex is the target position in
* the final array, per the KVC to-many contract. The array
* above is already one element short from the removal, so
* inserting at anIndex against it lands the node correctly.
*/
}
else
{
/*
* Detach the node from its old parent.
* Use the internal method to bypass KVO overhead.
*/
[aTreeNode._parentNode _removeChildNode:aTreeNode];
}
}
aTreeNode._parentNode = self;
[_childNodes insertObject:aTreeNode atIndex:anIndex];
}
- (void)removeObjectFromChildNodesAtIndex:(CPInteger)anIndex
{
var node = [_childNodes objectAtIndex:anIndex];
if ([node isKindOfClass:[CPTreeNode class]])
node._parentNode = nil;
node._parentNode = nil;
[_childNodes removeObjectAtIndex:anIndex];
}
- (void)replaceObjectInChildNodesAtIndex:(CPInteger)anIndex withObject:(CPTreeNode)aTreeNode
- (void)replaceObjectFromChildNodesAtIndex:(CPInteger)anIndex withObject:(id)aTreeNode
{
var oldTreeNode = [_childNodes objectAtIndex:anIndex];
[self _validateChildNode:aTreeNode];
if (oldTreeNode === aTreeNode)
return;
if ([self _wouldCreateCycleWithNode:aTreeNode])
{
[CPException raise:CPInvalidArgumentException
reason:"Replacing a child with itself or one of its ancestors makes a cycle."];
}
/*
* If the replacement node is already a child of this parent, remove it first.
* The removal shifts the array elements.
* Adjust the target index before the replace operation.
* This matches the Cocoa KVC mutation semantics.
*/
var oldParent = aTreeNode._parentNode;
if (oldParent === self)
{
var replacementIndex = [_childNodes indexOfObjectIdenticalTo:aTreeNode];
/*
* aTreeNode.parentNode already equals self at this point. If
* self._childNodes does not actually contain aTreeNode, the
* parent/child relationship is already broken. indexPath raises
* for this identical class of inconsistency; proceeding here would
* silently tolerate the same problem instead of surfacing it.
*/
if (replacementIndex === CPNotFound)
{
[CPException raise:CPInternalInconsistencyException
reason:"CPTreeNode parent and child relationship is inconsistent."];
}
/*
* Bypass KVO for this internal structural adjustment.
*/
aTreeNode._parentNode = nil;
[_childNodes removeObjectAtIndex:replacementIndex];
/*
* Unlike insertObject:inChildNodesAtIndex:, anIndex here cannot be
* treated as a plain final-array position: replace requires an
* existing slot, it cannot append past the end. The removal above
* already took a slot out of the array ahead of the target
* whenever the replacement's original position was before it.
* Shift anIndex down by one in that case, to keep it pointing at
* the same physical slot the caller named.
*/
if (replacementIndex < anIndex)
--anIndex;
}
else if (oldParent)
{
/*
* Detach the node from its old parent.
* Use the internal method to bypass KVO overhead.
*/
[oldParent _removeChildNode:aTreeNode];
}
oldTreeNode._parentNode = nil;
aTreeNode._parentNode = self;
if ([oldTreeNode isKindOfClass:[CPTreeNode class]])
oldTreeNode._parentNode = nil;
if ([aTreeNode isKindOfClass:[CPTreeNode class]])
aTreeNode._parentNode = self;
[_childNodes replaceObjectAtIndex:anIndex withObject:aTreeNode];
}
// MARK: - Convenience Accessors
- (id)objectInChildNodesAtIndex:(CPInteger)anIndex
{
return [_childNodes objectAtIndex:anIndex];
}
- (CPInteger)countOfChildNodes
- (CPInteger)count
{
return [_childNodes count];
}
- (id)objectAtIndex:(CPInteger)anIndex
{
return [_childNodes objectAtIndex:anIndex];
}
// MARK: - Utility
- (void)sortWithSortDescriptors:(CPArray)sortDescriptors recursively:(BOOL)shouldSortRecursively
{
[_childNodes sortUsingDescriptors:sortDescriptors];
if (!shouldSortRecursively)
{
[_childNodes sortUsingDescriptors:sortDescriptors];
return;
}
/*
* Use an explicit stack, not recursion.
* The recursive form is not a tail call.
* The sibling loop continues after each child call returns.
* Only JavaScriptCore performs tail call optimization.
* The explicit stack prevents stack overflow on deep trees.
*/
var stack = [];
[stack addObject:self];
while ([stack count])
var count = [_childNodes count];
while (count--)
{
var node = [stack lastObject];
[stack removeLastObject];
[node._childNodes sortUsingDescriptors:sortDescriptors];
var count = [node._childNodes count];
while (count--)
{
[stack addObject:[node._childNodes objectAtIndex:count]];
}
var child = [_childNodes objectAtIndex:count];
if ([child respondsToSelector:@selector(sortWithSortDescriptors:recursively:)])
[child sortWithSortDescriptors:sortDescriptors recursively:YES];
}
}
@@ -388,17 +169,17 @@
return self;
var node = self,
length = [indexPath length];
length = [indexPath length];
for (var i = 0; i < length; i++)
{
var index = [indexPath indexAtPosition:i],
count = [node countOfChildNodes];
if (index < 0 || index >= count)
count = [node count];
if (index >= count || index < 0)
return nil;
node = [node objectInChildNodesAtIndex:index];
node = [node objectAtIndex:index];
}
return node;
@@ -406,6 +187,7 @@
@end
// Coding implementation remains correct
var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
CPTreeNodeParentNodeKey = @"CPTreeNodeParentNodeKey",
CPTreeNodeChildNodesKey = @"CPTreeNodeChildNodesKey";
@@ -421,27 +203,10 @@ var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
_representedObject = [aCoder decodeObjectForKey:CPTreeNodeRepresentedObjectKey];
_parentNode = [aCoder decodeObjectForKey:CPTreeNodeParentNodeKey];
_childNodes = [aCoder decodeObjectForKey:CPTreeNodeChildNodesKey];
// Safety check to ensure decoding gave us a CPArray
if (!_childNodes)
_childNodes = [];
if (![_childNodes isKindOfClass:[CPMutableArray class]])
_childNodes = [_childNodes mutableCopy];
/*
* The child array is the authoritative structure.
* Re-establish the parent links.
* This makes the decoded tree match the tree built by the mutation methods.
*/
var count = [_childNodes count];
while (count--)
{
var child = [_childNodes objectAtIndex:count];
[self _validateChildNode:child];
child._parentNode = self;
}
_childNodes = [[CPMutableArray alloc] init];
}
return self;
+2 -19
View File
@@ -1474,24 +1474,6 @@ var CPViewHighDPIDrawingEnabled = YES;
origin.y *= size.height / frameSize.height;
}
var newScaleSize;
if (size && size.width !== 0 && size.height !== 0 && frameSize)
newScaleSize = CGSizeMake(frameSize.width / size.width, frameSize.height / size.height);
else
newScaleSize = CGSizeMake(1.0, 1.0);
// Only update and propagate if the scale factor has actually changed
if (!CGSizeEqualToSize(_scaleSize, newScaleSize))
{
[self willChangeValueForKey:@"scaleSize"];
_scaleSize = newScaleSize;
_isScaled = (_scaleSize.width !== 1.0 || _scaleSize.height !== 1.0);
[self didChangeValueForKey:@"scaleSize"];
[self _scaleSizeUnitSquareToSize:CGSizeMake(1.0, 1.0)];
}
if (_layer)
[_layer _owningViewBoundsChanged];
@@ -1505,6 +1487,7 @@ var CPViewHighDPIDrawingEnabled = YES;
[self _updateTrackingAreasWithRecursion:YES];
}
/*!
Notifies subviews that the superview changed size.
@param aSize the size of the old superview
@@ -2623,7 +2606,7 @@ setBoundsOrigin:
*/
- (void)_scaleSizeUnitSquareToSize:(CGSize)aSize
{
_hierarchyScaleSize = _superview ? CGSizeMakeCopy([_superview _hierarchyScaleSize]) : CGSizeMake(1.0, 1.0);
_hierarchyScaleSize = CGSizeMakeCopy([_superview _hierarchyScaleSize]);
if (_isScaled)
{
+2 -2
View File
@@ -481,9 +481,9 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
_DOMWindow.addEventListener("blur", onBlurEventCallback, NO);
_DOMWindow.addEventListener("focus", onFocusEventCallback, NO);
_DOMWindow.addEventListener("pagehide", function()
_DOMWindow.addEventListener("unload", function()
{
_DOMWindow.removeEventListener("pagehide", arguments.callee, NO);
_DOMWindow.removeEventListener("unload", arguments.callee, NO);
[self blurEvent:nil];
[self _notifyPlatformWindowWillClose];
+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
+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

@@ -6,19 +6,19 @@
* Copyright 2012, Your Company All rights reserved.
*/
var ENV = process.env,
cp = require("child_process"),
fs = require("fs"),
path = require("path"),
var ENV = require("system").env,
FILE = require("file"),
JAKE = require("jake"),
task = JAKE.task,
FileList = JAKE.FileList,
app = CAPPUCCINO.Jake.applicationtask.app,
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug";
app = require("cappuccino/jake").app,
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug",
OS = require("os");
app ("ArrayControllerInitialSelectionTest", function(task)
{
task.setBuildIntermediatesPath(path.join("Build", "ArrayControllerInitialSelectionTest.build", configuration));
task.setBuildPath(path.join("Build", configuration));
task.setBuildIntermediatesPath(FILE.join("Build", "ArrayControllerInitialSelectionTest.build", configuration));
task.setBuildPath(FILE.join("Build", configuration));
task.setProductName("ArrayControllerInitialSelectionTest");
task.setIdentifier("com.yourcompany.ArrayControllerInitialSelectionTest");
@@ -26,7 +26,7 @@ app ("ArrayControllerInitialSelectionTest", function(task)
task.setAuthor("Your Company");
task.setEmail("feedback @nospam@ yourcompany.com");
task.setSummary("ArrayControllerInitialSelectionTest");
task.setSources((new FileList("**/*.j")).exclude(path.join("Build", "**")));
task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**")));
task.setResources(new FileList("Resources/**"));
task.setIndexFilePath("index.html");
task.setInfoPlistPath("Info.plist");
@@ -58,36 +58,36 @@ task ("release", function()
task ("run", ["debug"], function()
{
cp.execSync(["open", path.join("Build", "Debug", "ArrayControllerInitialSelectionTest", "index.html")].join(" "), { stdio: "inherit" });
OS.system(["open", FILE.join("Build", "Debug", "ArrayControllerInitialSelectionTest", "index.html")]);
});
task ("run-release", ["release"], function()
{
cp.execSync(["open", path.join("Build", "Release", "ArrayControllerInitialSelectionTest", "index.html")].join(" "), { stdio: "inherit" });
OS.system(["open", FILE.join("Build", "Release", "ArrayControllerInitialSelectionTest", "index.html")]);
});
task ("deploy", ["release"], function()
{
fs.mkdirSync(path.join("Build", "Deployment", "ArrayControllerInitialSelectionTest"), { recursive: true });
cp.execSync(["press", "-f", path.join("Build", "Release", "ArrayControllerInitialSelectionTest"), path.join("Build", "Deployment", "ArrayControllerInitialSelectionTest")].join(" "), { stdio: "inherit" });
FILE.mkdirs(FILE.join("Build", "Deployment", "ArrayControllerInitialSelectionTest"));
OS.system(["press", "-f", FILE.join("Build", "Release", "ArrayControllerInitialSelectionTest"), FILE.join("Build", "Deployment", "ArrayControllerInitialSelectionTest")]);
printResults("Deployment")
});
task ("desktop", ["release"], function()
{
fs.mkdirSync(path.join("Build", "Desktop", "ArrayControllerInitialSelectionTest"), { recursive: true });
require("@objj/nativehost").buildNativeHost(path.join("Build", "Release", "ArrayControllerInitialSelectionTest"), path.join("Build", "Desktop", "ArrayControllerInitialSelectionTest", "ArrayControllerInitialSelectionTest.app"));
FILE.mkdirs(FILE.join("Build", "Desktop", "ArrayControllerInitialSelectionTest"));
require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "ArrayControllerInitialSelectionTest"), FILE.join("Build", "Desktop", "ArrayControllerInitialSelectionTest", "ArrayControllerInitialSelectionTest.app"));
printResults("Desktop")
});
task ("run-desktop", ["desktop"], function()
{
cp.execSync([path.join("Build", "Desktop", "ArrayControllerInitialSelectionTest", "ArrayControllerInitialSelectionTest.app", "Contents", "MacOS", "NativeHost"), "-i"].join(" "), { stdio: "inherit" });
OS.system([FILE.join("Build", "Desktop", "ArrayControllerInitialSelectionTest", "ArrayControllerInitialSelectionTest.app", "Contents", "MacOS", "NativeHost"), "-i"]);
});
function printResults(configuration)
{
console.log("----------------------------");
console.log(configuration+" app built at path: "+path.join("Build", configuration, "ArrayControllerInitialSelectionTest"));
console.log("----------------------------");
print("----------------------------");
print(configuration+" app built at path: "+FILE.join("Build", configuration, "ArrayControllerInitialSelectionTest"));
print("----------------------------");
}
@@ -6,19 +6,19 @@
* Copyright 2012, Your Company All rights reserved.
*/
var ENV = process.env,
cp = require("child_process"),
fs = require("fs"),
path = require("path"),
var ENV = require("system").env,
FILE = require("file"),
JAKE = require("jake"),
task = JAKE.task,
FileList = JAKE.FileList,
app = CAPPUCCINO.Jake.applicationtask.app,
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug";
app = require("cappuccino/jake").app,
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug",
OS = require("os");
app ("ArrayControllerRemovingFirstTest", function(task)
{
task.setBuildIntermediatesPath(path.join("Build", "ArrayControllerRemovingFirstTest.build", configuration));
task.setBuildPath(path.join("Build", configuration));
task.setBuildIntermediatesPath(FILE.join("Build", "ArrayControllerRemovingFirstTest.build", configuration));
task.setBuildPath(FILE.join("Build", configuration));
task.setProductName("ArrayControllerRemovingFirstTest");
task.setIdentifier("com.yourcompany.ArrayControllerRemovingFirstTest");
@@ -26,7 +26,7 @@ app ("ArrayControllerRemovingFirstTest", function(task)
task.setAuthor("Your Company");
task.setEmail("feedback @nospam@ yourcompany.com");
task.setSummary("ArrayControllerRemovingFirstTest");
task.setSources((new FileList("**/*.j")).exclude(path.join("Build", "**")));
task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**")));
task.setResources(new FileList("Resources/**"));
task.setIndexFilePath("index.html");
task.setInfoPlistPath("Info.plist");
@@ -58,36 +58,36 @@ task ("release", function()
task ("run", ["debug"], function()
{
cp.execSync(["open", path.join("Build", "Debug", "ArrayControllerRemovingFirstTest", "index.html")].join(" "), { stdio: "inherit" });
OS.system(["open", FILE.join("Build", "Debug", "ArrayControllerRemovingFirstTest", "index.html")]);
});
task ("run-release", ["release"], function()
{
cp.execSync(["open", path.join("Build", "Release", "ArrayControllerRemovingFirstTest", "index.html")].join(" "), { stdio: "inherit" });
OS.system(["open", FILE.join("Build", "Release", "ArrayControllerRemovingFirstTest", "index.html")]);
});
task ("deploy", ["release"], function()
{
fs.mkdirSync(path.join("Build", "Deployment", "ArrayControllerRemovingFirstTest"), { recursive: true });
cp.execSync(["press", "-f", path.join("Build", "Release", "ArrayControllerRemovingFirstTest"), path.join("Build", "Deployment", "ArrayControllerRemovingFirstTest")].join(" "), { stdio: "inherit" });
FILE.mkdirs(FILE.join("Build", "Deployment", "ArrayControllerRemovingFirstTest"));
OS.system(["press", "-f", FILE.join("Build", "Release", "ArrayControllerRemovingFirstTest"), FILE.join("Build", "Deployment", "ArrayControllerRemovingFirstTest")]);
printResults("Deployment")
});
task ("desktop", ["release"], function()
{
fs.mkdirSync(path.join("Build", "Desktop", "ArrayControllerRemovingFirstTest"), { recursive: true });
require("@objj/nativehost").buildNativeHost(path.join("Build", "Release", "ArrayControllerRemovingFirstTest"), path.join("Build", "Desktop", "ArrayControllerRemovingFirstTest", "ArrayControllerRemovingFirstTest.app"));
FILE.mkdirs(FILE.join("Build", "Desktop", "ArrayControllerRemovingFirstTest"));
require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "ArrayControllerRemovingFirstTest"), FILE.join("Build", "Desktop", "ArrayControllerRemovingFirstTest", "ArrayControllerRemovingFirstTest.app"));
printResults("Desktop")
});
task ("run-desktop", ["desktop"], function()
{
cp.execSync([path.join("Build", "Desktop", "ArrayControllerRemovingFirstTest", "ArrayControllerRemovingFirstTest.app", "Contents", "MacOS", "NativeHost"), "-i"].join(" "), { stdio: "inherit" });
OS.system([FILE.join("Build", "Desktop", "ArrayControllerRemovingFirstTest", "ArrayControllerRemovingFirstTest.app", "Contents", "MacOS", "NativeHost"), "-i"]);
});
function printResults(configuration)
{
console.log("----------------------------");
console.log(configuration+" app built at path: "+path.join("Build", configuration, "ArrayControllerRemovingFirstTest"));
console.log("----------------------------");
print("----------------------------");
print(configuration+" app built at path: "+FILE.join("Build", configuration, "ArrayControllerRemovingFirstTest"));
print("----------------------------");
}
-94
View File
@@ -1,94 +0,0 @@
/*
* Jakefile
* ArrayControllerTest
*
* Created by Alexander Ljungberg on April 2, 2011.
* Copyright 2011, WireLoad All rights reserved.
*/
var ENV = process.env,
cp = require("child_process"),
fs = require("fs"),
path = require("path"),
task = JAKE.task,
FileList = JAKE.FileList,
app = CAPPUCCINO.Jake.applicationtask.app,
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug";
app ("ArrayControllerTest", function(task)
{
task.setBuildIntermediatesPath(path.join("Build", "ArrayControllerTest.build", configuration));
task.setBuildPath(path.join("Build", configuration));
task.setProductName("ArrayControllerTest");
task.setIdentifier("com.yourcompany.ArrayControllerTest");
task.setVersion("1.0");
task.setAuthor("WireLoad");
task.setEmail("feedback @nospam@ yourcompany.com");
task.setSummary("ArrayControllerTest");
task.setSources((new FileList("**/*.j")).exclude(path.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", ["ArrayControllerTest"], 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()
{
cp.execSync(["open", path.join("Build", "Debug", "ArrayControllerTest", "index.html")].join(" "), { stdio: "inherit" });
});
task ("run-release", ["release"], function()
{
cp.execSync(["open", path.join("Build", "Release", "ArrayControllerTest", "index.html")].join(" "), { stdio: "inherit" });
});
task ("deploy", ["release"], function()
{
fs.mkdirSync(path.join("Build", "Deployment", "ArrayControllerTest"), { recursive: true });
cp.execSync(["press", "-f", path.join("Build", "Release", "ArrayControllerTest"), path.join("Build", "Deployment", "ArrayControllerTest")].join(" "), { stdio: "inherit" });
printResults("Deployment")
});
task ("desktop", ["release"], function()
{
fs.mkdirSync(path.join("Build", "Desktop", "ArrayControllerTest"), { recursive: true });
require("@objj/nativehost").buildNativeHost(path.join("Build", "Release", "ArrayControllerTest"), path.join("Build", "Desktop", "ArrayControllerTest", "ArrayControllerTest.app"));
printResults("Desktop")
});
task ("run-desktop", ["desktop"], function()
{
cp.execSync([path.join("Build", "Desktop", "ArrayControllerTest", "ArrayControllerTest.app", "Contents", "MacOS", "NativeHost"), "-i"].join(" "), { stdio: "inherit" });
});
function printResults(configuration)
{
console.log("----------------------------");
console.log(configuration+" app built at path: "+path.join("Build", configuration, "ArrayControllerTest"));
console.log("----------------------------");
}
File diff suppressed because one or more lines are too long
@@ -1,161 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="25098" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment version="1050" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="25098"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="450" id="451"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<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="445" width="480" height="305"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1050"/>
<view key="contentView" id="372">
<rect key="frame" x="0.0" y="0.0" width="480" height="305"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="460">
<rect key="frame" x="15" y="150" width="445" height="135"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" id="oWr-jb-zqy">
<rect key="frame" x="1" y="1" width="443" height="133"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" columnSelection="YES" autosaveColumns="NO" headerView="464" id="463">
<rect key="frame" x="0.0" y="0.0" width="443" height="105"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="462">
<rect key="frame" x="1" y="119" width="223" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="461">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" wantsLayer="YES" id="464">
<rect key="frame" x="0.0" y="0.0" width="443" height="28"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="469">
<rect key="frame" x="9" y="110" width="40" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" bezelStyle="rounded" image="NSAddTemplate" imagePosition="only" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="470">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="add:" target="511" id="513"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="471">
<rect key="frame" x="49" y="110" width="41" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" bezelStyle="rounded" image="NSRemoveTemplate" imagePosition="only" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="472">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="remove:" target="511" id="514"/>
</connections>
</button>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="484">
<rect key="frame" x="12" y="77" width="155" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Selected Name:" id="485">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="488">
<rect key="frame" x="66" y="47" width="101" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Selected Price:" id="489">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="492">
<rect key="frame" x="17" y="20" width="150" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Sum of Selected Prices:" id="493">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="494">
<rect key="frame" x="174" y="20" width="292" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="495">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="499">
<rect key="frame" x="174" y="75" width="286" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="500">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="501">
<rect key="frame" x="174" y="45" width="286" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="502">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="509">
<rect key="frame" x="90" y="110" width="76" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Insert" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="510">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="insert:" target="511" id="515"/>
</connections>
</button>
</subviews>
</view>
<point key="canvasLocation" x="140" y="142"/>
</window>
<customObject id="450" customClass="AppController">
<connections>
<outlet property="arrayController" destination="511" id="512"/>
<outlet property="selectedNameField" destination="499" id="507"/>
<outlet property="selectedPriceField" destination="501" id="508"/>
<outlet property="tableView" destination="463" id="483"/>
<outlet property="theWindow" destination="371" id="459"/>
<outlet property="totalCountField" destination="494" id="498"/>
</connections>
</customObject>
<arrayController id="511"/>
</objects>
<resources>
<image name="NSAddTemplate" width="18" height="16"/>
<image name="NSRemoveTemplate" width="18" height="4"/>
</resources>
</document>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 455 B

After

Width:  |  Height:  |  Size: 455 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

+93
View File
@@ -0,0 +1,93 @@
/*
* Jakefile
* AttachedSheet2
*
* Created by Saikat Chakrabarti on March 16, 2010.
* Copyright 2010, gomockingbird.com 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 ("AttachedSheet2", function(task)
{
task.setBuildIntermediatesPath(FILE.join("Build", "AttachedSheet2.build", configuration));
task.setBuildPath(FILE.join("Build", configuration));
task.setProductName("AttachedSheet2");
task.setIdentifier("com.gomockingbird.AttachedSheet2");
task.setVersion("1.0");
task.setAuthor("Saikat Chakrabarti and Sheena Pakanati");
task.setEmail("contact @nospam@ gomockingbird.com");
task.setSummary("AttachedSheet2");
task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**")));
task.setResources(new FileList("Resources/*"));
task.setIndexFilePath("index.html");
task.setInfoPlistPath("Info.plist");
if (configuration === "Debug")
task.setCompilerFlags("-DDEBUG -g");
else
task.setCompilerFlags("-O");
});
function printResults(configuration)
{
print("----------------------------")
print(configuration+" app built at path: "+FILE.join("Build", configuration, "AttachedSheet2"));
print("----------------------------")
}
task ("default", ["AttachedSheet2"], 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", "AttachedSheet2", "index.html")]);
});
task ("run-release", ["release"], function()
{
OS.system(["open", FILE.join("Build", "Release", "AttachedSheet2", "index.html")]);
});
task ("deploy", ["release"], function()
{
FILE.mkdirs(FILE.join("Build", "Deployment", "AttachedSheet2"));
OS.system(["press", "-f", FILE.join("Build", "Release", "AttachedSheet2"), FILE.join("Build", "Deployment", "AttachedSheet2")]);
printResults("Deployment")
});
task ("press", ["release"], function()
{
FILE.mkdirs(FILE.join("Build", "Press", "AttachedSheet2"));
OS.system(["press", "-f", FILE.join("Build", "Release", "AttachedSheet2"), FILE.join("Build", "Press", "AttachedSheet2")]);
});
task ("flatten", ["press"], function()
{
FILE.mkdirs(FILE.join("Build", "Flatten", "AttachedSheet2"));
OS.system(["flatten", "-f", "--verbose", "--split", "3", "-c", "closure-compiler", FILE.join("Build", "Press", "AttachedSheet2"), FILE.join("Build", "Flatten", "AttachedSheet2")]);
});
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -14,6 +14,6 @@
function main(args, namedArgs)
{
CPLogRegister(CPLogDefault);
CPLogRegister(CPLogDefault);
CPApplicationMain(args, namedArgs);
}
@@ -0,0 +1,304 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
B210EF7E153B0A8D005D15EE /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B210EF7D153B0A8D005D15EE /* Cocoa.framework */; };
B210EF88153B0A8D005D15EE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B210EF86153B0A8D005D15EE /* InfoPlist.strings */; };
B210EF8A153B0A8D005D15EE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B210EF89153B0A8D005D15EE /* main.m */; };
B210EF8E153B0A8D005D15EE /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = B210EF8C153B0A8D005D15EE /* Credits.rtf */; };
B210EF91153B0A8D005D15EE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B210EF90153B0A8D005D15EE /* AppDelegate.m */; };
B2129637153B0B3000E15669 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2129636153B0B3000E15669 /* MainMenu.xib */; };
B212963B153B0B4900E15669 /* Window.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2129639153B0B4900E15669 /* Window.xib */; };
B212963C153B0E6A00E15669 /* SheetWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = B2129630153B0ADF00E15669 /* SheetWindow.m */; };
B212963D153B0E6C00E15669 /* SheetWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = B2129632153B0ADF00E15669 /* SheetWindowController.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
B210EF79153B0A8D005D15EE /* TestSheet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestSheet.app; sourceTree = BUILT_PRODUCTS_DIR; };
B210EF7D153B0A8D005D15EE /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
B210EF80153B0A8D005D15EE /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
B210EF81153B0A8D005D15EE /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
B210EF82153B0A8D005D15EE /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
B210EF85153B0A8D005D15EE /* TestSheet-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TestSheet-Info.plist"; sourceTree = "<group>"; };
B210EF87153B0A8D005D15EE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
B210EF89153B0A8D005D15EE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
B210EF8B153B0A8D005D15EE /* TestSheet-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TestSheet-Prefix.pch"; sourceTree = "<group>"; };
B210EF8D153B0A8D005D15EE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = "<group>"; };
B210EF8F153B0A8D005D15EE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
B210EF90153B0A8D005D15EE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
B212962F153B0ADF00E15669 /* SheetWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SheetWindow.h; sourceTree = "<group>"; };
B2129630153B0ADF00E15669 /* SheetWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SheetWindow.m; sourceTree = "<group>"; };
B2129631153B0ADF00E15669 /* SheetWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SheetWindowController.h; sourceTree = "<group>"; };
B2129632153B0ADF00E15669 /* SheetWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SheetWindowController.m; sourceTree = "<group>"; };
B2129636153B0B3000E15669 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../Resources/MainMenu.xib; sourceTree = "<group>"; };
B2129639153B0B4900E15669 /* Window.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Window.xib; path = ../Resources/Window.xib; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
B210EF76153B0A8D005D15EE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B210EF7E153B0A8D005D15EE /* Cocoa.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
B210EF6E153B0A8D005D15EE = {
isa = PBXGroup;
children = (
B2129639153B0B4900E15669 /* Window.xib */,
B2129636153B0B3000E15669 /* MainMenu.xib */,
B210EF83153B0A8D005D15EE /* TestSheet */,
B210EF7C153B0A8D005D15EE /* Frameworks */,
B210EF7A153B0A8D005D15EE /* Products */,
);
sourceTree = "<group>";
};
B210EF7A153B0A8D005D15EE /* Products */ = {
isa = PBXGroup;
children = (
B210EF79153B0A8D005D15EE /* TestSheet.app */,
);
name = Products;
sourceTree = "<group>";
};
B210EF7C153B0A8D005D15EE /* Frameworks */ = {
isa = PBXGroup;
children = (
B210EF7D153B0A8D005D15EE /* Cocoa.framework */,
B210EF7F153B0A8D005D15EE /* Other Frameworks */,
);
name = Frameworks;
sourceTree = "<group>";
};
B210EF7F153B0A8D005D15EE /* Other Frameworks */ = {
isa = PBXGroup;
children = (
B210EF80153B0A8D005D15EE /* AppKit.framework */,
B210EF81153B0A8D005D15EE /* CoreData.framework */,
B210EF82153B0A8D005D15EE /* Foundation.framework */,
);
name = "Other Frameworks";
sourceTree = "<group>";
};
B210EF83153B0A8D005D15EE /* TestSheet */ = {
isa = PBXGroup;
children = (
B210EF8F153B0A8D005D15EE /* AppDelegate.h */,
B210EF90153B0A8D005D15EE /* AppDelegate.m */,
B212962F153B0ADF00E15669 /* SheetWindow.h */,
B2129630153B0ADF00E15669 /* SheetWindow.m */,
B2129631153B0ADF00E15669 /* SheetWindowController.h */,
B2129632153B0ADF00E15669 /* SheetWindowController.m */,
B210EF84153B0A8D005D15EE /* Supporting Files */,
);
path = TestSheet;
sourceTree = "<group>";
};
B210EF84153B0A8D005D15EE /* Supporting Files */ = {
isa = PBXGroup;
children = (
B210EF85153B0A8D005D15EE /* TestSheet-Info.plist */,
B210EF86153B0A8D005D15EE /* InfoPlist.strings */,
B210EF89153B0A8D005D15EE /* main.m */,
B210EF8B153B0A8D005D15EE /* TestSheet-Prefix.pch */,
B210EF8C153B0A8D005D15EE /* Credits.rtf */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
B210EF78153B0A8D005D15EE /* TestSheet */ = {
isa = PBXNativeTarget;
buildConfigurationList = B210EF97153B0A8D005D15EE /* Build configuration list for PBXNativeTarget "TestSheet" */;
buildPhases = (
B210EF75153B0A8D005D15EE /* Sources */,
B210EF76153B0A8D005D15EE /* Frameworks */,
B210EF77153B0A8D005D15EE /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TestSheet;
productName = TestSheet;
productReference = B210EF79153B0A8D005D15EE /* TestSheet.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
B210EF70153B0A8D005D15EE /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0430;
};
buildConfigurationList = B210EF73153B0A8D005D15EE /* Build configuration list for PBXProject "TestSheet" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = B210EF6E153B0A8D005D15EE;
productRefGroup = B210EF7A153B0A8D005D15EE /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
B210EF78153B0A8D005D15EE /* TestSheet */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
B210EF77153B0A8D005D15EE /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B210EF88153B0A8D005D15EE /* InfoPlist.strings in Resources */,
B2129637153B0B3000E15669 /* MainMenu.xib in Resources */,
B212963B153B0B4900E15669 /* Window.xib in Resources */,
B210EF8E153B0A8D005D15EE /* Credits.rtf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
B210EF75153B0A8D005D15EE /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B210EF8A153B0A8D005D15EE /* main.m in Sources */,
B210EF91153B0A8D005D15EE /* AppDelegate.m in Sources */,
B212963D153B0E6C00E15669 /* SheetWindowController.m in Sources */,
B212963C153B0E6A00E15669 /* SheetWindow.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
B210EF86153B0A8D005D15EE /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
B210EF87153B0A8D005D15EE /* en */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
B210EF8C153B0A8D005D15EE /* Credits.rtf */ = {
isa = PBXVariantGroup;
children = (
B210EF8D153B0A8D005D15EE /* en */,
);
name = Credits.rtf;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
B210EF95153B0A8D005D15EE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_ENABLE_OBJC_ARC = YES;
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.7;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
B210EF96153B0A8D005D15EE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_ENABLE_OBJC_ARC = YES;
COPY_PHASE_STRIP = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.7;
SDKROOT = macosx;
};
name = Release;
};
B210EF98153B0A8D005D15EE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "TestSheet/TestSheet-Prefix.pch";
INFOPLIST_FILE = "TestSheet/TestSheet-Info.plist";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
name = Debug;
};
B210EF99153B0A8D005D15EE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "TestSheet/TestSheet-Prefix.pch";
INFOPLIST_FILE = "TestSheet/TestSheet-Info.plist";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
B210EF73153B0A8D005D15EE /* Build configuration list for PBXProject "TestSheet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B210EF95153B0A8D005D15EE /* Debug */,
B210EF96153B0A8D005D15EE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B210EF97153B0A8D005D15EE /* Build configuration list for PBXNativeTarget "TestSheet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B210EF98153B0A8D005D15EE /* Debug */,
B210EF99153B0A8D005D15EE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = B210EF70153B0A8D005D15EE /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:TestSheet.xcodeproj">
</FileRef>
</Workspace>
-93
View File
@@ -1,93 +0,0 @@
/*
* Jakefile
* AttachedSheet2Test
*
* Created by Saikat Chakrabarti on March 16, 2010.
* Copyright 2010, gomockingbird.com All rights reserved.
*/
var ENV = process.env,
cp = require("child_process"),
fs = require("fs"),
path = require("path"),
task = JAKE.task,
FileList = JAKE.FileList,
app = CAPPUCCINO.Jake.applicationtask.app,
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug";
app ("AttachedSheet2Test", function(task)
{
task.setBuildIntermediatesPath(path.join("Build", "AttachedSheet2Test.build", configuration));
task.setBuildPath(path.join("Build", configuration));
task.setProductName("AttachedSheet2Test");
task.setIdentifier("com.gomockingbird.AttachedSheet2Test");
task.setVersion("1.0");
task.setAuthor("Saikat Chakrabarti and Sheena Pakanati");
task.setEmail("contact @nospam@ gomockingbird.com");
task.setSummary("AttachedSheet2Test");
task.setSources((new FileList("**/*.j")).exclude(path.join("Build", "**")));
task.setResources(new FileList("Resources/*"));
task.setIndexFilePath("index.html");
task.setInfoPlistPath("Info.plist");
if (configuration === "Debug")
task.setCompilerFlags("-DDEBUG -g");
else
task.setCompilerFlags("-O");
});
function printResults(configuration)
{
console.log("----------------------------")
console.log(configuration+" app built at path: "+path.join("Build", configuration, "AttachedSheet2Test"));
console.log("----------------------------")
}
task ("default", ["AttachedSheet2Test"], 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()
{
cp.execSync(["open", path.join("Build", "Debug", "AttachedSheet2Test", "index.html")].join(" "), { stdio: "inherit" });
});
task ("run-release", ["release"], function()
{
cp.execSync(["open", path.join("Build", "Release", "AttachedSheet2Test", "index.html")].join(" "), { stdio: "inherit" });
});
task ("deploy", ["release"], function()
{
fs.mkdirSync(path.join("Build", "Deployment", "AttachedSheet2Test"), { recursive: true });
cp.execSync(["press", "-f", path.join("Build", "Release", "AttachedSheet2Test"), path.join("Build", "Deployment", "AttachedSheet2Test")].join(" "), { stdio: "inherit" });
printResults("Deployment")
});
task ("press", ["release"], function()
{
fs.mkdirSync(path.join("Build", "Press", "AttachedSheet2Test"), { recursive: true });
cp.execSync(["press", "-f", path.join("Build", "Release", "AttachedSheet2Test"), path.join("Build", "Press", "AttachedSheet2Test")].join(" "), { stdio: "inherit" });
});
task ("flatten", ["press"], function()
{
fs.mkdirSync(path.join("Build", "Flatten", "AttachedSheet2Test"), { recursive: true });
cp.execSync(["flatten", "-f", "--verbose", "--split", "3", "-c", "closure-compiler", path.join("Build", "Press", "AttachedSheet2Test"), path.join("Build", "Flatten", "AttachedSheet2Test")].join(" "), { stdio: "inherit" });
});
File diff suppressed because one or more lines are too long
@@ -1,489 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="25098" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment version="1050" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="25098"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="450" id="451"/>
</connections>
</customObject>
<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="NewApplication" id="56" userLabel="AttachedSheet2">
<menu key="submenu" title="NewApplication" systemMenu="apple" id="57">
<items>
<menuItem title="About AttachedSheet2" id="58">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-2" id="142"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="236">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Preferences…" keyEquivalent="," id="129" userLabel="121"/>
<menuItem isSeparatorItem="YES" id="143">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Services" id="131">
<menu key="submenu" title="Services" systemMenu="services" id="130"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="144">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Hide AttachedSheet2" keyEquivalent="h" id="134">
<connections>
<action selector="hide:" target="-1" id="367"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="145">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="368"/>
</connections>
</menuItem>
<menuItem title="Show All" id="150">
<connections>
<action selector="unhideAllApplications:" target="-1" id="370"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="149">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Quit AttachedSheet2" keyEquivalent="q" id="136" userLabel="1111">
<connections>
<action selector="terminate:" target="-3" id="449"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="83">
<menu key="submenu" title="File" id="81">
<items>
<menuItem title="New Window" keyEquivalent="n" id="82" userLabel="9">
<connections>
<action selector="newDocument:" target="-1" id="498"/>
</connections>
</menuItem>
<menuItem title="New Modal Window" keyEquivalent="N" id="488" userLabel="9">
<connections>
<action selector="newModalWindow:" target="-1" id="494"/>
</connections>
</menuItem>
<menuItem title="New Sheet" keyEquivalent="s" id="490" userLabel="9">
<connections>
<action selector="newSheet:" target="-1" id="495"/>
</connections>
</menuItem>
<menuItem title="New Modal Sheet" keyEquivalent="S" id="492" userLabel="9">
<connections>
<action selector="newModalSheet:" target="-1" id="497"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="79" userLabel="7">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Close" keyEquivalent="w" id="73" userLabel="1">
<connections>
<action selector="performClose:" target="-1" id="193"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="74" userLabel="2">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Page Setup..." keyEquivalent="P" id="77" userLabel="5">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<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"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="217">
<menu key="submenu" title="Edit" id="205">
<items>
<menuItem title="Undo" keyEquivalent="z" id="207">
<connections>
<action selector="undo:" target="-1" id="223"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="215">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="redo:" target="-1" id="231"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="206">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Cut" keyEquivalent="x" id="199">
<connections>
<action selector="cut:" target="-1" id="228"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="197">
<connections>
<action selector="copy:" target="-1" id="224"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="203">
<connections>
<action selector="paste:" target="-1" id="226"/>
</connections>
</menuItem>
<menuItem title="Delete" id="202">
<connections>
<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"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="214">
<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"/>
</connections>
</menuItem>
<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"/>
</menuItem>
<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"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="216">
<menu key="submenu" title="Spelling and Grammar" id="200">
<items>
<menuItem title="Show Spelling…" keyEquivalent=":" id="204">
<connections>
<action selector="showGuessPanel:" target="-1" id="230"/>
</connections>
</menuItem>
<menuItem title="Check Spelling" keyEquivalent=";" id="201">
<connections>
<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"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="346">
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="347"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="348">
<menu key="submenu" title="Substitutions" id="349">
<items>
<menuItem title="Smart Copy/Paste" tag="1" keyEquivalent="f" id="350">
<connections>
<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"/>
</connections>
</menuItem>
<menuItem title="Smart Links" tag="3" keyEquivalent="G" id="354">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="357"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="211">
<menu key="submenu" title="Speech" id="212">
<items>
<menuItem title="Start Speaking" id="196">
<connections>
<action selector="startSpeaking:" target="-1" id="233"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="195">
<connections>
<action selector="stopSpeaking:" target="-1" id="227"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="375">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="376">
<items>
<menuItem title="Font" id="377">
<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="Underline" keyEquivalent="u" id="392">
<connections>
<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 title="Kern" id="397">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="415">
<items>
<menuItem title="Use Default" id="416">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="-1" id="438"/>
</connections>
</menuItem>
<menuItem title="Use None" id="417">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="-1" id="441"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="418">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="-1" id="431"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="419">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="-1" id="435"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligature" id="398">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligature" id="411">
<items>
<menuItem title="Use Default" id="412">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="-1" id="439"/>
</connections>
</menuItem>
<menuItem title="Use None" id="413">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="-1" id="440"/>
</connections>
</menuItem>
<menuItem title="Use All" id="414">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="-1" id="434"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="399">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="405">
<items>
<menuItem title="Use Default" id="406">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="-1" id="437"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="407">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="-1" id="430"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="408">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="-1" id="429"/>
</connections>
</menuItem>
<menuItem title="Raise" id="409">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="-1" id="426"/>
</connections>
</menuItem>
<menuItem title="Lower" id="410">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="-1" id="427"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="400"/>
<menuItem title="Show Colors" keyEquivalent="C" id="401">
<connections>
<action selector="orderFrontColorPanel:" target="-1" id="433"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="402"/>
<menuItem title="Copy Style" keyEquivalent="c" id="403">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="-1" id="428"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="404">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="-1" id="436"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="378">
<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"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="381">
<connections>
<action selector="alignCenter:" target="-1" id="445"/>
</connections>
</menuItem>
<menuItem title="Justify" id="382">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="-1" id="443"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="383">
<connections>
<action selector="alignRight:" target="-1" id="447"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="384"/>
<menuItem title="Show Ruler" id="385">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="-1" id="446"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="386">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="-1" id="444"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="387">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="-1" id="448"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="295">
<menu key="submenu" title="View" id="296">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="297">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="-1" id="366"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="298">
<connections>
<action selector="runToolbarCustomizationPalette:" target="-1" id="365"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="19">
<menu key="submenu" title="Window" systemMenu="window" id="24">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="23">
<connections>
<action selector="performMiniaturize:" target="-1" id="37"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="239">
<connections>
<action selector="performZoom:" target="-1" id="240"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="92">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Bring All to Front" id="5">
<connections>
<action selector="arrangeInFront:" target="-1" id="39"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="103" userLabel="1">
<menu key="submenu" title="Help" id="106" userLabel="2">
<items>
<menuItem title="NewApplication Help" keyEquivalent="?" id="111">
<connections>
<action selector="showHelp:" target="-1" id="360"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="140" y="293"/>
</menu>
<customObject id="450" customClass="AppController"/>
</objects>
</document>
File diff suppressed because one or more lines are too long

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