diff --git a/AppKit/CPCollectionView.j b/AppKit/CPCollectionView.j index 61e68082d..a48c11c39 100644 --- a/AppKit/CPCollectionView.j +++ b/AppKit/CPCollectionView.j @@ -589,9 +589,7 @@ _mouseDownEvent = anEvent; var location = [self convertPoint:[anEvent locationInWindow] fromView:nil], - row = FLOOR(location.y / (_itemSize.height + _verticalMargin)), - column = FLOOR(location.x / (_itemSize.width + _horizontalMargin)), - index = row * _numberOfColumns + column; + index = [self _indexAtPoint:location]; if (index >= 0 && index < _items.length) { @@ -727,6 +725,28 @@ return _delegate; } +/*! + @ignore +*/ +- (CPMenu)menuForEvent:(CPEvent)theEvent +{ + if (![[self delegate] respondsToSelector:@selector(collectionView:menuForItemAtIndex:)]) + return [super menuForEvent:theEvent]; + + var location = [self convertPoint:[theEvent locationInWindow] fromView:nil], + index = [self _indexAtPoint:location]; + + return [_delegate collectionView:self menuForItemAtIndex:index]; +} + +- (int)_indexAtPoint:(CGPoint)thePoint +{ + var row = FLOOR(thePoint.y / (_itemSize.height + _verticalMargin)), + column = FLOOR(thePoint.x / (_itemSize.width + _horizontalMargin)); + + return row * _numberOfColumns + column; +} + - (CPCollectionViewItem)itemAtIndex:(unsigned)anIndex { return [_items objectAtIndex:anIndex]; diff --git a/AppKit/CPDragServer.j b/AppKit/CPDragServer.j index 9d3344b09..bc67ee490 100644 --- a/AppKit/CPDragServer.j +++ b/AppKit/CPDragServer.j @@ -270,10 +270,10 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0, { if ([scrollView hasVerticalScroller]) { - if (eventLocation.y < CGRectGetMinY(insetBounds)) - deltaY = CGRectGetMinY(insetBounds) - eventLocation.y; - else if (eventLocation.y > CGRectGetMaxY(insetBounds)) - deltaY = CGRectGetMaxY(insetBounds) - eventLocation.y; + if (eventLocation.y < _CGRectGetMinY(insetBounds)) + deltaY = _CGRectGetMinY(insetBounds) - eventLocation.y; + else if (eventLocation.y > _CGRectGetMaxY(insetBounds)) + deltaY = _CGRectGetMaxY(insetBounds) - eventLocation.y; if (deltaY < -insetBounds.size.height) deltaY = -insetBounds.size.height; if (deltaY > insetBounds.size.height) @@ -282,17 +282,21 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0, if ([scrollView hasHorizontalScroller]) { - if (eventLocation.x < CGRectGetMinX(insetBounds)) - deltaX = CGRectGetMinX(insetBounds) - eventLocation.x; - else if (eventLocation.x > CGRectGetMaxX(insetBounds)) - deltaX = CGRectGetMaxX(insetBounds) - eventLocation.x; + if (eventLocation.x < _CGRectGetMinX(insetBounds)) + deltaX = _CGRectGetMinX(insetBounds) - eventLocation.x; + else if (eventLocation.x > _CGRectGetMaxX(insetBounds)) + deltaX = _CGRectGetMaxX(insetBounds) - eventLocation.x; if (deltaX < -insetBounds.size.width) deltaX = -insetBounds.size.width; if (deltaX > insetBounds.size.width) deltaX = insetBounds.size.width; } - [contentView scrollToPoint:CGPointMake(bounds.origin.x - deltaX, bounds.origin.y - deltaY)]; + var scrollPoint = _CGPointMake(bounds.origin.x - deltaX, bounds.origin.y - deltaY); + + [contentView scrollToPoint:scrollPoint]; + [[scrollView _headerView] scrollPoint:scrollPoint]; + } } } diff --git a/AppKit/CPMenu/CPMenu.j b/AppKit/CPMenu/CPMenu.j index 059c918b7..db5aa8e61 100644 --- a/AppKit/CPMenu/CPMenu.j +++ b/AppKit/CPMenu/CPMenu.j @@ -536,7 +536,6 @@ var _CPMenuBarVisible = NO, */ - (void)submenuAction:(id)aSender { - } /*! @@ -584,11 +583,34 @@ var _CPMenuBarVisible = NO, } /*! - Not implemented. + Enables or disables the receiver’s menu items. + If the target does not implement the menu item's action method the item is disabled. + If the target responsds to selector validateMenuItem: or validateUserInterfaceItem: (in that order) the return value is used. */ - (void)update { + if (![self autoenablesItems]) + return; + var items = [self itemArray]; + for (var i = 0; i < [items count]; i++) + { + var item = [items objectAtIndex:i]; + + if ([item hasSubmenu]) + continue; + + var validator = [CPApp targetForAction:[item action] to:[item target] from:item]; + + if (!validator || ![validator respondsToSelector:[item action]]) + [item _setEnabled:NO]; + else if ([validator respondsToSelector:@selector(validateMenuItem:)]) + [item _setEnabled:[validator validateMenuItem:item]]; + else if ([validator respondsToSelector:@selector(validateUserInterfaceItem:)]) + [item _setEnabled:[validator validateUserInterfaceItem:item]]; + } + + [[_menuWindow _menuView] tile]; } // Managing the Title @@ -1100,6 +1122,8 @@ var CPMenuTitleKey = @"CPMenuTitleKey", _showsStateColumn = ![aCoder containsValueForKey:CPMenuShowsStateColumnKey] || [aCoder decodeBoolForKey:CPMenuShowsStateColumnKey]; + _autoenablesItems = YES; + [self setMinimumWidth:0]; } diff --git a/AppKit/CPMenu/_CPMenuManager.j b/AppKit/CPMenu/_CPMenuManager.j index a95bc5fba..15af786fb 100644 --- a/AppKit/CPMenu/_CPMenuManager.j +++ b/AppKit/CPMenu/_CPMenuManager.j @@ -243,9 +243,15 @@ var STICKY_TIME_INTERVAL = 500, // Close the current menu item because we are going to select a new one after a short delay [self showMenu:nil fromMenu:activeMenu atPoint:CGPointMakeZero()]; - _showTimerID = setTimeout(function() { + + if (![activeMenuContainer isMenuBar]) + { + _showTimerID = setTimeout(function() { + [self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:newMenuOrigin]; + }, 250); + } + else [self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:newMenuOrigin]; - }, 250); } } diff --git a/AppKit/CPMenu/_CPMenuWindow.j b/AppKit/CPMenu/_CPMenuWindow.j index cb52e9822..053d493d8 100644 --- a/AppKit/CPMenu/_CPMenuWindow.j +++ b/AppKit/CPMenu/_CPMenuWindow.j @@ -208,8 +208,14 @@ var STICKY_TIME_INTERVAL = 500, return [_menuView menu]; } +- (_CPMenuView)_menuView +{ + return _menuView; +} + - (void)orderFront:(id)aSender { + [[self menu] update]; [self setFrame:_unconstrainedFrame]; [super orderFront:aSender]; diff --git a/AppKit/CPMenuItem/CPMenuItem.j b/AppKit/CPMenuItem/CPMenuItem.j index e0dcd305e..fd8f117db 100644 --- a/AppKit/CPMenuItem/CPMenuItem.j +++ b/AppKit/CPMenuItem/CPMenuItem.j @@ -133,10 +133,17 @@ if ([_menu autoenablesItems]) return; - _isEnabled = isEnabled; + [self _setEnabled:isEnabled]; +} + +- (void)_setEnabled:(BOOL)isEnabled +{ + if (_isEnabled === isEnabled) + return; + + _isEnabled = !!isEnabled; [_menuItemView setDirty]; - [_menu itemChanged:self]; } @@ -851,6 +858,11 @@ CPControlKeyMask return ![self submenu] && [self menu] === [CPApp mainMenu]; } +- (CPString)description +{ + return [super description] + @" target: " + [self target] + @" action: " + CPStringFromSelector([self action]); +} + @end var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey", diff --git a/AppKit/CPSplitView.j b/AppKit/CPSplitView.j index 16e58d422..f47f1e4b7 100644 --- a/AppKit/CPSplitView.j +++ b/AppKit/CPSplitView.j @@ -739,41 +739,59 @@ var CPSplitViewHorizontalImage = nil, Sets the delegate of the receiver. Possible delegate methods to implement are listed below. -
-        - (void)splitViewDidResizeSubviews:(CPSplitView)aSplitView;
-            Notifies the delegate when the subviews have resized.
+Notifies the delegate when the subviews have resized.
+@code
+- (void)splitViewDidResizeSubviews:(CPNotification)aNotification;
+@endcode
 
-        - (void)splitViewWillResizeSubviews:(CPSplitView)aSplitView;
-            Notifies the delegate when the subviews will be resized.
+Notifies the delegate when the subviews will be resized.
+@code
+- (void)splitViewWillResizeSubviews:(CPNotification)aNotification;
+@endcode
 
-        - (CGRect)splitView:(CPSplitView)aSplitView effectiveRect:(CGRect)aRect forDrawnRect:(CGRect)aDrawnRect ofDividerAtIndex:(int)aDividerIndex;
-            Lets the delegate specify a different rect for which the user can drag the splitView divider.
+Lets the delegate specify a different rect for which the user can drag the splitView divider.
+@code
+- (CGRect)splitView:(CPSplitView)aSplitView effectiveRect:(CGRect)aRect forDrawnRect:(CGRect)aDrawnRect ofDividerAtIndex:(int)aDividerIndex;
+@endcode
 
-        - (CGRect)splitView:(CPSplitView)aSplitView additionalEffectiveRectOfDividerAtIndex:(int)indexOfDivider;
-            Lets the delegate specify an additional rect for which the user can drag the splitview divider.
+Lets the delegate specify an additional rect for which the user can drag the splitview divider.
+@code
+- (CGRect)splitView:(CPSplitView)aSplitView additionalEffectiveRectOfDividerAtIndex:(int)indexOfDivider;
+@endcode
 
-        - (BOOL)splitView:(CPSplitView)aSplitView canCollapseSubview:(CPView)aSubview;
-            Notifies the delegate that the splitview is about to be collapsed. This usually happens when the user
-            Double clicks on the divider. Return YES if the subview can be collapsed, otherwise NO.
+Notifies the delegate that the splitview is about to be collapsed. This usually happens when the user
+Double clicks on the divider. Return YES if the subview can be collapsed, otherwise NO.
+@code
+- (BOOL)splitView:(CPSplitView)aSplitView canCollapseSubview:(CPView)aSubview;
+@endcode
 
-         - (BOOL)splitView:(CPSplitView)aSplitView shouldCollapseSubview:(CPView)aSubview forDoubleClickOnDividerAtIndex:(int)indexOfDivider;
-            Notifies the delegate that the subview at indexOfDivider is about to be collapsed. This usually happens when the user
-            Double clicks on the divider. Return YES if the subview should be collapsed, otherwise NO.
+Notifies the delegate that the subview at indexOfDivider is about to be collapsed. This usually happens when the user
+Double clicks on the divider. Return YES if the subview should be collapsed, otherwise NO.
+@code
+ - (BOOL)splitView:(CPSplitView)aSplitView shouldCollapseSubview:(CPView)aSubview forDoubleClickOnDividerAtIndex:(int)indexOfDivider;
+@endcode
 
-        - (float)splitView:(CPSplitView)aSpiltView constrainSplitPosition:(float)proposedPosition ofSubviewAt:(int)subviewIndex;
-            Allows the delegate to constrain the subview beings resized. This method is called continuously as the user resizes the divider.
-            For example if the subview needs to have a width which is a multiple of a certain number you could return that multiple with this method.
+Allows the delegate to constrain the subview beings resized. This method is called continuously as the user resizes the divider.
+For example if the subview needs to have a width which is a multiple of a certain number you could return that multiple with this method.
+@code
+- (float)splitView:(CPSplitView)aSpiltView constrainSplitPosition:(float)proposedPosition ofSubviewAt:(int)subviewIndex;
+@endcode
 
-        - (float)splitView:(CPSplitView)aSplitView constrainMinCoordinate:(float)proposedMin ofSubviewAt:(int)subviewIndex;
-            Allows the delegate to constrain the minimum position of a subview.
+Allows the delegate to constrain the minimum position of a subview.
+@code
+- (float)splitView:(CPSplitView)aSplitView constrainMinCoordinate:(float)proposedMin ofSubviewAt:(int)subviewIndex;
+@endcode
 
-        - (float)splitView:(CPSplitView)aSplitView constrainMaxCoordinate:(float)proposedMax ofSubviewAt:(int)subviewIndex;
-            Allows the delegate to constrain the maximum position of a subview.
+Allows the delegate to constrain the maximum position of a subview.
+@code
+- (float)splitView:(CPSplitView)aSplitView constrainMaxCoordinate:(float)proposedMax ofSubviewAt:(int)subviewIndex;
+@endcode
 
-        - (void)splitView:(CPSplitView)aSplitView resizeSubviewsWithOldSize:(CGSize)oldSize;
-            Allows the splitview to specify a custom resizing behavior. This is called when the splitview is resized.
-            The sum of the views and the sum of the dividers should be equal to the size of the splitview.
-    
+Allows the splitview to specify a custom resizing behavior. This is called when the splitview is resized. +The sum of the views and the sum of the dividers should be equal to the size of the splitview. +@code +- (void)splitView:(CPSplitView)aSplitView resizeSubviewsWithOldSize:(CGSize)oldSize; +@endcode @param delegate - The delegate of the splitview. */ diff --git a/AppKit/CPWindow/CPWindow.j b/AppKit/CPWindow/CPWindow.j index dcdc9e727..8a2f6e43f 100644 --- a/AppKit/CPWindow/CPWindow.j +++ b/AppKit/CPWindow/CPWindow.j @@ -193,24 +193,24 @@ var SHADOW_MARGIN_LEFT = 20.0, _CPWindowShadowColor = nil; var CPWindowSaveImage = nil, - CPWindowSavingImage = nil; + CPWindowSavingImage = nil, -var CPWindowResizeTime = 0.2; + CPWindowResizeTime = 0.2; /* Keys for which action messages will be sent by default when unhandled, e.g. complete:. */ var CPWindowActionMessageKeys = [ - CPLeftArrowFunctionKey, - CPRightArrowFunctionKey, - CPUpArrowFunctionKey, - CPDownArrowFunctionKey, - CPPageUpFunctionKey, - CPPageDownFunctionKey, - CPHomeFunctionKey, - CPEndFunctionKey, - CPEscapeFunctionKey -]; + CPLeftArrowFunctionKey, + CPRightArrowFunctionKey, + CPUpArrowFunctionKey, + CPDownArrowFunctionKey, + CPPageUpFunctionKey, + CPPageDownFunctionKey, + CPHomeFunctionKey, + CPEndFunctionKey, + CPEscapeFunctionKey + ]; /*! @ingroup appkit @@ -2377,7 +2377,9 @@ CPTexturedBackgroundWindowMask // an event going of the responder chain is passed to the input system as a last resort. // However, the only methods I could get Cocoa to call automatically are // moveUp: moveDown: moveLeft: moveRight: pageUp: pageDown: and complete: - [self _processKeyboardUIKey:anEvent]; + // Unhandled events just travel further up the responder chain _past_ the window. + if (![self _processKeyboardUIKey:anEvent]) + [super keyDown:anEvent]; } /* @@ -2412,6 +2414,8 @@ CPTexturedBackgroundWindowMask // The difference is that doCommandBySelector: will also send the action to the window and application delegates. [[self firstResponder] doCommandBySelector:@selector(complete:)]; } + + return NO; } - (void)_dirtyKeyViewLoop diff --git a/AppKit/Cib/CPCibControlConnector.j b/AppKit/Cib/CPCibControlConnector.j index 68934e572..ddb64d8c3 100644 --- a/AppKit/Cib/CPCibControlConnector.j +++ b/AppKit/Cib/CPCibControlConnector.j @@ -47,12 +47,8 @@ // If the destination doesn't respond to this selector, warn but don't die. if (_destination && ![_destination respondsToSelector:selector]) - { CPLog.warn(@"Could not connect the action " + selector + @" to target of class " + [_destination className]); - return; - } - // Not being able to set the action is a fatal error. if ([_source respondsToSelector:@selector(setAction:)]) objj_msgSend(_source, @selector(setAction:), selector); diff --git a/AppKit/CoreGraphics/CGColor.j b/AppKit/CoreGraphics/CGColor.j index 321d091b6..7b8ee6088 100644 --- a/AppKit/CoreGraphics/CGColor.j +++ b/AppKit/CoreGraphics/CGColor.j @@ -108,7 +108,7 @@ function CGColorCreateCopy(aColor) */ function CGColorCreateGenericGray(gray, alpha) { - return CGColorCreate(0, [gray, alpha]); + return CGColorCreate(CGColorSpaceCreateDeviceRGB(), [gray,gray,gray, alpha]); } /*! @@ -122,7 +122,7 @@ function CGColorCreateGenericGray(gray, alpha) */ function CGColorCreateGenericRGB(red, green, blue, alpha) { - return CGColorCreate(0, [red, green, blue, alpha]); + return CGColorCreate(CGColorSpaceCreateDeviceRGB(), [red, green, blue, alpha]); } /*! @@ -137,7 +137,8 @@ function CGColorCreateGenericRGB(red, green, blue, alpha) */ function CGColorCreateGenericCMYK(cyan, magenta, yellow, black, alpha) { - return CGColorCreate(0, [cyan, magenta, yellow, black, alpha]); + return CGColorCreate(CGColorSpaceCreateDeviceCMYK(), + [cyan, magenta, yellow, black, alpha]); } /*! @@ -149,19 +150,21 @@ function CGColorCreateGenericCMYK(cyan, magenta, yellow, black, alpha) */ function CGColorCreateCopyWithAlpha(aColor, anAlpha) { - var components = aColor.components; + if ( !aColor ) return aColor; // Avoid error null pointer in next line - if (!aColor || anAlpha == components[components.length - 1]) + var components = aColor.components.slice(); + + if (anAlpha == components[components.length - 1]) return aColor; + // set new alpha value now so that a potentially a new cache entry is made and + // not that an existing cache entry is mutated. + components[components.length - 1] = anAlpha; + if (aColor.pattern) - var copy = CGColorCreateWithPattern(aColor.colorspace, aColor.pattern, components); + return CGColorCreateWithPattern(aColor.colorspace, aColor.pattern, components); else - var copy = CGColorCreate(aColor.colorspace, components); - - copy.components[components.length - 1] = anAlpha; - - return copy; + return CGColorCreate(aColor.colorspace, components); } /*! diff --git a/AppKit/CoreGraphics/CGContext.j b/AppKit/CoreGraphics/CGContext.j index 34eb0c603..a67d7b200 100644 --- a/AppKit/CoreGraphics/CGContext.j +++ b/AppKit/CoreGraphics/CGContext.j @@ -129,7 +129,7 @@ function CGGStateCreateCopy(aGState) return { alpha:aGState.alpha, strokeStyle:aGState.strokeStyle, fillStyle:aGState.fillStyle, lineWidth:aGState.lineWidth, lineJoin:aGState.lineJoin, lineCap:aGState.lineCap, miterLimit:aGState.miterLimit, globalAlpha:aGState.globalAlpha, blendMode:aGState.blendMode, - shadowOffset:aGState.shadowOffset, shadowBlur:aGState.shadowBlur, shadowColor:aGState.shadowColor, CTM:_CGAffineTransformMakeCopy(aGState.CTM) }; + shadowOffset:_CGSizeMakeCopy(aGState.shadowOffset), shadowBlur:aGState.shadowBlur, shadowColor:aGState.shadowColor, CTM:_CGAffineTransformMakeCopy(aGState.CTM) }; } /*! @@ -606,7 +606,7 @@ function CGContextStrokeLineSegments(aContext, points, count) { var i = 0; - if (arguments["count"] == NULL) + if (count === NULL) var count = points.length; CGContextBeginPath(aContext); diff --git a/AppKit/CoreGraphics/CGContextCanvas.j b/AppKit/CoreGraphics/CGContextCanvas.j index 5a12aae01..288ab147f 100644 --- a/AppKit/CoreGraphics/CGContextCanvas.j +++ b/AppKit/CoreGraphics/CGContextCanvas.j @@ -144,7 +144,7 @@ function CGContextAddRects(aContext, rects, count) { var i = 0; - if (arguments["count"] == NULL) + if (count === NULL) var count = rects.length; for (; i < count; ++i) @@ -194,7 +194,7 @@ function CGContextFillRects(aContext, rects, count) { var i = 0; - if (arguments["count"] == NULL) + if (count === NULL) var count = rects.length; for (; i < count; ++i) @@ -225,7 +225,7 @@ function CGContextClipToRect(aContext, aRect) function CGContextClipToRects(aContext, rects, count) { - if (arguments["count"] == NULL) + if (count === NULL) var count = rects.length; _CGContextBeginPathCanvas(aContext); diff --git a/AppKit/CoreGraphics/CGGradient.j b/AppKit/CoreGraphics/CGGradient.j index 29b5d149f..24d0c3573 100644 --- a/AppKit/CoreGraphics/CGGradient.j +++ b/AppKit/CoreGraphics/CGGradient.j @@ -29,10 +29,15 @@ kCGGradientDrawsAfterEndLocation = 1 << 1; function CGGradientCreateWithColorComponents(aColorSpace, components, locations, count) { - if (arguments["locations"] == NULL) - var locations = [0.0, 1.0]; + if ( locations === undefined || locations === NULL ) + { + var num_of_colors = components.length / 4, + locations = []; + for ( var idx = 0; idx < num_of_colors; idx++ ) + locations.push( idx / (num_of_colors - 1) ); + } - if (arguments["count"] == NULL) + if ( count === undefined || count === NULL) var count = locations.length; var colors = []; @@ -58,4 +63,4 @@ function CGGradientRelease() function CGGradientRetain(aGradient) { return aGradient; -} \ No newline at end of file +} diff --git a/AppKit/CoreGraphics/CGPath.j b/AppKit/CoreGraphics/CGPath.j index 5dde217cb..bc0fe4c2f 100644 --- a/AppKit/CoreGraphics/CGPath.j +++ b/AppKit/CoreGraphics/CGPath.j @@ -141,7 +141,7 @@ function CGPathAddLines(aPath, aTransform, points, count) { var i = 1; - if (arguments["count"] == NULL) + if (count === NULL) var count = points.length; if (!aPath || count < 2) @@ -224,7 +224,7 @@ function CGPathAddRects(aPath, aTransform, rects, count) { var i = 0; - if (arguments["count"] == NULL) + if (count === NULL) var count = rects.length; for (; i < count; ++i) diff --git a/Foundation/CPArray/CPMutableArray.j b/Foundation/CPArray/CPMutableArray.j index bf0874224..e9588dd51 100644 --- a/Foundation/CPArray/CPMutableArray.j +++ b/Foundation/CPArray/CPMutableArray.j @@ -332,7 +332,7 @@ [self replaceObjectAtIndex:otherIndex withObject:temporary]; } -- (CPArray)sortUsingDescriptors:(CPArray)descriptors +- (void)sortUsingDescriptors:(CPArray)descriptors { [self sortUsingFunction:compareObjectsUsingDescriptors context:descriptors]; } diff --git a/Foundation/CPKeyValueObserving.j b/Foundation/CPKeyValueObserving.j index fbac24c09..ee72d91fc 100644 --- a/Foundation/CPKeyValueObserving.j +++ b/Foundation/CPKeyValueObserving.j @@ -678,6 +678,14 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti { var observers = _observersForKey[aPath]; + if (!observers) + { + CPLog.warn(@"Cannot remove an observer %@ for the key path \"%@\" from %@ because it is not registered as an observer.", + _targetObject, aPath, anObserver); + + return; + } + if (aPath.indexOf('.') != CPNotFound) { var forwarder = [observers objectForKey:[anObserver UID]].forwarder; diff --git a/Foundation/CPKeyedUnarchiver.j b/Foundation/CPKeyedUnarchiver.j index 666b4f1e3..b9baf93c5 100644 --- a/Foundation/CPKeyedUnarchiver.j +++ b/Foundation/CPKeyedUnarchiver.j @@ -441,71 +441,48 @@ var _CPKeyedUnarchiverDecodeObjectAtIndex = function(self, anIndex) var object = self._objects[anIndex]; if (object) + { if (object === self._objects[0]) return nil; - else - return object; - - var object, - plistObject = self._plistObjects[anIndex], - plistObjectClass = plistObject.isa; - - if (plistObjectClass === CPDictionaryClass || plistObjectClass === CPMutableDictionaryClass) + // Don't return immediately here. The _CPKeyedArchiverValueClass unwrapper code + // hasn't executed yet. + } + else { - var plistClass = self._plistObjects[plistObject.valueForKey(_CPKeyedArchiverClassKey).valueForKey(_CPKeyedArchiverUIDKey)], - className = plistClass.valueForKey(_CPKeyedArchiverClassNameKey), - classes = plistClass.valueForKey(_CPKeyedArchiverClassesKey), - theClass = [self classForClassName:className]; + var plistObject = self._plistObjects[anIndex], + plistObjectClass = plistObject.isa; - if (!theClass) - theClass = CPClassFromString(className); - - if (!theClass && (self._delegateSelectors & CPKeyedUnarchiverDelegate_unarchiver_cannotDecodeObjectOfClassName_originalClasses_)) - theClass = [_delegate unarchiver:self cannotDecodeObjectOfClassName:className originalClasses:classes]; - - if (!theClass) - [CPException raise:CPInvalidUnarchiveOperationException reason:@"-[CPKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (" + className + @")"]; - - var savedPlistObject = self._plistObject; - - self._plistObject = plistObject; - - // Should we only call this on _CPCibClassSwapper? (currently the only class that makes use of this). - object = [theClass allocWithCoder:self]; - - // It is important to do this before calling initWithCoder so that decoding can be self referential (something = self). - self._objects[anIndex] = object; - - var processedObject = [object initWithCoder:self]; - - self._plistObject = savedPlistObject; - - if (processedObject !== object) + if (plistObjectClass === CPDictionaryClass || plistObjectClass === CPMutableDictionaryClass) { - if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector) - [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject]; + var plistClass = self._plistObjects[plistObject.valueForKey(_CPKeyedArchiverClassKey).valueForKey(_CPKeyedArchiverUIDKey)], + className = plistClass.valueForKey(_CPKeyedArchiverClassNameKey), + classes = plistClass.valueForKey(_CPKeyedArchiverClassesKey), + theClass = [self classForClassName:className]; - object = processedObject; - self._objects[anIndex] = processedObject; - } + if (!theClass) + theClass = CPClassFromString(className); - processedObject = [object awakeAfterUsingCoder:self]; + if (!theClass && (self._delegateSelectors & CPKeyedUnarchiverDelegate_unarchiver_cannotDecodeObjectOfClassName_originalClasses_)) + theClass = [_delegate unarchiver:self cannotDecodeObjectOfClassName:className originalClasses:classes]; - if (processedObject !== object) - { - if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector) - [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject]; + if (!theClass) + [CPException raise:CPInvalidUnarchiveOperationException reason:@"-[CPKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (" + className + @")"]; - object = processedObject; - self._objects[anIndex] = processedObject; - } + var savedPlistObject = self._plistObject; - if (self._delegate) - { - if (self._delegateSelectors & _CPKeyedUnarchiverDidDecodeObjectSelector) - processedObject = [self._delegate unarchiver:self didDecodeObject:object]; + self._plistObject = plistObject; - if (processedObject && processedObject != object) + // Should we only call this on _CPCibClassSwapper? (currently the only class that makes use of this). + object = [theClass allocWithCoder:self]; + + // It is important to do this before calling initWithCoder so that decoding can be self referential (something = self). + self._objects[anIndex] = object; + + var processedObject = [object initWithCoder:self]; + + self._plistObject = savedPlistObject; + + if (processedObject !== object) { if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector) [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject]; @@ -513,22 +490,48 @@ var _CPKeyedUnarchiverDecodeObjectAtIndex = function(self, anIndex) object = processedObject; self._objects[anIndex] = processedObject; } - } - } - else - { - self._objects[anIndex] = object = plistObject; - if ([object class] === CPStringClass) - { - if (object === _CPKeyedArchiverNullString) + processedObject = [object awakeAfterUsingCoder:self]; + + if (processedObject !== object) { - self._objects[anIndex] = self._objects[0]; + if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector) + [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject]; - return nil; + object = processedObject; + self._objects[anIndex] = processedObject; + } + + if (self._delegate) + { + if (self._delegateSelectors & _CPKeyedUnarchiverDidDecodeObjectSelector) + processedObject = [self._delegate unarchiver:self didDecodeObject:object]; + + if (processedObject && processedObject != object) + { + if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector) + [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject]; + + object = processedObject; + self._objects[anIndex] = processedObject; + } + } + } + else + { + self._objects[anIndex] = object = plistObject; + + if ([object class] === CPStringClass) + { + if (object === _CPKeyedArchiverNullString) + { + self._objects[anIndex] = self._objects[0]; + + return nil; + } + else + self._objects[anIndex] = object = plistObject; } - else - self._objects[anIndex] = object = plistObject; } } diff --git a/Foundation/CPURL.j b/Foundation/CPURL.j index 1b60010a0..810677c7e 100644 --- a/Foundation/CPURL.j +++ b/Foundation/CPURL.j @@ -227,7 +227,7 @@ CPURLCustomIconKey = @"CPURLCustomIconKey"; return self.setResourcePropertyForKey(aKey, anObject); } -- (CPString)staticResourceData +- (CPData)staticResourceData { return self.staticResourceData(); } diff --git a/Tests/AppKit/CGAffineTransformTest.j b/Tests/AppKit/CGAffineTransformTest.j new file mode 100644 index 000000000..f1d47a034 --- /dev/null +++ b/Tests/AppKit/CGAffineTransformTest.j @@ -0,0 +1,511 @@ +@import +@import "CGTestCase.j" + +@implementation CGAffineTransformTest : CGTestCase + +- (void)testAffineTransformMake +{ + [self compareTransform:CGAffineTransformMake(1, 2, 3, 4, 3.2, 5.4) + with:{ a: 1, b: 2, c:3, d:4, tx:3.2, ty:5.4 } + message:"transform make"]; +} + +- (void)testCGAffineTransformMakeIdentity +{ + [self compareTransform:CGAffineTransformMakeIdentity() + with:{ a: 1, b: 0, c:0, d:1, tx:0, ty:0 } + message:"transform make identity"]; +} + +- (void)testAffineTransformMakeCopy +{ + var transform = CGAffineTransformMake(1, 2, 3, 4, 3.2, 5.4), + t2 = CGAffineTransformMakeCopy(transform); + + transform.a = transform.b = transform.c = transform.d = + transform.tx = transform.ty = 0; + + [self compareTransform:t2 + with:CGAffineTransformMake( 1,2,3,4,3.2,5.4 ) + message:"copy correctly made"]; + + [self compareTransform:transform + with:CGAffineTransformMake( 0, 0, 0, 0, 0, 0 ) + message:"original transform was changed"] ; +} + +- (void)testAffineTransformCreateCopy +{ + // FIXME: CGAffineTransformCreateCopy seems to be working but code has a Fixme? + var transform = CGAffineTransformMake(1, 2, 3, 4, 3.2, 5.4), + t2 = CGAffineTransformCreateCopy(transform); + + transform.a = transform.b = transform.c = transform.d = + transform.tx = transform.ty = 0; + + [self compareTransform:t2 + with:CGAffineTransformMake( 1,2,3,4,3.2,5.4 ) + message:"copy correctly made"]; + + [self compareTransform:transform + with:CGAffineTransformMake( 0, 0, 0, 0, 0, 0 ) + message:"original transform was changed"] ; +} + +- (void)testAffineTransformMakeScale +{ + [self compareTransform:CGAffineTransformMakeScale(3,4) + with:CGAffineTransformMake(3, 0, 0, 4, 0, 0) + message:"make scale"]; +} + +- (void)testAffineTransformMakeTranslation +{ + [self compareTransform:CGAffineTransformMakeTranslation(3,4) + with:CGAffineTransformMake(1,0,0,1,3,4) + message:"make translation"]; +} + +- (void)testAffineTransformTranslate +{ + var transform = CGAffineTransformMakeTranslation(3,4); + + [self compareTransform:CGAffineTransformTranslate(transform,-3,-4) + with:CGAffineTransformMakeIdentity() + message:"translate to identity"]; + + [self compareTransform:CGAffineTransformTranslate(transform,0,0) + with:transform + message:"zero translate"]; +} + +- (void)testAffineTransformScale +{ + var transform = CGAffineTransformMakeScale(3,4); + + [self compareTransform:CGAffineTransformScale( transform, 1/3, 1/4) + with:CGAffineTransformMakeIdentity() + message:"scale to identity"]; + + transform = CGAffineTransformMake(2, -2, -3, 3, 3.2, 5.4); + [self compareTransform:CGAffineTransformScale(transform, 1, 1) + with:transform + message:"scale by 1"]; + + [self compareTransform:CGAffineTransformScale(transform, 2, 5) + with:CGAffineTransformMake(4,-4,-15,15, 3.2,5.4) + message:"random scale to somewhere"]; +} + +- (void)testAffineTransformConcat +{ + var testcases = { + "identity concat" : { + testdata: CGAffineTransformConcat( CGAffineTransformMakeIdentity(), + CGAffineTransformMakeIdentity() ), + expdata: CGAffineTransformMakeIdentity() + }, + + "translation" : { + testdata: CGAffineTransformConcat( CGAffineTransformMakeTranslation(3,4), + CGAffineTransformMakeTranslation(-3,-4) ), + expdata: CGAffineTransformMakeIdentity() + }, + + "translation (reversed)" : { + testdata: CGAffineTransformConcat( CGAffineTransformMakeTranslation(-3,-4), + CGAffineTransformMakeTranslation(3,4) ), + expdata: CGAffineTransformMakeIdentity() + }, + + "scale" : { + testdata: CGAffineTransformConcat(CGAffineTransformMakeScale(3,4), + CGAffineTransformMakeScale(1/3,1/4)), + expdata: CGAffineTransformMakeIdentity() + }, + + "scale (reversed)" : { + testdata: CGAffineTransformConcat(CGAffineTransformMakeScale(1/3,1/4), + CGAffineTransformMakeScale(3,4)), + expdata: CGAffineTransformMakeIdentity() + }, + + "rotation" : { + testdata: CGAffineTransformConcat(CGAffineTransformMakeRotation(Math.PI), + CGAffineTransformMakeRotation(-Math.PI)), + expdata: CGAffineTransformMakeIdentity() + }, + }; + + for ( var key in testcases ) + [self compareTransform:testcases[key].testdata with:testcases[key].expdata message:key]; +} + +- (void)testPointApplyAffineTransform +{ + var testcases = { + "translate to zero" : { + testdata: CGPointApplyAffineTransform( CGPointMake( 3, 4 ), + CGAffineTransformMakeTranslation(-3,-4)), + expdata: CGPointMakeZero() + }, + + "scale to 1,1" : { + testdata: CGPointApplyAffineTransform( CGPointMake( 3, 4 ), + CGAffineTransformMakeScale(1/3,1/4) ), + expdata: CGPointMake(1,1) + }, + + "scale and translate to zero" : { + testdata: CGPointApplyAffineTransform( CGPointMake( 3, 4 ), + CGAffineTransformConcat( + CGAffineTransformMakeScale(1/3,1/4), + CGAffineTransformMakeTranslation(-1,-1))), + expdata: CGPointMakeZero() + + }, + }; + + for ( var key in testcases ) + [self comparePoint:testcases[key].testdata with:testcases[key].expdata message:key]; +} + +- (void)testSizeApplyAffineTransform +{ + var testcases = { + "translation on size should do nothing" : { + testdata: CGSizeApplyAffineTransform( CGSizeMake(3, 12), + CGAffineTransformMakeTranslation(-3,-4)), + expdata: CGSizeMake(3, 12) + }, + + "scale to 1,1" : { + testdata: CGSizeApplyAffineTransform( CGSizeMake( 3, 4 ), + CGAffineTransformMakeScale(1/3,1/4) ), + expdata: CGSizeMake(1, 1) + }, + + "scale and translate combined" : { + testdata: CGSizeApplyAffineTransform( CGSizeMake( 3, 4 ), + CGAffineTransformConcat( + CGAffineTransformMakeScale(1/3,1/4), + CGAffineTransformMakeTranslation(-1,-1))), + expdata: CGSizeMake(1, 1) + + }, + }; + + for ( var key in testcases ) + [self compareSize:testcases[key].testdata with:testcases[key].expdata message:key]; +} + +- (void)testAffineTransformIsIdentityPositive +{ + var testcases = { + "identity is identity" : { + testdata: CGAffineTransformMakeIdentity() + }, + + "zero rotation is identity" : { + testdata: CGAffineTransformMakeRotation(0), + }, + + "zero translation is identity" : { + testdata: CGAffineTransformMakeTranslation(0,0) + }, + + "one scale is identity" : { + testdata: CGAffineTransformMakeScale(1,1) + }, + + "identity concat'ed" : { + testdata: CGAffineTransformConcat( CGAffineTransformMakeIdentity(), + CGAffineTransformMakeIdentity() ), + }, + + "translation" : { + testdata: CGAffineTransformConcat( CGAffineTransformMakeTranslation(3,4), + CGAffineTransformMakeTranslation(-3,-4) ), + }, + + "scale" : { + testdata: CGAffineTransformConcat(CGAffineTransformMakeScale(3,4), + CGAffineTransformMakeScale(1/3,1/4)), + }, + + "rotation" : { + testdata: CGAffineTransformConcat(CGAffineTransformMakeRotation(-Math.PI), + CGAffineTransformMakeRotation(Math.PI)), + }, + + }; + + for ( var key in testcases ) + [self assert:YES + equals:CGAffineTransformIsIdentity(testcases[key].testdata) + message:key]; +} + +- (void)testAffineTransformIsIdentityNegative +{ + var testcases = { + "some random transform" : { + testdata: CGAffineTransformMake(1,1,1,1,1,1) + }, + + "non-zero translation is not identity" : { + testdata: CGAffineTransformMakeTranslation(1,1) + }, + + "non-one scale is not identity" : { + testdata: CGAffineTransformMakeScale(2,2) + }, + + "rotation" : { + testdata: CGAffineTransformMakeRotation(Math.PI), + }, + + // TODO a two-pi rotation is actually identity + "2PI rotation is NOT identity?" : { + testdata: CGAffineTransformMakeRotation(Math.PI * 2), + }, + + }; + + for ( var key in testcases ) + [self assert:NO + equals:CGAffineTransformIsIdentity(testcases[key].testdata) + message:key]; +} + +- (void)testAffineTransformEqualToTransform +{ + var testcases = { + "identity" : { + lhs: CGAffineTransformMakeIdentity(), + rhs: CGAffineTransformMakeIdentity(), + expdata: YES + }, + + "translate" : { + lhs: CGAffineTransformMakeTranslation(1,1), + rhs: CGAffineTransformMakeTranslation(1,1), + expdata: YES + }, + + "scale" : { + lhs: CGAffineTransformMakeScale(1,1), + rhs: CGAffineTransformMakeScale(1,1), + expdata: YES + }, + + "rotation" : { + lhs: CGAffineTransformMakeRotation(Math.PI), + rhs: CGAffineTransformMakeRotation(Math.PI), + expdata: YES + }, + + "translate and scale" : { + lhs: CGAffineTransformMakeScale(1,1), + rhs: CGAffineTransformMakeTranslation(1,1), + expdata: NO + }, + }; + + for ( var key in testcases ) + [self assert:testcases[key].expdata + equals:CGAffineTransformEqualToTransform(testcases[key].lhs, testcases[key].rhs) + message:key]; +} + +- (void)testStringCreateWithCGAffineTransform +{ + // FIXME?: should there be a leading space on these strings + var testcases = { + "identity" : { + testdata: CGAffineTransformMakeIdentity(), + expdata: " [[ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1]]" + }, + + "scale" : { + testdata: CGAffineTransformMakeScale(3,4), + expdata: " [[ 3, 0, 0 ], [ 0, 4, 0 ], [ 0, 0, 1]]" + }, + + "translation" : { + testdata: CGAffineTransformMakeTranslation(3,4), + expdata: " [[ 1, 0, 0 ], [ 0, 1, 0 ], [ 3, 4, 1]]" + }, + + "scale and translation" : { + testdata: CGAffineTransformTranslate(CGAffineTransformMakeScale(3,4),5,6), + expdata: " [[ 3, 0, 0 ], [ 0, 4, 0 ], [ 15, 24, 1]]" + }, + }; + + for ( var key in testcases ) + [self assert:testcases[key].expdata + equals:CGStringCreateWithCGAffineTransform(testcases[key].testdata) + message:key]; +} + +- (void)testStringFromCGAffineTransform +{ + var testcases = { + "identity" : { + testdata: CGAffineTransformMakeIdentity(), + expdata: "{1, 0, 0, 1, 0, 0}" + }, + + "scale" : { + testdata: CGAffineTransformMakeScale(3,4), + expdata: "{3, 0, 0, 4, 0, 0}" + }, + + "translation" : { + testdata: CGAffineTransformMakeTranslation(3,4), + expdata: "{1, 0, 0, 1, 3, 4}" + }, + + "rotation - zero" : { + testdata: CGAffineTransformMakeRotation(0), + expdata: "{1, 0, 0, 1, 0, 0}" + }, + + "rotation - pi" : { + testdata: CGAffineTransformMakeRotation(Math.PI), + expdata: "{-1, 1.2246467991473532e-16, -1.2246467991473532e-16, -1, 0, 0}" + }, + + "rotation - 2pi" : { + testdata: CGAffineTransformMakeRotation(2 * Math.PI), + expdata: "{1, -2.4492935982947064e-16, 2.4492935982947064e-16, 1, 0, 0}" + }, + + "rotation - 3pi" : { + testdata: CGAffineTransformMakeRotation(3 * Math.PI), + expdata: "{-1, 3.6739403974420594e-16, -3.6739403974420594e-16, -1, 0, 0}" + }, + + "scale and translation and rotate" : { + testdata: CGAffineTransformRotate(CGAffineTransformTranslate(CGAffineTransformMakeScale(3,4),5,6),Math.PI), + expdata: "{-3, 4.898587196589413e-16, -3.6739403974420594e-16, -4, 15, 24}" + }, + }; + + for ( var key in testcases ) + [self assert:testcases[key].expdata + equals:CPStringFromCGAffineTransform(testcases[key].testdata) + message:key]; +} + +- (void)testAffineTransformRotate +{ + var ang = Math.PI + 0.5 * Math.PI, + transform = CGAffineTransformMake( 1,2,3,4, 5,6 ), + cos = COS(ang), + sin = SIN(ang); + + var testcases = { + "affine transform rotate failed" : { + testdata: CGAffineTransformRotate(transform, ang), + expdata: CGAffineTransformMake( transform.a * cos + transform.c * sin, + transform.b * cos + transform.d * sin, + transform.c * cos - transform.a * sin, + transform.d * cos - transform.b * sin, + transform.tx,transform.ty ) + }, + + "rotation negation" : { + testdata: CGAffineTransformRotate(CGAffineTransformRotate(CGAffineTransformMakeScale(3,4),Math.PI),-Math.PI), + expdata: CGAffineTransformMakeScale(3,4) + } + }; + + for ( var key in testcases ) + [self compareTransform:testcases[key].testdata with:testcases[key].expdata message:key]; +} + +- (void)testAffineTransformInvert +{ + var transform = CGAffineTransformRotate(CGAffineTransformTranslate(CGAffineTransformMakeScale(3,4),5,6),Math.PI), + determinant = 1 / (transform.a * transform.d - transform.b * transform.c), + invertedtransform = CGAffineTransformMake(determinant * transform.d, + -determinant * transform.b, + -determinant * transform.c, + determinant * transform.a, + determinant * (transform.c * transform.ty - + transform.d * transform.tx), + determinant * (transform.b * transform.tx - + transform.a * transform.ty)); + + var testcases = { + "test invert algorithm" : { + testdata: transform, + expdata: invertedtransform + }, + + "identity should be it self on inversion" : { + testdata: CGAffineTransformMakeIdentity(), + expdata: CGAffineTransformMakeIdentity() + }, + + "rotation" : { + testdata: CGAffineTransformMakeRotation(-Math.PI), + expdata: CGAffineTransformMakeRotation(Math.PI), + }, + + "translation" : { + testdata: CGAffineTransformMakeTranslation(4,5), + expdata: CGAffineTransformMakeTranslation(-4,-5) + }, + + "scale" : { + testdata: CGAffineTransformMakeScale(3,4), + expdata: CGAffineTransformMakeScale(1/3,1/4) + }, + }; + + for ( var key in testcases ) + [self compareTransform:CGAffineTransformInvert(testcases[key].testdata) + with:testcases[key].expdata + message:key]; +} + +- (void)testRectApplyAffineTransform +{ + var rect = CGRectMake( 3,4,5,6 ); + + var testcases = { + "identity does nothing" : { + testdata: CGRectApplyAffineTransform(rect, CGAffineTransformMakeIdentity() ), + expdata: rect + }, + + "rotation 90 degrees" : { + testdata: CGRectApplyAffineTransform(rect, CGAffineTransformMakeRotation(Math.PI/2)), + expdata: CGRectMake( -10,3.0000000000000004,6,5 ) + }, + + "translation" : { + testdata: CGRectApplyAffineTransform(rect, CGAffineTransformMakeTranslation(3,4)), + expdata: CGRectMake( 6,8,5,6 ) + }, + + "scale" : { + testdata: CGRectApplyAffineTransform(rect, CGAffineTransformMakeScale(1,4)), + expdata: CGRectMake( 3,16,5,24 ) + }, + + "rotate, translate and scale" : { + testdata: CGRectApplyAffineTransform(rect, CGAffineTransformRotate(CGAffineTransformTranslate(CGAffineTransformMakeScale(3,4),5,6),Math.PI)), + expdata: CGRectMake( -9.000000000000004, -16, + 15.000000000000002, 24.000000000000004 ) + }, + }; + + for ( var key in testcases ) + [self compareRect:testcases[key].testdata with:testcases[key].expdata message:key]; +} + +@end diff --git a/Tests/AppKit/CGColorSpaceTest.j b/Tests/AppKit/CGColorSpaceTest.j new file mode 100644 index 000000000..3df8b4c3f --- /dev/null +++ b/Tests/AppKit/CGColorSpaceTest.j @@ -0,0 +1,168 @@ +@import + +@implementation CGColorSpaceTest : OJTestCase + +- (void)testColorSpaceCreateWithName +{ + var testdata = { + "CGColorSpaceGenericGray" : { + model: kCGColorSpaceModelMonochrome, + count: 1, + base: NULL + }, + "CGColorSpaceGenericRGB" : { + model: kCGColorSpaceModelRGB, + count: 3, + base: NULL + }, + "CGColorSpaceGenericCMYK" : { + model: kCGColorSpaceModelCMYK, + count: 4, + base: NULL + }, + "CGColorSpaceGenericRGBLinear" : { + model: kCGColorSpaceModelRGB, + count: 3, + base: NULL + }, + "CGColorSpaceGenericRGBHDR" : { + model: kCGColorSpaceModelRGB, + count: 3, + base: NULL + }, + "CGColorSpaceAdobeRGB1998" : { + model: kCGColorSpaceModelRGB, + count: 3, + base: NULL + }, + "CGColorSpaceSRGB" : { + model: kCGColorSpaceModelRGB, + count: 3, + base: NULL + }, + } + + for ( var key in testdata ) { + var clrsp = CGColorSpaceCreateWithName(key), + expdata = testdata[key]; + [self assert:expdata.model equals:clrsp.model message:"model for "+key]; + [self assert:expdata.count equals:clrsp.count message:"count for "+key]; + [self assert:expdata.base equals:clrsp.base message:"base for "+key]; + } + + [self assert:NULL + equals:CGColorSpaceCreateWithName("doesnotexist") + message:"should be missing"]; +} + +- (void)testColorSpaceStandardizeComponents +{ + var testcases = { + "notsupported1" : { + testdata : { + model: kCGColorSpaceModelIndexed, + count: 5 + }, + expected: [5,4,3,2,1,NaN] // alpha value normalisation adds value at index 5 + }, + "notsupported2" : { + testdata : { + model: kCGColorSpaceModelLab, + count: 5 + }, + expected: [5,4,3,2,1,NaN] + }, + "notsupported3" : { + testdata : { + model: kCGColorSpaceModelPattern, + count: 4 + }, + expected: [5,4,3,2,1] + }, + "base override with count value" : { + testdata : { + model: kCGColorSpaceModelPattern, + base: { model: kCGColorSpaceModelMonochrome }, + count: 4 + }, + expected: [1,1,1,1,1], + }, + "base override no count value" : { + testdata : { + model: kCGColorSpaceModelPattern, + base: { model: kCGColorSpaceModelMonochrome, count: 4 }, + count: 0 + }, + expected: [1,4,3,2,1], // alpha value is assumed to be at index 0 + }, + "monochrome color space model" : { + testdata : { + model: kCGColorSpaceModelMonochrome, + count: 2 + }, + expected: [1,1,1,2,1], + }, + "rgb color space model" : { + testdata : { + model: kCGColorSpaceModelRGB, + count: 2 + }, + expected: [1,1,1,2,1], + }, + "cmyk color space model" : { + testdata : { + model: kCGColorSpaceModelCMYK, + count: 2 + }, + expected: [1,1,1,2,1], + }, + "devicen color space model" : { + testdata : { + model: kCGColorSpaceModelDeviceN, + count: 2 + }, + expected: [1,1,1,2,1], + }, + } + + for ( var key in testcases ) { + var components = [5,4,3,2,1]; + CGColorSpaceStandardizeComponents( testcases[key].testdata, components); + [self assert:testcases[key].expected equals:components message:"Failed for "+key]; + } +} + +- (void)testRgbStandardize +{ + var clrspc = CGColorSpaceCreateDeviceRGB(), + testcases = { + "negative values" : { + components: [-5,-4,-3,-2,-1,-1,-1,-1], + expected: [0,0,0,0,-1,-1,-1,-1], + }, + // TODO: this happens because first the alpha value is normalised, + // TODO: so the components array does not have length 0, rather 4. + "missing values become undefined" : { + components: [], + expected: [undefined,undefined,undefined,1], + }, + "values standarized if between 0 and 1" : { + components: [0.3, 0.4, 0.5, 0.6], + expected: [ROUND(0.3*255)/255, ROUND(0.4*255)/255, + ROUND(0.5*255)/255, ROUND(0.6*255)/255], + }, + "values 0 or 1 if zero or one" : { + components: [0, 1, 0, 1], + expected: [0,1,0,1], + } + }; + + for ( var key in testcases ) { + CGColorSpaceStandardizeComponents( clrspc, testcases[key].components); + [self assert:testcases[key].expected + equals:testcases[key].components + message:"Failed for "+key]; + } +} + +@end diff --git a/Tests/AppKit/CGColorTest.j b/Tests/AppKit/CGColorTest.j new file mode 100644 index 000000000..97416ef87 --- /dev/null +++ b/Tests/AppKit/CGColorTest.j @@ -0,0 +1,217 @@ +@import + +@implementation CGColorTest : OJTestCase + +- (void)testColorCreateReturnNullIfNull +{ + [self assert:NULL equals:CGColorCreate( NULL, [1,2,3] ) message:"colorspace null failed"]; + [self assert:NULL + equals:CGColorCreate( CGColorSpaceCreateDeviceRGB(), NULL ) + message:"components null failed"]; +} + +- (void)testColorCreateEnsureComponentsAreCopied +{ + var colorspace = CGColorSpaceCreateDeviceRGB(), + components = [2,3,4,5], + clr = CGColorCreate(colorspace, components); + + components[0] = components[1] = components[2] = components[3] = 0; + [self assert:[1,1,1,1] equals:clr.components]; + [self assert:[0,0,0,0] equals:components]; +} + +- (void)testColorCreateCachesColorsAlphaChangeIsPropagated +{ + var clr = CGColorCreate(CGColorSpaceCreateDeviceRGB(), [0.4, 0.3, 0.2, 0.2]), + newclr = CGColorCreate(CGColorSpaceCreateDeviceRGB(), [0.4, 0.3, 0.2, 0.2]); + newclr.components[newclr.components.length - 1] = 0.6; + [self assert:0.6 equals:clr.components[clr.components.length-1]]; +} + +- (void)testColorCreate +{ + var colorspace = CGColorSpaceCreateDeviceRGB(), + components = [2,3,4,5], + clruid = CFHashCode(colorspace) + components.join(""); + + // Can't access the colormap cache, but it would be nice to + // [self assert:NULL equals:_CGColorMap[clruid]]; + + var clr = CGColorCreate( colorspace, components ); + [self assert:colorspace equals:clr.colorspace message:"colorspace failed"]; + [self assert:[1,1,1,1] equals:clr.components message:"components failed"]; + [self assert:NULL equals:clr.pattern message:"pattern failed"]; + + // Can't access the colormap cache, but it would be nice to + // [self assert:clr equals:_CGColorMap[clruid]]; +} + +- (void)testColorCopy +{ + // it returns it's argument, a little artifical but will fail if something + // changes with the function. + [self assert:"banana" equals:CGColorCreateCopy( "banana" )]; + + var colorspace = CGColorSpaceCreateDeviceRGB(), + components = [2,3,4,5], + clr = CGColorCreate( colorspace, components ); + [self assert:clr equals:CGColorCreateCopy(clr)]; +} + +- (void)testColorCreateGenericGray +{ + var clr = CGColorCreateGenericGray(0.3, 0.5); + [self assert:CGColorSpaceCreateDeviceRGB() + equals:clr.colorspace message:"colorspace failed"]; + [self assert:[ROUND(0.3*255)/255,ROUND(0.3*255)/255,ROUND(0.3*255)/255,0.5] + equals:clr.components message:"components failed"]; + [self assert:NULL equals:clr.pattern message:"pattern failed"]; +} + +- (void)testColorCreateGenericRGB +{ + var clr = CGColorCreateGenericRGB(0.4,0.3,0.2,0.1); + [self assert:CGColorSpaceCreateDeviceRGB() + equals:clr.colorspace message:"colorspace failed"]; + [self assert:[ROUND(0.4*255)/255,ROUND(0.3*255)/255,ROUND(0.2*255)/255,0.1] + equals:clr.components message:"components failed"]; + [self assert:NULL equals:clr.pattern message:"pattern failed"]; +} + +- (void)testColorCreateGenericCMYK +{ + var clr = CGColorCreateGenericCMYK(0.2, 0.3, 0.4, 0.5, 0.6) + [self assert:CGColorSpaceCreateDeviceCMYK() + equals:clr.colorspace message:"colorspace failed"]; + [self assert:[ROUND(0.2*255)/255,ROUND(0.3*255)/255,ROUND(0.4*255)/255, + ROUND(0.5*255)/255, 0.6] + equals:clr.components message:"components failed"]; + [self assert:NULL equals:clr.pattern message:"pattern failed"]; +} + +- (void)testColorCreateWithPattern +{ + var clrspc = CGColorSpaceCreateDeviceRGB(), + pattern = "a new pattern", + components = [0.2, 0.4, 0.6, 0.8], + clr = CGColorCreateWithPattern(clrspc, pattern, components); + + [self assert:clrspc equals:clr.colorspace]; + [self assert:pattern equals:clr.pattern]; + [self assert:components equals:clr.components]; + + // ensure that there was a copy made of the components + components[0] = components[1] = components[2] = components[3] = 0.0; + [self assert:[0.2, 0.4, 0.6, 0.8] equals:clr.components]; + + // ensure that null is returned + [self assert:NULL equals:CGColorCreateWithPattern(NULL, "a new pattern", [])]; + [self assert:NULL equals:CGColorCreateWithPattern(CGColorSpaceCreateDeviceRGB(), + NULL, [])]; + [self assert:NULL equals:CGColorCreateWithPattern(CGColorSpaceCreateDeviceRGB(), + "a new pattern", NULL)]; + [self assert:NULL equals:CGColorCreateWithPattern(NULL, NULL, NULL)]; +} + +- (void)testColorGetAlpha +{ + var clr = CGColorCreateGenericCMYK(0.2, 0.3, 0.4, 0.5, 0.6); + [self assert:0.6 equals:CGColorGetAlpha(clr)]; +} + +- (void)testColorGetColorSpace +{ + var clr = CGColorCreateGenericCMYK(0.2, 0.3, 0.4, 0.5, 0.6); + [self assert:CGColorSpaceCreateDeviceCMYK() + equals:CGColorGetColorSpace(clr)]; +} + +- (void)testColorGetComponents +{ + var clr = CGColorCreateGenericCMYK(0.2, 0.3, 0.4, 0.5, 0.6); + [self assert:[ROUND(0.2*255)/255, ROUND(0.3*255)/255, ROUND(0.4*255)/255, + ROUND(0.5*255)/255, ROUND(0.6*255)/255] + equals:CGColorGetComponents(clr)]; +} + +- (void)testColorGetNumberOfComponents +{ + var clr = CGColorCreateGenericCMYK(0.2, 0.3, 0.4, 0.5, 0.6); + [self assert:5 equals:CGColorGetNumberOfComponents(clr)]; +} + +- (void)testColorGetPattern +{ + var clr = CGColorCreateWithPattern(CGColorSpaceCreateDeviceRGB(), "a new pattern", + [1,1,1,1]); + [self assert:"a new pattern" equals:CGColorGetPattern(clr)]; +} + +- (void)testColorCreateCopyWithAlphaWithPattern +{ + var clr = CGColorCreateWithPattern(CGColorSpaceCreateDeviceRGB(), "a new pattern", + [1,1,1,1]); + [self assert:1 equals:CGColorGetAlpha(clr) message:"initial alpha"]; + + var newclr = CGColorCreateCopyWithAlpha(clr, 0.6); + [self assert:0.6 equals:CGColorGetAlpha(newclr) message:"new color alpha value"]; + [self assert:1 equals:CGColorGetAlpha(clr) message:"alpha after copy"]; + + [self assert:clr.colorspace equals:newclr.colorspace message:"newclr colorspace failed"]; + [self assert:clr.pattern equals:newclr.pattern message:"newclr pattern failed"]; +} + +- (void)testColorCreateCopyWithAlphaWithComponents +{ + var clr = CGColorCreateGenericRGB(0.4, 0.3, 0.2, 0.3); + [self assert:0.3 equals:CGColorGetAlpha(clr) message:"initial alpha"]; + + var newclr = CGColorCreateCopyWithAlpha(clr, 0.6); + [self assert:0.6 equals:CGColorGetAlpha(newclr) message:"new color alpha value"]; + [self assert:0.3 equals:CGColorGetAlpha(clr) message:"alpha after copy"]; + + [self assert:CGColorSpaceCreateDeviceRGB() + equals:newclr.colorspace message:"newclr colorspace failed"]; + [self assert:[ROUND(0.4*255)/255,ROUND(0.3*255)/255,ROUND(0.2*255)/255,0.6] + equals:newclr.components message:"newclr components failed"]; + [self assert:NULL equals:newclr.pattern message:"newclr pattern failed"]; + + [self assert:CGColorSpaceCreateDeviceRGB() + equals:clr.colorspace message:"orig clr colorspace failed"]; + [self assert:[ROUND(0.4*255)/255,ROUND(0.3*255)/255,ROUND(0.2*255)/255,0.3] + equals:clr.components message:"orig clr components failed"]; + [self assert:NULL equals:clr.pattern message:"orig clr pattern failed"]; +} + +- (void)testColorCreateCopyWithAlphaWithComponentsNullColor +{ + [self assert:NULL + equals:CGColorCreateCopyWithAlpha(NULL,0.5) + message:"null color failed"]; + + var clr = CGColorCreateGenericRGB(0.4,0.3,0.2,0.1), + newclr = CGColorCreateCopyWithAlpha(clr, 0.1); + [self assert:clr equals:newclr]; +} + +- (void)testColorEqualToColor +{ + [self assert:true equals:CGColorEqualToColor(NULL,NULL)]; + + var clr1 = CGColorCreateGenericRGB(0.4, 0.3, 0.2, 0.3); + [self assert:false equals:CGColorEqualToColor(NULL,clr1)]; + [self assert:true equals:CGColorEqualToColor(clr1,clr1)]; + [self assert:false equals:CGColorEqualToColor(clr1,NULL)]; + + var clr2 = CGColorCreateGenericRGB(0.4, 0.3, 0.2, 0.3); + [self assert:true equals:CGColorEqualToColor(clr1,clr2)]; + [self assert:true equals:CGColorEqualToColor(clr2,clr1)]; + + clr2 = CGColorCreateGenericCMYK(0.2, 0.3, 0.4, 0.5, 0.6); + [self assert:false equals:CGColorEqualToColor(clr1,clr2)]; + [self assert:false equals:CGColorEqualToColor(clr2,clr1)]; +} + +@end + diff --git a/Tests/AppKit/CGContextTest.j b/Tests/AppKit/CGContextTest.j new file mode 100644 index 000000000..0ed6242e6 --- /dev/null +++ b/Tests/AppKit/CGContextTest.j @@ -0,0 +1,78 @@ +@import +@import "CGTestCase.j" + +@implementation CGContextTest : CGTestCase + +- (void)testEnsureTestingCanvas +{ + /* + Ensure that we have no canvas nor vml support. + */ + [self assert:YES equals:!CPFeatureIsCompatible(CPHTMLCanvasFeature)]; + [self assert:YES equals:!CPFeatureIsCompatible(CPVMLFeature)]; +} + +- (void)testGStateCreate +{ + var gstate = CGGStateCreate(), + testdata = { alpha: 1.0, + strokeStyle: "#000", + fillStyle: "#ccc", + lineWidth: 1.0, + lineJoin: kCGLineJoinMiter, + lineCap: kCGLineCapButt, + miterLimit: 10.0, + globalAlpha: 1.0, + blendMode: kCGBlendModeNormal, + shadowBlur: 0.0, + shadowColor: NULL }; + + for ( var key in testdata ) + [self assert:testdata[key] equals:gstate[key] message:"Failed for " + key]; + + [self compareSize:CGSizeMakeZero() + with:gstate.shadowOffset + message:"Failed for shadowOffset"]; + + [self compareTransform:CGAffineTransformMakeIdentity() + with:gstate.CTM + message:"Failed for CTM"]; +} + +- (void)testGStateCreateCopy +{ + var gstate = CGGStateCreate(), + gstatecopy = CGGStateCreateCopy(gstate), + testdata = { alpha: 1.0, + strokeStyle: "#000", + fillStyle: "#ccc", + lineWidth: 1.0, + lineJoin: kCGLineJoinMiter, + lineCap: kCGLineCapButt, + miterLimit: 10.0, + globalAlpha: 1.0, + blendMode: kCGBlendModeNormal, + shadowBlur: 0.0, + shadowColor: NULL }; + + for ( var key in testdata ) + [self assert:testdata[key] equals:gstatecopy[key] message:"Failed for " + key]; + + // ensure that the size is copied + var sizecopy = CGSizeMakeCopy( gstate.shadowOffset ); + gstate.shadowOffset.width += 1000; + gstate.shadowOffset.height += 2000; + [self compareSize:sizecopy with:gstatecopy.shadowOffset message:"Failed for shadowOffset"]; + + // ensure that the transform is copied + var transformcopy = CGAffineTransformMakeCopy( gstate.CTM ); + gstate.CTM.a += 1000; + gstate.CTM.b += 1000; + gstate.CTM.c += 1000; + gstate.CTM.d += 1000; + gstate.CTM.tx += 1000; + gstate.CTM.ty += 1000; + [self compareTransform:transformcopy with:gstatecopy.CTM message:"Failed with CTM"]; +} + +@end diff --git a/Tests/AppKit/CGGradientTest.j b/Tests/AppKit/CGGradientTest.j new file mode 100644 index 000000000..0f89d0c60 --- /dev/null +++ b/Tests/AppKit/CGGradientTest.j @@ -0,0 +1,100 @@ +@import + +@implementation CGGradientTest : OJTestCase + +/* + Using default values for location and count. +*/ +- (void)testCreateWithColorComponentsDefault +{ + + // Test one color and no location + var components = [ 0.2,0.2,0.4,0.4 ]; + + var tmp = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), + components); + [self assert:1 equals:tmp.colors.length]; + [self assert:1 equals:tmp.locations.length]; + + // TODO: This should not be supported since there is no gradient if only one color + // TODO: NaN not really helpful... + [self assert:NaN equals:tmp.locations[0]]; + + [self assert:components.slice(0,4) equals:tmp.colors[0].components]; + + // Test two colors and no location + var components = [ 0.2,0.2,0.4,0.4, + 0.6,0.6,0.8,0.8 ]; + + var tmp = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), + components); + [self assert:2 equals:tmp.colors.length]; + [self assert:2 equals:tmp.locations.length]; + + [self assert:0 equals:tmp.locations[0]]; + [self assert:1.0 equals:tmp.locations[1]]; + + [self assert:components.slice(4,8) equals:tmp.colors[1].components]; + [self assert:components.slice(0,4) equals:tmp.colors[0].components]; + + // Test three colors and no location + var components = [ 0.2,0.2,0.4,0.4, + 0.6,0.6,0.8,0.8, + 0.6,0.6,0.8,0.8 ]; + + var tmp = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), + components); + [self assert:3 equals:tmp.colors.length]; + [self assert:3 equals:tmp.locations.length]; + + [self assert:0 equals:tmp.locations[0]]; + [self assert:0.5 equals:tmp.locations[1]]; + [self assert:1.0 equals:tmp.locations[2]]; + + [self assert:components.slice(8,12) equals:tmp.colors[2].components]; + [self assert:components.slice(4,8) equals:tmp.colors[1].components]; + [self assert:components.slice(0,4) equals:tmp.colors[0].components]; +} + +/* + Using location and components to specify two color stops that aren't + evenly spread out. +*/ +- (void)testCreateWithColorComponentsUsingTwoColorStops +{ + var components = [ 0.2,0.2,0.4,0.4, + 0.6,0.6,0.8,0.8 ]; + var locations = [0.3,0.6]; + + var tmp = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), + components,locations); + [self assert:2 equals:tmp.colors.length]; + [self assert:2 equals:tmp.locations.length]; + + [self assert:0.3 equals:tmp.locations[0]]; + [self assert:0.6 equals:tmp.locations[1]]; + + [self assert:components.slice(4,8) equals:tmp.colors[1].components]; + [self assert:components.slice(0,4) equals:tmp.colors[0].components]; +} + +/* + Define three color components and ensure that the Gradient defined contains 3 color stops. +*/ +- (void)testCanHaveMoreThanTwoColorStops +{ + var components = [ 1,2,3,4, 5,6,7,8, 9,10,11,12 ]; + var locations = [1,2,3]; + + var tmp = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), + components, locations, 3); + [self assert:3 equals:tmp.colors.length]; + [self assert:3 equals:tmp.locations.length]; + + [self assert:1 equals:tmp.locations[0]]; + [self assert:2 equals:tmp.locations[1]]; + [self assert:3 equals:tmp.locations[2]]; +} + + +@end diff --git a/Tests/AppKit/CGTestCase.j b/Tests/AppKit/CGTestCase.j new file mode 100644 index 000000000..e00bb1e11 --- /dev/null +++ b/Tests/AppKit/CGTestCase.j @@ -0,0 +1,44 @@ +/* + Used for comparing geometry forms. Otherwise each unit test would repeat these + comparisons. +*/ +@implementation CGTestCase : OJTestCase + +- (void)compareRect:(CGRect)aRect + with:(id)anotherRect + message:(CPString)aMsg +{ + aMsg += " (via cmp. rect)"; + [self comparePoint:aRect.origin with:anotherRect.origin message:aMsg]; + [self compareSize:aRect.size with:anotherRect.size message:aMsg]; +} + +- (void)compareSize:(CGSize)aSize + with:(id)anotherSize + message:(CPString)aMsg +{ + [self assert:anotherSize.width equals:aSize.width message:aMsg + ": Failed for width"]; + [self assert:anotherSize.height equals:aSize.height message:aMsg + ": Failed for height"]; +} + +- (void)comparePoint:(CGPoint)aPoint + with:(id)anotherPoint + message:(CPString)aMsg +{ + [self assert:anotherPoint.x equals:aPoint.x message:aMsg + ": Failed for x"]; + [self assert:anotherPoint.y equals:aPoint.y message:aMsg + ": Failed for y"]; +} + +- (void)compareTransform:(CGAffineTransform)aTransform + with:(id)aDataSet + message:(CPString)aMsg +{ + [self assert:aDataSet.a equals:aTransform.a message:aMsg + ": Failed for a"]; + [self assert:aDataSet.b equals:aTransform.b message:aMsg + ": Failed for b"]; + [self assert:aDataSet.c equals:aTransform.c message:aMsg + ": Failed for c"]; + [self assert:aDataSet.d equals:aTransform.d message:aMsg + ": Failed for d"]; + [self assert:aDataSet.tx equals:aTransform.tx message:aMsg + ": Failed for tx"]; + [self assert:aDataSet.ty equals:aTransform.ty message:aMsg + ": Failed for ty"]; +} + +@end diff --git a/Tests/AppKit/CPKeyValueObservingTest.j b/Tests/AppKit/CPKeyValueObservingTest.j deleted file mode 100644 index 67e3cd07b..000000000 --- a/Tests/AppKit/CPKeyValueObservingTest.j +++ /dev/null @@ -1,106 +0,0 @@ -@import - -@implementation CPKeyValueObservingTest : OJTestCase -{ - CPString _lastKeyPath; - id _lastObject; - CPDictionary _lastChange; - id _lastContext; -} - -- (void)setup -{ - _lastKeyPath = _lastObject = _lastChange = _lastContext = nil; -} - -- (void)testInitialObserving -{ - var tester = [ObservingTester testerWithCheese:@"CHEESE!"]; - [tester addObserver:self forKeyPath:@"cheese" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionInitial context:nil]; - - [self assert:@"cheese" equals:_lastKeyPath]; - [self assert:tester equals:_lastObject]; - [self assert:[CPDictionary dictionaryWithObject:@"CHEESE!" forKey:CPKeyValueChangeNewKey] equals:_lastChange]; - [self assert:nil equals:_lastContext]; -} - -- (void)observeValueForKeyPath:(CPString)aKeyPath - ofObject:(id)anObject - change:(CPDictionary)aChange - context:(id)aContext -{ - _lastKeyPath = aKeyPath; - _lastObject = anObject; - _lastChange = aChange; - _lastContext = aContext; -} - -- (void)testSendNotificationsForDependantKeyPaths -{ - var observingTester = [ObservingTester testerWithCheese:@"cheese"], - dependantKeyPathTester = [DependantKeyPathsTester testerWithObservingTester:observingTester]; - - [dependantKeyPathTester addObserver:self forKeyPath:@"observedCheese" options:CPKeyValueObservingOptionNew context:nil]; - [observingTester setCheese:@"changed cheese"]; - - [self assert:@"observedCheese" equals:_lastKeyPath] - [self assert:dependantKeyPathTester equals:_lastObject]; -} - -@end - -@implementation ObservingTester : CPObject -{ - id cheese; -} - -+ (id)testerWithCheese:(id)aCheese -{ - var tester = [[self alloc] init]; - [tester setCheese:aCheese]; - return tester; -} - -- (void)setCheese:(id)aCheese -{ - cheese = aCheese; -} - -- (id)cheese -{ - return cheese; -} - -@end - -@implementation DependantKeyPathsTester: CPObject -{ - ObservingTester _observingTester @accessors(property=observingTester); -} - -+ (CPSet)keyPathsForValuesAffectingObservedCheese -{ - return [CPSet setWithObjects:@"observingTester.cheese"]; -} - -+ (id)testerWithObservingTester:(ObservingTester)theObservingTester -{ - return [[self alloc] initWithObservingTester:theObservingTester]; -} - -- (id)initWithObservingTester:(ObservingTester)theObservingTester -{ - if (self = [super init]) - { - _observingTester = theObservingTester; - } - - return self; -} - -- (CPString)observedCheese -{ - return [[self observingTester] cheese]; -} - -@end diff --git a/Tests/AppKit/CPMenuValidatedUserInterfaceItemTest.j b/Tests/AppKit/CPMenuValidatedUserInterfaceItemTest.j new file mode 100644 index 000000000..4322239fa --- /dev/null +++ b/Tests/AppKit/CPMenuValidatedUserInterfaceItemTest.j @@ -0,0 +1,86 @@ +var CPMenuValidatedUserInterfaceItemTestValidatedItems = []; + +@implementation CPMenuValidatedUserInterfaceItemTest : OJTestCase +{ + CPMenu _menu @accessors(property=menu); + + MenuTarget _menuTarget @accessors(property=menuTarget); +} + +- (void)setUp +{ + _menuTarget = [[MenuTarget alloc] init]; + [[CPApplication sharedApplication] setDelegate:_menuTarget]; + + _menu = [[CPMenu alloc] init]; + + [_menu addItem:[[CPMenuItem alloc] initWithTitle:@"implemented" action:@selector(implementedAction:) keyEquivalent:nil]]; + [_menu addItem:[[CPMenuItem alloc] initWithTitle:@"disabled" action:@selector(disabledAction:) keyEquivalent:nil]]; + [_menu addItem:[[CPMenuItem alloc] initWithTitle:@"unimplemented" action:@selector(unimplementedAction:) keyEquivalent:nil]]; + + var parentItem = [[CPMenuItem alloc] initWithTitle:@"parent" action:nil keyEquivalent:nil]; + [_menu addItem:parentItem]; + + [parentItem setSubmenu:[[CPMenu alloc] init]]; + [[parentItem submenu] addItem:[[CPMenuItem alloc] initWithTitle:@"Submenu 1" action:nil keyEquivalent:nil]]; + + CPMenuValidatedUserInterfaceItemTestValidatedItems = []; + + // Update is what performs the autoenabling so we need to call it manually. + // It's normally called automatically just before a menu becomes visible. + [_menu update]; +} + +- (void)testAutoenable +{ + [self assertTrue:[[[self menu] itemWithTitle:@"implemented"] isEnabled] message:@"The implemented action should be enabled"]; + [self assertFalse:[[[self menu] itemWithTitle:@"disabled"] isEnabled] message:@"The disbabled action should be disabled"]; + [self assertFalse:[[[self menu] itemWithTitle:@"unimplemented"] isEnabled] message:@"The unimplemented action should be disabled"]; +} + +- (void)testThatParentMenusAreNotValidated +{ + var parentItem = [[self menu] itemWithTitle:@"parent"]; + + [self assertTrue:[parentItem isEnabled] message:@"Parent items should never be disabled"]; + [self assertFalse:[[[self menuTarget] validatedItems] containsObject:parent] message:@"Parent items should never be validated"]; +} + +@end + +@implementation MenuTarget : CPObject +{ + CPArray _validatedItems @accessors(property=validatedItems, readonly); +} + +- (id)init +{ + if (self = [super init]) + { + _validatedItems = []; + } + + return self; +} + +- (BOOL)validateMenuItem:(CPMenuItem)theMenuItem +{ + [_validatedItems addObject:theMenuItem]; + + if ([theMenuItem action] === @selector(disabledAction:)) + return NO; + + return YES; +} + +- (@action)implementedAction:(id)theSender +{ + +} + +- (@action)disabledAction:(id)theSender +{ + +} + +@end \ No newline at end of file diff --git a/Tests/AppKit/CPTextFieldTest.j b/Tests/AppKit/CPTextFieldTest.j new file mode 100644 index 000000000..65fba1642 --- /dev/null +++ b/Tests/AppKit/CPTextFieldTest.j @@ -0,0 +1,41 @@ +@import + +[CPApplication sharedApplication] + +@implementation CPTextFieldTest : OJTestCase +{ +} + +/*! + Detect regressions with JS object theme attribute encoding in subclasses. + + This test is not actually CPTextField specific at all. This just happens + to be the first place the keyed archiving bug was discovered, and there is + an additional, more specific, test in CPKeyedArchiverTest. +*/ +- (void)testArchiveThemeAttributes +{ + var view = [[CPTextFieldSubclass alloc] initWithFrame:CGRectMakeZero()], + decoded = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:view]]; + + [self assert:[view valueForThemeAttribute:@"content-inset"].top equals:2 message:@"content-inset should initialise correctly"]; + [self assert:[view valueForThemeAttribute:@"content-inset"].top equals:[decoded valueForThemeAttribute:@"content-inset"].top message:@"content-inset should unarchive correctly"]; +} + +@end + +@implementation CPTextFieldSubclass : CPTextField +{ + +} + +- (id)initWithFrame:aFrame +{ + if (self = [super initWithFrame:aFrame]) + { + [self setValue:CGInsetMake(2.0, 2.0, 2.0, 2.0) forThemeAttribute:"content-inset"]; + } + return self; +} + +@end \ No newline at end of file diff --git a/Tests/Foundation/CPKeyValueObservingTest.j b/Tests/Foundation/CPKeyValueObservingTest.j index 5d43fc8da..2d409618f 100644 --- a/Tests/Foundation/CPKeyValueObservingTest.j +++ b/Tests/Foundation/CPKeyValueObservingTest.j @@ -22,6 +22,45 @@ return [theClass new]; } +- (void)setup +{ + _lastKeyPath = _lastObject = _lastChange = _lastContext = nil; +} + +- (void)testInitialObserving +{ + var tester = [ObservingTester testerWithCheese:@"CHEESE!"]; + [tester addObserver:self forKeyPath:@"cheese" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionInitial context:nil]; + + [self assert:@"cheese" equals:_lastKeyPath]; + [self assert:tester equals:_lastObject]; + [self assert:[CPDictionary dictionaryWithObject:@"CHEESE!" forKey:CPKeyValueChangeNewKey] equals:_lastChange]; + [self assert:nil equals:_lastContext]; +} + +- (void)observeValueForKeyPath:(CPString)aKeyPath + ofObject:(id)anObject + change:(CPDictionary)aChange + context:(id)aContext +{ + _lastKeyPath = aKeyPath; + _lastObject = anObject; + _lastChange = aChange; + _lastContext = aContext; +} + +- (void)testSendNotificationsForDependantKeyPaths +{ + var observingTester = [ObservingTester testerWithCheese:@"cheese"], + dependantKeyPathTester = [DependantKeyPathsTester testerWithObservingTester:observingTester]; + + [dependantKeyPathTester addObserver:self forKeyPath:@"observedCheese" options:CPKeyValueObservingOptionNew context:nil]; + [observingTester setCheese:@"changed cheese"]; + + [self assert:@"observedCheese" equals:_lastKeyPath] + [self assert:dependantKeyPathTester equals:_lastObject]; +} + - (void)testOnlyInsertObject_AtKeyIndex_Implemented { var insertSelector = @selector(insertObject:inObjectsAtIndex:), @@ -155,3 +194,59 @@ } @end + +@implementation ObservingTester : CPObject +{ + id cheese; +} + ++ (id)testerWithCheese:(id)aCheese +{ + var tester = [[self alloc] init]; + [tester setCheese:aCheese]; + return tester; +} + +- (void)setCheese:(id)aCheese +{ + cheese = aCheese; +} + +- (id)cheese +{ + return cheese; +} + +@end + +@implementation DependantKeyPathsTester: CPObject +{ + ObservingTester _observingTester @accessors(property=observingTester); +} + ++ (CPSet)keyPathsForValuesAffectingObservedCheese +{ + return [CPSet setWithObjects:@"observingTester.cheese"]; +} + ++ (id)testerWithObservingTester:(ObservingTester)theObservingTester +{ + return [[self alloc] initWithObservingTester:theObservingTester]; +} + +- (id)initWithObservingTester:(ObservingTester)theObservingTester +{ + if (self = [super init]) + { + _observingTester = theObservingTester; + } + + return self; +} + +- (CPString)observedCheese +{ + return [[self observingTester] cheese]; +} + +@end diff --git a/Tests/Foundation/CPKeyedArchiverTest.j b/Tests/Foundation/CPKeyedArchiverTest.j new file mode 100644 index 000000000..ea61e832e --- /dev/null +++ b/Tests/Foundation/CPKeyedArchiverTest.j @@ -0,0 +1,44 @@ +@import +@import + +@implementation CPKeyedArchiverTest : OJTestCase +{ + +} + +- (void)testJavaScriptObject +{ + var original = [Archivable new]; + + [original setAJsObject:{ 'top': 5 }]; + + var decoded = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:original]] + + [self assert:5 equals:[decoded aJsObject].top message:"JS object encoded and decoded right"]; +} + +@end + +@implementation Archivable : CPObject +{ + JSObject aJsObject @accessors; +} + +- (id)initWithCoder:(CPCoder)aCoder +{ + if (self = [super init]) + { + // Note we decode this twice to expose a bug where the cached decoded + // values did not properly unwrap JS objects. + aJsObject = [aCoder decodeObjectForKey:@"aJsObject"]; + aJsObject = [aCoder decodeObjectForKey:@"aJsObject"]; + } + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeObject:aJsObject forKey:@"aJsObject"]; +} + +@end diff --git a/Tools/xcodecapp/XCResourceMonitor.j b/Tools/xcodecapp/XCResourceMonitor.j index d0fb64191..b6241090f 100644 --- a/Tools/xcodecapp/XCResourceMonitor.j +++ b/Tools/xcodecapp/XCResourceMonitor.j @@ -40,6 +40,8 @@ var FILE = require("file"), paths.exclude(FILE.join(FILE.dirname(aPath), "**", "*")); }); + paths.exclude(FILE.join("Build", "**", "*")); + var mtimesForFilePaths = [CPMutableDictionary new]; paths.forEach(function(aPath)