diff --git a/AppKit/AppKit.j b/AppKit/AppKit.j index 84fc150c9..b9de2065b 100644 --- a/AppKit/AppKit.j +++ b/AppKit/AppKit.j @@ -27,6 +27,7 @@ @import "CPAccordionView.j" @import "CPAlert.j" @import "CPAnimation.j" +@import "CPAnimationContext.j" @import "CPAppearance.j" @import "CPApplication.j" @import "CPArrayController.j" @@ -100,6 +101,7 @@ @import "CPTabView.j" @import "CPText.j" @import "CPTextField.j" +@import "CPTextView.j" @import "CPTokenField.j" @import "CPToolbar.j" @import "CPToolbarItem.j" @@ -107,6 +109,7 @@ @import "CPTreeNode.j" @import "CPUserDefaultsController.j" @import "CPView.j" +@import "CPViewAnimator.j" @import "CPViewAnimation.j" @import "CPViewController.j" @import "CPVisualEffectView.j" diff --git a/AppKit/CPBox.j b/AppKit/CPBox.j index d36fb85e8..9ab1d47d1 100644 --- a/AppKit/CPBox.j +++ b/AppKit/CPBox.j @@ -618,8 +618,17 @@ var CPBoxTypeKey = @"CPBoxTypeKey", if (_boxType != CPBoxSeparator) { - _contentView = [aCoder decodeObjectForKey:CPBoxContentView] || [[CPView alloc] initWithFrame:[self bounds]]; - [self replaceSubview:_contentView with:[self subviews][0]]; + // FIXME: we have a problem with CIB decoding here. + // We should be able to simply add : _contentView = [self subviews][0] + // but first box subview seems to be malformed (badly decoded). + // For example, when deployed, this view doesn't have its _trackingAreas array initialized. + // As a (temporary) workaround, we encode/decode the _contentView property. We then transfer the subview hierarchy + // and replace the first (and only) box subview with this _contentView + + _contentView = [aCoder decodeObjectForKey:CPBoxContentView] || [[CPView alloc] initWithFrame:[self bounds]]; + var malformedContentView = [self subviews][0]; + [_contentView setSubviews:[malformedContentView subviews]]; + [self replaceSubview:malformedContentView with:_contentView]; } [self setAutoresizesSubviews:YES]; diff --git a/AppKit/CPCollectionView.j b/AppKit/CPCollectionView.j index b7ff621d0..a980cc953 100644 --- a/AppKit/CPCollectionView.j +++ b/AppKit/CPCollectionView.j @@ -31,6 +31,7 @@ @import "CPDragServer_Constants.j" @import "CPPasteboard.j" @import "CPView.j" +@import "CPKeyValueBinding.j" @class _CPCollectionViewDropIndicator @@ -132,7 +133,6 @@ var HORIZONTAL_MARGIN = 2; CGSize _storedFrameSize; BOOL _uniformSubviewsResizing @accessors(property=uniformSubviewsResizing); - BOOL _lockResizing; CPInteger _currentDropIndex; CPDragOperation _currentDragOperation; @@ -140,6 +140,14 @@ var HORIZONTAL_MARGIN = 2; _CPCollectionViewDropIndicator _dropView; } ++ (Class)_binderClassForBinding:(CPString)aBinding +{ + if (aBinding == CPContentBinding) + return [_CPCollectionViewContentBinder class]; + + return [super _binderClassForBinding:aBinding]; +} + - (id)initWithFrame:(CGRect)aFrame { self = [super initWithFrame:aFrame]; @@ -182,7 +190,7 @@ var HORIZONTAL_MARGIN = 2; _needsMinMaxItemSizeUpdate = YES; _uniformSubviewsResizing = NO; - _lockResizing = NO; + _inLiveResize = NO; _currentDropIndex = -1; _currentDragOperation = CPDragOperationNone; @@ -384,14 +392,7 @@ var HORIZONTAL_MARGIN = 2; _isSelectable = isSelectable; if (!_isSelectable) - { - var index = CPNotFound, - itemCount = [_items count]; - - // Be wary of invalid selection ranges since setContent: does not clear selection indexes. - while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < itemCount) - [_items[index] setSelected:NO]; - } + [self _applySelectionToItems:NO]; } /*! @@ -445,22 +446,15 @@ var HORIZONTAL_MARGIN = 2; { if (!anIndexSet) anIndexSet = [CPIndexSet indexSet]; + if (!_isSelectable || [_selectionIndexes isEqual:anIndexSet]) return; - var index = CPNotFound, - itemCount = [_items count]; - - // Be wary of invalid selection ranges since setContent: does not clear selection indexes. - while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound && index < itemCount) - [_items[index] setSelected:NO]; + [self _applySelectionToItems:NO]; _selectionIndexes = anIndexSet; - var index = CPNotFound; - - while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound) - [_items[index] setSelected:YES]; + [self _applySelectionToItems:YES]; var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"]; [[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectionIndexes"]; @@ -525,14 +519,14 @@ var HORIZONTAL_MARGIN = 2; - (void)resizeWithOldSuperviewSize:(CGSize)oldBoundsSize { - if (_lockResizing) + if (_inLiveResize) return; - _lockResizing = YES; + _inLiveResize = YES; [self tile]; - _lockResizing = NO; + _inLiveResize = NO; } - (void)tile @@ -972,6 +966,20 @@ var HORIZONTAL_MARGIN = 2; return frame; } +- (void)_applySelectionToItems:(BOOL)select +{ + var numberOfItems = [_items count]; + + [_selectionIndexes enumerateIndexesUsingBlock:function(idx, stop) + { + if (idx < numberOfItems) + [[_items objectAtIndex:idx] setSelected:select]; + else { + stop(YES); + } + }]; +} + @end @implementation CPCollectionView (DragAndDrop) @@ -1586,3 +1594,14 @@ var CPCollectionViewMinItemSizeKey = @"CPCollectionViewMinItemSizeK } @end + +@implementation _CPCollectionViewContentBinder : CPBinder +{ +} + +- (void)setValue:(id)aValue forBinding:(CPString)aBinding +{ + [_source setContent:aValue]; +} + +@end diff --git a/AppKit/CPColor.j b/AppKit/CPColor.j index 2e3d1d31a..813a0995a 100644 --- a/AppKit/CPColor.j +++ b/AppKit/CPColor.j @@ -100,8 +100,10 @@ var cachedBlackColor, + (CPDictionary)themeAttributes { return @{ - @"alternate-selected-control-color": [CPNull null], - @"secondary-selected-control-color" : [CPNull null] + @"alternate-selected-control-color": [CPNull null], + @"secondary-selected-control-color": [CPNull null], + @"selected-text-background-color": [CPNull null], + @"selected-text-inactive-background-color": [CPNull null] }; } @@ -498,6 +500,16 @@ var cachedBlackColor, return [[CPColor alloc] _initWithCSSString: aString]; } ++ (CPColor)selectedTextBackgroundColor +{ + return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-background-color"] || [CPColor colorWithHexString:"99CCFF"]; +} + ++ (CPColor)_selectedTextBackgroundColorUnfocussed +{ + return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-inactive-background-color"] || [CPColor colorWithHexString:"CCCCCC"]; +} + /* @ignore */ - (id)_initWithCSSString:(CPString)aString { diff --git a/AppKit/CPColorPanel.j b/AppKit/CPColorPanel.j index d00baa311..e8a9320eb 100644 --- a/AppKit/CPColorPanel.j +++ b/AppKit/CPColorPanel.j @@ -483,13 +483,12 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie"; ]; } - var cookieValue = eval(cookieValue), - result = []; + var cookieValue = eval(cookieValue); - for (var i = 0; i < cookieValue.length; i++) - result.push([CPColor colorWithHexString:cookieValue[i]]); - - return result; + return [cookieValue arrayByApplyingBlock:function(value) + { + return [CPColor colorWithHexString:value]; + }]; } - (CPArray)saveColorList diff --git a/AppKit/CPColorPicker.j b/AppKit/CPColorPicker.j index ea3afcd88..66af9b289 100644 --- a/AppKit/CPColorPicker.j +++ b/AppKit/CPColorPicker.j @@ -125,7 +125,8 @@ _brightnessSlider = [[CPSlider alloc] initWithFrame:CGRectMake(0, (aFrame.size.height - 34), aFrame.size.width, 15)]; [_brightnessSlider setValue:15.0 forThemeAttribute:@"track-width"]; - [_brightnessSlider setValue:[CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPColorPicker class]] pathForResource:@"brightness_bar.png"]]] forThemeAttribute:@"track-color"]; + var brightnessImage = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPColorPicker class]] pathForResource:@"brightness_bar.png"] size:CGSizeMake(272, 20)]; + [_brightnessSlider setValue:[CPColor colorWithPatternImage:brightnessImage] forThemeAttribute:@"track-color"]; [_brightnessSlider setMinValue:0.0]; [_brightnessSlider setMaxValue:100.0]; diff --git a/AppKit/CPColorWell.j b/AppKit/CPColorWell.j index 15fa5343b..fb1b0f0dc 100644 --- a/AppKit/CPColorWell.j +++ b/AppKit/CPColorWell.j @@ -255,6 +255,8 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv var colorPanel = [CPColorPanel sharedColorPanel]; + [colorPanel setPlatformWindow:[[self window] platformWindow]]; + [colorPanel setColor:_color]; [colorPanel orderFront:self]; } diff --git a/AppKit/CPComboBox.j b/AppKit/CPComboBox.j index b5aa1b7c2..61a159ed7 100644 --- a/AppKit/CPComboBox.j +++ b/AppKit/CPComboBox.j @@ -1142,11 +1142,9 @@ var CPComboBoxTextSubview = @"text", // Directly nuke _items, [_items removeAll] will trigger an extra call to setContent _items = []; - var values = []; - - [anArray enumerateObjectsUsingBlock:function(object) + var values = [anArray arrayByApplyingBlock:function(object) { - values.push([object description]); + return [object description]; }]; [self addItemsWithObjectValues:values]; diff --git a/AppKit/CPCompatibility.j b/AppKit/CPCompatibility.j index 1a48ab7ff..d2b31889a 100644 --- a/AppKit/CPCompatibility.j +++ b/AppKit/CPCompatibility.j @@ -116,7 +116,7 @@ if (typeof window !== "undefined" && window.opera) } // Internet Explorer -else if (typeof window !== "undefined" && window.attachEvent) // Must follow Opera check. +else if (typeof window !== "undefined" && (window.attachEvent || (!(window.ActiveXObject) && "ActiveXObject" in window))) // Must follow Opera check. { PLATFORM_ENGINE = CPInternetExplorerBrowserEngine; @@ -369,6 +369,18 @@ function CPBrowserStyleProperty(aProperty) r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transform']] || nil; break; + case 'animationend': + var candidates = { + 'WebkitAnimation' : 'webkitAnimationEnd', + 'MozAnimation' : 'animationend', + 'OAnimation' : 'oAnimationEnd', + 'msAnimation' : 'MSAnimationEnd', + 'animation' : 'animationend' + }; + + r = candidates[PLATFORM_STYLE_JS_PROPERTIES['animation']] || nil; + break; + default: var prefixes = ["Webkit", "Moz", "O", "ms"], strippedProperty = aProperty.split('-').join(' '), diff --git a/AppKit/CPControl.j b/AppKit/CPControl.j index 26aaca67d..bdb657ce2 100644 --- a/AppKit/CPControl.j +++ b/AppKit/CPControl.j @@ -25,7 +25,7 @@ @import "CPFont.j" @import "CPShadow.j" -@import "CPView.j" +@import "CPText.j" @import "CPKeyValueBinding.j" @import "CPTrackingArea.j" diff --git a/AppKit/CPEvent.j b/AppKit/CPEvent.j index 66837649d..42dbad35b 100644 --- a/AppKit/CPEvent.j +++ b/AppKit/CPEvent.j @@ -28,7 +28,6 @@ @import "CPCompatibility.j" @import "CGGeometry.j" -@import "CPText.j" @import "CPTrackingArea.j" @class CPTextField @@ -36,6 +35,9 @@ @class CPGraphicsContext @global CPApp +@global CPNewlineCharacter +@global CPCarriageReturnCharacter +@global CPEnterCharacter @typedef DOMEvent @typedef CPEventType @@ -83,7 +85,7 @@ var _CPEventPeriodicEventPeriod = 0, BOOL _suppressCappuccinoCut; BOOL _suppressCappuccinoPaste; #endif - + CPTrackingArea _trackingArea; } @@ -146,7 +148,7 @@ var _CPEventPeriodicEventPeriod = 0, /*! Creates a new mouse tracking event. - + @param anEventType the event type @param aPoint the location of the cursor in the window specified by \c aWindowNumber @param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals @@ -232,7 +234,7 @@ var _CPEventPeriodicEventPeriod = 0, { if ((anEventType != CPMouseEntered) && (anEventType != CPMouseExited) && (anEventType != CPCursorUpdate)) [CPException raise:CPInternalInconsistencyException reason:"Invalid event type"]; - + if (self = [self _initWithType:anEventType]) { _location = CGPointCreateCopy(aPoint); @@ -243,7 +245,7 @@ var _CPEventPeriodicEventPeriod = 0, _trackingArea = aTrackingArea; _window = [CPApp windowWithWindowNumber:aWindowNumber]; } - + return self; } @@ -336,6 +338,14 @@ var _CPEventPeriodicEventPeriod = 0, return _type; } +/*! + Returns the subtype of the event. +*/ +- (CPEventType)subtype +{ + return _subtype; +} + /*! Returns the event's associated window. */ @@ -633,7 +643,7 @@ var _CPEventPeriodicEventPeriod = 0, { if ((_type !== CPMouseEntered) && (_type !== CPMouseExited) && (_type !== CPCursorUpdate)) [CPException raise:CPInternalInconsistencyException format:@"You can't call trackingArea for events of type %#x", _type] - + return _trackingArea; } diff --git a/AppKit/CPFont.j b/AppKit/CPFont.j index 24e8190cd..90bc28826 100644 --- a/AppKit/CPFont.j +++ b/AppKit/CPFont.j @@ -24,6 +24,7 @@ @import @import "CPView.j" +@import "CPFontDescriptor.j" CPFontDefaultSystemFontFace = @"Arial, sans-serif"; CPFontDefaultSystemFontSize = 12; @@ -433,6 +434,43 @@ following: @end +@implementation CPFont(DescriptorAdditions) + +- (id)_initWithFontDescriptor:(CPFontDescriptor)fontDescriptor +{ + var aName = [fontDescriptor objectForKey: CPFontNameAttribute] , + aSize = [fontDescriptor pointSize], + isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait, + isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait; + + return [self _initWithName:aName size:aSize bold:isBold italic:isItalic system:NO]; +} + ++ (CPFont)fontWithDescriptor:(CPFontDescriptor)fontDescriptor size:(float)aSize +{ + var aName = [fontDescriptor objectForKey: CPFontNameAttribute], + isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait, + isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait; + + return [self _fontWithName:aName size:aSize || [fontDescriptor pointSize] bold:isBold italic:isItalic]; +} + +- (CPFontDescriptor)fontDescriptor +{ + var traits = 0; + + if ([self isBold]) + traits |= CPFontBoldTrait; + + if ([self isItalic]) + traits |= CPFontItalicTrait; + + return [[CPFontDescriptor fontDescriptorWithName:_name size:_size] fontDescriptorWithSymbolicTraits:traits]; +} + +@end + + var CPFontNameKey = @"CPFontNameKey", CPFontSizeKey = @"CPFontSizeKey", CPFontIsBoldKey = @"CPFontIsBoldKey", diff --git a/AppKit/CPFontManager.j b/AppKit/CPFontManager.j index b470025ba..ffffe6464 100644 --- a/AppKit/CPFontManager.j +++ b/AppKit/CPFontManager.j @@ -22,9 +22,12 @@ @import +@import "CPControl.j" @import "CPFont.j" +@import "CPFontDescriptor.j" @global CPApp +@class CPFontPanel CPItalicFontMask = 1 << 0; CPBoldFontMask = 1 << 1; @@ -41,7 +44,20 @@ CPUnitalicFontMask = 1 << 24; var CPSharedFontManager = nil, - CPFontManagerFactory = Nil; + CPFontManagerFactory = nil, + CPFontPanelFactory = nil; + +/* + modifyFont: sender's tag +*/ +CPNoFontChangeAction = 0; +CPViaPanelFontAction = 1; +CPAddTraitFontAction = 2; +CPSizeUpFontAction = 3; +CPSizeDownFontAction = 4; +CPHeavierFontAction = 5; +CPLighterFontAction = 6; +CPRemoveTraitFontAction = 7; /*! @ingroup appkit @@ -59,6 +75,8 @@ var CPSharedFontManager = nil, BOOL _multiple @accessors(getter=isMultiple, setter=setMultiple:); CPDictionary _activeChange; + + unsigned _fontAction; } // Getting the Shared Font Manager @@ -83,6 +101,15 @@ var CPSharedFontManager = nil, { CPFontManagerFactory = aClass; } +/*! + Sets the class that will be used to create the application's + Font panel. +*/ ++ (void)setFontPanelFactory:(Class)aClass +{ + CPFontPanelFactory = aClass; +} + - (id)init { @@ -210,6 +237,7 @@ var CPSharedFontManager = nil, { var tag = [sender tag]; _activeChange = tag === nil ? @{} : @{ @"addTraits": tag }; + _fontAction = CPAddTraitFontAction; [self sendAction]; } @@ -219,6 +247,185 @@ var CPSharedFontManager = nil, return [CPApp sendAction:_action to:_target from:self]; } + +/*! + This method open the font panel, create it if necessary. + @param sender The object that sent the message. +*/ +- (CPFontPanel)fontPanel:(BOOL)createIt +{ + var panel = nil, + panelExists = [CPFontPanelFactory sharedFontPanelExists]; + + if ((panelExists) || (!panelExists && createIt)) + panel = [CPFontPanelFactory sharedFontPanel]; + + return panel; +} + +/*! + Convert a font to have the specified Font traits. The font is unchanged expect for the specified Font traits. + Using CPUnboldFontMask or CPUnitalicFontMask will respectively remove Bold and Italic traits. + @param aFont The font to convert. + @param fontTrait The new font traits mask. + @result The converted font or \c aFont if the conversion failed. +*/ +- (CPFont)convertFont:(CPFont)aFont toHaveTrait:(CPFontTraitMask)fontTrait +{ + var attributes = [[[aFont fontDescriptor] fontAttributes] copy], + symbolicTrait = [[aFont fontDescriptor] symbolicTraits]; + + if (fontTrait & CPBoldFontMask) + symbolicTrait |= CPFontBoldTrait; + + if (fontTrait & CPItalicFontMask) + symbolicTrait |= CPFontItalicTrait; + + if (fontTrait & CPUnboldFontMask) /* FIXME: this only change CPFontSymbolicTrait what about CPFontWeightTrait */ + symbolicTrait &= ~CPFontBoldTrait; + + if (fontTrait & CPUnitalicFontMask) + symbolicTrait &= ~CPFontItalicTrait; + + if (fontTrait & CPExpandedFontMask) + symbolicTrait |= CPFontExpandedTrait; + + if (fontTrait & CPSmallCapsFontMask) + symbolicTrait |= CPFontSmallCapsTrait; + + if (![attributes containsKey:CPFontTraitsAttribute]) + [attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait] + forKey:CPFontSymbolicTrait] + forKey:CPFontTraitsAttribute]; + else + [[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait] + forKey:CPFontSymbolicTrait]; + + return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0]; +} + +/*! + Convert a font to not have the specified Font traits. The font is unchanged expect for the specified Font traits. + @param aFont The font to convert. + @param fontTrait The font traits mask to remove. + @result The converted font or \c aFont if the conversion failed. +*/ +- (CPFont)convertFont:(CPFont)aFont toNotHaveTrait:(CPFontTraitMask)fontTrait +{ + var attributes = [[[aFont fontDescriptor] fontAttributes] copy], + symbolicTrait = [[aFont fontDescriptor] symbolicTraits]; + + if ((fontTrait & CPBoldFontMask) || (fontTrait & CPUnboldFontMask)) /* FIXME: see convertFont:toHaveTrait: about CPFontWeightTrait */ + symbolicTrait &= ~CPFontBoldTrait; + + if ((fontTrait & CPItalicFontMask) || (fontTrait & CPUnitalicFontMask)) + symbolicTrait &= ~CPFontItalicTrait; + + if (fontTrait & CPExpandedFontMask) + symbolicTrait &= ~CPFontExpandedTrait; + + if (fontTrait & CPSmallCapsFontMask) + symbolicTrait &= ~CPFontSmallCapsTrait; + + if (![attributes containsKey:CPFontTraitsAttribute]) + [attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait] + forKey:CPFontSymbolicTrait] + forKey:CPFontTraitsAttribute]; + else + [[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait] + forKey:CPFontSymbolicTrait]; + + return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0]; +} + +/*! + Convert a font to have specified size. The font is unchanged expect for the specified size. + @param aFont The font to convert. + @param aSize The new font size. + @result The converted font or \c aFont if the conversion failed. +*/ +- (CPFont)convertFont:(CPFont)aFont toSize:(float)aSize +{ + var descriptor = [aFont fontDescriptor]; + + return [[aFont class] fontWithDescriptor: descriptor size:aSize] +} + +- (void)orderFrontFontPanel:(id)sender +{ + [[self fontPanel:YES] orderFront:sender]; +} + +- (void)modifyFont:(id)sender +{ + _fontAction = [sender tag]; + [self sendAction]; + + if (_selectedFont) + [self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO]; +} + +/*! + This method causes the receiver to send its action message. + @param sender The object that sent the message. (a Font panel) +*/ +- (void)modifyFontViaPanel:(id)sender +{ + _fontAction = CPViaPanelFontAction; + if (_selectedFont) + [self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO]; + + [self sendAction]; +} + +/*! + Convert a font according to current font changes, provided by the object that initiated the font change. + @param aFont The font to convert. + @result The converted font or \c aFont if the conversion failed. +*/ +- (CPFont)convertFont:(CPFont)aFont +{ + var newFont = nil; + switch (_fontAction) + { + case CPNoFontChangeAction: + newFont = aFont; + break; + + case CPViaPanelFontAction: + newFont = [[self fontPanel:NO] panelConvertFont:aFont]; + break; + + case CPAddTraitFontAction: + newFont = aFont; + if (!_activeChange) + break; + + var addTraits = [_activeChange valueForKey:@"addTraits"]; + + if (addTraits) + newFont = [self convertFont:aFont toHaveTrait:addTraits]; + break; + + case CPSizeUpFontAction: + newFont = [self convertFont:aFont toSize:[aFont size] + 1.0]; /* any limit ? */ + break; + + case CPSizeDownFontAction: + if ([aFont size] > 1) + newFont = [self convertFont:aFont toSize:[aFont size] - 1.0]; + /* else CPBeep() :-p */ + break; + + default: + CPLog.trace(@"-[" + [self className] + " " + _cmd + "] unsupported font action: " + _fontAction + " aFont unchanged"); + newFont = aFont; + break; + } + + return newFont; +} + @end var _CPFontDetectSpan, diff --git a/AppKit/CPGradient.j b/AppKit/CPGradient.j index dfa7a3437..93b08b2d2 100644 --- a/AppKit/CPGradient.j +++ b/AppKit/CPGradient.j @@ -70,11 +70,12 @@ CPGradientDrawsAfterEndingLocation = kCGGradientDrawsAfterEndLocation; { if (self = [super init]) { - var cgColors = [], - count = [someColors count], - colorSpace = [aColorSpace CGColorSpace] || CGColorSpaceCreateDeviceRGB; - for (var i = 0; i < count; i++) - cgColors.push(CGColorCreate(colorSpace, [someColors[i] components])); + var colorSpace = [aColorSpace CGColorSpace] || CGColorSpaceCreateDeviceRGB, + cgColors = [someColors arrayByApplyingBlock:function(color) + { + return CGColorCreate(colorSpace, [color components]) + }]; + _gradient = CGGradientCreateWithColors(colorSpace, cgColors, someLocations); } diff --git a/AppKit/CPGraphics.j b/AppKit/CPGraphics.j index 380bea393..55dbb9f73 100644 --- a/AppKit/CPGraphics.j +++ b/AppKit/CPGraphics.j @@ -43,10 +43,10 @@ function CPDrawTiledRects( if (sides.length != grays.length) [CPException raise:CPInvalidArgumentException reason:@"sides (length: " + sides.length + ") and grays (length: " + grays.length + ") must have the same length."]; - var colors = []; - - for (var i = 0; i < grays.length; ++i) - colors.push([CPColor colorWithCalibratedWhite:grays[i] alpha:1.0]); + var colors = [grays arrayByApplyingBlock:function(gray) + { + return [CPColor colorWithCalibratedWhite:gray alpha:1.0]; + }]; return CPDrawColorTiledRects(boundsRect, clipRect, sides, colors); } diff --git a/AppKit/CPObjectController.j b/AppKit/CPObjectController.j index 6cac9a80e..6bd5102db 100644 --- a/AppKit/CPObjectController.j +++ b/AppKit/CPObjectController.j @@ -27,6 +27,9 @@ @import "CPController.j" @import "CPKeyValueBinding.j" +@class _CPManagedProxy +@class CPPredicate; + /*! @class @@ -47,6 +50,9 @@ BOOL _isEditable; BOOL _automaticallyPreparesContent; + BOOL _usesLazyFetching @accessors(getter=usesLazyFetching, setter=setUsesLazyFetching:); + BOOL _isUsingManagedProxy; + _CPManagedProxy _managedProxy; CPCountedSet _observedKeys; } @@ -179,6 +185,52 @@ return _automaticallyPreparesContent; } +/*! + Sets the entity name the controller handles. + + @param CPString newEntityName - The new entity name. +*/ +- (void)setEntityName:(CPString)newEntityName +{ + if (!_managedProxy) + { + _managedProxy = [[_CPManagedProxy alloc] init]; + _isUsingManagedProxy = YES; + } + + [_managedProxy setEntityName:newEntityName]; +} + +/*! + Returns the entity name. + + @return CPString - The name of the entity. +*/ +- (CPString)entityName +{ + return [_managedProxy entityName]; +} + +/*! + Sets the predicate used to fetch content. + + @param CPPredicate newPredicate - The fetch predicate. +*/ +- (void)setFetchPredicate:(CPPredicate)newPredicate +{ + [_managedProxy setFetchPredicate:newPredicate]; +} + +/*! + Returns the fetch predicate. + + @return CPPredicate - The predicate used to fetch content. +*/ +- (CPPredicate)fetchPredicate +{ + return [_managedProxy fetchPredicate]; +} + /*! Overridden by a subclass that require control over the creation of new objects. */ @@ -189,6 +241,7 @@ /*! Sets the object class when creating new objects. + @param Class - the class of new objects that will be created. */ - (void)setObjectClass:(Class)aClass @@ -363,7 +416,10 @@ var CPObjectControllerContentKey = @"CPObjectControllerContentKey", CPObjectControllerObjectClassNameKey = @"CPObjectControllerObjectClassNameKey", CPObjectControllerIsEditableKey = @"CPObjectControllerIsEditableKey", - CPObjectControllerAutomaticallyPreparesContentKey = @"CPObjectControllerAutomaticallyPreparesContentKey"; + CPObjectControllerAutomaticallyPreparesContentKey = @"CPObjectControllerAutomaticallyPreparesContentKey", + CPObjectControllerUsesLazyFetchingKey = @"CPObjectControllerUsesLazyFetchingKey", + CPObjectControllerIsUsingManagedProxyKey = @"CPObjectControllerIsUsingManagedProxyKey", + CPObjectControllerManagedProxyKey = @"CPObjectControllerManagedProxyKey"; @implementation CPObjectController (CPCoding) @@ -374,12 +430,18 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo if (self) { var objectClassName = [aCoder decodeObjectForKey:CPObjectControllerObjectClassNameKey], - objectClass = CPClassFromString(objectClassName); + objectClass = CPClassFromString(objectClassName), + content = [aCoder decodeObjectForKey:CPObjectControllerContentKey]; [self setObjectClass:objectClass || [CPMutableDictionary class]]; [self setEditable:[aCoder decodeBoolForKey:CPObjectControllerIsEditableKey]]; [self setAutomaticallyPreparesContent:[aCoder decodeBoolForKey:CPObjectControllerAutomaticallyPreparesContentKey]]; - [self setContent:[aCoder decodeObjectForKey:CPObjectControllerContentKey]]; + [self setUsesLazyFetching:[aCoder decodeBoolForKey:CPObjectControllerUsesLazyFetchingKey]]; + _isUsingManagedProxy = [aCoder decodeBoolForKey:CPObjectControllerIsUsingManagedProxyKey]; + _managedProxy = [aCoder decodeObjectForKey:CPObjectControllerManagedProxyKey]; + + if (content != nil) + [self setContent:content]; _observedKeys = [[CPCountedSet alloc] init]; } @@ -398,6 +460,11 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo [aCoder encodeBool:[self isEditable] forKey:CPObjectControllerIsEditableKey]; [aCoder encodeBool:[self automaticallyPreparesContent] forKey:CPObjectControllerAutomaticallyPreparesContentKey]; + [aCoder encodeBool:[self usesLazyFetching] forKey:CPObjectControllerUsesLazyFetchingKey]; + [aCoder encodeBool:_isUsingManagedProxy forKey:CPObjectControllerIsUsingManagedProxyKey]; + + if (_managedProxy) + [aCoder encodeObject:_managedProxy forKey:CPObjectControllerManagedProxyKey]; } - (void)awakeFromCib @@ -825,3 +892,38 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo } @end + + +@implementation _CPManagedProxy : CPObject +{ + CPString _entityName @accessors(getter=entityName, setter=setEntityName:); + CPPredicate _fetchPredicate @accessors(getter=fetchPredicate, setter=setFetchPredicate:); +} + +@end + +var CPManagedProxyEntityNameKey = @"CPManagedProxyEntityNameKey", + CPManagedProxyFetchPredicateKey = @"CPManagedProxyFetchPredicateKey"; + +@implementation _CPManagedProxy (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + if (self) + { + [self setEntityName:[aCoder decodeObjectForKey:CPManagedProxyEntityNameKey]]; + [self setFetchPredicate:[aCoder decodeObjectForKey:CPManagedProxyFetchPredicateKey]]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeObject:[self entityName] forKey:CPManagedProxyEntityNameKey]; + [aCoder encodeObject:[self fetchPredicate] forKey:CPManagedProxyFetchPredicateKey]; +} + +@end \ No newline at end of file diff --git a/AppKit/CPPasteboard.j b/AppKit/CPPasteboard.j index 5372a5bef..4e75db8eb 100644 --- a/AppKit/CPPasteboard.j +++ b/AppKit/CPPasteboard.j @@ -44,6 +44,7 @@ CPStringPboardType = @"CPStringPboardType"; CPURLPboardType = @"CPURLPboardType"; CPImagesPboardType = @"CPImagesPboardType"; CPVideosPboardType = @"CPVideosPboardType"; +CPRTFPboardType = @"CPRTFPboardType"; UTF8PboardType = @"public.utf8-plain-text"; @@ -51,8 +52,7 @@ UTF8PboardType = @"public.utf8-plain-text"; CPImagePboardType = @"CPImagePboardType"; -var CPPasteboards = nil, - supportsNativePasteboard = NO; +var CPPasteboards = nil; /*! @ingroup appkit @@ -68,8 +68,6 @@ var CPPasteboards = nil, unsigned _changeCount; CPString _stateUID; - - CPWebScriptObject _nativePasteboard; } /* @@ -83,9 +81,6 @@ var CPPasteboards = nil, [self setVersion:1.0]; CPPasteboards = @{}; - - if (typeof window.cpPasteboardWithName !== "undefined") - supportsNativePasteboard = YES; } /*! @@ -128,12 +123,6 @@ var CPPasteboards = nil, _provided = @{}; _changeCount = 0; - - if (supportsNativePasteboard) - { - _nativePasteboard = window.cpPasteboardWithName(aName); - [self _synchronizePasteboard]; - } } return self; @@ -163,15 +152,6 @@ var CPPasteboards = nil, [_owners setObject:anOwner forKey:type]; } - if (_nativePasteboard) - { - var nativeTypes = [types copy]; - if ([types containsObject:CPStringPboardType]) - nativeTypes.push(UTF8PboardType); - - _nativePasteboard.addTypes_(nativeTypes); - } - return ++_changeCount; } @@ -182,12 +162,6 @@ var CPPasteboards = nil, @return the pasteboard's change count */ - (unsigned)declareTypes:(CPArray)types owner:(id)anOwner -{ - [self _declareTypes:types owner:anOwner updateNativePasteboard:YES]; -} - -/*! @ignore */ -- (unsigned)_declareTypes:(CPArray)types owner:(id)anOwner updateNativePasteboard:(BOOL)shouldUpdate { [_types setArray:types]; @@ -201,16 +175,6 @@ var CPPasteboards = nil, [_owners setObject:anOwner forKey:_types[count]]; } - if (_nativePasteboard && shouldUpdate) - { - var nativeTypes = [types copy]; - if ([types containsObject:CPStringPboardType]) - nativeTypes.push(UTF8PboardType); - - _nativePasteboard.declareTypes_(nativeTypes); - _changeCount = _nativePasteboard.changeCount(); - } - return ++_changeCount; } @@ -273,7 +237,6 @@ var CPPasteboards = nil, */ - (CPArray)types { - [self _synchronizePasteboard]; return _types; } @@ -312,36 +275,6 @@ var CPPasteboards = nil, return nil; } -- (void)_synchronizePasteboard -{ - if (_nativePasteboard && _nativePasteboard.changeCount() > _changeCount) - { - var nativeTypes = [_nativePasteboard.types() copy]; - if ([nativeTypes containsObject:UTF8PboardType]) - nativeTypes.push(CPStringPboardType); - - [self _declareTypes:nativeTypes owner:self updateNativePasteboard:NO]; - - _changeCount = _nativePasteboard.changeCount(); - } -} - -/*! @ignore - method provided for integration with native pasteboard -*/ -- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType -{ - if (aType === CPStringPboardType) - { - var string = _nativePasteboard.stringForType_(UTF8PboardType); - - [self setString:string forType:CPStringPboardType]; - [self setString:string forType:UTF8PboardType]; - } - else - [self setString:_nativePasteboard.stringForType_(aType) forType:aType]; -} - /*! Returns the property list for the specified data type @param aType the requested data type diff --git a/AppKit/CPPopUpButton.j b/AppKit/CPPopUpButton.j index 09aecb6fe..12f7b8638 100644 --- a/AppKit/CPPopUpButton.j +++ b/AppKit/CPPopUpButton.j @@ -361,15 +361,10 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); */ - (CPArray)itemTitles { - var titles = [], - items = [self itemArray], - index = 0, - count = [items count]; - - for (; index < count; ++index) - titles.push([items[index] title]); - - return titles; + return [[self itemArray] arrayByApplyingBlock:function(item) + { + return [item title]; + }]; } /*! @@ -852,7 +847,17 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); { var count = [aValue count], options = [_info objectForKey:CPOptionsKey], - offset = [self _getInsertNullOffset]; + offset = [self _getInsertNullOffset], + selectedBindingInfo = [_source infoForBinding:CPSelectedObjectBinding], + selectedObject = nil; + + if (selectedBindingInfo) + { + var destination = [selectedBindingInfo objectForKey:CPObservedObjectKey], + keyPath = [selectedBindingInfo objectForKey:CPObservedKeyPathKey]; + + selectedObject = [destination valueForKeyPath:keyPath]; + } if (count + offset != [_source numberOfItems]) { @@ -863,9 +868,19 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down"); for (var i = 0; i < count; i++) { - var item = [[CPMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:nil]; - [self _setValue:[aValue objectAtIndex:i] forItem:item withOptions:options]; + var item = [[CPMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:nil], + itemValue = [aValue objectAtIndex:i]; + + [self _setValue:itemValue forItem:item withOptions:options]; [_source addItem:item]; + + // Select this item if it is the one selected by the selected object binding + // This is needed if the selected object binding is set before the items + // from the content binding + if (itemValue === selectedObject) + { + [_source setSelectedIndex:[_source numberOfItems] - 1]; + } } } else diff --git a/AppKit/CPRuleEditor/CPPredicateEditorRowTemplate.j b/AppKit/CPRuleEditor/CPPredicateEditorRowTemplate.j index 0ce2e2f0e..70768f15e 100644 --- a/AppKit/CPRuleEditor/CPPredicateEditorRowTemplate.j +++ b/AppKit/CPRuleEditor/CPPredicateEditorRowTemplate.j @@ -471,9 +471,11 @@ CPTransformableAttributeType = 1800; var value; if (attributeType >= CPInteger16AttributeType && attributeType <= CPFloatAttributeType) - value = [aView intValue]; + value = [aView floatValue]; else if (attributeType == CPBooleanAttributeType) value = [aView state]; + else if (attributeType == CPDateAttributeType) + value = [aView dateValue]; else value = [aView stringValue]; diff --git a/AppKit/CPRuleEditor/CPRuleEditor.j b/AppKit/CPRuleEditor/CPRuleEditor.j index 141e06d63..ac3bcd52c 100644 --- a/AppKit/CPRuleEditor/CPRuleEditor.j +++ b/AppKit/CPRuleEditor/CPRuleEditor.j @@ -146,7 +146,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType", @"slice-top-border-color": [CPNull null], @"slice-bottom-border-color": [CPNull null], @"slice-last-bottom-border-color": [CPNull null], - @"font": [CPNull null], + @"font": [CPFont systemFontOfSize:12], @"font-color": [CPNull null], @"add-image": [CPNull null], @"remove-image": [CPNull null], diff --git a/AppKit/CPRuleEditor/_CPRuleEditorViewSliceRow.j b/AppKit/CPRuleEditor/_CPRuleEditorViewSliceRow.j index 80509dd16..c7d1524ff 100644 --- a/AppKit/CPRuleEditor/_CPRuleEditorViewSliceRow.j +++ b/AppKit/CPRuleEditor/_CPRuleEditorViewSliceRow.j @@ -366,11 +366,12 @@ count = [_ruleOptionViews count], sliceFrame = [self frame], image = [_ruleEditor _imageAdd], + imageSize = image ? [image size] : CGSizeMake(0,0), - buttonFrame = CGRectMake(CGRectGetWidth(sliceFrame) - [image size].width - [self _rowButtonsRightHorizontalPadding], ([_ruleEditor rowHeight] - [image size].height) / 2 - 1, [image size].width, [image size].height); + buttonFrame = CGRectMake(CGRectGetWidth(sliceFrame) - imageSize.width - [self _rowButtonsRightHorizontalPadding], ([_ruleEditor rowHeight] - imageSize.height) / 2 - 1, imageSize.width, imageSize.height); [_addButton setFrame:buttonFrame]; - buttonFrame.origin.x -= [image size].width + [self _rowButtonsInterviewHorizontalPadding]; + buttonFrame.origin.x -= imageSize.width + [self _rowButtonsInterviewHorizontalPadding]; [_subtractButton setFrame:buttonFrame]; if (widthChanged) diff --git a/AppKit/CPSearchField.j b/AppKit/CPSearchField.j index e6c12bfb4..ebdc86496 100644 --- a/AppKit/CPSearchField.j +++ b/AppKit/CPSearchField.j @@ -34,8 +34,7 @@ CPSearchFieldRecentsMenuItemTag = 1001; CPSearchFieldClearRecentsMenuItemTag = 1002; CPSearchFieldNoRecentsMenuItemTag = 1003; -var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotification", - RECENT_SEARCH_PREFIX = @" "; +var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotification"; /*! @ingroup appkit @@ -629,10 +628,10 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat for (var recentIndex = 0; recentIndex < countOfRecents; ++recentIndex) { - // RECENT_SEARCH_PREFIX is a hack until CPMenuItem -setIndentationLevel works - var recentItem = [[CPMenuItem alloc] initWithTitle:RECENT_SEARCH_PREFIX + [_recentSearches objectAtIndex:recentIndex] + var recentItem = [[CPMenuItem alloc] initWithTitle:[_recentSearches objectAtIndex:recentIndex] action:itemAction keyEquivalent:[item keyEquivalent]]; + [recentItem setIndentationLevel:1]; [item setTarget:self]; [menu addItem:recentItem]; } @@ -718,7 +717,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat - (void)_searchFieldSearch:(id)sender { - var searchString = [[sender title] substringFromIndex:[RECENT_SEARCH_PREFIX length]]; + var searchString = [sender title]; if ([sender tag] != CPSearchFieldRecentsMenuItemTag) [self _addStringToRecentSearches:searchString]; diff --git a/AppKit/CPStringDrawing.j b/AppKit/CPStringDrawing.j index 6c3ce7ce0..cbffafb85 100644 --- a/AppKit/CPStringDrawing.j +++ b/AppKit/CPStringDrawing.j @@ -24,9 +24,15 @@ @import "CGGeometry.j" @import "CPPlatformString.j" +@import "CPFont.j" +@import "CPCompatibility.j" -var CPStringSizeWithFontInWidthCache = {}; +var CPStringSizeWithFontInWidthCache = [], + CPStringSizeWithFontHeightCache = [], + CPStringSizeMeasuringContext, + CPStringSizeIsCanvasSizingInvalid, + CPStringSizeDidTestCanvasSizingValid; CPStringSizeCachingEnabled = YES; @@ -53,20 +59,71 @@ CPStringSizeCachingEnabled = YES; return [self sizeWithFont:aFont inWidth:NULL]; } +- (void) _initializeStringSizing +{ +#if PLATFORM(DOM) + CPStringSizeIsCanvasSizingInvalid = YES; + + if (CPFeatureIsCompatible(CPHTMLCanvasFeature)) + { + var aFont = [CPFont systemFontOfSize:12.0]; + + if (!CPStringSizeMeasuringContext) + CPStringSizeMeasuringContext = CGBitmapGraphicsContextCreate(); + + CPStringSizeMeasuringContext.font = [aFont cssString]; + var teststring = "0123456879abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.-()"; + CPStringSizeIsCanvasSizingInvalid = ABS([CPPlatformString sizeOfString:teststring withFont:aFont forWidth:0].width - CPStringSizeMeasuringContext.measureText(teststring).width) > 2; + } +#endif +} + - (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth { + var size; + +#if PLATFORM(DOM) if (!CPStringSizeCachingEnabled) return [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth]; - var cacheKey = self + [aFont cssString] + aWidth, - size = CPStringSizeWithFontInWidthCache[cacheKey]; + var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self]; - if (size === undefined) + if (sizeCacheForFont === undefined) + sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = []; + + var cssString = [aFont cssString], + cacheKey = cssString + '_' + aWidth; + + size = sizeCacheForFont[cacheKey]; + + if (size !== undefined && sizeCacheForFont.hasOwnProperty(cacheKey)) + return CGSizeMakeCopy(size); + + if (CPStringSizeDidTestCanvasSizingValid === undefined) { - size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth]; - CPStringSizeWithFontInWidthCache[cacheKey] = size; + [self _initializeStringSizing]; + CPStringSizeDidTestCanvasSizingValid = YES; } + if (CPStringSizeIsCanvasSizingInvalid || aWidth > 0) + size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth]; + else + { + if (CPStringSizeMeasuringContext.font !== cssString) + CPStringSizeMeasuringContext.font = cssString; + + var fontHeight = CPStringSizeWithFontHeightCache[cssString]; + + if (fontHeight === undefined) + fontHeight = CPStringSizeWithFontHeightCache[cssString] = [aFont defaultLineHeightForFont]; + + size = CGSizeMake(CPStringSizeMeasuringContext.measureText(self).width, fontHeight); + } + + sizeCacheForFont[cacheKey] = size; +#else + size = CGSizeMake(0, 0); +#endif return CGSizeMakeCopy(size); } diff --git a/AppKit/CPTableHeaderView.j b/AppKit/CPTableHeaderView.j index 58f5cb152..d092a64ef 100644 --- a/AppKit/CPTableHeaderView.j +++ b/AppKit/CPTableHeaderView.j @@ -417,8 +417,7 @@ var CPTableHeaderViewResizeZone = 3.0, } else if (_isDragging) { - // Disable autoscrolling until it behaves correctly. - //[self _autoscroll:theEvent localLocation:currentLocation]; + [self _autoscroll:theEvent localLocation:currentLocation]; [self _dragTableColumn:_activeColumn to:currentLocation]; } else // tracking a press, could become a drag @@ -466,9 +465,12 @@ var CPTableHeaderViewResizeZone = 3.0, - (void)updateTrackingAreas { [self removeAllTrackingAreas]; - + var options = CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow; - + + if (!_tableView) + return; + for (var i = 0; i < _tableView._tableColumns.length; i++) [self addTrackingArea:[[CPTrackingArea alloc] initWithRect:[self _cursorRectForColumn:i] options:options diff --git a/AppKit/CPTableView.j b/AppKit/CPTableView.j index 0762c2b9d..04f298f37 100644 --- a/AppKit/CPTableView.j +++ b/AppKit/CPTableView.j @@ -644,12 +644,8 @@ NOT YET IMPLEMENTED }]; } -// Reloads the views AND the data -- (void)_reloadDataViews +- (void)_setupReload { - //if (!_dataSource) - // return; - _reloadAllRows = YES; _objectValues = { }; _cachedRowHeights = []; @@ -661,11 +657,24 @@ NOT YET IMPLEMENTED // This updates the size too. [self noteNumberOfRowsChanged]; +} +// Reloads the views AND the data +- (void)_reloadDataViews +{ + [self _setupReload]; [self setNeedsLayout]; [self setNeedsDisplay:YES]; } +// Reloads the views AND the data +- (void)_reloadDataViewsSynchronously +{ + [self _setupReload]; + [self layout]; + [self display]; +} + //Target-action Behavior /*! Sets the message sent to the target when the user double-clicks an @@ -4680,7 +4689,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad { if ([self _sendDelegateShouldEditTableColumn:column row:rowIndex]) { - [self editColumn:columnIndex row:rowIndex withEvent:nil select:YES]; + [self editColumn:columnIndex row:rowIndex withEvent:[CPApp currentEvent] select:YES]; return; } } @@ -5088,14 +5097,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad } } else if (!_isViewBased && [aView isKindOfClass:[CPControl class]] && ![aView isKindOfClass:[CPTextField class]]) - { - [self getColumn:@ref(column) row:@ref(row) forView:aView]; - - _editingColumn = column; - _editingRow = row; - [aView addObserver:self forKeyPath:@"objectValue" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:"editing"]; - } return aView; } @@ -5227,7 +5229,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad if (!_isViewBased) { [self _setEditingState:NO forView:textField]; - [self _commitDataViewObjectValue:textField]; + [self _commitDataViewObjectValue:textField forColumn:_editingColumn andRow:_editingRow]; } else [textField setBezeled:NO]; @@ -5277,19 +5279,19 @@ Your delegate can implement this method to avoid subclassing the tableview to ad The action for any dataview that supports editing. This will only be called when the value was changed. The table view becomes the first responder after user is done editing a dataview. */ -- (void)_commitDataViewObjectValue:(id)aDataView +- (void)_commitDataViewObjectValue:(id)aDataView forColumn:(CPInteger)column andRow:(CPInteger)row { - var editingTableColumn = _tableColumns[_editingColumn]; + var editingTableColumn = _tableColumns[column]; if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_) - [_dataSource tableView:self setObjectValue:[aDataView objectValue] forTableColumn:editingTableColumn row:_editingRow]; + [_dataSource tableView:self setObjectValue:[aDataView objectValue] forTableColumn:editingTableColumn row:row]; // Allow the column binding to do a reverse set. Note that we do this even if the data source method above // is implemented. - [editingTableColumn _reverseSetDataView:aDataView forRow:_editingRow]; + [editingTableColumn _reverseSetDataView:aDataView forRow:row]; - if (_editingRow !== CPNotFound && _editingColumn !== CPNotFound) - [self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:_editingRow] columnIndexes:[CPIndexSet indexSetWithIndex:_editingColumn]]; + if (row !== CPNotFound && column !== CPNotFound) + [self _reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:row] columnIndexes:[CPIndexSet indexSetWithIndex:column]]; } - (void)_setEditingState:(BOOL)editingState forView:(CPView)aView @@ -5303,6 +5305,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad if ([aView isKindOfClass:[CPTextField class]]) [aView setBezeled:editingState]; } + /*! Edits the dataview at a given row and column. This method is usually invoked automatically and should rarely be invoked directly The row at supplied rowIndex must be selected otherwise an exception is thrown. @@ -5317,11 +5320,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad if (![self isRowSelected:rowIndex]) [[CPException exceptionWithName:@"Error" reason:@"Attempt to edit row " + rowIndex + " when not selected." userInfo:nil] raise]; - [self reloadData]; - - // Process all events immediately to make sure table data views are reloaded. - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; - + [self _reloadDataViewsSynchronously]; [self scrollRowToVisible:rowIndex]; [self scrollColumnToVisible:columnIndex]; @@ -5342,9 +5341,12 @@ Your delegate can implement this method to avoid subclassing the tableview to ad if (context === "editing" && [object superview] === self) { [object removeObserver:self forKeyPath:keyPath]; - [self _commitDataViewObjectValue:object]; - _editingRow = CPNotFound; - _editingColumn = CPNotFound; + + var row, + column; + + [self getColumn:@ref(column) row:@ref(row) forView:object]; + [self _commitDataViewObjectValue:object forColumn:column andRow:row]; } } diff --git a/AppKit/CPText.j b/AppKit/CPText.j index bfbc25594..da3e47a8b 100644 --- a/AppKit/CPText.j +++ b/AppKit/CPText.j @@ -5,6 +5,13 @@ * Created by Alexander Ljungberg. * Copyright 2010, WireLoad, LLC. * + * additions from + * + * Daniel Boehringer on 8/02/2014. + * Copyright Daniel Boehringer on 8/02/2014. + * + * + * * 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 @@ -20,6 +27,27 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +@import + +@import "CPPasteboard.j" +@import "CPView.j" + +@global CPStringPboardType +@class CPAttributedString +@class _CPRTFParser + +@protocol CPTextDelegate + +- (BOOL)textShouldBeginEditing:(CPText)aTextObject; +- (BOOL)textShouldEndEditing:(CPText)aTextObject; +- (void)textDidBeginEditing:(CPNotification)aNotification; +- (void)textDidChange:(CPNotification)aNotification; +- (void)textDidEndEditing:(CPNotification)aNotification; + +@end + +CPParagraphSeparatorCharacter = 0x2029; +CPLineSeparatorCharacter = 0x2028; CPEnterCharacter = "\u0003"; CPBackspaceCharacter = "\u0008"; CPTabCharacter = "\u0009"; @@ -29,6 +57,7 @@ CPCarriageReturnCharacter = "\u000d"; CPBackTabCharacter = "\u0019"; CPDeleteCharacter = "\u007f"; +@typedef CPTextMovement CPIllegalTextMovement = 0; CPOtherTextMovement = 0; CPReturnTextMovement = 16; @@ -46,8 +75,274 @@ CPWritingDirectionLeftToRight = 0; CPWritingDirectionRightToLeft = 1; @typedef CPTextAlignment -CPLeftTextAlignment = 0; -CPRightTextAlignment = 1; -CPCenterTextAlignment = 2; -CPJustifiedTextAlignment = 3; -CPNaturalTextAlignment = 4; \ No newline at end of file +CPLeftTextAlignment = 0; +CPRightTextAlignment = 1; +CPCenterTextAlignment = 2; +CPJustifiedTextAlignment = 3; +CPNaturalTextAlignment = 4; + +/* + CPText notifications +*/ +CPTextDidBeginEditingNotification = @"CPTextDidBeginEditingNotification"; +CPTextDidChangeNotification = @"CPTextDidChangeNotification"; +CPTextDidEndEditingNotification = @"CPTextDidEndEditingNotification"; + +/* + CPTextView Notifications +*/ +CPTextViewDidChangeSelectionNotification = @"CPTextViewDidChangeSelectionNotification"; +CPTextViewDidChangeTypingAttributesNotification = @"CPTextViewDidChangeTypingAttributesNotification"; + +/* + FIXME: move these to CPAttributed string + Make use of attributed keys in AppKit +*/ +CPFontAttributeName = @"CPFontAttributeName"; +CPForegroundColorAttributeName = @"CPForegroundColorAttributeName"; +CPBackgroundColorAttributeName = @"CPBackgroundColorAttributeName"; +CPShadowAttributeName = @"CPShadowAttributeName"; +CPUnderlineStyleAttributeName = @"CPUnderlineStyleAttributeName"; +CPSuperscriptAttributeName = @"CPSuperscriptAttributeName"; +CPBaselineOffsetAttributeName = @"CPBaselineOffsetAttributeName"; +CPAttachmentAttributeName = @"CPAttachmentAttributeName"; +CPLigatureAttributeName = @"CPLigatureAttributeName"; +CPKernAttributeName = @"CPKernAttributeName"; + +@implementation CPText : CPView +{ + BOOL _isEditable @accessors(getter=isEditable, setter=setEditable:); + BOOL _isSelectable @accessors(getter=isSelectable, setter=setSelectable:); + BOOL _isRichText @accessors(getter=isRichText, setter=setRichText:); +} + +- (void)setSelectable:(BOOL)flag +{ + [self willChangeValueForKey:@"selectable"]; + _isSelectable = flag; + [self didChangeValueForKey:@"selectable"]; + + if (!flag) + [self setEditable:flag]; +} + +- (void)setEditable:(BOOL)flag +{ + [self willChangeValueForKey:@"editable"]; + _isEditable = flag; + [self didChangeValueForKey:@"editable"]; + + if (flag) + [self setSelectable:flag]; +} + +- (void)changeFont:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)copy:(id)sender +{ + var selectedRange = [self selectedRange]; + + if (selectedRange.length < 1) + return; + + var pasteboard = [CPPasteboard generalPasteboard]; + + // put plain representation on the pasteboad unconditionally + [pasteboard declareTypes:[CPStringPboardType] owner:nil]; + [pasteboard setString:[[self stringValue] substringWithRange:selectedRange] forType:CPStringPboardType]; +} + +- (id)_stringForPasting +{ + var pasteboard = [CPPasteboard generalPasteboard], + dataForPasting = [pasteboard stringForType:CPRTFPboardType], + stringForPasting = [pasteboard stringForType:CPStringPboardType]; + + if (dataForPasting || [stringForPasting hasPrefix:"{\\rtf1\\ansi"]) + stringForPasting = [[_CPRTFParser new] parseRTF:dataForPasting ? dataForPasting : stringForPasting]; + + if (![self isRichText] && [stringForPasting isKindOfClass:[CPAttributedString class]]) + stringForPasting = stringForPasting._string; + + return stringForPasting; +} + +- (void)paste:(id)sender +{ + var stringForPasting = [self _stringForPasting]; + + if (stringForPasting) + [self insertText:stringForPasting]; +} + +- (void)copyFont:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)delete:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (CPFont)font:(CPFont)aFont +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return nil; +} + +- (BOOL)isHorizontallyResizable +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return NO; +} + +- (BOOL)isRulerVisible +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return NO; +} + +- (BOOL)isVerticallyResizable +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return NO; +} + +- (CGSize)maxSize +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return CGSizeMake(0,0); +} + +- (CGSize)minSize +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + return CGSizeMake(0,0); +} + +- (void)pasteFont:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)scrollRangeToVisible:(CPRange)aRange +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)selectedAll:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (CPRange)selectedRange +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return CPMakeRange(CPNotFound, 0); +} + +- (void)setFont:(CPFont)aFont +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setFont:(CPFont)aFont range:(CPRange)aRange +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setHorizontallyResizable:(BOOL)flag +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setMaxSize:(CGSize)aSize +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setMinSize:(CGSize)aSize +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setString:(CPString)aString +{ + [self replaceCharactersInRange:CPMakeRange(0, [[self string] length]) withString:aString]; +} + +- (void)setUsesFontPanel:(BOOL)flag +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setVerticallyResizable:(BOOL)flag +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (CPString)string +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return nil; +} + +- (void)underline:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (BOOL)usesFontPanel +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return NO; +} + +@end + +var CPTextViewIsEditableKey = @"CPTextViewIsEditableKey", + CPTextViewIsSelectableKey = @"CPTextViewIsSelectableKey", + CPTextViewIsRichTextKey = @"CPTextViewIsRichTextKey"; + +@implementation CPText (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super initWithCoder:aCoder]; + + if (self) + { + [self setSelectable:[aCoder decodeBoolForKey:CPTextViewIsSelectableKey]]; + [self setEditable:[aCoder decodeBoolForKey:CPTextViewIsEditableKey]]; + [self setRichText:[aCoder decodeBoolForKey:CPTextViewIsRichTextKey]]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [super encodeWithCoder:aCoder]; + [aCoder encodeBool:_isEditable forKey:CPTextViewIsEditableKey]; + [aCoder encodeBool:_isSelectable forKey:CPTextViewIsSelectableKey]; + [aCoder encodeBool:_isRichText forKey:CPTextViewIsRichTextKey]; +} + +@end diff --git a/AppKit/CPTextField.j b/AppKit/CPTextField.j index 09ed728f4..3e07825e5 100644 --- a/AppKit/CPTextField.j +++ b/AppKit/CPTextField.j @@ -77,25 +77,16 @@ function CPTextFieldBlurFunction(anEvent, owner, domElement, inputElement, resig var ownerWindow = [owner window]; - if (!resigning && [ownerWindow isKeyWindow]) + if (!resigning && [ownerWindow isKeyWindow] && [ownerWindow firstResponder] === owner) { /* - Browsers blur text fields when a click occurs anywhere outside the text field. That is normal for browsers, but in Cocoa the key view retains focus unless the click target accepts first responder. So if we lost focus but were not told to resign and our window is still key, restore focus, - but only if the text field is completely within the browser window. If we restore focus when it - is off screen, the entire body scrolls out of our control. + Previously we had code here which would force the input to regain focus if the input lost focus without the CPTextField having actually lost first responder status. This typically happened because the user clicked away from the input in the browser, without clicking on something that could become the first responder. In the browser, clicking outside of an input blurs it, but in Cocoa and Cappuccino it does not (unless you actually click something that will become the first responder). + + That refocusing code has now been removed because we simply prevent the default action on clicks in the browser instead, which combined with the fix in 58d5d7d7, successfully prevents unintentional focus loss in (at least) Safari 9.1.1, Chrome 51 and Safari for iOS 9.3 when you click outside of a text field. + + Now we can still lose focus unexpectedly: this is when the 'done' button is tapped on the virtual keyboard of a mobile device. In this case we actually do want to resign first responder status, because that is what the done button should do (and if we did not the keyboard would go away and then immediately come back which looks dumb and isn't what the user wanted). */ - if ([owner _isWithinUsablePlatformRect]) - { - [[CPRunLoop mainRunLoop] performBlock:function() - { - // This will prevent to jump to the focused element - var previousScrollingOrigin = [owner _scrollToVisibleRectAndReturnPreviousOrigin]; - - inputElement.focus(); - - [owner _restorePreviousScrollingOrigin:previousScrollingOrigin]; - } argument:nil order:0 modes:[CPDefaultRunLoopMode]]; - } + [ownerWindow makeFirstResponder:nil]; } CPTextFieldHandleBlur(anEvent, @ref(owner)); @@ -402,7 +393,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); else [self unsetThemeState:CPThemeStateEditable]; - // We only allow first responder status if the field is enable, and editable or selectable. + // We only allow first responder status if the field is enabled, and editable or selectable. if (!(shouldBeEditable && ![self isSelectable]) && [[self window] firstResponder] === self) [[self window] makeFirstResponder:nil]; @@ -410,6 +401,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); [self setThemeState:CPThemeStateEditable]; else [self unsetThemeState:CPThemeStateEditable]; + + [self updateTrackingAreas]; } /*! @@ -431,6 +424,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); // We only allow first responder status if the field is enabled. if (!shouldBeEnabled && [[self window] firstResponder] === self) [[self window] makeFirstResponder:nil]; + + [self updateTrackingAreas]; } /*! @@ -440,6 +435,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); - (void)setSelectable:(BOOL)aFlag { _isSelectable = aFlag; + + [self updateTrackingAreas]; } /*! @@ -838,6 +835,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); [self _resignFirstKeyResponder]; _isEditing = NO; + if ([self isEditable]) { [self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:@{"CPTextMovement": [self _currentTextMovement]}]]; @@ -997,14 +995,11 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]]; } -- (void)mouseMoved:(CPEvent)anEvent -{ - [super mouseMoved:anEvent]; - [self _updateCursorForEvent:anEvent]; -} - - (void)mouseDown:(CPEvent)anEvent { + if (![self isEnabled]) + return [[self nextResponder] mouseDown:anEvent]; + // Don't track! (ever?) if ([self isEditable] && [self isEnabled]) { @@ -1213,7 +1208,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); - (void)textDidFocus:(CPNotification)note { // this looks to prevent false propagation of notifications for other objects - if ([note object] != self) + if ([note object] !== self) return; if (_implementedDelegateMethods & CPTextFieldDelegate_controlTextDidFocus_) @@ -1259,27 +1254,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); [_delegate controlTextDidEndEditing:note]; } -- (void)_updateCursorForEvent:(CPEvent)anEvent -{ - var frame = CGRectMakeCopy([self frame]), - contentInset = [self currentValueForThemeAttribute:@"content-inset"]; - - frame = [[self superview] convertRectToBase:CGRectInsetByInset(frame, contentInset)]; - - if ([self isEnabled] && ([self isSelectable] || [self isEditable]) && CGRectContainsPoint(frame, [anEvent locationInWindow])) - { -#if PLATFORM(DOM) - self._DOMElement.style.cursor = "text"; -#endif - } - else - { -#if PLATFORM(DOM) - self._DOMElement.style.cursor = "default"; -#endif - } -} - /*! Returns the string in the text field. */ @@ -2013,7 +1987,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); frame.origin = [wind convertBaseToGlobal:frame.origin]; - // Here we restore the the previous scrolling posiition + // Here we restore the previous scrolling posiition [self _restorePreviousScrollingOrigin:previousScrollingOrigin]; return (CGRectGetMinX(frame) >= CGRectGetMinX(usableRect) && @@ -2200,4 +2174,29 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey", [_source setObjectValue:aValue]; } -@end \ No newline at end of file +@end + +@implementation CPTextField (CPTrackingArea) + +- (void)updateTrackingAreas +{ + [self removeAllTrackingAreas]; + + if ([self isEnabled] && (_isEditable || _isSelectable)) + { + var myBounds = CGRectMakeCopy([self bounds]), + contentInset = [self currentValueForThemeAttribute:@"content-inset"]; + + [self addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectInsetByInset(myBounds, contentInset) + options:CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow + owner:self + userInfo:nil]]; + } +} + +- (void)cursorUpdate:(CPEvent)anEvent +{ + [[CPCursor IBeamCursor] set]; +} + +@end diff --git a/AppKit/CPTextView/CPFontDescriptor.j b/AppKit/CPTextView/CPFontDescriptor.j new file mode 100644 index 000000000..fb4065ddd --- /dev/null +++ b/AppKit/CPTextView/CPFontDescriptor.j @@ -0,0 +1,315 @@ +/* + * CPFontDescriptor.j + * AppKit + * + * Created by Emmanuel Maillard on 07/03/10. + * Copyright Emmanuel Maillard 2010. + * + * 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 + */ + +@import +/* + Font descriptor dictionary keys +*/ + +/* + CPFontNameAttribute contains a CPString that specified the font name + (may be an name list like: 'Marker Felt, Lucida Grande, Helvetica') +*/ +CPFontNameAttribute = @"CPFontNameAttribute"; +/* + CPFontSizeAttribute contains a CPString that specified the font size + (as a float value) +*/ +CPFontSizeAttribute = @"CPFontSizeAttribute"; +/* + CPFontTraitsAttribute a CPDictionary that contains font traits keys + (CPFontSymbolicTrait or CPFontWeightTrait) +*/ +CPFontTraitsAttribute = @"CPFontTraitsAttribute"; + +// Font traits dictionary keys +/* + CPFontSymbolicTrait a CPNumber that contains CPFontFamilyClass and + typeface information flags. +*/ +CPFontSymbolicTrait = @"CPFontSymbolicTrait"; + +/* + CPFontWeightTrait + We use CPString with CSS string values for font weight + (normal | bold | bolder | lighter | 100 | 200 | 300 | 400 + | 500 | 600 | 700 | 800 | 900) + NOTE: Cocoa compatibility issue: NSFontWeightTrait are NSNumber for + font weight (from -1.0 to 1.0, 0.0 for normal weight). +*/ +CPFontWeightTrait = @"CPFontWeightTrait"; + +/* + CPFontFamilyClass +*/ +CPFontUnknownClass = 0 << 28; +CPFontOldStyleSerifsClass = 1 << 28; +CPFontTransitionalSerifsClass = 2 << 28; +CPFontModernSerifsClass = 3 << 28; +CPFontClarendonSerifsClass = 4 << 28; +CPFontSlabSerifsClass = 5 << 28; +CPFontFreeformSerifsClass = 7 << 28; +CPFontSansSerifClass = 8 << 28; + +CPFontSerifClass = (CPFontOldStyleSerifsClass | CPFontTransitionalSerifsClass | + CPFontModernSerifsClass | CPFontClarendonSerifsClass | + CPFontSlabSerifsClass | CPFontFreeformSerifsClass); + +CPFontFamilyClassMask = 0xF0000000; + +/* + Typeface information +*/ +CPFontItalicTrait = 1 << 0; +CPFontBoldTrait = 1 << 1; +CPFontExpandedTrait = 1 << 5; /* TODO: CCS 3 font-stretch */ +CPFontCondensedTrait = 1 << 6; +CPFontSmallCapsTrait = 1 << 7; + +/*! + @ingroup appkit + @class CPFontDescriptor +*/ +@implementation CPFontDescriptor : CPObject +{ + CPDictionary _attributes; +} + +/*! + Returns a font descriptor with the specified attributes. + + @param attributes a dictionary that describe the desired font descriptor + @return the requested font descriptor +*/ ++ (CPFontDescriptor)fontDescriptorWithFontAttributes:(CPDictionary)attributes +{ + return [[CPFontDescriptor alloc] initWithFontAttributes:attributes]; +} + +/*! + Returns a font descriptor with the specified name and size. + + @param fontName the name of the font + @param aSize the size of the font (in points) + @return the requested font descriptor +*/ ++ (CPFontDescriptor)fontDescriptorWithName:(CPString)fontName size:(float)size +{ + return [[CPFontDescriptor alloc] initWithFontAttributes:[CPDictionary dictionaryWithObjects:[fontName, [CPString stringWithString:size + '']] forKeys:[CPFontNameAttribute,CPFontSizeAttribute]]]; +} + +/*! + Initialize a font descriptor with the specified attributes. + + @param attributes a dictionary that describe the desired font descriptor + @return the requested font descriptor +*/ +- (id)initWithFontAttributes:(CPDictionary)attributes +{ + if (self = [super init]) + { + _attributes = [[CPMutableDictionary alloc] init]; + + if (attributes) + [_attributes addEntriesFromDictionary:attributes]; + } + + return self; +} + +/*! + Returns a new font descriptor that is the same as the receiver but with the + specified attributes taking precedence over the existing ones. + + @param attributes a dictionary that describe the desired font descriptor + @return the new font descriptor +*/ +- (CPFontDescriptor)fontDescriptorByAddingAttributes:(CPDictionary)attributes +{ + var attrib = [_attributes copy]; + + [attrib addEntriesFromDictionary:attributes]; + + return [[CPFontDescriptor alloc] initWithFontAttributes:attrib]; +} + +/*! + Returns a new font descriptor that is the same as the receiver but with the specified size taking precedence over the existing ones. + + @param aSize the new size + @return the new font descriptor +*/ +- (CPFontDescriptor)fontDescriptorWithSize:(float)aSize +{ + var attrib = [_attributes copy]; + + [attrib setObject:[CPString stringWithString:aSize + ''] forKey:CPFontSizeAttribute]; + + return [[CPFontDescriptor alloc] initWithFontAttributes:attrib]; +} + +/*! + Returns a new font descriptor that is the same as the receiver but with + the specified symbolic traits taking precedence over the existing ones. + + @param symbolicTraits the desired new symbolic traits + @return the new font descriptor +*/ +- (CPFontDescriptor)fontDescriptorWithSymbolicTraits:(CPFontSymbolicTraits)symbolicTraits +{ + var attrib = [_attributes copy]; + + if ([attrib objectForKey:CPFontTraitsAttribute]) + [[attrib objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTraits] + forKey:CPFontSymbolicTrait]; + else + [attrib setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTraits] + forKey:CPFontSymbolicTrait] forKey:CPFontTraitsAttribute]; + + return [[CPFontDescriptor alloc] initWithFontAttributes:attrib]; +} + +- (id)objectForKey:(id)aKey +{ + return [_attributes objectForKey:aKey]; +} + +- (CPDictionary)fontAttributes +{ + return _attributes; +} + +- (float)pointSize +{ + var value = [_attributes objectForKey:CPFontSizeAttribute]; + + return value ? [value floatValue] : 0.0; +} + +- (CPFontSymbolicTraits)symbolicTraits +{ + var traits = [_attributes objectForKey:CPFontTraitsAttribute]; + + return (traits && [traits objectForKey:CPFontSymbolicTrait]) ? [[traits objectForKey:CPFontSymbolicTrait] unsignedIntValue] : 0; +} + +@end + +var CPFontDescriptorAttributesKey = @"CPFontDescriptorAttributesKey"; + +@implementation CPFontDescriptor (CPCoding) + +/*! + Initializes the font descriptor from a coder. + + @param aCoder the coder from which to read the font descriptor data + @return the initialized font +*/ +- (id)initWithCoder:(CPCoder)aCoder +{ + return [self initWithFontAttributes:[aCoder decodeObjectForKey:CPFontDescriptorAttributesKey]]; +} + +/*! + Writes the font descriptor to a coder. + + @param aCoder the coder to which the data will be written +*/ +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeObject:_attributes forKey:CPFontDescriptorAttributesKey]; +} + +@end + +var _wrapNameRegEx = new RegExp(/(\w+\s+\w+)(,*)/g); + +/* + Helper methods to CPFont for generating CSS font style +*/ +@implementation CPFontDescriptor (CPFontCSSHelper) + +- (CPString)fontStyleCSSString +{ + return [self symbolicTraits] & CPFontItalicTrait ? @"italic" : @"normal"; +} + +- (CPString)fontWeightCSSString +{ + var traitsAttributes = [_attributes objectForKey:CPFontTraitsAttribute]; + + if (traitsAttributes) + { + /* give preference to CPFontWeightTrait */ + if ([traitsAttributes objectForKey:CPFontWeightTrait]) + return [traitsAttributes objectForKey:CPFontWeightTrait]; + /* else fallback to facetype symbolic traits */ + if ([self symbolicTraits] & CPFontBoldTrait) + return @"bold"; + } + + return @"normal"; +} + +- (CPString)fontSizeCSSString +{ + return [_attributes objectForKey:CPFontSizeAttribute] ? [[_attributes objectForKey:CPFontSizeAttribute] intValue] + "px" : @""; +} + +- (CPString)fontFamilyCSSString +{ + var aName = @""; + + if ([_attributes objectForKey:CPFontNameAttribute]) + aName += [_attributes objectForKey:CPFontNameAttribute].replace(_wrapNameRegEx, '"$1"$2'); + + var symbolicTraits = [self symbolicTraits]; + + if (symbolicTraits) + { + if ((symbolicTraits & CPFontFamilyClassMask) & CPFontSansSerifClass) + aName += @", sans-serif"; + else if ((symbolicTraits & CPFontFamilyClassMask) & CPFontSerifClass) + aName += @", serif"; + } + + return aName; +} + +- (CPString)fontVariantCSSString +{ + if ([self symbolicTraits] & CPFontSmallCapsTrait) + return @"small-caps"; + + return @"normal"; +} + +- (CPString)cssString +{ + return [CPString stringWithString:[self fontStyleCSSString] + " " + + [self fontVariantCSSString] + " " + + [self fontWeightCSSString] + " " + + [self fontSizeCSSString] + " " + + [self fontFamilyCSSString]]; +} + +@end diff --git a/AppKit/CPTextView/CPFontPanel.j b/AppKit/CPTextView/CPFontPanel.j new file mode 100644 index 000000000..c4c4a6449 --- /dev/null +++ b/AppKit/CPTextView/CPFontPanel.j @@ -0,0 +1,502 @@ +/* + * CPFontPanel.j + * AppKit + * + * TODOs: + * 1. make browser-width for size smaller and fix columns + * 2. sampleview is currently not shown + * 3. add all the missing features from the MacOS X counterpart + * + * + * Created by Daniel Boehringer on 2/JAN/2014. + * All modifications copyright Daniel Boehringer 2013. + * Extensive code formatting and review by Andrew Hankinson + * Based on original work by + * Created by Emmanuel Maillard on 06/03/2010. + * Copyright Emmanuel Maillard 2010. + * + * 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 + */ + + +@import "CPPanel.j" +@import "CPColorWell.j" +@import "CPColorPanel.j" +@import "CPBrowser.j" +@import "CPText.j" +@import "CPFontManager.j" + + +@class CPTextStorage +@class CPLayoutManager +@class CPTextContainer +@class CPFontManager + +/* + Collection indexes +*/ +var kTypefaceIndex_Normal = 0, + kTypefaceIndex_Italic = 1, + kTypefaceIndex_Bold = 2, + kTypefaceIndex_BoldItalic = 3, + kToolbarHeight = 32, + kBorderSpacing = 6, + kInnerSpacing = 2, + kNothingChanged = 0, + kFontNameChanged = 1, + kTypefaceChanged = 2, + kSizeChanged = 3, + kTextColorChanged = 4, + kBackgroundColorChanged = 5, + kUnderlineChanged = 6, + kWeightChanged = 7, + _sharedFontPanel; + +// FIXME Locale support +var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"], + _availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"72", @"96"]; + +@implementation _CPFontPanelSampleView : CPView +{ + CPLayoutManager _layoutManager; + CPTextStorage _textStorage; + CPTextContainer _textContainer; +} + +- (id)initWithFrame:(CGRect)rect +{ + if (self = [super initWithFrame:rect]) + { + _textStorage = [[CPTextStorage alloc] init]; + _layoutManager = [[CPLayoutManager alloc] init]; + + _textContainer = [[CPTextContainer alloc] init]; + [_layoutManager addTextContainer:_textContainer]; + + [_textStorage addLayoutManager:_layoutManager]; + } + + return self; +} + +- (void)setAttributedString:(CPAttributedString)aSting +{ + [_textStorage replaceCharactersInRange:CPMakeRange(0, [_textStorage length]) + withAttributedString:aSting]; + + [self setNeedsDisplay:YES]; +} + +- (void)drawRect:(CGRect)rect +{ + var ctx = [[CPGraphicsContext currentContext] graphicsPort], + glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer], + usedRect = [_layoutManager usedRectForTextContainer:_textContainer], + bounds = [self bounds], + pos = CGPointMake((bounds.size.width - usedRect.size.width) / 2.0, (bounds.size.height - usedRect.size.height) / 2.0); + + CGContextSaveGState(ctx); + CGContextSetFillColor(ctx, [CPColor whiteColor]); + CGContextFillRect(ctx, bounds); + CGContextRestoreGState(ctx); + + [_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:pos]; +} + +@end + +/*! + @ingroup appkit + @class CPFontPanel +*/ +@implementation CPFontPanel : CPPanel +{ + id _fontBrowser; + id _traitBrowser; + id _sizeBrowser; + CPArray _availableFonts; + id _textColorWell; + CPColor _textColor; + int _currentColorButtonTag; + BOOL _setupDone; + int _fontChanges; + + _CPFontPanelSampleView _sampleView; +} + + +#pragma mark - +#pragma mark Class methods + +/*! + Check if the shared Font panel exists. +*/ ++ (BOOL)sharedFontPanelExists +{ + return _sharedFontPanel !== nil; +} + +/*! + Return the shared Font panel. +*/ ++ (CPFontPanel)sharedFontPanel +{ + if (!_sharedFontPanel) + _sharedFontPanel = [[CPFontPanel alloc] init]; + + return _sharedFontPanel; +} + + +#pragma mark - +#pragma mark Init methods + +/*! @ignore */ +- (id)init +{ + if (self = [super initWithContentRect:CGRectMake(100, 90, 450, 394) styleMask:(CPTitledWindowMask | CPClosableWindowMask /*| CPResizableWindowMask*/ )]) + { + [[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]]; + [self setTitle:@"Font Panel"]; + [self setLevel:CPFloatingWindowLevel]; + [self setFloatingPanel:YES]; + [self setBecomesKeyOnlyIfNeeded:YES]; + [self setMinSize:CGSizeMake(378, 394)]; + + _availableFonts = [[CPFontManager sharedFontManager] availableFonts]; + _textColor = [CPColor blackColor]; + _setupDone = NO; + _fontChanges = kNothingChanged; + } + + return self; +} + +/*! @ignore */ +- (void)_setupToolbarView +{ + var colorPanel = [CPColorPanel sharedColorPanel]; + + _toolbarView = [[CPView alloc] initWithFrame:CGRectMake(0, kBorderSpacing, CGRectGetWidth([self frame]), kToolbarHeight)]; + [_toolbarView setAutoresizingMask:CPViewWidthSizable]; + + // Text color + _textColorWell = [[CPColorWell alloc] initWithFrame:CGRectMake(10, 0, 25, 25)]; + [_textColorWell setColor:_textColor]; + [_toolbarView addSubview:_textColorWell]; + [colorPanel setTarget:self]; + [colorPanel setAction:@selector(changeColor:)]; +} + +- (void)_setupBrowser:(CPBrowser)aBrowser +{ + [aBrowser setTarget:self]; + [aBrowser setAction:@selector(browserClicked:)]; + [aBrowser setDoubleAction:@selector(dblClicked:)]; + [aBrowser setAllowsEmptySelection:NO]; + [aBrowser setAllowsMultipleSelection:NO]; + [aBrowser setDelegate:self]; + [[self contentView] addSubview:aBrowser]; +} + +- (void)_setupContents +{ + if (_setupDone) + return; + + _setupDone = YES; + + [self _setupToolbarView]; + + var contentView = [self contentView], + label = [CPTextField labelWithTitle:@"Font name"], + contentBounds = [contentView bounds], + upperView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(contentBounds), CGRectGetHeight(contentBounds) - (kBorderSpacing + kToolbarHeight + kInnerSpacing))]; + + [contentView addSubview:_toolbarView]; + + _fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(10, 35, 150, 350)]; + _traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(155, 35, 150, 350)]; + _sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(300, 35, 140, 350)]; + + [self _setupBrowser:_fontBrowser]; + [self _setupBrowser:_traitBrowser]; + [self _setupBrowser:_sizeBrowser]; + + [[CPNotificationCenter defaultCenter] addObserver:self + selector:@selector(textViewDidChangeSelection:) + name:CPTextViewDidChangeSelectionNotification + object:nil]; +} + +- (void)textViewDidChangeSelection:(CPNotification)notification +{ + [self _refreshWithTextView:[notification object]]; + +} + +- (void)_refreshWithTextView:(CPTextView)textView +{ + if (![self isVisible]) + return; + + var attribs = [textView _attributesForFontPanel], + font = [attribs objectForKey:CPFontAttributeName] || [[textView textStorage] font] || [CPFont systemFontOfSize:12.0], + color = [attribs objectForKey:CPForegroundColorAttributeName]; + + if (!font) + return; + + var trait = kTypefaceIndex_Normal; + + if ([font isItalic] && [font isBold]) + trait = kTypefaceIndex_BoldItalic; + else if ([font isItalic]) + trait = kTypefaceIndex_Italic; + else if ([font isBold]) + trait = kTypefaceIndex_Bold; + + [self setCurrentFont:font]; + [self setCurrentTrait:trait]; + [self setCurrentSize:[font size] + ""]; //cast to string + + if (!color) + return; + + [_textColorWell setColor:color]; +} + +- (void)orderFront:(id)sender +{ + [self _setupContents]; + [super orderFront:sender]; + [self _refreshWithTextView:[[CPApp keyWindow] firstResponder]]; +} + +- (void)reloadDefaultFontFamilies +{ + _availableFonts = [[CPFontManager sharedFontManager] availableFonts]; +} + +- (BOOL)worksWhenModal +{ + return YES; +} + +/*! + @param aFont the font to convert. + @return The converted font or \c aFont if failed to convert. +*/ +- (CPFont)panelConvertFont:(CPFont)aFont +{ + var newFont = aFont, + index = 0; + + switch (_fontChanges) + { + case kFontNameChanged: + newFont = [CPFont fontWithDescriptor:[[aFont fontDescriptor] fontDescriptorByAddingAttributes: + [CPDictionary dictionaryWithObject:[self currentFont] forKey:CPFontNameAttribute]] size:0.0]; + break; + + case kTypefaceChanged: + index = [self currentTrait]; + if (index == kTypefaceIndex_BoldItalic) + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPBoldFontMask | CPItalicFontMask]; + else if (index == kTypefaceIndex_Bold) + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPBoldFontMask]; + else if (index == kTypefaceIndex_Italic) + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPItalicFontMask]; + else + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toNotHaveTrait:CPBoldFontMask | CPItalicFontMask]; + break; + + case kSizeChanged: + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toSize:[self currentSize]]; + break; + + case kNothingChanged: + break; + + default: + CPLog.trace(@"FIXME: -[" + [self className] + " " + _cmd + "] unhandled _fontChanges: " + _fontChanges); + break; + } + + return newFont; +} + +- (void)setCurrentSize:(CGSize)aSize +{ + [_sizeBrowser selectRow:[_availableSizes indexOfObject:aSize] inColumn:0]; +} + +- (CPString)currentSize +{ + return [_sizeBrowser selectedItem]; +} + +- (void)setCurrentFont:(CPFont)aFont +{ + [_fontBrowser selectRow:[_availableFonts indexOfObject:[aFont familyName]] inColumn:0]; +} + +- (CPString)currentFont +{ + return [_fontBrowser selectedItem]; +} + +- (void)setCurrentTrait:(unsigned)aTrait +{ + var row = 0; + + switch (aTrait) + { + case kTypefaceIndex_Italic: + row = 1; + break; + + case kTypefaceIndex_Bold: + row = 2; + break; + + case kTypefaceIndex_BoldItalic: + row = 3; + break; + } + + [_traitBrowser selectRow:row inColumn:0]; +} + +// FIXME Locale support +- (unsigned)currentTrait +{ + var sel = [_traitBrowser selectedItem]; + + if (sel === "Italic") + return kTypefaceIndex_Italic; + + if (sel === "Bold") + return kTypefaceIndex_Bold; + + if (sel === "Bold Italic") + return kTypefaceIndex_BoldItalic; + + return kTypefaceIndex_Normal; +} + +/*! + Set the selected font in Font panel. + @param font the selected font + @param flag if \c the current selection have multiple fonts. +*/ +- (void)setPanelFont:(CPFont)font isMultiple:(BOOL)flag +{ + [self _setupContents]; + + if ([self currentFont] !== [font familyName]) + [self setCurrentFont:[font familyName]]; + + if ([self currentSize] != [font size]) + [self setCurrentSize:[font size]]; + + var typefaceIndex = kTypefaceIndex_Normal, + symbolicTraits = [[font fontDescriptor] symbolicTraits]; + + if ((symbolicTraits & CPFontItalicTrait) && (symbolicTraits & CPFontBoldTrait)) + typefaceIndex = kTypefaceIndex_BoldItalic; + else if (symbolicTraits & CPFontItalicTrait) + typefaceIndex = kTypefaceIndex_Italic; + else if (symbolicTraits & CPFontBoldTrait) + typefaceIndex = kTypefaceIndex_Bold; + + if ([self currentTrait] != typefaceIndex) + [self setCurrentTrait:typefaceIndex ]; + + [_sampleView setAttributedString: [[CPAttributedString alloc] initWithString:[font familyName] + attributes:[CPDictionary dictionaryWithObjects:[font, [CPColor blackColor]] + forKeys:[CPFontAttributeName, CPForegroundColorAttributeName]]]]; + + _fontChanges = kNothingChanged; +} + +- (void)changeColor:(id)sender +{ + _textColor = [sender color]; + _fontChanges = kTextColorChanged; + [[CPFontManager sharedFontManager] modifyFontViaPanel:self]; +} + +//////////////////////////////////////////////////////////////////// +// TODO: ask CPFontManager for traits // +- (void)browserClicked:(id)aBrowser +{ + if (aBrowser === _fontBrowser) + { + _fontChanges = kFontNameChanged; + [[CPFontManager sharedFontManager] modifyFontViaPanel:self]; + } + else if (aBrowser === _traitBrowser) + { + _fontChanges = kTypefaceChanged; + [[CPFontManager sharedFontManager] modifyFontViaPanel:self]; + } + else if (aBrowser === _sizeBrowser) + { + _fontChanges = kSizeChanged; + [[CPFontManager sharedFontManager] modifyFontViaPanel:self]; + } +} + +- (void)dblClicked:(id)sender +{ + // alert("DOUBLE"); +} + +- (id)browser:(id)aBrowser numberOfChildrenOfItem:(id)anItem +{ + if (aBrowser === _fontBrowser) + return [_availableFonts count]; + + if (aBrowser === _traitBrowser) + return [_availableTraits count] + + return [_availableSizes count] +} + +- (id)browser:(id)aBrowser child:(int)index ofItem:(id)anItem +{ + if (aBrowser === _fontBrowser) + return [_availableFonts objectAtIndex:index]; + + if (aBrowser === _traitBrowser) + return [_availableTraits objectAtIndex:index]; + + return [_availableSizes objectAtIndex:index]; +} + +- (id)browser:(id)aBrowser objectValueForItem:(id)anItem +{ + return anItem; +} + +- (BOOL)browser:(id)aBrowser isLeafItem:(id)anItem +{ + return YES; +} + +@end + +[CPFontManager setFontPanelFactory:[CPFontPanel class]]; diff --git a/AppKit/CPTextView/CPLayoutManager.j b/AppKit/CPTextView/CPLayoutManager.j new file mode 100644 index 000000000..99d403be3 --- /dev/null +++ b/AppKit/CPTextView/CPLayoutManager.j @@ -0,0 +1,1373 @@ +/* + * CPLayoutManager.j + * AppKit + * + * FIXME remove from DOM when scrolled out of visible area? (as done in CPTableView) + * + * + * Created by Daniel Boehringer on 27/12/2013. + * All modifications copyright Daniel Boehringer 2013. + * Extensive code formatting and review by Andrew Hankinson + * Based on original work by + * Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * 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 + */ + +@import "CPText.j" +@import "CPTextContainer.j" +@import "CGContext.j" +@import "CPTypesetter.j" +@import "CPFont.j" + +@global _MakeRangeFromAbs + +@class CPTextContainer +@class CPTextView + +function _isNewlineCharacter(chr) +{ + return (chr === '\n' || chr === '\r'); +} + +function _RectEqualToRectHorizontally(lhsRect, rhsRect) +{ + return (lhsRect.origin.x == rhsRect.origin.x && + lhsRect.size.width == rhsRect.size.width && + lhsRect.size.height == rhsRect.size.height); +} + +_oncontextmenuhandler = function () { return false; }; + + +/*! + @ingroup appkit + @class CPLayoutManager +*/ +@implementation CPLayoutManager : CPObject +{ + Class _lineFragmentFactory @accessors(setter=setLineFragmentFactory:); + CPMutableArray _textContainers @accessors(getter=textContainers); + CPTextStorage _textStorage @accessors(property=textStorage); + CPTypesetter _typesetter @accessors(property=typesetter); + + CPMutableArray _lineFragments; + CPMutableArray _lineFragmentsForRescue; + id _extraLineFragment; + + CPMutableArray _temporaryAttributes; + + BOOL _isValidatingLayoutAndGlyphs; + CPRange _removeInvalidLineFragmentsRange; +} + + +#pragma mark - +#pragma mark Init methods + +- (id)init +{ + if (self = [super init]) + { + [self _init]; + } + + return self; +} + +- (void)_init +{ + _isValidatingLayoutAndGlyphs = NO; + _lineFragmentFactory = [_CPLineFragment class]; + _lineFragments = [[CPMutableArray alloc] init]; + _textContainers = [[CPMutableArray alloc] init]; + _textStorage = [[CPTextStorage alloc] init]; + _typesetter = [CPTypesetter sharedSystemTypesetter]; + + [_textStorage addLayoutManager:self]; +} + + +#pragma mark - +#pragma mark Text containes method + +- (void)insertTextContainer:(CPTextContainer)aContainer atIndex:(int)index +{ + [_textContainers insertObject:aContainer atIndex:index]; + [aContainer setLayoutManager:self]; +} + +- (void)addTextContainer:(CPTextContainer)aContainer +{ + [_textContainers addObject:aContainer]; + [aContainer setLayoutManager:self]; +} + +- (void)removeTextContainerAtIndex:(int)index +{ + var container = [_textContainers objectAtIndex:index]; + [container setLayoutManager:nil]; + [_textContainers removeObjectAtIndex:index]; +} + +// fixme +- (int)numberOfGlyphs +{ + return [_textStorage length]; +} + +- (int)numberOfCharacters +{ + return [_textStorage length]; +} + +- (CPTextView)firstTextView +{ + return [_textContainers[0] textView]; +} + +// from cocoa (?) +- (CPTextView)textViewForBeginningOfSelection +{ + return [[_textContainers objectAtIndex:0] textView]; +} + +- (BOOL)layoutManagerOwnsFirstResponderInWindow:(CPWindow)aWindow +{ + var firstResponder = [aWindow firstResponder], + c = [_textContainers count]; + + for (var i = 0; i < c; i++) + { + if ([_textContainers[i] textView] === firstResponder) + return YES; + } + + return NO; +} + +- (CGRect)boundingRectForGlyphRange:(CGRange)aRange inTextContainer:(CPTextContainer)container +{ + if (![self numberOfGlyphs]) + return CGRectMake(0, 0, 1, 12); // crude hack to give a cursor in an empty doc. + + if (CPMaxRange(aRange) >= [self numberOfGlyphs]) + aRange = CPMakeRange([self numberOfGlyphs] - 1, 1); + + var fragments = _objectsInRange(_lineFragments, aRange), + rect = nil, + c = [fragments count]; + + for (var i = 0; i < c; i++) + { + var fragment = fragments[i]; + + if (fragment._textContainer === container) + { + var frames = [fragment glyphFrames], + l = frames ? frames.length : 0; + + for (var j = 0; j < l; j++) + { + if (CPLocationInRange(fragment._range.location + j, aRange)) + { + if (!rect) + rect = CGRectCreateCopy(frames[j]); + else + rect = CGRectUnion(rect, frames[j]); + } + } + } + } + + return rect ? rect : CGRectMakeZero(); +} + +- (CPRange)glyphRangeForTextContainer:(CPTextContainer)aTextContainer +{ + var range = nil, + c = [_lineFragments count]; + + for (var i = 0; i < c; i++) + { + var fragment = _lineFragments[i]; + + if (fragment._textContainer === aTextContainer) + { + if (!range) + range = CPMakeRangeCopy(fragment._range); + else + range = CPUnionRange(range, fragment._range); + } + } + + return range ? range : CPMakeRange(CPNotFound, 0); +} + +- (void)_removeInvalidLineFragments +{ + _lineFragmentsForRescue = [_lineFragments copy]; + [_lineFragmentsForRescue makeObjectsPerformSelector:@selector(_deinvalidate)]; + + if (_removeInvalidLineFragmentsRange && _removeInvalidLineFragmentsRange.length && _lineFragments.length) + { + // [[_lineFragments subarrayWithRange:_removeInvalidLineFragmentsRange] makeObjectsPerformSelector:@selector(invalidate)]; + [_lineFragments removeObjectsInRange:_removeInvalidLineFragmentsRange]; + [[_lineFragmentsForRescue subarrayWithRange:_removeInvalidLineFragmentsRange] makeObjectsPerformSelector:@selector(invalidate)]; + } + +} + +- (void)_cleanUpDOM +{ + var l = _lineFragmentsForRescue? _lineFragmentsForRescue.length : 0; + + for (var i = 0; i < l; i++) + { + if (_lineFragmentsForRescue[i]._isInvalid) + [_lineFragmentsForRescue[i] _removeFromDOM]; + } +} + +- (void)_validateLayoutAndGlyphs +{ + if (_isValidatingLayoutAndGlyphs) + return; + + _isValidatingLayoutAndGlyphs = YES; + + var startIndex = CPNotFound, + removeRange = CPMakeRange(0, 0), + l = _lineFragments.length; + + if (l) + { + for (var i = 0; i < l; i++) + { + if (_lineFragments[i]._isInvalid) + { + startIndex = _lineFragments[i]._range.location; + removeRange.location = i; + removeRange.length = l - i; + break; + } + } + + // start one line above current line to make sure that a word can jump up + if (startIndex == CPNotFound && CPMaxRange (_lineFragments[l - 1]._range) < [_textStorage length]) + startIndex = CPMaxRange(_lineFragments[l - 1]._range); + } + else + { + startIndex = 0; + } + + /* nothing to validate and layout */ + if (startIndex == CPNotFound) + { + _isValidatingLayoutAndGlyphs = NO; + return; + } + + if (removeRange.length) + _removeInvalidLineFragmentsRange = CPMakeRangeCopy(removeRange); + + // We erased all lines + if (!startIndex) + [self setExtraLineFragmentRect:CGRectMake(0, 0) usedRect:CGRectMake(0, 0) textContainer:nil]; + // document.title=startIndex; + + [_typesetter layoutGlyphsInLayoutManager:self startingAtGlyphIndex:startIndex maxNumberOfLineFragments:-1 nextGlyphIndex:nil]; + +#if PLATFORM(DOM) + [self _cleanUpDOM]; +#endif + + _isValidatingLayoutAndGlyphs = NO; +} + +- (BOOL)_rescuingInvalidFragmentsWasPossibleForGlyphRange:(CPRange)aRange +{ + var l = _lineFragments.length, + location = aRange.location, + found = NO, + targetLine = 0; + + // try to find the first linefragment of the desired range + for (; targetLine < l; targetLine++) + { + if (CPLocationInRange(location, _lineFragments[targetLine]._range)) + { + found = YES; + break; + } + } + + if (!found) + return NO; + + if (!_lineFragmentsForRescue[targetLine]) + return NO; + + var startLineForDOMRemoval = targetLine, + isIdentical = YES, + newLineFragment= _lineFragments[targetLine], + oldLineFragment = _lineFragmentsForRescue[targetLine], + oldLength = CPMaxRange([_lineFragmentsForRescue lastObject]._range), + newLength = [[_textStorage string].length], + removalSkip = 1; + + // if (ABS(newLength - oldLength) > 1) + // return NO; + + if (![oldLineFragment isVisuallyIdenticalToFragment:newLineFragment]) + { + isIdentical = NO; + + // deleting newline in its own line-> move up instead of re-layouting + if (newLength < oldLength && oldLineFragment._range.length == 1 && newLineFragment._range.length > 1 && newLineFragment._range.location === oldLineFragment._range.location && oldLineFragment._isLast) + { + isIdentical = YES; + targetLine--; + removalSkip++; + } + + // newline entered in its own line-> move down instead of re-layouting + if (newLength > oldLength && newLineFragment._range.length == 1 && oldLineFragment._range.length > 1 && newLineFragment._range.location === oldLineFragment._range.location && newLineFragment._isLast) + { + isIdentical = YES; + startLineForDOMRemoval--; + } + } + + // patch the linefragments instead of re-layoutung + if (isIdentical) + { + var rangeOffset = CPMaxRange(_lineFragments[targetLine]._range) - CPMaxRange(_lineFragmentsForRescue[startLineForDOMRemoval]._range); + + if (ABS(rangeOffset) !== ABS(newLength - oldLength)) + return NO; + + var verticalOffset = _lineFragments[targetLine]._fragmentRect.origin.y - _lineFragmentsForRescue[startLineForDOMRemoval]._fragmentRect.origin.y, + l = _lineFragmentsForRescue.length, + newTargetLine = startLineForDOMRemoval + removalSkip; + + for (; newTargetLine < l; newTargetLine++) + { + _lineFragmentsForRescue[newTargetLine]._isInvalid = NO; // protect them from final removal + [_lineFragmentsForRescue[newTargetLine] _relocateVerticallyByY:verticalOffset rangeOffset:rangeOffset]; + _lineFragments.push(_lineFragmentsForRescue[newTargetLine]); + } + } + + return isIdentical; +} + +- (void)invalidateDisplayForGlyphRange:(CPRange)range +{ + var lineFragments = _objectsInRange(_lineFragments, range); + + for (var i = 0; i < lineFragments.length; i++) + [[lineFragments[i]._textContainer textView] setNeedsDisplayInRect:lineFragments[i]._fragmentRect]; +} + +- (void)invalidateLayoutForCharacterRange:(CPRange)aRange isSoft:(BOOL)flag actualCharacterRange:(CPRangePointer)actualCharRange +{ + var firstFragmentIndex = _lineFragments.length ? [_lineFragments _indexOfObject: aRange.location sortedByFunction:_sortRange context:nil] : CPNotFound; + + if (firstFragmentIndex == CPNotFound) + { + if (_lineFragments.length) + { + firstFragmentIndex = _lineFragments.length - 1; + } + else + { + if (actualCharRange) + { + actualCharRange.length = aRange.length; + actualCharRange.location = 0; + } + + return; + } + } + else + { + firstFragmentIndex = firstFragmentIndex + (firstFragmentIndex ? -1 : 0); + } + + var fragment = _lineFragments[firstFragmentIndex], + range = CPMakeRangeCopy(fragment._range); + + fragment._isInvalid = YES; + + /* invalidated all fragments that follow */ + for (var i = firstFragmentIndex + 1; i < _lineFragments.length; i++) + { + _lineFragments[i]._isInvalid = YES; + range = CPUnionRange(range, _lineFragments[i]._range); + } + + if (CPMaxRange(range) < CPMaxRange(aRange)) + range = CPUnionRange(range, aRange); + + if (actualCharRange) + { + actualCharRange.length = range.length; + actualCharRange.location = range.location; + } +} + +- (void)textStorage:(CPTextStorage)textStorage edited:(unsigned)mask range:(CPRange)charRange changeInLength:(int)delta invalidatedRange:(CPRange)invalidatedRange +{ + var actualRange = CPMakeRange(CPNotFound,0); + + [self invalidateLayoutForCharacterRange:invalidatedRange isSoft:NO actualCharacterRange:actualRange]; + [self invalidateDisplayForGlyphRange:actualRange]; + [self _validateLayoutAndGlyphs]; + [[self firstTextView] sizeToFit]; +} + +- (CPRange)glyphRangeForBoundingRect:(CGRect)aRect inTextContainer:(CPTextContainer)container +{ + var c = [_lineFragments count], + range; + + for (var i = 0; i < c; i++) + { + var fragment = _lineFragments[i]; + + if (fragment._textContainer === container) + { + if (CGRectContainsRect(aRect, fragment._fragmentRect)) + { + if (!range) + range = CPMakeRangeCopy(fragment._range); + else + range = CPUnionRange(range, fragment._range); + } + else + { + var glyphRange = CPMakeRange(CPNotFound, 0), + frames = [fragment glyphFrames]; + + for (var j = 0; j < frames.length; j++) + { + if (CGRectIntersectsRect(aRect, frames[j])) + { + if (glyphRange.location == CPNotFound) + glyphRange.location = fragment._range.location + j; + else + glyphRange.length++; + } + } + + if (glyphRange.location != CPNotFound) + { + if (!range) + range = CPMakeRangeCopy(glyphRange); + else + range = CPUnionRange(range, glyphRange); + } + } + } + } + + return range ? range : CPMakeRange(0,0); +} + +- (void)drawBackgroundForGlyphRange:(CPRange)aRange atPoint:(CGPoint)aPoint +{ + +} + +- (void)drawUnderlineForGlyphRange:(CPRange)glyphRange + underlineType:(int)underlineVal + baselineOffset:(float)baselineOffset + lineFragmentRect:(CGRect)lineFragmentRect + lineFragmentGlyphRange:(CPRange)lineGlyphRange + containerOrigin:(CGPoint)containerOrigin +{ +// FIXME +} + +- (void)drawGlyphsForGlyphRange:(CPRange)aRange atPoint:(CGPoint)aPoint +{ + var lineFragments = _objectsInRange(_lineFragments, aRange); + + if (!lineFragments.length) + return; + + var paintedRange = CPMakeRangeCopy(aRange), + l = lineFragments.length, + lineFragmentIndex, + ctx; + + for (lineFragmentIndex = 0; lineFragmentIndex < l; lineFragmentIndex++) + { + var currentFragment = lineFragments[lineFragmentIndex]; + [currentFragment drawInContext:ctx atPoint:aPoint forRange:paintedRange]; + } +} + +- (unsigned)glyphIndexForPoint:(CGPoint)point inTextContainer:(CPTextContainer)container fractionOfDistanceThroughGlyph:(FloatArray)partialFraction +{ + var c = [_lineFragments count]; + + for (var i = 0; i < c; i++) + { + var fragment = _lineFragments[i]; + + if (fragment._textContainer === container) + { + var frames = [fragment glyphFrames], + len = fragment._range.length; + + for (var j = 0; j < len; j++) + { + if (CGRectContainsPoint(frames[j], point)) + { + if (partialFraction) + partialFraction[0] = (point.x - frames[j].origin.x) / frames[j].size.width; + + return fragment._range.location + j; + } + } + } + } + + // Not found, maybe a point left to the last character was clicked -> search again with broader constraints + if ([[_textStorage string] length]) + { + for (var i = 0; i < c; i++) + { + var fragment = _lineFragments[i]; + + if (fragment._textContainer === container) + { + // Within the horizontal territory of the current (not-empty) line? + if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y && + point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height) + { + // Skip tabs and move on the last fragment in this line + if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y) + continue; + + var nlLoc = CPMaxRange(fragment._range), + lastFrame = [fragment glyphFrames][fragment._range.length - 1], + firstFrame = [fragment glyphFrames][0]; + + // stay on the line the newline character belongs to + if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0])) + nlLoc--; + + // Clicked right to the last character + if (point.x > CGRectGetMaxX(lastFrame)) + return nlLoc; + // Clicked left to the last character + else if (point.x <= CGRectGetMinX(firstFrame)) + return fragment._range.location; + else + return nlLoc; + } + } + } + } + + return point.y > 0 ? [[_textStorage string] length] : 0; +} + +- (unsigned)glyphIndexForPoint:(CGPoint)point inTextContainer:(CPTextContainer)container +{ + return [self glyphIndexForPoint:point inTextContainer:container fractionOfDistanceThroughGlyph:nil]; +} + +- (void)_setAttributes:(CPDictionary)attributes toTemporaryAttributes:(_CPTemporaryAttributes)tempAttributes +{ + tempAttributes._attributes = attributes; +} + +- (void)_addAttributes:(CPDictionary)attributes toTemporaryAttributes:(_CPTemporaryAttributes)tempAttributes +{ + [tempAttributes._attributes addEntriesFromDictionary:attributes]; +} + +- (void)_handleTemporaryAttributes:(CPDictionary)attributes forCharacterRange:(CPRange)charRange withSelector:(SEL)attributesOperation +{ + // FIXME +} + +- (void)setTemporaryAttributes:(CPDictionary)attributes forCharacterRange:(CPRange)charRange +{ + [self _handleTemporaryAttributes:attributes forCharacterRange:charRange withSelector:@selector(_setAttributes:toTemporaryAttributes:)]; +} + +- (void)addTemporaryAttributes:(CPDictionary)attributes forCharacterRange:(CPRange)charRange +{ + [self _handleTemporaryAttributes:attributes forCharacterRange:charRange withSelector:@selector(_addAttributes:toTemporaryAttributes:)]; +} + +- (void)removeTemporaryAttribute:(CPString)attributeName forCharacterRange:(CPRange)charRange +{ + // FIXME +} + +- (CPDictionary)temporaryAttributesAtCharacterIndex:(unsigned)index effectiveRange:(CPRangePointer)effectiveRange +{ + // FIXME +} + +- (void)textContainerChangedTextView:(CPTextContainer)aContainer +{ + // FIXME +} + +- (void)_appendNewLineFragmentInTextContainer:(CPTextContainer)aTextContainer forGlyphRange:(CPRange)glyphRange +{ + _lineFragments.push([[_lineFragmentFactory alloc] initWithRange:glyphRange textContainer:aTextContainer textStorage:_textStorage]); +} + +- (void)setTextContainer:(CPTextContainer)aTextContainer forGlyphRange:(CPRange)glyphRange +{ + var fragments = _objectsInRange(_lineFragments, glyphRange), + l = fragments.length; + + for (var i = 0; i < l; i++) + fragments[i]._textContainer = aTextContainer; +} + +- (id)_lineFragmentForLocation:(unsigned) aLoc +{ + var fragments = _objectsInRange(_lineFragments, CPMakeRange(aLoc, 0)), + l = fragments.length; + + if (l > 0) + return fragments[0]; + + return nil; +} + +- (id)_firstLineFragmentForLineFromLocation:(unsigned)location +{ + var l = _lineFragments.length; + + for (var i = 0; i < l; i++) + { + if (CPLocationInRange(location, _lineFragments[i]._range)) + { + var j = i; + + while (--j > 0 && !_lineFragments[j]._isLast) + { + // body intentionally left empty + } + + return _lineFragments[j + 1]; + } + } + + return nil; +} +- (id)_lastLineFragmentForLineFromLocation:(unsigned)location +{ + var l = _lineFragments.length; + + for (var i = 0; i < l; i++) + { + if (CPLocationInRange(location, _lineFragments[i]._range)) + { + var j = i; + + while (!_lineFragments[j]._isLast) + j++; + + return _lineFragments[j]; + } + } + + return nil; +} + +- (double)_characterOffsetAtLocation:(unsigned)location +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, location); + + if (!lineFragment) + return 0.0; + + var index = location - lineFragment._range.location; + + return lineFragment._glyphsOffsets[index]; +} + +- (double)_descentAtLocation:(unsigned)location +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, location); + + if (!lineFragment) + return 0.0; + + var index = location - lineFragment._range.location; + + return lineFragment._glyphsFrames[index]._descent; +} + +- (void)setLineFragmentRect:(CGRect)fragmentRect forGlyphRange:(CPRange)glyphRange usedRect:(CGRect)usedRect +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphRange.location); + + if (lineFragment) + { + lineFragment._fragmentRect = CGRectCreateCopy(fragmentRect); + lineFragment._usedRect = CGRectCreateCopy(usedRect); + } +} + +- (void)_setAdvancements:(CPArray)someAdvancements forGlyphRange:(CPRange)glyphRange +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphRange.location); + + if (lineFragment) + [lineFragment setAdvancements:someAdvancements]; +} + +- (void)setLocation:(CGPoint)aPoint forStartOfGlyphRange:(CPRange)glyphRange +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphRange.location); + + if (lineFragment) + lineFragment._location = CGPointCreateCopy(aPoint); +} + +- (CGRect)extraLineFragmentRect +{ + if (_extraLineFragment) + return CGRectCreateCopy(_extraLineFragment._fragmentRect); + + return CGRectMakeZero(); +} + +- (CPTextContainer)extraLineFragmentTextContainer +{ + if (_extraLineFragment) + return _extraLineFragment._textContainer; + + return nil; +} + +- (CGRect)extraLineFragmentUsedRect +{ + if (_extraLineFragment) + return CGRectCreateCopy(_extraLineFragment._usedRect); + + return CGRectMakeZero(); +} + +- (void)setExtraLineFragmentRect:(CGRect)rect usedRect:(CGRect)usedRect textContainer:(CPTextContainer)textContainer +{ + if (textContainer) + { + _extraLineFragment = {}; + _extraLineFragment._fragmentRect = CGRectCreateCopy(rect); + _extraLineFragment._usedRect = CGRectCreateCopy(usedRect); + _extraLineFragment._textContainer = textContainer; + } + else + { + _extraLineFragment = nil; + } +} + +- (CGRect)usedRectForTextContainer:(CPTextContainer)textContainer +{ + var rect, + l = _lineFragments.length; + + for (var i = 0; i < l; i++) + { + if (_lineFragments[i]._textContainer === textContainer) + { + if (rect) + rect = CGRectUnion(rect, _lineFragments[i]._usedRect); + else + rect = CGRectCreateCopy(_lineFragments[i]._usedRect); + } + } + + return rect ? rect : CGRectMakeZero(); +} + +- (CGRect)lineFragmentRectForGlyphAtIndex:(unsigned)glyphIndex effectiveRange:(CPRangePointer)effectiveGlyphRange +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphIndex); + + if (!lineFragment) + return CGRectMakeZero(); + + if (effectiveGlyphRange) + { + effectiveGlyphRange.location = lineFragment._range.location; + effectiveGlyphRange.length = lineFragment._range.length; + } + + return CGRectCreateCopy(lineFragment._fragmentRect); +} + +- (CGRect)lineFragmentUsedRectForGlyphAtIndex:(unsigned)glyphIndex effectiveRange:(CPRangePointer)effectiveGlyphRange +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphIndex); + + if (!lineFragment) + return CGRectMakeZero(); + + if (effectiveGlyphRange) + { + effectiveGlyphRange.location = lineFragment._range.location; + effectiveGlyphRange.length = lineFragment._range.length; + } + + return CGRectCreateCopy(lineFragment._usedRect); +} + +- (CGPoint)locationForGlyphAtIndex:(unsigned)index +{ + if (_lineFragments.length > 0 && index >= [self numberOfGlyphs] - 1) + { + var lineFragment= _lineFragments[_lineFragments.length - 1], + glyphFrames = [lineFragment glyphFrames]; + + if (glyphFrames.length > 0) + return CGPointCreateCopy(glyphFrames[glyphFrames.length - 1].origin); + } + + var lineFragment = _objectWithLocationInRange(_lineFragments, index); + + if (lineFragment) + { + if (index == lineFragment._range.location) + return CGPointCreateCopy(lineFragment._location); + + var glyphFrames = [lineFragment glyphFrames]; + + return CGPointCreateCopy(glyphFrames[index - lineFragment._range.location].origin); + } + + return CGPointMakeZero(); +} + +- (CPTextContainer)textContainerForGlyphAtIndex:(unsigned)index effectiveRange:(CPRangePointer)effectiveGlyphRange withoutAdditionalLayout:(BOOL)flag +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, index); + + if (lineFragment) + { + if (effectiveGlyphRange) + { + effectiveGlyphRange.location = lineFragment._range.location; + effectiveGlyphRange.length = lineFragment._range.length; + } + + return lineFragment._textContainer; + } + + return [_textContainers lastObject]; +} + +- (CPTextContainer)textContainerForGlyphAtIndex:(unsigned)index effectiveRange:(CPRangePointer)effectiveGlyphRange +{ + return [self textContainerForGlyphAtIndex:index effectiveRange:effectiveGlyphRange withoutAdditionalLayout:NO]; +} + +- (CPRange)characterRangeForGlyphRange:(CPRange)aRange actualGlyphRange:(CPRangePointer)actualRange +{ + return _MakeRangeFromAbs([self characterIndexForGlyphAtIndex:aRange.location], + [self characterIndexForGlyphAtIndex:CPMaxRange(aRange)]); +} + +- (unsigned)characterIndexForGlyphAtIndex:(unsigned)index +{ + /* FIXME: stub */ + return index; +} + +- (CPArray)rectArrayForCharacterRange:(CPRange)charRange + withinSelectedCharacterRange:(CPRange)selectedCharRange + inTextContainer:(CPTextContainer)container + rectCount:(CGRectPointer)rectCount +{ + + var rectArray = [], + lineFragments = _objectsInRange(_lineFragments, selectedCharRange); + + if (!lineFragments.length) + return rectArray; + + var containerSize = [container containerSize]; + + for (var i = 0; i < lineFragments.length; i++) + { + var fragment = lineFragments[i]; + + if (fragment._textContainer === container) + { + var frames = [fragment glyphFrames], + rect = nil, + len = fragment._range.length; + + for (var j = 0; j < len; j++) + { + if (CPLocationInRange(fragment._range.location + j, selectedCharRange)) + { + var correctedRect = CGRectCreateCopy(frames[j]); + correctedRect.size.height -= frames[j]._descent; + correctedRect.origin.y -= frames[j]._descent; + if (!rect) + rect = CGRectCreateCopy(correctedRect); + else + rect = CGRectUnion(rect, correctedRect); + + if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)])) + { + rect.size.width = containerSize.width - rect.origin.x; + } + } + } + + if (rect) + rectArray.push(rect); + } + } + + var len = rectArray.length; + + for (var i = 0; i < len - 1; i++) // extend the width of all but the last one + { + if (FLOOR(CGRectGetMaxY(rectArray[i])) == FLOOR(CGRectGetMaxY(rectArray[i + 1]))) + continue; + + rectArray[i].size.width = containerSize.width - rectArray[i].origin.x; + } + + return rectArray; +} + +@end + + +var CPLayoutManagerTextStorageKey = @"CPLayoutManagerTextStorageKey"; + +@implementation CPLayoutManager (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + if (self) + { + [self _init]; + + _textStorage = [aCoder decodeObjectForKey:CPLayoutManagerTextStorageKey]; + [_textStorage addLayoutManager:self]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeObject:_textStorage forKey:CPLayoutManagerTextStorageKey]; +} + +@end + + +@implementation CPArray (SortedSearching) + +- (unsigned)_indexOfObject:(id)anObject sortedByFunction:(Function)aFunction context:(id)aContext +{ + var length= self.length; + + if (!aFunction) + return CPNotFound; + + if (length === 0) + return -1; + + var mid, + c, + first = 0, + last = length - 1; + + while (first <= last) + { + mid = FLOOR((first + last) / 2); + c = aFunction(anObject, self[mid], aContext); + + if (c > 0) + { + first = mid + 1; + } + else if (c < 0) + { + last = mid - 1; + } + else + { + while (mid < length - 1 && aFunction(anObject, self[mid + 1], aContext) == CPOrderedSame) + mid++; + + return mid; + } + } + + var result = -first - 1; + + return result >= 0 ? result : CPNotFound; +} + +@end + +var _sortRange = function(location, anObject) +{ + if (CPLocationInRange(location, anObject._range)) + return CPOrderedSame; + else if (CPMaxRange(anObject._range) <= location) + return CPOrderedDescending; + else + return CPOrderedAscending; +} + +var _objectWithLocationInRange = function(aList, aLocation) +{ + var index = [aList _indexOfObject:aLocation sortedByFunction:_sortRange context:nil]; + + if (index != CPNotFound) + return aList[index]; + + return nil; +} + +var _objectsInRange = function(aList, aRange) +{ + var firstIndex = [aList _indexOfObject:aRange.location sortedByFunction:_sortRange context:nil], + lastIndex = [aList _indexOfObject:CPMaxRange(aRange) sortedByFunction:_sortRange context:nil]; + + if (firstIndex === CPNotFound) + firstIndex = 0; + + if (lastIndex === CPNotFound) + lastIndex = aList.length - 1; + + return aList.slice(firstIndex, lastIndex + 1); +} + +@implementation _CPLineFragment : CPObject +{ + CPArray _glyphsFrames @accessors(getter=glyphFrames); + CPArray _glyphsOffsets; + + BOOL _isInvalid; + BOOL _isLast; + CGRect _fragmentRect; + CGRect _usedRect; + CGPoint _location; + CPRange _range; + CPTextContainer _textContainer; + CPMutableArray _runs; +} + +#pragma mark - +#pragma mark Init methods + +- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)aColor +{ +#if PLATFORM(DOM) + var style, + span = document.createElement("span"); + + span.oncontextmenu = span.onmousedown = span.onselectstart = _oncontextmenuhandler; + // span.contentEditable = true; // this unfortunately does not work to make native pasting work on safari + + style = span.style; + style.position = "absolute"; + style.visibility = "visible"; + style.padding = "0px"; + style.margin = "0px"; + style.whiteSpace = "pre"; + style.backgroundColor = "transparent"; + style.font = [aFont cssString]; + + if (aColor) + style.color = [aColor cssString]; + + if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature)) + span.innerText = aString; + else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature)) + span.textContent = aString; + + // FIXME aString.replace(/&/g,'&') + return span; +#else + return nil; +#endif +} + +- (id)initWithRange:(CPRange)aRange textContainer:(CPTextContainer)aContainer textStorage:(CPTextStorage)textStorage +{ + if (self = [super init]) + { + var effectiveRange = CPMakeRange(0,0), + location; + + _fragmentRect = CGRectMakeZero(); + _usedRect = CGRectMakeZero(); + _location = CGPointMakeZero(); + _range = CPMakeRangeCopy(aRange); + _textContainer = aContainer; + _isInvalid = NO; + _runs = [[CPMutableArray alloc] init]; + + for (location = aRange.location; location < CPMaxRange(aRange); location = CPMaxRange(effectiveRange)) + { + var attributes = [textStorage attributesAtIndex:location effectiveRange:effectiveRange]; + + effectiveRange = attributes ? CPIntersectionRange(aRange, effectiveRange) : aRange; + + var string = [textStorage._string substringWithRange:effectiveRange], + font = [textStorage font] || [CPFont systemFontOfSize:12.0]; + + if ([attributes containsKey:CPFontAttributeName]) + font = [attributes objectForKey:CPFontAttributeName]; + + var color = [attributes objectForKey:CPForegroundColorAttributeName], + elem = [self createDOMElementWithText:string andFont:font andColor:color], + run = {_range:CPMakeRangeCopy(effectiveRange), color:color, font:font, elem:nil, string:string}; + + _runs.push(run); + + if (!CPMaxRange(effectiveRange)) + break; + } + } + + return self; +} + +- (void)setAdvancements:(CPArray)someAdvancements +{ + var count = someAdvancements.length, + origin = CGPointMake(_fragmentRect.origin.x + _location.x, _fragmentRect.origin.y), + height = _usedRect.size.height; + + _glyphsFrames = new Array(count); + _glyphsOffsets = new Array(count); + + for (var i = 0; i < count; i++) + { + _glyphsFrames[i] = CGRectMake(origin.x, origin.y, someAdvancements[i].width, height); + _glyphsFrames[i]._descent = someAdvancements[i].descent + _glyphsOffsets[i] = height - someAdvancements[i].height; + origin.x += someAdvancements[i].width; + } +} + +- (void)_adjustForHeight:(double)height +{ + var count = _glyphsFrames.length; + + for (var i = 0; i < count; i++) + _glyphsFrames[i].origin.y += (height - _fragmentRect.size.height); + + _fragmentRect.size.height = height; +} + +- (CPString)description +{ + return [super description] + + "\n\t_fragmentRect="+CPStringFromRect(_fragmentRect) + + "\n\t_usedRect="+CPStringFromRect(_usedRect) + + "\n\t_location="+CPStringFromPoint(_location) + + "\n\t_range="+CPStringFromRange(_range); +} + +- (void)drawUnderlineForGlyphRange:(CPRange)glyphRange + underlineType:(int)underlineVal + baselineOffset:(float)baselineOffset + containerOrigin:(CGPoint)containerOrigin +{ +// FIXME +} + +- (void)invalidate +{ + _isInvalid = YES; +} + +- (void)_deinvalidate +{ + _isInvalid = NO; +} + +- (void)_removeFromDOM +{ + var l = _runs.length; + + for (var i = 0; i < l; i++) + { + if (_runs[i].elem && _runs[i].DOMactive) + _textContainer._textView._DOMElement.removeChild(_runs[i].elem); + + _runs[i].elem = nil; + _runs[i].DOMactive = NO; + } +} + +- (void)drawInContext:(CGContext)context atPoint:(CGPoint)aPoint forRange:(CPRange)aRange +{ + var runs = _objectsInRange(_runs, aRange), + c = runs.length, + orig = CGPointMake(_fragmentRect.origin.x, _fragmentRect.origin.y); + + for (var i = 0; i < c; i++) + { + var run = runs[i]; + + if (!run.elem && CPRectIntersectsRect([_textContainer._textView exposedRect], _fragmentRect)) + { + run.elem=[self createDOMElementWithText:run.string andFont:run.font andColor:run.color]; + } + + if (run.DOMactive && !run.DOMpatched) + continue; + + if (!_glyphsFrames) + continue; + + var loc = run._range.location - _runs[0]._range.location; + orig.x = _glyphsFrames[loc].origin.x + aPoint.x; + orig.y = _glyphsFrames[loc].origin.y + aPoint.y + _glyphsOffsets[loc]; + + if(run.elem) + { + run.elem.style.left = (orig.x) + "px"; + run.elem.style.top = (orig.y) + "px"; + + if (!run.DOMactive) + _textContainer._textView._DOMElement.appendChild(run.elem); + + run.DOMactive = YES; + } + + run.DOMpatched = NO; + + } +} + +- (void)backgroundColorForGlyphAtIndex:(unsigned)index +{ + var run = _objectWithLocationInRange(_runs, index); + + if (run) + return run.backgroundColor; + + return [CPColor clearColor]; +} + +- (BOOL)isVisuallyIdenticalToFragment:(_CPLineFragment)newLineFragment +{ + var newFragmentRuns= newLineFragment._runs, + oldFragmentRuns= _runs; + + if (!oldFragmentRuns || !newFragmentRuns || oldFragmentRuns.length !== newFragmentRuns.length) + return NO; + + var l = oldFragmentRuns.length; + + for (var i = 0; i < l; i++) + { + if (newFragmentRuns[i].string !== oldFragmentRuns[i].string) + return NO; + + if (!_RectEqualToRectHorizontally(newLineFragment._fragmentRect, _fragmentRect)) + return NO; + + if (newFragmentRuns[i].color !== oldFragmentRuns[i].color || newFragmentRuns[i].font !== oldFragmentRuns[i].font) + return NO; + + } + + return YES; +} + +- (void)_relocateVerticallyByY:(double)verticalOffset rangeOffset:(unsigned)rangeOffset +{ + var l = _runs.length; + + _range.location += rangeOffset; + + for (var i = 0; i < l; i++) + { + _runs[i]._range.location += rangeOffset; + + if (verticalOffset && _runs[i].elem) + { + _runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px'; + _runs[i].DOMpatched = YES; + } + } + + if (!verticalOffset) + return NO; + + _fragmentRect.origin.y += verticalOffset; + _usedRect.origin.y += verticalOffset; + + var l = _glyphsFrames.length; + + for (var i = 0; i < l ; i++) + { + _glyphsFrames[i].origin.y += verticalOffset; + } +} + +@end + +@implementation _CPTemporaryAttributes : CPObject +{ + CPDictionary _attributes; + CPRange _range; +} + +- (id)initWithRange:(CPRange)aRange attributes:(CPDictionary)attributes +{ + if (self = [super init]) + { + _attributes = attributes; + _range = CPMakeRangeCopy(aRange); + } + + return self; +} + +- (CPString)description +{ + return [super description] + + "\n\t_range="+CPStringFromRange(_range) + + "\n\t_attributes="+[_attributes description]; +} + +@end \ No newline at end of file diff --git a/AppKit/CPTextView/CPParagraphStyle.j b/AppKit/CPTextView/CPParagraphStyle.j new file mode 100644 index 000000000..54d88ee73 --- /dev/null +++ b/AppKit/CPTextView/CPParagraphStyle.j @@ -0,0 +1,223 @@ +/* + * CPParagraphStyle.j + * AppKit + * + * FIXME + * This is basically a stub. + * We need to store all the spacing informations as well as writing direction (among others) + * + * Created by Daniel Boehringer on 11/01/2014 + * Copyright Daniel Boehringer 2014. + * + * 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 + */ + +@import +@import + +@import "CPText.j" + +CPLeftTabStopType = 0; + +CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName"; + +var _sharedDefaultParagraphStyle, + _defaultTabStopArray; + +@implementation CPParagraphStyle : CPObject +{ + CPArray _tabStops @accessors(property=tabStops); + CPTextAlignment _alignment @accessors(property=alignment); + unsigned _firstLineHeadIndent @accessors(property=firstLineHeadIndent); + unsigned _headIndent @accessors(property=headIndent); + unsigned _tailIndent @accessors(property=tailIndent); + unsigned _paragraphSpacing @accessors(property=paragraphSpacing); + unsigned _minimumLineHeight @accessors(property=minimumLineHeight); + unsigned _maximumLineHeight @accessors(property=maximumLineHeight); + unsigned _lineSpacing @accessors(property=lineSpacing); +} + + +#pragma mark - +#pragma mark Class methods + ++ (CPParagraphStyle)defaultParagraphStyle +{ + if (!_sharedDefaultParagraphStyle) + _sharedDefaultParagraphStyle = [self new]; + + return _sharedDefaultParagraphStyle; +} + ++ (CPArray)_defaultTabStops +{ + if (!_defaultTabStopArray) + { + var i; + _defaultTabStopArray = []; + + // FIXME: Define constants for these magic numbers: 13, 28 + for (i = 1; i < 16 ; i++) + { + _defaultTabStopArray.push([[CPTextTab alloc] initWithType:CPLeftTabStopType location:i * 28]); + } + } + + return _defaultTabStopArray; +} + + +#pragma mark - +#pragma mark Init methods + +- (id)init +{ + [self _initWithDefaults]; + + return self; +} + +- (CPParagraphStyle)initWithParagraphStyle:(CPParagraphStyle)other +{ + self = [super init]; + + _tabStops = [other._tabStops copy]; + _alignment = other._alignment; + _firstLineHeadIndent = other._firstLineHeadIndent; + _headIndent = other._headIndent; + _tailIndent = other._tailIndent; + _paragraphSpacing = other._paragraphSpacing; + _minimumLineHeight = other._minimumLineHeight; + _maximumLineHeight = other._maximumLineHeight; + _lineSpacing = other._lineSpacing; + + return self; +} + +- (void)_initWithDefaults +{ + _alignment = CPLeftTextAlignment; + _tabStops = [[[self class] _defaultTabStops] copy]; +} + +- (void)addTabStop:(CPTextTab)aStop +{ + _tabStops.push(aStop); +} + +- (id)copy +{ + var other = [[self class] alloc]; + + return [other initWithParagraphStyle:self]; +} + +@end + + +var CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey", + CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey", + CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey", + CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey", + CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey", + CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey", + CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey", + CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey", + CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey"; + +@implementation CPParagraphStyle (CPCoding) + +- (id)initWithCoder:(id)aCoder +{ + self = [self init]; + + if (self) + { + _tabStops = [aCoder decodeObjectForKey:"CPParagraphStyleTabStopsKey"]; + _alignment = [aCoder decodeIntForKey:"CPParagraphStyleAlignmentKey"]; + _firstLineHeadIndent = [aCoder decodeIntForKey:"CPParagraphStyleFirstLineHeadIndentKey"]; + _headIndent = [aCoder decodeIntForKey:"CPParagraphStyleHeadIndentKey"]; + _tailIndent = [aCoder decodeIntForKey:"CPParagraphStyleTailIndentKey"]; + _paragraphSpacing = [aCoder decodeIntForKey:"CPParagraphStyleParagraphSpacingKey"]; + _minimumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMinimumLineHeightKey"]; + _maximumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMaximumLineHeightKey"]; + _lineSpacing = [aCoder decodeIntForKey:"CPParagraphStyleLineSpacingKey"]; + } + + return self; +} + +- (void)encodeWithCoder:(id)aCoder +{ + [aCoder encodeInt:_alignment forKey:"CPParagraphStyleAlignmentKey"]; + [aCoder encodeObject:_tabStops forKey:"CPParagraphStyleTabStopsKey"]; + [aCoder encodeInt:_firstLineHeadIndent forKey:"CPParagraphStyleFirstLineHeadIndentKey"]; + [aCoder encodeInt:_headIndent forKey:"CPParagraphStyleHeadIndentKey"]; + [aCoder encodeInt:_tailIndent forKey:"CPParagraphStyleTailIndentKey"]; + [aCoder encodeInt:_paragraphSpacing forKey:"CPParagraphStyleParagraphSpacingKey"]; + [aCoder encodeInt:_minimumLineHeight forKey:"CPParagraphStyleMinimumLineHeightKey"]; + [aCoder encodeInt:_maximumLineHeight forKey:"CPParagraphStyleMaximumLineHeightKey"]; + [aCoder encodeInt:_lineSpacing forKey:"CPParagraphStyleLineSpacingKey"]; +} + + +@end + + +@implementation CPTextTab : CPObject +{ + int _type @accessors(property = tabStopType); + double _location @accessors(property = location); +} + +- (id)initWithType:(CPTabStopType) aType location:(double) aLocation +{ + if ([self = [super init]]) + { + _type = aType; + _location = aLocation; + } + + return self; +} + +@end + + +var CPTextTabTypeKey = @"CPTextTabTypeKey", + CPTextTabLocationKey = @"CPTextTabLocationKey"; + +@implementation CPTextTab (CPCoding) + +- (id)initWithCoder:(id)aCoder +{ + self = [self init]; + + if (self) + { + _type = [aCoder decodeIntForKey:"CPTextTabTypeKey"]; + _location = [aCoder decodeDoubleForKey:"CPTextTabLocationKey"]; + } + + return self; +} + +- (void)encodeWithCoder:(id)aCoder +{ + [aCoder encodeInt:_type forKey:"CPTextTabTypeKey"]; + [aCoder encodeDouble:_location forKey:"CPTextTabLocationKey"]; +} + +@end diff --git a/AppKit/CPTextView/CPTextContainer.j b/AppKit/CPTextView/CPTextContainer.j new file mode 100644 index 000000000..5c3f485a9 --- /dev/null +++ b/AppKit/CPTextView/CPTextContainer.j @@ -0,0 +1,255 @@ +/* + * CPTextContainer.j + * AppKit + * + * Created by Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * 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 + */ + +@import +@import "CPLayoutManager.j" + +@class CPTextView +@class CPLayoutManager + +/* + @global + @group CPLineSweepDirection +*/ +CPLineSweepLeft = 0; +/* + @global + @group CPLineSweepDirection +*/ +CPLineSweepRight = 1; +/* + @global + @group CPLineSweepDirection +*/ +CPLineSweepDown = 2; +/* + @global + @group CPLineSweepDirection +*/ +CPLineSweepUp = 3; + +/* + @global + @group CPLineMovementDirection +*/ +CPLineDoesntMoves = 0; +/* + @global + @group CPLineMovementDirection +*/ +CPLineMovesLeft = 1; +/* + @global + @group CPLineMovementDirection +*/ +CPLineMovesRight = 2; +/* + @global + @group CPLineMovementDirection +*/ +CPLineMovesDown = 3; +/* + @global + @group CPLineMovementDirection +*/ +CPLineMovesUp = 4; + +/*! + @ingroup appkit + @class CPTextContainer +*/ +@implementation CPTextContainer : CPObject +{ + float _lineFragmentPadding @accessors(property=lineFragmentPadding); + CGSize _size @accessors(property=containerSize) + CPLayoutManager _layoutManager @accessors(property=layoutManager); + CPTextView _textView @accessors(property=textView); + BOOL _inResizing; +} + + +#pragma mark - +#pragma mark Init methods + +- (id)initWithContainerSize:(CGSize)aSize +{ + self = [super init]; + + if (self) + { + _size = aSize; + [self _init]; + } + + return self; +} + +- (id)init +{ + return [self initWithContainerSize:CPMakeSize(1e7, 1e7)]; +} + +- (void)_init +{ + _lineFragmentPadding = 0.0; + + _layoutManager = [[CPLayoutManager alloc] init]; + [_layoutManager addTextContainer:self]; +} + +#pragma mark - +#pragma mark Setter methods + +- (void)setContainerSize:(CGSize)someSize +{ + var oldSize = _size; + + _size = CGSizeMakeCopy(someSize); + + if (oldSize.width != _size.width) + { + _inResizing = YES; + [_layoutManager invalidateLayoutForCharacterRange:CPMakeRange(0, [[_layoutManager textStorage] length]) + isSoft:NO + actualCharacterRange:NULL]; + + [_layoutManager _validateLayoutAndGlyphs]; + [_textView sizeToFit]; // this is necessary to adopt the height of CPTextView in case of rewrapping + _inResizing = NO; + } +} + +// Controls whether the receiver adjusts the width of its bounding rectangle when its text view is resized. +- (void)setWidthTracksTextView:(BOOL)flag +{ + [_textView setPostsFrameChangedNotifications:flag]; + + if (flag) + { + [[CPNotificationCenter defaultCenter] addObserver:self + selector:@selector(textViewFrameChanged:) + name:CPViewFrameDidChangeNotification + object:_textView]; + } + else + { + [[CPNotificationCenter defaultCenter] removeObserver:self + name:CPViewFrameDidChangeNotification + object:_textView]; + } +} + +- (void)textViewFrameChanged:(CPNotification)aNotification +{ + var newSize = CGSizeMake([_textView frame].size.width, _size.height); + + [self setContainerSize:newSize]; +} + +- (void)setTextView:(CPTextView)aTextView +{ + if (_textView) + { + [self _removeAllLines]; + [_textView setTextContainer:nil]; + } + + _textView = aTextView; + + if (_textView) + [_textView setTextContainer:self]; + + [_layoutManager textContainerChangedTextView:self]; +} + +- (BOOL)containsPoint:(CGPoint)aPoint +{ + return CGRectContainsPoint(CGRectMake(0, 0, _size.width, _size.height), aPoint); +} + +- (BOOL)isSimpleRectangularTextContainer +{ + return YES; +} + +- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect + sweepDirection:(CPLineSweepDirection)sweep + movementDirection:(CPLineMovementDirection)movement + remainingRect:(CGRectPointer)remainingRect +{ + var resultRect = CGRectCreateCopy(proposedRect); + + if (sweep != CPLineSweepRight || movement != CPLineMovesDown) + { + CPLog.trace(@"FIXME: unsupported sweep (" + sweep + ") or movement (" + movement + ")"); + return CGRectMakeZero(); + } + + if (resultRect.origin.x + resultRect.size.width > _size.width) + resultRect.size.width = _size.width - resultRect.origin.x; + + if (resultRect.size.width < 0) + resultRect = CGRectMakeZero(); + + if (remainingRect) + { + remainingRect.origin.x = resultRect.origin.x + resultRect.size.width; + remainingRect.origin.y = resultRect.origin.y; + remainingRect.size.height = resultRect.size.height; + remainingRect.size.width = _size.width - (resultRect.origin.x + resultRect.size.width); + } + + return resultRect; +} + +@end + + +var CPTextContainerSizeKey = @"CPTextContainerSizeKey", + CPTextContainerLayoutManagerKey = @"CPTextContainerLayoutManagerKey"; + +@implementation CPTextContainer (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + if (self) + { + [self _init]; + + _size = [aCoder decodeSizeForKey:CPTextContainerSizeKey]; + + _layoutManager = [aCoder decodeObjectForKey:CPTextContainerLayoutManagerKey]; + [_layoutManager addTextContainer:self]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeSize:_size forKey:CPTextContainerSizeKey]; + [aCoder encodeObject:_layoutManager forKey:CPTextContainerLayoutManagerKey]; +} + +@end \ No newline at end of file diff --git a/AppKit/CPTextView/CPTextStorage.j b/AppKit/CPTextView/CPTextStorage.j new file mode 100644 index 000000000..65a71cf1a --- /dev/null +++ b/AppKit/CPTextView/CPTextStorage.j @@ -0,0 +1,303 @@ +/* + * CPTextStorage.j + * AppKit + * + * Created by Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * 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 + */ + + +@import +@import + +@import "CPText.j" +@import "CPFont.j" + +@class CPLayoutManager; + +CPTextStorageEditedAttributes = 1; +CPTextStorageEditedCharacters = 2; + +CPTextStorageWillProcessEditingNotification = @"CPTextStorageWillProcessEditingNotification"; +CPTextStorageDidProcessEditingNotification = @"CPTextStorageDidProcessEditingNotification"; + +@protocol CPTextStorageDelegate + +- (void)textStorageWillProcessEditing:(CPNotification)aNotification; +- (void)textStorageDidProcessEditing:(CPNotification)aNotification; + +@end + + +var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1, + CPTextStorageDelegate_textStorageDidProcessEditing_ = 1 << 2; + +/*! + @ingroup appkit + @class CPTextStorage +*/ +@implementation CPTextStorage : CPMutableAttributedString +{ + CPColor _foregroundColor @accessors(property=foregroundColor); + CPFont _font @accessors(property=font); + CPMutableArray _layoutManagers @accessors(getter=layoutManagers); + CPRange _editedRange @accessors(getter=editedRange); + id _delegate @accessors(property=delegate); + int _changeInLength @accessors(property=changeInLength); + unsigned _editedMask @accessors(property=editedMask); + + int _editCount; // {begin,end}Editing counter + unsigned _implementedDelegateMethods; +} + + +#pragma mark - +#pragma mark Init methods + +- (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes +{ + self = [super initWithString:aString attributes:attributes]; + + if (self) + { + _layoutManagers = [[CPMutableArray alloc] init]; + _editedRange = CPMakeRange(CPNotFound, 0); + _changeInLength = 0; + _editedMask = 0; + } + + return self; +} + +- (id)initWithString:(CPString)aString +{ + return [self initWithString:aString attributes:nil]; +} + +- (id)init +{ + return [self initWithString:@"" attributes:nil]; +} + + +#pragma mark - +#pragma mark Delegate methods + +- (void)setDelegate:(id )aDelegate +{ + if (_delegate === aDelegate) + return; + + _implementedDelegateMethods = 0; + _delegate = aDelegate; + + if (_delegate) + { + if ([_delegate respondsToSelector:@selector(textStorageWillProcessEditing:)]) + _implementedDelegateMethods |= CPTextStorageDelegate_textStorageWillProcessEditing_; + + if ([_delegate respondsToSelector:@selector(textStorageDidProcessEditing:)]) + _implementedDelegateMethods |= CPTextStorageDelegate_textStorageDidProcessEditing_; + } +} + + +#pragma mark - +#pragma mark Layout manager methods + +- (void)addLayoutManager:(CPLayoutManager)aManager +{ + if ([_layoutManagers containsObject:aManager]) + return + + [aManager setTextStorage:self]; + [_layoutManagers addObject:aManager]; +} + +- (void)removeLayoutManager:(CPLayoutManager)aManager +{ + if (![_layoutManagers containsObject:aManager]) + return + + [aManager setTextStorage:nil]; + [_layoutManagers removeObject:aManager]; +} + +- (void)invalidateAttributesInRange:(CPRange)aRange +{ + /* FIXME: stub */ +} + + +#pragma mark - +#pragma mark Editing methods + +- (void)processEditing +{ + [self _sendDelegateWillProcessEditingNotification]; + [self invalidateAttributesInRange:[self editedRange]]; + [self _sendDelegateDidProcessEditingNotification]; + + var c = [_layoutManagers count]; + + for (var i = 0; i < c; i++) + { + [[_layoutManagers objectAtIndex:i] textStorage:self + edited:_editedMask + range:_editedRange + changeInLength:_changeInLength + invalidatedRange:_editedRange]; + } + + _editedRange.location = CPNotFound; + _editedMask = 0; + _changeInLength = 0; +} + +- (void)beginEditing +{ + if (_editCount == 0) + _editedRange = CPMakeRange(CPNotFound, 0); + + _editCount++; +} + +- (void)endEditing +{ + _editCount--; + + if (_editCount == 0) + [self processEditing]; +} + +- (void)edited:(unsigned)editedMask range:(CPRange)aRange changeInLength:(int)lengthChange +{ + var copyRange = CPMakeRangeCopy(aRange); + + if (_editCount == 0) // used outside a beginEditing/endEditing + { + _editedMask = editedMask; + _changeInLength = lengthChange; + copyRange.length += lengthChange; + _editedRange = copyRange; + [self processEditing]; + } + else + { + _editedMask |= editedMask; + _changeInLength += lengthChange; + copyRange.length += lengthChange; + + if (_editedRange.location == CPNotFound) + _editedRange = copyRange; + else + _editedRange = CPUnionRange(_editedRange,copyRange); + } +} + +- (void)removeAttribute:(CPString)anAttribute range:(CPRange)aRange +{ + [self beginEditing]; + [super removeAttribute:anAttribute range:aRange]; + [self edited:CPTextStorageEditedAttributes range:aRange changeInLength:0]; + [self endEditing]; +} + +- (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange +{ + [self beginEditing]; + [super addAttributes:aDictionary range:aRange]; + [self edited:CPTextStorageEditedAttributes range:aRange changeInLength:0]; + [self endEditing]; +} + +- (void)deleteCharactersInRange:(CPRange)aRange +{ + [self beginEditing]; + [super deleteCharactersInRange:aRange]; + [self edited:CPTextStorageEditedCharacters range:aRange changeInLength:-aRange.length]; + [self endEditing]; +} + +- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString +{ + [self beginEditing]; + [super replaceCharactersInRange:aRange withString:aString]; + [self edited:CPTextStorageEditedCharacters range:aRange changeInLength:([aString length] - aRange.length)]; + [self endEditing]; +} + +- (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString +{ + [self beginEditing]; + [super replaceCharactersInRange:aRange withAttributedString:aString]; + [self edited:(CPTextStorageEditedAttributes | CPTextStorageEditedCharacters) range:aRange changeInLength:([aString length] - aRange.length)]; + [self endEditing]; +} + +- (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange +{ + if (!aRange.length) + return [CPAttributedString new]; + + return [super attributedSubstringFromRange:aRange]; +} + +@end + + +@implementation CPTextStorage (CPTextStorageDelegate) + +- (void)_sendDelegateWillProcessEditingNotification +{ + if (_implementedDelegateMethods & CPTextStorageDelegate_textStorageWillProcessEditing_) + [_delegate textStorageWillProcessEditing:[[CPNotification alloc] initWithName:CPTextStorageWillProcessEditingNotification object:self userInfo:nil]]; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextStorageWillProcessEditingNotification object:self]; +} + +- (void)_sendDelegateDidProcessEditingNotification +{ + if (_implementedDelegateMethods & CPTextStorageDelegate_textStorageDidProcessEditing_) + [_delegate textStorageWillProcessEditing:[[CPNotification alloc] initWithName:CPTextStorageDidProcessEditingNotification object:self userInfo:nil]]; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextStorageDidProcessEditingNotification object:self]; +} + +@end + + +@implementation CPTextStorage (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super initWithCoder:aCoder]; + + if (self) + { + + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [super encodeWithCoder:aCoder]; +} + +@end \ No newline at end of file diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j new file mode 100644 index 000000000..7080aa297 --- /dev/null +++ b/AppKit/CPTextView/CPTextView.j @@ -0,0 +1,2670 @@ +/* + * CPTextView.j + * AppKit + * + * Created by Daniel Boehringer on 27/12/2013. + * All modifications copyright Daniel Boehringer 2013. + * Extensive code formatting and review by Andrew Hankinson + * Based on original work by + * Created by Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * 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 + */ + +@import "CPText.j" +@import "CPPasteboard.j" +@import "CPColorPanel.j" +@import "CPFontManager.j" +@import "CPTextStorage.j" +@import "CPTextContainer.j" +@import "CPLayoutManager.j" + +@import "_CPRTFParser.j" +@import "_CPRTFProducer.j" + + +@class CPClipView; +@class _CPSelectionBox; +@class _CPCaret; +@class _CPNativeInputManager; + +@protocol CPTextViewDelegate + +- (BOOL)textView:(CPTextView)aTextView doCommandBySelector:(SEL)aSelector; +- (BOOL)textView:(CPTextView)aTextView shouldChangeTextInRange:(CPRange)affectedCharRange replacementString:(CPString)replacementString; +- (CPDictionary)textView:(CPTextView)textView shouldChangeTypingAttributes:(CPDictionary)oldTypingAttributes toAttributes:(CPDictionary)newTypingAttributes; +- (CPRange)textView:(CPTextView)aTextView willChangeSelectionFromCharacterRange:(CPRange)oldSelectedCharRange toCharacterRange:(CPRange)newSelectedCharRange; +- (void)textViewDidChangeSelection:(CPNotification)aNotification; +- (void)textViewDidChangeTypingAttributes:(CPNotification)aNotification; + +@end + +_MakeRangeFromAbs = function(a1, a2) +{ + return (a1 < a2) ? CPMakeRange(a1, a2 - a1) : CPMakeRange(a2, a1 - a2); +}; + +_MidRange = function(a1) +{ + return Math.floor((CPMaxRange(a1) + a1.location) / 2); +}; + +function _isWhitespaceCharacter(chr) +{ + return (chr === '\n' || chr === '\r' || chr === ' ' || chr === '\t'); +} + +_characterTripletFromStringAtIndex = function(string, index) +{ + if ([string isKindOfClass:CPAttributedString]) + string = string._string; + + var tripletRange = _MakeRangeFromAbs(MAX(0, index - 1), MIN(string.length, index + 2)); + + return [string substringWithRange:tripletRange]; +} + +_regexMatchesStringAtIndex=function(regex, string, index) +{ + var triplet = _characterTripletFromStringAtIndex(string, index); + + return regex.exec(triplet) !== null; +} + +// these two functions are to support chrome rich native paste +_CPwalkTheDOM = function(node, func) +{ + func(node); + node = node.firstChild; + while (node) + { + _CPwalkTheDOM(node, func); + node = node.nextSibling; + } +} + +/* + CPSelectionGranularity +*/ +@typedef CPSelectionGranularity +CPSelectByCharacter = 0; +CPSelectByWord = 1; +CPSelectByParagraph = 2; + +var kDelegateRespondsTo_textShouldBeginEditing = 1 << 0, + kDelegateRespondsTo_textView_doCommandBySelector = 1 << 1, + kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange = 1 << 2, + kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString = 1 << 3, + kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes = 1 << 4, + kDelegateRespondsTo_textView_textDidChange = 1 << 5, + kDelegateRespondsTo_textView_didChangeSelection = 1 << 6, + kDelegateRespondsTo_textView_didChangeTypingAttributes = 1 << 7; + +@class _CPCaret; + +/*! + @ingroup appkit + @class CPTextView +*/ +@implementation CPTextView : CPText +{ + BOOL _allowsUndo @accessors(property=allowsUndo); + BOOL _isHorizontallyResizable @accessors(getter=isHorizontallyResizable, setter=setHorinzontallyResizable:); + BOOL _isVerticallyResizable @accessors(getter=isVerticallyResizable, setter=setVerticallyResizable:); + BOOL _usesFontPanel @accessors(property=usesFontPanel); + CGPoint _textContainerOrigin @accessors(getter=textContainerOrigin); + CGSize _minSize @accessors(property=minSize); + CGSize _maxSize @accessors(property=maxSize); + CGSize _textContainerInset @accessors(property=textContainerInset); + CPColor _insertionPointColor @accessors(property=insertionPointColor); + CPColor _textColor @accessors(property=textColor); + CPDictionary _selectedTextAttributes @accessors(property=selectedTextAttributes); + CPDictionary _typingAttributes @accessors(property=typingAttributes); + CPFont _font @accessors(property=font); + CPLayoutManager _layoutManager @accessors(getter=layoutManager); + CPRange _selectionRange @accessors(getter=selectedRange); + CPSelectionGranularity _selectionGranularity @accessors(property=selectionGranularity); + + CPSelectionGranularity _previousSelectionGranularity; // private + CPSelectionGranularity _copySelectionGranularity; // private + + CPTextContainer _textContainer @accessors(property=textContainer); + CPTextStorage _textStorage @accessors(getter=textStorage); + id _delegate @accessors(property=delegate); + + unsigned _delegateRespondsToSelectorMask; + + int _startTrackingLocation; + + _CPCaret _caret; + CPTimer _scrollingTimer; + + BOOL _scrollingDownward; + + int _stickyXLocation; + + CPArray _selectionSpans; + CPView _observedClipView; + CGRect _exposedRect; + + CPTimer _scrollingTimer; +} + + +#pragma mark - +#pragma mark Class methods + +/* FIXME + just a testing characterSet + all of this depend of the current language. + Need some CPLocale support and maybe even a FSM... + */ + +#pragma mark - +#pragma mark Init methods + +- (id)initWithFrame:(CGRect)aFrame textContainer:(CPTextContainer)aContainer +{ + if (self = [super initWithFrame:aFrame]) + { + [self _init]; + [aContainer setTextView:self]; + + [self setEditable:YES]; + [self setSelectable:YES]; + [self setRichText:NO]; + [self setBackgroundColor:[CPColor whiteColor]]; + + _usesFontPanel = YES; + _allowsUndo = YES; + + _selectedTextAttributes = [CPDictionary dictionaryWithObject:[CPColor selectedTextBackgroundColor] + forKey:CPBackgroundColorAttributeName]; + + _insertionPointColor = [CPColor blackColor]; + + _textColor = [CPColor blackColor]; + _font = [CPFont systemFontOfSize:12.0]; + [self setFont:_font]; + + _typingAttributes = [[CPDictionary alloc] initWithObjects:[_font, _textColor] forKeys:[CPFontAttributeName, CPForegroundColorAttributeName]]; + } + + [self registerForDraggedTypes:[CPColorDragType]]; + + return self; +} + +- (id)initWithFrame:(CGRect)aFrame +{ + var container = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(aFrame.size.width, 1e7)]; + + return [self initWithFrame:aFrame textContainer:container]; +} + +- (void)_init +{ +#if PLATFORM(DOM) + _DOMElement.style.cursor = "text"; +#endif + + _selectionRange = CPMakeRange(0, 0); + _textContainerInset = CGSizeMake(2, 0); + _textContainerOrigin = CGPointMake(_bounds.origin.x, _bounds.origin.y); + + _selectionGranularity = CPSelectByCharacter; + + _minSize = CGSizeCreateCopy(_frame.size); + _maxSize = CGSizeMake(_frame.size.width, 1e7); + + _isVerticallyResizable = YES; + _isHorizontallyResizable = NO; + + _typingAttributes = [CPMutableDictionary new]; + _selectedTextAttributes = [CPMutableDictionary new]; + + _caret = [[_CPCaret alloc] initWithTextView:self]; + [_caret setRect:CGRectMake(0, 0, 1, 11)] +} + +- (void)_setObserveWindowKeyNotifications:(BOOL)shouldObserve +{ + if (shouldObserve) + { + [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowDidResignKey:) name:CPWindowDidResignKeyNotification object:[self window]]; + [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowDidBecomeKey:) name:CPWindowDidBecomeKeyNotification object:[self window]]; + } + else + { + [[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidResignKeyNotification object:[self window]]; + [[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidBecomeKeyNotification object:[self window]]; + } +} + +- (void)_removeObservers +{ + if (!_isObserving) + return; + + [super _removeObservers]; + [self _setObserveWindowKeyNotifications:NO]; +} + +- (void)_addObservers +{ + if (_isObserving) + return; + + [super _addObservers]; + [self _setObserveWindowKeyNotifications:YES]; + [self _startObservingClipView]; +} +- (void)_startObservingClipView +{ + if (!_observedClipView) + return; + + var defaultCenter = [CPNotificationCenter defaultCenter]; + + [_observedClipView setPostsFrameChangedNotifications:YES]; + [_observedClipView setPostsBoundsChangedNotifications:YES]; + + [defaultCenter addObserver:self + selector:@selector(superviewFrameChanged:) + name:CPViewFrameDidChangeNotification + object:_observedClipView]; + + [defaultCenter addObserver:self + selector:@selector(superviewBoundsChanged:) + name:CPViewBoundsDidChangeNotification + object:_observedClipView]; +} +- (CGRect)exposedRect +{ + if (!_exposedRect) + { + var superview = [self superview]; + + if ([superview isKindOfClass:[CPClipView class]]) + _exposedRect = [superview bounds]; + else + _exposedRect = [self bounds]; + } + + return _exposedRect; +} + +/*! + @ignore +*/ +- (void)superviewBoundsChanged:(CPNotification)aNotification +{ + _exposedRect = nil; + [self setNeedsDisplay:YES]; +} + +/*! + @ignore +*/ +- (void)superviewFrameChanged:(CPNotification)aNotification +{ + _exposedRect = nil; +} + +- (void)viewWillMoveToSuperview:(CPView)aView +{ + if ([aView isKindOfClass:[CPClipView class]]) + _observedClipView = aView; + else + [self _stopObservingClipView]; + + [super viewWillMoveToSuperview:aView]; +} + +- (void)_stopObservingClipView +{ + if (!_observedClipView) + return; + + var defaultCenter = [CPNotificationCenter defaultCenter]; + + [defaultCenter removeObserver:self + name:CPViewFrameDidChangeNotification + object:_observedClipView]; + + [defaultCenter removeObserver:self + name:CPViewBoundsDidChangeNotification + object:_observedClipView]; + + _observedClipView = nil; +} + +- (void)_windowDidResignKey:(CPNotification)aNotification +{ + if (![[self window] isKeyWindow]) + [self resignFirstResponder]; +} + +- (void)_windowDidBecomeKey:(CPNotification)aNotification +{ + if ([self _isFocused]) + [self _becomeFirstResponder]; +} + +#pragma mark - +#pragma mark Copy and paste methods + +- (void)copy:(id)sender +{ + _copySelectionGranularity = _previousSelectionGranularity; + [super copy:sender]; + + if (![self isRichText]) + return; + + var selectedRange = [self selectedRange], + pasteboard = [CPPasteboard generalPasteboard], + stringForPasting = [[self textStorage] attributedSubstringFromRange:CPMakeRangeCopy(selectedRange)], + richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}]; + + [pasteboard declareTypes:[CPStringPboardType, CPRTFPboardType] owner:nil]; + [pasteboard setString:stringForPasting._string forType:CPStringPboardType]; + [pasteboard setString:richData forType:CPRTFPboardType]; +} + +- (void)paste:(id)sender +{ + if (![sender isKindOfClass:_CPNativeInputManager] && [[CPApp currentEvent] type] != CPAppKitDefined) + return + + var stringForPasting = [self _stringForPasting]; + + if (!stringForPasting) + return; + + if (_copySelectionGranularity > 0 && _selectionRange.location > 0) + { + if (!_isWhitespaceCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]) && + _selectionRange.location != [_layoutManager numberOfCharacters]) + { + [self insertText:" "]; + } + } + + if (_copySelectionGranularity == CPSelectByParagraph) + { + var peekStr = stringForPasting, + i = 0; + + if (![stringForPasting isKindOfClass:[CPString class]]) + peekStr = stringForPasting._string; + + while (_isWhitespaceCharacter([peekStr characterAtIndex:i])) + i++; + + if (i) + { + if ([stringForPasting isKindOfClass:[CPString class]]) + stringForPasting = [stringForPasting stringByReplacingCharactersInRange:CPMakeRange(0, i) withString:'']; + else + [stringForPasting replaceCharactersInRange:CPMakeRange(0, i) withString:'']; + } + } + + [self insertText:stringForPasting]; + + if (_copySelectionGranularity > 0) + { + if (!_isWhitespaceCharacter([[_textStorage string] characterAtIndex:CPMaxRange(_selectionRange)]) && + !_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, _selectionRange.location - 1)]) && + _selectionRange.location != [_layoutManager numberOfCharacters]) + { + [self insertText:" "]; + } + } +} + +#pragma mark - +#pragma mark Responders method + +- (BOOL)acceptsFirstResponder +{ + return [self isSelectable]; // editable textviews are automatically selectable +} + +- (void)_becomeFirstResponder +{ + [self updateInsertionPointStateAndRestartTimer:YES]; + [[CPFontManager sharedFontManager] setSelectedFont:[self font] isMultiple:NO]; + [self setNeedsDisplay:YES]; + [[CPRunLoop currentRunLoop] performSelector:@selector(focusForTextView:) target:[_CPNativeInputManager class] argument:self order:0 modes:[CPDefaultRunLoopMode]]; +} + + +- (BOOL)becomeFirstResponder +{ + [super becomeFirstResponder]; + [self _becomeFirstResponder]; + + return YES; +} + +- (BOOL)resignFirstResponder +{ + [_caret stopBlinking]; + [self setNeedsDisplay:YES]; + [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; + + return YES; +} + + +#pragma mark - +#pragma mark Delegate methods + +/*! + TODO : documentation +*/ +- (void)setDelegate:(id )aDelegate +{ + if (aDelegate === _delegate) + return; + + _delegateRespondsToSelectorMask = 0; + _delegate = aDelegate; + + if (_delegate) + { + if ([_delegate respondsToSelector:@selector(textDidChange:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_textDidChange; + + if ([_delegate respondsToSelector:@selector(textViewDidChangeSelection:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_didChangeSelection; + + if ([_delegate respondsToSelector:@selector(textViewDidChangeTypingAttributes:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_didChangeTypingAttributes; + + if ([_delegate respondsToSelector:@selector(textView:doCommandBySelector:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_doCommandBySelector; + + if ([_delegate respondsToSelector:@selector(textShouldBeginEditing:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textShouldBeginEditing; + + if ([_delegate respondsToSelector:@selector(textView:willChangeSelectionFromCharacterRange:toCharacterRange:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange; + + if ([_delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementString:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString; + + if ([_delegate respondsToSelector:@selector(textView:shouldChangeTypingAttributes:toAttributes:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes; + } +} + + +#pragma mark - +#pragma mark Key window methods + +- (void)becomeKeyWindow +{ + [self setNeedsDisplay:YES]; +} + +/*! + @ignore +*/ +- (void)resignKeyWindow +{ + [self setNeedsDisplay:YES]; +} + +- (BOOL)_isFirstResponder +{ + return [[self window] firstResponder] === self; +} + +- (BOOL)_isFocused +{ + return [[self window] isKeyWindow] && [self _isFirstResponder]; +} + + +#pragma mark - +#pragma mark Undo redo methods + +- (void)undo:(id)sender +{ + if (_allowsUndo) + [[[self window] undoManager] undo]; +} + +- (void)redo:(id)sender +{ + if (_allowsUndo) + [[[self window] undoManager] redo]; +} + + +#pragma mark - +#pragma mark Accessors + +- (CPString)stringValue +{ + return _textStorage._string; +} + +// fixme: rich text should return attributed string, shouldn't it? +- (CPString)objectValue +{ + return [self stringValue]; +} + +- (void)setString:(CPString)aString +{ + [_textStorage replaceCharactersInRange:CPMakeRange(0, [_layoutManager numberOfCharacters]) withString:aString]; + + if (CPMaxRange(_selectionRange) > [_layoutManager numberOfCharacters]) + [self setSelectedRange:CPMakeRange([_layoutManager numberOfCharacters], 0)]; + + [self didChangeText]; + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + [self setNeedsDisplay:YES]; +} + +- (CPString)string +{ + return [_textStorage string]; +} + +// KVO support +- (void)setValue:(CPString)aValue +{ + [self setString:[aValue description]]; +} + +- (id)value +{ + [self string]; +} + +- (void)setTextContainer:(CPTextContainer)aContainer +{ + _textContainer = aContainer; + _layoutManager = [_textContainer layoutManager]; + _textStorage = [_layoutManager textStorage]; + [_textStorage setFont:_font]; + [_textStorage setForegroundColor:_textColor]; + + [self invalidateTextContainerOrigin]; +} + +- (void)setTextContainerInset:(CGSize)aSize +{ + _textContainerInset = aSize; + [self invalidateTextContainerOrigin]; +} + +- (void)invalidateTextContainerOrigin +{ + _textContainerOrigin.x = _bounds.origin.x; + _textContainerOrigin.x += _textContainerInset.width; + + _textContainerOrigin.y = _bounds.origin.y; + _textContainerOrigin.y += _textContainerInset.height; +} + +- (void)doCommandBySelector:(SEL)aSelector +{ + if (![self _sendDelegateDoCommandBySelector:aSelector]) + [super doCommandBySelector:aSelector]; +} + +- (void)didChangeText +{ + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextDidChangeNotification object:self]; + + if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_textDidChange) + [_delegate textDidChange:[[CPNotification alloc] initWithName:CPTextDidChangeNotification object:self userInfo:nil]]; +} + +- (BOOL)shouldChangeTextInRange:(CPRange)aRange replacementString:(CPString)aString +{ + if (![self isEditable]) + return NO; + + return [self _sendDelegateTextShouldBeginEditing] && [self _sendDelegateShouldChangeTextInRange:aRange replacementString:aString]; +} + + +#pragma mark - +#pragma mark Insert characters methods + +- (void)_fixupReplaceForRange:(CPRange)aRange +{ + [self setSelectedRange:aRange]; + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + [self scrollRangeToVisible:_selectionRange]; + [self setNeedsDisplay:YES]; +} + +- (void)_replaceCharactersInRange:aRange withAttributedString:(CPString)aString +{ + [[[[self window] undoManager] prepareWithInvocationTarget:self] + _replaceCharactersInRange:CPMakeRange(aRange.location, [aString length]) + withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(aRange)]]; + + [_textStorage replaceCharactersInRange:aRange withAttributedString:aString]; + [self _fixupReplaceForRange:CPMakeRange(CPMaxRange(aRange), 0)]; +} + +- (void)insertText:(CPString)aString +{ + var isAttributed = [aString isKindOfClass:CPAttributedString], + string = isAttributed ? [aString string]:aString; + + if (![self shouldChangeTextInRange:CPMakeRangeCopy(_selectionRange) replacementString:string]) + return; + + if (!isAttributed) + aString = [[CPAttributedString alloc] initWithString:aString attributes:_typingAttributes]; + + var undoManager = [[self window] undoManager]; + [undoManager setActionName:@"Replace/insert text"]; + + [[undoManager prepareWithInvocationTarget:self] + _replaceCharactersInRange:CPMakeRange(_selectionRange.location, [aString length]) + withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(_selectionRange)]]; + + [_textStorage replaceCharactersInRange:CPMakeRangeCopy(_selectionRange) withAttributedString:aString]; + + [self _setSelectedRange:CPMakeRange(_selectionRange.location + [string length], 0) affinity:0 stillSelecting:NO overwriteTypingAttributes:NO]; + _startTrackingLocation = _selectionRange.location; + + [self didChangeText]; + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + [self scrollRangeToVisible:_selectionRange]; + _stickyXLocation = MAX(0, _caret._rect.origin.x - 1); +} + +#pragma mark - +#pragma mark Drawing methods + +- (void)drawInsertionPointInRect:(CGRect)aRect color:(CPColor)aColor turnedOn:(BOOL)flag +{ + [_caret setRect:aRect]; + [_caret setVisibility:flag stop:NO]; +} + +- (void)displayRectIgnoringOpacity:(CGRect)aRect inContext:(CPGraphicsContext)aGraphicsContext +{ if ([self isHidden]) + return; + + [self drawRect:aRect]; +} + +- (void)drawRect:(CGRect)aRect +{ +#if PLATFORM(DOM) + var range = [_layoutManager glyphRangeForBoundingRect:aRect inTextContainer:_textContainer]; + + for (var i = 0; i < [_selectionSpans count]; i++) + [_selectionSpans[i] removeFromTextView]; + + _selectionSpans = []; + + if (_selectionRange.length) + { + var rects = [_layoutManager rectArrayForCharacterRange:_selectionRange + withinSelectedCharacterRange:_selectionRange + inTextContainer:_textContainer + rectCount:nil], + effectiveSelectionColor = [self _isFocused] ? [_selectedTextAttributes objectForKey:CPBackgroundColorAttributeName] : [CPColor _selectedTextBackgroundColorUnfocussed], + lengthRect = rects.length; + + for (var i = 0; i < lengthRect; i++) + { + rects[i].origin.x += _textContainerOrigin.x; + rects[i].origin.y += _textContainerOrigin.y; + + var newSpan = [[_CPSelectionBox alloc] initWithTextView:self rect:rects[i] color:effectiveSelectionColor]; + [_selectionSpans addObject:newSpan]; + } + } + + if (range.length) + [_layoutManager drawGlyphsForGlyphRange:range atPoint:_textContainerOrigin]; + + if ([self shouldDrawInsertionPoint]) + { + [self updateInsertionPointStateAndRestartTimer:NO]; + [self drawInsertionPointInRect:_caret._rect color:_insertionPointColor turnedOn:_caret._drawCaret]; + } + else + [_caret setVisibility:NO]; +#endif +} + + +#pragma mark - +#pragma mark Select methods + +- (void)selectAll:(id)sender +{ + if ([self isSelectable]) + { + [_caret stopBlinking]; + [self setSelectedRange:CPMakeRange(0, [_layoutManager numberOfCharacters])]; + } +} + +- (void)setSelectedRange:(CPRange)range +{ + [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; + [self setSelectedRange:range affinity:0 stillSelecting:NO]; +} + +- (void)setSelectedRange:(CPRange)range affinity:(CPSelectionAffinity)affinity stillSelecting:(BOOL)selecting +{ + [self _setSelectedRange:range affinity:affinity stillSelecting:selecting overwriteTypingAttributes:YES]; +} + +- (void)_setSelectedRange:(CPRange)range affinity:(CPSelectionAffinity)affinity stillSelecting:(BOOL)selecting overwriteTypingAttributes:(BOOL)doOverwrite +{ + var maxRange = CPMakeRange(0, [_layoutManager numberOfCharacters]); + + range = CPIntersectionRange(maxRange, range); + + if (!selecting && [self _delegateRespondsToWillChangeSelectionFromCharacterRangeToCharacterRange]) + { + _selectionRange = [self _sendDelegateWillChangeSelectionFromCharacterRange:_selectionRange toCharacterRange:range]; + } + else + { + _selectionRange = CPMakeRangeCopy(range); + _selectionRange = [self selectionRangeForProposedRange:_selectionRange granularity:[self selectionGranularity]]; + } + + if (_selectionRange.length) + [_layoutManager invalidateDisplayForGlyphRange:_selectionRange]; + else + [self setNeedsDisplay:YES]; + + if (!selecting) + { + if ([self _isFirstResponder]) + [self updateInsertionPointStateAndRestartTimer:((_selectionRange.length === 0) && ![_caret isBlinking])]; + + if (doOverwrite) + [self setTypingAttributes:[_textStorage attributesAtIndex:CPMaxRange(range) effectiveRange:nil]]; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeSelectionNotification object:self]; + + if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_didChangeSelection) + [_delegate textViewDidChangeSelection:[[CPNotification alloc] initWithName:CPTextViewDidChangeSelectionNotification object:self userInfo:nil]]; + } + + if (!selecting && _selectionRange.length > 0) + [_CPNativeInputManager focusForClipboardOfTextView:self]; +} + +#if PLATFORM(DOM) +- (CGPoint)_cumulativeOffset +{ + var top = 0, + left = 0, + element = self._DOMElement; + + do + { + top += element.offsetTop || 0; + left += element.offsetLeft || 0; + element = element.offsetParent; + } + while(element); + + return CGPointMake(left, top); +} +#endif + + +// interface to the _CPNativeInputManager +- (void)_activateNativeInputElement:(DOMElemet)aNativeField +{ + var attributes = [[self typingAttributes] copy]; + + // make it invisible + [attributes setObject:[CPColor colorWithRed:1 green:1 blue:1 alpha:0] forKey:CPForegroundColorAttributeName]; + + // FIXME: this hack to provide the visual space for the inputmanager should at least bypass the undomanager + var placeholderString = [[CPAttributedString alloc] initWithString:aNativeField.innerHTML attributes:attributes]; + [self insertText:placeholderString]; + + var caretOrigin = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, _selectionRange.location - 1), 1) inTextContainer:_textContainer].origin; + caretOrigin.y += [_layoutManager _characterOffsetAtLocation:MAX(0, _selectionRange.location - 1)]; + caretOrigin.x += 2; // two pixel offset to the LHS character + var cumulativeOffset = [self _cumulativeOffset]; + + +#if PLATFORM(DOM) + aNativeField.style.left = (caretOrigin.x + cumulativeOffset.x) + "px"; + aNativeField.style.top = (caretOrigin.y + cumulativeOffset.y) + "px"; + aNativeField.style.font = [[_typingAttributes objectForKey:CPFontAttributeName] cssString]; + aNativeField.style.color = [[_typingAttributes objectForKey:CPForegroundColorAttributeName] cssString]; +#endif + + [_caret setVisibility:NO]; // hide our caret because now the system caret takes over +} + +- (CPArray)selectedRanges +{ + return [_selectionRange]; +} + +#pragma mark - +#pragma mark Keyboard events + +- (void)keyDown:(CPEvent)event +{ + + [[_window platformWindow] _propagateCurrentDOMEvent:YES]; // for the _CPNativeInputManager (necessary at least on FF and chrome) + + if (![_CPNativeInputManager isNativeInputFieldActive] && [event charactersIgnoringModifiers].charCodeAt(0) != 229) // filter out 229 because this would be inserted in chrome on each deadkey + [self interpretKeyEvents:[event]]; + + [_caret setPermanentlyVisible:YES]; +} + +- (void)keyUp:(CPEvent)event +{ + [super keyUp:event]; + + setTimeout(function() { + [_caret setPermanentlyVisible:NO]; + }, 500); +} + + +#pragma mark - +#pragma mark Mouse Events + +- (void)mouseDown:(CPEvent)event +{ + [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; + + var fraction = [], + point = [self convertPoint:[event locationInWindow] fromView:nil], + granularities = [CPNotFound, CPSelectByCharacter, CPSelectByWord, CPSelectByParagraph]; + + [_caret setVisibility:NO]; + + // convert to container coordinate + point.x -= _textContainerOrigin.x; + point.y -= _textContainerOrigin.y; + + _startTrackingLocation = [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction]; + + if (_startTrackingLocation === CPNotFound) + _startTrackingLocation = [_layoutManager numberOfCharacters]; + else if (fraction[0] > 0.5) + _startTrackingLocation++; + + [self setSelectionGranularity:granularities[[event clickCount]]]; + + var setRange = CPMakeRange(_startTrackingLocation, 0); + + if ([event modifierFlags] & CPShiftKeyMask) + setRange = _MakeRangeFromAbs(_startTrackingLocation < _MidRange(_selectionRange) ? CPMaxRange(_selectionRange) : _selectionRange.location, _startTrackingLocation); + else + _scrollingTimer = [CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(_supportScrolling:) userInfo:nil repeats:YES]; // fixme: only start if we are in the scrolling areas + + [self setSelectedRange:setRange affinity:0 stillSelecting:YES]; +} + +- (void)_supportScrolling:(CPTimer)aTimer +{ + [self mouseDragged:[CPApp currentEvent]]; +} + +- (void)mouseDragged:(CPEvent)event +{ + var fraction = [], + point = [self convertPoint:[event locationInWindow] fromView:nil]; + + // convert to container coordinate + point.x -= _textContainerOrigin.x; + point.y -= _textContainerOrigin.y; + + var oldRange = [self selectedRange], + index = [_layoutManager glyphIndexForPoint:point + inTextContainer:_textContainer + fractionOfDistanceThroughGlyph:fraction]; + + if (index === CPNotFound) + index = _scrollingDownward ? CPMaxRange(oldRange) : oldRange.location; + else if (fraction[0] > 0.5) + index++; + + if (index > oldRange.location) + _scrollingDownward = YES; + + if (index < CPMaxRange(oldRange)) + _scrollingDownward = NO; + + [self setSelectedRange:_MakeRangeFromAbs(index, _startTrackingLocation) + affinity:0 + stillSelecting:YES]; + + [self scrollRangeToVisible:CPMakeRange(index, 0)]; +} + +// handle all the other methods from CPKeyBinding.j + +- (void)mouseUp:(CPEvent)event +{ + /* will post CPTextViewDidChangeSelectionNotification */ + _previousSelectionGranularity = [self selectionGranularity]; + [self setSelectionGranularity:CPSelectByCharacter]; + [self setSelectedRange:[self selectedRange] affinity:0 stillSelecting:NO]; + + var point = [_layoutManager locationForGlyphAtIndex:[self selectedRange].location]; + _stickyXLocation = point.x; + _startTrackingLocation = _selectionRange.location; + + if (_scrollingTimer) + { + [_scrollingTimer invalidate]; + _scrollingTimer = nil; + } +} + +- (void)moveDown:(id)sender +{ + if (![self isSelectable]) + return; + + var fraction = [], + nglyphs = [_layoutManager numberOfCharacters], + sindex = CPMaxRange([self selectedRange]), + rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(sindex, 1) inTextContainer:_textContainer], + rectEnd = nglyphs ? [_layoutManager boundingRectForGlyphRange:CPMakeRange(nglyphs - 1, 1) inTextContainer:_textContainer] : rectSource, + point = rectSource.origin; + + if (_stickyXLocation) + point.x = _stickyXLocation; + + // FIXME: find a better way for getting the coordinates of the next line + point.y += 2 + rectSource.size.height; + + var dindex = point.y >= CGRectGetMaxY(rectEnd) ? nglyphs : [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction], + oldStickyLoc = _stickyXLocation; + + if (fraction[0] > 0.5) + dindex++; + + [self _establishSelection:CPMakeRange(dindex, 0) byExtending:NO]; + _stickyXLocation = oldStickyLoc; + [self scrollRangeToVisible:CPMakeRange(dindex, 0)] + +} + +- (void)moveDownAndModifySelection:(id)sender +{ + if (![self isSelectable]) + return; + + var oldStartTrackingLocation = _startTrackingLocation; + + [self _performSelectionFixupForRange:CPMakeRange(_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange), 0)]; + [self moveDown:sender]; + _startTrackingLocation = oldStartTrackingLocation; + [self _performSelectionFixupForRange:_MakeRangeFromAbs(_startTrackingLocation, (_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange)))]; +} + +- (void)moveUp:(id)sender +{ + if (![self isSelectable]) + return; + + var dindex = [self selectedRange].location; + + if (dindex < 1) + return; + + var rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(dindex, 1) inTextContainer:_textContainer]; + + if (!(dindex === [_layoutManager numberOfCharacters] && _isNewlineCharacter([[_textStorage string] characterAtIndex:dindex - 1]))) + dindex = [_layoutManager glyphIndexForPoint:CGPointMake(0, rectSource.origin.y + 1) inTextContainer:_textContainer fractionOfDistanceThroughGlyph:nil]; + + if (dindex < 1) + return; + + var fraction = []; + rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(dindex - 1, 1) inTextContainer:_textContainer]; + dindex = [_layoutManager glyphIndexForPoint:CGPointMake(_stickyXLocation, rectSource.origin.y + 1) inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction]; + + if (fraction[0] > 0.5) + dindex++; + + var oldStickyLoc = _stickyXLocation; + [self _establishSelection:CPMakeRange(dindex,0) byExtending:NO]; + _stickyXLocation = oldStickyLoc; + + [self scrollRangeToVisible:CPMakeRange(dindex, 0)]; +} + +- (void)moveUpAndModifySelection:(id)sender +{ + if (![self isSelectable]) + return; + + var oldStartTrackingLocation = _startTrackingLocation; + + [self _performSelectionFixupForRange:CPMakeRange(_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange), 0)]; + [self moveUp:sender]; + _startTrackingLocation = oldStartTrackingLocation; + [self _performSelectionFixupForRange:_MakeRangeFromAbs(_startTrackingLocation, (_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange)))]; +} + +- (void)_performSelectionFixupForRange:(CPRange)aSel +{ + aSel.location = MAX(0, aSel.location); + + if (CPMaxRange(aSel) > [_layoutManager numberOfCharacters]) + aSel = CPMakeRange([_layoutManager numberOfCharacters], 0); + + [self setSelectedRange:aSel]; + + var point = [_layoutManager locationForGlyphAtIndex:aSel.location]; + + _stickyXLocation = point.x; +} + +- (void)_establishSelection:(CPSelection)aSel byExtending:(BOOL)flag +{ + if (flag) + aSel = CPUnionRange(aSel, _selectionRange); + + [self _performSelectionFixupForRange:aSel]; + _startTrackingLocation = _selectionRange.location; +} + +- (unsigned)_calculateMoveSelectionFromRange:(CPRange)aRange intoDirection:(integer)move granularity:(CPSelectionGranularity)granularity +{ + var inWord = [self _isCharacterAtIndex:(move > 0 ? CPMaxRange(aRange) : aRange.location) + move granularity:granularity], + aSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aRange) : aRange.location) + move, 0) granularity:granularity], + bSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aSel) : aSel.location) + move, 0) granularity:granularity]; + + return move > 0 ? CPMaxRange(inWord? aSel:bSel) : (inWord? aSel:bSel).location; +} + +- (void)_moveSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity +{ + var pos = [self _calculateMoveSelectionFromRange:_selectionRange intoDirection:move granularity:granularity]; + + [self _performSelectionFixupForRange:CPMakeRange(pos, 0)]; + _startTrackingLocation = _selectionRange.location; +} + +- (void)_extendSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity +{ + var aSel = CPMakeRangeCopy(_selectionRange); + + if (granularity !== CPSelectByCharacter) + { + var pos = [self _calculateMoveSelectionFromRange:CPMakeRange(aSel.location < _startTrackingLocation ? aSel.location : CPMaxRange(aSel), 0) + intoDirection:move + granularity:granularity]; + aSel = CPMakeRange(pos, 0); + } + + else + aSel = CPMakeRange((aSel.location < _startTrackingLocation? aSel.location : CPMaxRange(aSel)) + move, 0); + + aSel = _MakeRangeFromAbs(_startTrackingLocation, aSel.location); + [self _performSelectionFixupForRange:aSel]; +} + +- (void)moveLeftAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:-1 granularity:CPSelectByCharacter]; +} + +- (void)moveBackward:(id)sender +{ + [self moveLeft:sender]; +} + +- (void)moveBackwardAndModifySelection:(id)sender +{ + [self moveLeftAndModifySelection:sender]; +} + +- (void)moveRightAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:1 granularity:CPSelectByCharacter]; +} + +- (void)moveLeft:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange(_selectionRange.location - (_selectionRange.length ? 0 : 1), 0) byExtending:NO]; +} + +- (void)moveToEndOfParagraph:(id)sender +{ + if (![self isSelectable]) + return; + + if (!_isNewlineCharacter([[_textStorage string] characterAtIndex:_selectionRange.location])) + [self _moveSelectionIntoDirection:1 granularity:CPSelectByParagraph]; + + if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, _selectionRange.location - 1)])) + [self moveLeft:sender]; +} + +- (void)moveToEndOfParagraphAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:1 granularity:CPSelectByParagraph]; +} + +- (void)moveParagraphForwardAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:1 granularity:CPSelectByParagraph]; +} + +- (void)moveParagraphForward:(id)sender +{ + if ([self isSelectable]) + [self _moveSelectionIntoDirection:1 granularity:CPSelectByParagraph]; +} + +- (void)moveWordBackwardAndModifySelection:(id)sender +{ + [self moveWordLeftAndModifySelection:sender]; +} + +- (void)moveWordBackward:(id)sender +{ + [self moveWordLeft:sender]; +} + +- (void)moveWordForwardAndModifySelection:(id)sender +{ + [self moveWordRightAndModifySelection:sender]; +} + +- (void)moveWordForward:(id)sender +{ + [self moveWordRight:sender]; +} + +- (void)moveToBeginningOfDocument:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange(0, 0) byExtending:NO]; +} + +- (void)moveToBeginningOfDocumentAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange(0, 0) byExtending:YES]; +} + +- (void)moveToEndOfDocument:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange([_layoutManager numberOfCharacters], 0) byExtending:NO]; +} + +- (void)moveToEndOfDocumentAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange([_layoutManager numberOfCharacters], 0) byExtending:YES]; +} + +- (void)moveWordRight:(id)sender +{ + if ([self isSelectable]) + [self _moveSelectionIntoDirection:1 granularity:CPSelectByWord]; +} + +- (void)moveToBeginningOfParagraph:(id)sender +{ + if (![self isSelectable]) + return; + + if (!_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, _selectionRange.location - 1)])) + [self _moveSelectionIntoDirection:-1 granularity:CPSelectByParagraph]; +} + +- (void)moveToBeginningOfParagraphAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:-1 granularity:CPSelectByParagraph]; +} + +- (void)moveParagraphBackward:(id)sender +{ + if ([self isSelectable]) + [self _moveSelectionIntoDirection:-1 granularity:CPSelectByParagraph]; +} + +- (void)moveParagraphBackwardAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:-1 granularity:CPSelectByParagraph]; +} + +- (void)moveWordRightAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:+1 granularity:CPSelectByWord]; +} + +- (void)deleteToEndOfParagraph:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveToEndOfParagraphAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteToBeginningOfParagraph:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveToBeginningOfParagraphAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteToBeginningOfLine:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveToLeftEndOfLineAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteToEndOfLine:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveToRightEndOfLineAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteWordBackward:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveWordLeftAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteWordForward:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveWordRightAndModifySelection:self]; + [self delete:self]; +} + +- (void)moveToLeftEndOfLine:(id)sender byExtending:(BOOL)flag +{ + if (![self isSelectable]) + return; + + var nglyphs = [_layoutManager numberOfCharacters], + loc = nglyphs == _selectionRange.location ? MAX(0, _selectionRange.location - 1) : _selectionRange.location, + fragment = [_layoutManager _firstLineFragmentForLineFromLocation:loc]; + + if (fragment) + [self _establishSelection:CPMakeRange(fragment._range.location, 0) byExtending:flag]; +} + +- (void)moveToLeftEndOfLine:(id)sender +{ + [self moveToLeftEndOfLine:sender byExtending:NO]; +} + +- (void)moveToLeftEndOfLineAndModifySelection:(id)sender +{ + [self moveToLeftEndOfLine:sender byExtending:YES]; +} + +- (void)moveToRightEndOfLine:(id)sender byExtending:(BOOL)flag +{ + if (![self isSelectable]) + return; + + var fragment = [_layoutManager _lastLineFragmentForLineFromLocation:_selectionRange.location]; + + if (!fragment) + return; + + var nglyphs = [_layoutManager numberOfCharacters], + loc = nglyphs == CPMaxRange(fragment._range) ? nglyphs : MAX(0, CPMaxRange(fragment._range) - 1); + + [self _establishSelection:CPMakeRange(loc, 0) byExtending:flag]; +} + +- (void)moveToRightEndOfLine:(id)sender +{ + [self moveToRightEndOfLine:sender byExtending:NO]; +} + +- (void)moveToRightEndOfLineAndModifySelection:(id)sender +{ + [self moveToRightEndOfLine:sender byExtending:YES]; +} + +- (void)moveWordLeftAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:-1 granularity:CPSelectByWord]; +} + +- (void)moveWordLeft:(id)sender +{ + if ([self isSelectable]) + [self _moveSelectionIntoDirection:-1 granularity:CPSelectByWord] +} + +- (void)moveRight:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange(CPMaxRange(_selectionRange) + (_selectionRange.length ? 0 : 1), 0) byExtending:NO]; +} + +- (void)_deleteForRange:(CPRange)changedRange +{ + if (![self shouldChangeTextInRange:changedRange replacementString:@""]) + return; + + changedRange = CPIntersectionRange(CPMakeRange(0, [_layoutManager numberOfCharacters]), changedRange); + + [[[_window undoManager] prepareWithInvocationTarget:self] _replaceCharactersInRange:CPMakeRange(changedRange.location, 0) withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(changedRange)]]; + [_textStorage deleteCharactersInRange:CPMakeRangeCopy(changedRange)]; + + [self setSelectedRange:CPMakeRange(changedRange.location, 0)]; + [self didChangeText]; + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + _stickyXLocation = _caret._rect.origin.x; +} + +- (void)cancelOperation:(id)sender +{ + [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; // handle ESC during native input +} + +- (void)deleteBackward:(id)sender ignoreSmart:(BOOL)ignoreFlag +{ + var changedRange; + + if (CPEmptyRange(_selectionRange) && _selectionRange.location > 0) + changedRange = CPMakeRange(_selectionRange.location - 1, 1); + else + changedRange = _selectionRange; + + // smart delete + if (!ignoreFlag && _copySelectionGranularity > 0 && + changedRange.location > 0 && _isWhitespaceCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]) && + changedRange.location < [[self string] length] && _isWhitespaceCharacter([[_textStorage string] characterAtIndex:CPMaxRange(changedRange)])) + changedRange.length++; + + [self _deleteForRange:changedRange]; + _startTrackingLocation = _selectionRange.location; +} + +- (void)deleteBackward:(id)sender +{ + _copySelectionGranularity = _previousSelectionGranularity; // smart delete + [self deleteBackward:self ignoreSmart:_selectionRange.length > 0? NO:YES]; +} + +- (void)deleteForward:(id)sender +{ + var changedRange; + + if (CPEmptyRange(_selectionRange) && _selectionRange.location < [_layoutManager numberOfCharacters]) + changedRange = CPMakeRange(_selectionRange.location, 1); + else + changedRange = _selectionRange; + + [self _deleteForRange:changedRange]; +} + +- (void)cut:(id)sender +{ + var selectedRange = [self selectedRange]; + + if (selectedRange.length < 1) + return; + + [self copy:sender]; + [self deleteBackward:sender ignoreSmart:NO]; +} + +- (void)insertLineBreak:(id)sender +{ + [self insertText:@"\n"]; +} + +- (void)insertTab:(id)sender +{ + [self insertText:@"\t"]; +} + +- (void)insertTabIgnoringFieldEditor:(id)sender +{ + [self insertTab:sender]; +} + +- (void)insertNewlineIgnoringFieldEditor:(id)sender +{ + [self insertLineBreak:sender]; +} + +- (void)insertNewline:(id)sender +{ + [self insertLineBreak:sender]; +} + +- (void)_enrichEssentialTypingAttributes:(CPDictionary)attributes +{ + if (![attributes containsKey:CPFontAttributeName]) + [attributes setObject:[self font] forKey:CPFontAttributeName]; + + if (![attributes containsKey:CPForegroundColorAttributeName]) + [attributes setObject:[self textColor] forKey:CPForegroundColorAttributeName]; +} + +- (void)setTypingAttributes:(CPDictionary)attributes +{ + if (!attributes) + attributes = [CPDictionary dictionary]; + + if ([self _delegateRespondsToShouldChangeTypingAttributesToAttributes]) + { + _typingAttributes = [self _sendDelegateShouldChangeTypingAttributes:_typingAttributes toAttributes:attributes]; + } + else + { + _typingAttributes = [attributes copy]; + + [self _enrichEssentialTypingAttributes:_typingAttributes]; + } + + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification + object:self]; + + if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_didChangeTypingAttributes) + [_delegate textViewDidChangeTypingAttributes:[[CPNotification alloc] initWithName:CPTextViewDidChangeTypingAttributesNotification object:self userInfo:nil]]; +} + +- (CPDictionary)_attributesForFontPanel +{ + var attributes = [[_textStorage attributesAtIndex:CPMaxRange(_selectionRange) effectiveRange:nil] copy]; + + [self _enrichEssentialTypingAttributes:attributes]; + + return attributes; +} + +- (void)delete:(id)sender +{ + [self deleteBackward:sender]; +} + + +#pragma mark - +#pragma mark Font methods + +- (void)setFont:(CPFont)font +{ + _font = font; + + var length = [_layoutManager numberOfCharacters]; + + if (length) + { + [_textStorage addAttribute:CPFontAttributeName value:_font range:CPMakeRange(0, length)]; + [_textStorage setFont:_font]; + [self scrollRangeToVisible:CPMakeRange(length, 0)]; + } +} + +- (void)setFont:(CPFont)font range:(CPRange)range +{ + if (![self isRichText]) + { + _font = font; + [_textStorage setFont:_font]; + } + + var currentAttributes = [_textStorage attributesAtIndex:range.location effectiveRange:nil] || _typingAttributes; + + [[[[self window] undoManager] prepareWithInvocationTarget:self] + setFont:[currentAttributes objectForKey:CPFontAttributeName] || _font + range:CPMakeRangeCopy(range)]; + + [_textStorage addAttribute:CPFontAttributeName value:font range:CPMakeRangeCopy(range)]; + [_layoutManager _validateLayoutAndGlyphs]; +} + +- (void)changeFont:(id)sender +{ + var currRange = CPMakeRange(_selectionRange.location, 0), + oldFont, + attributes, + scrollRange = CPMakeRange(CPMaxRange(_selectionRange), 0), + undoManager = [[self window] undoManager]; + + [undoManager beginUndoGrouping]; + + if ([self isRichText]) + { + if (!CPEmptyRange(_selectionRange)) + { + while (CPMaxRange(currRange) < CPMaxRange(_selectionRange)) // iterate all "runs" + { + attributes = [_textStorage attributesAtIndex:CPMaxRange(currRange) + longestEffectiveRange:currRange + inRange:_selectionRange]; + oldFont = [attributes objectForKey:CPFontAttributeName] || [self font]; + + [self setFont:[sender convertFont:oldFont] range:currRange]; + } + } + else + { + [_typingAttributes setObject:[sender selectedFont] forKey:CPFontAttributeName]; + } + } + else + { + var length = [_textStorage length]; + + oldFont = [self font]; + [self setFont:[sender convertFont:oldFont] range:CPMakeRange(0, length)]; + scrollRange = CPMakeRange(length, 0); + } + + [undoManager endUndoGrouping]; + + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + [self setNeedsDisplay:YES]; + [self scrollRangeToVisible:scrollRange]; +} + + +#pragma mark - +#pragma mark Color methods + +- (void)changeColor:(id)sender +{ + [self setTextColor:[sender color] range:_selectionRange]; +} + +- (void)setTextColor:(CPColor)aColor +{ + _textColor = [aColor copy]; + [self setTextColor:aColor range:CPMakeRange(0, [_layoutManager numberOfCharacters])]; + [_typingAttributes setObject:_textColor forKey:CPForegroundColorAttributeName]; +} + +- (void)setTextColor:(CPColor)aColor range:(CPRange)range +{ + var currentAttributes = [_textStorage attributesAtIndex:range.location effectiveRange:nil] || _typingAttributes; + + [[[[self window] undoManager] prepareWithInvocationTarget:self] + setTextColor:[currentAttributes objectForKey:CPForegroundColorAttributeName] || _textColor + range:CPMakeRangeCopy(range)]; + + if (!CPEmptyRange(range)) + { + if (aColor) + [_textStorage addAttribute:CPForegroundColorAttributeName value:aColor range:CPMakeRangeCopy(range)]; + else + [_textStorage removeAttribute:CPForegroundColorAttributeName range:CPMakeRangeCopy(range)]; + } + else + [_typingAttributes setObject:aColor forKey:CPForegroundColorAttributeName]; + + [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(range) changeInLength:0 invalidatedRange:CPMakeRangeCopy(range)]; +} + +- (void)underline:(id)sender +{ + if (![self shouldChangeTextInRange:_selectionRange replacementString:nil]) + return; + + if (!CPEmptyRange(_selectionRange)) + { + var attrib = [_textStorage attributesAtIndex:_selectionRange.location effectiveRange:nil]; + + if ([attrib containsKey:CPUnderlineStyleAttributeName] && [[attrib objectForKey:CPUnderlineStyleAttributeName] intValue]) + [_textStorage removeAttribute:CPUnderlineStyleAttributeName range:_selectionRange]; + else + [_textStorage addAttribute:CPUnderlineStyleAttributeName value:[CPNumber numberWithInt:1] range:CPMakeRangeCopy(_selectionRange)]; + } + else + { + if ([_typingAttributes containsKey:CPUnderlineStyleAttributeName] && [[_typingAttributes objectForKey:CPUnderlineStyleAttributeName] intValue]) + [_typingAttributes setObject:[CPNumber numberWithInt:0] forKey:CPUnderlineStyleAttributeName]; + else + [_typingAttributes setObject:[CPNumber numberWithInt:1] forKey:CPUnderlineStyleAttributeName]; + } + + [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(_selectionRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(_selectionRange)]; +} + +- (CPSelectionAffinity)selectionAffinity +{ + return 0; +} + +- (BOOL)isRulerVisible +{ + return NO; +} + +- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString +{ + [_textStorage replaceCharactersInRange:aRange withString:aString]; +} + +- (void)setConstrainedFrameSize:(CGSize)desiredSize +{ + [self setFrameSize:desiredSize]; +} + +- (void)sizeToFit +{ + [self setFrameSize:[self frameSize]]; +} + +- (void)setFrameSize:(CGSize)aSize +{ + var minSize = [self minSize], + maxSize = [self maxSize], + desiredSize = CGSizeCreateCopy(aSize), + rect = CGRectUnion([_layoutManager boundingRectForGlyphRange:CPMakeRange(0, 1) inTextContainer:_textContainer], + [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, [_layoutManager numberOfCharacters] - 2), 1) inTextContainer:_textContainer]), + myClipviewSize = nil; + + if ([[self superview] isKindOfClass:[CPClipView class]]) + myClipviewSize = [[self superview] frame].size; + + if ([_layoutManager extraLineFragmentTextContainer] === _textContainer) + rect = CGRectUnion(rect, [_layoutManager extraLineFragmentRect]); + + if (_isHorizontallyResizable) + { + rect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(0, MAX(0, [_layoutManager numberOfCharacters] - 1)) inTextContainer:_textContainer]; // needs expensive "deep" recalculation + + desiredSize.width = rect.size.width + 2 * _textContainerInset.width; + + if (desiredSize.width < minSize.width) + desiredSize.width = minSize.width; + else if (desiredSize.width > maxSize.width) + desiredSize.width = maxSize.width; + } + + if (_isVerticallyResizable) + { + desiredSize.height = rect.size.height + 2 * _textContainerInset.height; + + if (desiredSize.height < minSize.height) + desiredSize.height = minSize.height; + else if (desiredSize.height > maxSize.height) + desiredSize.height = maxSize.height; + } + + if (myClipviewSize) + { + if (desiredSize.width < myClipviewSize.width) + desiredSize.width = myClipviewSize.width; + if (desiredSize.height < myClipviewSize.height) + desiredSize.height = myClipviewSize.height; + } + + [super setFrameSize:desiredSize]; +} + +- (void)scrollRangeToVisible:(CPRange)aRange +{ + var rect; + + if (CPEmptyRange(aRange)) + { + if (aRange.location >= [_layoutManager numberOfCharacters]) + rect = [_layoutManager extraLineFragmentRect]; + else + rect = [_layoutManager lineFragmentRectForGlyphAtIndex:aRange.location effectiveRange:nil]; + } + else + { + rect = [_layoutManager boundingRectForGlyphRange:aRange inTextContainer:_textContainer]; + } + + rect.origin.x += _textContainerOrigin.x; + rect.origin.y += _textContainerOrigin.y; + + [self scrollRectToVisible:rect]; +} + +- (BOOL)_isCharacterAtIndex:(unsigned)index granularity:(CPSelectionGranularity)granularity +{ + var characterSet; + + switch (granularity) + { + case CPSelectByWord: + characterSet = [[self class] _wordBoundaryRegex]; + break; + + case CPSelectByParagraph: + characterSet = [[self class] _paragraphBoundaryRegex]; + break; + default: + // FIXME if (!characterSet) croak! + } + + return _regexMatchesStringAtIndex(characterSet, [_textStorage string], index); +} + ++ (JSObject)_wordBoundaryRegex +{ + return /(^[0-9][\.,])|(^.[^-\.,+#'"!§$%&/\(<\[\]>\)=?`´*\s{}\|¶])/m; +} + ++ (JSObject)_paragraphBoundaryRegex +{ + return /^.[^\n\r]/m; +} + ++ (JSObject)_whitespaceRegex +{ + // do not include \n here or we will get cross paragraph selections + return /^.[ \t]+/m; +} + +- (CPRange)_characterRangeForIndex:(unsigned)index asDefinedByRegex:(JSObject)regex +{ + return [self _characterRangeForIndex:index asDefinedByLRegex:regex andRRegex:regex] +} + +- (CPRange)_characterRangeForIndex:(unsigned)index asDefinedByLRegex:(JSObject)lregex andRRegex:(JSObject)rregex +{ + var wordRange = CPMakeRange(index, 0), + numberOfCharacters = [_layoutManager numberOfCharacters], + string = [_textStorage string]; + + // extend to the left + for (var searchIndex = index - 1; searchIndex >= 0 && _regexMatchesStringAtIndex(lregex, string, searchIndex); searchIndex--) + wordRange.location = searchIndex; + + // extend to the right + searchIndex = index + 1; + + while (searchIndex < numberOfCharacters && _regexMatchesStringAtIndex(rregex, string, searchIndex)) + searchIndex++; + + return _MakeRangeFromAbs(wordRange.location, MIN(MAX(0, numberOfCharacters), searchIndex)); +} + +- (CPRange)selectionRangeForProposedRange:(CPRange)proposedRange granularity:(CPSelectionGranularity)granularity +{ + var textStorageLength = [_layoutManager numberOfCharacters]; + + if (textStorageLength == 0) + return CPMakeRange(0, 0); + + if (proposedRange.location >= textStorageLength) + proposedRange = CPMakeRange(textStorageLength, 0); + + if (CPMaxRange(proposedRange) > textStorageLength) + proposedRange.length = textStorageLength - proposedRange.location; + + var string = [_textStorage string], + lregex, + rregex, + lloc = proposedRange.location, + rloc = CPMaxRange(proposedRange); + + switch (granularity) + { + case CPSelectByWord: + lregex = _isWhitespaceCharacter([string characterAtIndex:lloc])? [[self class] _whitespaceRegex] : [[self class] _wordBoundaryRegex]; + rregex = _isWhitespaceCharacter([string characterAtIndex:CPMaxRange(proposedRange)])? [[self class] _whitespaceRegex] : [[self class] _wordBoundaryRegex]; + break; + case CPSelectByParagraph: + lregex = rregex = [[self class] _paragraphBoundaryRegex]; + + // triple click right in last line of a paragraph-> select this paragraph completely + if (lloc > 0 && _isNewlineCharacter([string characterAtIndex:lloc]) && + !_isNewlineCharacter([string characterAtIndex:lloc - 1])) + lloc--; + + if (rloc > 0 && _isNewlineCharacter([string characterAtIndex:rloc])) + rloc--; + + break; + default: + return proposedRange; + } + + var granularRange = [self _characterRangeForIndex:lloc + asDefinedByLRegex:lregex + andRRegex:rregex]; + + if (proposedRange.length == 0 && _isNewlineCharacter([string characterAtIndex:proposedRange.location])) + return _MakeRangeFromAbs(_isNewlineCharacter([string characterAtIndex:lloc])? proposedRange.location : granularRange.location, proposedRange.location + 1); + + if (proposedRange.length) + granularRange = CPUnionRange(granularRange, [self _characterRangeForIndex:rloc + asDefinedByLRegex:lregex + andRRegex:rregex]); + + // include the newline character in case of triple click selecting as is done by apple + if (granularity == CPSelectByParagraph && _isNewlineCharacter([string characterAtIndex:CPMaxRange(granularRange)])) + granularRange.length++; + + return granularRange; +} + +- (BOOL)shouldDrawInsertionPoint +{ + return (_selectionRange.length === 0 && [self _isFocused]); +} + +- (void)updateInsertionPointStateAndRestartTimer:(BOOL)flag +{ + var caretRect, + numberOfGlyphs= [_layoutManager numberOfCharacters]; + + if (_selectionRange.length) + [_caret setVisibility:NO]; + + if (_selectionRange.location >= numberOfGlyphs) // cursor is "behind" the last chacacter + { + caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0,_selectionRange.location - 1), 1) inTextContainer:_textContainer]; + + if (!numberOfGlyphs) + { + var font = [_typingAttributes objectForKey:CPFontAttributeName]; + + caretRect.size.height = [font size]; + caretRect.origin.y = ([font ascender] - [font descender]) * 0.5; + } + + caretRect.origin.x += caretRect.size.width; + + if (_selectionRange.location > 0 && [[_textStorage string] characterAtIndex:_selectionRange.location - 1] === '\n') + { + caretRect.origin.y += caretRect.size.height; + caretRect.origin.x = 0; + } + } + else + { + caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(_selectionRange.location, 1) inTextContainer:_textContainer]; + } + + var loc = (_selectionRange.location === numberOfGlyphs && numberOfGlyphs > 0) ? _selectionRange.location - 1 : _selectionRange.location, + caretOffset = [_layoutManager _characterOffsetAtLocation:loc], + oldYPosition = CGRectGetMaxY(caretRect), + caretDescend = [_layoutManager _descentAtLocation:loc]; + + if (caretOffset > 0) + { + caretRect.origin.y += caretOffset; + caretRect.size.height = oldYPosition - caretRect.origin.y; + } + if (caretDescend < 0) + { + caretRect.size.height -= caretDescend; + } + + caretRect.origin.x += _textContainerOrigin.x; + caretRect.origin.y += _textContainerOrigin.y; + caretRect.size.width = 1; + + [_caret setRect:caretRect]; + + if (flag) + [_caret startBlinking]; +} + + +#pragma mark - +#pragma mark Dragging operation + +- (void)performDragOperation:(CPDraggingInfo)aSender +{ + var location = [self convertPoint:[aSender draggingLocation] fromView:nil], + pasteboard = [aSender draggingPasteboard]; + + if (![pasteboard availableTypeFromArray:[CPColorDragType]]) + return NO; + + [self setTextColor:[CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]] range:_selectionRange]; +} + +@end + + +@implementation CPTextView (CPTextViewDelegate) + +- (BOOL)_delegateRespondsToShouldChangeTypingAttributesToAttributes +{ + return _delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes; +} + +- (BOOL)_delegateRespondsToWillChangeSelectionFromCharacterRangeToCharacterRange +{ + return _delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange; +} + +- (BOOL)_sendDelegateDoCommandBySelector:(SEL)aSelector +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_doCommandBySelector)) + return NO; + + return [_delegate textView:self doCommandBySelector:aSelector]; +} + +- (BOOL)_sendDelegateTextShouldBeginEditing +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textShouldBeginEditing)) + return YES; + + return [_delegate textShouldBeginEditing:self]; +} + +- (BOOL)_sendDelegateShouldChangeTextInRange:(CPRange)aRange replacementString:(CPString)aString +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString)) + return YES; + + return [_delegate textView:self shouldChangeTextInRange:aRange replacementString:aString]; +} + +- (CPDictionary)_sendDelegateShouldChangeTypingAttributes:(CPDictionary)typingAttributes toAttributes:(CPDictionary)attributes +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_doCommandBySelector)) + return [CPDictionary dictionary]; + + return [_delegate textView:self shouldChangeTypingAttributes:typingAttributes toAttributes:attributes]; +} + +- (CPRange)_sendDelegateWillChangeSelectionFromCharacterRange:(CPRange)selectionRange toCharacterRange:(CPRange)range +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange)) + return CPMakeRange(0, 0); + + return [_delegate textView:self willChangeSelectionFromCharacterRange:selectionRange toCharacterRange:range]; +} + +@end + + +var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey", + CPTextViewUsesFontPanelKey = @"CPTextViewUsesFontPanelKey", + CPTextViewContainerKey = @"CPTextViewContainerKey", + CPTextViewLayoutManagerKey = @"CPTextViewLayoutManagerKey", + CPTextViewTextStorageKey = @"CPTextViewTextStorageKey", + CPTextViewInsertionPointColorKey = @"CPTextViewInsertionPointColorKey", + CPTextViewSelectedTextAttributesKey = @"CPTextViewSelectedTextAttributesKey", + CPTextViewDelegateKey = @"CPTextViewDelegateKey"; + +@implementation CPTextView (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super initWithCoder:aCoder]; + + if (self) + { + [self _init]; + + [self setInsertionPointColor:[aCoder decodeObjectForKey:CPTextViewInsertionPointColorKey]]; + + var selectedTextAttributes = [aCoder decodeObjectForKey:CPTextViewSelectedTextAttributesKey], + enumerator = [selectedTextAttributes keyEnumerator], + key; + + while (key = [enumerator nextObject]) + [_selectedTextAttributes setObject:[selectedTextAttributes valueForKey:key] forKey:key]; + + if (![_selectedTextAttributes valueForKey:CPBackgroundColorAttributeName]) + [_selectedTextAttributes setObject:[CPColor selectedTextBackgroundColor] forKey:CPBackgroundColorAttributeName]; + + [self setAllowsUndo:[aCoder decodeBoolForKey:CPTextViewAllowsUndoKey]]; + [self setUsesFontPanel:[aCoder decodeBoolForKey:CPTextViewUsesFontPanelKey]]; + + [self setDelegate:[aCoder decodeObjectForKey:CPTextViewDelegateKey]]; + + var container = [aCoder decodeObjectForKey:CPTextViewContainerKey]; + [container setTextView:self]; + + _typingAttributes = [[_textStorage attributesAtIndex:0 effectiveRange:nil] copy]; + + if (![_typingAttributes valueForKey:CPForegroundColorAttributeName]) + [_typingAttributes setObject:[CPColor blackColor] forKey:CPForegroundColorAttributeName]; + + _textColor = [_typingAttributes valueForKey:CPForegroundColorAttributeName]; + [self setFont:[_typingAttributes valueForKey:CPFontAttributeName]]; + + [self setString:[_textStorage string]]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [super encodeWithCoder:aCoder]; + + [aCoder encodeObject:_delegate forKey:CPTextViewDelegateKey]; + [aCoder encodeObject:_textContainer forKey:CPTextViewContainerKey]; + [aCoder encodeObject:_insertionPointColor forKey:CPTextViewInsertionPointColorKey]; + [aCoder encodeObject:_selectedTextAttributes forKey:CPTextViewSelectedTextAttributesKey]; + [aCoder encodeBool:_allowsUndo forKey:CPTextViewAllowsUndoKey]; + [aCoder encodeBool:_usesFontPanel forKey:CPTextViewUsesFontPanelKey]; +} + +@end + + +@implementation _CPSelectionBox : CPObject +{ + DOMElement _selectionBoxDOM; + CGRect _rect; + CPColor _color + CPTextView _textView; +} + +- (id)initWithTextView:(CPTextView)aTextView rect:(CGRect)aRect color:(CPColor)aColor +{ + if (self = [super init]) + { + _textView = aTextView; + _rect = aRect; + _color = aColor; + + [self _createSpan]; + _textView._DOMElement.appendChild(_selectionBoxDOM); + } + + return self; +} + +- (void)removeFromTextView +{ + _textView._DOMElement.removeChild(_selectionBoxDOM); +} + +- (void)_createSpan +{ + +#if PLATFORM(DOM) + _selectionBoxDOM = document.createElement("span"); + _selectionBoxDOM.style.position = "absolute"; + _selectionBoxDOM.style.visibility = "visible"; + _selectionBoxDOM.style.padding = "0px"; + _selectionBoxDOM.style.margin = "0px"; + _selectionBoxDOM.style.whiteSpace = "pre"; + _selectionBoxDOM.style.backgroundColor = [_color cssString]; + + _selectionBoxDOM.style.width = (_rect.size.width) + "px"; + _selectionBoxDOM.style.left = (_rect.origin.x) + "px"; + _selectionBoxDOM.style.top = (_rect.origin.y) + "px"; + _selectionBoxDOM.style.height = (_rect.size.height) + "px"; + _selectionBoxDOM.style.zIndex = -1000; + _selectionBoxDOM.oncontextmenu = _selectionBoxDOM.onmousedown = _selectionBoxDOM.onselectstart = function () { return false; }; +#endif + +} + +@end + + +@implementation _CPCaret : CPObject +{ + BOOL _drawCaret; + BOOL _permanentlyVisible @accessors(property=permanentlyVisible); + CGRect _rect; + CPTextView _textView; + CPTimer _caretTimer; + DOMElement _caretDOM; +} + +- (void)setRect:(CGRect)aRect +{ + _rect = CGRectCreateCopy(aRect); + +#if PLATFORM(DOM) + _caretDOM.style.left = (aRect.origin.x) + "px"; + _caretDOM.style.top = (aRect.origin.y) + "px"; + _caretDOM.style.height = (aRect.size.height) + "px"; +#endif +} + +- (id)initWithTextView:(CPTextView)aView +{ + if (self = [super init]) + { +#if PLATFORM(DOM) + var style; + + if (!_caretDOM) + { + _caretDOM = document.createElement("span"); + style = _caretDOM.style; + style.position = "absolute"; + style.visibility = "visible"; + style.padding = "0px"; + style.margin = "0px"; + style.whiteSpace = "pre"; + style.backgroundColor = "black"; + _caretDOM.style.width = "1px"; + _textView = aView; + _textView._DOMElement.appendChild(_caretDOM); + } +#endif + } + + return self; +} + +- (void)setVisibility:(BOOL)visibilityFlag stop:(BOOL)stopFlag +{ + +#if PLATFORM(DOM) + _caretDOM.style.visibility = visibilityFlag ? "visible" : "hidden"; +#endif + + if (!visibilityFlag && stopFlag) + [self stopBlinking]; +} + +- (void)setVisibility:(BOOL)visibilityFlag +{ + [self setVisibility:visibilityFlag stop:YES]; +} + +- (void)_blinkCaret:(CPTimer)aTimer +{ + _drawCaret = (!_drawCaret) || _permanentlyVisible; + [_textView setNeedsDisplayInRect:_rect]; +} + +- (void)startBlinking +{ + _drawCaret = YES; + + if ([self isBlinking]) + return; + + _caretTimer = [CPTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(_blinkCaret:) userInfo:nil repeats:YES]; +} + +- (void)isBlinking +{ + return [_caretTimer isValid]; +} + +- (void)stopBlinking +{ + _drawCaret = NO; + + if (_caretTimer) + { + [_caretTimer invalidate]; + _caretTimer = nil; + } +} + +@end + + +var _CPNativeInputField, + _CPNativeInputFieldKeyDownCalled, + _CPNativeInputFieldKeyUpCalled, + _CPNativeInputFieldKeyPressedCalled, + _CPNativeInputFieldActive; + +var _CPCopyPlaceholder = '-'; + +@implementation _CPNativeInputManager : CPObject + ++ (BOOL)isNativeInputFieldActive +{ + return _CPNativeInputFieldActive; +} + ++ (void)cancelCurrentNativeInputSession +{ + +#if PLATFORM(DOM) + _CPNativeInputField.innerHTML = ''; +#endif + + [self _endInputSessionWithString:_CPNativeInputField.innerHTML]; +} + ++ (void)cancelCurrentInputSessionIfNeeded +{ + if (!_CPNativeInputFieldActive) + return; + + [self cancelCurrentNativeInputSession]; +} + ++ (void)_endInputSessionWithString:(CPString)aStr +{ + _CPNativeInputFieldActive = NO; + + var currentFirstResponder = [[CPApp keyWindow] firstResponder], + placeholderRange = CPMakeRange([currentFirstResponder selectedRange].location - 1, 1); + + [currentFirstResponder setSelectedRange:placeholderRange]; + [currentFirstResponder insertText:aStr]; + _CPNativeInputField.innerHTML = ''; + + + [self hideInputElement]; + [currentFirstResponder updateInsertionPointStateAndRestartTimer:YES]; +} + ++ (void)initialize +{ +#if PLATFORM(DOM) + _CPNativeInputField = document.createElement("div"); + _CPNativeInputField.contentEditable = YES; + _CPNativeInputField.style.width = "64px"; + _CPNativeInputField.style.zIndex = 10000; + _CPNativeInputField.style.position = "absolute"; + _CPNativeInputField.style.visibility = "visible"; + _CPNativeInputField.style.padding = "0px"; + _CPNativeInputField.style.margin = "0px"; + _CPNativeInputField.style.whiteSpace = "pre"; + _CPNativeInputField.style.outline = "0px solid transparent"; + + document.body.appendChild(_CPNativeInputField); + + _CPNativeInputField.addEventListener("keyup", function(e) + { + _CPNativeInputFieldKeyUpCalled = YES; + + // filter out the shift-up, cursor keys and friends used to access the deadkeys + // fixme: e.which is depreciated(?) -> find a better way to identify the modifier-keyups + if (e.which < 27 || e.which == 91 || e.which == 93) // include apple command keys + { + if (e.which == 13) + _CPNativeInputField.innerHTML = ''; + + if (_CPNativeInputField.innerHTML.length == 0 || _CPNativeInputField.innerHTML.length > 2) // backspace + [self cancelCurrentInputSessionIfNeeded]; + + return false; // prevent the default behaviour + } + + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) + return false; // prevent the default behaviour + + var charCode = _CPNativeInputField.innerHTML.charCodeAt(0); + + // å and Å need to be filtered out in keyDown: due to chrome inserting 229 on a deadkey + if (charCode == 229 || charCode == 197) + { + [currentFirstResponder insertText:_CPNativeInputField.innerHTML]; + _CPNativeInputField.innerHTML = ''; + return; + } + + // chrome-trigger: keypressed is omitted for deadkeys + if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyPressedCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3) + { + _CPNativeInputFieldActive = YES; + [currentFirstResponder _activateNativeInputElement:_CPNativeInputField]; + } + else + { + if (_CPNativeInputFieldActive) + [self _endInputSessionWithString:_CPNativeInputField.innerHTML]; + + // prevent the copy placeholder beeing removed by cursor keys + if (_CPNativeInputFieldKeyPressedCalled) + _CPNativeInputField.innerHTML = ''; + } + + _CPNativeInputFieldKeyDownCalled = NO; + + return false; // prevent the default behaviour + }, true); + + _CPNativeInputField.addEventListener("keydown", function(e) + { + // this protects from heavy typing and the shift key + if (_CPNativeInputFieldKeyDownCalled) + return true; + + _CPNativeInputFieldKeyDownCalled = YES; + _CPNativeInputFieldKeyUpCalled = NO; + _CPNativeInputFieldKeyPressedCalled = NO; + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + // webkit-browsers: cursor keys do not emit keypressed and would otherwise activate deadkey mode + if (!CPBrowserIsEngine(CPGeckoBrowserEngine) && e.which >= 37 && e.which <= 40) + _CPNativeInputFieldKeyPressedCalled = YES; + + if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) + return; + + // FF-trigger: here the best way to detect a dead key is the missing keyup event + if (CPBrowserIsEngine(CPGeckoBrowserEngine)) + setTimeout(function(){ + _CPNativeInputFieldKeyDownCalled = NO; + + if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyUpCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3 && !e.repeat) + { + _CPNativeInputFieldActive = YES; + [currentFirstResponder _activateNativeInputElement:_CPNativeInputField]; + } + else if (!_CPNativeInputFieldActive) + [self hideInputElement]; + }, 200); + + return false; + }, true); // capture mode + + _CPNativeInputField.addEventListener("keypress", function(e) + { + _CPNativeInputFieldKeyUpCalled = YES; + _CPNativeInputFieldKeyPressedCalled = YES; + return false; + + }, true); // capture mode + + _CPNativeInputField.onpaste = function(e) + { + var nativeClipboard = (e.originalEvent || e).clipboardData, + richtext, + pasteboard = [CPPasteboard generalPasteboard], + rtfdata = [CPAttributedString new], + _CPDOMParsefunction = function(node) + { + if (node.nodeType === 1 && node.nodeName === 'SPAN') + { + var text = node.innerHTML, + style = window.getComputedStyle(node), + styleAttributes = @{}; + + // extract color from the DOM + var rgbmatch = style.getPropertyValue('color').match(new RegExp(/rgb\((\d+)[, ]+(\d+)[, ]+(\d+)\)/)); + + if (rgbmatch) + [styleAttributes setObject:[CPColor colorWithRed:rgbmatch[1]/255.0 green:rgbmatch[2]/255.0 blue:rgbmatch[3]/255.0 alpha:1] + forKey:CPForegroundColorAttributeName]; + + // extract font from the DOM + + var fontname = style.getPropertyValue('font-family'), + fontsize = parseInt(style.getPropertyValue('font-size'), 10); + + if (fontname && fontsize) + [styleAttributes setObject:[CPFont fontWithName:fontname size:fontsize italic:NO] forKey:CPFontAttributeName]; + + [rtfdata appendAttributedString:[[[CPAttributedString alloc] initWithString:text attributes:styleAttributes] _stringByParsingHTMLEntities]]; + } + }; + + // this is the native rich safari path + // the detection leverages the observation that safari puts a lot of cryptic types on the pasteboard (16 or so) + // this is not the case with any other browser that i have seen so far. + // safari does not currently provide data for any of the rich types that it advertises, though. + // for this reason, we have to let the paste execute and collect data from the DOM afterwards + // i did not get this working so far. the event is not forwarded for reasons that are beyond my understanding :-( + // for this reason, i disabled the code path so at least the plain content gets pasted + if (NO && nativeClipboard.types.length > 10) + { + // http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser/6804718#6804718 + function waitForPastedData(elem) + { + if (elem.childNodes && elem.childNodes.length > 0) + { + _CPwalkTheDOM(elem, _CPDOMParsefunction); + [pasteboard declareTypes:[CPRTFPboardType] owner:nil]; + [pasteboard setString:[_CPRTFProducer produceRTF:rtfdata documentAttributes:@{}] forType:CPRTFPboardType]; + + [[[CPApp keyWindow] firstResponder] paste:self]; + elem.innerHTML = _CPCopyPlaceholder; + } + else + { + setTimeout(function() + { + waitForPastedData(elem) + }, 20); + } + } + + waitForPastedData(_CPNativeInputField); + + return true; + } + + // this is the native rich chrome path: + // we have to construct an CPAttributedString whilst walking the dom and looking at the CSS attributes + if (richtext = nativeClipboard.getData('text/html')) + { + e.preventDefault(); + _CPNativeInputField.innerHTML = richtext; + _CPwalkTheDOM(_CPNativeInputField, _CPDOMParsefunction); + + [pasteboard declareTypes:[CPRTFPboardType] owner:nil]; + [pasteboard setString:[_CPRTFProducer produceRTF:rtfdata documentAttributes:@{}] forType:CPRTFPboardType]; + + [[[CPApp keyWindow] firstResponder] paste:self]; + _CPNativeInputField.innerHTML = _CPCopyPlaceholder; + return false; + } + + // this is the rich FF codepath (here we can use RTF directly) + if (richtext = nativeClipboard.getData('text/rtf')) + { + e.preventDefault(); + [pasteboard declareTypes:[CPRTFPboardType] owner:nil]; + [pasteboard setString:richtext forType:CPRTFPboardType]; + + // prevent dom-flickering (settimeout does not work here) + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + setTimeout(function(){ // prevent dom-flickering (only FF) + [currentFirstResponder paste:self]; + }, 20); + + return false; + } + + // plain is the same in all browsers... + + var data = e.clipboardData.getData('text/plain'), + cappString = [pasteboard stringForType:CPStringPboardType]; + + if (cappString != data) + { + [pasteboard declareTypes:[CPStringPboardType] owner:nil]; + [pasteboard setString:data forType:CPStringPboardType]; + } + + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + setTimeout(function(){ // prevent dom-flickering (only needed for FF) + [currentFirstResponder paste:self]; + }, 20); + + return false; + } + + if (CPBrowserIsEngine(CPGeckoBrowserEngine)) + { + _CPNativeInputField.oncopy = function(e) + { + var pasteboard = [CPPasteboard generalPasteboard], + string, + currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + [currentFirstResponder copy:self]; + + var stringForPasting = [pasteboard stringForType:CPStringPboardType]; + e.clipboardData.setData('text/plain', stringForPasting); + + return false; + } + + _CPNativeInputField.oncut = function(e) + { + var pasteboard = [CPPasteboard generalPasteboard], + string, + currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + // prevent dom-flickering + setTimeout(function(){ + [currentFirstResponder cut:self]; + }, 20); + + // this is necessary because cut will only execute in the future + [currentFirstResponder copy:self]; + + var stringForPasting = [pasteboard stringForType:CPStringPboardType]; + + e.clipboardData.setData('text/plain', stringForPasting); + + return false; + } + } +#endif +} + ++ (void)focusForTextView:(CPTextView)currentFirstResponder +{ + if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) + return; + + [self hideInputElement]; + +#if PLATFORM(DOM) + _CPNativeInputField.focus(); +#endif + +} + ++ (void)focusForClipboardOfTextView:(CPTextView)textview +{ + +#if PLATFORM(DOM) + if (!_CPNativeInputFieldActive && _CPNativeInputField.innerHTML.length == 0) + _CPNativeInputField.innerHTML = _CPCopyPlaceholder; // make sure we have a selection to allow the native pasteboard work in safari + + [self focusForTextView:textview]; + + // select all in the contenteditable div (http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div) + if (document.body.createTextRange) + { + var range = document.body.createTextRange(); + + range.moveToElementText(_CPNativeInputField); + range.select(); + } + else if (window.getSelection) + { + var selection = window.getSelection(), + range = document.createRange(); + + range.selectNodeContents(_CPNativeInputField); + selection.removeAllRanges(); + selection.addRange(range); + } +#endif + +} + ++ (void)hideInputElement +{ + +#if PLATFORM(DOM) + _CPNativeInputField.style.top = "-10000px"; + _CPNativeInputField.style.left = "-10000px"; +#endif + +} + +@end + +@implementation CPAttributedString(_MinimalHTMLParser) + +-(void) _setRegularExpression:(JSObject)re toFontTrait:(CPFontTrait)aTrait +{ + while (match = re.exec(_string)) + { + var attribs = [[self attributesAtIndex:match.index effectiveRange:nil] copy], + font = [attribs objectForKey:CPFontAttributeName]; + [attribs setObject:[[CPFontManager sharedFontManager] convertFont:font toHaveTrait:aTrait] forKey:CPFontAttributeName] + [self setAttributes:attribs range:CPMakeRange(match.index, match[0].length)]; + } +} + +-(void) _replaceEveryOccurenceOfRegularExpression:(JSObject)re withString:(CPString)aString +{ + while (match = re.exec(_string)) + [self replaceCharactersInRange:CPMakeRange(match.index, match[0].length) withString:aString]; +} + + +-(CPAttributedString) _stringByParsingHTMLEntities +{ + [self _setRegularExpression:/(.+?)<\/b>/gi toFontTrait:CPFontBoldTrait]; + [self _setRegularExpression:/(.+?)<\/i>/gi toFontTrait:CPFontItalicTrait]; + [self _replaceEveryOccurenceOfRegularExpression:/<[^>]+>/i withString:'']; + [self _replaceEveryOccurenceOfRegularExpression:/</i withString:'<']; + [self _replaceEveryOccurenceOfRegularExpression:/>/i withString:'>']; + [self _replaceEveryOccurenceOfRegularExpression:/&/i withString:'&']; + + return self; +} + +@end diff --git a/AppKit/CPTextView/CPTypesetter.j b/AppKit/CPTextView/CPTypesetter.j new file mode 100644 index 000000000..475e65f87 --- /dev/null +++ b/AppKit/CPTextView/CPTypesetter.j @@ -0,0 +1,430 @@ + +/* + * CPTypesetter.j + * AppKit + * + * Created by Daniel Boehringer on 27/12/2013. + * All modifications copyright Daniel Boehringer 2013. + * Extensive code formatting and review by Andrew Hankinson + * Based on original work by + * Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * 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 + */ + +@import + +@import "CPParagraphStyle.j" +@import "CPTextStorage.j" +@import "CPFont.j" + +// forward declare these classes for type matching +@class CPLayoutManager +@class CPTextContainer +@class CPTextView + +/* + CPTypesetterControlCharacterAction +*/ +CPTypesetterZeroAdvancementAction = 1 << 0; +CPTypesetterWhitespaceAction = 1 << 1; +CPSTypesetterHorizontalTabAction = 1 << 2; +CPTypesetterLineBreakAction = 1 << 3; +CPTypesetterParagraphBreakAction = 1 << 4; +CPTypesetterContainerBreakAction = 1 << 5; + +var CPSystemTypesetterFactory, + _sharedSimpleTypesetter; + +@implementation CPTypesetter : CPObject + + +#pragma mark - +#pragma mark Class methods + ++ (void)initialize +{ + [CPTypesetter _setSystemTypesetterFactory:[CPSimpleTypesetter class]]; +} + ++ (id)sharedSystemTypesetter +{ + return [CPSystemTypesetterFactory sharedInstance]; +} + ++ (void)_setSystemTypesetterFactory:(Class)aClass +{ + CPSystemTypesetterFactory = aClass; +} + +- (CPTypesetterControlCharacterAction)actionForControlCharacterAtIndex:(unsigned)charIndex +{ + return CPTypesetterZeroAdvancementAction; +} + +- (CPLayoutManager)layoutManager +{ + return nil; +} + +- (CPTextContainer)currentTextContainer +{ + return nil; +} + +- (CPArray)textContainers +{ + return nil; +} + +- (void)layoutGlyphsInLayoutManager:(CPLayoutManager)layoutManager + startingAtGlyphIndex:(unsigned)startGlyphIndex + maxNumberOfLineFragments:(unsigned)maxNumLines + nextGlyphIndex:(UIntegerReference)nextGlyph +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +@end + +@implementation CPSimpleTypesetter : CPTypesetter +{ + CPLayoutManager _layoutManager @accessors(property=layoutManager); + CPTextContainer _currentTextContainer @accessors(property=currentTextContainer); + CPTextStorage _textStorage; + + CPRange _attributesRange; + CPDictionary _currentAttributes; + CPParagraphStyle _currentParagraph; + + float _lineHeight; + float _lineBase; + float _lineWidth; + + unsigned _indexOfCurrentContainer; + + CPArray _lineFragments; +} + + +#pragma mark - +#pragma mark Class methods + ++ (id)sharedInstance +{ + if (!_sharedSimpleTypesetter) + _sharedSimpleTypesetter = [[CPSimpleTypesetter alloc] init]; + + return _sharedSimpleTypesetter; +} + +- (CPArray)textContainers +{ + return [_layoutManager textContainers]; +} + +- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction +{ + var tabStops = [_currentParagraph tabStops]; + + if (!tabStops) + tabStops = [CPParagraphStyle _defaultTabStops]; + + var l = tabStops.length; + + if (aWidth > tabStops[l - 1]._location) + return nil; + + for (var i = l - 1; i >= 0; i--) + { + if (aWidth > tabStops[i]._location) + { + if (i + 1 < l) + return tabStops[i + 1]; + } + } + + if (i === -1) + return tabStops[0]; + + return nil; +} + +- (BOOL)_flushRange:(CPRange)lineRange + lineOrigin:(CGPoint)lineOrigin + currentContainer:(CPTextContainer)aContainer + advancements:(CPArray)advancements + lineCount:(unsigned)lineCount + sameLine:(BOOL)sameLine +{ + var myX = 0, + rect = CGRectMake(lineOrigin.x, lineOrigin.y, _lineWidth, _lineHeight), + containerSize = aContainer._size; + + [_layoutManager _appendNewLineFragmentInTextContainer:_currentTextContainer forGlyphRange:lineRange] + + var fragment = [_layoutManager._lineFragments lastObject]; + fragment._isLast = !sameLine; + _lineFragments.push(fragment); + + [_layoutManager setLineFragmentRect:rect forGlyphRange:lineRange usedRect:rect]; + + switch ([_currentParagraph alignment]) + { + case CPLeftTextAlignment: + myX = 0; + break; + + case CPCenterTextAlignment: + myX = (containerSize.width - _lineWidth) / 2; + break; + + case CPRightTextAlignment: + myX = containerSize.width - _lineWidth; + break; + } + + [_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange]; + [_layoutManager _setAdvancements:advancements forGlyphRange:lineRange]; + + if (!sameLine) //fix the _lineFragments when fontsizes differ + { + var l = _lineFragments.length; + + for (var i = 0 ; i < l ; i++) + [_lineFragments[i] _adjustForHeight:_lineHeight]; + } + + if (!lineCount) // do not rescue on first line + return NO; + + if (aContainer._inResizing) + return NO; + + return ([_layoutManager _rescuingInvalidFragmentsWasPossibleForGlyphRange:lineRange]); +} + +- (void)layoutGlyphsInLayoutManager:(CPLayoutManager)layoutManager + startingAtGlyphIndex:(unsigned)glyphIndex + maxNumberOfLineFragments:(unsigned)maxNumLines + nextGlyphIndex:(UIntegerReference)nextGlyph +{ + var textContainers = [layoutManager textContainers], + textContainersCount = [textContainers count]; + + _layoutManager = layoutManager; + _textStorage = [_layoutManager textStorage]; + _indexOfCurrentContainer = MAX(0, [textContainers + indexOfObject:[_layoutManager textContainerForGlyphAtIndex:glyphIndex effectiveRange:nil withoutAdditionalLayout:YES] + inRange:CPMakeRange(0, textContainersCount)]); + + _currentTextContainer = textContainers[_indexOfCurrentContainer]; + + _attributesRange = CPMakeRange(0, 0); + _lineHeight = 0; + _lineBase = 0; + _lineWidth = 0; + + var containerSize = [_currentTextContainer containerSize], + containerSizeWidth = containerSize.width, + containerSizeHeight = containerSize.height, + lineRange = CPMakeRange(glyphIndex, 0), + wrapRange = CPMakeRange(0, 0), + wrapWidth = 0, + isNewline = NO, + isTabStop = NO, + isWordWrapped = NO, + numberOfGlyphs= [_textStorage length], + leading, + numLines = 0, + theString = [_textStorage string], + lineOrigin, + ascent, + descent, + advancements = [], + prevRangeWidth = 0, + measuringRange = CPMakeRange(glyphIndex, 0), + currentAnchor = 0, + currentFont, + currentFontLineHeight, + previousFont, + currentParagraphMinimumLineHeight, + currentParagraphMaximumLineHeight, + currentParagraphLineSpacing; + + if (glyphIndex > 0) + lineOrigin = CGPointCreateCopy([_layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:nil].origin); + else if ([_layoutManager extraLineFragmentTextContainer]) + lineOrigin = CGPointMake(0, [_layoutManager extraLineFragmentUsedRect].origin.y); + else + lineOrigin = CGPointMake(0, 0); + + [_layoutManager _removeInvalidLineFragments]; + + if (![_textStorage length]) + return; + + _lineFragments = []; + + for (; numLines != maxNumLines && glyphIndex < numberOfGlyphs; glyphIndex++) + { + // check whether there any change in the attributes from here on + if (!CPLocationInRange(glyphIndex, _attributesRange)) + { + _currentAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:_attributesRange]; + currentFont = [_currentAttributes objectForKey:CPFontAttributeName]; + _currentParagraph = [_currentAttributes objectForKey:CPParagraphStyleAttributeName] || [CPParagraphStyle defaultParagraphStyle]; + currentParagraphMinimumLineHeight = [_currentParagraph minimumLineHeight]; + currentParagraphMaximumLineHeight = [_currentParagraph maximumLineHeight]; + currentParagraphLineSpacing = [_currentParagraph lineSpacing]; + + if (!currentFont) + currentFont = [_textStorage font] || [CPFont systemFontOfSize:12.0]; + + ascent = [currentFont ascender] + descent = [currentFont descender] + leading = (ascent - descent) * 0.2; // FAKE leading + + currentFontLineHeight = ascent - descent + leading; + + if (previousFont !== currentFont) + { + measuringRange = CPMakeRange(glyphIndex, 0); + currentAnchor = prevRangeWidth; + previousFont = currentFont; + } + + } + + if (currentFontLineHeight > _lineHeight) + _lineHeight = currentFontLineHeight; + + if (ascent > _lineBase) + _lineBase = ascent; + + lineRange.length++; + measuringRange.length++; + + 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) // faster than sending actionForControlCharacterAtIndex: called for each char. + { + case 9: // '\t' + { + var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0]; + + isTabStop = YES; + + if (nextTab) + rangeWidth = nextTab._location - lineOrigin.x; + else + rangeWidth += 28; //FIXME + } // fallthrough intentional + case 32: // ' ' + wrapRange = CPMakeRangeCopy(lineRange); + wrapWidth = rangeWidth; + wrapRange._height = _lineHeight; + wrapRange._base = _lineBase; + break; + + case 10: + case 13: + isNewline = YES; + } + + advancements.push({width: rangeWidth - prevRangeWidth, height: ascent, descent: descent}); + + prevRangeWidth = _lineWidth = rangeWidth; + + if (lineOrigin.x + rangeWidth > containerSizeWidth) + { + if (wrapWidth) + { + lineRange = wrapRange; + _lineWidth = wrapWidth; + _lineHeight = wrapRange._height; + _lineBase = wrapRange._base; + } + + isNewline = YES; + isWordWrapped = YES; + glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character + } + + if (isNewline || isTabStop) + { + if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline]) + return; + + if (isTabStop) + { + lineOrigin.x += rangeWidth; + isTabStop = NO; + } + + if (isNewline) + { + if (currentParagraphMinimumLineHeight && currentParagraphMinimumLineHeight > _lineHeight) + _lineHeight = currentParagraphMinimumLineHeight; + + if (currentParagraphMaximumLineHeight && currentParagraphMaximumLineHeight < _lineHeight) + _lineHeight = currentParagraphMaximumLineHeight; + + lineOrigin.y += _lineHeight; + + if (currentParagraphLineSpacing) + lineOrigin.y += currentParagraphLineSpacing; + + if (lineOrigin.y > containerSizeHeight && _indexOfCurrentContainer < textContainersCount - 1) + { + _currentTextContainer = textContainers[++_indexOfCurrentContainer]; + containerSize = [_currentTextContainer containerSize]; + containerSizeWidth = containerSize.width; + containerSizeHeight = containerSize.height; + } + + lineOrigin.x = 0; + numLines++; + isNewline = NO; + _lineFragments = []; + _lineHeight = 0; + _lineBase = ascent; + } + + _lineWidth = 0; + advancements = []; + currentAnchor = 0; + prevRangeWidth = 0; + lineRange = CPMakeRange(glyphIndex + 1, 0); + measuringRange = CPMakeRange(glyphIndex + 1, 0); + wrapRange = CPMakeRange(0, 0); + wrapWidth = 0; + isWordWrapped = NO; + } + } + + // this is to "flush" the remaining characters + if (lineRange.length) + { + [self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO]; + } + + var rect = CGRectMake(0, lineOrigin.y, containerSizeWidth, [_layoutManager._lineFragments lastObject]._usedRect.size.height - descent); + [_layoutManager setExtraLineFragmentRect:rect usedRect:rect textContainer:_currentTextContainer]; +} + +@end diff --git a/AppKit/CPTextView/_CPRTFParser.j b/AppKit/CPTextView/_CPRTFParser.j new file mode 100644 index 000000000..a94081c8d --- /dev/null +++ b/AppKit/CPTextView/_CPRTFParser.j @@ -0,0 +1,788 @@ +/* RTFParser.j + + Parse a RTF string into a CPAttributedString + + Copyright (C) 2014 Daniel Boehringer + +FIXME: this class should be redone using a 'real' parser + + * all paragraph spacing information is currently not parsed + + + * 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 +*/ + +@import +@import +@import "CPFontManager.j" +@import "CPParagraphStyle.j" + +@global CPLeftTextAlignment +@global CPRightTextAlignment +@global CPCenterTextAlignment +@global CPJustifiedTextAlignment +@global CPNaturalTextAlignment + +@global CPFontAttributeName +@global CPForegroundColorAttributeName + +var hexTable = []; + +// Hold the attributes of the current run +@implementation _RTFAttribute : CPObject +{ + CPRange _range; + CPParagraphStyle paragraph; + CPColor fgColour; + CPColor bgColour; + CPColor ulColour; + CPString fontName; + unsigned fontSize; + BOOL bold; + BOOL italic; + BOOL underline; + BOOL strikethrough; + BOOL script; + BOOL _tabChanged; +} + +- (id)init +{ + if (self = [super init]) + { + [self resetFont]; + [self resetParagraphStyle]; + _range = CPMakeRange(0, 0); + } + + return self; +} + +- (id)copy +{ + var mynew = [_RTFAttribute new]; + + mynew.paragraph = [paragraph copy]; + mynew.fontName = fontName; + mynew.fgColour = fgColour; + mynew.bgColour = bgColour; + mynew.ulColour = ulColour; + + return mynew; +} + +- (CPFont)currentFont +{ + var font = [CPFont _fontWithName:fontName size:fontSize bold:bold italic:italic]; + + if (font) + return font; + + //Before giving up and using a default font, we try if this is + //not the case of a font with a composite name, such as + //'Helvetica-Light'. In that case, even if we don't have + //exactly an 'Helvetica-Light' font family, we might have an + //'Helvetica' one. + var range = [fontName rangeOfString:@"-"]; + + if (range.location != CPNotFound) + { + var fontFamily = [fontName substringToIndex: range.location]; + + font = [CPFont fontWithName:fontFamily size:fontSize]; + } + + /* Last resort, default font. :-( */ + if (font == nil) + font = [CPFont systemFontOfSize:fontSize]; + + return font; +} + +- (CPNumber)script +{ + return [CPNumber numberWithInt: script]; +} + +- (CPNumber)underline +{ + if (underline != 0) + return [CPNumber numberWithInteger: underline]; + else + return nil; +} + +- (CPNumber)strikethrough +{ + if (strikethrough != 0) + return [CPNumber numberWithInteger: strikethrough]; + else + return nil; +} + +- (void)resetParagraphStyle +{ + paragraph = [[CPParagraphStyle defaultParagraphStyle] copy]; +} + +- (void)resetFont +{ + var font = [CPFont systemFontOfSize:12]; + + fontName = [font familyName]; + fontSize = 12.0; + italic = NO; + bold = NO; + underline = 0; + strikethrough = 0; + script = 0; +} + +- (void)addTab:(float)location type:(CPTextTabType)type +{ + var tab = [[CPTextTab alloc] initWithType:CPLeftTabStopType + location:location]; + + if (!_tabChanged) + { + [paragraph setTabStops:[tab]]; + _tabChanged = YES; + } + else + { + [paragraph addTabStop: tab]; + } +} + +- (CPDictionary)dictionary +{ + var ret = @{}; + [ret setObject:[self currentFont] forKey:CPFontAttributeName]; + [ret setObject:paragraph forKey:CPParagraphStyleAttributeName]; + + if (fgColour) + [ret setObject:fgColour forKey:CPForegroundColorAttributeName]; + + return ret; +} +@end + + +// based on https://github.com/lazygyu/RTF-parser + +var kRTFParserType_char = 0, + kRTFParserType_dest = 1, + kRTFParserType_prop = 2, + kRTFParserType_spec = 3; + +// Keyword descriptions +var kRgsymRtf = { + // keyword dflt fPassDflt kwd idx + "b" : [ "b", 1, false, kRTFParserType_prop, "propBold"], + "ul" : [ "ul", 1, false, kRTFParserType_prop, "propUnderline"], + "i" : [ "i", 1, false, kRTFParserType_prop, "propItalic"], + "li" : [ "li", 0, false, kRTFParserType_prop, "propPgnFormat"], + "pgnucltr" : [ "pgnucltr", "pgULtr", true, kRTFParserType_prop, "propPgnFormat"], + "pgnlcltr" : [ "pgnlcltr", "pgLLtr", true, kRTFParserType_prop, "propPgnFormat"], + "qc" : [ "qc", "justC", true, kRTFParserType_prop, "propJust"], + "ql" : [ "ql", "justL", true, kRTFParserType_prop, "propJust"], + "qr" : [ "qr", "justR", true, kRTFParserType_prop, "propJust"], + "qj" : [ "qj", "justF", true, kRTFParserType_prop, "propJust"], + "paperw" : [ "paperw", 12240, false, kRTFParserType_prop, "propXaPage"], + "paperh" : [ "paperh", 15480, false, kRTFParserType_prop, "propYaPage"], + "margl" : [ "margl", 1800, false, kRTFParserType_prop, "propXaLeft"], + "margr" : [ "margr", 1800, false, kRTFParserType_prop, "propXaRight"], + "margt" : [ "margt", 1440, false, kRTFParserType_prop, "propYaTop"], + "margb" : [ "margb", 1440, false, kRTFParserType_prop, "propYaBottom"], + "pgnstart" : [ "pgnstart", 1, true, kRTFParserType_prop, "propPgnStart"], + "facingp" : [ "facingp", 1, true, kRTFParserType_prop, "propFacingp"], + "landscape" : [ "landscape",1, true, kRTFParserType_prop, "propLandscape"], + "par" : [ "par", 0, false, kRTFParserType_char, "\n"], + "pard" : [ "pard", 0, false, kRTFParserType_prop, "propDefaultPara"], + "\0x0a" : [ "\0x0a", 0, false, kRTFParserType_char, "\n"], + "\0x0d" : [ "\0x0d", 0, false, kRTFParserType_char, ""], + "tab" : [ "tab", 0, false, kRTFParserType_char, "\t"], + "ldblquote" : [ "ldblquote",0, false, kRTFParserType_char, '"'], + "rdblquote" : [ "rdblquote",0, false, kRTFParserType_char, '"'], + "bin" : [ "bin", 0, false, kRTFParserType_spec, "ipfnBin"], + "*" : [ "*", 0, false, kRTFParserType_spec, "ipfnDestSkip"], + "'" : [ "'", 0, false, kRTFParserType_spec, "ipfnHex"], + "author" : [ "author", 0, false, kRTFParserType_dest, "destSkip"], + "buptim" : [ "buptim", 0, false, kRTFParserType_dest, "destSkip"], + "colortbl" : [ "colortbl", 0, false, kRTFParserType_dest, "destSkip"], + "comment" : [ "comment", 0, false, kRTFParserType_dest, "destSkip"], + "creatim" : [ "creatim", 0, false, kRTFParserType_dest, "destSkip"], + "doccomm" : [ "doccomm", 0, false, kRTFParserType_dest, "destSkip"], + "fonttbl" : [ "fonttbl", 0, false, kRTFParserType_dest, "destSkip"], + "footer" : [ "footer", 0, false, kRTFParserType_dest, "destSkip"], + "footerf" : [ "footerf", 0, false, kRTFParserType_dest, "destSkip"], + "footerl" : [ "footerl", 0, false, kRTFParserType_dest, "destSkip"], + "footerr" : [ "footerr", 0, false, kRTFParserType_dest, "destSkip"], + "footnote" : [ "footnote", 0, false, kRTFParserType_dest, "destSkip"], + "ftncn" : [ "ftncn", 0, false, kRTFParserType_dest, "destSkip"], + "ftnsep" : [ "ftnsep", 0, false, kRTFParserType_dest, "destSkip"], + "ftnsepc" : [ "ftnsepc", 0, false, kRTFParserType_dest, "destSkip"], + "fprq" : [ "fprq", 0, false, kRTFParserType_dest, "destSkip"], +// "fcharset" : [ "fcharset", 0, false, kRTFParserType_dest, "destSkip"], + "rquote" : [ "rquote", 0, false, kRTFParserType_char, "'"], +// "s" : [ "s", 0, false, kRTFParserType_dest, "destSkip"], + "header" : [ "header", 0, false, kRTFParserType_dest, "destSkip"], + "headerf" : [ "headerf", 0, false, kRTFParserType_dest, "destSkip"], + "headerl" : [ "headerl", 0, false, kRTFParserType_dest, "destSkip"], + "headerr" : [ "headerr", 0, false, kRTFParserType_dest, "destSkip"], + "info" : [ "info", 0, false, kRTFParserType_dest, "destSkip"], + "keywords" : [ "keywords", 0, false, kRTFParserType_dest, "destSkip"], + "operator" : [ "operator", 0, false, kRTFParserType_dest, "destSkip"], + "pict" : [ "pict", 0, false, kRTFParserType_dest, "destSkip"], + "printim" : [ "printim", 0, false, kRTFParserType_dest, "destSkip"], + "private1" : [ "private1", 0, false, kRTFParserType_dest, "destSkip"], + "revtim" : [ "revtim", 0, false, kRTFParserType_dest, "destSkip"], + "rxe" : [ "rxe", 0, false, kRTFParserType_dest, "destSkip"], + "stylesheet" : [ "stylesheet",0, false, kRTFParserType_dest, "destSkip"], + "subject" : [ "subject", 0, false, kRTFParserType_dest, "destSkip"], + "tc" : [ "tc", 0, false, kRTFParserType_dest, "destSkip"], + "title" : [ "title", 0, false, kRTFParserType_dest, "destSkip"], + "txe" : [ "txe", 0, false, kRTFParserType_dest, "destSkip"], + "xe" : [ "xe", 0, false, kRTFParserType_dest, "destSkip"], + "[" : [ "[", 0, false, kRTFParserType_char, '['], + " " : [ " ", 0, false, kRTFParserType_char, ' '], + "]" : [ "]", 0, false, kRTFParserType_char, ']'], + "{" : [ "{", 0, false, kRTFParserType_char, '{'], + "}" : [ "}", 0, false, kRTFParserType_char, '}'], + "\\" : [ "\\", 0, false, kRTFParserType_char, '\\'] + }; + +@implementation _CPRTFParser : CPObject +{ + CPString _codePage; + CGSize _paper; + CPString _rtf; + unsigned _curState; + CPArray _states; + unsigned _currentParseIndex; + BOOL _hexreturn; + _RTFAttribute _currentRun; + CPAttributedString _result; + CPArray _colorArray; + CPArray _fontArray; + CPString _freename; + BOOL _parsingFontTable; +} + +- (id)init +{ + if (self = [super init]) + { + _paper = CPMakeSize(0, 0); + _rtf = ""; + _curState = 0; // 0 = normal, 1 = skip + _states = []; + _currentParseIndex = 0; + _hexreturn = NO; + _result = [CPAttributedString new]; + _colorArray = []; + _fontArray = ['Arial']; // FIXME: should be name of system font + _freename = ""; + _parsingFontTable = NO; + } + + return self; +} + +- (CPString)_checkChar:(CPArray)sym parameter:(CPString)ch +{ + switch (_curState) + { + case 0: + if (sym && sym[4]) + return sym[4]; + + case 1: + // CPLogConsole("skipped : " + sym[4]); + return ''; + + default: + if (sym && sym[4]) + return sym[4]; + } +} + +- (BOOL)pushState +{ + _states.push["group"]; + return YES; +} + +- (BOOL)popState +{ + _states.pop(); + + if (_curState > 0) + _curState--; + + return YES; +} + +- (CPString)_parseSpec:(CPArray)sym parameter:(CPString)v +{ + var ch = ''; + + switch (sym[4]) + { + case "ipfnDestSkip": + _curState++; + return ''; + + case "ipfnHex": + ch = _rtf.charAt(++_currentParseIndex); + + var hex = ''; + + while (/[a-fA-F0-9\']/.test(ch)) + { + if (ch == "'") + { + _currentParseIndex++; + continue; + } + + hex += (ch + ''); + ch = _rtf.charAt(++_currentParseIndex); + } + //ch = parseInt(ch, 16); + //console.log("hex : " + hex); + _hexreturn = YES; + _currentParseIndex--; + + if (_curState !== 0) + return ''; + else + return hex; + break; + + case "codePage": + ch = _rtf.charAt(++_currentParseIndex); + + var code = ''; + + while (/[0-9]/.test(ch)) + { + code += (ch + ''); + ch = _rtf.charAt(++_currentParseIndex); + } + + _codePage = code; + _currentParseIndex--; + break; + } + + return ''; +} + +- (void)_flushCurrentRun +{ + var newOffset = 0; + + if (_currentRun) + { + if ([_result length] == _currentRun._range.location) + return; + + _currentRun._range.length = [_result length] - _currentRun._range.location; + newOffset = CPMaxRange(_currentRun._range); + + var dict = [_currentRun dictionary]; + + [_result setAttributes:dict range:_currentRun._range]; // flush previous run + _currentRun.fgColour = [CPColor blackColor]; + } + else + _currentRun = [_RTFAttribute new]; + + _currentRun._range = CPMakeRange(newOffset, 0); // open a new one +} + +- (CPString)_applyPropChange:sym parameter:param +{ + //console.log("prop : " + sym[0] + " / param : " + param+ ' '); + + switch (sym[0]) + { + case "pard": + [self _flushCurrentRun]; + break; + + case "b": // bold + if (param === 0) + { + if (_currentRun && _currentRun.bold) + [self _flushCurrentRun]; + + _currentRun.bold = NO + } + else + { + if (_currentRun && !_currentRun.bold) + [self _flushCurrentRun]; + + _currentRun.bold = YES; + } + + break; + + case "i": // italic + if (param === 0) + { + if (_currentRun && _currentRun.italic) + [self _flushCurrentRun]; + + _currentRun.italic = NO + } + else + { + if (_currentRun && !_currentRun.italic) + [self _flushCurrentRun]; + + _currentRun.italic = YES; + } + + break; + + case "qc": // paragraph center + [_currentRun.paragraph setAlignment:CPCenterTextAlignment]; + break; + + case "paperw": + _paper.width = param; + break; + + case "paperh": + _paper.height = param; + break; + } + + return ''; +} + + +- (CPString)_changeDest:(CPArray)sym +{ + switch (sym[0]) + { + case "colortbl": + _colorArray.push([CPColor blackColor]); + break; + + case "fonttbl": + _parsingFontTable = YES; + break; + } + + if (sym[4] == "destSkip") + { + CPLogConsole("Dest skip start : [" + sym[0] + "]"); + _curState++; + } + + return ''; +} + +- (CPString)_translateKeyword:(CPString)keyword parameter:(CPString)param fParameter:(BOOL)fParam +{ + if (kRgsymRtf[keyword] !== undefined) + { + var sym = kRgsymRtf[keyword]; + + switch (sym[3]) + { + case kRTFParserType_prop: + if (sym[2] || !fParam) + param = sym[1]; + + return [self _applyPropChange:sym parameter:param]; + + case kRTFParserType_char: + return [self _checkChar:sym parameter:param]; + + case kRTFParserType_dest: + return [self _changeDest:sym]; + + case kRTFParserType_spec: + return [self _parseSpec:sym parameter:param]; + + default: + return ''; + } + } + else + { + switch (keyword) + { + case "red": + var oldColor = [_colorArray lastObject], + green = [oldColor greenComponent], + blue = [oldColor blueComponent]; + + _colorArray.pop(); + _colorArray.push([CPColor colorWithRed:parseInt(param) / 255 green:green blue:blue alpha:1.0]); + break; + + case "green": + var oldColor = [_colorArray lastObject], + red = [oldColor redComponent], + blue = [oldColor blueComponent]; + + _colorArray.pop(); + _colorArray.push([CPColor colorWithRed:red green: parseInt(param) / 255 blue:blue alpha:1.0]); + break; + + case "blue": + var oldColor = [_colorArray lastObject], + green = [oldColor greenComponent], + red = [oldColor redComponent]; + + _colorArray.pop(); + _colorArray.push([CPColor colorWithRed:red green:green blue:parseInt(param) / 255 alpha:1.0]); + _colorArray.push([CPColor blackColor]); // placeholder for next color + break; + + case "cf": // change foreground color + [self _flushCurrentRun]; + var fontIndex = parseInt(param) - 1; + + if (_currentRun && fontIndex >= 0) + _currentRun.fgColour = _colorArray[fontIndex]; + + break; + + case "f": // change font + [self _flushCurrentRun]; + var fontIndex = parseInt(param); + + if (_currentRun && fontIndex >= 0 && fontIndex < _fontArray.length) + _currentRun.fontName = _fontArray[fontIndex]; + break; + + case "fs": // change font size + [self _flushCurrentRun]; + _currentRun.fontSize = parseInt(param) / 2; + break; + + case "tx": // tabstop + var location = parseInt(param) / 20; + + if (_currentRun) + [_currentRun addTab:location type:CPLeftTabStopType]; + + break; + + default: + CPLogConsole("skip : " + keyword + " param: " + param); + + } + + if (_states.length > 0) + _curState = 1; + + return ''; + } +} + +- (CPString)_parseKeyword:(CPString)rtf length:(unsigned)len +{ + var ch = '', + fParam = false, + fNeg = false, + keyword = '', + param = ''; + + _rtf = rtf; + + if (++_currentParseIndex >= len) + return len; + + ch = rtf.charAt(_currentParseIndex); + + if (!/[a-zA-Z]/.test(ch)) + return [self _translateKeyword:ch parameter:nil fParameter:fParam]; + + while (/[a-zA-Z]/.test(ch)) + { + keyword += ch; + ch = rtf.charAt(++_currentParseIndex); + } + + if (ch == '-') + { + fNeg = true; + ch = rtf.charAt(++_currentParseIndex); + } + + fParam = true; + + while (/[0-9]/.test(ch)) + { + param += (ch + ''); + ch = rtf.charAt(++_currentParseIndex); + } + + _currentParseIndex--; + param = parseInt(param); + + if (fNeg) + param *= -1; + + return [self _translateKeyword:keyword parameter:param fParameter:fParam]; +} + +- (void)_appendPlainString:(CPString) aString +{ + [_result replaceCharactersInRange:CPMakeRange([_result length], 0) withString:aString]; + +} +- (CPAttributedString)parseRTF:(CPString)rtf +{ + if (rtf.length == 0) + return ''; + + _currentParseIndex = -1; + + var len = rtf.length, + tmp = '', + ch = '', + hex = '', + lastchar = 0; + + while (_currentParseIndex < len) + { + tmp = rtf.charAt(++_currentParseIndex); + + if (tmp !== "\\" && hex.length > 0) + { + [self _appendPlainString: String.fromCharCode(parseInt((hex), 16))]; + hex = ''; + } + + switch (tmp) + { + case " ": + if (lastchar == 1) + { + lastchar = 0; + } + else + { + _freename += tmp; + [self _appendPlainString:tmp]; + } + + break; + + case "{": + if ([self pushState]) + CPLogConsole("push"); + + break; + + case "}": + if ([self popState]) + CPLogConsole("pop"); + + if (_freename) + { + CPLogConsole(_freename); + + if (_parsingFontTable) + { + _fontArray.push(_freename); + _parsingFontTable = NO; + } + + _freename = ""; + } + + [self _flushCurrentRun] + break; + + case "\\": + _freename = ''; + ch = [self _parseKeyword:rtf length:len]; + + if (!_hexreturn && ch.length == 0) + lastchar = 1; + else + lastchar = 0; + + if (_hexreturn) + { + if (ch.length > 0) + { + if (parseInt(ch, 16) & 0x80) + { + hex += ch.toUpperCase(); + } + else + { + [self _appendPlainString: String.fromCharCode(parseInt((hex + ch), 16))]; + hex = ''; + } + + if (hex.length == 4) + { + var temp = parseInt(hex, 16); + + if (hexTable && hexTable[hex.toUpperCase()] !== undefined) + temp = parseInt(hexTable[hex.toUpperCase()], 16); + + [self _appendPlainString: String.fromCharCode(temp)] + hex = ''; + } + } + else + { + CPLogConsole("hex skipped"); + } + + _hexreturn = NO; + } + else if (ch !== undefined && _curState === 0) + { + [self _appendPlainString:ch]; + } + + break; + + case 0x0d: + case 0x0a: + case '\n': + case '\r': + break; + + default: + lastchar = 0; + + if (_curState == 0) + [self _appendPlainString:tmp]; + else if (tmp !== ';') + _freename += tmp; + + break; + } + } + + return _result; +} + +@end \ No newline at end of file diff --git a/AppKit/CPTextView/_CPRTFProducer.j b/AppKit/CPTextView/_CPRTFProducer.j new file mode 100644 index 000000000..ef540ef3d --- /dev/null +++ b/AppKit/CPTextView/_CPRTFProducer.j @@ -0,0 +1,615 @@ +/* + RTFProducer.j + + Serialize CPAttributedString to a RTF String + + Copyright (C) 2014 Daniel Boehringer + This file is based on the RTFProducer from GNUStep + (which i co-authored with Fred Kiefer in 1999) + + * 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 + */ + +@import +@import "CPParagraphStyle.j" +@import "CPColor.j" +@import "CPGraphics.j" +@import "CPFontManager.j" + +@global CPForegroundColorAttributeName +@global CPBackgroundColorAttributeName +@global CPUnderlineStyleAttributeName +@global CPSuperscriptAttributeName +@global CPBaselineOffsetAttributeName +@global CPAttachmentAttributeName +@global CPLigatureAttributeName +@global CPKernAttributeName + +@global CPLeftTextAlignment +@global CPRightTextAlignment +@global CPCenterTextAlignment +@global CPJustifiedTextAlignment +@global CPNaturalTextAlignment + +var PAPERSIZE = @"PaperSize", + LEFTMARGIN = @"LeftMargin", + RIGHTMARGIN = @"RightMargin", + TOPMARGIN = @"TopMargin", + BUTTOMMARGIN = @"ButtomMargin"; + +function _points2twips(a) { return (a) * 20.0; } + +@implementation _CPRTFProducer : CPObject +{ + CPAttributedString text; + CPMutableDictionary fontDict; + CPMutableDictionary colorDict; + CPDictionary docDict; + CPMutableArray attachments; + CPFont currentFont; + CPColor fgColor; + CPColor bgColor; + CPColor ulColor; +} + +#pragma mark - +#pragma mark Class methods + + ++ (CPString)produceRTF:(CPAttributedString)aText documentAttributes:(CPDictionary)dict +{ + var mynew = [self new]; + + return [mynew RTFDStringFromAttributedString:aText documentAttributes:dict]; +} + + +#pragma mark - +#pragma mark init methods + +- (id)init +{ + if (self = [super init]) + { + // maintain a dictionary for the used colours + // (for rtf-header generation) + colorDict = [CPMutableDictionary new]; + + //maintain a dictionary for the used fonts + //(for rtf-header generation) + fontDict = [CPMutableDictionary new]; + + fgColor = [CPColor blackColor]; + bgColor= [CPColor whiteColor]; + } + + return self; +} + +// private stuff follows +- (CPString)fontTable +{ + if (![fontDict count]) + return @""; + + var fontlistString = "", + fontEnum, + currFont, + keyArray; + + keyArray = [fontDict allKeys]; + keyArray = [keyArray sortedArrayUsingSelector:@selector(compare:)]; + fontEnum = [keyArray objectEnumerator]; + + while ((currFont = [fontEnum nextObject]) !== nil) + { + var fontFamily, + detail; + + if ([currFont isEqualToString:@"Symbol"]) + fontFamily = @"tech"; + else if ([currFont isEqualToString:@"Helvetica"]) + fontFamily = @"swiss"; + else if ([currFont isEqualToString:@"Arial"]) + fontFamily = @"swiss"; + else if ([currFont isEqualToString:@"Courier"]) + fontFamily = @"modern"; + else if ([currFont isEqualToString:@"Times"]) + fontFamily = @"roman"; + else fontFamily = @"nil"; + + detail = [CPString stringWithFormat:@"%@\\f%@ %@;", [fontDict objectForKey:currFont], fontFamily, currFont]; + fontlistString += detail; + } + + return [CPString stringWithFormat:@"{\\fonttbl%@}\n", fontlistString]; +} + +- (CPString)colorTable +{ + if (![colorDict count]) + return @""; + + var result, + count = [colorDict count], + list = [CPMutableArray arrayWithCapacity:count], + keyEnum = [colorDict keyEnumerator], + next, + i; + + while ((next = [keyEnum nextObject]) !== nil) + { + var cn = [colorDict objectForKey:next]; + [list insertObject:[CPColor colorWithCSSString:next] atIndex:[cn intValue]-1]; + } + + result = [CPString stringWithString:@"{\\colortbl;"]; + + for (i = 0; i < count; i++) + { + var color = [[list objectAtIndex:i] + colorUsingColorSpaceName:CPCalibratedRGBColorSpace]; + + result += [CPString stringWithFormat:@"\\red%d\\green%d\\blue%d;", + ([color redComponent] * 255), + ([color greenComponent] * 255), + ([color blueComponent] * 255)]; + } + + result += @"}\n"; + + return result; +} + +- (CPString)documentAttributes +{ + if (!docDict) + return @""; + + var result, + detail, + val, + num; + + result = [CPString string]; + + val = [docDict objectForKey:PAPERSIZE]; + + if (val) + { + var size = [val sizeValue]; + + detail = [CPString stringWithFormat:@"\\paperw%d \\paperh%d", + _points2twips(size.width), + _points2twips(size.height)]; + + result += detail; + } + + num = [docDict objectForKey:LEFTMARGIN]; + + if (num) + { + var f = [num floatValue]; + + detail = [CPString stringWithFormat:@"\\margl%d", _points2twips(f)]; + result += detail; + } + + num = [docDict objectForKey:RIGHTMARGIN]; + + if (num) + { + var f = [num floatValue]; + + detail = [CPString stringWithFormat:@"\\margr%d", _points2twips(f)]; + result += detail; + } + + num = [docDict objectForKey:TOPMARGIN]; + + if (num) + { + var f = [num floatValue]; + + detail = [CPString stringWithFormat:@"\\margt%d", _points2twips(f)]; + result += detail; + } + + num = [docDict objectForKey:BUTTOMMARGIN]; + + if (num) + { + var f = [num floatValue]; + + detail = [CPString stringWithFormat:@"\\margb%d", _points2twips(f)]; + result += detail; + } + + return result; +} + +- (CPString)headerString +{ + var result; + + result = [CPString stringWithString:@"{\\rtf1\\ansi"]; + result += [self fontTable]; + result += [self colorTable]; + result += [self documentAttributes]; + + return result; +} + +- (CPString)trailerString +{ + return @"}"; +} + +- (CPString)fontToken:(CPString) fontName +{ + var fCount = [fontDict objectForKey:fontName]; + + if (fCount == nil) + { + var count = [fontDict count]; + + fCount = [CPString stringWithFormat:@"\\f%d", count]; + [fontDict setObject:fCount forKey:fontName]; + } + + return fCount; +} + +- (int)numberForColor:(CPColor)color +{ + var num = [colorDict objectForKey:[color cssString]]; + + if (!num) + [colorDict setObject:num = [CPNumber numberWithInt:[colorDict count] + 1] + forKey:[color cssString]]; + + return [num intValue]; +} + +- (CPString)paragraphStyle:(CPParagraphStyle)paraStyle +{ + var headerString = [CPString stringWithString:@"\\pard"], + twips; + + if (paraStyle == nil) + return headerString; + + switch ([paraStyle alignment]) + { + case CPRightTextAlignment: + headerString += @"\\qr"; + break; + + case CPCenterTextAlignment: + headerString += @"\\qc"; + break; + + case CPLeftTextAlignment: + headerString += @"\\ql"; + break; + + case CPJustifiedTextAlignment: + headerString += @"\\qj"; + break; + + default: + headerString += @"\\ql"; + break; + } + + // write first line indent and left indent + var twips = _points2twips([paraStyle firstLineHeadIndent]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\fi%d", twips]; + + twips = _points2twips([paraStyle headIndent]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\li%d", twips]; + + twips = _points2twips([paraStyle tailIndent]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\ri%d", twips]; + + twips = _points2twips([paraStyle paragraphSpacing]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\sa%d", twips]; + + twips = _points2twips([paraStyle minimumLineHeight]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\sl%d", twips]; + + twips = _points2twips([paraStyle maximumLineHeight]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\sl-%d", twips]; + + var enumerator, + tab; + + enumerator = [[paraStyle tabStops] objectEnumerator]; + + while ((tab = [enumerator nextObject])) + { + switch ([tab tabStopType]) + { + case CPLeftTabStopType: + // no tabkind emission needed + break; +/* case NSRightTabStopType: + headerString += @"\\tqr"; + break; + case NSCenterTabStopType: + headerString += @"\\tqc"; + break; + case NSDecimalTabStopType: + headerString += @"\\tqdec"; + break; + default: + NSLog(@"Unknown tab stop type."); +*/ + } + + headerString += [CPString stringWithFormat:@"\\tx%d",_points2twips([tab location])]; + } + + return headerString; +} + +- (CPString)runStringForString:(CPString) substring + attributes:(CPDictionary) attributes + paragraphStart:(BOOL) first +{ + var result = "", + headerString = "", + trailerString = "", + attribEnum, + currAttrib; + + if (first) + { + var paraStyle = [attributes objectForKey:CPParagraphStyleAttributeName]; + headerString += [self paragraphStyle:paraStyle]; + } + + /* + * analyze attributes of current run + * + * FIXME: All the character attributes should be output relative to the font + * attributes of the paragraph. So if the paragraph has underline on it should + * still be possible to switch it off for some characters, which currently is + * not possible. + */ + attribEnum = [attributes keyEnumerator]; + + while ((currAttrib = [attribEnum nextObject]) != nil) + { + if ([currAttrib isEqualToString:CPFontAttributeName]) + { + /* + * handle fonts + */ + var font, + fontName, + traits; + + font = [attributes objectForKey:CPFontAttributeName]; + fontName = [font familyName]; + traits = [[CPFontManager sharedFontManager] traitsOfFont:font]; + + /* + * font name + */ + if (currentFont == nil || ![fontName isEqualToString:[currentFont familyName]]) + headerString += [self fontToken:fontName]; + + /* + * font size + */ + if (currentFont == nil || [font size] != [currentFont size]) + { + var points = [font size] * 2, + pString; + + pString = [CPString stringWithFormat:@"\\fs%d", points]; + headerString += pString; + } + /* + * font attributes + */ + if (traits & CPItalicFontMask) + { + headerString += @"\\i"; + trailerString += @"\\i0"; + } + + if (traits & CPBoldFontMask) + { + headerString += @"\\b"; + trailerString += @"\\b0"; + } + + if (first) + currentFont = font; + } + else if ([currAttrib isEqualToString:CPForegroundColorAttributeName]) + { + var color = [attributes objectForKey:CPForegroundColorAttributeName]; + + if (![color isEqual:fgColor]) + { + headerString += [CPString stringWithFormat:@"\\cf%d", [self numberForColor:color]]; + trailerString += @"\\cf0"; + } + } + else if ([currAttrib isEqualToString:CPBackgroundColorAttributeName]) + { + var color = [attributes objectForKey:CPBackgroundColorAttributeName]; + + if (![color isEqual:bgColor]) + { + headerString += [CPString stringWithFormat:@"\\cb%d", [self numberForColor:color]]; + trailerString += @"\\cb0"; + } + } + else if ([currAttrib isEqualToString:CPUnderlineStyleAttributeName]) + { + headerString += @"\\ul"; + trailerString += @"\\ulnone"; + } + else if ([currAttrib isEqualToString:CPSuperscriptAttributeName]) + { + var value = [attributes objectForKey:CPSuperscriptAttributeName], + svalue = [value intValue] * 6; + + if (svalue > 0) + { + headerString += [CPString stringWithFormat:@"\\up%d", svalue]; + trailerString += @"\\up0"; + } + else if (svalue < 0) + { + headerString += [CPString stringWithFormat:@"\\dn-%d", svalue]; + trailerString += @"\\dn0"; + } + } + else if ([currAttrib isEqualToString:CPBaselineOffsetAttributeName]) + { + var value = [attributes objectForKey:CPBaselineOffsetAttributeName], + svalue = [value floatValue] * 2; + + if (svalue > 0) + { + headerString += [CPString stringWithFormat:@"\\up%d", svalue]; + trailerString += @"\\up0"; + } + else if (svalue < 0) + { + headerString += [CPString stringWithFormat:@"\\dn-%d", svalue]; + trailerString += @"\\dn0"; + } + } + else if ([currAttrib isEqualToString:CPAttachmentAttributeName]) + { + } + else if ([currAttrib isEqualToString:CPLigatureAttributeName]) + { + } + else if ([currAttrib isEqualToString:CPKernAttributeName]) + { + } + } + + substring = substring.replace(/\\/g, '\\\\'); + substring = substring.replace(/\n/g, '\\par\n'); + substring = substring.replace(/\t/g, '\\tab'); + substring = substring.replace(/{/g, '\\{'); + substring = substring.replace(/}/g, '\\}'); + // FIXME: All characters not in the standard encoding must be + // replaced by \'xx + + if (!first) + { + var braces; + + if ([headerString length]) + braces = [CPString stringWithFormat:@"{%@ %@}", headerString, substring]; + else + braces = substring; + + result += braces; + } + else + { + var nobraces; + + if ([headerString length]) + nobraces = [CPString stringWithFormat:@"%@ %@", headerString, substring]; + else + nobraces = substring; + + result += nobraces; + } + + return result + trailerString; +} + +- (CPString)bodyString +{ + var string = [text string], + result = "", + loc = 0, + length = [string length], + currRange = CPMakeRange(loc, 0), + completeRange = CPMakeRange(0, length), + first = YES; + + // FIXME split along newline characters and run as outer loop + while (CPMaxRange(currRange) < CPMaxRange(completeRange)) // save all "runs" + { + var attributes, + substring, + runString; + + attributes = [text attributesAtIndex:CPMaxRange(currRange) + longestEffectiveRange:currRange + inRange:completeRange]; + substring = [string substringWithRange:currRange]; + runString = [self runStringForString:substring + attributes:attributes + paragraphStart:YES]; + + result += runString; + first = NO; + } + + return result; +} + + +- (CPString)RTFDStringFromAttributedString:(CPAttributedString)aText + documentAttributes:(CPDictionary)dict +{ + var output = [CPString string], + headerString, + trailerString, + bodyString; + + text = aText; + docDict = dict; + + /* + * do not change order! (esp. body has to be generated first; builds context) + */ + bodyString = [self bodyString]; + trailerString = [self trailerString]; + headerString = [self headerString]; + + output += headerString; + output += bodyString; + output += trailerString; + return output; +} +@end \ No newline at end of file diff --git a/AppKit/CPThemeBlend.j b/AppKit/CPThemeBlend.j index 8455bd4c0..78cceaac3 100644 --- a/AppKit/CPThemeBlend.j +++ b/AppKit/CPThemeBlend.j @@ -64,12 +64,10 @@ */ - (CPArray)themeNames { - var names = []; - - for (var i = 0; i < _themes.length; ++i) - names.push(_themes[i].substring(0, _themes[i].indexOf(".keyedtheme"))); - - return names; + return [_themes arrayByApplyingBlock:function(theme) + { + return theme.substring(0, theme.indexOf(".keyedtheme")); + }]; } - (void)loadWithDelegate:(id)aDelegate diff --git a/AppKit/CPToolbar.j b/AppKit/CPToolbar.j index 49c78924e..b62aa82d6 100644 --- a/AppKit/CPToolbar.j +++ b/AppKit/CPToolbar.j @@ -459,11 +459,10 @@ var CPToolbarsByIdentifier = nil, /* @ignore */ - (id)_itemsWithIdentifiers:(CPArray)identifiers { - var items = []; - for (var i = 0; i < identifiers.length; i++) - [items addObject:[self _itemForItemIdentifier:identifiers[i] willBeInsertedIntoToolbar:NO]]; - - return items; + return [identifiers arrayByApplyingBlock:function(identifier) + { + return [self _itemForItemIdentifier:identifier willBeInsertedIntoToolbar:NO]; + }]; } /* @ignore */ diff --git a/AppKit/CPView.j b/AppKit/CPView.j index 5b8d66007..cadcdf356 100644 --- a/AppKit/CPView.j +++ b/AppKit/CPView.j @@ -112,6 +112,11 @@ CPViewHeightSizable = 16; */ CPViewMaxYMargin = 32; +_CPViewWillAppearNotification = @"CPViewWillAppearNotification"; +_CPViewDidAppearNotification = @"CPViewDidAppearNotification"; +_CPViewWillDisappearNotification = @"CPViewWillDisappearNotification"; +_CPViewDidDisappearNotification = @"CPViewDidDisappearNotification"; + CPViewBoundsDidChangeNotification = @"CPViewBoundsDidChangeNotification"; CPViewFrameDidChangeNotification = @"CPViewFrameDidChangeNotification"; @@ -172,6 +177,7 @@ var CPViewHighDPIDrawingEnabled = YES; CPArray _registeredDraggedTypesArray; BOOL _isHidden; + BOOL _isHiddenOrHasHiddenAncestor; BOOL _hitTests; BOOL _clipsToBounds; @@ -243,6 +249,9 @@ var CPViewHighDPIDrawingEnabled = YES; CPMutableArray _trackingAreas @accessors(getter=trackingAreas, copy); BOOL _inhibitUpdateTrackingAreas; + + id _animator; + CPDictionary _animationsDictionary; } /* @@ -369,6 +378,7 @@ var CPViewHighDPIDrawingEnabled = YES; _opacity = 1.0; _isHidden = NO; + _isHiddenOrHasHiddenAncestor = NO; _hitTests = YES; _hierarchyScaleSize = CGSizeMake(1.0 , 1.0); @@ -389,6 +399,9 @@ var CPViewHighDPIDrawingEnabled = YES; _DOMImageSizes = []; #endif + _animator = nil; + _animationsDictionary = @{}; + [self _setupViewFlags]; [self _loadThemeAttributes]; } @@ -396,7 +409,6 @@ var CPViewHighDPIDrawingEnabled = YES; return self; } - /*! Sets the tooltip for the receiver. @@ -591,8 +603,9 @@ var CPViewHighDPIDrawingEnabled = YES; // Remove the view from its previous superview. [aSubview _removeFromSuperview]; + [aSubview _postViewWillAppearNotification]; // Set ourselves as the superview. - aSubview._superview = self; + [aSubview _setSuperview:self]; } if (anIndex === CPNotFound || anIndex >= count) @@ -617,11 +630,6 @@ var CPViewHighDPIDrawingEnabled = YES; [aSubview setNextResponder:self]; [aSubview _scaleSizeUnitSquareToSize:[self _hierarchyScaleSize]]; - // If the subview is not hidden and one of its ancestors is hidden, - // notify the subview that it is now hidden. - if (![aSubview isHidden] && [self isHiddenOrHasHiddenAncestor]) - [aSubview _notifyViewDidHide]; - [aSubview viewDidMoveToSuperview]; // Set the subview's window to our own. @@ -682,6 +690,7 @@ var CPViewHighDPIDrawingEnabled = YES; [[self window] _dirtyKeyViewLoop]; [_superview willRemoveSubview:self]; + [self _postViewWillDisappearNotification]; [_superview._subviews removeObjectIdenticalTo:self]; @@ -691,13 +700,10 @@ var CPViewHighDPIDrawingEnabled = YES; // If the view is not hidden and one of its ancestors is hidden, // notify the view that it is now unhidden. - if (!_isHidden && [_superview isHiddenOrHasHiddenAncestor]) - [self _notifyViewDidUnhide]; + [self _setSuperview:nil]; [self _notifyWindowDidResignKey]; [self _notifyViewDidResignFirstResponder]; - - _superview = nil; } /*! @@ -805,13 +811,13 @@ var CPViewHighDPIDrawingEnabled = YES; [_window _noteUnregisteredDraggedTypes:_registeredDraggedTypes]; [aWindow _noteRegisteredDraggedTypes:_registeredDraggedTypes]; } - + // View must be removed from the current window viewsWithTrackingAreas if (_window && (_trackingAreas.length > 0)) [_window _removeTrackingAreaView:self]; _window = aWindow; - + if (_window) { var owners; @@ -1593,10 +1599,8 @@ var CPViewHighDPIDrawingEnabled = YES; // FIXME: Should we return to visibility? This breaks in FireFox, Opera, and IE. // _DOMElement.style.visibility = (_isHidden = aFlag) ? "hidden" : "visible"; - _isHidden = aFlag; - #if PLATFORM(DOM) - _DOMElement.style.display = _isHidden ? "none" : "block"; + _DOMElement.style.display = aFlag ? "none" : "block"; #endif if (aFlag) @@ -1616,33 +1620,89 @@ var CPViewHighDPIDrawingEnabled = YES; while (view = [view superview]); } - [self _notifyViewDidHide]; + [self _postViewWillDisappearNotification]; + [self _recursiveGainedHiddenAncestor]; } else { [self setNeedsDisplay:YES]; - [self _notifyViewDidUnhide]; + + [self _postViewWillAppearNotification]; + [self _recursiveLostHiddenAncestor]; } + + _isHidden = aFlag; } -- (void)_notifyViewDidHide +- (void)_postViewWillAppearNotification { - [self viewDidHide]; - - var count = [_subviews count]; - - while (count--) - [_subviews[count] _notifyViewDidHide]; + [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewWillAppearNotification object:self userInfo:nil]; } -- (void)_notifyViewDidUnhide +- (void)_postViewDidAppearNotification { - [self viewDidUnhide]; + [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewDidAppearNotification object:self userInfo:nil]; +} - var count = [_subviews count]; +- (void)_postViewWillDisappearNotification +{ + [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewWillDisappearNotification object:self userInfo:nil]; +} - while (count--) - [_subviews[count] _notifyViewDidUnhide]; +- (void)_postViewDidDisappearNotification +{ + [[CPNotificationCenter defaultCenter] postNotificationName:_CPViewDidDisappearNotification object:self userInfo:nil]; +} + +- (void)_setSuperview:(CPView)aSuperview +{ + var hasOldSuperview = (_superview !== nil), + hasNewSuperview = (aSuperview !== nil), + oldSuperviewIsHidden = hasOldSuperview && [_superview isHiddenOrHasHiddenAncestor], + newSuperviewIsHidden = hasNewSuperview && [aSuperview isHiddenOrHasHiddenAncestor]; + + if (!newSuperviewIsHidden && oldSuperviewIsHidden) + [self _recursiveLostHiddenAncestor]; + + if (newSuperviewIsHidden && !oldSuperviewIsHidden) + [self _recursiveGainedHiddenAncestor]; + + _superview = aSuperview; + + if (hasOldSuperview) + [self _postViewDidDisappearNotification]; + + if (hasNewSuperview) + [self _postViewDidAppearNotification]; +} + +- (void)_recursiveLostHiddenAncestor +{ + if (_isHiddenOrHasHiddenAncestor) + { + _isHiddenOrHasHiddenAncestor = NO; + [self viewDidUnhide]; + } + + [_subviews enumerateObjectsUsingBlock:function(view, idx, stop) + { + [view _recursiveLostHiddenAncestor]; + }]; +} + +- (void)_recursiveGainedHiddenAncestor +{ + if (!_isHidden) + { + [self viewDidHide]; + } + + _isHiddenOrHasHiddenAncestor = YES; + + [_subviews enumerateObjectsUsingBlock:function(view, idx, stop) + { + [view _recursiveGainedHiddenAncestor]; + }]; } /*! @@ -1712,12 +1772,7 @@ var CPViewHighDPIDrawingEnabled = YES; */ - (BOOL)isHiddenOrHasHiddenAncestor { - var view = self; - - while (view && ![view isHidden]) - view = [view superview]; - - return view !== nil; + return _isHiddenOrHasHiddenAncestor; } /*! @@ -2684,20 +2739,23 @@ setBoundsOrigin: return _needsLayout; } +- (void)layout +{ + _needsLayout = NO; + + if (_viewClassFlags & CPViewHasCustomViewWillLayout) + [self viewWillLayout]; + + if (_viewClassFlags & CPViewHasCustomLayoutSubviews) + [self layoutSubviews]; + + [self viewDidLayout]; +} + - (void)layoutIfNeeded { if (_needsLayout) - { - _needsLayout = NO; - - if (_viewClassFlags & CPViewHasCustomViewWillLayout) - [self viewWillLayout]; - - if (_viewClassFlags & CPViewHasCustomLayoutSubviews) - [self layoutSubviews]; - - [self viewDidLayout]; - } + [self layout]; } /*! @@ -2848,8 +2906,8 @@ setBoundsOrigin: return YES; } -/* - FIXME Not yet implemented +/*! + Scrolls the view’s CPClipView in the direction of a mouse event that occurs outside of it. */ - (BOOL)autoscroll:(CPEvent)anEvent { @@ -3456,16 +3514,16 @@ setBoundsOrigin: // Consistency check if (!trackingArea || [_trackingAreas containsObjectIdenticalTo:trackingArea]) return; - + if ([trackingArea view]) [CPException raise:CPInternalInconsistencyException reason:"Tracking area has already been added to another view."]; [_trackingAreas addObject:trackingArea]; [trackingArea setView:self]; - + if (_window) [_window _addTrackingArea:trackingArea]; - + [trackingArea _updateWindowRect]; } @@ -3474,25 +3532,25 @@ setBoundsOrigin: // Consistency check if (!trackingArea) return; - + if (![_trackingAreas containsObjectIdenticalTo:trackingArea]) [CPException raise:CPInternalInconsistencyException reason:"Trying to remove unreferenced trackingArea"]; [self _removeTrackingArea:trackingArea]; } -/*! +/*! Invoked automatically when the view’s geometry changes such that its tracking areas need to be recalculated. You should override this method to remove out of date tracking areas and add recomputed tracking areas; - + Cocoa calls this on every view, whereas they have tracking area(s) or not. Cappuccino behaves differently : - updateTrackingAreas is called when placing a view in the view hierarchy (that is in a window) - if you have only CPTrackingInVisibleRect tracking areas attached to a view, it will not be called again (until you move the view in the hierarchy) - if you have at least one non-CPTrackingInVisibleRect tracking area attached, it will be called every time the view geometry could be modified You don't have to touch to CPTrackingInVisibleRect tracking areas, they will be automatically updated - + Please note that it is the owner of a tracking area who is called for updateTrackingAreas. But, if a view without any tracking area is inserted in the view hierarchy (that is, in a window), the view is called for updateTrackingAreas. This enables you to use updateTrackingArea to initially attach your tracking areas to the view. @@ -3504,16 +3562,16 @@ setBoundsOrigin: /*! This utility method is intended for CPView subclasses overriding updateTrackingAreas - + Typical use would be : - + - (void)updateTrackingAreas { [self removeAllTrackingAreas]; - + ... add your specific updated tracking areas ... } - + */ - (void)removeAllTrackingAreas { @@ -3527,7 +3585,7 @@ setBoundsOrigin: { if (_window) [_window _removeTrackingArea:trackingArea]; - + [trackingArea setView:nil]; [_trackingAreas removeObjectIdenticalTo:trackingArea]; } @@ -3535,16 +3593,16 @@ setBoundsOrigin: - (void)_updateTrackingAreas { _inhibitUpdateTrackingAreas = YES; - + [self _recursivelyUpdateTrackingAreas]; - + _inhibitUpdateTrackingAreas = NO; } - (void)_recursivelyUpdateTrackingAreas { [self _updateTrackingAreasForOwners:[self _calcTrackingAreaOwners]]; - + for (var i = 0; i < _subviews.length; i++) [_subviews[i] _recursivelyUpdateTrackingAreas]; } @@ -3554,25 +3612,25 @@ setBoundsOrigin: // First search all owners that must be notified // Remark: 99.99% of time, the only owner will be the view itself // In the same time, update the rects of InVisibleRect tracking areas - + var owners = []; - + for (var i = 0; i < _trackingAreas.length; i++) { var trackingArea = _trackingAreas[i]; - + if ([trackingArea options] & CPTrackingInVisibleRect) [trackingArea _updateWindowRect]; - + else { var owner = [trackingArea owner]; - + if (![owners containsObjectIdenticalTo:owner]) [owners addObject:owner]; } } - + return owners; } @@ -3603,7 +3661,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", CPViewScaleKey = @"CPViewScaleKey", CPViewSizeScaleKey = @"CPViewSizeScaleKey", CPViewIsScaledKey = @"CPViewIsScaledKey", - CPViewAppearanceKey = @"CPViewAppearanceKey"; + CPViewAppearanceKey = @"CPViewAppearanceKey", CPViewTrackingAreasKey = @"CPViewTrackingAreasKey"; @implementation CPView (CPCoding) @@ -3633,17 +3691,16 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", if (self) { _trackingAreas = [aCoder decodeObjectForKey:CPViewTrackingAreasKey]; - + if (!_trackingAreas) _trackingAreas = []; - + // We have to manually check because it may be 0, so we can't use || _tag = [aCoder containsValueForKey:CPViewTagKey] ? [aCoder decodeIntForKey:CPViewTagKey] : -1; _identifier = [aCoder decodeObjectForKey:CPReuseIdentifierKey]; _window = [aCoder decodeObjectForKey:CPViewWindowKey]; _superview = [aCoder decodeObjectForKey:CPViewSuperviewKey]; - // We have to manually add the subviews so that they will receive // viewWillMoveToSuperview: and viewDidMoveToSuperview: _subviews = []; @@ -3698,6 +3755,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", #endif [self setHidden:[aCoder decodeBoolForKey:CPViewIsHiddenKey]]; + _isHiddenOrHasHiddenAncestor = NO; if ([aCoder containsValueForKey:CPViewOpacityKey]) [self setAlphaValue:[aCoder decodeIntForKey:CPViewOpacityKey]]; diff --git a/AppKit/CPViewController.j b/AppKit/CPViewController.j index 0c9bec99e..104313474 100644 --- a/AppKit/CPViewController.j +++ b/AppKit/CPViewController.j @@ -149,9 +149,10 @@ var CPViewControllerCachedCibs; If you use Interface Builder to create your views, and you initialize the controller using the initWithCibName:bundle: methods, then you MUST NOT override - this method. The consequences risk shattering the space-time continuum. + this method. - Note: The cib loading system is currently synchronous. + @note When using this method, the cib loading system is synchronous. + See the loadViewWithCompletionHandler: method for an asynchronous loading. */ - (void)loadView { @@ -176,6 +177,67 @@ var CPViewControllerCachedCibs; _view = [CPView new]; } +/*! + Asynchronously load the cib and create the view that the controller manages. + + @param aHandler a function which will be passed the loaded view as the first + argument and a network error or nil as the second argument: function(view, error). + + @note If the view has already been loaded, the completion handler is run immediatly + and the process is synchronous. +*/ +- (void)loadViewWithCompletionHandler:(Function/*(view, error)*/)aHandler +{ + if (_view) + return; + + if (_cibName) + { + // check if a cib is already cached for the current _cibName + var cib = [CPViewControllerCachedCibs objectForKey:_cibName]; + + if (!cib) + { + var cibName = _cibName; + + if (![cibName hasSuffix:@".cib"]) + cibName = [cibName stringByAppendingString:@".cib"]; + + // If aBundle is nil, use mainBundle, but ONLY for searching for the nib, not for resources later. + var bundle = _cibBundle || [CPBundle mainBundle], + url = [bundle _cibPathForResource:cibName]; + + // if the cib isn't cached yet : fetch it and cache it + [CPURLConnection sendAsynchronousRequest:[CPURLRequest requestWithURL:url] queue:[CPOperationQueue mainQueue] completionHandler:function(aResponse, aData, anError) + { + if (anError == nil) + { + var data = [CPData dataWithRawString:aData], + aCib = [[CPCib alloc] _initWithData:data bundle:_cibBundle cibName:_cibName]; + + [CPViewControllerCachedCibs setObject:aCib forKey:_cibName]; + [aCib instantiateCibWithExternalNameTable:_cibExternalNameTable]; + [self _viewDidLoadWithCompletionHandler:aHandler]; + } + else + { + aHandler(nil, anError); + } + }]; + } + else + { + [cib instantiateCibWithExternalNameTable:_cibExternalNameTable]; + [self _viewDidLoadWithCompletionHandler:aHandler]; + } + } + else + { + _view = [CPView new]; + [self _viewDidLoadWithCompletionHandler:aHandler]; + } +} + /*! Returns the view that the controller manages. @@ -230,6 +292,16 @@ var CPViewControllerCachedCibs; [self didChangeValueForKey:"isViewLoaded"]; } +- (void)_viewDidLoadWithCompletionHandler:(Function)aHandler +{ + [self _registerOrUnregister:YES notificationsForView:_view]; + + [self willChangeValueForKey:"isViewLoaded"]; + aHandler(_view, nil); + _isViewLoaded = YES; + [self didChangeValueForKey:"isViewLoaded"]; +} + /*! This method is called after the view controller has loaded its associated views into memory. @@ -243,6 +315,79 @@ var CPViewControllerCachedCibs; } +/*! + Called after the view controller’s view has been loaded into memory is about to be added to the + view hierarchy in the window. + + @discussion You can override this method to perform tasks prior to a view controller’s view + getting added to view hierarchy, such as setting the view’s highlight color. This method is called when: + + • The view is about to be added to the view hierarchy of the view controller + + If you override this method, call this method on super at some point in your implementation in case + a superclass also overrides this method. + + The default implementation of this method does nothing. +*/ +- (void)viewWillAppear +{ + +} + +/*! + Called when the view controller’s view is fully transitioned onto the screen. + + @discussion This method is called after the completion of any drawing and animations + involved in the initial appearance of the view. You can override this method to + perform tasks appropriate for that time, such as work that should not interfere + with the presentation animation, or starting an animation that you want to begin + after the view appears. + + If you override this method, call this method on super at some point in your + implementation in case a superclass also overrides this method. + + The default implementation of this method does nothing. +*/ +- (void)viewDidAppear +{ + +} + +/*! + Called when the view controller’s view is about to be removed from the view hierarchy in the window. + + @discussion You can override this method to perform tasks that are to precede the disappearance + of the view controller’s view, such as stopping a continuous animation that you + started in response to the viewDidAppear method call. This method is called when: + + • The view is about to be removed from the view hierarchy of the window + + If you override this method, call this method on super at some point in your + implementation in case a superclass also overrides this method. + + The default implementation of this method does nothing. +*/ +- (void)viewWillDisappear +{ + +} + +/*! + Called after the view controller’s view is removed from the view hierarchy in a window. + + @discussion You can override this method to perform tasks associated with removing the view + controller’s view from the window’s view hierarchy, such as releasing resources + not needed when the view is not visible or no longer part of the window. + + If you override this method, call this method on super at some point in your + implementation in case a superclass also overrides this method. + + The default implementation of this method does nothing. +*/ +- (void)viewDidDisappear +{ + +} /*! Manually sets the view that the controller manages. @@ -256,6 +401,9 @@ var CPViewControllerCachedCibs; { var willChangeIsViewLoaded = (_isViewLoaded == NO && aView != nil) || (_isViewLoaded == YES && aView == nil); + [self _registerOrUnregister:NO notificationsForView:_view]; + [self _registerOrUnregister:YES notificationsForView:aView]; + if (willChangeIsViewLoaded) [self willChangeValueForKey:"isViewLoaded"]; @@ -271,6 +419,30 @@ var CPViewControllerCachedCibs; return NO; } +- (void)_registerOrUnregister:(BOOL)shouldRegister notificationsForView:(CPView)aView +{ + if (aView === nil) + return; + + var center = [CPNotificationCenter defaultCenter], + notifs_to_sel = @{_CPViewWillAppearNotification : @"viewWillAppear", + _CPViewDidAppearNotification : @"viewDidAppear", + _CPViewWillDisappearNotification : @"viewWillDisappear", + _CPViewDidDisappearNotification : @"viewDidDisappear"}; + + [notifs_to_sel enumerateKeysAndObjectsUsingBlock:function(notif, selString, stop) + { + var selector = CPSelectorFromString(selString); + if ([self implementsSelector:selector]) + { + if (shouldRegister) + [center addObserver:self selector:selector name:notif object:aView]; + else + [center removeObserver:self name:notif object:aView]; + } + }]; +} + @end diff --git a/AppKit/CPWindow/CPWindow.j b/AppKit/CPWindow/CPWindow.j index 79b4b7324..380b2b149 100644 --- a/AppKit/CPWindow/CPWindow.j +++ b/AppKit/CPWindow/CPWindow.j @@ -198,8 +198,13 @@ var CPWindowActionMessageKeys = [ CPView _contentView; CPView _toolbarView; + BOOL _handlingTrackingAreaEvent; + BOOL _restartHandlingTrackingAreaEvent; + CPArray _previousMouseEnteredStack; + CPArray _previousCursorUpdateStack; CPArray _mouseEnteredStack; CPArray _cursorUpdateStack; + CPArray _queuedEvents; CPArray _trackingAreaViews; id _activeCursorTrackingArea; CPArray _queuedTrackingEvents; @@ -303,7 +308,7 @@ CPTexturedBackgroundWindowMask [self setPlatformWindow:[CPPlatformWindow primaryPlatformWindow]]; else { - // give zero sized borderless bridge windows a default size if we're not in the browser so they show up in NativeHost. + // give zero sized borderless bridge windows a default size. if ((aStyleMask & CPBorderlessBridgeWindowMask) && aContentRect.size.width === 0 && aContentRect.size.height === 0) { var visibleFrame = [[[CPScreen alloc] init] visibleFrame]; @@ -340,9 +345,14 @@ CPTexturedBackgroundWindowMask [self setLevel:CPNormalWindowLevel]; + _handlingTrackingAreaEvent = NO; + _restartHandlingTrackingAreaEvent = NO; _trackingAreaViews = []; + _previousMouseEnteredStack = []; + _previousCursorUpdateStack = []; _mouseEnteredStack = []; _cursorUpdateStack = []; + _queuedEvents = []; _queuedTrackingEvents = []; _activeCursorTrackingArea = nil; @@ -645,7 +655,6 @@ CPTexturedBackgroundWindowMask CPBorderlessWindowMask CPTitledWindowMask CPClosableWindowMask - CPMiniaturizableWindowMask (NOTE: only available in NativeHost) CPResizableWindowMask CPTexturedBackgroundWindowMask CPBorderlessBridgeWindowMask @@ -1980,7 +1989,7 @@ CPTexturedBackgroundWindowMask // First, we search for any tracking area requesting CPTrackingEnabledDuringMouseDrag. // At the same time, we update the entered stack. [self _handleTrackingAreaEvent:anEvent]; - + // Normal mouseDragged workflow if (!_leftMouseDownView) return [[_windowView hitTest:point] mouseDragged:anEvent]; @@ -2004,7 +2013,7 @@ CPTexturedBackgroundWindowMask // Ignore mouse moves for parents of sheets if (!_acceptsMouseMovedEvents || sheet) return; - + [self _handleTrackingAreaEvent:anEvent]; } } @@ -3824,7 +3833,7 @@ var interpolate = function(fromValue, toValue, progress) - (void)_addTrackingAreaView:(CPView)aView { var trackingAreas = [aView trackingAreas]; - + for (var i = 0; i < trackingAreas.length; i++) [self _addTrackingArea:trackingAreas[i]]; } @@ -3832,7 +3841,7 @@ var interpolate = function(fromValue, toValue, progress) - (void)_removeTrackingAreaView:(CPView)aView { var trackingAreas = [aView trackingAreas]; - + for (var i = 0; i < trackingAreas.length; i++) [self _removeTrackingArea:trackingAreas[i]]; } @@ -3840,63 +3849,143 @@ var interpolate = function(fromValue, toValue, progress) - (void)_addTrackingArea:(CPTrackingArea)trackingArea { var trackingAreaView = [trackingArea view]; - + if (![_trackingAreaViews containsObjectIdenticalTo:trackingAreaView]) [_trackingAreaViews addObject:trackingAreaView]; - - // If CPTrackingAssumeInside option is set, put the tracking area in the _mouseEnteredStack - - if ([trackingArea options] & CPTrackingAssumeInside) - [_mouseEnteredStack addObject:trackingArea]; + + // If CPTrackingAssumeInside option is set, insert the tracking area in the events management system + // in order to have the first event sent only when mouse leaves the tracking area + + [self _insertTrackingArea:trackingArea assumeInside:([trackingArea options] & CPTrackingAssumeInside)]; } - (void)_removeTrackingArea:(CPTrackingArea)trackingArea { // If mouse is in the tracking area, we remove it from the stack to avoid to fire a future mouseExited event - - [_mouseEnteredStack removeObjectIdenticalTo:trackingArea]; - + + [self _purgeTrackingArea:trackingArea]; + var trackingAreaView = [trackingArea view]; - + [_trackingAreaViews removeObjectIdenticalTo:trackingAreaView]; } +- (void)_insertTrackingArea:(CPTrackingArea)trackingArea assumeInside:(BOOL)assumeInside +{ + if (_handlingTrackingAreaEvent) + _restartHandlingTrackingAreaEvent = YES; + + if (assumeInside) + { + if (_handlingTrackingAreaEvent) + [_mouseEnteredStack addObject:trackingArea]; + else + [_previousMouseEnteredStack addObject:trackingArea]; + } +} + +- (void)_purgeTrackingArea:(CPTrackingArea)trackingArea +{ + if (_handlingTrackingAreaEvent) + { + [_mouseEnteredStack removeObjectIdenticalTo:trackingArea]; + + var i = _queuedEvents.length; + + while (i--) + if ([_queuedEvents[i] trackingArea] === trackingArea) + [_queuedEvents removeObjectAtIndex:i]; + + _cursorUpdateStack = []; + _activeCursorTrackingArea = nil; + } + else + { + [_previousMouseEnteredStack removeObjectIdenticalTo:trackingArea]; + [_previousCursorUpdateStack removeObjectIdenticalTo:trackingArea]; + } +} + - (void)_handleTrackingAreaEvent:(CPEvent)anEvent { - var mouseEnteredStack = [], - cursorUpdateStack = [], - point = [anEvent locationInWindow], - dragging = ([anEvent type] !== CPMouseMoved); + _handlingTrackingAreaEvent = YES; - // Handle mouse entering tracking areas (and calc mouseEnteredStack and cursorUpdateStack) - - [self _handleMouseMovedAndEnteredEventsForEvent:anEvent atPoint:point dragging:dragging mouseEnteredStack:mouseEnteredStack cursorUpdateStack:cursorUpdateStack]; + var point = [anEvent locationInWindow], + dragging = ([anEvent type] !== CPMouseMoved); - // Handle mouse exiting tracking areas - - [self _handleMouseExitedEventsForEvent:anEvent atPoint:point dragging:dragging mouseEnteredStack:mouseEnteredStack]; - - // Cursor update - - if (cursorUpdateStack.length > 0) + do { - [self _handleCursorUpdateEventsForEvent:anEvent atPoint:point dragging:dragging cursorUpdateStack:cursorUpdateStack]; - } - else if (!dragging) - { - // Here, we are outsite the window content view tracking area, so let _windowView set the cursor (resize cursor, ...) + // Initialize this run + _restartHandlingTrackingAreaEvent = NO; - [_windowView setCursorForLocation:point resizing:NO]; - _activeCursorTrackingArea = nil; - } + _mouseEnteredStack = []; + _cursorUpdateStack = []; - // Prepare for next call - - _mouseEnteredStack = mouseEnteredStack; - _cursorUpdateStack = cursorUpdateStack; + // Important remark: we must queue events to avoid running conditions when a view uses a mouse event to modify view hierarchy + + _queuedEvents = []; + + // Handle mouse entering tracking areas (and calc _mouseEnteredStack and _cursorUpdateStack) + + [self _handleMouseMovedAndEnteredEventsForEvent:anEvent atPoint:point dragging:dragging]; + + // Handle mouse exiting tracking areas + + [self _handleMouseExitedEventsForEvent:anEvent atPoint:point dragging:dragging]; + + // Cursor update + + if (_cursorUpdateStack.length > 0) + { + [self _handleCursorUpdateEventsForEvent:anEvent atPoint:point dragging:dragging]; + } + else if (!dragging) + { + // Here, we are outside the window content view tracking area, so let _windowView set the cursor (resize cursor, ...) + + [_windowView setCursorForLocation:point resizing:NO]; + _activeCursorTrackingArea = nil; + } + + // Send all queued events + // Important remark : as an event can modify the view hierarchy, the events queue could be modified while processing it + + while (_queuedEvents.length > 0) + { + var queuedEvent = _queuedEvents[0], + trackingArea = [queuedEvent trackingArea], + trackingOwner = [trackingArea owner]; + + switch ([queuedEvent type]) + { + case CPMouseEntered: + [trackingOwner mouseEntered:queuedEvent]; + break; + + case CPMouseExited: + [trackingOwner mouseExited:queuedEvent]; + break; + + case CPCursorUpdate: + [trackingOwner cursorUpdate:queuedEvent]; + break; + } + + if (queuedEvent === _queuedEvents[0]) + [_queuedEvents removeObjectAtIndex:0]; + } + + // Prepare for next call + + _previousMouseEnteredStack = _mouseEnteredStack; + _previousCursorUpdateStack = _cursorUpdateStack; + } + while (_restartHandlingTrackingAreaEvent) + + _handlingTrackingAreaEvent = NO; } -- (void)_handleMouseMovedAndEnteredEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging mouseEnteredStack:(CPArray)mouseEnteredStack cursorUpdateStack:(CPArray)cursorUpdateStack +- (void)_handleMouseMovedAndEnteredEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging { var isKeyWindow = [self isKeyWindow]; @@ -3923,9 +4012,9 @@ var interpolate = function(fromValue, toValue, progress) continue; } - [mouseEnteredStack addObject:aTrackingArea]; + [_mouseEnteredStack addObject:aTrackingArea]; - if ([_mouseEnteredStack containsObjectIdenticalTo:aTrackingArea]) + if ([_previousMouseEnteredStack containsObjectIdenticalTo:aTrackingArea]) { // Mouse was already in this rect so it's a mouseMoved @@ -3946,32 +4035,33 @@ var interpolate = function(fromValue, toValue, progress) if (dragging && !(trackingOptions & CPTrackingEnabledDuringMouseDrag)) [self _queueTrackingEvent:mouseEnteredEvent]; else - [[aTrackingArea owner] mouseEntered:mouseEnteredEvent]; + [_queuedEvents addObject:mouseEnteredEvent]; } if ((trackingOptions & CPTrackingCursorUpdate) && (trackingImplementedMethods & CPTrackingOwnerImplementsCursorUpdate)) - [cursorUpdateStack addObject:aTrackingArea]; + [_cursorUpdateStack addObject:aTrackingArea]; } } } -- (void)_handleMouseExitedEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging mouseEnteredStack:(CPArray)mouseEnteredStack +- (void)_handleMouseExitedEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging { - // Search for exited views (were in _mouseEnteredStack but no more in mouseEnteredStack) + // Search for exited views (were in _previousMouseEnteredStack but no more in _mouseEnteredStack) - for (var i = 0; i < _mouseEnteredStack.length; i++) + for (var i = 0; i < _previousMouseEnteredStack.length; i++) { - var aTrackingArea = _mouseEnteredStack[i], + var aTrackingArea = _previousMouseEnteredStack[i], trackingOptions = [aTrackingArea options]; - if ([mouseEnteredStack containsObjectIdenticalTo:aTrackingArea]) + if ([_mouseEnteredStack containsObjectIdenticalTo:aTrackingArea]) continue; // Mouse is no more in this area so it's a mouseExited if ((trackingOptions & CPTrackingMouseEnteredAndExited) && ([aTrackingArea implementedOwnerMethods] & CPTrackingOwnerImplementsMouseExited)) { - var mouseExitedEvent = [CPEvent enterExitEventWithType:CPMouseExited + var theView = [aTrackingArea owner], + mouseExitedEvent = [CPEvent enterExitEventWithType:CPMouseExited location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] @@ -3983,132 +4073,127 @@ var interpolate = function(fromValue, toValue, progress) if (dragging && !(trackingOptions & CPTrackingEnabledDuringMouseDrag)) [self _queueTrackingEvent:mouseExitedEvent]; else - [[aTrackingArea owner] mouseExited:mouseExitedEvent]; + [_queuedEvents addObject:mouseExitedEvent]; } - // If this is the active cursor area, we reset _cursorUpdateStack so a new active area will be computed + // If this is the active cursor area, we reset _previousCursorUpdateStack so a new active area will be computed if (aTrackingArea === _activeCursorTrackingArea) { - _cursorUpdateStack = []; + _previousCursorUpdateStack = []; _activeCursorTrackingArea = nil; } } } -- (void)_handleCursorUpdateEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging cursorUpdateStack:(CPArray)cursorUpdateStack +- (void)_handleCursorUpdateEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging { var overlappingTrackingAreas = []; - for (var i = 0; i < cursorUpdateStack.length; i++) + for (var i = 0; i < _cursorUpdateStack.length; i++) { - var aTrackingArea = cursorUpdateStack[i]; + var aTrackingArea = _cursorUpdateStack[i]; - if ((![_cursorUpdateStack containsObjectIdenticalTo:aTrackingArea]) || (aTrackingArea === _activeCursorTrackingArea)) + if ((![_previousCursorUpdateStack containsObjectIdenticalTo:aTrackingArea]) || (aTrackingArea === _activeCursorTrackingArea)) [overlappingTrackingAreas addObject:aTrackingArea]; } - var nbOverlappingTrackingAreas = overlappingTrackingAreas.length; + var frontmostTrackingArea = overlappingTrackingAreas[0], + frontmostView = [frontmostTrackingArea view]; - if (nbOverlappingTrackingAreas > 0) + for (var i = 1; i < overlappingTrackingAreas.length; i++) { - var frontmostTrackingArea = overlappingTrackingAreas[0], - frontmostView = [frontmostTrackingArea view]; + var aTrackingArea = overlappingTrackingAreas[i], + aView = [aTrackingArea view]; - for (var i = 1; i < nbOverlappingTrackingAreas; i++) + // First, if aView is _windowView, skip to next overlapping tracking area + // as _windowView can't be the frontmost view if there's multiple overlapping tracking areas. + + if (aView === _windowView) + continue; + + // Then, if frontmostView is _windowView, aView must become frontmostView + + if (frontmostView === _windowView) { - var aTrackingArea = overlappingTrackingAreas[i], - aView = [aTrackingArea view]; + frontmostTrackingArea = aTrackingArea; + frontmostView = aView; - // First, if aView is _windowView, skip to next overlapping tracking area - // as _windowView can't be the frontmost view if there's multiple overlapping tracking areas. - - if (aView === _windowView) - continue; - - // Then, if frontmostView is _windowView, aView must become frontmostView - - if (frontmostView === _windowView) - { - frontmostTrackingArea = aTrackingArea; - frontmostView = aView; - - continue; - } - - // Next verify if aView is a subview of frontmostView - // If so, it's our new frontmost view - - var searchingView = aView; - - while ((searchingView !== _contentView) && ([searchingView superview] !== frontmostView)) - searchingView = [searchingView superview]; - - if (searchingView !== _contentView) - { - frontmostTrackingArea = aTrackingArea; - frontmostView = aView; - - continue; - } - - // aView is not a subview of frontmostView - // Search in view hierarchy which one will be over the other - // (this is done by comparing their draw order) - - var firstView = frontmostView, - firstSuperview = [firstView superview]; - - while (firstView !== _contentView) - { - var secondView = aView, - secondSuperview = [secondView superview]; - - while ((secondSuperview !== _contentView) && (firstSuperview !== secondSuperview)) - { - secondView = secondSuperview; - secondSuperview = [secondView superview]; - } - - if (firstSuperview === secondSuperview) - break; - - firstView = firstSuperview; - firstSuperview = [firstView superview]; - } - - if (firstSuperview !== secondSuperview) - [CPException raise:CPInternalInconsistencyException reason:"Problem with view hierarchy"]; - - var firstSuperviewSubviews = [firstSuperview subviews], - firstViewIndex = [firstSuperviewSubviews indexOfObject:firstView], - secondViewIndex = [firstSuperviewSubviews indexOfObject:secondView]; - - if (secondViewIndex > firstViewIndex) - { - frontmostTrackingArea = aTrackingArea; - frontmostView = aView; - } + continue; } - if (frontmostTrackingArea !== _activeCursorTrackingArea) + // Next verify if aView is a subview of frontmostView + // If so, it's our new frontmost view + + var searchingView = aView; + + while ((searchingView !== _contentView) && ([searchingView superview] !== frontmostView)) + searchingView = [searchingView superview]; + + if (searchingView !== _contentView) { - var cursorUpdateEvent = [CPEvent enterExitEventWithType:CPCursorUpdate - location:point - modifierFlags:[anEvent modifierFlags] - timestamp:[anEvent timestamp] - windowNumber:_windowNumber - context:nil - eventNumber:-1 - trackingArea:frontmostTrackingArea]; + frontmostTrackingArea = aTrackingArea; + frontmostView = aView; - if (dragging) - [self _queueTrackingEvent:cursorUpdateEvent]; - else - [[frontmostTrackingArea owner] cursorUpdate:cursorUpdateEvent]; - - _activeCursorTrackingArea = frontmostTrackingArea; + continue; } + + // aView is not a subview of frontmostView + // Search in view hierarchy which one will be over the other + // (this is done by comparing their draw order) + + var firstView = frontmostView, + firstSuperview = [firstView superview]; + + while (firstView !== _contentView) + { + var secondView = aView, + secondSuperview = [secondView superview]; + + while ((secondSuperview !== _contentView) && (firstSuperview !== secondSuperview)) + { + secondView = secondSuperview; + secondSuperview = [secondView superview]; + } + + if (firstSuperview === secondSuperview) + break; + + firstView = firstSuperview; + firstSuperview = [firstView superview]; + } + + if (firstSuperview !== secondSuperview) + [CPException raise:CPInternalInconsistencyException reason:"Problem with view hierarchy"]; + + var firstSuperviewSubviews = [firstSuperview subviews], + firstViewIndex = [firstSuperviewSubviews indexOfObject:firstView], + secondViewIndex = [firstSuperviewSubviews indexOfObject:secondView]; + + if (secondViewIndex > firstViewIndex) + { + frontmostTrackingArea = aTrackingArea; + frontmostView = aView; + } + } + + if (frontmostTrackingArea !== _activeCursorTrackingArea) + { + var cursorUpdateEvent = [CPEvent enterExitEventWithType:CPCursorUpdate + location:point + modifierFlags:[anEvent modifierFlags] + timestamp:[anEvent timestamp] + windowNumber:_windowNumber + context:nil + eventNumber:-1 + trackingArea:frontmostTrackingArea]; + + if (dragging) + [self _queueTrackingEvent:cursorUpdateEvent]; + else + [_queuedEvents addObject:cursorUpdateEvent]; + + _activeCursorTrackingArea = frontmostTrackingArea; } } @@ -4116,12 +4201,11 @@ var interpolate = function(fromValue, toValue, progress) { // This will put a tracking event in the _queuedTrackingEvents queue. // - // We optimize this queue with this policy : + // We optimize this queue with this policy: // - if mouseEntered, search if queue contains a previous mouseExited for the same tracking area. If so, discard both. // - if mouseExited, search if queue contains a previous mouseEntered for the same tracking area. If so, discard both. // - // This is not Cocoa way of doing as it would send every event. - // But final result should be the same. + // This is not the Cocoa way of doing as it would send every event, but the final result should be the same. var eventType = [anEvent type], trackingArea = [anEvent trackingArea]; @@ -4181,7 +4265,7 @@ var interpolate = function(fromValue, toValue, progress) case CPMouseExited: [trackingOwner mouseExited:queuedEvent]; break; - + case CPCursorUpdate: [trackingOwner updateTrackingAreas]; diff --git a/AppKit/Cib/CPCib.j b/AppKit/Cib/CPCib.j index 64ec7d981..d7ac4b500 100644 --- a/AppKit/Cib/CPCib.j +++ b/AppKit/Cib/CPCib.j @@ -55,6 +55,18 @@ var CPCibObjectDataKey = @"CPCibObjectDataKey"; id _loadDelegate; } +- (id)_initWithData:(CPData)data bundle:(CPBundle)aBundle cibName:(CPString)aCibName +{ + self = [super init]; + + _data = data; + _cibName = aCibName; + _bundle = aBundle; + _awakenCustomResources = YES; + + return self; +} + - (id)initWithContentsOfURL:(CPURL)aURL { self = [super init]; diff --git a/AppKit/CoreAnimation/CAAnimation.j b/AppKit/CoreAnimation/CAAnimation.j index 2f13f5d8b..77c53745b 100644 --- a/AppKit/CoreAnimation/CAAnimation.j +++ b/AppKit/CoreAnimation/CAAnimation.j @@ -30,8 +30,10 @@ */ @implementation CAAnimation : CPObject { - BOOL _isRemovedOnCompletion; - id _delegate; + BOOL _isRemovedOnCompletion; + id _delegate; + CAMediaTimingFunction _timingFunction @accessors(property=timingFunction); + double _duration @accessors(property=duration); } /*! @@ -47,8 +49,10 @@ { self = [super init]; - if (self) - _isRemovedOnCompletion = YES; + _isRemovedOnCompletion = YES; + _timingFunction = nil; + _duration = 0.0; + _delegate = nil; return self; } @@ -102,7 +106,7 @@ - (CAMediaTimingFunction)timingFunction { // Linear Pacing - return nil; + return _timingFunction; } /*! @@ -127,131 +131,4 @@ [anObject addAnimation:self forKey:aKey]; } -@end - -/* - -*/ -@implementation CAPropertyAnimation : CAAnimation -{ - CPString _keyPath; - - BOOL _isCumulative; - BOOL _isAdditive; -} - -+ (id)animationWithKeyPath:(CPString)aKeyPath -{ - var animation = [self animation]; - - [animation setKeyPath:aKeyPath]; - - return animation; -} - -- (void)setKeyPath:(CPString)aKeyPath -{ - _keyPath = aKeyPath; -} - -- (CPString)keyPath -{ - return _keyPath; -} - -- (void)setCumulative:(BOOL)isCumulative -{ - _isCumulative = isCumulative; -} - -- (BOOL)cumulative -{ - return _isCumulative; -} - -- (BOOL)isCumulative -{ - return _isCumulative; -} - -- (void)setAdditive:(BOOL)isAdditive -{ - _isAdditive = isAdditive; -} - -- (BOOL)additive -{ - return _isAdditive; -} - -- (BOOL)isAdditive -{ - return _isAdditive; -} - -@end - -/*! - A CABasicAnimation is a simple animation that moves a - CALayer from one point to another over a specified - period of time. -*/ -@implementation CABasicAnimation : CAPropertyAnimation -{ - id _fromValue; - id _toValue; - id _byValue; -} - -/*! - Sets the starting position for the animation. - @param aValue the animation starting position -*/ -- (void)setFromValue:(id)aValue -{ - _fromValue = aValue; -} - -/*! - Returns the animation's starting position. -*/ -- (id)fromValue -{ - return _fromValue; -} - -/*! - Sets the ending position for the animation. - @param aValue the animation ending position -*/ -- (void)setToValue:(id)aValue -{ - _toValue = aValue; -} - -/*! - Returns the animation's ending position. -*/ -- (id)toValue -{ - return _toValue; -} - -/*! - Sets the optional byValue for animation interpolation. - @param aValue the byValue -*/ -- (void)setByValue:(id)aValue -{ - _byValue = aValue; -} - -/*! - Returns the animation's byValue. -*/ -- (id)byValue -{ - return _byValue; -} - -@end +@end \ No newline at end of file diff --git a/AppKit/CoreAnimation/CABasicAnimation.j b/AppKit/CoreAnimation/CABasicAnimation.j new file mode 100644 index 000000000..e6b8ca49a --- /dev/null +++ b/AppKit/CoreAnimation/CABasicAnimation.j @@ -0,0 +1,84 @@ +@import + +@import "CAPropertyAnimation.j" + +/*! + A CABasicAnimation is a simple animation that moves a + CALayer from one point to another over a specified + period of time. +*/ +/*! + A CABasicAnimation is a simple animation that moves a + CALayer from one point to another over a specified + period of time. +*/ +@implementation CABasicAnimation : CAPropertyAnimation +{ + id _fromValue; + id _toValue; + id _byValue; +} + +- (id)init +{ + self = [super init]; + + _fromValue = nil; + _toValue = nil; + _byValue = nil; + + return self; +} + +/*! + Sets the starting position for the animation. + @param aValue the animation starting position +*/ +- (void)setFromValue:(id)aValue +{ + _fromValue = aValue; +} + +/*! + Returns the animation's starting position. +*/ +- (id)fromValue +{ + return _fromValue; +} + +/*! + Sets the ending position for the animation. + @param aValue the animation ending position +*/ +- (void)setToValue:(id)aValue +{ + _toValue = aValue; +} + +/*! + Returns the animation's ending position. +*/ +- (id)toValue +{ + return _toValue; +} + +/*! + Sets the optional byValue for animation interpolation. + @param aValue the byValue +*/ +- (void)setByValue:(id)aValue +{ + _byValue = aValue; +} + +/*! + Returns the animation's byValue. +*/ +- (id)byValue +{ + return _byValue; +} + +@end \ No newline at end of file diff --git a/AppKit/CoreAnimation/CAKeyframeAnimation.j b/AppKit/CoreAnimation/CAKeyframeAnimation.j new file mode 100644 index 000000000..d0c135e1d --- /dev/null +++ b/AppKit/CoreAnimation/CAKeyframeAnimation.j @@ -0,0 +1,23 @@ +@import + +@import "CAPropertyAnimation.j" + +@implementation CAKeyframeAnimation : CAPropertyAnimation +{ + CPArray _values @accessors(property=values); + CPArray _keyTimes @accessors(property=keyTimes); + CPArray _timingFunctions @accessors(property=timingFunctions); +} + +- (id)init +{ + self = [super init]; + + _values = [CPArray array]; + _keyTimes = [CPArray array]; + _timingFunctions = [CPArray array]; + + return self; +} + +@end \ No newline at end of file diff --git a/AppKit/CoreAnimation/CAPropertyAnimation.j b/AppKit/CoreAnimation/CAPropertyAnimation.j new file mode 100644 index 000000000..c2eded4f1 --- /dev/null +++ b/AppKit/CoreAnimation/CAPropertyAnimation.j @@ -0,0 +1,74 @@ + +@import + +@import "CAAnimation.j" + +@implementation CAPropertyAnimation : CAAnimation +{ + CPString _keyPath; + + BOOL _isCumulative; + BOOL _isAdditive; +} + +- (id)init +{ + self = [super init]; + + _keyPath = nil; + _isCumulative = NO; + _isAdditive = NO; + + return self; +} + ++ (id)animationWithKeyPath:(CPString)aKeyPath +{ + var animation = [self animation]; + + [animation setKeyPath:aKeyPath]; + + return animation; +} + +- (void)setKeyPath:(CPString)aKeyPath +{ + _keyPath = aKeyPath; +} + +- (CPString)keyPath +{ + return _keyPath; +} + +- (void)setCumulative:(BOOL)isCumulative +{ + _isCumulative = isCumulative; +} + +- (BOOL)cumulative +{ + return _isCumulative; +} + +- (BOOL)isCumulative +{ + return _isCumulative; +} + +- (void)setAdditive:(BOOL)isAdditive +{ + _isAdditive = isAdditive; +} + +- (BOOL)additive +{ + return _isAdditive; +} + +- (BOOL)isAdditive +{ + return _isAdditive; +} + +@end diff --git a/AppKit/CoreAnimation/CPAnimationContext.j b/AppKit/CoreAnimation/CPAnimationContext.j new file mode 100644 index 000000000..df87259e6 --- /dev/null +++ b/AppKit/CoreAnimation/CPAnimationContext.j @@ -0,0 +1,710 @@ +@import "CABasicAnimation.j" +@import "CAKeyframeAnimation.j" +@import "CPView.j" + +@import + +@import "jshashtable.j" +@import "CSSAnimation.j" + +@typedef HashTable; + +var _CPAnimationContextStack = nil, + _animationFlushingObserver = nil, + _animationFrameUpdaters = {}; + +@implementation CPAnimationContext : CPObject +{ + double _duration @accessors(property=duration); + CAMediaTimingFunction _timingFunction @accessors(property=timingFunction); + Function _completionHandlerAgent; + HashTable _animationsByObject; +} + ++ (id)currentContext +{ + var contextStack = [self contextStack], + context = [contextStack lastObject]; + + if (!context) + { + context = [[CPAnimationContext alloc] init]; + + [contextStack addObject:context]; + [self _scheduleAnimationContextStackFlush]; + } + + return context; +} + ++ (CPArray)contextStack +{ + if (!_CPAnimationContextStack) + _CPAnimationContextStack = [CPArray array]; + + return _CPAnimationContextStack; +} + ++ (void)runAnimationGroup:(Function/*(CPAnimationContext context)*/)animationsBlock completionHandler:(Function)aCompletionHandler +{ + [CPAnimationContext beginGrouping]; + + var context = [CPAnimationContext currentContext]; + [context setCompletionHandler:aCompletionHandler]; + + animationsBlock(context); + + [CPAnimationContext endGrouping]; +} + +- (id)init +{ + self = [super init]; + + _duration = 0.0; + _timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + _completionHandlerAgent = nil; + _animationsByObject = new Hashtable(); + + return self; +} + +- (id)copy +{ + var context = [[CPAnimationContext alloc] init]; + [context setDuration:[self duration]]; + [context setTimingFunction:[self timingFunction]]; + [context setCompletionHandler:[self completionHandler]]; + + return context; +} + ++ (void)_scheduleAnimationContextStackFlush +{ + if (!_animationFlushingObserver) + { + CPLog.debug("create new observer"); + _animationFlushingObserver = CFRunLoopObserverCreate(2, true, 0, _animationFlushingObserverCallback,0); + CFRunLoopAddObserver([CPRunLoop mainRunLoop], _animationFlushingObserver); + } +} + ++ (void)beginGrouping +{ + var newContext; + + if ([_CPAnimationContextStack count]) + { + var currentContext = [_CPAnimationContextStack lastObject]; + newContext = [currentContext copy]; + } + else + { + newContext = [[CPAnimationContext alloc] init]; + } + + [_CPAnimationContextStack addObject:newContext]; +} + ++ (BOOL)endGrouping +{ + if (![_CPAnimationContextStack count]) + return NO; + + var context = [_CPAnimationContextStack lastObject]; + [context _flushAnimations]; + [_CPAnimationContextStack removeLastObject]; + +CPLog.debug(_cmd + "context stack =" + _CPAnimationContextStack); + return YES; +} + +- (void)_enqueueActionForObject:(id)anObject keyPath:(id)aKeyPath targetValue:(id)aTargetValue animationCompletion:(id)animationCompletion +{ + var resolvedAction = [self _actionForObject:anObject keyPath:aKeyPath targetValue:aTargetValue animationCompletion:animationCompletion]; + + if (!resolvedAction) + return; + + var animByKeyPath = _animationsByObject.get(anObject); + + if (!animByKeyPath) + { + var newAnimByKeyPath = @{aKeyPath:resolvedAction}; + _animationsByObject.put(anObject, newAnimByKeyPath); + } + else + [animByKeyPath setObject:resolvedAction forKey:aKeyPath]; +} + +- (Object)_actionForObject:(id)anObject keyPath:(CPString)aKeyPath targetValue:(id)aTargetValue animationCompletion:(Function)animationCompletion +{ + var animation, + duration, + animatedKeyPath, + values, + keyTimes, + timingFunctions, + objectId; + + if (!aKeyPath || !anObject || !(animation = [anObject animationForKey:aKeyPath]) || ![animation isKindOfClass:[CAAnimation class]]) + return nil; + + duration = [animation duration] || [self duration]; + + var needsFrameTimer = (aKeyPath == @"frame" || aKeyPath == @"frameSize") && + ([anObject hasCustomLayoutSubviews] || [anObject hasCustomDrawRect]) && + (objectId = [anObject UID]); + + if (_completionHandlerAgent) + _completionHandlerAgent.increment(); + + var completionFunction = function() + { + if (needsFrameTimer) + [self stopFrameUpdaterWithIdentifier:objectId]; + else if (animationCompletion) + animationCompletion(); + + if (_completionHandlerAgent) + _completionHandlerAgent.decrement(); + }; + + if (![animation isKindOfClass:[CAPropertyAnimation class]] || !(animatedKeyPath = [animation keyPath])) + animatedKeyPath = aKeyPath; + + if ([animation isKindOfClass:[CAKeyframeAnimation class]]) + { + values = [animation values]; + keyTimes = [animation keyTimes]; + timingFunctions = [animation timingFunctionsControlPoints]; + } + else + { + var isBasicAnimation = [animation isKindOfClass:[CABasicAnimation class]], + fromValue, + toValue; + + if (!isBasicAnimation || (fromValue = [animation fromValue]) == nil) + fromValue = [anObject valueForKey:animatedKeyPath]; + + if (!isBasicAnimation || (toValue = [animation toValue]) == nil) + toValue = aTargetValue; + + values = [fromValue, toValue]; + keyTimes = [0, 1]; + timingFunctions = isBasicAnimation ? [animation timingFunctionControlPoints] : [_timingFunction controlPoints]; + } + + return { + object:anObject, + keypath:animatedKeyPath, + values:values, + keytimes:keyTimes, + duration:duration, + timingfunctions:timingFunctions, + completion:completionFunction + }; +} + +- (void)_flushAnimations +{ + if (![_CPAnimationContextStack count]) + return; + + if (_animationsByObject.size() == 0) + { + if (_completionHandlerAgent) + _completionHandlerAgent.fire(); + } + else + [self _startAnimations]; +} + +- (void)_startAnimations +{ + var targetViews = _animationsByObject.keys(), + cssAnimations = [], + timers = []; + + [targetViews enumerateObjectsUsingBlock:function(targetView, idx, stop) + { + var animByKeyPath = _animationsByObject.get(targetView); + + [animByKeyPath enumerateKeysAndObjectsUsingBlock:function(aKey, anAction, stop) + { + [self getAnimations:cssAnimations getTimers:timers forView:targetView usingAction:anAction rootView:targetView cssAnimate:YES]; + }]; + + _animationsByObject.remove(targetView); + }]; + +// start timers + var k = timers.length; + while(k--) + { + CPLog.debug("START TIMER " + timers[k].identifier()); + timers[k].start(); + } + +// start css animations + var n = cssAnimations.length; + while(n--) + { + CPLog.debug("START ANIMATION " + cssAnimations[n].animationsnames); + cssAnimations[n].start(); + } +} + +- (void)getAnimations:(CPArray)cssAnimations getTimers:(CPArray)timers forView:(CPView)aTargetView usingAction:(Object)anAction rootView:(CPView)rootView cssAnimate:(BOOL)needsCSSAnimation +{ + var keyPath = anAction.keypath, + isFrameKeyPath = (keyPath == @"frame" || keyPath == @"frameSize"), + customLayout = [aTargetView hasCustomLayoutSubviews], + customDrawing = [aTargetView hasCustomDrawRect], + needsFrameTimer = isFrameKeyPath && (customLayout || customDrawing); + + if (needsCSSAnimation) + { + var identifier = [aTargetView UID], + duration = anAction.duration, + timingFunctions = anAction.timingfunctions, + properties = [], + valueFunctions = [], + cssAnimation = nil; + + [cssAnimations enumerateObjectsUsingBlock:function(anim, idx, stop) + { + if (anim.identifier == identifier) + { + cssAnimation = anim; + stop(YES); + } + }]; + + if (cssAnimation == nil) + { + var domElement = [aTargetView DOMElementForKeyPath:keyPath]; + cssAnimation = new CSSAnimation(domElement, identifier); + cssAnimations.push(cssAnimation); + } + + var css_mapping = [[aTargetView class] cssPropertiesForKeyPath:keyPath]; + + [css_mapping enumerateObjectsUsingBlock:function(aDict, anIndex, stop) + { + var completionFunction = (anIndex == 0) ? anAction.completion : null; + var property = [aDict objectForKey:@"property"], + getter = [aDict objectForKey:@"value"]; + + cssAnimation.addPropertyAnimation(property, getter, duration, anAction.keytimes, anAction.values, timingFunctions, completionFunction); + }]; + + if (needsFrameTimer) + cssAnimation.setRemoveAnimationPropertyOnCompletion(false); + } + + if (needsFrameTimer) + { + var timer = [self addFrameUpdaterWithIdentifier:[rootView UID] forView:aTargetView keyPath:keyPath duration:anAction.duration]; + + if (timer) + timers.push(timer); + } + + var subviews = [aTargetView subviews], + count = [subviews count]; + + if (count && isFrameKeyPath) + { + var frameTimerId = [rootView UID], + lastIndex = count - 1; + + [subviews enumerateObjectsUsingBlock:function(aSubview, idx, stop) + { + var action = [self actionFromAction:anAction forAnimatedSubview:aSubview], + targetFrame = [action.values lastObject]; + + if (CGRectEqualToRect([aSubview frame], targetFrame)) + return; + + if ([aSubview hasCustomDrawRect]) + { + action.completion = function() + { + [aSubview setFrame:targetFrame]; + CPLog.debug(aSubview + " setFrame: "); + + if (idx == lastIndex) + [self stopFrameUpdaterWithIdentifier:frameTimerId]; + }; + } + + [self getAnimations:cssAnimations getTimers:timers forView:aSubview usingAction:action rootView:rootView cssAnimate:!customLayout]; + }]; + } +} + +- (Object)actionFromAction:(Object)anAction forAnimatedSubview:(CPView)aView +{ + var targetValue = [anAction.values lastObject], + endFrame, + values; + + if (anAction.keypath == @"frame") + targetValue = targetValue.size; + + endFrame = [aView frameWithNewSuperviewSize:targetValue]; + values = [[aView frame], endFrame]; + + return { + object:aView, + keypath:"frame", + values:values, + keytimes:[0, 1], + duration:anAction.duration, + timingfunctions:anAction.timingFunctions + }; +} + +- (Function)addFrameUpdaterWithIdentifier:(CPString)anIdentifier forView:(CPView)aView keyPath:(CPString)aKeyPath duration:(float)aDuration +{ + var frameUpdater = _animationFrameUpdaters[anIdentifier], + result = nil; + + if (frameUpdater == null) + { + frameUpdater = new FrameUpdater(anIdentifier); + _animationFrameUpdaters[anIdentifier] = frameUpdater; + result = frameUpdater; + } + + frameUpdater.addTarget(aView, aKeyPath, aDuration); + + return result; +} + +- (void)stopFrameUpdaterWithIdentifier:(CPString)anIdentifier +{ + var frameUpdater = _animationFrameUpdaters[anIdentifier]; + + if (frameUpdater) + { + frameUpdater.stop(); + delete _animationFrameUpdaters[anIdentifier]; + } + else + CPLog.warn("Could not find FrameUpdater with identifier " + anIdentifier); +} + +- (void)setCompletionHandler:(Function)aCompletionHandler +{ + if (_completionHandlerAgent) + _completionHandlerAgent.invalidate(); + + _completionHandlerAgent = aCompletionHandler ? (new CompletionHandlerAgent(aCompletionHandler)) : nil; +} + +- (void)completionHandler +{ + if (!_completionHandlerAgent) + return nil; + + return _completionHandlerAgent.completionHandler(); +} + +@end + +@implementation CPView (CPAnimationContext) + +- (CGRect)frameWithNewSuperviewSize:(CGSize)newSize +{ + var mask = [self autoresizingMask]; + + if (mask == CPViewNotSizable) + return _frame; + + var oldSize = _superview._frame.size, + newFrame = CGRectMakeCopy(_frame), + dX = newSize.width - oldSize.width, + dY = newSize.height - oldSize.height, + evenFractionX = 1.0 / ((mask & CPViewMinXMargin ? 1 : 0) + (mask & CPViewWidthSizable ? 1 : 0) + (mask & CPViewMaxXMargin ? 1 : 0)), + evenFractionY = 1.0 / ((mask & CPViewMinYMargin ? 1 : 0) + (mask & CPViewHeightSizable ? 1 : 0) + (mask & CPViewMaxYMargin ? 1 : 0)), + baseX = (mask & CPViewMinXMargin ? _frame.origin.x : 0) + + (mask & CPViewWidthSizable ? _frame.size.width : 0) + + (mask & CPViewMaxXMargin ? oldSize.width - _frame.size.width - _frame.origin.x : 0), + baseY = (mask & CPViewMinYMargin ? _frame.origin.y : 0) + + (mask & CPViewHeightSizable ? _frame.size.height : 0) + + (mask & CPViewMaxYMargin ? oldSize.height - _frame.size.height - _frame.origin.y : 0); + + + if (mask & CPViewMinXMargin) + newFrame.origin.x += dX * (baseX > 0 ? _frame.origin.x / baseX : evenFractionX); + if (mask & CPViewWidthSizable) + newFrame.size.width += dX * (baseX > 0 ? _frame.size.width / baseX : evenFractionX); + + if (mask & CPViewMinYMargin) + newFrame.origin.y += dY * (baseY > 0 ? _frame.origin.y / baseY : evenFractionY); + if (mask & CPViewHeightSizable) + newFrame.size.height += dY * (baseY > 0 ? _frame.size.height / baseY : evenFractionY); + + return newFrame; +} + +- (BOOL)hasCustomDrawRect +{ + return self._viewClassFlags & 1; +} + +- (BOOL)hasCustomLayoutSubviews +{ + return self._viewClassFlags & 2; +} + +@end + +@implementation CAMediaTimingFunction (Additions) + +- (CPArray)controlPoints +{ + return [_c1x, _c1y, _c2x, _c2y]; +} + +@end + +@implementation CAAnimation (Additions) + +- (CPArray)timingFunctionControlPoints +{ + if (_timingFunction) + return [_timingFunction controlPoints]; + + return [0, 0, 1, 1]; +} + +@end + +@implementation CAKeyframeAnimation (Additions) + +- (CPArray)timingFunctionsControlPoints +{ + var result = [CPArray array]; + + [_timingFunctions enumerateObjectsUsingBlock:function(timingFunction, idx) + { + [result addObject:[timingFunction controlPoints]]; + }]; + + return result; +} + +@end + +var CompletionHandlerAgent = function(aCompletionHandler) +{ + this._completionHandler = aCompletionHandler; + this.total = 0; + this.valid = true; +}; + +CompletionHandlerAgent.prototype.completionHandler = function() +{ + return this._completionHandler; +}; + +CompletionHandlerAgent.prototype.fire = function() +{ + this._completionHandler(); +}; + +CompletionHandlerAgent.prototype.increment = function() +{ + this.total++; +}; + +CompletionHandlerAgent.prototype.decrement = function() +{ + if (this.total <= 0) + return; + + this.total--; + + if (this.valid && this.total == 0) + { + this.fire(); + } +}; + +CompletionHandlerAgent.prototype.invalidate = function() +{ + this.valid = false; +}; + +var _animationFlushingObserverCallback = function() +{ +CPLog.debug("_animationFlushingObserverCallback"); + if ([_CPAnimationContextStack count] == 1) + { + var context = [_CPAnimationContextStack lastObject]; + [context _flushAnimations]; + [_CPAnimationContextStack removeLastObject]; + } + +CPLog.debug("_animationFlushingObserver "+_animationFlushingObserver+" stack:" + [_CPAnimationContextStack count]); + + if (_animationFlushingObserver && ![_CPAnimationContextStack count]) + { + CPLog.debug("removeObserver"); + CFRunLoopObserverInvalidate([CPRunLoop mainRunLoop], _animationFlushingObserver); + _animationFlushingObserver = nil; + } +}; + +CFRunLoopObserver = function(activities, repeats, order, callout, context) +{ + this.activities = activities; + this.repeats = repeats; + this.order = order; + this.callout = callout; + this.context = context; + + this.isvalid = true; +}; + +CFRunLoopObserverCreate = function(activities, repeats, order, callout, context) +{ + return new CFRunLoopObserver(activities, repeats, order, callout, context); +}; + +CFRunLoopAddObserver = function(runloop, observer, mode) +{ + var observers = runloop._observers; + + if (!observers) + observers = (runloop._observers = []); + + if (observers.indexOf(observer) == -1) + observers.push(observer); +}; + +CFRunLoopObserverInvalidate = function(runloop, observer, mode) +{ + CFRunLoopRemoveObserver(runloop, observer, mode); +}; + +CFRunLoopRemoveObserver = function(runloop, observer, mode) +{ + var observers = runloop._observers; + if (observers) + { + var idx = observers.indexOf(observer); + if (idx !== -1) + { + observers.splice(idx, 1); + + if (observers.length == 0) + runloop._observers = nil; + } + } +}; + +var FrameUpdater = function(anIdentifier) +{ + this._identifier = anIdentifier; + this._duration = 0; + this._stop = false; + this._targets = []; + this._callbacks = []; + var frameUpdater = this; + + this._updateFunction = function(timestamp) + { + if (frameUpdater._stop) + return; + + if (this._startDate == null) + this._startDate = timestamp; + + for (var i = 0; i < frameUpdater._callbacks.length; i++) + frameUpdater._callbacks[i](); + + if (timestamp - this._startDate < frameUpdater._duration * 1000) + window.requestAnimationFrame(frameUpdater._updateFunction); + }; +}; + +FrameUpdater.prototype.start = function() +{ + window.requestAnimationFrame(this._updateFunction); +}; + +FrameUpdater.prototype.stop = function() +{ +CPLog.debug("stop FrameUpdater with id " + this.identifier()); + + this._stop = true; + + var targets = this._targets; + + for (var i = 0; i < targets.length; i++) + { + CPLog.debug(targets[i] + " Remove animation-name property"); + targets[i]._DOMElement.style.removeProperty(CPBrowserCSSProperty("animation-name")); + } +}; + +FrameUpdater.prototype.updateFunction = function() +{ + return this._updateFunction; +}; + +FrameUpdater.prototype.identifier = function() +{ + return this._identifier; +}; + +FrameUpdater.prototype.addTarget = function(target, keyPath, duration) +{ + var callback = createUpdateFrame(target, keyPath); + + if (callback) + { + this._duration = MAX(this._duration, duration); + this._targets.push(target); + this._callbacks.push(callback); + } +}; + +var createUpdateFrame = function(aView, aKeyPath) +{ + if (aKeyPath !== "frame" && aKeyPath !== "frameSize") + return nil; + + var style = getComputedStyle(aView._DOMElement), + getCSSPropertyValue = function(prop) { + return ROUND(parseFloat(style.getPropertyValue(prop))); + }; + + var updateFrame = function(timestamp) + { + var width = getCSSPropertyValue("width"), + height = getCSSPropertyValue("height"); + + if (aKeyPath == "frame") + { + var left = getCSSPropertyValue("left"), + top = getCSSPropertyValue("top"), + frame = CGRectMake(left, top, width, height); + + [aView setFrame:frame]; + } + else if (aKeyPath == "frameSize") + { + [aView setFrameSize:CGSizeMake(width, height)]; + } + + [[CPRunLoop currentRunLoop] performSelectors]; + }; + + return updateFrame; +}; diff --git a/AppKit/CoreAnimation/CPViewAnimator.j b/AppKit/CoreAnimation/CPViewAnimator.j new file mode 100644 index 000000000..b2e811e3b --- /dev/null +++ b/AppKit/CoreAnimation/CPViewAnimator.j @@ -0,0 +1,222 @@ + +@import "_CPObjectAnimator.j" +@import "CPView.j" + +@implementation CPViewAnimator : _CPObjectAnimator +{ +} + +- (void)viewWillMoveToSuperview:(CPView)aSuperview +{ + var orderInAnim = [self animationForKey:@"CPAnimationTriggerOrderIn"]; + + if (orderInAnim && [orderInAnim isKindOfClass:[CAPropertyAnimation class]]) + { + [_target setValue:[orderInAnim fromValue] forKeyPath:[orderInAnim keyPath]]; + } + + [_target viewWillMoveToSuperview:aSuperview]; +} + +- (void)viewDidMoveToSuperview +{ + var orderInAnim = [self animationForKey:@"CPAnimationTriggerOrderIn"]; + + if (orderInAnim && [orderInAnim isKindOfClass:[CAPropertyAnimation class]]) + { + [self _setTargetValue:YES withKeyPath:@"CPAnimationTriggerOrderIn" fallback:nil completion:function() + { + [_target setValue:[orderInAnim toValue] forKeyPath:[orderInAnim keyPath]]; + }]; + } + else + { + [_target viewDidMoveToSuperview]; + } +} + +- (void)removeFromSuperview +{ + [self _setTargetValue:nil withKeyPath:@"CPAnimationTriggerOrderOut" setter:_cmd]; +} + +- (void)setHidden:(BOOL)shouldHide +{ + if ([_target isHidden] == shouldHide) + return; + + if (shouldHide == NO) + return [_target setHidden:NO]; + + [self _setTargetValue:YES withKeyPath:@"CPAnimationTriggerOrderOut" setter:_cmd]; +} + +- (void)setAlphaValue:(CGPoint)alphaValue +{ + [self _setTargetValue:alphaValue withKeyPath:@"alphaValue" setter:_cmd]; +} + +- (void)setBackgroundColor:(CPColor)aColor +{ + [self _setTargetValue:aColor withKeyPath:@"backgroundColor" setter:_cmd]; +} + +- (void)setFrameOrigin:(CGPoint)aFrameOrigin +{ + [self _setTargetValue:aFrameOrigin withKeyPath:@"frameOrigin" setter:_cmd]; +} + +- (void)setFrame:(CGRect)aFrame +{ + [self _setTargetValue:aFrame withKeyPath:@"frame" setter:_cmd]; +} + +- (void)setFrameSize:(CGSize)aFrameSize +{ + [self _setTargetValue:aFrameSize withKeyPath:@"frameSize" setter:_cmd]; +} + +// Convenience method for the common case where the setter has zero or one argument +- (void)_setTargetValue:(id)aTargetValue withKeyPath:(CPString)aKeyPath setter:(SEL)aSelector +{ + var handler = function() + { + [_target performSelector:aSelector withObject:aTargetValue]; + }; + + [self _setTargetValue:aTargetValue withKeyPath:aKeyPath fallback:handler completion:handler]; +} + +- (void)_setTargetValue:(id)aTargetValue withKeyPath:(CPString)aKeyPath fallback:(Function)fallback completion:(Function)completion +{ + var animation = [_target animationForKey:aKeyPath], + context = [CPAnimationContext currentContext]; + + if (!animation || ![animation isKindOfClass:[CAAnimation class]] || (![context duration] && ![animation duration]) || ![_CPObjectAnimator supportsCSSAnimations]) + { + if (fallback) + fallback(); + } + else + { + [context _enqueueActionForObject:_target keyPath:aKeyPath targetValue:aTargetValue animationCompletion:completion]; + } +} + +@end + +var transformOrigin = function(start, current) +{ + return "translate(" + (current.x - start.x) + "px," + (current.y - start.y) + "px)"; +}; + +var transformFrameToTranslate = function(start, current) +{ + return transformOrigin(start.origin, current.origin); +}; + +var transformFrameToWidth = function(start, current) +{ + return current.size.width + "px"; +}; + +var transformFrameToHeight = function(start, current) +{ + return current.size.height + "px"; +}; + +var transformSizeToWidth = function(start, current) +{ + return current.width + "px"; +}; + +var transformSizeToHeight = function(start, current) +{ + return current.height + "px"; +}; + +var DEFAULT_CSS_PROPERTIES = nil; + +@implementation CPView (CPAnimatablePropertyContainer) + ++ (CPDictionary)defaultCSSProperties +{ + if (DEFAULT_CSS_PROPERTIES == nil) + { + var transformProperty = CPBrowserCSSProperty("transform"); + + DEFAULT_CSS_PROPERTIES = @{ + "backgroundColor" : [@{"property":"background", "value":function(sv, val){return [val cssString];}}], + "alphaValue" : [@{"property":"opacity"}], + "frame" : [@{"property":transformProperty, "value":transformFrameToTranslate}, + @{"property":"width", "value":transformFrameToWidth}, + @{"property":"height", "value":transformFrameToHeight}], + "frameOrigin" : [@{"property":transformProperty, "value":transformOrigin}], + "frameSize" : [@{"property":"width", "value":transformSizeToWidth}, + @{"property":"height", "value":transformSizeToHeight}] + }; + } + + return DEFAULT_CSS_PROPERTIES; +} + ++ (CPArray)cssPropertiesForKeyPath:(CPString)aKeyPath +{ + return [[self defaultCSSProperties] objectForKey:aKeyPath]; +} + ++ (Class)animatorClass +{ + var anim_class = CPClassFromString(CPStringFromClass(self) + "Animator"); + + if (anim_class) + return anim_class; + + return [[self superclass] animatorClass]; +} + +- (id)animator +{ + if (!_animator) + _animator = [[[[self class] animatorClass] alloc] initWithTarget:self]; + + return _animator; +} + +- (id)DOMElementForKeyPath:(CPString)aKeyPath +{ + return _DOMElement; +} + ++ (CAAnimation)defaultAnimationForKey:(CPString)aKey +{ + if ([self cssPropertiesForKeyPath:aKey] !== nil) + return [CAAnimation animation]; + + return nil; +} + +- (CAAnimation)animationForKey:(CPString)aKey +{ + var animations = [self animations], + animation = nil; + + if (!animations || !(animation = [animations objectForKey:aKey])) + { + animation = [[self class] defaultAnimationForKey:aKey]; + } + + return animation; +} + +- (CPDictionary)animations +{ + return _animationsDictionary; +} + +- (void)setAnimations:(CPDictionary)animationsDict +{ + _animationsDictionary = [animationsDict copy]; +} + +@end \ No newline at end of file diff --git a/AppKit/CoreAnimation/CSSAnimation.j b/AppKit/CoreAnimation/CSSAnimation.j new file mode 100644 index 000000000..31d0c8f7f --- /dev/null +++ b/AppKit/CoreAnimation/CSSAnimation.j @@ -0,0 +1,288 @@ + +var ANIMATIONS_GLOBAL_ID = 0, + CURRENT_ANIMATIONS = {}, + + ANIMATION_END_EVENT_NAME, + ANIMATION__PROPERTY, + ANIMATION_NAME_PROPERTY, + ANIMATION_DURATION_PROPERTY, + ANIMATION_TIMING_FUNCTION_PROPERTY, + ANIMATION_FILL_MODE_PROPERTY, + ANIMATION_KEYFRAMES_RULE; + +var defineCSSProperties = function() +{ + if (this.done) + return; + + ANIMATION_END_EVENT_NAME = CPBrowserStyleProperty("animationend"), + ANIMATION_PROPERTY = CPBrowserCSSProperty("animation"), + ANIMATION_NAME_PROPERTY = CPBrowserCSSProperty("animation-name"), + ANIMATION_DURATION_PROPERTY = CPBrowserCSSProperty("animation-duration"), + ANIMATION_TIMING_FUNCTION_PROPERTY = CPBrowserCSSProperty("animation-timing-function"), + ANIMATION_FILL_MODE_PROPERTY = CPBrowserCSSProperty("animation-fill-mode"), + ANIMATION_KEYFRAMES_RULE = "@" + ANIMATION_PROPERTY.substring(0, ANIMATION_PROPERTY.indexOf("animation")) + "keyframes"; + + this.done = true; +} + +CSSAnimation = function(aTarget/*DOM Element*/, anIdentifier) +{ + defineCSSProperties(); + + if (!anIdentifier) + anIdentifier = ANIMATIONS_GLOBAL_ID++; + + var animationName = "anim_" + anIdentifier, + animation = CURRENT_ANIMATIONS[anIdentifier]; + + if (animation) + console.warn("Animation "+ anIdentifier + " is already in use. Ignoring."); + else + { + this.target = aTarget; + this.identifier = anIdentifier; + this.animationName = animationName; + this.listener = null; + this.styleElement = null; + this.propertyanimations = []; + this.animationsnames = []; + this.animationstimingfunctions = []; + this.animationsdurations = []; + this.islive = false; + this.didBuildDOMElements = false; + this.removeAnimationPropertyOnCompletion = true; + + animation = this; + CURRENT_ANIMATIONS[anIdentifier] = animation; + } + + return animation; +} + +CSSAnimation.prototype.addPropertyAnimation = function(propertyName/*String*/, valueFunction/*Function*/, aDuration/*float*/, aKeyTimes/*d, [d]*/, aValues/*Array*/, aTimingFunctions/*[d,d,d,d],[[d,d,d,d]]*/, aCompletionfunction/*Function*/) +{ + if (this.islive) + return false; +// TODO: If a property already exist, replace its values & valueFunctions. + + var name = this.animationName + "_" + propertyName; + + var animation = {name:name, + property:propertyName, + valuefunction:valueFunction, + keytimes:aKeyTimes, + values:aValues, + duration:aDuration, + completionfunction:aCompletionfunction}; + + var animationTimingFunction; + if (aTimingFunctions && (aTimingFunctions[0] instanceof Array)) + { + animation.keyframestimingFunctions = aTimingFunctions; + // dummy timing function overriden by keyframes timings functions + animationTimingFunction = "linear"; + } + else + animationTimingFunction = "cubic-bezier(" + aTimingFunctions + ")"; + + this.animationstimingfunctions.push(animationTimingFunction); + + this.propertyanimations.push(animation); + this.animationsnames.push(name); + this.animationsdurations.push(aDuration + "s"); + + return true; +} + +CSSAnimation.prototype.keyFrames = function() +{ + var keyframesRules = []; + + var count = this.propertyanimations.length; + for (var i = 0; i < count; i++) + { + var animation = this.propertyanimations[i], + property = animation.property, + valuefunction = animation.valuefunction, + keytimes = animation.keytimes, + values = animation.values, + timingFunctions = animation.keyframestimingFunctions; + + var keyframes = [], + keytimescount = keytimes.length, + start_value = values[0]; + + for (var j = 0; j < keytimescount; j++) + { + var keytime = keytimes[j], + value = values[j], + timingFunction; + + if (valuefunction !== nil) + value = valuefunction(start_value, value); + + var keyframeContent = property + ": " + value + ";"; + + if (timingFunctions && timingFunctions.length && (timingFunction = timingFunctions[j])) + { + keyframeContent += ANIMATION_TIMING_FUNCTION_PROPERTY + ":cubic-bezier(" + timingFunction + ");"; + } + + var keyframe = "\t" + Math.round(keytime * 100) + "% {\n\t\t" + keyframeContent + "\n\t}\n"; + keyframes.push(keyframe); + } + // TODO ! Add keyframe rule to CPCompatibility + var rule = ANIMATION_KEYFRAMES_RULE + " " + animation.name + " {\n" + keyframes.join(" ") + "}\n"; + keyframesRules.push(rule); + } + + return keyframesRules.join("\n"); +} + +CSSAnimation.prototype.appendKeyFramesRule = function() +{ + var styleElement = this.createKeyFramesStyleElement(), + keyframesText = this.keyFrames(), + nodeText = document.createTextNode(keyframesText); + + styleElement.appendChild(nodeText); + document.head.appendChild(styleElement); +} + +CSSAnimation.prototype.createKeyFramesStyleElement = function() +{ + if (!this.styleElement) + { + var styleElement = document.createElement("style"); + styleElement.setAttribute("type", "text/css"); + + this.styleElement = styleElement; + } + + return this.styleElement; +} + +CSSAnimation.prototype.endEventListener = function() +{ + var animation = this, + animationsNames = this.animationsnames, + inFlightAnimationsNames = animationsNames.slice(); + + if (!animation.listener) + { + var AnimationEndListener = function(event) + { + var idx = inFlightAnimationsNames.indexOf(event.animationName); + if (idx !== -1) + inFlightAnimationsNames.splice(idx, 1); + + if (inFlightAnimationsNames.length == 0) + { + for (var i = 0; i < animationsNames.length; i++) + { + var completion = animation.completionFunctionForAnimationName(animationsNames[i]); + if (completion) + completion(); + } + + var eventTarget = event.target, + style = eventTarget.style; + + if (animation.removeAnimationPropertyOnCompletion) + style.removeProperty(ANIMATION_NAME_PROPERTY); + + style.removeProperty(ANIMATION_DURATION_PROPERTY); + style.removeProperty(ANIMATION_FILL_MODE_PROPERTY); + style.removeProperty("-webkit-backface-visibility"); + + if (animation.animationstimingfunctions.length) + style.removeProperty(ANIMATION_TIMING_FUNCTION_PROPERTY); + + removeFromParent(animation.styleElement); + + eventTarget.removeEventListener(ANIMATION_END_EVENT_NAME, AnimationEndListener); + animation.listener = null; + delete (CURRENT_ANIMATIONS[animation.identifier]); + } + }; + + this.listener = AnimationEndListener; + } + + return this.listener; +} + +CSSAnimation.prototype.completionFunctionForAnimationName = function(aName) +{ + var propanims = this.propertyanimations, + count = propanims.length; + + while (count--) + { + var anim = propanims[count]; + if (anim.name == aName) + return anim.completionfunction; + } + + return null; +} + +CSSAnimation.prototype.addAnimationEndEventListener = function() +{ + var listener = this.endEventListener(); + this.target.addEventListener(ANIMATION_END_EVENT_NAME, listener, false); +} + +CSSAnimation.prototype.setTargetStyleProperties = function() +{ + var style = this.target.style; + + if (this.animationstimingfunctions.length) + style.setProperty(ANIMATION_TIMING_FUNCTION_PROPERTY, this.animationstimingfunctions.join(",")); + + style.setProperty(ANIMATION_DURATION_PROPERTY, this.animationsdurations.join(",")); + + style.setProperty(ANIMATION_FILL_MODE_PROPERTY, "forwards"); + +// http://webdesign.tutsplus.com/tutorials/htmlcss-tutorials/css3-animations-the-hiccups-and-bugs-youll-want-to-avoid/ + style.setProperty("-webkit-backface-visibility", "hidden"); +} + +CSSAnimation.prototype.buildDOMElements = function() +{ + this.appendKeyFramesRule(); + + this.addAnimationEndEventListener(); + + this.setTargetStyleProperties(); + + this.didBuildDOMElements = true; +} + +CSSAnimation.prototype.setRemoveAnimationPropertyOnCompletion = function(flag) +{ + this.removeAnimationPropertyOnCompletion = flag; +} + +CSSAnimation.prototype.start = function() +{ + if (this.propertyanimations.length == 0 || this.islive) + return false; + + if (!this.didBuildDOMElements) + this.buildDOMElements(); + + this.target.style.setProperty(ANIMATION_NAME_PROPERTY, this.animationsnames.join(",")); + + this.islive = true; + + return true; +} + +var removeFromParent= function(aNode) +{ + var parentNode = aNode.parentNode; + if (parentNode) + parentNode.removeChild(aNode); +} diff --git a/AppKit/CoreAnimation/_CPObjectAnimator.j b/AppKit/CoreAnimation/_CPObjectAnimator.j new file mode 100644 index 000000000..289d079f0 --- /dev/null +++ b/AppKit/CoreAnimation/_CPObjectAnimator.j @@ -0,0 +1,91 @@ + +@import +@import "CPAnimationContext.j" + +var _supportsCSSAnimations = null; + +@protocol CPAnimatablePropertyContainer + ++ (id)defaultAnimationForKey:(CPString)key; +- (id)animationForKey:(CPString)key; + +- (id)animator; +- (CPDictionary)animations; +- (void)setAnimations:(CPDictionary)animations; + +@end + +@implementation _CPObjectAnimator : CPProxy +{ + id _target; +} + ++ (BOOL)supportsCSSAnimations +{ + if (_supportsCSSAnimations === null) + _supportsCSSAnimations = CPBrowserCSSProperty("animation"); + + return _supportsCSSAnimations; +} + +- (id)initWithTarget:(id)aTarget +{ + _target = aTarget; + + return self; +} + +- (id)animator +{ + return self; +} + +- (BOOL)isEqual:(id)anObject +{ + return [_target isEqual:anObject]; +} + +- (id)forwardingTargetForSelector:(SEL)aSelector +{ + return _target; +} + +- (CPMethodSignature)methodSignatureForSelector:(SEL)aSelector +{ + return [_target methodSignatureForSelector:aSelector]; +} + +- (void)forwardInvocation:(CPInvocation)anInvocation +{ + var target = [self forwardingTargetForSelector:[anInvocation selector]]; + [anInvocation invokeWithTarget:target]; + return; +} + +- (void)doesNotRecognizeSelector:(SEL)aSelector +{ + [CPException raise:CPInvalidArgumentException reason:@"Animator does not recognize selector " + CPStringFromSelector(aSelector)]; +} + +- (CPString)description +{ + return [CPString stringWithFormat:@"%@ Animator Proxy for %@", [self class], _target]; +} + +- (void)setValue:(id)aTargetValue forKey:(id)aKeyPath +{ + var animation = [_target animationForKey:aKeyPath], + context = [CPAnimationContext currentContext]; + + if (!animation || ![animation isKindOfClass:[CAAnimation class]] || (![context duration] && ![animation duration]) || ![_CPObjectAnimator supportsCSSAnimations]) + [_target setValue:aTargetValue forKey:aKeyPath]; + else + { + [context _enqueueActionForObject:_target keyPath:aKeyPath targetValue:aTargetValue completionHandler:function() + { + [_target setValue:aTargetValue forKey:aKeyPath]; + }]; + } +} + +@end diff --git a/AppKit/CoreAnimation/jshashtable.j b/AppKit/CoreAnimation/jshashtable.j new file mode 100644 index 000000000..f685e9b38 --- /dev/null +++ b/AppKit/CoreAnimation/jshashtable.j @@ -0,0 +1,370 @@ +/** + * Copyright 2010 Tim Down. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * jshashtable + * + * jshashtable is a JavaScript implementation of a hash table. It creates a single constructor function called Hashtable + * in the global scope. + * + * Author: Tim Down + * Version: 2.1 + * Build date: 21 March 2010 + * Website: http://www.timdown.co.uk/jshashtable + */ + +Hashtable = (function() { + var FUNCTION = "function"; + + var arrayRemoveAt = (typeof Array.prototype.splice == FUNCTION) ? + function(arr, idx) { + arr.splice(idx, 1); + } : + + function(arr, idx) { + var itemsAfterDeleted, i, len; + if (idx === arr.length - 1) { + arr.length = idx; + } else { + itemsAfterDeleted = arr.slice(idx + 1); + arr.length = idx; + for (i = 0, len = itemsAfterDeleted.length; i < len; ++i) { + arr[idx + i] = itemsAfterDeleted[i]; + } + } + }; + + function hashObject(obj) { + var hashCode; + if (typeof obj == "string") { + return obj; + } else if (typeof obj.hashCode == FUNCTION) { + // Check the hashCode method really has returned a string + hashCode = obj.hashCode(); + return (typeof hashCode == "string") ? hashCode : hashObject(hashCode); + } else if (typeof obj.toString == FUNCTION) { + return obj.toString(); + } else { + try { + return String(obj); + } catch (ex) { + // For host objects (such as ActiveObjects in IE) that have no toString() method and throw an error when + // passed to String() + return Object.prototype.toString.call(obj); + } + } + } + + function equals_fixedValueHasEquals(fixedValue, variableValue) { + return fixedValue.equals(variableValue); + } + + function equals_fixedValueNoEquals(fixedValue, variableValue) { + return (typeof variableValue.equals == FUNCTION) ? + variableValue.equals(fixedValue) : (fixedValue === variableValue); + } + + function createKeyValCheck(kvStr) { + return function(kv) { + if (kv === null) { + throw new Error("null is not a valid " + kvStr); + } else if (typeof kv == "undefined") { + throw new Error(kvStr + " must not be undefined"); + } + }; + } + + var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value"); + + /*----------------------------------------------------------------------------------------------------------------*/ + + function Bucket(hash, firstKey, firstValue, equalityFunction) { + this[0] = hash; + this.entries = []; + this.addEntry(firstKey, firstValue); + + if (equalityFunction !== null) { + this.getEqualityFunction = function() { + return equalityFunction; + }; + } + } + + var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2; + + function createBucketSearcher(mode) { + return function(key) { + var i = this.entries.length, entry, equals = this.getEqualityFunction(key); + while (i--) { + entry = this.entries[i]; + if ( equals(key, entry[0]) ) { + switch (mode) { + case EXISTENCE: + return true; + case ENTRY: + return entry; + case ENTRY_INDEX_AND_VALUE: + return [ i, entry[1] ]; + } + } + } + return false; + }; + } + + function createBucketLister(entryProperty) { + return function(aggregatedArr) { + var startIndex = aggregatedArr.length; + for (var i = 0, len = this.entries.length; i < len; ++i) { + aggregatedArr[startIndex + i] = this.entries[i][entryProperty]; + } + }; + } + + Bucket.prototype = { + getEqualityFunction: function(searchValue) { + return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals; + }, + + getEntryForKey: createBucketSearcher(ENTRY), + + getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE), + + removeEntryForKey: function(key) { + var result = this.getEntryAndIndexForKey(key); + if (result) { + arrayRemoveAt(this.entries, result[0]); + return result[1]; + } + return null; + }, + + addEntry: function(key, value) { + this.entries[this.entries.length] = [key, value]; + }, + + keys: createBucketLister(0), + + values: createBucketLister(1), + + getEntries: function(entries) { + var startIndex = entries.length; + for (var i = 0, len = this.entries.length; i < len; ++i) { + // Clone the entry stored in the bucket before adding to array + entries[startIndex + i] = this.entries[i].slice(0); + } + }, + + containsKey: createBucketSearcher(EXISTENCE), + + containsValue: function(value) { + var i = this.entries.length; + while (i--) { + if ( value === this.entries[i][1] ) { + return true; + } + } + return false; + } + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Supporting functions for searching hashtable buckets + + function searchBuckets(buckets, hash) { + var i = buckets.length, bucket; + while (i--) { + bucket = buckets[i]; + if (hash === bucket[0]) { + return i; + } + } + return null; + } + + function getBucketForHash(bucketsByHash, hash) { + var bucket = bucketsByHash[hash]; + + // Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype + return ( bucket && (bucket instanceof Bucket) ) ? bucket : null; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + function Hashtable(hashingFunctionParam, equalityFunctionParam) { + var that = this; + var buckets = []; + var bucketsByHash = {}; + + var hashingFunction = (typeof hashingFunctionParam == FUNCTION) ? hashingFunctionParam : hashObject; + var equalityFunction = (typeof equalityFunctionParam == FUNCTION) ? equalityFunctionParam : null; + + this.put = function(key, value) { + checkKey(key); + checkValue(value); + var hash = hashingFunction(key), bucket, bucketEntry, oldValue = null; + + // Check if a bucket exists for the bucket key + bucket = getBucketForHash(bucketsByHash, hash); + if (bucket) { + // Check this bucket to see if it already contains this key + bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so replace old value and we're done. + oldValue = bucketEntry[1]; + bucketEntry[1] = value; + } else { + // The bucket does not contain an entry for this key, so add one + bucket.addEntry(key, value); + } + } else { + // No bucket exists for the key, so create one and put our key/value mapping in + bucket = new Bucket(hash, key, value, equalityFunction); + buckets[buckets.length] = bucket; + bucketsByHash[hash] = bucket; + } + return oldValue; + }; + + this.get = function(key) { + checkKey(key); + + var hash = hashingFunction(key); + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, hash); + if (bucket) { + // Check this bucket to see if it contains this key + var bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so return the value. + return bucketEntry[1]; + } + } + return null; + }; + + this.containsKey = function(key) { + checkKey(key); + var bucketKey = hashingFunction(key); + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, bucketKey); + + return bucket ? bucket.containsKey(key) : false; + }; + + this.containsValue = function(value) { + checkValue(value); + var i = buckets.length; + while (i--) { + if (buckets[i].containsValue(value)) { + return true; + } + } + return false; + }; + + this.clear = function() { + buckets.length = 0; + bucketsByHash = {}; + }; + + this.isEmpty = function() { + return !buckets.length; + }; + + var createBucketAggregator = function(bucketFuncName) { + return function() { + var aggregated = [], i = buckets.length; + while (i--) { + buckets[i][bucketFuncName](aggregated); + } + return aggregated; + }; + }; + + this.keys = createBucketAggregator("keys"); + this.values = createBucketAggregator("values"); + this.entries = createBucketAggregator("getEntries"); + + this.remove = function(key) { + checkKey(key); + + var hash = hashingFunction(key), bucketIndex, oldValue = null; + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, hash); + + if (bucket) { + // Remove entry from this bucket for this key + oldValue = bucket.removeEntryForKey(key); + if (oldValue !== null) { + // Entry was removed, so check if bucket is empty + if (!bucket.entries.length) { + // Bucket is empty, so remove it from the bucket collections + bucketIndex = searchBuckets(buckets, hash); + arrayRemoveAt(buckets, bucketIndex); + delete bucketsByHash[hash]; + } + } + } + return oldValue; + }; + + this.size = function() { + var total = 0, i = buckets.length; + while (i--) { + total += buckets[i].entries.length; + } + return total; + }; + + this.each = function(callback) { + var entries = that.entries(), i = entries.length, entry; + while (i--) { + entry = entries[i]; + callback(entry[0], entry[1]); + } + }; + + this.putAll = function(hashtable, conflictCallback) { + var entries = hashtable.entries(); + var entry, key, value, thisValue, i = entries.length; + var hasConflictCallback = (typeof conflictCallback == FUNCTION); + while (i--) { + entry = entries[i]; + key = entry[0]; + value = entry[1]; + + // Check for a conflict. The default behaviour is to overwrite the value for an existing key + if ( hasConflictCallback && (thisValue = that.get(key)) ) { + value = conflictCallback(key, thisValue, value); + } + that.put(key, value); + } + }; + + this.clone = function() { + var clone = new Hashtable(hashingFunctionParam, equalityFunctionParam); + clone.putAll(that); + return clone; + }; + } + + return Hashtable; +})(); \ No newline at end of file diff --git a/AppKit/CoreGraphics/CGContext.j b/AppKit/CoreGraphics/CGContext.j index ed518129a..158169cff 100644 --- a/AppKit/CoreGraphics/CGContext.j +++ b/AppKit/CoreGraphics/CGContext.j @@ -24,6 +24,7 @@ @import "CPCompatibility.j" @import "CGGeometry.j" @import "CGPath.j" +@import "CGContextText.j" @typedef CGContext diff --git a/AppKit/CoreGraphics/CGContextText.j b/AppKit/CoreGraphics/CGContextText.j new file mode 100644 index 000000000..92cf30c9d --- /dev/null +++ b/AppKit/CoreGraphics/CGContextText.j @@ -0,0 +1,86 @@ +/* + * CGContextText.j + * CoreText + * + * Created by Nicholas Small. + * Copyright 2011, 280 North, Inc. + * + * 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 + */ + +kCGTextFill = 0; +kCGTextStroke = 1; +kCGTextFillStroke = 2; +kCGTextInvisible = 3; + +function CGContextGetTextMatrix(/* CGContext */ aContext) +{ + return aContext._textMatrix; +} + +function CGContextSetTextMatrix(/* CGContext */ aContext, /* CGAffineTransform */ aTransform) +{ + aContext._textMatrix = aTransform; +} + +function CGContextGetTextPosition(/* CGContext */ aContext) +{ + return aContext._textPosition || _CGPointMakeZero(); +} + +function CGContextSetTextPosition(/* CGContext */ aContext, /* float */ x, /* float */ y) +{ + aContext._textPosition = CGPointMake(x, y); +} + +function CGContextGetFont(/* CGContext */ aContext) +{ + return aContext._CPFont; +} + +function CGContextSelectFont(/* CGContext */ aContext, /* CPFont */ aFont) +{ + aContext.font = [aFont cssString]; + aContext._CPFont = aFont; +} + +function CGContextSetTextDrawingMode(/* CGContext */ aContext, /* CGTextDrawingMode */ aMode) +{ + aContext._textDrawingMode = aMode; +} + +function CGContextShowText(/* CGContext */ aContext, /* CPString */ aString) +{ + CGContextShowTextAtPoint(aContext, aContext._textPosition.x, aContext._textPosition.y, aString); +} + +function CGContextShowTextAtPoint(/* CGContext */ aContext, /* float */ x, /* float */ y, /* CPString */ aString) +{ + aContext.textBaseline = @"middle"; + aContext.textAlign = @"left"; + + var mode = aContext._textDrawingMode; + if (!mode && mode !== 0) + mode = kCGTextFill; + + var width = aContext.measureText(aString).width; + + if (mode === kCGTextFill || mode === kCGTextFillStroke) + aContext.fillText(aString, x, y); + if (mode === kCGTextStroke || mode === kCGTextFillStroke) + aContext.strokeText(aString, x, y); + + aContext._textPosition = CGPointMake(x + width, y); +} diff --git a/AppKit/Platform/CPPlatformWindow.j b/AppKit/Platform/CPPlatformWindow.j index f73c9205f..16b1a4954 100644 --- a/AppKit/Platform/CPPlatformWindow.j +++ b/AppKit/Platform/CPPlatformWindow.j @@ -60,6 +60,7 @@ var PrimaryPlatformWindow = NULL; BOOL _mouseIsDown; BOOL _mouseDownIsRightClick; + int _firstMouseDownButton; CGPoint _lastMouseEventLocation; CPWindow _mouseDownWindow; CPTimeInterval _lastMouseUp; diff --git a/AppKit/Platform/DOM/CPPlatformPasteboard.j b/AppKit/Platform/DOM/CPPlatformPasteboard.j index e749a1fae..9ca0b3141 100644 --- a/AppKit/Platform/DOM/CPPlatformPasteboard.j +++ b/AppKit/Platform/DOM/CPPlatformPasteboard.j @@ -384,9 +384,10 @@ Return true if the event may be a copy and paste event, but the target is not an if ([value length]) { - var pasteboard = [CPPasteboard generalPasteboard]; + var pasteboard = [CPPasteboard generalPasteboard], + cappString = [pasteboard stringForType:CPStringPboardType]; - if ([pasteboard _stateUID] != value) + if (cappString != value) { [pasteboard declareTypes:[CPStringPboardType] owner:self]; [pasteboard setString:value forType:CPStringPboardType]; @@ -450,9 +451,10 @@ Return true if the event may be a copy and paste event, but the target is not an if ([value length]) { - var pasteboard = [CPPasteboard generalPasteboard]; + var pasteboard = [CPPasteboard generalPasteboard], + cappString = [pasteboard stringForType:CPStringPboardType]; - if ([pasteboard _stateUID] != value) + if (cappString != value) { [pasteboard declareTypes:[CPStringPboardType] owner:self]; [pasteboard setString:value forType:CPStringPboardType]; diff --git a/AppKit/Platform/DOM/CPPlatformString.j b/AppKit/Platform/DOM/CPPlatformString.j index 844eeea3e..fb440d36d 100644 --- a/AppKit/Platform/DOM/CPPlatformString.j +++ b/AppKit/Platform/DOM/CPPlatformString.j @@ -171,7 +171,9 @@ var DOMFixedWidthSpanElement = nil, span.style.width = ROUND(aWidth) + "px"; } - span.style.font = [(aFont || DefaultFont) cssString]; + var effectiveFontCSSString = [(aFont || DefaultFont) cssString]; + if (span.style.font !== effectiveFontCSSString) + span.style.font = effectiveFontCSSString; if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature)) span.innerText = aString; diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index bb183bd99..360fbf9f1 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -952,8 +952,8 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0]; event._DOMEvent = aDOMEvent; - // We lag 1 event behind without this timeout. - setTimeout(function() + // We lag 1 event behind without this approach + window.requestAnimationFrame(function() { if (aDOMEvent.deltaMode !== undefined && aDOMEvent.deltaMode !== 0) { @@ -992,10 +992,10 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _DOMScrollingElement.scrollLeft = 150; _DOMScrollingElement.scrollTop = 150; - // Is this needed? - //[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + // this is needed to prevent flickering during scrolling + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; - }, 0); + }); // We hide the dom element after a little bit // so that other DOM elements such as inputs @@ -1171,7 +1171,11 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio var touch = aDOMEvent.touches.length ? aDOMEvent.touches[0] : aDOMEvent.changedTouches[0]; newEvent.clientX = touch.clientX; - newEvent.clientY = touch.clientY; + + /* + Normally the document can't scroll in Cappuccino: our body element has top:0 and bottom:0 with absolute positioning. So it should always be exactly the height of the viewport. The below handles a special case. iOS scrolls the document when the virtual keyboard is present and it needs to move a text input upwards visually to avoid covering the input with the keyboard. For most purposes we can ignore this, except here. In theory I think we could always apply this (scrollTop should always be 0 on every other device and situation) but let's be defensive and only apply it for touch events to minimise the risk of surprises. + */ + newEvent.clientY = _DOMWindow.document.body.scrollTop + touch.clientY; newEvent.timestamp = [CPEvent currentTimestamp]; newEvent.target = aDOMEvent.target; @@ -1245,12 +1249,16 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio { if (_mouseIsDown) { + if (aDOMEvent.button !== _firstMouseDownButton) + return; + event = _CPEventFromNativeMouseEvent(aDOMEvent, _mouseDownIsRightClick ? CPRightMouseUp : CPLeftMouseUp, location, modifierFlags, timestamp, windowNumber, nil, -1, CPDOMEventGetClickCount(_lastMouseUp, timestamp, location), 0, nil); _mouseIsDown = NO; _lastMouseUp = event; _mouseDownWindow = nil; _mouseDownIsRightClick = NO; + _firstMouseDownButton = -1; } if (_DOMEventMode) @@ -1270,6 +1278,16 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _mouseDownIsRightClick = button == 2 || (CPBrowserIsOperatingSystem(CPMacOperatingSystem) && button == 0 && modifierFlags & CPControlKeyMask); + // If mouse is already down, that means that a second mouse button is pushed. This could interfere in mouse events treatment. Just ignore it. + // BUT we have to track which button will be first released. + if (_mouseIsDown) + { + _mouseDownIsRightClick = !_mouseDownIsRightClick; + return; + } + + _firstMouseDownButton = button; + if ((sourceElement.tagName === "INPUT" || sourceElement.tagName === "TEXTAREA") && sourceElement != _DOMFocusElement) { if ([CPPlatform supportsDragAndDrop]) @@ -1857,11 +1875,10 @@ function CPWindowObjectList() function CPWindowList() { - var windowObjectList = CPWindowObjectList(), - windowList = []; + var windowObjectList = CPWindowObjectList(); - for (var i = 0, count = [windowObjectList count]; i < count; i++) - windowList.push([windowObjectList[i] windowNumber]); - - return windowList; + return [windowObjectList arrayByApplyingBlock:function(windowObject) + { + return [windowObject windowNumber]; + }]; } diff --git a/AppKit/Themes/Aristo/ThemeDescriptors.j b/AppKit/Themes/Aristo/ThemeDescriptors.j index 31bd445ac..6902f92c7 100755 --- a/AppKit/Themes/Aristo/ThemeDescriptors.j +++ b/AppKit/Themes/Aristo/ThemeDescriptors.j @@ -389,8 +389,11 @@ var themedButtonValues = nil, var color = [CPColor blackColor], themedColorValues = [ - [@"alternate-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]], - [@"secondary-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]] + [@"alternate-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]], + [@"secondary-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]], + [@"selected-text-background-color", [CPColor colorWithHexString:"99CCFF"]], + [@"selected-text-inactive-background-color", [CPColor colorWithHexString:"CCCCCC"]] + ]; [self registerThemeValues:themedColorValues forObject:color]; diff --git a/AppKit/Themes/Aristo2/ThemeDescriptors.j b/AppKit/Themes/Aristo2/ThemeDescriptors.j index ee6917791..365cadba0 100644 --- a/AppKit/Themes/Aristo2/ThemeDescriptors.j +++ b/AppKit/Themes/Aristo2/ThemeDescriptors.j @@ -106,8 +106,10 @@ var themedButtonValues = nil, var color = [CPColor redColor], themedColorValues = [ - [@"alternate-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]], - [@"secondary-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]] + [@"alternate-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]], + [@"secondary-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]], + [@"selected-text-background-color", [CPColor colorWithHexString:"99CCFF"]], + [@"selected-text-inactive-background-color", [CPColor colorWithHexString:"CCCCCC"]] ]; [self registerThemeValues:themedColorValues forObject:color]; diff --git a/AppKit/_CPImageAndTextView.j b/AppKit/_CPImageAndTextView.j index 1a02755f5..f7efc1e34 100644 --- a/AppKit/_CPImageAndTextView.j +++ b/AppKit/_CPImageAndTextView.j @@ -550,14 +550,16 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0, case CPLineBreakByCharWrapping: case CPLineBreakByWordWrapping: textStyle.wordWrap = "break-word"; - try { + try + { textStyle.whiteSpace = "pre"; textStyle.whiteSpace = "-o-pre-wrap"; textStyle.whiteSpace = "-pre-wrap"; textStyle.whiteSpace = "-moz-pre-wrap"; textStyle.whiteSpace = "pre-wrap"; } - catch (e) { + catch (e) + { //internet explorer doesn't like these properties textStyle.whiteSpace = "pre"; } diff --git a/CommonJS/lib/cappuccino/nativehost.js b/CommonJS/lib/cappuccino/nativehost.js deleted file mode 100644 index 8def87642..000000000 --- a/CommonJS/lib/cappuccino/nativehost.js +++ /dev/null @@ -1,70 +0,0 @@ -var FILE = require("file"); -var OS = require("os"); - -var NATIVEHOST_SOURCE = FILE.path(module.path).dirname().dirname().dirname().join("support", "NativeHost.app"); - -exports.buildNativeHost = function(rootPath, buildNative, options) { - options = options || {}; - options.index = options.index || "index.html"; - - rootPath = FILE.path(rootPath); - buildNative = FILE.path(buildNative); - - if (buildNative.exists()) - buildNative.rmtree(); - - buildNative.dirname().mkdirs(); - // FIXME: Narwhal doesn't preserve permissions - // FILE.copyTree(NATIVEHOST_SOURCE, buildNative); - OS.system(["cp", "-r", NATIVEHOST_SOURCE, buildNative]); - FILE.chmod(buildNative.join("Contents", "MacOS", "NativeHost"), 0755); - - var rootBaseName = rootPath.basename(); - var buildClientDirectory = buildNative.join("Contents", "Resources", rootBaseName); - - FILE.mkdirs(FILE.dirname(buildClientDirectory)); - // FILE.copyTree(rootPath, buildClientDirectory); - OS.system(["cp", "-r", rootPath, buildClientDirectory]); - - var defaultBundleName = buildNative.basename().match(/^(.*)(\.app)?$/)[1]; - - function mergePlist(plist, path) { - var otherPlist = CFPropertyList.readPropertyListFromFile(String(path)); - - otherPlist.keys().forEach(function(key) { - var value = otherPlist.valueForKey(key); - plist.setValueForKey(key, value); - - if (key === "CPBundleName") - plist.setValueForKey("CFBundleName", value); - - if (key === "CFBundleIconFile") { - var iconPath = rootPath.join("Resources", value); - if (iconPath.isFile()) - iconPath.copy(buildNative.join("Contents", "Resources", value)); - else - print("Warning: CFBundleIconFile references " + value + " but does not exist in the resources directory."); - } - - if (key === "CFBundleExecutable") { - buildNative.join("Contents", "MacOS", "NativeHost").rename(value); - // FIXME: - FILE.chmod(buildNative.join("Contents", "MacOS", value), 0755); - } - }); - } - - CFPropertyList.modifyPlist(buildNative.join("Contents", "Info.plist"), function(plist) { - - plist.setValueForKey("CFBundleName", defaultBundleName); - plist.setValueForKey("NHInitialResource", FILE.join(rootBaseName, options.index)); - - // merge Cappuccino plist - var cappPlistPath = rootPath.join("Info.plist"); - if (cappPlistPath.isFile()) - mergePlist(plist, cappPlistPath); - - if (options.extraPlistPath) - mergePlist(plist, options.extraPlistPath); - }); -} diff --git a/Foundation/CPArray/_CPArray.j b/Foundation/CPArray/_CPArray.j index 7607aa480..9a18fc879 100755 --- a/Foundation/CPArray/_CPArray.j +++ b/Foundation/CPArray/_CPArray.j @@ -881,22 +881,22 @@ Returns a hash for the object. Unlike Cocoa, the hash value does not take conten } /*! - Returns an Array formed by applying a function to the objects in the receiver. + Returns an Array formed by applying a function to each object in the receiver. @param aFunction a function taking two arguments: (element, index). @return an Array containing the transformed elements. */ - (CPArray)arrayByApplyingBlock:(Function/*element, index*/)aFunction { - var result = [], - count = [self count]; + var result = [], + count = [self count]; - for (var idx = 0; idx < count; idx++) - { - var obj = aFunction([self objectAtIndex:idx], idx); - [result addObject:obj]; - } + for (var idx = 0; idx < count; idx++) + { + var obj = aFunction([self objectAtIndex:idx], idx); + [result addObject:obj]; + } - return result; + return result; } // Creating a description of the array diff --git a/Foundation/CPArray/_CPJavaScriptArray.j b/Foundation/CPArray/_CPJavaScriptArray.j index 6d36a8586..8822ed0b4 100644 --- a/Foundation/CPArray/_CPJavaScriptArray.j +++ b/Foundation/CPArray/_CPJavaScriptArray.j @@ -235,15 +235,15 @@ var concat = Array.prototype.concat, - (CPArray)arrayByApplyingBlock:(Function/*element, index*/)aFunction { - var result = []; + var result = []; - for (var idx = 0; idx < self.length; idx++) - { - var obj = aFunction(self[idx], idx); - result.push(obj); - } + for (var idx = 0; idx < self.length; idx++) + { + var obj = aFunction(self[idx], idx); + result.push(obj); + } - return result; + return result; } - (void)insertObject:(id)anObject atIndex:(CPUInteger)anIndex diff --git a/Foundation/CPAttributedString.j b/Foundation/CPAttributedString.j index 04c131631..3416b64bc 100755 --- a/Foundation/CPAttributedString.j +++ b/Foundation/CPAttributedString.j @@ -763,13 +763,10 @@ - (void)setAttributedString:(CPAttributedString)aString { _string = aString._string; - _rangeEntries = []; - - var i = 0, - count = aString._rangeEntries.length; - - for (; i < count; i++) - _rangeEntries.push(copyRangeEntry(aString._rangeEntries[i])); + _rangeEntries = [aString._rangeEntries arrayByApplyingBlock:function(entry) + { + return copyRangeEntry(entry); + }]; } //Private methods diff --git a/Foundation/CPBundle.j b/Foundation/CPBundle.j index 069e0e6eb..fa752574a 100644 --- a/Foundation/CPBundle.j +++ b/Foundation/CPBundle.j @@ -212,15 +212,12 @@ var CPBundlesForURLStrings = { }; - (CPArray)staticResourceURLs { - var staticResourceURLs = [], - staticResources = _bundle.staticResources(), - index = 0, - count = [staticResources count]; + var staticResources = _bundle.staticResources(); - for (; index < count; ++index) - [staticResourceURLs addObject:staticResources[index].URL()]; - - return staticResourceURLs; + return [staticResources arrayByApplyingBlock:function(resource) + { + return resource.URL(); + }]; } - (CPArray)environments diff --git a/Foundation/CPCache.j b/Foundation/CPCache.j index c8efe2e2d..217758a6b 100644 --- a/Foundation/CPCache.j +++ b/Foundation/CPCache.j @@ -264,8 +264,11 @@ var CPCacheDelegate_cache_willEvictObject_ = 1 << 1; // Sort keys by position var sortedKeys = [[_items allKeys] sortedArrayUsingFunction: - function(k1, k2) { - return [[[_items objectForKey:k1] position] compare:[[_items objectForKey:k2] position]]; }]; + function(k1, k2) + { + return [[[_items objectForKey:k1] position] compare:[[_items objectForKey:k2] position]]; + } + ]; // Affect new positions for (var i = 0; i < sortedKeys.length; ++i) @@ -300,8 +303,11 @@ var CPCacheDelegate_cache_willEvictObject_ = 1 << 1; // Sort keys by position var sortedKeys = [[_items allKeys] sortedArrayUsingFunction: - function(k1, k2) { - return [[[_items objectForKey:k1] position] compare:[[_items objectForKey:k2] position]]; }]; + function(k1, k2) + { + return [[[_items objectForKey:k1] position] compare:[[_items objectForKey:k2] position]]; + } + ]; // Remove oldest objects until to satisfy the break condition for (var i = 0; i < sortedKeys.length; ++i) @@ -313,7 +319,7 @@ var CPCacheDelegate_cache_willEvictObject_ = 1 << 1; [self _sendDelegateWillEvictObjectForKey:sortedKeys[i]]; // Remove object - [_items removeObjectForKey: sortedKeys[i]]; + [_items removeObjectForKey:sortedKeys[i]]; // Invalid cost cache _totalCostCache = -1; diff --git a/Foundation/CPIndexSet.j b/Foundation/CPIndexSet.j index c9f94fddb..1783caf27 100644 --- a/Foundation/CPIndexSet.j +++ b/Foundation/CPIndexSet.j @@ -1018,14 +1018,13 @@ var CPIndexSetCountKey = @"CPIndexSetCountKey", if (self) { _count = [aCoder decodeIntForKey:CPIndexSetCountKey]; - _ranges = []; - var rangeStrings = [aCoder decodeObjectForKey:CPIndexSetRangeStringsKey], - index = 0, - count = rangeStrings.length; + var rangeStrings = [aCoder decodeObjectForKey:CPIndexSetRangeStringsKey]; - for (; index < count; ++index) - _ranges.push(CPRangeFromString(rangeStrings[index])); + _ranges = [rangeStrings arrayByApplyingBlock:function(range) + { + return CPRangeFromString(range); + }]; } return self; diff --git a/Foundation/CPKeyValueObserving.j b/Foundation/CPKeyValueObserving.j index 95c00d36a..e4b55f752 100644 --- a/Foundation/CPKeyValueObserving.j +++ b/Foundation/CPKeyValueObserving.j @@ -399,299 +399,299 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti - (void)_replaceModifiersForKey:(CPString)aKey { - if ([_replacedKeys containsObject:aKey] || ![_nativeClass automaticallyNotifiesObserversForKey:aKey]) - return; - - [_replacedKeys addObject:aKey]; - - var theClass = _nativeClass, - KVOClass = _targetObject.isa, - capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1); - - // Attribute and To-One Relationships - var setKey_selector = sel_getUid("set" + capitalizedKey + ":"), - setKey_method = class_getInstanceMethod(theClass, setKey_selector); - - if (setKey_method) + if (![_replacedKeys containsObject:aKey] && [_nativeClass automaticallyNotifiesObserversForKey:aKey]) { - var setKey_method_imp = setKey_method.method_imp; + [_replacedKeys addObject:aKey]; - class_addMethod(KVOClass, setKey_selector, function(self, _cmd, anObject) + var theClass = _nativeClass, + KVOClass = _targetObject.isa, + capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substring(1); + + // Attribute and To-One Relationships + var setKey_selector = sel_getUid("set" + capitalizedKey + ":"), + setKey_method = class_getInstanceMethod(theClass, setKey_selector); + + if (setKey_method) { - [self willChangeValueForKey:aKey]; + var setKey_method_imp = setKey_method.method_imp; - setKey_method_imp(self, _cmd, anObject); - - [self didChangeValueForKey:aKey]; - }, setKey_method.method_types); - } - - // FIXME: Deprecated. - var _setKey_selector = sel_getUid("_set" + capitalizedKey + ":"), - _setKey_method = class_getInstanceMethod(theClass, _setKey_selector); - - if (_setKey_method) - { - var _setKey_method_imp = _setKey_method.method_imp; - - class_addMethod(KVOClass, _setKey_selector, function(self, _cmd, anObject) - { - [self willChangeValueForKey:aKey]; - - _setKey_method_imp(self, _cmd, anObject); - - [self didChangeValueForKey:aKey]; - }, _setKey_method.method_types); - } - - // Ordered To-Many Relationships - var insertObject_inKeyAtIndex_selector = sel_getUid("insertObject:in" + capitalizedKey + "AtIndex:"), - insertObject_inKeyAtIndex_method = - class_getInstanceMethod(theClass, insertObject_inKeyAtIndex_selector), - - insertKey_atIndexes_selector = sel_getUid("insert" + capitalizedKey + ":atIndexes:"), - insertKey_atIndexes_method = - class_getInstanceMethod(theClass, insertKey_atIndexes_selector), - - removeObjectFromKeyAtIndex_selector = sel_getUid("removeObjectFrom" + capitalizedKey + "AtIndex:"), - removeObjectFromKeyAtIndex_method = - class_getInstanceMethod(theClass, removeObjectFromKeyAtIndex_selector), - - removeKeyAtIndexes_selector = sel_getUid("remove" + capitalizedKey + "AtIndexes:"), - removeKeyAtIndexes_method = class_getInstanceMethod(theClass, removeKeyAtIndexes_selector); - - if ((insertObject_inKeyAtIndex_method || insertKey_atIndexes_method) && - (removeObjectFromKeyAtIndex_method || removeKeyAtIndexes_method)) - { - if (insertObject_inKeyAtIndex_method) - { - var insertObject_inKeyAtIndex_method_imp = insertObject_inKeyAtIndex_method.method_imp; - - class_addMethod(KVOClass, insertObject_inKeyAtIndex_selector, function(self, _cmd, anObject, anIndex) + class_addMethod(KVOClass, setKey_selector, function(self, _cmd, anObject) { - [self willChange:CPKeyValueChangeInsertion - valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] - forKey:aKey]; + [self willChangeValueForKey:aKey]; - insertObject_inKeyAtIndex_method_imp(self, _cmd, anObject, anIndex); + setKey_method_imp(self, _cmd, anObject); - [self didChange:CPKeyValueChangeInsertion - valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] - forKey:aKey]; - }, insertObject_inKeyAtIndex_method.method_types); + [self didChangeValueForKey:aKey]; + }, setKey_method.method_types); } - if (insertKey_atIndexes_method) + // FIXME: Deprecated. + var _setKey_selector = sel_getUid("_set" + capitalizedKey + ":"), + _setKey_method = class_getInstanceMethod(theClass, _setKey_selector); + + if (_setKey_method) { - var insertKey_atIndexes_method_imp = insertKey_atIndexes_method.method_imp; + var _setKey_method_imp = _setKey_method.method_imp; - class_addMethod(KVOClass, insertKey_atIndexes_selector, function(self, _cmd, objects, indexes) + class_addMethod(KVOClass, _setKey_selector, function(self, _cmd, anObject) { - [self willChange:CPKeyValueChangeInsertion - valuesAtIndexes:[indexes copy] - forKey:aKey]; + [self willChangeValueForKey:aKey]; - insertKey_atIndexes_method_imp(self, _cmd, objects, indexes); + _setKey_method_imp(self, _cmd, anObject); - [self didChange:CPKeyValueChangeInsertion - valuesAtIndexes:[indexes copy] - forKey:aKey]; - }, insertKey_atIndexes_method.method_types); + [self didChangeValueForKey:aKey]; + }, _setKey_method.method_types); } - if (removeObjectFromKeyAtIndex_method) + // Ordered To-Many Relationships + var insertObject_inKeyAtIndex_selector = sel_getUid("insertObject:in" + capitalizedKey + "AtIndex:"), + insertObject_inKeyAtIndex_method = + class_getInstanceMethod(theClass, insertObject_inKeyAtIndex_selector), + + insertKey_atIndexes_selector = sel_getUid("insert" + capitalizedKey + ":atIndexes:"), + insertKey_atIndexes_method = + class_getInstanceMethod(theClass, insertKey_atIndexes_selector), + + removeObjectFromKeyAtIndex_selector = sel_getUid("removeObjectFrom" + capitalizedKey + "AtIndex:"), + removeObjectFromKeyAtIndex_method = + class_getInstanceMethod(theClass, removeObjectFromKeyAtIndex_selector), + + removeKeyAtIndexes_selector = sel_getUid("remove" + capitalizedKey + "AtIndexes:"), + removeKeyAtIndexes_method = class_getInstanceMethod(theClass, removeKeyAtIndexes_selector); + + if ((insertObject_inKeyAtIndex_method || insertKey_atIndexes_method) && + (removeObjectFromKeyAtIndex_method || removeKeyAtIndexes_method)) { - var removeObjectFromKeyAtIndex_method_imp = removeObjectFromKeyAtIndex_method.method_imp; - - class_addMethod(KVOClass, removeObjectFromKeyAtIndex_selector, function(self, _cmd, anIndex) + if (insertObject_inKeyAtIndex_method) { - [self willChange:CPKeyValueChangeRemoval - valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] - forKey:aKey]; + var insertObject_inKeyAtIndex_method_imp = insertObject_inKeyAtIndex_method.method_imp; - removeObjectFromKeyAtIndex_method_imp(self, _cmd, anIndex); + class_addMethod(KVOClass, insertObject_inKeyAtIndex_selector, function(self, _cmd, anObject, anIndex) + { + [self willChange:CPKeyValueChangeInsertion + valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] + forKey:aKey]; - [self didChange:CPKeyValueChangeRemoval - valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] - forKey:aKey]; - }, removeObjectFromKeyAtIndex_method.method_types); + insertObject_inKeyAtIndex_method_imp(self, _cmd, anObject, anIndex); + + [self didChange:CPKeyValueChangeInsertion + valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] + forKey:aKey]; + }, insertObject_inKeyAtIndex_method.method_types); + } + + if (insertKey_atIndexes_method) + { + var insertKey_atIndexes_method_imp = insertKey_atIndexes_method.method_imp; + + class_addMethod(KVOClass, insertKey_atIndexes_selector, function(self, _cmd, objects, indexes) + { + [self willChange:CPKeyValueChangeInsertion + valuesAtIndexes:[indexes copy] + forKey:aKey]; + + insertKey_atIndexes_method_imp(self, _cmd, objects, indexes); + + [self didChange:CPKeyValueChangeInsertion + valuesAtIndexes:[indexes copy] + forKey:aKey]; + }, insertKey_atIndexes_method.method_types); + } + + if (removeObjectFromKeyAtIndex_method) + { + var removeObjectFromKeyAtIndex_method_imp = removeObjectFromKeyAtIndex_method.method_imp; + + class_addMethod(KVOClass, removeObjectFromKeyAtIndex_selector, function(self, _cmd, anIndex) + { + [self willChange:CPKeyValueChangeRemoval + valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] + forKey:aKey]; + + removeObjectFromKeyAtIndex_method_imp(self, _cmd, anIndex); + + [self didChange:CPKeyValueChangeRemoval + valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] + forKey:aKey]; + }, removeObjectFromKeyAtIndex_method.method_types); + } + + if (removeKeyAtIndexes_method) + { + var removeKeyAtIndexes_method_imp = removeKeyAtIndexes_method.method_imp; + + class_addMethod(KVOClass, removeKeyAtIndexes_selector, function(self, _cmd, indexes) + { + [self willChange:CPKeyValueChangeRemoval + valuesAtIndexes:[indexes copy] + forKey:aKey]; + + removeKeyAtIndexes_method_imp(self, _cmd, indexes); + + [self didChange:CPKeyValueChangeRemoval + valuesAtIndexes:[indexes copy] + forKey:aKey]; + }, removeKeyAtIndexes_method.method_types); + } + + // These are optional. + var replaceObjectInKeyAtIndex_withObject_selector = + sel_getUid("replaceObjectIn" + capitalizedKey + "AtIndex:withObject:"), + replaceObjectInKeyAtIndex_withObject_method = + class_getInstanceMethod(theClass, replaceObjectInKeyAtIndex_withObject_selector); + + if (replaceObjectInKeyAtIndex_withObject_method) + { + var replaceObjectInKeyAtIndex_withObject_method_imp = + replaceObjectInKeyAtIndex_withObject_method.method_imp; + + class_addMethod(KVOClass, replaceObjectInKeyAtIndex_withObject_selector, + function(self, _cmd, anIndex, anObject) + { + [self willChange:CPKeyValueChangeReplacement + valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] + forKey:aKey]; + + replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, anIndex, anObject); + + [self didChange:CPKeyValueChangeReplacement + valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] + forKey:aKey]; + }, replaceObjectInKeyAtIndex_withObject_method.method_types); + } + + var replaceKeyAtIndexes_withKey_selector = + sel_getUid("replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":"), + replaceKeyAtIndexes_withKey_method = + class_getInstanceMethod(theClass, replaceKeyAtIndexes_withKey_selector); + + if (replaceKeyAtIndexes_withKey_method) + { + var replaceKeyAtIndexes_withKey_method_imp = replaceKeyAtIndexes_withKey_method.method_imp; + + class_addMethod(KVOClass, replaceKeyAtIndexes_withKey_selector, function(self, _cmd, indexes, objects) + { + [self willChange:CPKeyValueChangeReplacement + valuesAtIndexes:[indexes copy] + forKey:aKey]; + + replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, indexes, objects); + + [self didChange:CPKeyValueChangeReplacement + valuesAtIndexes:[indexes copy] + forKey:aKey]; + }, replaceKeyAtIndexes_withKey_method.method_types); + } } - if (removeKeyAtIndexes_method) + // Unordered To-Many Relationships + var addKeyObject_selector = sel_getUid("add" + capitalizedKey + "Object:"), + addKeyObject_method = class_getInstanceMethod(theClass, addKeyObject_selector), + + addKey_selector = sel_getUid("add" + capitalizedKey + ":"), + addKey_method = class_getInstanceMethod(theClass, addKey_selector), + + removeKeyObject_selector = sel_getUid("remove" + capitalizedKey + "Object:"), + removeKeyObject_method = class_getInstanceMethod(theClass, removeKeyObject_selector), + + removeKey_selector = sel_getUid("remove" + capitalizedKey + ":"), + removeKey_method = class_getInstanceMethod(theClass, removeKey_selector); + + if ((addKeyObject_method || addKey_method) && (removeKeyObject_method || removeKey_method)) { - var removeKeyAtIndexes_method_imp = removeKeyAtIndexes_method.method_imp; - - class_addMethod(KVOClass, removeKeyAtIndexes_selector, function(self, _cmd, indexes) + if (addKeyObject_method) { - [self willChange:CPKeyValueChangeRemoval - valuesAtIndexes:[indexes copy] - forKey:aKey]; + var addKeyObject_method_imp = addKeyObject_method.method_imp; - removeKeyAtIndexes_method_imp(self, _cmd, indexes); + class_addMethod(KVOClass, addKeyObject_selector, function(self, _cmd, anObject) + { + [self willChangeValueForKey:aKey + withSetMutation:CPKeyValueUnionSetMutation + usingObjects:[CPSet setWithObject:anObject]]; - [self didChange:CPKeyValueChangeRemoval - valuesAtIndexes:[indexes copy] - forKey:aKey]; - }, removeKeyAtIndexes_method.method_types); - } + addKeyObject_method_imp(self, _cmd, anObject); - // These are optional. - var replaceObjectInKeyAtIndex_withObject_selector = - sel_getUid("replaceObjectIn" + capitalizedKey + "AtIndex:withObject:"), - replaceObjectInKeyAtIndex_withObject_method = - class_getInstanceMethod(theClass, replaceObjectInKeyAtIndex_withObject_selector); + [self didChangeValueForKey:aKey + withSetMutation:CPKeyValueUnionSetMutation + usingObjects:[CPSet setWithObject:anObject]]; + }, addKeyObject_method.method_types); + } - if (replaceObjectInKeyAtIndex_withObject_method) - { - var replaceObjectInKeyAtIndex_withObject_method_imp = - replaceObjectInKeyAtIndex_withObject_method.method_imp; - - class_addMethod(KVOClass, replaceObjectInKeyAtIndex_withObject_selector, - function(self, _cmd, anIndex, anObject) + if (addKey_method) { - [self willChange:CPKeyValueChangeReplacement - valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] - forKey:aKey]; + var addKey_method_imp = addKey_method.method_imp; - replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, anIndex, anObject); + class_addMethod(KVOClass, addKey_selector, function(self, _cmd, objects) + { + [self willChangeValueForKey:aKey + withSetMutation:CPKeyValueUnionSetMutation + usingObjects:[objects copy]]; - [self didChange:CPKeyValueChangeReplacement - valuesAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] - forKey:aKey]; - }, replaceObjectInKeyAtIndex_withObject_method.method_types); - } + addKey_method_imp(self, _cmd, objects); - var replaceKeyAtIndexes_withKey_selector = - sel_getUid("replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":"), - replaceKeyAtIndexes_withKey_method = - class_getInstanceMethod(theClass, replaceKeyAtIndexes_withKey_selector); + [self didChangeValueForKey:aKey + withSetMutation:CPKeyValueUnionSetMutation + usingObjects:[objects copy]]; + }, addKey_method.method_types); + } - if (replaceKeyAtIndexes_withKey_method) - { - var replaceKeyAtIndexes_withKey_method_imp = replaceKeyAtIndexes_withKey_method.method_imp; - - class_addMethod(KVOClass, replaceKeyAtIndexes_withKey_selector, function(self, _cmd, indexes, objects) + if (removeKeyObject_method) { - [self willChange:CPKeyValueChangeReplacement - valuesAtIndexes:[indexes copy] - forKey:aKey]; + var removeKeyObject_method_imp = removeKeyObject_method.method_imp; - replaceObjectInKeyAtIndex_withObject_method_imp(self, _cmd, indexes, objects); + class_addMethod(KVOClass, removeKeyObject_selector, function(self, _cmd, anObject) + { + [self willChangeValueForKey:aKey + withSetMutation:CPKeyValueMinusSetMutation + usingObjects:[CPSet setWithObject:anObject]]; - [self didChange:CPKeyValueChangeReplacement - valuesAtIndexes:[indexes copy] - forKey:aKey]; - }, replaceKeyAtIndexes_withKey_method.method_types); - } - } + removeKeyObject_method_imp(self, _cmd, anObject); - // Unordered To-Many Relationships - var addKeyObject_selector = sel_getUid("add" + capitalizedKey + "Object:"), - addKeyObject_method = class_getInstanceMethod(theClass, addKeyObject_selector), + [self didChangeValueForKey:aKey + withSetMutation:CPKeyValueMinusSetMutation + usingObjects:[CPSet setWithObject:anObject]]; + }, removeKeyObject_method.method_types); + } - addKey_selector = sel_getUid("add" + capitalizedKey + ":"), - addKey_method = class_getInstanceMethod(theClass, addKey_selector), - - removeKeyObject_selector = sel_getUid("remove" + capitalizedKey + "Object:"), - removeKeyObject_method = class_getInstanceMethod(theClass, removeKeyObject_selector), - - removeKey_selector = sel_getUid("remove" + capitalizedKey + ":"), - removeKey_method = class_getInstanceMethod(theClass, removeKey_selector); - - if ((addKeyObject_method || addKey_method) && (removeKeyObject_method || removeKey_method)) - { - if (addKeyObject_method) - { - var addKeyObject_method_imp = addKeyObject_method.method_imp; - - class_addMethod(KVOClass, addKeyObject_selector, function(self, _cmd, anObject) + if (removeKey_method) { - [self willChangeValueForKey:aKey - withSetMutation:CPKeyValueUnionSetMutation - usingObjects:[CPSet setWithObject:anObject]]; + var removeKey_method_imp = removeKey_method.method_imp; - addKeyObject_method_imp(self, _cmd, anObject); + class_addMethod(KVOClass, removeKey_selector, function(self, _cmd, objects) + { + [self willChangeValueForKey:aKey + withSetMutation:CPKeyValueMinusSetMutation + usingObjects:[objects copy]]; - [self didChangeValueForKey:aKey - withSetMutation:CPKeyValueUnionSetMutation - usingObjects:[CPSet setWithObject:anObject]]; - }, addKeyObject_method.method_types); - } + removeKey_method_imp(self, _cmd, objects); - if (addKey_method) - { - var addKey_method_imp = addKey_method.method_imp; + [self didChangeValueForKey:aKey + withSetMutation:CPKeyValueMinusSetMutation + usingObjects:[objects copy]]; + }, removeKey_method.method_types); + } - class_addMethod(KVOClass, addKey_selector, function(self, _cmd, objects) + // intersect: is optional. + var intersectKey_selector = sel_getUid("intersect" + capitalizedKey + ":"), + intersectKey_method = class_getInstanceMethod(theClass, intersectKey_selector); + + if (intersectKey_method) { - [self willChangeValueForKey:aKey - withSetMutation:CPKeyValueUnionSetMutation - usingObjects:[objects copy]]; + var intersectKey_method_imp = intersectKey_method.method_imp; - addKey_method_imp(self, _cmd, objects); + class_addMethod(KVOClass, intersectKey_selector, function(self, _cmd, aSet) + { + [self willChangeValueForKey:aKey + withSetMutation:CPKeyValueIntersectSetMutation + usingObjects:[aSet copy]]; - [self didChangeValueForKey:aKey - withSetMutation:CPKeyValueUnionSetMutation - usingObjects:[objects copy]]; - }, addKey_method.method_types); - } + intersectKey_method_imp(self, _cmd, aSet); - if (removeKeyObject_method) - { - var removeKeyObject_method_imp = removeKeyObject_method.method_imp; - - class_addMethod(KVOClass, removeKeyObject_selector, function(self, _cmd, anObject) - { - [self willChangeValueForKey:aKey - withSetMutation:CPKeyValueMinusSetMutation - usingObjects:[CPSet setWithObject:anObject]]; - - removeKeyObject_method_imp(self, _cmd, anObject); - - [self didChangeValueForKey:aKey - withSetMutation:CPKeyValueMinusSetMutation - usingObjects:[CPSet setWithObject:anObject]]; - }, removeKeyObject_method.method_types); - } - - if (removeKey_method) - { - var removeKey_method_imp = removeKey_method.method_imp; - - class_addMethod(KVOClass, removeKey_selector, function(self, _cmd, objects) - { - [self willChangeValueForKey:aKey - withSetMutation:CPKeyValueMinusSetMutation - usingObjects:[objects copy]]; - - removeKey_method_imp(self, _cmd, objects); - - [self didChangeValueForKey:aKey - withSetMutation:CPKeyValueMinusSetMutation - usingObjects:[objects copy]]; - }, removeKey_method.method_types); - } - - // intersect: is optional. - var intersectKey_selector = sel_getUid("intersect" + capitalizedKey + ":"), - intersectKey_method = class_getInstanceMethod(theClass, intersectKey_selector); - - if (intersectKey_method) - { - var intersectKey_method_imp = intersectKey_method.method_imp; - - class_addMethod(KVOClass, intersectKey_selector, function(self, _cmd, aSet) - { - [self willChangeValueForKey:aKey - withSetMutation:CPKeyValueIntersectSetMutation - usingObjects:[aSet copy]]; - - intersectKey_method_imp(self, _cmd, aSet); - - [self didChangeValueForKey:aKey - withSetMutation:CPKeyValueIntersectSetMutation - usingObjects:[aSet copy]]; - }, intersectKey_method.method_types); + [self didChangeValueForKey:aKey + withSetMutation:CPKeyValueIntersectSetMutation + usingObjects:[aSet copy]]; + }, intersectKey_method.method_types); + } } } @@ -1300,6 +1300,10 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti if (aKeyPath === _firstPart) { var pathChanges = [CPMutableDictionary dictionaryWithObject:CPKeyValueChangeSetting forKey:CPKeyValueChangeKindKey]; + var isBeforeFlag = !![changes objectForKey:CPKeyValueChangeNotificationIsPriorKey]; + + if (isBeforeFlag) + [pathChanges setObject:1 forKey:CPKeyValueChangeNotificationIsPriorKey]; if (_options & CPKeyValueObservingOptionOld) { @@ -1308,7 +1312,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti [pathChanges setObject:oldValue != null ? oldValue : [CPNull null] forKey:CPKeyValueChangeOldKey]; } - if (_options & CPKeyValueObservingOptionNew) + if (!isBeforeFlag && (_options & CPKeyValueObservingOptionNew)) { var newValue = [_object valueForKeyPath:_firstPart + "." + _secondPart]; @@ -1317,14 +1321,17 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti [_observer observeValueForKeyPath:_firstPart + "." + _secondPart ofObject:_object change:pathChanges context:_context]; - //since a has changed, we should remove ourselves as an observer of the old a, and observe the new one - if (_value) - [_value removeObserver:self forKeyPath:_secondPart]; + // Nothing has changed yet when doing willChange.... + if (!isBeforeFlag) { + //since a has changed, we should remove ourselves as an observer of the old a, and observe the new one + if (_value) + [_value removeObserver:self forKeyPath:_secondPart]; - _value = [_object valueForKey:_firstPart]; + _value = [_object valueForKey:_firstPart]; - if (_value) - [_value addObserver:self forKeyPath:_secondPart options:_options context:nil]; + if (_value) + [_value addObserver:self forKeyPath:_secondPart options:_options context:nil]; + } } else { diff --git a/Foundation/CPKeyedArchiver.j b/Foundation/CPKeyedArchiver.j index e4e0d7bbd..20bb7b4be 100644 --- a/Foundation/CPKeyedArchiver.j +++ b/Foundation/CPKeyedArchiver.j @@ -380,12 +380,10 @@ var _CPKeyedArchiverStringClass = Nil, /* @ignore */ - (void)_encodeArrayOfObjects:(CPArray)objects forKey:(CPString)aKey { - var i = 0, - count = objects.length, - references = []; - - for (; i < count; ++i) - [references addObject:_CPKeyedArchiverEncodeObject(self, objects[i], NO)]; + var references = [objects arrayByApplyingBlock:function(object) + { + return _CPKeyedArchiverEncodeObject(self, object, NO); + }]; [_plistObject setObject:references forKey:aKey]; } diff --git a/Foundation/CPKeyedUnarchiver.j b/Foundation/CPKeyedUnarchiver.j index 8fe7480d7..4f35f66ff 100644 --- a/Foundation/CPKeyedUnarchiver.j +++ b/Foundation/CPKeyedUnarchiver.j @@ -332,7 +332,7 @@ var CPArrayClass = Ni */ - (id)decodeObjectForKey:(CPString)aKey { - var object = _plistObject.valueForKey(aKey), + var object = _plistObject && _plistObject.valueForKey(aKey), objectClass = (object != nil) && object.isa; if (objectClass === CPDictionaryClass || objectClass === CPMutableDictionaryClass) diff --git a/Foundation/CPLocale.j b/Foundation/CPLocale.j index 219b5c690..c506ce299 100644 --- a/Foundation/CPLocale.j +++ b/Foundation/CPLocale.j @@ -61,9 +61,9 @@ CPLocaleLanguageDirectionRightToLeft = @"CPLocaleLanguageDirectionRightTo CPLocaleLanguageDirectionTopToBottom = @"CPLocaleLanguageDirectionTopToBottom"; CPLocaleLanguageDirectionBottomToTop = @"CPLocaleLanguageDirectionBottomToTop"; -var countryCodes = [@"DE", @"FR", @"ES", @"GB", @"US"], - languageCodes = [@"en", @"de", @"es", @"fr"], - availableLocaleIdentifiers = [@"de_DE", @"en_GB", @"en_US", @"es_ES", @"fr_FR"]; +var countryCodes = [@"DE", @"FR", @"ES", @"GB", @"US", @"SE"], + languageCodes = [@"en", @"de", @"es", @"fr", @"sv"], + availableLocaleIdentifiers = [@"de_DE", @"en_GB", @"en_US", @"es_ES", @"fr_FR", @"sv_SE"]; var sharedSystemLocale = nil, sharedCurrentLocale = nil; diff --git a/Foundation/CPNumber.j b/Foundation/CPNumber.j index 33bcb73ac..4aec997f5 100644 --- a/Foundation/CPNumber.j +++ b/Foundation/CPNumber.j @@ -25,6 +25,8 @@ @import "CPObject.j" @import "CPObjJRuntime.j" +#define CAST_TO_INT(x) ((x) >= 0 ? Math.floor((x)) : Math.ceil((x))) + var CPNumberUIDs = new CFMutableDictionary(); /*! @@ -239,6 +241,7 @@ FIXME: Do we need this? { if (typeof self == "boolean") return self ? 1 : 0; + return self; } @@ -246,35 +249,33 @@ FIXME: Do we need this? { if (typeof self == "boolean") return self ? 1 : 0; + return self; } - (int)intValue { - if (typeof self == "boolean") - return self ? 1 : 0; - return self; + return CAST_TO_INT(self); +} + +- (int)integerValue +{ + return CAST_TO_INT(self); } - (long long)longLongValue { - if (typeof self == "boolean") - return self ? 1 : 0; - return self; + return CAST_TO_INT(self); } - (long)longValue { - if (typeof self == "boolean") - return self ? 1 : 0; - return self; + return CAST_TO_INT(self); } - (short)shortValue { - if (typeof self == "boolean") - return self ? 1 : 0; - return self; + return CAST_TO_INT(self); } - (CPString)stringValue @@ -289,9 +290,8 @@ FIXME: Do we need this? - (unsigned int)unsignedIntValue { - if (typeof self == "boolean") - return self ? 1 : 0; - return self; + // Despite the name this method does not make a negative value positive in Objective-C, so neither does it here. + return CAST_TO_INT(self); } /* - (unsigned long long)unsignedLongLongValue @@ -302,16 +302,14 @@ FIXME: Do we need this? */ - (unsigned long)unsignedLongValue { - if (typeof self == "boolean") - return self ? 1 : 0; - return self; + // Despite the name this method does not make a negative value positive in Objective-C, so neither does it here. + return CAST_TO_INT(self); } - (unsigned short)unsignedShortValue { - if (typeof self == "boolean") - return self ? 1 : 0; - return self; + // Despite the name this method does not make a negative value positive in Objective-C, so neither does it here. + return CAST_TO_INT(self); } - (CPComparisonResult)compare:(CPNumber)aNumber diff --git a/Foundation/CPObject.j b/Foundation/CPObject.j index f5aea7aa6..311e67998 100644 --- a/Foundation/CPObject.j +++ b/Foundation/CPObject.j @@ -275,13 +275,13 @@ CPLog(@"Got some class: %@", inst); } /*! - Tests whether the receiver implements to the provided selector regardless of inheritance. + Tests if the receiver implements the provided selector regardless of inheritance. @param aSelector the selector for which to test the receiver @return \c YES if the receiver implements the selector */ - (BOOL)implementsSelector:(SEL)aSelector { - var methods = class_copyMethodList(isa), + var methods = class_copyMethodList([self class]), count = methods.length; while (count--) diff --git a/Foundation/CPPredicate/CPExpression.j b/Foundation/CPPredicate/CPExpression.j index 06ed6b0ba..10ca2b598 100644 --- a/Foundation/CPPredicate/CPExpression.j +++ b/Foundation/CPPredicate/CPExpression.j @@ -28,3 +28,5 @@ @import "_CPAggregateExpression.j" @import "_CPSetExpression.j" @import "_CPSubqueryExpression.j" +@import "_CPBlockExpression.j" +@import "_CPConditionalExpression.j" diff --git a/Foundation/CPPredicate/_CPAggregateExpression.j b/Foundation/CPPredicate/_CPAggregateExpression.j index 688166398..e141b1915 100644 --- a/Foundation/CPPredicate/_CPAggregateExpression.j +++ b/Foundation/CPPredicate/_CPAggregateExpression.j @@ -25,7 +25,7 @@ @implementation _CPAggregateExpression : CPExpression { - CPArray _aggregate; + CPArray _aggregate @accessors(getter=collection); } - (id)initWithAggregate:(CPArray)collection @@ -34,6 +34,7 @@ if (self) _aggregate = collection; + return self; } @@ -48,48 +49,30 @@ return YES; } -- (id)collection -{ - return _aggregate; -} - - (id)expressionValueWithObject:(id)object context:(CPDictionary)context { - var eval_array = [CPArray array], - collection = [_aggregate objectEnumerator], - exp; - - while ((exp = [collection nextObject]) !== nil) + return [_aggregate arrayByApplyingBlock:function(exp) { - var eval = [exp expressionValueWithObject:object context:context]; - [eval_array addObject:eval]; - } - - return eval_array; + return [exp expressionValueWithObject:object context:context]; + }]; } - (CPString)description -{ - var i = 0, - count = [_aggregate count], - result = "{"; +{ + var descriptions = [_aggregate arrayByApplyingBlock:function(exp) + { + return [exp description]; + }]; - for (; i < count; i++) - result = result + [CPString stringWithFormat:@"%s%s", [[_aggregate objectAtIndex:i] description], (i + 1 < count) ? @", " : @""]; - - result = result + "}"; - - return result; + return "{" + [descriptions componentsJoinedByString:","] + "}" ; } - (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables { - var subst_array = [CPArray array], - count = [_aggregate count], - i = 0; - - for (; i < count; i++) - [subst_array addObject:[[_aggregate objectAtIndex:i] _expressionWithSubstitutionVariables:variables]]; + var subst_array = [_aggregate arrayByApplyingBlock:function(exp) + { + return [exp _expressionWithSubstitutionVariables:variables]; + }]; return [CPExpression expressionForAggregate:subst_array]; } diff --git a/Foundation/CPPredicate/_CPBlockExpression.j b/Foundation/CPPredicate/_CPBlockExpression.j new file mode 100644 index 000000000..846c4f418 --- /dev/null +++ b/Foundation/CPPredicate/_CPBlockExpression.j @@ -0,0 +1,79 @@ +/* + * _CPBlockExpression.j + * + * Created by cacaodev. + * Copyright 2015. + * + * 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 + */ + +@import "_CPExpression.j" + +@implementation _CPBlockExpression : CPExpression +{ + Function _block @accessors(getter=expressionBlock); + CPArray _arguments @accessors(getter=arguments); +} + +- (id)initWithBlock:(Function)aBlock arguments:(CPArray)arguments +{ + self = [super initWithExpressionType:CPBlockExpressionType]; + + if (self) + { + _block = aBlock; + _arguments = arguments; + } + + return self; +} + +- (BOOL)isEqual:(id)object +{ + if (self === object) + return YES; + + if (object === nil || object.isa !== self.isa || [object expressionBlock] !== _block || ![[object arguments] isEqual:_arguments]) + return NO; + + return YES; +} + +- (id)expressionValueWithObject:(id)object context:(CPDictionary)context +{ + var args = [_arguments arrayByApplyingBlock:function(exp) + { + return [exp expressionValueWithObject:object context:context]; + }]; + + return _block(object, args, context); +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)bindings +{ + var args = [_arguments arrayByApplyingBlock:function(exp) + { + return [exp _expressionWithSubstitutionVariables:bindings]; + }]; + + return [[_CPBlockExpression alloc] initWithBlock:_block arguments:args]; +} + +- (CPString)description +{ + return [CPString stringWithFormat:@"Block(function, %@)", [_arguments description]]; +} + +@end \ No newline at end of file diff --git a/Foundation/CPPredicate/_CPConditionalExpression.j b/Foundation/CPPredicate/_CPConditionalExpression.j new file mode 100644 index 000000000..8fc544580 --- /dev/null +++ b/Foundation/CPPredicate/_CPConditionalExpression.j @@ -0,0 +1,78 @@ +/* + * _CPConditionalExpression.j + * + * Created by cacaodev. + * Copyright 2015. + * + * 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 + */ +@import "_CPPredicate.j" +@import "_CPExpression.j" + +@implementation _CPConditionalExpression : CPExpression +{ + CPPredicate _predicate @accessors(getter=predicate); + CPExpression _trueExpression @accessors(getter=trueExpression); + CPExpression _falseExpression @accessors(getter=falseExpression); +} + +- (id)initWithPredicate:(CPPredicate)aPredicate trueExpression:(CPExpression)trueExpression falseExpression:(CPExpression)falseExpression +{ + self = [super initWithExpressionType:CPConditionalExpressionType]; + + if (self) + { + _predicate = aPredicate; + _trueExpression = trueExpression; + _falseExpression = falseExpression; + } + + return self; +} + +- (BOOL)isEqual:(id)object +{ + if (self === object) + return YES; + + if (object === nil || object.isa !== self.isa || ![[object predicate] isEqual:_predicate] || ![[object trueExpression] isEqual:_trueExpression] || ![[object falseExpression] isEqual:_falseExpression]) + return NO; + + return YES; +} + +- (id)expressionValueWithObject:(id)object context:(CPDictionary)context +{ + var eval = [_predicate evaluateWithObject:object substitutionVariables:context], + exp = eval ? _trueExpression : _falseExpression; + + return [exp expressionValueWithObject:object context:context]; +} + +- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)bindings +{ + var predicate = [_predicate predicateWithSubstitutionVariables:bindings], + trueExp = [_trueExpression _expressionWithSubstitutionVariables:bindings], + falseExp = [_falseExpression _expressionWithSubstitutionVariables:bindings]; + + return [[_CPConditionalExpression alloc] initWithPredicate:predicate trueExpression:trueExp falseExpression:falseExp]; +} + +- (CPString)description +{ + return [CPString stringWithFormat:@"TERNARY(%@,%@,%@)", [_predicate predicateFormat], [_trueExpression description], [_falseExpression description]]; +} + +@end \ No newline at end of file diff --git a/Foundation/CPPredicate/_CPConstantValueExpression.j b/Foundation/CPPredicate/_CPConstantValueExpression.j index 397ce6371..1c1bbbce9 100644 --- a/Foundation/CPPredicate/_CPConstantValueExpression.j +++ b/Foundation/CPPredicate/_CPConstantValueExpression.j @@ -27,7 +27,7 @@ @implementation _CPConstantValueExpression : CPExpression { - id _value; + id _value @accessors(getter=constantValue); } - (id)initWithValue:(id)value @@ -51,11 +51,6 @@ return YES; } -- (id)constantValue -{ - return _value; -} - - (id)expressionValueWithObject:(id)object context:(CPDictionary)context { return _value; diff --git a/Foundation/CPPredicate/_CPExpression.j b/Foundation/CPPredicate/_CPExpression.j index 5a9561cb7..8a671c5b2 100644 --- a/Foundation/CPPredicate/_CPExpression.j +++ b/Foundation/CPPredicate/_CPExpression.j @@ -65,6 +65,14 @@ CPIntersectSetExpressionType = 8; An expression that combines two nested expression results by set subtraction. */ CPMinusSetExpressionType = 9; +/*! + An expression that returns the result of evaluating a block. +*/ +CPBlockExpressionType = 10; +/*! + An expression that returns an expression that depends on the evaluation of a predicate. +*/ +CPConditionalExpressionType = 11; /*! @ingroup foundation @@ -259,6 +267,32 @@ CPMinusSetExpressionType = 9; return [[_CPSubqueryExpression alloc] initWithExpression:expression usingIteratorVariable:variable predicate:predicate]; } +/*! + Returns Creates an NSExpression object that will use the Block for evaluating objects. + @param aBlock The Block is applied to the object to be evaluated. + +The Block takes three arguments and returns a value: + +evaluatedObject +The object to be evaluated. +expressions +An array of predicate expressions that evaluates to a collection. +context +A dictionary that the expression can use to store temporary state for one predicate evaluation. + +@discussion Note that context is mutable, and that it can only be accessed during the evaluation of the expression. +@param arguments An array containing NSExpression objects that will be used as parameters during the invocation of the block. +*/ ++ (CPExpression)expressionForBlock:(Function)aBlock arguments:(CPArray)args +{ + return [[_CPBlockExpression alloc] initWithBlock:aBlock arguments:args]; +} + ++ (CPExpression)expressionForConditional:(CPPredicate)aPredicate trueExpression:(CPExpression)trueExpression falseExpression:(CPExpression)falseExpression +{ + return [[_CPConditionalExpression alloc] initWithPredicate:aPredicate trueExpression:trueExpression falseExpression:falseExpression]; +} + // Getting Information About an Expression /*! Returns the expression type for the receiver. @@ -316,7 +350,7 @@ CPMinusSetExpressionType = 9; /*! Returns the arguments for the receiver. - @return The arguments for the receiver—that is, the array of expressions that will be passed as parameters during invocation of the selector on the operand of a function expression. + @return The arguments for the receiver—that is, the array of expressions that will be passed as parameters during invocation of the selector on the operand of a function expression or as a parameter of the block of a block expression. This method raises an exception if it is not applicable to the receiver. */ - (CPArray)arguments @@ -337,8 +371,8 @@ CPMinusSetExpressionType = 9; } /*! - Returns the predicate in a subquery expression. - @return The predicate in a subquery expression.. + Returns the predicate in a subquery expression or a conditional expression. + @return The predicate in a subquery expression or a conditional expression. This method raises an exception if it is not applicable to the receiver. */ - (CPPredicate)predicate @@ -380,6 +414,39 @@ CPMinusSetExpressionType = 9; return nil; } +/*! + Returns the block of a block expression. + @return The block of a block expression. + This method raises an exception if it is not applicable to the receiver. +*/ +- (Function)expressionBlock +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + return nil; +} + +/*! + Returns the true expression of a conditional expression. + @return The true expression of a conditional expression. + This method raises an exception if it is not applicable to the receiver. +*/ +- (CPExpression)trueExpression +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + return nil; +} + +/*! + Returns the false expression of a conditional expression. + @return The false expression of a conditional expression. + This method raises an exception if it is not applicable to the receiver. +*/ +- (CPExpression)falseExpression +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + return nil; +} + - (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables { return self; diff --git a/Foundation/CPPredicate/_CPFunctionExpression.j b/Foundation/CPPredicate/_CPFunctionExpression.j index 394c93612..7de9f0d73 100644 --- a/Foundation/CPPredicate/_CPFunctionExpression.j +++ b/Foundation/CPPredicate/_CPFunctionExpression.j @@ -28,9 +28,9 @@ @implementation _CPFunctionExpression : CPExpression { - CPExpression _operand; + CPExpression _operand @accessors(getter=operand); SEL _selector; - CPArray _arguments; + CPArray _arguments @accessors(getter=arguments); int _argc; int _maxargs; } @@ -88,16 +88,6 @@ return [self _function]; } -- (CPArray)arguments -{ - return _arguments; -} - -- (CPExpression)operand -{ - return _operand; -} - - (id)expressionValueWithObject:(id)object context:(CPDictionary)context { var target = [_operand expressionValueWithObject:object context:context], @@ -143,11 +133,10 @@ - (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables { var operand = [[self operand] _expressionWithSubstitutionVariables:variables], - args = [CPArray array], - i = 0; - - for (; i < _argc; i++) - [args addObject:[_arguments[i] _expressionWithSubstitutionVariables:variables]]; + args = [_arguments arrayByApplyingBlock:function(arg) + { + return [arg _expressionWithSubstitutionVariables:variables]; + }]; return [CPExpression expressionForFunction:operand selectorName:[self _function] arguments:args]; } diff --git a/Foundation/CPPredicate/_CPPredicate.j b/Foundation/CPPredicate/_CPPredicate.j index 56bfcd643..34001c415 100644 --- a/Foundation/CPPredicate/_CPPredicate.j +++ b/Foundation/CPPredicate/_CPPredicate.j @@ -744,16 +744,43 @@ if (![self scanString:@"," intoString:NULL]) CPRaiseParseError(self, @"expression"); + variableExpression = [self parseExpression]; if (![self scanString:@"," intoString:NULL]) CPRaiseParseError(self, @"expression"); + subpredicate = [self parsePredicate]; + if (![self scanString:@")" intoString:NULL]) + CPRaiseParseError(self, @"predicate"); + + return [[_CPSubqueryExpression alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; + } + + if ([self scanString:@"TERNARY" intoString:NULL]) + { + if (![self scanString:@"(" intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + var predicate = [self parsePredicate], + trueExpression, + falseExpression; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"predicate"); + + trueExpression = [self parseExpression]; + + if (![self scanString:@"," intoString:NULL]) + CPRaiseParseError(self, @"expression"); + + falseExpression = [self parseExpression]; + if (![self scanString:@")" intoString:NULL]) CPRaiseParseError(self, @"expression"); - return [[_CPSubqueryExpression alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate]; + return [CPExpression expressionForConditional:predicate trueExpression:trueExpression falseExpression:falseExpression]; } if ([self scanString:@"FUNCTION" intoString:NULL]) diff --git a/Foundation/CPPredicate/_CPSetExpression.j b/Foundation/CPPredicate/_CPSetExpression.j index a0e9df3bb..707486b32 100644 --- a/Foundation/CPPredicate/_CPSetExpression.j +++ b/Foundation/CPPredicate/_CPSetExpression.j @@ -25,8 +25,8 @@ @implementation _CPSetExpression : CPExpression { - CPExpression _left; - CPExpression _right; + CPExpression _left @accessors(getter=leftExpression); + CPExpression _right @accessors(getter=rightExpression); } - (id)initWithType:(int)type left:(CPExpression)left right:(CPExpression)right @@ -88,16 +88,6 @@ return self; } -- (CPExpression)leftExpression -{ - return _left; -} - -- (CPExpression)rightExpression -{ - return _right; -} - - (CPString)description { var desc; diff --git a/Foundation/CPPredicate/_CPSubqueryExpression.j b/Foundation/CPPredicate/_CPSubqueryExpression.j index e8f648403..d69483d2c 100644 --- a/Foundation/CPPredicate/_CPSubqueryExpression.j +++ b/Foundation/CPPredicate/_CPSubqueryExpression.j @@ -26,9 +26,9 @@ @implementation _CPSubqueryExpression : CPExpression { - CPExpression _collection; + CPExpression _collection @accessors(getter=collection); CPExpression _variableExpression; - CPPredicate _subpredicate; + CPPredicate _subpredicate @accessors(getter=predicate); } - (id)initWithExpression:(CPExpression)collection usingIteratorVariable:(CPString)variable predicate:(CPPredicate)subpredicate @@ -81,21 +81,11 @@ return YES; } -- (CPExpression)collection -{ - return _collection; -} - - (id)copy { return [[_CPSubqueryExpression alloc] initWithExpression:[_collection copy] usingIteratorExpression:[_variableExpression copy] predicate:[_subpredicate copy]]; } -- (CPPredicate)predicate -{ - return _subpredicate; -} - - (CPString)description { return [self predicateFormat]; diff --git a/Foundation/CPPredicate/_CPVariableExpression.j b/Foundation/CPPredicate/_CPVariableExpression.j index 910ef23d5..a33911222 100644 --- a/Foundation/CPPredicate/_CPVariableExpression.j +++ b/Foundation/CPPredicate/_CPVariableExpression.j @@ -29,7 +29,7 @@ @implementation _CPVariableExpression : CPExpression { - CPString _variable; + CPString _variable @accessors(getter=variable); } - (id)initWithVariable:(CPString)variable @@ -54,11 +54,6 @@ return YES; } -- (CPString)variable -{ - return _variable; -} - - (id)expressionValueWithObject:object context:(CPDictionary)context { var expression = [self _expressionWithSubstitutionVariables:context]; diff --git a/Foundation/CPRunLoop.j b/Foundation/CPRunLoop.j index da7906ee2..a745040d7 100644 --- a/Foundation/CPRunLoop.j +++ b/Foundation/CPRunLoop.j @@ -200,6 +200,7 @@ var CPRunLoopLastNativeRunLoop = 0; CPArray _orderedPerforms; int _runLoopInsuranceTimer; + CPArray _observers; } /* @@ -224,6 +225,7 @@ var CPRunLoopLastNativeRunLoop = 0; _timersForModes = {}; _nativeTimersForModes = {}; _nextTimerFireDatesForModes = {}; + _observers = nil; } return self; @@ -473,6 +475,19 @@ var CPRunLoopLastNativeRunLoop = 0; else _orderedPerforms = performs; + if (_observers) + { + var count = _observers.length; + while(count--) + { + var obs = _observers[count]; + obs.callout(); + + if (!obs.repeats) + _observers.splice(count, 1); + } + } + _runLoopLock = NO; return nextFireDate; diff --git a/Foundation/CPString.j b/Foundation/CPString.j index 69e9caf8a..d87c4efd8 100644 --- a/Foundation/CPString.j +++ b/Foundation/CPString.j @@ -731,6 +731,14 @@ var CPStringNull = [CPNull null]; return parseInt(self, 10); } +/*! + Returns the text as an integer +*/ +- (int)integerValue +{ + return parseInt(self, 10); +} + /*! Returns an the path components of this string. This method assumes that the string's content is a '/' diff --git a/Foundation/CPURLRequest.j b/Foundation/CPURLRequest.j index 9c0ad1b46..36e02144f 100644 --- a/Foundation/CPURLRequest.j +++ b/Foundation/CPURLRequest.j @@ -97,6 +97,8 @@ CPURLRequestReturnCacheDataDontLoad = 3; { _cachePolicy = aCachePolicy; _timeoutInterval = aTimeoutInterval; + + [self _updateCacheControlHeader]; } return self; @@ -122,31 +124,8 @@ CPURLRequestReturnCacheDataDontLoad = 3; _cachePolicy = CPURLRequestUseProtocolCachePolicy; [self setValue:"Thu, 01 Jan 1970 00:00:00 GMT" forHTTPHeaderField:"If-Modified-Since"]; - - switch (_cachePolicy) - { - case CPURLRequestUseProtocolCachePolicy: - // TODO: implement everything about cache... - [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; - break; - - case CPURLRequestReturnCacheDataElseLoad: - [self setValue:"max-stale=31536000" forHTTPHeaderField:"Cache-Control"]; - break; - - case CPURLRequestReturnCacheDataDontLoad: - [self setValue:"only-if-cached" forHTTPHeaderField:"Cache-Control"]; - break; - - case CPURLRequestReloadIgnoringLocalCacheData: - [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; - break; - - default: - [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; - } - [self setValue:"XMLHttpRequest" forHTTPHeaderField:"X-Requested-With"]; + [self _updateCacheControlHeader]; } return self; @@ -181,6 +160,35 @@ CPURLRequestReturnCacheDataDontLoad = 3; [_HTTPHeaderFields setObject:aValue forKey:aField]; } +/* + @ignore +*/ +- (void)_updateCacheControlHeader +{ + switch (_cachePolicy) + { + case CPURLRequestUseProtocolCachePolicy: + // TODO: implement everything about cache... + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReturnCacheDataElseLoad: + [self setValue:"max-stale=31536000" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReturnCacheDataDontLoad: + [self setValue:"only-if-cached" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReloadIgnoringLocalCacheData: + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + break; + + default: + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + } +} + @end /* diff --git a/Objective-J/Bootstrap.js b/Objective-J/Bootstrap.js index ddd7263bf..2810c76fd 100644 --- a/Objective-J/Bootstrap.js +++ b/Objective-J/Bootstrap.js @@ -49,17 +49,26 @@ if (DOMBaseElementsCount > 0) if (typeof OBJJ_COMPILER_FLAGS !== 'undefined') { - var flags = 0; + var flags = {}; for (var i = 0; i < OBJJ_COMPILER_FLAGS.length; i++) { - var flag = ObjJAcornCompiler.Flags[OBJJ_COMPILER_FLAGS[i]]; - - if (flag != null) + switch (OBJJ_COMPILER_FLAGS[i]) { - flags |= flag; + case "IncludeDebugSymbols": + flags.includeMethodFunctionNames = true; + break; + + case "IncludeTypeSignatures": + flags.includeIvarTypeSignatures = true; + flags.includeMethodArgumentTypeSignatures = true; + break; + + case "InlineMsgSend": + flags.inlineMsgSendFunctions = true; + break; } } - exports.setCurrentCompilerFlags(flags); + FileExecutable.setCurrentCompilerFlags(flags); } // Turn the main file into a URL. diff --git a/Objective-J/CFHTTPRequest.js b/Objective-J/CFHTTPRequest.js index 2c112e749..d07064696 100755 --- a/Objective-J/CFHTTPRequest.js +++ b/Objective-J/CFHTTPRequest.js @@ -371,7 +371,7 @@ function FileRequest(/*CFURL*/ aURL, onsuccess, onfailure, onprogress) { var aFilePath = aURL.toString().substring(5), OS = require("os"), - gccFlags = require("objective-j").currentGccCompilerFlags(), + gccFlags = require("objective-j").FileExecutable.currentGccCompilerFlags(), chunk, fileContents = ""; diff --git a/Objective-J/CommonJS/lib/objective-j.js b/Objective-J/CommonJS/lib/objective-j.js index 1d74d0f10..4d7feb707 100644 --- a/Objective-J/CommonJS/lib/objective-j.js +++ b/Objective-J/CommonJS/lib/objective-j.js @@ -1,6 +1,7 @@ var FILE = require("file"); var sprintf = require("printf").sprintf; +var OS = require("os"); var window = exports.window = require("browser/window"); @@ -80,6 +81,7 @@ exports.run = function(args) // copy the args since we're going to modify them var argv = args.slice(1); + var outputFormatInXML = false; if (argv[0] === "--version" || argv[0] === "-v") { @@ -120,25 +122,33 @@ exports.run = function(args) case "-x": case "--xml": argv.shift(); - exports.outputFormatInXML = true; + exports.messageOutputFormatInXML = true; + outputFormatInXML = true; break; case "-g": case "--include-debug-symbols": argv.shift(); - (OBJJ_COMPILER_FLAGS || (OBJJ_COMPILER_FLAGS = [])).push("IncludeDebugSymbols"); + var flags = ObjectiveJ.FileExecutable.currentCompilerFlags(); + flags.includeMethodFunctionNames = true; + ObjectiveJ.FileExecutable.setCurrentCompilerFlags(flags); break; case "-T": case "--dont-include-type-signatures": argv.shift(); - (OBJJ_COMPILER_FLAGS || (OBJJ_COMPILER_FLAGS = [])).push("IncludeTypeSignatures"); + var flags = ObjectiveJ.FileExecutable.currentCompilerFlags(); + flags.includeIvarTypeSignatures = true; + flags.includeMethodArgumentTypeSignatures = true; + ObjectiveJ.FileExecutable.setCurrentCompilerFlags(flags); break; case "-O2": case "--inline-msg-send": argv.shift(); - (OBJJ_COMPILER_FLAGS || (OBJJ_COMPILER_FLAGS = [])).push("InlineMsgSend"); + var flags = ObjectiveJ.FileExecutable.currentCompilerFlags(); + flags.inlineMsgSendFunctions = true; + ObjectiveJ.FileExecutable.setCurrentCompilerFlags(flags); break; } } @@ -160,19 +170,7 @@ exports.run = function(args) } catch(e) { - if (exports.outputFormatInXML) - { - var dict = new CFMutableDictionary(); - dict.addValueForKey('line', e.line ? e.line : 0); - dict.addValueForKey('sourcePath', e.path ? e.path : mainFilePath); - dict.addValueForKey('message', e.message); - - errors.push(dict); - } - else - { - errors.push("\n" + e); - } + errors.push(e); } if (typeof main === "function") @@ -194,10 +192,8 @@ exports.run = function(args) if (errors.length) { - if (exports.outputFormatInXML) - throw CFPropertyListCreateXMLData(errors, kCFPropertyListXMLFormat_v1_0).rawString(); - else - throw errors; + // Make sure we get exit will failiure + OS.exit(1); } } else @@ -271,7 +267,6 @@ function getPackage() { exports.version = function() { return getPackage()["version"]; } exports.revision = function() { return getPackage()["cappuccino-revision"]; } exports.timestamp = function() { return new Date(getPackage()["cappuccino-timestamp"]); } -exports.outputFormatInXML = false; exports.fullVersionString = function() { return sprintf("objective-j %s (%04d-%02d-%02d %s)", diff --git a/Objective-J/CommonJS/lib/objective-j/compiler.js b/Objective-J/CommonJS/lib/objective-j/compiler.js index 6f761e3a5..681c00116 100644 --- a/Objective-J/CommonJS/lib/objective-j/compiler.js +++ b/Objective-J/CommonJS/lib/objective-j/compiler.js @@ -6,10 +6,6 @@ var FILE = require("file"), require("objective-j/rhino/regexp-rhino-patch"); -ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess = 1 << 10; -ObjectiveJ.ObjJAcornCompiler.Flags.Compress = 1 << 11; -ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax = 1 << 12; - var compressors = { ss : { id : "minify/shrinksafe" } //,yui : { id : "minify/yuicompressor" } @@ -37,9 +33,8 @@ function compressor(code) function compileWithResolvedFlags(aFilePath, objjcFlags, gccFlags, asPlainJavascript) { - var shouldObjjPreprocess = objjcFlags & ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess, - shouldCheckSyntax = objjcFlags & ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax, - shouldCompress = objjcFlags & ObjectiveJ.ObjJAcornCompiler.Flags.Compress, + var shouldObjjPreprocess = true, + shouldCompress = objjcFlags.compress, fileContents = "", executable, code; @@ -118,7 +113,7 @@ function compileWithResolvedFlags(aFilePath, objjcFlags, gccFlags, asPlainJavasc } } - ObjectiveJ.setCurrentCompilerFlags(objjcFlags); + ObjectiveJ.FileExecutable.setCurrentCompilerFlags(objjcFlags); ObjectiveJ.make_narwhal_factory(absolutePath, basePath, translateFilenameToPath)(require, {}, module, system, print); executable = new ObjectiveJ.FileExecutable(FILE.basename(aFilePath)); @@ -153,7 +148,7 @@ function resolveFlags(args) count = args.length, gccFlags = [], - objjcFlags = ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess | ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax; + objjcFlags = {}; for (; index < count; ++index) { @@ -180,27 +175,23 @@ function resolveFlags(args) } } - else if (argument.indexOf("-E") === 0) - objjcFlags &= ~ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess; - - else if (argument.indexOf("-S") === 0) - objjcFlags &= ~ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax; - else if (argument.indexOf("-T") === 0) - objjcFlags &= ~ObjectiveJ.ObjJAcornCompiler.Flags.IncludeTypeSignatures; - + { + objjcFlags.includeIvarTypeSignatures = false; + objjcFlags.includeMethodArgumentTypeSignatures = false; + } else if (argument.indexOf("-g") === 0) - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.IncludeDebugSymbols; + objjcFlags.includeMethodFunctionNames = true; else if (argument.indexOf("-O") === 0) { - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.Compress; + objjcFlags.compress = true; // FIXME: currently we are sending in '-O2' when we want InlineMsgSend. Here we only check if we it is '-O...' if (argument.length > 2) - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.InlineMsgSend; + objjcFlags.inlineMsgSendFunctions = true; } else if (argument.indexOf("-G") === 0) - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.Generate; + objjcFlags.generate = true; else filePaths.push(argument); @@ -223,7 +214,7 @@ exports.main = function(args) { var shouldPrintOutput = false, asPlainJavascript = false, - objjcFlags = 0; + objjcFlags = {}; var argv = args.slice(1); @@ -251,7 +242,8 @@ exports.main = function(args) if (argv[0] === "-T" || argv[0] === "--includeTypeSignatures") { - objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.IncludeTypeSignatures; + objjcFlags.includeIvarTypeSignatures = true; + objjcFlags.includeMethodArgumentTypeSignatures = true; argv.shift(); continue; } @@ -280,9 +272,19 @@ exports.main = function(args) var resolved = resolveFlags(argv), outputFilePaths = resolved.outputFilePaths, - gccFlags = resolved.gccFlags; + gccFlags = resolved.gccFlags, + resolvedObjjcFlags = resolved.objjcFlags; + + // Merge resolved keys into objjcFlags + for (var key in resolvedObjjcFlags) + { + if (resolvedObjjcFlags.hasOwnProperty(key)) + { + if (resolvedObjjcFlags[key]) + objjcFlags[key] = resolvedObjjcFlags[key]; + } + } - objjcFlags |= resolved.objjcFlags; resolved.filePaths.forEach(function(filePath, index) { if (!shouldPrintOutput) diff --git a/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js b/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js index 613d9b254..136a8388e 100644 --- a/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js +++ b/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js @@ -907,7 +907,7 @@ BundleTask.prototype.defineSourceTasks = function() basePath = absolutePath.substring(0, absolutePath.length - theTranslatedFilename.length); // Here we set the current compiler flags so the load system will know what compiler flags to use - ObjectiveJ.setCurrentGccCompilerFlags(environmentCompilerFlags); + ObjectiveJ.FileExecutable.setCurrentGccCompilerFlags(environmentCompilerFlags); // Here we tell the CFBundle to load frameworks for the current build enviroment and not the enviroment that is running CFBundle.environments = function() {return [anEnvironment.name(), "ObjJ"]}; ObjectiveJ.make_narwhal_factory(absolutePath, basePath, translateFilenameToPath)(require, e, module, system, print); diff --git a/Objective-J/Executable.js b/Objective-J/Executable.js index 95ea2b4fc..8da88e457 100644 --- a/Objective-J/Executable.js +++ b/Objective-J/Executable.js @@ -169,6 +169,10 @@ Executable.prototype.execute = function() this._compiler.popImport(); this.setCode(this._compiler.compilePass2()); + + if (FileExecutable.printWarningsAndErrors(this._compiler, exports.messageOutputFormatInXML)) + throw "Compilation error"; + this._compiler = null; } @@ -209,16 +213,16 @@ Executable.prototype.setCode = function(code) { #endif #if DEBUG - // "//@ sourceURL=" at the end lets us name our eval'd files for debuggers, etc. + // "//# sourceURL=" at the end lets us name our eval'd files for debuggers, etc. // * WebKit: http://pmuellr.blogspot.com/2009/06/debugger-friendly.html // * Firebug: http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/ //if (YES) { var absoluteString = this.URL().absoluteString(); - code += "/**/\n//@ sourceURL=" + absoluteString; + code += "/**/\n//# sourceURL=" + absoluteString; //} else { // // Firebug only does it for "eval()", not "new Function()". Ugh. Slower. - // var functionText = "(function(){"+GET_CODE(aFragment)+"/**/\n})\n//@ sourceURL="+GET_FILE(aFragment).path; + // var functionText = "(function(){"+GET_CODE(aFragment)+"/**/\n})\n//# sourceURL="+GET_FILE(aFragment).path; // compiled = eval(functionText); //} #endif @@ -499,7 +503,7 @@ Executable.fileExecutableSearcherForURL = function(/*CFURL*/ referenceURL) { if (!aStaticResource) { - var compilingFileUrl = ObjJAcornCompiler ? ObjJAcornCompiler.currentCompileFile : null; + var compilingFileUrl = exports.ObjJCompiler ? exports.ObjJCompiler.currentCompileFile : null; throw new Error("Could not load file at " + aURL + (compilingFileUrl ? " when compiling " + compilingFileUrl : "")); } diff --git a/Objective-J/FileExecutable.js b/Objective-J/FileExecutable.js index a3b471a7e..d6e72e655 100644 --- a/Objective-J/FileExecutable.js +++ b/Objective-J/FileExecutable.js @@ -22,6 +22,9 @@ var FileExecutablesForURLStrings = { }; +var currentCompilerFlags = {}; +var currentGccCompilerFlags = ""; + function FileExecutable(/*CFURL|String*/ aURL, /*Dictionary*/ aFilenameTranslateDictionary) { aURL = makeAbsoluteURL(aURL); @@ -41,7 +44,17 @@ function FileExecutable(/*CFURL|String*/ aURL, /*Dictionary*/ aFilenameTranslate if (fileContents.match(/^@STATIC;/)) executable = decompile(fileContents, aURL); else if ((extension === "j" || !extension) && !fileContents.match(/^{/)) - executable = exports.ObjJAcornCompiler.compileFileDependencies(fileContents, aURL, exports.currentCompilerFlags()); + { + var compiler = exports.ObjJCompiler.compileFileDependencies(fileContents, aURL, currentCompilerFlags || {}); + + if (FileExecutable.printWarningsAndErrors(compiler, exports.messageOutputFormatInXML)) + throw "Compilation error"; + + var fileDependencies = compiler.dependencies.map(function (aFileDep) { + return new FileDependency(new CFURL(aFileDep.url), aFileDep.isLocal); + }); + executable = new Executable(compiler.jsBuffer ? compiler.jsBuffer.toString() : null, fileDependencies, compiler.URL, null, compiler); + } else executable = new Executable(fileContents, [], aURL); @@ -139,3 +152,110 @@ FileExecutable._lookupCachedFunction = function(/*CFURL|String*/ aURL) aURL = typeof aURL === "string" ? aURL : aURL.absoluteString(); return FunctionCache[aURL]; } + +FileExecutable.setCurrentGccCompilerFlags = function(/*String*/ compilerFlags) +{ + if (currentGccCompilerFlags === compilerFlags) return; + + currentGccCompilerFlags = compilerFlags; + + var args = compilerFlags.split(" "), + count = args.length, + objjcFlags = {}; + + for (var index = 0; index < count; ++index) + { + var argument = args[index]; + + if (argument.indexOf("-g") === 0) + objjcFlags.includeMethodFunctionNames = true; + else if (argument.indexOf("-O") === 0) { + objjcFlags.inlineMsgSendFunctions = true; + // FIXME: currently we are sending in '-O2' when we want InlineMsgSend. Here we only check if it is '-O...'. + // Maybe we should have some other option for this + if (argument.length > 2) + objjcFlags.inlineMsgSendFunctions = true; + } + //else if (argument.indexOf("-G") === 0) + //objjcFlags |= ObjJAcornCompiler.Flags.Generate; + else if (argument.indexOf("-T") === 0) { + objjcFlags.includeIvarTypeSignatures = false; + objjcFlags.includeMethodArgumentTypeSignatures = false; + } + } + + FileExecutable.setCurrentCompilerFlags(objjcFlags); +} + +FileExecutable.currentGccCompilerFlags = function(/*String*/ compilerFlags) +{ + return currentGccCompilerFlags; +} + +FileExecutable.setCurrentCompilerFlags = function(/*JSObject*/ compilerFlags) +{ + currentCompilerFlags = compilerFlags; + // Here we set the default flags if they are not included. We do this as the default values + // in the compiler might not be what we want. + if (currentCompilerFlags.transformNamedFunctionDeclarationToAssignment == null) + currentCompilerFlags.transformNamedFunctionDeclarationToAssignment = true; + if (currentCompilerFlags.sourceMap == null) + currentCompilerFlags.sourceMap = false; + if (currentCompilerFlags.inlineMsgSendFunctions == null) + currentCompilerFlags.inlineMsgSendFunctions = false; +} + +FileExecutable.currentCompilerFlags = function(/*JSObject*/ compilerFlags) +{ + return currentCompilerFlags; +} + +/*! + This funtion prints all errors and warnings for the provieded compiler. It returns true if there + are any errors in the list. it will print it in xml format if printXML is 'true' + */ +FileExecutable.printWarningsAndErrors = function(/*ObjJCompiler*/ compiler, /*BOOL*/ printXML) +{ + var warnings = [], + anyErrors = false; + + for (var i = 0; i < compiler.warningsAndErrors.length; i++) + { + var warning = compiler.warningsAndErrors[i], + message = compiler.prettifyMessage(warning); + + // Set anyErrors to 'true' if there are any errors in the list + anyErrors = anyErrors || warning.messageType === "ERROR"; +#ifdef BROWSER + console.log(message); +#else + if (printXML) + { + var dict = new CFMutableDictionary(); + if (warning.messageOnLine != null) dict.addValueForKey('line', warning.messageOnLine) + if (warning.path != null) dict.addValueForKey('sourcePath', new CFURL(warning.path).path()) + if (message != null) dict.addValueForKey('message', message) + + warnings.push(dict); + } + else + { + print(message); + } +#endif + } + +#ifndef BROWSER + if (warnings.length && printXML) + try { + print(CFPropertyListCreateXMLData(warnings, kCFPropertyListXMLFormat_v1_0).rawString()); + } catch (e) { + print ("XML encode error: " + e); + } +#endif + + return anyErrors; +} + +// Set the compiler flags to empty dictionary so the default values are correct. +FileExecutable.setCurrentCompilerFlags({}); diff --git a/Objective-J/Jakefile b/Objective-J/Jakefile index 2c83a5562..5c5ef03ca 100644 --- a/Objective-J/Jakefile +++ b/Objective-J/Jakefile @@ -32,7 +32,7 @@ $BROWSER_FILE = FILE.join("Browser", "Objective-J.js"); $BUILD_OBJECTIVE_J = FILE.join($BUILD_CONFIGURATION_DIR, "Objective-J"); $BUILD_BROWSER_FILE = FILE.join($BUILD_OBJECTIVE_J, "Objective-J.js"); -$INCLUDE_FLAGS = ["-I" + FILE.cwd()]; +$INCLUDE_FLAGS = ["'-I" + FILE.cwd() + "'"]; $DEBUG_FLAGS = $CONFIGURATION === "Debug" ? ["-DDEBUG=1"] : [""]; $OBJECTIVEJ_FILES = new FileList("*.js"); diff --git a/Objective-J/ObjJAcornCompiler.js b/Objective-J/ObjJAcornCompiler.js index fb53b2481..961c3f06d 100644 --- a/Objective-J/ObjJAcornCompiler.js +++ b/Objective-J/ObjJAcornCompiler.js @@ -1,31 +1,55 @@ -/* - * ObjJAcornCompiler.js - * Objective-J - * - * Created by Martin Carlberg. - * Copyright 2013, Martin Carlberg. - * - * 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 - */ +// ObjJAcornCompiler was written by Martin Carlberg and released under +// an MIT license. +// +// Git repositories for ObjJAcornCompiler are available at +// +// https://github.com/mrcarlberg/ObjJAcornCompiler.git +// +// Please use the [github bug tracker][ghbt] to report issues. +// +// [ghbt]: https://github.com/mrcarlberg/ObjJAcornCompiler/issues +// +// This file defines the main compiler interface. +// +// Copyright 2013, 2014, 2015, 2016, Martin Carlberg. + + +(function(mod) +{ + //print("Compiler INIT! exports: " + typeof exports + ", module: " + typeof module + ", define: " + typeof define); + mod(exports.ObjJCompiler || (exports.ObjJCompiler = {}), exports.acorn, exports.acorn.walk/*, sourceMap*/); // Plain browser env +})(function(exports, acorn, walk, sourceMap) +{ +"use strict"; + +exports.version = "0.3.7"; +//exports.acorn = acorn; var Scope = function(prev, base) { this.vars = Object.create(null); + if (base) for (var key in base) this[key] = base[key]; this.prev = prev; - if (prev) this.compiler = prev.compiler; + + if (prev) + { + this.compiler = prev.compiler; + this.nodeStack = prev.nodeStack.slice(0); + this.nodePriorStack = prev.nodePriorStack.slice(0); + this.nodeStackOverrideType = prev.nodeStackOverrideType.slice(0); + } + else + { + this.nodeStack = []; + this.nodePriorStack = []; + this.nodeStackOverrideType = []; + } +} + +Scope.prototype.toString = function() +{ + return this.ivars ? "ivars: " + JSON.stringify(this.ivars) : ""; } Scope.prototype.compiler = function() @@ -107,9 +131,20 @@ Scope.prototype.copyAddedSelfToIvarsToParent = function() Scope.prototype.addMaybeWarning = function(warning) { - var rootScope = this.rootScope(); + var rootScope = this.rootScope(), + maybeWarnings = rootScope._maybeWarnings; - (rootScope._maybeWarnings || (rootScope._maybeWarnings = [])).push(warning); + if (!maybeWarnings) + rootScope._maybeWarnings = maybeWarnings = [warning]; + else + { + var lastWarning = maybeWarnings[maybeWarnings.length - 1]; + + // MessageSendExpression (and maybe others) will walk some expressions multible times and + // possible generate warnings multible times. Here we check if this warning is already added + if (!lastWarning.isEqualTo(warning)) + maybeWarnings.push(warning); + } } Scope.prototype.maybeWarnings = function() @@ -117,6 +152,113 @@ Scope.prototype.maybeWarnings = function() return this.rootScope()._maybeWarnings; } +Scope.prototype.pushNode = function(node, overrideType) +{ + // Here we push 3 things to a stack. The node, override type and an array that can keep track of prior nodes on this level. + // The current node is also pushed to the last prior array. + // Special case when node is the same as the parent node. This happends when using an override type when walking the AST + // The same prior list is then used instead of a new empty one. + var nodePriorStack = this.nodePriorStack, + length = nodePriorStack.length, + lastPriorList = length ? nodePriorStack[length - 1] : null, + lastNode = length ? this.nodeStack[length - 1] : null; + // First add this node to parent list of nodes, if it has one + if (lastPriorList) { + if (lastNode !== node) { + // If not the same node push the node + lastPriorList.push(node); + } + } + // Use the last prior list if it is the same node + nodePriorStack.push(lastNode === node ? lastPriorList : []); + this.nodeStack.push(node); + this.nodeStackOverrideType.push(overrideType); +} + +Scope.prototype.popNode = function() +{ + this.nodeStackOverrideType.pop(); + this.nodePriorStack.pop(); + return this.nodeStack.pop(); +} + +Scope.prototype.currentNode = function() +{ + var nodeStack = this.nodeStack; + return nodeStack[nodeStack.length - 1]; +} + +Scope.prototype.currentOverrideType = function() +{ + var nodeStackOverrideType = this.nodeStackOverrideType; + return nodeStackOverrideType[nodeStackOverrideType.length - 1]; +} + +Scope.prototype.priorNode = function() +{ + var nodePriorStack = this.nodePriorStack, + length = nodePriorStack.length; + + if (length > 1) { + var parent = nodePriorStack[length - 2], + l = parent.length; + return parent[l - 2] || null; + } + return null; +} + +Scope.prototype.formatDescription = function(index, formatDescription, useOverrideForNode) +{ + var nodeStack = this.nodeStack, + length = nodeStack.length; + + index = index || 0; + if (index >= length) + return null; + + // Get the nodes backwards from the stack + var i = length - index - 1; + var currentNode = nodeStack[i]; + var currentFormatDescription = formatDescription || this.compiler.formatDescription; + // Get the parent descriptions except if no formatDescription was provided, then it is the root description + var parentFormatDescriptions = formatDescription ? formatDescription.parent : currentFormatDescription; + + var nextFormatDescription; + if (parentFormatDescriptions) { + var nodeType = useOverrideForNode === currentNode ? this.nodeStackOverrideType[i] : currentNode.type; + //console.log("nodeType: " + nodeType + ", (useOverrideForNode === currentNode):" + + !!(useOverrideForNode === currentNode)); + nextFormatDescription = parentFormatDescriptions[nodeType]; + if (useOverrideForNode === currentNode && !nextFormatDescription) { + //console.log("Stop"); + return null; + } + } + + //console.log("index: " + index + ", currentNode: " + JSON.stringify(currentNode) + ", currentFormatDescription: " + JSON.stringify(currentFormatDescription) + ", nextFormatDescription: " + JSON.stringify(nextFormatDescription)); + + if (nextFormatDescription) { + // Check for more 'parent' attributes or return nextFormatDescription + return this.formatDescription(index + 1, nextFormatDescription); + } else { + // Check for a virtual node one step up in the stack + nextFormatDescription = this.formatDescription(index + 1, formatDescription, currentNode); + if (nextFormatDescription) + return nextFormatDescription; + else { + // Ok, we have found a format description (currentFormatDescription). + // Lets check if we have any other descriptions dependent on the prior node. + var priorFormatDescriptions = currentFormatDescription.prior; + if (priorFormatDescriptions) { + var priorNode = this.priorNode(), + priorFormatDescription = priorFormatDescriptions[priorNode ? priorNode.type : "None"]; + if (priorFormatDescription) + return priorFormatDescription; + } + return currentFormatDescription; + } + } +} + var GlobalVariableMaybeWarning = function(/* String */ aMessage, /* SpiderMonkey AST node */ node, /* String */ code) { this.message = createMessage(aMessage, node, code); @@ -126,29 +268,129 @@ var GlobalVariableMaybeWarning = function(/* String */ aMessage, /* SpiderMonkey GlobalVariableMaybeWarning.prototype.checkIfWarning = function(/* Scope */ st) { var identifier = this.node.name; - return !st.getLvar(identifier) && typeof global[identifier] === "undefined" && typeof window[identifier] === "undefined" && !st.compiler.getClassDef(identifier); + return !st.getLvar(identifier) && typeof global[identifier] === "undefined" && (typeof window === 'undefined' || typeof window[identifier] === "undefined") && !st.compiler.getClassDef(identifier); } -function StringBuffer() +GlobalVariableMaybeWarning.prototype.isEqualTo = function(/* GlobalVariableMaybeWarning */ aWarning) { - this.atoms = []; + if (this.message.message !== aWarning.message.message) return false; + if (this.node.start !== aWarning.node.start) return false; + if (this.node.end !== aWarning.node.end) return false; + + return true; } -StringBuffer.prototype.toString = function() +function StringBuffer(useSourceNode, file) +{ + if (useSourceNode) { + this.rootNode = new sourceMap.SourceNode(); + this.concat = this.concatSourceNode; + this.toString = this.toStringSourceNode; + this.isEmpty = this.isEmptySourceNode; + this.appendStringBuffer = this.appendStringBufferSourceNode; + this.length = this.lengthSourceNode; + if (file) + this.file = file.toString(); + } else { + this.atoms = []; + this.concat = this.concatString; + this.toString = this.toStringString; + this.isEmpty = this.isEmptyString; + this.appendStringBuffer = this.appendStringBufferString; + this.length = this.lengthString; + } +} + +StringBuffer.prototype.toStringString = function() { return this.atoms.join(""); } -StringBuffer.prototype.concat = function(aString) +StringBuffer.prototype.toStringSourceNode = function() +{ + return this.rootNode.toStringWithSourceMap({file: this.file}); +} + +StringBuffer.prototype.concatString = function(aString) { this.atoms.push(aString); } -StringBuffer.prototype.isEmpty = function() +StringBuffer.prototype.concatSourceNode = function(aString, node) +{ + if (node) { + //console.log("Snippet: " + aString + ", line: " + node.loc.start.line + ", column: " + node.loc.start.column + ", source: " + node.loc.source); + this.rootNode.add(new sourceMap.SourceNode(node.loc.start.line, node.loc.start.column, node.loc.source, aString)); + } else + this.rootNode.add(aString); + if (!this.notEmpty) + this.notEmpty = true; +} + +// '\n' will indent. '\n\0' will not indent. '\n\1' will indent one more then the current indent level. +// '\n\-1' will indent one less then the current indent level. Numbers from 0-9 can me used. +StringBuffer.prototype.concatFormat = function(aString) +{ + if (!aString) return; + var lines = aString.split("\n"), + size = lines.length; + if (size > 1) { + this.concat(lines[0]); + for (var i = 1; i < size; i++) { + var line = lines[i]; + this.concat("\n"); + if (line.slice(0, 1) === "\\") { + var numberLength = 1; + var indent = line.slice(1, 1 + numberLength); + if (indent === '-') { + numberLength = 2; + indent = line.slice(1, 1 + numberLength); + } + var indentationNumber = parseInt(indent); + if (indentationNumber) { + this.concat(indentationNumber > 0 ? indentation + Array(indentationNumber * indentationSpaces + 1).join(indentType) : indentation.substring(indentationSize * -indentationNumber)); + } + line = line.slice(1 + numberLength); + } else if (line || i === size - 1) { + // Ident if there is something between line breaks or the last linebreak + this.concat(indentation); + } + if (line) this.concat(line); + } + } else + this.concat(aString); +} + +StringBuffer.prototype.isEmptyString = function() { return this.atoms.length !== 0; } +StringBuffer.prototype.isEmptySourceNode = function() +{ + return this.notEmpty; +} + +StringBuffer.prototype.appendStringBufferString = function(stringBuffer) +{ + this.atoms.push.apply(this.atoms, stringBuffer.atoms); +} + +StringBuffer.prototype.appendStringBufferSourceNode = function(stringBuffer) +{ + this.rootNode.add(stringBuffer.rootNode); +} + +StringBuffer.prototype.lengthString = function() +{ + return this.atoms.length; +} + +StringBuffer.prototype.lengthSourceNode = function() +{ + return this.rootNode.children.length; +} + // Both the ClassDef and ProtocolDef conforms to a 'protocol' (That we can't declare in Javascript). // Both Objects have the attribute 'protocols': Array of ProtocolDef that they conform to // Both also have the functions: addInstanceMethod, addClassMethod, getInstanceMethod and getClassMethod @@ -188,21 +430,19 @@ ClassDef.prototype.listOfNotImplementedMethodsForProtocols = function(protocolDe protocolClassMethods = protocolDef.requiredClassMethods, inheritFromProtocols = protocolDef.protocols; - if (protocolInstanceMethods) - for (var methodName in protocolInstanceMethods) { - var methodDef = protocolInstanceMethods[methodName]; + if (protocolInstanceMethods) for (var methodName in protocolInstanceMethods) { + var methodDef = protocolInstanceMethods[methodName]; - if (!instanceMethods[methodName]) - resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); - } + if (!instanceMethods[methodName]) + resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); + } - if (protocolClassMethods) - for (var methodName in protocolClassMethods) { - var methodDef = protocolClassMethods[methodName]; + if (protocolClassMethods) for (var methodName in protocolClassMethods) { + var methodDef = protocolClassMethods[methodName]; - if (!classMethods[methodName]) - resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); - } + if (!classMethods[methodName]) + resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); + } if (inheritFromProtocols) resultList = resultList.concat(this.listOfNotImplementedMethodsForProtocols(inheritFromProtocols)); @@ -365,184 +605,227 @@ var MethodDef = function(name, types) this.types = types; } -var reservedIdentifiers = exports.acorn.makePredicate("self _cmd undefined localStorage arguments"); +var reservedIdentifiers = acorn.makePredicate("self _cmd undefined localStorage arguments"); -var wordPrefixOperators = exports.acorn.makePredicate("delete in instanceof new typeof void"); +var wordPrefixOperators = acorn.makePredicate("delete in instanceof new typeof void"); -var isLogicalBinary = exports.acorn.makePredicate("LogicalExpression BinaryExpression"); -var isInInstanceof = exports.acorn.makePredicate("in instanceof"); +var isLogicalBinary = acorn.makePredicate("LogicalExpression BinaryExpression"); +var isInInstanceof = acorn.makePredicate("in instanceof"); -var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, /*unsigned*/ pass, /* Dictionary */ classDefs, /* Dictionary */ protocolDefs, /* Dictionary */ typeDefs) + // A optional argument can be given to further configure + // the compiler. These options are recognized: + + var defaultOptions = { + // Acorn options. For more information check objj-acorn. + // We have a function here to create a new object every time we copy + // the default options. + acornOptions: function() { return Object.create(null) }, + + // Turn on `sourceMap` generate a source map for the compiler file. + sourceMap: false, + + // The compiler can do different passes. + // 1: Parse and walk AST tree to collect file dependencies. + // 2: Parse and walk to generate code. + // Pass one is only for when the Objective-J load and runtime. + pass: 2, + + // Pass in class definitions. New class definitions in source file will be added here when compiling. + classDefs: function() { return Object.create(null) }, + + // Pass in protocol definitions. New protocol definitions in source file will be added here when compiling. + protocolDefs: function() { return Object.create(null) }, + + // Pass in typeDef definitions. New typeDef definitions in source file will be added here when compiling. + typeDefs: function() { return Object.create(null) }, + + // Turn off `generate` to make the compile copy the code from the source file (and replace needed parts) + // instead of generate it from the AST tree. The preprocessor does not work if this is turn off as it alters + // the AST tree and not the original source. We should deprecate this in the future. + generate: true, + + // Turn on `generateObjJ` to generate Objecitve-J code instead of Javascript code. This can be used to beautify + // the code. + generateObjJ: false, + + // Format description for generated code. For more information look at the readme file in the format folder. + formatDescription: null, + + // How many spaces for indentation when generation code. + indentationSpaces: 4, + + // The type of indentation. Default is space. Can be changed to tab or any other string. + indentationType: " ", + + // Include comments when generating code. This option will turn on the acorn options trackComments and trackCommentsIncludeLineBreak. + includeComments: false, + + // There is a bug in Safari 2.0 that can't handle a named function declaration. See http://kangax.github.io/nfe/#safari-bug + // Turn on `transformNamedFunctionDeclarationToAssignment` to make the compiler transform these. + // We support this here as the old Objective-J compiler (Not a real compiler, Preprocessor.js) transformed + // named function declarations to assignments. + // Example: 'function f(x) { return x }' transforms to: 'f = function(x) { return x }' + transformNamedFunctionDeclarationToAssignment: false, + + // Turn off `includeMethodFunctionNames` to remove function names on methods. + includeMethodFunctionNames: true, + + // Turn off `includeMethodArgumentTypeSignatures` to remove type information on method arguments. + includeMethodArgumentTypeSignatures: true, + + // Turn off `includeIvarTypeSignatures` to remove type information on ivars. + includeIvarTypeSignatures: true, + + // Turn off `inlineMsgSendFunctions` to use message send functions. Needed to use message send decorators. + inlineMsgSendFunctions: true, + }; + + // We copy the options to a new object as we don't want to mess up incoming options when we start compiling. + function setupOptions(opts) { + var options = Object.create(null); + for (var opt in defaultOptions) { + if (opts && Object.prototype.hasOwnProperty.call(opts, opt)) { + var incomingOpt = opts[opt]; + options[opt] = typeof incomingOpt === 'function' ? incomingOpt() : incomingOpt; + } else if (defaultOptions.hasOwnProperty(opt)) { + var defaultOpt = defaultOptions[opt]; + options[opt] = typeof defaultOpt === 'function' ? defaultOpt() : defaultOpt; + } + } + return options; + } + +var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, options) { this.source = aString; - this.URL = new CFURL(aURL); - this.pass = pass; - this.jsBuffer = new StringBuffer(); + this.URL = aURL && aURL.toString(); + options = setupOptions(options); + this.options = options; + this.pass = options.pass; + this.classDefs = options.classDefs; + this.protocolDefs = options.protocolDefs; + this.typeDefs = options.typeDefs; + this.generate = options.generate; + this.createSourceMap = options.sourceMap; + this.formatDescription = options.formatDescription; + this.includeComments = options.includeComments; + this.transformNamedFunctionDeclarationToAssignment = options.transformNamedFunctionDeclarationToAssignment; + this.jsBuffer = new StringBuffer(this.createSourceMap, aURL); this.imBuffer = null; this.cmBuffer = null; - this.warnings = []; + this.dependencies = []; + this.warningsAndErrors = []; + this.lastPos = 0; + + //this.formatDescription = { + // Identifier: {before:"", after:"", parent: {ReturnStatement: {after:"", before:""}, Statement: {after:"", before:""}}}, + // BlockStatement: {before:" ", after:"", afterLeftBrace: "\n", beforeRightBrace: "/* Before Brace */"}, + // Statement: {before:"", after:"/*Statement after*/;\n"} + //}; + + var acornOptions = options.acornOptions; + + if (acornOptions) + { + if (!acornOptions.sourceFile && this.URL) + acornOptions.sourceFile = this.URL.substr(this.URL.lastIndexOf('/') + 1); + if (options.sourceMap && !acornOptions.locations) + acornOptions.locations = true; + } + else + { + acornOptions = options.acornOptions = this.URL && {sourceFile: this.URL.substr(this.URL.lastIndexOf('/') + 1)}; + if (options.sourceMap) + acornOptions.locations = true; + } try { - this.tokens = exports.acorn.parse(aString); + this.tokens = acorn.parse(aString, options.acornOptions); + (this.pass === 2 && (options.includeComments || options.formatDescription) ? compileWithFormat : compile)(this.tokens, new Scope(null ,{ compiler: this }), this.pass === 2 ? pass2 : pass1); } catch (e) { if (e.lineStart != null) { - var message = this.prettifyMessage(e, "ERROR"); -#ifdef BROWSER - console.log(message); -#else - if (exports.outputFormatInXML) - { - var dict = new CFMutableDictionary(); - dict.addValueForKey('line', e.line); - dict.addValueForKey('sourcePath', this.URL.path()); - dict.addValueForKey('message', message); - - print(CFPropertyListCreateXMLData([dict], kCFPropertyListXMLFormat_v1_0).rawString()); - } - else - { - print(message); - } -#endif + e.messageForLine = aString.substring(e.lineStart, e.lineEnd); } - - throw e; + this.addWarning(e); + return; } - this.dependencies = []; - this.flags = flags & (ObjJAcornCompiler.Flags.IncludeDebugSymbols | ObjJAcornCompiler.Flags.InlineMsgSend | ObjJAcornCompiler.Flags.IncludeTypeSignatures); - this.classDefs = classDefs ? classDefs : Object.create(null); - this.protocolDefs = protocolDefs ? protocolDefs : Object.create(null); - this.typeDefs = typeDefs ? typeDefs : Object.create(null); - this.lastPos = 0; - this.generate = true; // Before there was an option to generate the code or copy & paste it from the source. Today we always generate the code. - - compile(this.tokens, new Scope(null ,{ compiler: this }), pass === 2 ? pass2 : pass1); + this.setCompiledCode(this.jsBuffer); } -ObjJAcornCompiler.Flags = { }; - -ObjJAcornCompiler.Flags.IncludeDebugSymbols = 1 << 0; -ObjJAcornCompiler.Flags.IncludeTypeSignatures = 1 << 1; -ObjJAcornCompiler.Flags.Generate = 1 << 2; -ObjJAcornCompiler.Flags.InlineMsgSend = 1 << 3; - -var currentCompilerFlags = ObjJAcornCompiler.Flags.IncludeTypeSignatures; -var currentGccCompilerFlags = ""; - -exports.ObjJAcornCompiler = ObjJAcornCompiler; - -exports.ObjJAcornCompiler.compileToExecutable = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags) +ObjJAcornCompiler.prototype.setCompiledCode = function(stringBuffer) { - ObjJAcornCompiler.currentCompileFile = aURL; - return new ObjJAcornCompiler(aString, aURL, flags, 2).executable(); + if (this.createSourceMap) + { + var s = stringBuffer.toString(); + this.compiledCode = s.code; + this.sourceMap = s.map; + } + else + { + this.compiledCode = stringBuffer.toString(); + } } -exports.ObjJAcornCompiler.compileToIMBuffer = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, classDefs, protocolDefs, typeDefs) +// This might not be used +exports.compileToExecutable = function(/*String*/ aString, /*CFURL*/ aURL, options) { - return new ObjJAcornCompiler(aString, aURL, flags, 2, classDefs, protocolDefs, typeDefs).IMBuffer(); + exports.currentCompileFile = aURL; + return new ObjJAcornCompiler(aString, aURL, options).executable(); } -exports.ObjJAcornCompiler.compileFileDependencies = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags) +exports.compileToIMBuffer = function(/*String*/ aString, /*CFURL*/ aURL, options) { - ObjJAcornCompiler.currentCompileFile = aURL; - return new ObjJAcornCompiler(aString, aURL, flags, 1).executable(); + return new ObjJAcornCompiler(aString, aURL, options).IMBuffer(); +} + +exports.compile = function(/*String*/ aString, /*CFURL*/ aURL, options) +{ + return new ObjJAcornCompiler(aString, aURL, options); +} + +exports.compileFileDependencies = function(/*String*/ aString, /*CFURL*/ aURL, options) +{ + exports.currentCompileFile = aURL; + (options || (options = {})).pass = 1; + return new ObjJAcornCompiler(aString, aURL, options); } ObjJAcornCompiler.prototype.compilePass2 = function() { - var warnings = []; + exports.currentCompileFile = this.URL; + this.pass = this.options.pass = 2; + this.jsBuffer = new StringBuffer(this.createSourceMap, this.URL); - ObjJAcornCompiler.currentCompileFile = this.URL; - this.pass = 2; - this.jsBuffer = new StringBuffer(); - this.warnings = []; - //print(this.URL + ": Compiling"); - compile(this.tokens, new Scope(null ,{ compiler: this }), pass2); - for (var i = 0; i < this.warnings.length; i++) - { - var warning = this.warnings[i], - type = "WARNING"; + // To get the source mapping correct when the new Function construtor is used we add a + // new line as first thing in the code. + if (this.createSourceMap) + this.jsBuffer.concat("\n"); - var message = this.prettifyMessage(warning, type); -#ifdef BROWSER - console.log(message); -#else - if (exports.outputFormatInXML) - { - var dict = new CFMutableDictionary(); - dict.addValueForKey('line', warning.line) - dict.addValueForKey('sourcePath', this.URL.path()) - dict.addValueForKey('message', message) - - warnings.push(dict); - } - else - { - print(message); - } -#endif + this.warningsAndErrors = []; + try { + compile(this.tokens, new Scope(null ,{ compiler: this }), pass2); + } catch (e) { + this.addWarning(e); + return null; } - if (warnings.length && exports.outputFormatInXML) - print(CFPropertyListCreateXMLData(warnings, kCFPropertyListXMLFormat_v1_0).rawString()); + this.setCompiledCode(this.jsBuffer); - //print(this.URL + ": " + this.jsBuffer.toString()); - return this.jsBuffer.toString(); -} - -exports.setCurrentGccCompilerFlags = function(/*String*/ compilerFlags) -{ - if (currentGccCompilerFlags === compilerFlags) return; - - currentGccCompilerFlags = compilerFlags; - - var args = compilerFlags.split(" "), - count = args.length, - objjcFlags = ObjJAcornCompiler.Flags.IncludeTypeSignatures; - - for (var index = 0; index < count; ++index) - { - var argument = args[index]; - - if (argument.indexOf("-g") === 0) - objjcFlags |= ObjJAcornCompiler.Flags.IncludeDebugSymbols; - else if (argument.indexOf("-O") === 0) { - objjcFlags |= ObjJAcornCompiler.Flags.Compress; - // FIXME: currently we are sending in '-O2' when we want InlineMsgSend. Here we only check if it is '-O...'. - // Maybe we should have some other option for this - if (argument.length > 2) - objjcFlags |= ObjJAcornCompiler.Flags.InlineMsgSend; - } - else if (argument.indexOf("-G") === 0) - objjcFlags |= ObjJAcornCompiler.Flags.Generate; - else if (argument.indexOf("-T") === 0) - objjcFlags &= ~ObjJAcornCompiler.Flags.IncludeTypeSignatures; - } - - currentCompilerFlags = objjcFlags; -} - -exports.currentGccCompilerFlags = function(/*String*/ compilerFlags) -{ - return currentGccCompilerFlags; -} - -exports.setCurrentCompilerFlags = function(/*String*/ compilerFlags) -{ - currentCompilerFlags = compilerFlags; -} - -exports.currentCompilerFlags = function(/*String*/ compilerFlags) -{ - return currentCompilerFlags; + return this.compiledCode; } +/*! + Add warning or error to the list + */ ObjJAcornCompiler.prototype.addWarning = function(/* Warning */ aWarning) { - this.warnings.push(aWarning); + if (aWarning.path == null) + aWarning.path = this.URL; + + this.warningsAndErrors.push(aWarning); } ObjJAcornCompiler.prototype.getIvarForClass = function(/* String */ ivarName, /* Scope */ scope) @@ -569,13 +852,11 @@ ObjJAcornCompiler.prototype.getIvarForClass = function(/* String */ ivarName, /* ObjJAcornCompiler.prototype.getClassDef = function(/* String */ aClassName) { - if (!aClassName) - return null; + if (!aClassName) return null; var c = this.classDefs[aClassName]; - if (c) - return c; + if (c) return c; if (typeof objj_getClass === 'function') { @@ -619,13 +900,11 @@ ObjJAcornCompiler.prototype.getClassDef = function(/* String */ aClassName) ObjJAcornCompiler.prototype.getProtocolDef = function(/* String */ aProtocolName) { - if (!aProtocolName) - return null; + if (!aProtocolName) return null; var p = this.protocolDefs[aProtocolName]; - if (p) - return p; + if (p) return p; if (typeof objj_getProtocol === 'function') { @@ -696,6 +975,7 @@ ObjJAcornCompiler.methodDefsFromMethodList = function(/* Array */ methodList) return myMethods; } +//FIXME: Does not work anymore ObjJAcornCompiler.prototype.executable = function() { if (!this._executable) @@ -708,31 +988,44 @@ ObjJAcornCompiler.prototype.IMBuffer = function() return this.imBuffer; } -ObjJAcornCompiler.prototype.JSBuffer = function() +ObjJAcornCompiler.prototype.code = function() { - return this.jsBuffer; + return this.compiledCode; } -ObjJAcornCompiler.prototype.prettifyMessage = function(/* Message */ aMessage, /* String */ messageType) +ObjJAcornCompiler.prototype.ast = function() { - var line = this.source.substring(aMessage.lineStart, aMessage.lineEnd), + return JSON.stringify(this.tokens, null, indentationSpaces); +} + +ObjJAcornCompiler.prototype.map = function() +{ + return JSON.stringify(this.sourceMap); +} + +ObjJAcornCompiler.prototype.prettifyMessage = function(/* Message */ aMessage) +{ + var line = aMessage.messageForLine, message = "\n" + line; - message += (new Array(aMessage.column + 1)).join(" "); + message += (new Array(aMessage.messageOnColumn + 1)).join(" "); message += (new Array(Math.min(1, line.length) + 1)).join("^") + "\n"; - message += messageType + " line " + aMessage.line + " in " + this.URL + ": " + aMessage.message; + message += aMessage.messageType + " line " + aMessage.messageOnLine + " in " + this.URL + ": " + aMessage.message; return message; } ObjJAcornCompiler.prototype.error_message = function(errorMessage, node) { - var pos = exports.acorn.getLineInfo(this.source, node.start), - syntaxErrorData = {message: errorMessage, line: pos.line, column: pos.column, lineStart: pos.lineStart, lineEnd: pos.lineEnd}, - syntaxError = new SyntaxError(this.prettifyMessage(syntaxErrorData, "ERROR")); + var pos = acorn.getLineInfo(this.source, node.start), + syntaxError = new SyntaxError(errorMessage); - syntaxError.line = pos.line; - syntaxError.path = this.URL.path(); + syntaxError.messageOnLine = pos.line; + syntaxError.messageOnColumn = pos.column; + syntaxError.path = this.URL; + syntaxError.messageForNode = node; + syntaxError.messageType = "ERROR"; + syntaxError.messageForLine = this.source.substring(pos.lineStart, pos.lineEnd); return syntaxError; } @@ -751,21 +1044,59 @@ ObjJAcornCompiler.prototype.popImport = function() function createMessage(/* String */ aMessage, /* SpiderMonkey AST node */ node, /* String */ code) { - var message = exports.acorn.getLineInfo(code, node.start); + var message = acorn.getLineInfo(code, node.start); message.message = aMessage; + // As a SyntaxError object can't change the property 'line' we also set the property 'messageOnLine' + message.messageOnLine = message.line; + message.messageOnColumn = message.column; + message.messageForNode = node; + message.messageType = "WARNING"; + message.messageForLine = code.substring(message.lineStart, message.lineEnd); return message; } function compile(node, state, visitor) { function c(node, st, override) { - //print("c: " + (override ? override + ", " : "") + node.type + ", " + exports.acorn.getLineInfo(st.compiler.source, node.start).line); visitor[override || node.type](node, st, c); - //print("cc: " + (override ? override + ", " : "") + node.type + ", " + exports.acorn.getLineInfo(st.compiler.source, node.end).line); } c(node, state); -}; +} + +function compileWithFormat(node, state, visitor) { + var lastNode, lastComment; + function c(node, st, override) { + var compiler = st.compiler, + includeComments = compiler.includeComments, + parentNode = st.currentNode(), + localLastNode = lastNode, + sameNode = localLastNode === node; + //console.log(override || node.type); + lastNode = node; + if (includeComments && !sameNode && node.commentsBefore && node.commentsBefore !== lastComment) { + for (var i = 0; i < node.commentsBefore.length; i++) + compiler.jsBuffer.concat(node.commentsBefore[i]); + } + st.pushNode(node, override); + var formatDescription = st.formatDescription(); + //console.log("formatDescription: " + JSON.stringify(formatDescription) + ", node.type: " + node.type + ", override: " + override); + if (!sameNode && formatDescription && formatDescription.before) + compiler.jsBuffer.concatFormat(formatDescription.before); + visitor[override || node.type](node, st, c, formatDescription); + if (!sameNode && formatDescription && formatDescription.after) + compiler.jsBuffer.concatFormat(formatDescription.after); + st.popNode(); + if (includeComments && !sameNode && node.commentsAfter) { + for (var i = 0; i < node.commentsAfter.length; i++) + compiler.jsBuffer.concat(node.commentsAfter[i]); + lastComment = node.commentsAfter; + } else { + lastComment = null; + } + } + c(node, state); +} function isIdempotentExpression(node) { switch (node.type) { @@ -843,9 +1174,9 @@ function checkCanDereference(st, node) { // Surround expression with parentheses function surroundExpression(c) { - return function(node, st, override) { + return function(node, st, override, format) { st.compiler.jsBuffer.concat("("); - c(node, st, override); + c(node, st, override, format); st.compiler.jsBuffer.concat(")"); } } @@ -898,23 +1229,32 @@ function nodePrecedence(node, subNode, right) { return nodePrecedence < subNodePrecedence || (nodePrecedence === subNodePrecedence && isLogicalBinary(nodeType) && ((nodeOperatorPrecedence = operatorPrecedence[node.operator]) < (subNodeOperatorPrecedence = operatorPrecedence[subNode.operator]) || (right && nodeOperatorPrecedence === subNodeOperatorPrecedence))); } -var pass1 = exports.acorn.walk.make({ +var pass1 = walk.make({ ImportStatement: function(node, st, c) { var urlString = node.filename.value; - st.compiler.dependencies.push(new FileDependency(new CFURL(urlString), node.localfilepath)); + st.compiler.dependencies.push({url: urlString, isLocal: node.localfilepath}); + //st.compiler.dependencies.push(typeof FileDependency !== 'undefined' ? new FileDependency(typeof CFURL !== 'undefined' ? new CFURL(urlString) : urlString, node.localfilepath) : urlString); } }); +var indentType = " "; var indentationSpaces = 4; -var indentStep = Array(indentationSpaces + 1).join(" "); +var indentationSize = indentationSpaces * indentType.length; +var indentStep = Array(indentationSpaces + 1).join(indentType); var indentation = ""; -var pass2 = exports.acorn.walk.make({ +var pass2 = walk.make({ Program: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate; + + indentType = compiler.options.indentationType; + indentationSpaces = compiler.options.indentationSpaces; + indentationSize = indentationSpaces * indentType.length; + indentStep = Array(indentationSpaces + 1).join(indentType); indentation = ""; + for (var i = 0; i < node.body.length; ++i) { c(node.body[i], st, "Statement"); } @@ -929,7 +1269,7 @@ Program: function(node, st, c) { } } }, -BlockStatement: function(node, st, c) { +BlockStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, endOfScopeBody = st.endOfScopeBody, @@ -939,10 +1279,18 @@ BlockStatement: function(node, st, c) { delete st.endOfScopeBody; if (generate) { - st.indentBlockLevel = typeof st.indentBlockLevel === "undefined" ? 0 : st.indentBlockLevel + 1; + var skipIndentation = st.skipIndentation; buffer = compiler.jsBuffer; - buffer.concat(indentation.substring(indentationSpaces)); - buffer.concat("{\n"); + if (format) { + buffer.concat("{", node); + buffer.concatFormat(format.afterLeftBrace); + } else { + if (skipIndentation) + delete st.skipIndentation; + else + buffer.concat(indentation.substring(indentationSize)); + buffer.concat("{\n", node); + } } for (var i = 0; i < node.body.length; ++i) { c(node.body[i], st, "Statement"); @@ -960,46 +1308,73 @@ BlockStatement: function(node, st, c) { buffer.concat(";\n"); } - buffer.concat(indentation.substring(indentationSpaces)); - buffer.concat("}"); - if (st.isDecl || st.indentBlockLevel > 0) - buffer.concat("\n"); - st.indentBlockLevel--; + //Simulate a node for the last curly bracket + var endNode = node.loc && { loc: { start: { line : node.loc.end.line, column: node.loc.end.column-1}}, source: node.loc.source}; + if (format) { + buffer.concatFormat(format.beforeRightBrace); + buffer.concat("}", endNode); + } else { + buffer.concat(indentation.substring(indentationSize)); + buffer.concat("}", endNode); + if (!skipIndentation && st.isDecl !== false) + buffer.concat("\n"); + st.indentBlockLevel--; + } } }, -ExpressionStatement: function(node, st, c) { +ExpressionStatement: function(node, st, c, format) { var compiler = st.compiler, - generate = compiler.generate; + generate = compiler.generate && !format; if (generate) compiler.jsBuffer.concat(indentation); c(node.expression, st, "Expression"); if (generate) compiler.jsBuffer.concat(";\n"); }, -IfStatement: function(node, st, c) { +IfStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - if (!st.superNodeIsElse) - buffer.concat(indentation); - else - delete st.superNodeIsElse; - buffer.concat("if ("); + if (format) { + buffer.concat("if", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + // Keep the 'else' and 'if' on the same line if it is an 'else if' + if (!st.superNodeIsElse) + buffer.concat(indentation); + else + delete st.superNodeIsElse; + buffer.concat("if (", node); + } } c(node.test, st, "Expression"); - // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... - if (generate) buffer.concat(node.consequent.type === "EmptyStatement" ? ");\n" : ")\n"); + if (generate) { + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(node.consequent.type === "EmptyStatement" ? ");\n" : ")\n"); + } + } indentation += indentStep; c(node.consequent, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); var alternate = node.alternate; if (alternate) { var alternateNotIf = alternate.type !== "IfStatement"; if (generate) { - var emptyStatement = alternate.type === "EmptyStatement"; - buffer.concat(indentation); - // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... - buffer.concat(alternateNotIf ? emptyStatement ? "else;\n" : "else\n" : "else "); + if (format) { + buffer.concatFormat(format.beforeElse); // Do we need this? + buffer.concat("else"); + buffer.concatFormat(format.afterElse); + } else { + var emptyStatement = alternate.type === "EmptyStatement"; + buffer.concat(indentation); + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(alternateNotIf ? emptyStatement ? "else;\n" : "else\n" : "else "); + } } if (alternateNotIf) indentation += indentStep; @@ -1007,229 +1382,384 @@ IfStatement: function(node, st, c) { st.superNodeIsElse = true; c(alternate, st, "Statement"); - if (alternateNotIf) indentation = indentation.substring(indentationSpaces); + if (alternateNotIf) indentation = indentation.substring(indentationSize); } }, -LabeledStatement: function(node, st, c) { +LabeledStatement: function(node, st, c, format) { var compiler = st.compiler; if (compiler.generate) { var buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat(node.label.name); - buffer.concat(": "); + if (!format) buffer.concat(indentation); + c(node.label, st, "IdentifierName"); + if (format) { + buffer.concat(":"); + buffer.concatFormat(format.afterColon); + } else { + buffer.concat(": "); + } } c(node.body, st, "Statement"); }, -BreakStatement: function(node, st, c) { +BreakStatement: function(node, st, c, format) { var compiler = st.compiler; if (compiler.generate) { - compiler.jsBuffer.concat(indentation); - if (node.label) { - compiler.jsBuffer.concat("break "); - compiler.jsBuffer.concat(node.label.name); - compiler.jsBuffer.concat(";\n"); + var label = node.label, + buffer = compiler.jsBuffer; + if (!format) buffer.concat(indentation); + if (label) { + if (format) { + buffer.concat("break", node); + buffer.concatFormat(format.beforeLabel); + } else { + buffer.concat("break ", node); + } + c(label, st, "IdentifierName"); + if (!format) buffer.concat(";\n"); } else - compiler.jsBuffer.concat("break;\n"); + buffer.concat(format ? "break" : "break;\n", node); } }, -ContinueStatement: function(node, st, c) { +ContinueStatement: function(node, st, c, format) { var compiler = st.compiler; if (compiler.generate) { - var buffer = compiler.jsBuffer; - buffer.concat(indentation); - if (node.label) { - buffer.concat("continue "); - buffer.concat(node.label.name); - buffer.concat(";\n"); + var label = node.label, + buffer = compiler.jsBuffer; + if (!format) buffer.concat(indentation); + if (label) { + if (format) { + buffer.concat("continue", node); + buffer.concatFormat(format.beforeLabel); + } else { + buffer.concat("continue ", node); + } + c(label, st, "IdentifierName"); + if (!format) buffer.concat(";\n"); } else - buffer.concat("continue;\n"); + buffer.concat(format ? "continue" : "continue;\n", node); } }, -WithStatement: function(node, st, c) { +WithStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("with("); + if (format) { + buffer.concat("with", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("with(", node); + } } c(node.object, st, "Expression"); - if (generate) buffer.concat(")\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + buffer.concat(")\n"); + } indentation += indentStep; c(node.body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); }, -SwitchStatement: function(node, st, c) { +SwitchStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("switch("); + if (format) { + buffer.concat("switch", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("(", node); + } else { + buffer.concat(indentation); + buffer.concat("switch(", node); + } } c(node.discriminant, st, "Expression"); - if (generate) buffer.concat(") {\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + buffer.concat("{"); + buffer.concatFormat(format.afterLeftBrace); + } else { + buffer.concat(") {\n"); + } + indentation += indentStep; for (var i = 0; i < node.cases.length; ++i) { var cs = node.cases[i]; if (cs.test) { if (generate) { - buffer.concat(indentation); - buffer.concat("case "); + if (format) { + buffer.concatFormat(format.beforeCase); + buffer.concat("case", node); + buffer.concatFormat(format.afterCase); + } else { + buffer.concat(indentation); + buffer.concat("case "); + } } c(cs.test, st, "Expression"); - if (generate) buffer.concat(":\n"); + if (generate) + if (format) { + buffer.concat(":"); + buffer.concatFormat(format.afterColon); + } else { + buffer.concat(":\n"); + } } else - if (generate) buffer.concat("default:\n"); + if (generate) + if (format) { + buffer.concatFormat(format.beforeCase); + buffer.concat("default"); + buffer.concatFormat(format.afterCase); + buffer.concat(":"); + buffer.concatFormat(format.afterColon); + } else { + buffer.concat("default:\n"); + } indentation += indentStep; for (var j = 0; j < cs.consequent.length; ++j) c(cs.consequent[j], st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); } + indentation = indentation.substring(indentationSize); if (generate) { - buffer.concat(indentation); - buffer.concat("}\n"); + if (format) { + buffer.concatFormat(format.beforeRightBrace); + buffer.concat("}"); + } else { + buffer.concat(indentation); + buffer.concat("}\n"); + } } }, -ReturnStatement: function(node, st, c) { +ReturnStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("return"); + if (!format) buffer.concat(indentation); + buffer.concat("return", node); } if (node.argument) { - if (generate) buffer.concat(" "); + if (generate) buffer.concatFormat(format ? format.beforeExpression : " "); c(node.argument, st, "Expression"); } - if (generate) buffer.concat(";\n"); + if (generate && !format) buffer.concat(";\n"); }, -ThrowStatement: function(node, st, c) { +ThrowStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("throw "); + if (!format) buffer.concat(indentation); + buffer.concat("throw", node); + buffer.concatFormat(format ? format.beforeExpression : " "); } c(node.argument, st, "Expression"); - if (generate) buffer.concat(";\n"); + if (generate && !format) buffer.concat(";\n"); }, -TryStatement: function(node, st, c) { +TryStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("try"); + if (!format) buffer.concat(indentation); + buffer.concat("try", node); + buffer.concatFormat(format ? format.beforeStatement : " "); } indentation += indentStep; + if (!format) st.skipIndentation = true; c(node.block, st, "Statement"); - indentation = indentation.substring(indentationSpaces); - for (var i = 0; i < node.handlers.length; ++i) { - var handler = node.handlers[i], inner = new Scope(st), + indentation = indentation.substring(indentationSize); + if (node.handler) { + var handler = node.handler, + inner = new Scope(st), param = handler.param, name = param.name; inner.vars[name] = {type: "catch clause", node: param}; if (generate) { - buffer.concat(indentation); - buffer.concat("catch("); - buffer.concat(name); - buffer.concat(") "); + if (format) { + buffer.concatFormat(format.beforeCatch); + buffer.concat("catch"); + buffer.concatFormat(format.afterCatch); + buffer.concat("("); + c(param, st, "IdentifierName"); + buffer.concat(")"); + buffer.concatFormat(format.beforeCatchStatement); + } else { + buffer.concat("\n"); + buffer.concat(indentation); + buffer.concat("catch("); + buffer.concat(name); + buffer.concat(") "); + } } indentation += indentStep; + inner.skipIndentation = true; inner.endOfScopeBody = true; c(handler.body, inner, "ScopeBody"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); inner.copyAddedSelfToIvarsToParent(); } if (node.finalizer) { if (generate) { - buffer.concat(indentation); - buffer.concat("finally "); + if (format) { + buffer.concatFormat(format.beforeCatch); + buffer.concat("finally"); + buffer.concatFormat(format.beforeCatchStatement); + } else { + buffer.concat("\n"); + buffer.concat(indentation); + buffer.concat("finally "); + } } indentation += indentStep; + st.skipIndentation = true; c(node.finalizer, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); } + if (generate && !format) + buffer.concat("\n"); }, -WhileStatement: function(node, st, c) { +WhileStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, body = node.body, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("while ("); + if (format) { + buffer.concat("while", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("while (", node); + } } c(node.test, st, "Expression"); - if (generate) buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + } indentation += indentStep; c(body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); }, -DoWhileStatement: function(node, st, c) { +DoWhileStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("do\n"); + if (format) { + buffer.concat("do", node); + buffer.concatFormat(format.beforeStatement); + } else { + buffer.concat(indentation); + buffer.concat("do\n", node); + } } indentation += indentStep; c(node.body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); if (generate) { - buffer.concat(indentation); - buffer.concat("while ("); + if (format) { + buffer.concat("while"); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("while ("); + } } c(node.test, st, "Expression"); - if (generate) buffer.concat(");\n"); + if (generate) buffer.concatFormat(format ? ")" : ");\n"); }, -ForStatement: function(node, st, c) { +ForStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, body = node.body, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("for ("); + if (format) { + buffer.concat("for", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("for (", node); + } } if (node.init) c(node.init, st, "ForInit"); - if (generate) buffer.concat("; "); + if (generate) buffer.concat(format ? ";" : "; "); if (node.test) c(node.test, st, "Expression"); - if (generate) buffer.concat("; "); + if (generate) buffer.concat(format ? ";" : "; "); if (node.update) c(node.update, st, "Expression"); - if (generate) buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + } indentation += indentStep; c(body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); }, -ForInStatement: function(node, st, c) { +ForInStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, body = node.body, buffer; if (generate) { buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("for ("); + if (format) { + buffer.concat("for", node); + buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); + } else { + buffer.concat(indentation); + buffer.concat("for (", node); + } } c(node.left, st, "ForInit"); - if (generate) buffer.concat(" in "); + if (generate) + if (format) { + buffer.concatFormat(format.beforeIn); + buffer.concat("in"); + buffer.concatFormat(format.afterIn); + } else { + buffer.concat(" in "); + } c(node.right, st, "Expression"); - if (generate) buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + if (generate) + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + // We don't want EmptyStatements to generate an extra parenthesis except when it is in a while, for, ... + buffer.concat(body.type === "EmptyStatement" ? ");\n" : ")\n"); + } indentation += indentStep; c(body, st, "Statement"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); }, ForInit: function(node, st, c) { var compiler = st.compiler, @@ -1241,160 +1771,231 @@ ForInit: function(node, st, c) { } else c(node, st, "Expression"); }, -DebuggerStatement: function(node, st, c) { +DebuggerStatement: function(node, st, c, format) { var compiler = st.compiler; if (compiler.generate) { var buffer = compiler.jsBuffer; - buffer.concat(indentation); - buffer.concat("debugger;\n"); + if (format) { + buffer.concat("debugger", node); + } else { + buffer.concat(indentation); + buffer.concat("debugger;\n", node); + } } }, -Function: function(node, st, c) { +Function: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, - buffer = compiler.jsBuffer; + buffer = compiler.jsBuffer, inner = new Scope(st), - decl = node.type == "FunctionDeclaration"; + decl = node.type == "FunctionDeclaration", + id = node.id; - inner.isDecl = decl; + inner.isDecl = decl; for (var i = 0; i < node.params.length; ++i) inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]}; - if (node.id) { - (decl ? st : inner).vars[node.id.name] = - {type: decl ? "function" : "function name", node: node.id}; - if (generate) { - buffer.concat(node.id.name); - buffer.concat(" = "); - } else { - buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - buffer.concat(node.id.name); - buffer.concat(" = function"); - compiler.lastPos = node.id.end; + if (generate && !format) + buffer.concat(indentation); + if (id) { + var name = id.name; + (decl ? st : inner).vars[name] = {type: decl ? "function" : "function name", node: id}; + if (compiler.transformNamedFunctionDeclarationToAssignment) { + if (generate) { + buffer.concat(name); + buffer.concat(" = "); + } else { + buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + buffer.concat(name); + buffer.concat(" = function"); + compiler.lastPos = id.end; + } } } if (generate) { - buffer.concat("function("); + buffer.concat("function", node); + if (!compiler.transformNamedFunctionDeclarationToAssignment && id) + { + if (!format) buffer.concat(" "); + c(id, st, "IdentifierName"); + } + if (format) buffer.concatFormat(format.beforeLeftParenthesis); + buffer.concat("("); for (var i = 0; i < node.params.length; ++i) { if (i) - buffer.concat(", "); - buffer.concat(node.params[i].name); + buffer.concat(format ? "," : ", "); + c(node.params[i], st, "IdentifierName"); + } + if (format) { + buffer.concat(")"); + buffer.concatFormat(format.afterRightParenthesis); + } else { + buffer.concat(")\n"); } - buffer.concat(")\n"); } indentation += indentStep; inner.endOfScopeBody = true; c(node.body, inner, "ScopeBody"); - indentation = indentation.substring(indentationSpaces); + indentation = indentation.substring(indentationSize); inner.copyAddedSelfToIvarsToParent(); }, -VariableDeclaration: function(node, st, c) { +VariableDeclaration: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, buffer; if (generate) { buffer = compiler.jsBuffer; - if (!st.isFor) buffer.concat(indentation); - buffer.concat("var "); + if (!st.isFor && !format) buffer.concat(indentation); + buffer.concat(format ? "var" : "var ", node); } for (var i = 0; i < node.declarations.length; ++i) { var decl = node.declarations[i], identifier = decl.id.name; if (i) if (generate) { - if (st.isFor) - buffer.concat(", "); - else { - buffer.concat(",\n"); - buffer.concat(indentation); - buffer.concat(" "); + if (format) { + buffer.concat(","); + } else { + if (st.isFor) + buffer.concat(", "); + else { + buffer.concat(",\n"); + buffer.concat(indentation); + buffer.concat(" "); + } } } st.vars[identifier] = {type: "var", node: decl.id}; - if (generate) buffer.concat(identifier); + c(decl.id, st, "IdentifierName"); if (decl.init) { - if (generate) buffer.concat(" = "); + if (generate) { + if (format) { + buffer.concatFormat(format.beforeEqual); + buffer.concat("="); + buffer.concatFormat(format.afterEqual); + } else { + buffer.concat(" = "); + } + } c(decl.init, st, "Expression"); } // FIXME: Extract to function + // Here we check back if a ivar with the same name exists and if we have prefixed 'self.' on previous uses. + // If this is the case we have to remove the prefixes and issue a warning that the variable hides the ivar. if (st.addedSelfToIvars) { var addedSelfToIvar = st.addedSelfToIvars[identifier]; if (addedSelfToIvar) { - var buffer = st.compiler.jsBuffer.atoms; - for (var i = 0; i < addedSelfToIvar.length; i++) { + var atoms = st.compiler.jsBuffer.atoms; + for (var i = 0, size = addedSelfToIvar.length; i < size; i++) { var dict = addedSelfToIvar[i]; - buffer[dict.index] = ""; + atoms[dict.index] = ""; compiler.addWarning(createMessage("Local declaration of '" + identifier + "' hides instance variable", dict.node, compiler.source)); } st.addedSelfToIvars[identifier] = []; } } } - if (generate && !st.isFor) compiler.jsBuffer.concat(";\n"); // Don't add ';' if this is a for statement but do it if this is a statement + if (generate && !format && !st.isFor) buffer.concat(";\n"); // Don't add ';' if this is a for statement but do it if this is a statement }, ThisExpression: function(node, st, c) { var compiler = st.compiler; - if (compiler.generate) compiler.jsBuffer.concat("this"); -}, -ArrayExpression: function(node, st, c) { - var compiler = st.compiler, - generate = compiler.generate; - if (generate) compiler.jsBuffer.concat("["); - for (var i = 0; i < node.elements.length; ++i) { - var elt = node.elements[i]; - if (i !== 0) - if (generate) compiler.jsBuffer.concat(", "); - if (elt) c(elt, st, "Expression"); - } - if (generate) compiler.jsBuffer.concat("]"); + if (compiler.generate) compiler.jsBuffer.concat("this", node); }, -ObjectExpression: function(node, st, c) { +ArrayExpression: function(node, st, c, format) { var compiler = st.compiler, - generate = compiler.generate; - if (generate) compiler.jsBuffer.concat("{"); - for (var i = 0; i < node.properties.length; ++i) + generate = compiler.generate, + buffer; + + if (generate) { + buffer = compiler.jsBuffer; + buffer.concat("[", node); + } + + for (var i = 0; i < node.elements.length; ++i) { + var elt = node.elements[i]; + + if (generate && i !== 0) + if (format) { + buffer.concatFormat(format.beforeComma); + buffer.concat(","); + buffer.concatFormat(format.afterComma); + } else + buffer.concat(", "); + + if (elt) c(elt, st, "Expression"); + } + if (generate) buffer.concat("]"); +}, +ObjectExpression: function(node, st, c, format) { + var compiler = st.compiler, + generate = compiler.generate, + properties = node.properties, + buffer = compiler.jsBuffer; + if (generate) buffer.concat("{", node); + for (var i = 0, size = properties.length; i < size; ++i) { - var prop = node.properties[i]; + var prop = properties[i]; if (generate) { if (i) - compiler.jsBuffer.concat(", "); + if (format) { + buffer.concatFormat(format.beforeComma); + buffer.concat(","); + buffer.concatFormat(format.afterComma); + } else + buffer.concat(", "); st.isPropertyKey = true; c(prop.key, st, "Expression"); delete st.isPropertyKey; - compiler.jsBuffer.concat(": "); + if (format) { + buffer.concatFormat(format.beforeColon); + buffer.concat(":"); + buffer.concatFormat(format.afterColon); + } else { + buffer.concat(": "); + } } else if (prop.key.raw && prop.key.raw.charAt(0) === "@") { - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, prop.key.start)); + buffer.concat(compiler.source.substring(compiler.lastPos, prop.key.start)); compiler.lastPos = prop.key.start + 1; } c(prop.value, st, "Expression"); } - if (generate) compiler.jsBuffer.concat("}"); + if (generate) buffer.concat("}"); }, -SequenceExpression: function(node, st, c) { +SequenceExpression: function(node, st, c, format) { var compiler = st.compiler, - generate = compiler.generate; - if (generate) compiler.jsBuffer.concat("("); + generate = compiler.generate, + buffer; + if (generate) { + buffer = compiler.jsBuffer; + buffer.concat("("); + } for (var i = 0; i < node.expressions.length; ++i) { if (generate && i !== 0) - compiler.jsBuffer.concat(", "); + if (format) { + buffer.concatFormat(format.beforeComma); + buffer.concat(","); + buffer.concatFormat(format.afterComma); + } else + buffer.concat(", "); c(node.expressions[i], st, "Expression"); } - if (generate) compiler.jsBuffer.concat(")"); + if (generate) buffer.concat(")"); }, UnaryExpression: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, argument = node.argument; if (generate) { + var buffer = compiler.jsBuffer; if (node.prefix) { - compiler.jsBuffer.concat(node.operator); + buffer.concat(node.operator, node); if (wordPrefixOperators(node.operator)) - compiler.jsBuffer.concat(" "); + buffer.concat(" "); (nodePrecedence(node, argument) ? surroundExpression(c) : c)(argument, st, "Expression"); } else { (nodePrecedence(node, argument) ? surroundExpression(c) : c)(argument, st, "Expression"); - compiler.jsBuffer.concat(node.operator); + buffer.concat(node.operator); } } else { c(argument, st, "Expression"); @@ -1402,26 +2003,27 @@ UnaryExpression: function(node, st, c) { }, UpdateExpression: function(node, st, c) { var compiler = st.compiler, - generate = compiler.generate; + generate = compiler.generate, + buffer = compiler.jsBuffer; if (node.argument.type === "Dereference") { checkCanDereference(st, node.argument); // @deref(x)++ and ++@deref(x) require special handling. - if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); // Output the dereference function, "(...)(z)" - compiler.jsBuffer.concat((node.prefix ? "" : "(") + "("); + buffer.concat((node.prefix ? "" : "(") + "("); // The thing being dereferenced. if (!generate) compiler.lastPos = node.argument.expr.start; c(node.argument.expr, st, "Expression"); - if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.argument.expr.end)); - compiler.jsBuffer.concat(")("); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.argument.expr.end)); + buffer.concat(")("); if (!generate) compiler.lastPos = node.argument.start; c(node.argument, st, "Expression"); - if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.argument.end)); - compiler.jsBuffer.concat(" " + node.operator.substring(0, 1) + " 1)" + (node.prefix ? "" : node.operator == '++' ? " - 1)" : " + 1)")); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.argument.end)); + buffer.concat(" " + node.operator.substring(0, 1) + " 1)" + (node.prefix ? "" : node.operator == '++' ? " - 1)" : " + 1)")); if (!generate) compiler.lastPos = node.end; return; @@ -1429,42 +2031,42 @@ UpdateExpression: function(node, st, c) { if (node.prefix) { if (generate) { - compiler.jsBuffer.concat(node.operator); + buffer.concat(node.operator, node); if (wordPrefixOperators(node.operator)) - compiler.jsBuffer.concat(" "); + buffer.concat(" "); } (generate && nodePrecedence(node, node.argument) ? surroundExpression(c) : c)(node.argument, st, "Expression"); } else { (generate && nodePrecedence(node, node.argument) ? surroundExpression(c) : c)(node.argument, st, "Expression"); - if (generate) compiler.jsBuffer.concat(node.operator); + if (generate) buffer.concat(node.operator); } }, -BinaryExpression: function(node, st, c) { +BinaryExpression: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, operatorType = isInInstanceof(node.operator); (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression"); if (generate) { var buffer = compiler.jsBuffer; - buffer.concat(" "); + buffer.concatFormat(format ? format.beforeOperator : " "); buffer.concat(node.operator); - buffer.concat(" "); + buffer.concatFormat(format ? format.afterOperator : " "); } (generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression"); }, -LogicalExpression: function(node, st, c) { +LogicalExpression: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate; (generate && nodePrecedence(node, node.left) ? surroundExpression(c) : c)(node.left, st, "Expression"); if (generate) { var buffer = compiler.jsBuffer; - buffer.concat(" "); + buffer.concatFormat(format ? format.beforeOperator : " "); buffer.concat(node.operator); - buffer.concat(" "); + buffer.concatFormat(format ? format.afterOperator : " "); } (generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression"); }, -AssignmentExpression: function(node, st, c) { +AssignmentExpression: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, saveAssignment = st.assignment, @@ -1505,6 +2107,7 @@ AssignmentExpression: function(node, st, c) { var saveAssignment = st.assignment, nodeLeft = node.left; + st.assignment = true; if (nodeLeft.type === "Identifier" && nodeLeft.name === "self") { var lVar = st.getLvar("self", true); @@ -1516,44 +2119,67 @@ AssignmentExpression: function(node, st, c) { } (generate && nodePrecedence(node, nodeLeft) ? surroundExpression(c) : c)(nodeLeft, st, "Expression"); if (generate) { - buffer.concat(" "); + buffer.concatFormat(format ? format.beforeOperator : " "); buffer.concat(node.operator); - buffer.concat(" "); + buffer.concatFormat(format ? format.afterOperator : " "); } st.assignment = saveAssignment; (generate && nodePrecedence(node, node.right, true) ? surroundExpression(c) : c)(node.right, st, "Expression"); if (st.isRootScope() && nodeLeft.type === "Identifier" && !st.getLvar(nodeLeft.name)) st.vars[nodeLeft.name] = {type: "global", node: nodeLeft}; }, -ConditionalExpression: function(node, st, c) { - var compiler = st.compiler, - generate = compiler.generate; - (generate && nodePrecedence(node, node.test) ? surroundExpression(c) : c)(node.test, st, "Expression"); - if (generate) - compiler.jsBuffer.concat(" ? "); - c(node.consequent, st, "Expression"); - if (generate) compiler.jsBuffer.concat(" : "); - c(node.alternate, st, "Expression"); -}, -NewExpression: function(node, st, c) { - var compiler = st.compiler, - generate = compiler.generate; - if (generate) compiler.jsBuffer.concat("new "); - (generate && nodePrecedence(node, node.callee) ? surroundExpression(c) : c)(node.callee, st, "Expression"); - if (generate) compiler.jsBuffer.concat("("); - if (node.arguments) { - for (var i = 0; i < node.arguments.length; ++i) { - if (generate && i) - compiler.jsBuffer.concat(", "); - c(node.arguments[i], st, "Expression"); - } - } - if (generate) compiler.jsBuffer.concat(")"); -}, -CallExpression: function(node, st, c) { +ConditionalExpression: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, - callee = node.callee; + buffer; + (generate && nodePrecedence(node, node.test) ? surroundExpression(c) : c)(node.test, st, "Expression"); + if (generate) { + buffer = compiler.jsBuffer; + if (format) { + buffer.concatFormat(format.beforeOperator); + buffer.concat("?"); + buffer.concatFormat(format.afterOperator); + } else { + buffer.concat(" ? "); + } + } + c(node.consequent, st, "Expression"); + if (generate) + if (format) { + buffer.concatFormat(format.beforeOperator); + buffer.concat(":"); + buffer.concatFormat(format.afterOperator); + } else { + buffer.concat(" : "); + } + c(node.alternate, st, "Expression"); +}, +NewExpression: function(node, st, c, format) { + var compiler = st.compiler, + nodeArguments = node.arguments, + generate = compiler.generate, + buffer; + if (generate) { + buffer = compiler.jsBuffer; + buffer.concat("new ", node); + } + (generate && nodePrecedence(node, node.callee) ? surroundExpression(c) : c)(node.callee, st, "Expression"); + if (generate) buffer.concat("("); + if (nodeArguments) { + for (var i = 0, size = nodeArguments.length; i < size; ++i) { + if (i && generate) + buffer.concatFormat(format ? "," : ", "); + c(nodeArguments[i], st, "Expression"); + } + } + if (generate) buffer.concat(")"); +}, +CallExpression: function(node, st, c, format) { + var compiler = st.compiler, + nodeArguments = node.arguments, + generate = compiler.generate, + callee = node.callee, + buffer; // If call to function 'eval' we assume that 'self' can be altered and from this point // we check if 'self' is null before 'objj_msgSend' is called with 'self' as receiver. @@ -1568,27 +2194,26 @@ CallExpression: function(node, st, c) { } (generate && nodePrecedence(node, callee) ? surroundExpression(c) : c)(callee, st, "Expression"); - if (generate) compiler.jsBuffer.concat("("); - if (node.arguments) { - for (var i = 0; i < node.arguments.length; ++i) { - if (generate && i) - compiler.jsBuffer.concat(", "); - c(node.arguments[i], st, "Expression"); + if (generate) { + buffer = compiler.jsBuffer; + buffer.concat("("); + } + if (nodeArguments) { + for (var i = 0, size = nodeArguments.length; i < size; ++i) { + if (i && generate) + buffer.concat(format ? "," : ", "); + c(nodeArguments[i], st, "Expression"); } } - if (generate) compiler.jsBuffer.concat(")"); + if (generate) buffer.concat(")"); }, MemberExpression: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, computed = node.computed; (generate && nodePrecedence(node, node.object) ? surroundExpression(c) : c)(node.object, st, "Expression"); - if (generate) { - if (computed) - compiler.jsBuffer.concat("["); - else - compiler.jsBuffer.concat("."); - } + if (generate) + compiler.jsBuffer.concat(computed ? "[" : ".", node); st.secondMemberExpression = !computed; // No parentheses when it is computed, '[' amd ']' are the same thing. (generate && !computed && nodePrecedence(node, node.property) ? surroundExpression(c) : c)(node.property, st, "Expression"); @@ -1619,12 +2244,12 @@ Identifier: function(node, st, c) { } while (compiler.source.substr(nodeStart++, 1) === "(") // Save the index in where the "self." string is stored and the node. // These will be used if we find a variable declaration that is hoisting this identifier. - ((st.addedSelfToIvars || (st.addedSelfToIvars = Object.create(null)))[identifier] || (st.addedSelfToIvars[identifier] = [])).push({node: node, index: compiler.jsBuffer.atoms.length}); - compiler.jsBuffer.concat("self."); + ((st.addedSelfToIvars || (st.addedSelfToIvars = Object.create(null)))[identifier] || (st.addedSelfToIvars[identifier] = [])).push({node: node, index: compiler.jsBuffer.length()}); + compiler.jsBuffer.concat("self.", node); } } else if (!reservedIdentifiers(identifier)) { // Don't check for warnings if it is a reserved word like self, localStorage, _cmd, etc... var message, - classOrGlobal = typeof global[identifier] !== "undefined" || typeof window[identifier] !== "undefined" || compiler.getClassDef(identifier), + classOrGlobal = typeof global[identifier] !== "undefined" || (typeof window !== 'undefined' && typeof window[identifier] !== "undefined") || compiler.getClassDef(identifier), globalVar = st.getLvar(identifier); if (classOrGlobal && (!globalVar || globalVar.type !== "class")) { // It can't be declared with a @class statement. /* Turned off this warning as there are many many warnings when compiling the Cappuccino frameworks - Martin @@ -1644,16 +2269,31 @@ Identifier: function(node, st, c) { st.addMaybeWarning(message); } } - if (generate) compiler.jsBuffer.concat(identifier); + if (generate) compiler.jsBuffer.concat(identifier, node); +}, +// Use this when there should not be a look up to issue warnings or add 'self.' before ivars +IdentifierName: function(node, st, c) { + var compiler = st.compiler; + if (compiler.generate) + compiler.jsBuffer.concat(node.name, node); }, Literal: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate; if (generate) { - if (node.raw && node.raw.charAt(0) === "@") - compiler.jsBuffer.concat(node.raw.substring(1)); - else - compiler.jsBuffer.concat(node.raw); + if (node.raw) + if (node.raw.charAt(0) === "@") + compiler.jsBuffer.concat(node.raw.substring(1), node); + else + compiler.jsBuffer.concat(node.raw, node); + else { + var value = node.value, + doubleQuote = value.indexOf('"') !== -1; + compiler.jsBuffer.concat(doubleQuote ? "'" : '"', node); + compiler.jsBuffer.concat(value); + compiler.jsBuffer.concat(doubleQuote ? "'" : '"'); + } + } else if (node.raw.charAt(0) === "@") { compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start + 1; @@ -1662,8 +2302,9 @@ Literal: function(node, st, c) { ArrayLiteral: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, - buffer = compiler.jsBuffer; - + buffer = compiler.jsBuffer, + generateObjJ = compiler.options.generateObjJ, + elementLength = node.elements.length; if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start; @@ -1671,8 +2312,10 @@ ArrayLiteral: function(node, st, c) { if (!generate) buffer.concat(" "); // Add an extra space if it looks something like this: "return()". No space between return and expression. if (!st.receiverLevel) st.receiverLevel = 0; - if (!node.elements.length) { - if (compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend) { + if (generateObjJ) { + buffer.concat("@["); + } else if (!elementLength) { + if (compiler.options.inlineMsgSendFunctions) { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); buffer.concat(" = (CPArray.isa.method_msgSend[\"alloc\"] || _objj_forward)(CPArray, \"alloc\"), ___r"); @@ -1681,7 +2324,7 @@ ArrayLiteral: function(node, st, c) { buffer.concat(st.receiverLevel + ""); buffer.concat(".isa.method_msgSend[\"init\"] || _objj_forward)(___r"); buffer.concat(st.receiverLevel + ""); - buffer.concat(", \"init\"))"); + buffer.concat(", \"init\"))"); } else { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); @@ -1697,7 +2340,7 @@ ArrayLiteral: function(node, st, c) { if (!(st.maxReceiverLevel >= st.receiverLevel)) st.maxReceiverLevel = st.receiverLevel; } else { - if (compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend) { + if (compiler.options.inlineMsgSendFunctions) { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); buffer.concat(" = (CPArray.isa.method_msgSend[\"alloc\"] || _objj_forward)(CPArray, \"alloc\"), ___r"); @@ -1721,8 +2364,9 @@ ArrayLiteral: function(node, st, c) { if (!(st.maxReceiverLevel >= st.receiverLevel)) st.maxReceiverLevel = st.receiverLevel; - - for (var i = 0; i < node.elements.length; i++) { + } + if (elementLength) { + for (var i = 0; i < elementLength; i++) { var elt = node.elements[i]; if (i) @@ -1732,18 +2376,22 @@ ArrayLiteral: function(node, st, c) { c(elt, st, "Expression"); if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, elt.end)); } - buffer.concat("], " + node.elements.length + "))"); + if (!generateObjJ) buffer.concat("], " + elementLength + "))"); } - st.receiverLevel--; + if (generateObjJ) + buffer.concat("]"); + else + st.receiverLevel--; + if (!generate) compiler.lastPos = node.end; }, DictionaryLiteral: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, buffer = compiler.jsBuffer, - noOfKeys = node.keys.length; - + generateObjJ = compiler.options.generateObjJ, + keyLength = node.keys.length; if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start; @@ -1751,8 +2399,17 @@ DictionaryLiteral: function(node, st, c) { if (!generate) buffer.concat(" "); // Add an extra space if it looks something like this: "return()". No space between return and expression. if (!st.receiverLevel) st.receiverLevel = 0; - if (!noOfKeys) { - if (compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend) { + if (generateObjJ) { + buffer.concat("@{"); + for (var i = 0; i < keyLength; i++) { + if (i !== 0) buffer.concat(","); + c(node.keys[i], st, "Expression"); + buffer.concat(":"); + c(node.values[i], st, "Expression"); + } + buffer.concat("}"); + } else if (!keyLength) { + if (compiler.options.inlineMsgSendFunctions) { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); buffer.concat(" = (CPDictionary.isa.method_msgSend[\"alloc\"] || _objj_forward)(CPDictionary, \"alloc\"), ___r"); @@ -1777,7 +2434,7 @@ DictionaryLiteral: function(node, st, c) { if (!(st.maxReceiverLevel >= st.receiverLevel)) st.maxReceiverLevel = st.receiverLevel; } else { - if (compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend) { + if (compiler.options.inlineMsgSendFunctions) { buffer.concat("(___r"); buffer.concat(++st.receiverLevel + ""); buffer.concat(" = (CPDictionary.isa.method_msgSend[\"alloc\"] || _objj_forward)(CPDictionary, \"alloc\"), ___r"); @@ -1802,7 +2459,7 @@ DictionaryLiteral: function(node, st, c) { if (!(st.maxReceiverLevel >= st.receiverLevel)) st.maxReceiverLevel = st.receiverLevel; - for (var i = 0; i < noOfKeys; i++) { + for (var i = 0; i < keyLength; i++) { var value = node.values[i]; if (i) buffer.concat(", "); @@ -1813,10 +2470,11 @@ DictionaryLiteral: function(node, st, c) { buffer.concat("], ["); - for (var i = 0; i < noOfKeys; i++) { + for (var i = 0; i < keyLength; i++) { var key = node.keys[i]; if (i) buffer.concat(", "); + if (!generate) compiler.lastPos = key.start; c(key, st, "Expression"); if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, key.end)); @@ -1824,21 +2482,31 @@ DictionaryLiteral: function(node, st, c) { buffer.concat("]))"); } - st.receiverLevel--; + if (!generateObjJ) + st.receiverLevel--; if (!generate) compiler.lastPos = node.end; }, ImportStatement: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, - buffer = compiler.jsBuffer; + buffer = compiler.jsBuffer, + localfilepath = node.localfilepath, + generateObjJ = compiler.options.generateObjJ; if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - buffer.concat("objj_executeFile(\""); - buffer.concat(node.filename.value); - buffer.concat(node.localfilepath ? "\", YES);" : "\", NO);"); + if (generateObjJ) { + buffer.concat("@import "); + buffer.concat(localfilepath ? "\"" : "<"); + buffer.concat(node.filename.value); + buffer.concat(localfilepath ? "\"" : ">"); + } else { + buffer.concat("objj_executeFile(\"", node); + buffer.concat(node.filename.value); + buffer.concat(localfilepath ? "\", YES);" : "\", NO);"); + } if (!generate) compiler.lastPos = node.end; }, -ClassDeclarationStatement: function(node, st, c) { +ClassDeclarationStatement: function(node, st, c, format) { var compiler = st.compiler, generate = compiler.generate, saveJSBuffer = compiler.jsBuffer, @@ -1846,15 +2514,17 @@ ClassDeclarationStatement: function(node, st, c) { classDef = compiler.getClassDef(className), classScope = new Scope(st), isInterfaceDeclaration = node.type === "InterfaceDeclarationStatement", - protocols = node.protocols; + protocols = node.protocols, + generateObjJ = compiler.options.generateObjJ; - compiler.imBuffer = new StringBuffer(); - compiler.cmBuffer = new StringBuffer(); - compiler.classBodyBuffer = new StringBuffer(); // TODO: Check if this is needed + compiler.imBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); + compiler.cmBuffer = new StringBuffer(compiler.createSourceMap), compiler.URL; + compiler.classBodyBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); // TODO: Check if this is needed if (compiler.getTypeDef(className)) throw compiler.error_message(className + " is already declared as a type", node.classname); + if (!generate) saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); // First we declare the class @@ -1875,14 +2545,14 @@ ClassDeclarationStatement: function(node, st, c) { if (!superClassDef) { var errorMessage = "Can't find superclass " + node.superclassname.name; - for (var i = ObjJAcornCompiler.importStack.length; --i >= 0;) + if (ObjJAcornCompiler.importStack) for (var i = ObjJAcornCompiler.importStack.length; --i >= 0;) errorMessage += "\n" + Array((ObjJAcornCompiler.importStack.length - i) * 2 + 1).join(" ") + "Imported by: " + ObjJAcornCompiler.importStack[i]; throw compiler.error_message(errorMessage, node.superclassname); } classDef = new ClassDef(!isInterfaceDeclaration, className, superClassDef, Object.create(null)); - saveJSBuffer.concat("{var the_class = objj_allocateClassPair(" + node.superclassname.name + ", \"" + className + "\"),\nmeta_class = the_class.isa;"); + if (!generateObjJ) saveJSBuffer.concat("\n{var the_class = objj_allocateClassPair(" + node.superclassname.name + ", \"" + className + "\"),\nmeta_class = the_class.isa;", node); } else if (node.categoryname) { @@ -1890,25 +2560,50 @@ ClassDeclarationStatement: function(node, st, c) { if (!classDef) throw compiler.error_message("Class " + className + " not found ", node.classname); - saveJSBuffer.concat("{\nvar the_class = objj_getClass(\"" + className + "\")\n"); - saveJSBuffer.concat("if(!the_class) throw new SyntaxError(\"*** Could not find definition for class \\\"" + className + "\\\"\");\n"); - saveJSBuffer.concat("var meta_class = the_class.isa;"); + if (!generateObjJ) { + saveJSBuffer.concat("{\nvar the_class = objj_getClass(\"" + className + "\")\n", node); + saveJSBuffer.concat("if(!the_class) throw new SyntaxError(\"*** Could not find definition for class \\\"" + className + "\\\"\");\n"); + saveJSBuffer.concat("var meta_class = the_class.isa;"); + } } else { classDef = new ClassDef(!isInterfaceDeclaration, className, null, Object.create(null)); - saveJSBuffer.concat("{var the_class = objj_allocateClassPair(Nil, \"" + className + "\"),\nmeta_class = the_class.isa;"); + if (!generateObjJ) + saveJSBuffer.concat("{var the_class = objj_allocateClassPair(Nil, \"" + className + "\"),\nmeta_class = the_class.isa;", node); } - if (protocols) - for (var i = 0, size = protocols.length; i < size; i++) - { - saveJSBuffer.concat("\nvar aProtocol = objj_getProtocol(\"" + protocols[i].name + "\");"); + if (generateObjJ) { + saveJSBuffer.concat(isInterfaceDeclaration ? "@interface " : "@implementation "); + saveJSBuffer.concat(className); + if (node.superclassname) { + saveJSBuffer.concat(" : "); + c(node.superclassname, st, "IdentifierName"); + } else if (node.categoryname) { + saveJSBuffer.concat(" ("); + c(node.categoryname, st, "IdentifierName"); + saveJSBuffer.concat(")"); + } + } + + if (protocols) for (var i = 0, size = protocols.length; i < size; i++) + { + if (generateObjJ) { + if (i) + saveJSBuffer.concat(", "); + else + saveJSBuffer.concat(" <"); + c(protocols[i], st, "IdentifierName"); + if (i === size - 1) + saveJSBuffer.concat(">"); + } else { + saveJSBuffer.concat("\nvar aProtocol = objj_getProtocol(\"" + protocols[i].name + "\");", protocols[i]); saveJSBuffer.concat("\nif (!aProtocol) throw new SyntaxError(\"*** Could not find definition for protocol \\\"" + protocols[i].name + "\\\"\");"); saveJSBuffer.concat("\nclass_addProtocol(the_class, aProtocol);"); } - /* + } +/* if (isInterfaceDeclaration) classDef.interfaceDeclaration = true; */ @@ -1922,13 +2617,19 @@ ClassDeclarationStatement: function(node, st, c) { hasAccessors = false; // Then we add all ivars - if (node.ivardeclarations) + if (node.ivardeclarations) { + if (generateObjJ) { + saveJSBuffer.concat("{"); + indentation += indentStep; + } + for (var i = 0; i < node.ivardeclarations.length; ++i) { var ivarDecl = node.ivardeclarations[i], ivarType = ivarDecl.ivartype ? ivarDecl.ivartype.name : null, ivarTypeIsClass = ivarDecl.ivartype ? ivarDecl.ivartype.typeisclass : false, - ivarName = ivarDecl.id.name, + ivarIdentifier = ivarDecl.id, + ivarName = ivarIdentifier.name, ivar = {"type": ivarType, "name": ivarName}, accessors = ivarDecl.accessors; @@ -1946,20 +2647,24 @@ ClassDeclarationStatement: function(node, st, c) { || compiler.getClassDef(ivarType) || compiler.getTypeDef(ivarType) || ivarType == classDef.name; if (!isTypeDefined) - compiler.addWarning(createMessage("Unknown type '" + ivarType + "' for ivar '" + ivarName + "'", ivarDecl.id, compiler.source)); + compiler.addWarning(createMessage("Unknown type '" + ivarType + "' for ivar '" + ivarName + "'", ivarDecl.ivartype, compiler.source)); - if (firstIvarDeclaration) - { - firstIvarDeclaration = false; - saveJSBuffer.concat("class_addIvars(the_class, ["); + if (generateObjJ) { + c(ivarDecl, st, "IvarDeclaration"); + } else { + if (firstIvarDeclaration) + { + firstIvarDeclaration = false; + saveJSBuffer.concat("class_addIvars(the_class, ["); + } + else + saveJSBuffer.concat(", "); + + if (compiler.options.includeIvarTypeSignatures) + saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\", \"" + ivarType + "\")", node); + else + saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\")", node); } - else - saveJSBuffer.concat(", "); - - if (compiler.flags & ObjJAcornCompiler.Flags.IncludeTypeSignatures) - saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\", \"" + ivarType + "\")"); - else - saveJSBuffer.concat("new objj_ivar(\"" + ivarName + "\")"); if (ivarDecl.outlet) ivar.outlet = true; @@ -1969,18 +2674,17 @@ ClassDeclarationStatement: function(node, st, c) { if (!classScope.ivars) classScope.ivars = Object.create(null); - classScope.ivars[ivarName] = {type: "ivar", name: ivarName, node: ivarDecl.id, ivar: ivar}; + classScope.ivars[ivarName] = {type: "ivar", name: ivarName, node: ivarIdentifier, ivar: ivar}; - if (accessors) - { + if (accessors) { + // Declare the accessor methods in the class definition. // TODO: This next couple of lines for getting getterName and setterName are duplicated from below. Create functions for this. var property = (accessors.property && accessors.property.name) || ivarName, getterName = (accessors.getter && accessors.getter.name) || property; classDef.addInstanceMethod(new MethodDef(getterName, [ivarType])); - if (!accessors.readonly) - { + if (!accessors.readonly) { var setterName = accessors.setter ? accessors.setter.name : null; if (!setterName) @@ -1994,14 +2698,17 @@ ClassDeclarationStatement: function(node, st, c) { hasAccessors = true; } } - - if (!firstIvarDeclaration) + } + if (generateObjJ) { + indentation = indentation.substring(indentationSize); + saveJSBuffer.concatFormat("\n}"); + } else if (!firstIvarDeclaration) saveJSBuffer.concat("]);"); // If we have accessors add get and set methods for them - if (!isInterfaceDeclaration && hasAccessors) + if (!generateObjJ && !isInterfaceDeclaration && hasAccessors) { - var getterSetterBuffer = new StringBuffer(); + var getterSetterBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); // Add the class declaration to compile accessors correctly // Remove all protocols from class declaration @@ -2050,7 +2757,7 @@ ClassDeclarationStatement: function(node, st, c) { // Remove all @accessors or we will get a recursive loop in infinity var b = getterSetterBuffer.toString().replace(/@accessors(\(.*\))?/g, ""); - var imBuffer = ObjJAcornCompiler.compileToIMBuffer(b, "Accessors", compiler.flags, compiler.classDefs, compiler.protocolDefs, compiler.typeDefs); + var imBuffer = exports.compileToIMBuffer(b, "Accessors", compiler.options); // Add the accessors methods first to instance method buffer. // This will allow manually added set and get methods to override the compiler generated @@ -2074,45 +2781,46 @@ ClassDeclarationStatement: function(node, st, c) { if (bodyLength > 0) { - if (!generate) - compiler.lastPos = bodies[0].start; + if (!generate) compiler.lastPos = bodies[0].start; // And last add methods and other statements for (var i = 0; i < bodyLength; ++i) { var body = bodies[i]; c(body, classScope, "Statement"); } - if (!generate) - saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, body.end)); + if (!generate) saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, body.end)); } + // We must make a new class object for our class definition if it's not a category - if (!isInterfaceDeclaration && !node.categoryname) { + if (!generateObjJ && !isInterfaceDeclaration && !node.categoryname) { saveJSBuffer.concat("objj_registerClassPair(the_class);\n"); } // Add instance methods - if (compiler.imBuffer.isEmpty()) + if (!generateObjJ && compiler.imBuffer.isEmpty()) { saveJSBuffer.concat("class_addMethods(the_class, ["); - saveJSBuffer.atoms.push.apply(saveJSBuffer.atoms, compiler.imBuffer.atoms); // FIXME: Move this append to StringBuffer + saveJSBuffer.appendStringBuffer(compiler.imBuffer); saveJSBuffer.concat("]);\n"); } // Add class methods - if (compiler.cmBuffer.isEmpty()) + if (!generateObjJ && compiler.cmBuffer.isEmpty()) { saveJSBuffer.concat("class_addMethods(meta_class, ["); - saveJSBuffer.atoms.push.apply(saveJSBuffer.atoms, compiler.cmBuffer.atoms); // FIXME: Move this append to StringBuffer + saveJSBuffer.appendStringBuffer(compiler.cmBuffer); saveJSBuffer.concat("]);\n"); } - saveJSBuffer.concat("}"); + if (!generateObjJ) saveJSBuffer.concat("}\n"); compiler.jsBuffer = saveJSBuffer; // Skip the "@end" - if (!generate) - compiler.lastPos = node.end; + if (!generate) compiler.lastPos = node.end; + + if (generateObjJ) + saveJSBuffer.concat("\n@end"); // If the class conforms to protocols check that all required methods are implemented if (protocols) @@ -2133,8 +2841,8 @@ ClassDeclarationStatement: function(node, st, c) { var unimplementedMethods = classDef.listOfNotImplementedMethodsForProtocols(protocolDefs); if (unimplementedMethods && unimplementedMethods.length > 0) - for (var i = 0, size = unimplementedMethods.length; i < size; i++) { - var unimplementedMethod = unimplementedMethods[i], + for (var j = 0, unimpSize = unimplementedMethods.length; j < unimpSize; j++) { + var unimplementedMethod = unimplementedMethods[j], methodDef = unimplementedMethod.methodDef, protocolDef = unimplementedMethod.protocolDef; @@ -2150,35 +2858,55 @@ ProtocolDeclarationStatement: function(node, st, c) { protocolDef = compiler.getProtocolDef(protocolName), protocols = node.protocols, protocolScope = new Scope(st), - inheritFromProtocols = []; + inheritFromProtocols = [], + generateObjJ = compiler.options.generateObjJ; if (protocolDef) throw compiler.error_message("Duplicate protocol " + protocolName, node.protocolname); - compiler.imBuffer = new StringBuffer(); - compiler.cmBuffer = new StringBuffer(); + compiler.imBuffer = new StringBuffer(compiler.createSourceMap, compiler.URL); + compiler.cmBuffer = new StringBuffer(compiler.createSourceMap), compiler.URL; - if (!generate) - buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - buffer.concat("{var the_protocol = objj_allocateProtocol(\"" + protocolName + "\");"); + if (generateObjJ) { + buffer.concat("@protocol "); + c(node.protocolname, st, "IdentifierName"); + } else { + buffer.concat("{var the_protocol = objj_allocateProtocol(\"" + protocolName + "\");", node); + } + + if (protocols) { + if (generateObjJ) + buffer.concat(" <"); - if (protocols) for (var i = 0, size = protocols.length; i < size; i++) { var protocol = protocols[i], - inheritFromProtocolName = protocol.name; + inheritFromProtocolName = protocol.name, inheritProtocolDef = compiler.getProtocolDef(inheritFromProtocolName); if (!inheritProtocolDef) throw compiler.error_message("Can't find protocol " + inheritFromProtocolName, protocol); - buffer.concat("\nvar aProtocol = objj_getProtocol(\"" + inheritFromProtocolName + "\");"); - buffer.concat("\nif (!aProtocol) throw new SyntaxError(\"*** Could not find definition for protocol \\\"" + protocolName + "\\\"\");"); - buffer.concat("\nprotocol_addProtocol(the_protocol, aProtocol);"); + if (generateObjJ) { + if (i) + buffer.concat(", "); + + c(protocol, st, "IdentifierName"); + } else { + buffer.concat("\nvar aProtocol = objj_getProtocol(\"" + inheritFromProtocolName + "\");", node); + buffer.concat("\nif (!aProtocol) throw new SyntaxError(\"*** Could not find definition for protocol \\\"" + protocolName + "\\\"\");", node); + buffer.concat("\nprotocol_addProtocol(the_protocol, aProtocol);", node); + } + inheritFromProtocols.push(inheritProtocolDef); } + if (generateObjJ) + buffer.concat(">"); + } + protocolDef = new ProtocolDef(protocolName, inheritFromProtocols); compiler.protocolDefs[protocolName] = protocolDef; protocolScope.protocolDef = protocolDef; @@ -2191,109 +2919,168 @@ ProtocolDeclarationStatement: function(node, st, c) { if (requiredLength > 0) { // We only add the required methods - for (var i = 0; i < requiredLength; ++i) - { + for (var i = 0; i < requiredLength; ++i) { var required = someRequired[i]; - if (!generate) - compiler.lastPos = required.start; + if (!generate) compiler.lastPos = required.start; c(required, protocolScope, "Statement"); } - if (!generate) - buffer.concat(compiler.source.substring(compiler.lastPos, required.end)); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, required.end)); } } - buffer.concat("\nobjj_registerProtocol(the_protocol);\n"); + if (generateObjJ) { + buffer.concatFormat("\n@end"); + } else { + buffer.concat("\nobjj_registerProtocol(the_protocol);\n"); - // Add instance methods - if (compiler.imBuffer.isEmpty()) - { - buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); - buffer.atoms.push.apply(buffer.atoms, compiler.imBuffer.atoms); // FIXME: Move this append to StringBuffer - buffer.concat("], true, true);\n"); + // Add instance methods + if (compiler.imBuffer.isEmpty()) + { + buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); + buffer.appendStringBuffer(compiler.imBuffer); + buffer.concat("], true, true);\n"); + } + + // Add class methods + if (compiler.cmBuffer.isEmpty()) + { + buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); + buffer.appendStringBuffer(compiler.cmBuffer); + buffer.concat("], true, false);\n"); + } + + buffer.concat("}"); } - // Add class methods - if (compiler.cmBuffer.isEmpty()) - { - buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); - buffer.atoms.push.apply(buffer.atoms, compiler.cmBuffer.atoms); // FIXME: Move this append to StringBuffer - buffer.concat("], true, false);\n"); - } - - buffer.concat("}"); - compiler.jsBuffer = buffer; // Skip the "@end" - if (!generate) - compiler.lastPos = node.end; + if (!generate) compiler.lastPos = node.end; +}, +IvarDeclaration: function(node, st, c, format) { + var compiler = st.compiler, + buffer = compiler.jsBuffer; + + if (node.outlet) + buffer.concat("@outlet "); + c(node.ivartype, st, "IdentifierName"); + buffer.concat(" "); + c(node.id, st, "IdentifierName"); + if (node.accessors) + buffer.concat(" @accessors"); }, MethodDeclarationStatement: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, saveJSBuffer = compiler.jsBuffer, methodScope = new Scope(st), - isInstanceMethodType = node.methodtype === '-'; + isInstanceMethodType = node.methodtype === '-', selectors = node.selectors, nodeArguments = node.arguments, returnType = node.returntype, - types = [returnType ? returnType.name : (node.action ? "void" : "id")], - returnTypeProtocols = returnType ? returnType.protocols : null; - selector = selectors[0].name; // There is always at least one selector + types = [returnType ? returnType.name : (node.action ? "void" : "id")], // Return type is 'id' as default except if it is an action declared method, then it's 'void' + returnTypeProtocols = returnType ? returnType.protocols : null, + selector = selectors[0].name, // There is always at least one selector + generateObjJ = compiler.options.generateObjJ; - if (returnTypeProtocols) - for (var i = 0, size = returnTypeProtocols.length; i < size; i++) { - var returnTypeProtocol = returnTypeProtocols[i]; - if (!compiler.getProtocolDef(returnTypeProtocol.name)) { - compiler.addWarning(createMessage("Cannot find protocol declaration for '" + returnTypeProtocol.name + "'", returnTypeProtocol, compiler.source)); - } + if (returnTypeProtocols) for (var i = 0, size = returnTypeProtocols.length; i < size; i++) { + var returnTypeProtocol = returnTypeProtocols[i]; + if (!compiler.getProtocolDef(returnTypeProtocol.name)) { + compiler.addWarning(createMessage("Cannot find protocol declaration for '" + returnTypeProtocol.name + "'", returnTypeProtocol, compiler.source)); } - - if (!generate) - saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - - compiler.jsBuffer = isInstanceMethodType ? compiler.imBuffer : compiler.cmBuffer; - - // Put together the selector. Maybe this should be done in the parser... - for (var i = 0; i < nodeArguments.length; i++) { - var argument = nodeArguments[i], - argumentType = argument.type, - argumentTypeName = argumentType ? argumentType.name : "id", - argumentProtocols = argumentType ? argumentType.protocols : null; - - types.push(argumentType ? argumentType.name : "id"); - - if (argumentProtocols) for (var j = 0, size = argumentProtocols.length; j < size; j++) - { - var argumentProtocol = argumentProtocols[j]; - if (!compiler.getProtocolDef(argumentProtocol.name)) - compiler.addWarning(createMessage("Cannot find protocol declaration for '" + argumentProtocol.name + "'", argumentProtocol, compiler.source)); - } - - if (i === 0) - selector += ":"; - else - selector += (selectors[i] ? selectors[i].name : "") + ":"; } - if (compiler.jsBuffer.isEmpty()) // Add comma separator if this is not first method in this buffer - compiler.jsBuffer.concat(", "); + if (!generate) saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - compiler.jsBuffer.concat("new objj_method(sel_getUid(\""); - compiler.jsBuffer.concat(selector); - compiler.jsBuffer.concat("\"), "); + // If we are generating objective-J code write everything directly to the regular buffer + // Otherwise we have one for instance methods and one for class methods. + if (generateObjJ) { + compiler.jsBuffer.concat(isInstanceMethodType ? "- (" : "+ ("); + compiler.jsBuffer.concat(types[0]); + compiler.jsBuffer.concat(")"); + } else { + compiler.jsBuffer = isInstanceMethodType ? compiler.imBuffer : compiler.cmBuffer; + } - if (node.body) - { - compiler.jsBuffer.concat("function"); + // Put together the selector. Maybe this should be done in the parser... + // Or maybe we should do it here as when genereting Objective-J code it's kind of handy + var size = nodeArguments.length; + if (size > 0) { + for (var i = 0; i < nodeArguments.length; i++) { + var argument = nodeArguments[i], + argumentType = argument.type, + argumentTypeName = argumentType ? argumentType.name : "id", + argumentProtocols = argumentType ? argumentType.protocols : null; - if (compiler.flags & ObjJAcornCompiler.Flags.IncludeDebugSymbols) - { - compiler.jsBuffer.concat(" $" + st.currentClassName() + "__" + selector.replace(/:/g, "_")); + types.push(argumentTypeName); + + if (i === 0) + selector += ":"; + else + selector += (selectors[i] ? selectors[i].name : "") + ":"; + + if (argumentProtocols) for (var j = 0, size = argumentProtocols.length; j < size; j++) { + var argumentProtocol = argumentProtocols[j]; + if (!compiler.getProtocolDef(argumentProtocol.name)) { + compiler.addWarning(createMessage("Cannot find protocol declaration for '" + argumentProtocol.name + "'", argumentProtocol, compiler.source)); + } + } + + if (generateObjJ) { + var aSelector = selectors[i]; + + if (i) + compiler.jsBuffer.concat(" "); + + compiler.jsBuffer.concat((aSelector ? aSelector.name : "") + ":"); + compiler.jsBuffer.concat("("); + compiler.jsBuffer.concat(argumentTypeName); + if (argumentProtocols) { + compiler.jsBuffer.concat(" <"); + for (var j = 0, size = argumentProtocols.length; j < size; j++) { + var argumentProtocol = argumentProtocols[j]; + + if (j) + compiler.jsBuffer.concat(", "); + + compiler.jsBuffer.concat(argumentProtocol.name); + } + + compiler.jsBuffer.concat(">"); + } + compiler.jsBuffer.concat(")"); + c(argument.identifier, st, "IdentifierName"); + } } + } else if (generateObjJ) { + var selectorNode = selectors[0]; + compiler.jsBuffer.concat(selectorNode.name, selectorNode); + } - compiler.jsBuffer.concat("(self, _cmd"); + if (generateObjJ) { + if (node.parameters) { + compiler.jsBuffer.concat(", ..."); + } + } else { + if (compiler.jsBuffer.isEmpty()) // Add comma separator if this is not first method in this buffer + compiler.jsBuffer.concat(", "); + + compiler.jsBuffer.concat("new objj_method(sel_getUid(\"", node); + compiler.jsBuffer.concat(selector); + compiler.jsBuffer.concat("\"), "); + } + + if (node.body) { + if (!generateObjJ) { + compiler.jsBuffer.concat("function"); + + if (compiler.options.includeMethodFunctionNames) + { + compiler.jsBuffer.concat(" $" + st.currentClassName() + "__" + selector.replace(/:/g, "_")); + } + + compiler.jsBuffer.concat("(self, _cmd"); + } methodScope.methodType = node.methodtype; methodScope.vars["self"] = {type: "method base", scope: methodScope}; @@ -2304,35 +3091,40 @@ MethodDeclarationStatement: function(node, st, c) { var argument = nodeArguments[i], argumentName = argument.identifier.name; - compiler.jsBuffer.concat(", "); - compiler.jsBuffer.concat(argumentName); + if (!generateObjJ) { + compiler.jsBuffer.concat(", "); + compiler.jsBuffer.concat(argumentName, argument.identifier); + } methodScope.vars[argumentName] = {type: "method argument", node: argument}; } - compiler.jsBuffer.concat(")\n"); + if (!generateObjJ) + compiler.jsBuffer.concat(")\n"); - if (!generate) - compiler.lastPos = node.startOfBody; + if (!generate) compiler.lastPos = node.startOfBody; indentation += indentStep; methodScope.endOfScopeBody = true; c(node.body, methodScope, "Statement"); - indentation = indentation.substring(indentationSpaces); - if (!generate) - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.body.end)); + indentation = indentation.substring(indentationSize); + if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.body.end)); - compiler.jsBuffer.concat("\n"); + if (!generateObjJ) + compiler.jsBuffer.concat("\n"); } else { // It is a interface or protocol declatartion and we don't have a method implementation - compiler.jsBuffer.concat("Nil\n"); + if (generateObjJ) + compiler.jsBuffer.concat(";"); + else + compiler.jsBuffer.concat("Nil\n"); } - if (compiler.flags & ObjJAcornCompiler.Flags.IncludeTypeSignatures) - compiler.jsBuffer.concat(","+JSON.stringify(types)); + if (!generateObjJ) { + if (compiler.options.includeMethodArgumentTypeSignatures) + compiler.jsBuffer.concat(","+JSON.stringify(types)); + compiler.jsBuffer.concat(")"); + compiler.jsBuffer = saveJSBuffer; + } - compiler.jsBuffer.concat(")"); - compiler.jsBuffer = saveJSBuffer; - - if (!generate) - compiler.lastPos = node.end; + if (!generate) compiler.lastPos = node.end; // Add the method to the class or protocol definition var def = st.classDef, @@ -2345,7 +3137,7 @@ MethodDeclarationStatement: function(node, st, c) { def = st.protocolDef; if (!def) - throw "InternalError: MethodDeclaration without ClassDeclaration or ProtocolDeclaration at line: " + exports.acorn.getLineInfo(compiler.source, node.start).line; + throw "InternalError: MethodDeclaration without ClassDeclaration or ProtocolDeclaration at line: " + acorn.getLineInfo(compiler.source, node.start).line; // Create warnings if types does not corresponds to method declaration in superclass or interface declarations // If we don't find the method in superclass or interface declarations above or if it is a protocol @@ -2353,14 +3145,13 @@ MethodDeclarationStatement: function(node, st, c) { if (!alreadyDeclared) { var protocols = def.protocols; - if (protocols) - for (var i = 0, size = protocols.length; i < size; i++) { - var protocol = protocols[i], - alreadyDeclared = isInstanceMethodType ? protocol.getInstanceMethod(selector) : protocol.getClassMethod(selector); + if (protocols) for (var i = 0, size = protocols.length; i < size; i++) { + var protocol = protocols[i], + alreadyDeclared = isInstanceMethodType ? protocol.getInstanceMethod(selector) : protocol.getClassMethod(selector); - if (alreadyDeclared) - break; - } + if (alreadyDeclared) + break; + } } if (alreadyDeclared) { @@ -2398,21 +3189,26 @@ MethodDeclarationStatement: function(node, st, c) { MessageSendExpression: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, - inlineMsgSend = compiler.flags & ObjJAcornCompiler.Flags.InlineMsgSend, + inlineMsgSend = compiler.options.inlineMsgSendFunctions, buffer = compiler.jsBuffer, nodeObject = node.object, selectors = node.selectors, - arguments = node.arguments, - argumentsLength = arguments.length, + nodeArguments = node.arguments, + argumentsLength = nodeArguments.length, firstSelector = selectors[0], - selector = firstSelector ? firstSelector.name : ""; // There is always at least one selector + selector = firstSelector ? firstSelector.name : "", // There is always at least one selector + parameters = node.parameters, + generateObjJ = compiler.options.generateObjJ; // Put together the selector. Maybe this should be done in the parser... - for (var i = 0; i < argumentsLength; i++) - if (i === 0) - selector += ":"; - else - selector += (selectors[i] ? selectors[i].name : "") + ":"; + for (var i = 0; i < argumentsLength; i++) { + if (i !== 0) { + var nextSelector = selectors[i]; + if (nextSelector) + selector += nextSelector.name; + } + selector += ":"; + } if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); @@ -2421,24 +3217,28 @@ MessageSendExpression: function(node, st, c) { // Find out the total number of arguments so we can choose appropriate msgSend function. Only needed if call the function and not inline it var totalNoOfParameters = argumentsLength; - if (node.parameters) - totalNoOfParameters += node.parameters.length; + if (parameters) + totalNoOfParameters += parameters.length; } if (node.superObject) { if (!generate) buffer.concat(" "); // Add an extra space if it looks something like this: "return()". No space between return and expression. - if (inlineMsgSend) { - buffer.concat("("); - buffer.concat(st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass); - buffer.concat(".method_dtable[\""); - buffer.concat(selector); - buffer.concat("\"] || _objj_forward)(self"); + if (generateObjJ) { + buffer.concat("[super "); } else { - buffer.concat("objj_msgSendSuper"); - if (totalNoOfParameters < 4) { - buffer.concat("" + totalNoOfParameters); + if (inlineMsgSend) { + buffer.concat("("); + buffer.concat(st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass); + buffer.concat(".method_dtable[\""); + buffer.concat(selector); + buffer.concat("\"] || _objj_forward)(self"); + } else { + buffer.concat("objj_msgSendSuper"); + if (totalNoOfParameters < 4) { + buffer.concat("" + totalNoOfParameters); + } + buffer.concat("({ receiver:self, super_class:" + (st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass ) + " }"); } - buffer.concat("({ receiver:self, super_class:" + (st.currentMethodType() === "+" ? compiler.currentSuperMetaClass : compiler.currentSuperClass ) + " }"); } } else @@ -2498,129 +3298,182 @@ MessageSendExpression: function(node, st, c) { } } - if (generate && !node.superObject) { - if (!inlineMsgSend) { - if (totalNoOfParameters < 4) { - buffer.concat("" + totalNoOfParameters); + if (generateObjJ) { + for (var i = 0; i < argumentsLength || (argumentsLength === 0 && i === 0); i++) { + var selector = selectors[i]; + + buffer.concat(" "); + buffer.concat(selector ? selector.name : ""); + + if (argumentsLength > 0) { + var argument = nodeArguments[i]; + + buffer.concat(":"); + c(argument, st, "Expression"); } } - if (receiverIsIdentifier) { - buffer.concat("("); - c(nodeObject, st, "Expression"); - } else { - buffer.concat("(___r"); - buffer.concat(st.receiverLevel + ""); + if (parameters) for (var i = 0, size = parameters.length; i < size; ++i) + { + var parameter = parameters[i]; + + buffer.concat(", "); + c(parameter, st, "Expression"); } - } + buffer.concat("]"); + } else { + if (generate && !node.superObject) { + if (!inlineMsgSend) { + if (totalNoOfParameters < 4) { + buffer.concat("" + totalNoOfParameters); + } + } - buffer.concat(", \""); - buffer.concat(selector); // FIXME: sel_getUid(selector + "") ? This FIXME is from the old preprocessor compiler - buffer.concat("\""); - - if (node.arguments) for (var i = 0; i < node.arguments.length; i++) - { - var argument = node.arguments[i]; - - buffer.concat(", "); - if (!generate) - compiler.lastPos = argument.start; - c(argument, st, "Expression"); - if (!generate) { - buffer.concat(compiler.source.substring(compiler.lastPos, argument.end)); - compiler.lastPos = argument.end; + if (receiverIsIdentifier) { + buffer.concat("("); + c(nodeObject, st, "Expression"); + } else { + buffer.concat("(___r"); + buffer.concat(st.receiverLevel + ""); + } } - } - // TODO: Move this 'if' with body up inside the node.argument 'if' - if (node.parameters) for (var i = 0; i < node.parameters.length; ++i) - { - var parameter = node.parameters[i]; + buffer.concat(", \""); + buffer.concat(selector); // FIXME: sel_getUid(selector + "") ? This FIXME is from the old preprocessor compiler + buffer.concat("\""); - buffer.concat(", "); - if (!generate) - compiler.lastPos = parameter.start; - c(parameter, st, "Expression"); - if (!generate) { - buffer.concat(compiler.source.substring(compiler.lastPos, parameter.end)); - compiler.lastPos = parameter.end; + if (nodeArguments) for (var i = 0; i < nodeArguments.length; i++) + { + var argument = nodeArguments[i]; + + buffer.concat(", "); + if (!generate) + compiler.lastPos = argument.start; + c(argument, st, "Expression"); + if (!generate) { + buffer.concat(compiler.source.substring(compiler.lastPos, argument.end)); + compiler.lastPos = argument.end; + } } + + if (parameters) for (var i = 0; i < parameters.length; ++i) + { + var parameter = parameters[i]; + + buffer.concat(", "); + if (!generate) + compiler.lastPos = parameter.start; + c(parameter, st, "Expression"); + if (!generate) { + buffer.concat(compiler.source.substring(compiler.lastPos, parameter.end)); + compiler.lastPos = parameter.end; + } + } + + if (generate && !node.superObject) { + if (receiverIsNotSelf) + buffer.concat(")"); + if (!receiverIsIdentifier) + st.receiverLevel--; + } + + buffer.concat(")"); } - if (generate && !node.superObject) { - if (receiverIsNotSelf) - buffer.concat(")"); - if (!receiverIsIdentifier) - st.receiverLevel--; - } - - buffer.concat(")"); if (!generate) compiler.lastPos = node.end; }, SelectorLiteralExpression: function(node, st, c) { var compiler = st.compiler, buffer = compiler.jsBuffer, - generate = compiler.generate; + generate = compiler.generate, + generateObjJ = compiler.options.generateObjJ; + if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); buffer.concat(" "); // Add an extra space if it looks something like this: "return(@selector(a:))". No space between return and expression. } - buffer.concat("sel_getUid(\""); + + buffer.concat(generateObjJ ? "@selector(" : "sel_getUid(\"", node); buffer.concat(node.selector); - buffer.concat("\")"); + buffer.concat(generateObjJ ? ")" : "\")"); + if (!generate) compiler.lastPos = node.end; }, ProtocolLiteralExpression: function(node, st, c) { var compiler = st.compiler, buffer = compiler.jsBuffer, - generate = compiler.generate; + generate = compiler.generate, + generateObjJ = compiler.options.generateObjJ; + if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); buffer.concat(" "); // Add an extra space if it looks something like this: "return(@protocol(a))". No space between return and expression. } - buffer.concat("objj_getProtocol(\""); - buffer.concat(node.id.name); - buffer.concat("\")"); + buffer.concat(generateObjJ ? "@protocol(" : "objj_getProtocol(\"", node); + c(node.id, st, "IdentifierName"); + buffer.concat(generateObjJ ? ")" : "\")"); if (!generate) compiler.lastPos = node.end; }, Reference: function(node, st, c) { var compiler = st.compiler, buffer = compiler.jsBuffer, - generate = compiler.generate; + generate = compiler.generate, + generateObjJ = compiler.options.generateObjJ; + if (!generate) { buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); buffer.concat(" "); // Add an extra space if it looks something like this: "return()". No space between return and expression. } - buffer.concat("function(__input) { if (arguments.length) return "); - c(node.element, st, "Expression"); - buffer.concat(" = __input; return "); - c(node.element, st, "Expression"); - buffer.concat("; }"); + if (generateObjJ) { + buffer.concat("@ref(", node); + buffer.concat(node.element.name, node.element); + buffer.concat(")", node); + } else { + buffer.concat("function(__input) { if (arguments.length) return ", node); + c(node.element, st, "Expression"); + buffer.concat(" = __input; return "); + c(node.element, st, "Expression"); + buffer.concat("; }"); + } + if (!generate) compiler.lastPos = node.end; }, Dereference: function(node, st, c) { var compiler = st.compiler, - generate = compiler.generate; + buffer = compiler.jsBuffer, + generate = compiler.generate, + generateObjJ = compiler.options.generateObjJ; checkCanDereference(st, node.expr); // @deref(y) -> y() // @deref(@deref(y)) -> y()() if (!generate) { - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.expr.start; } + if (generateObjJ) + buffer.concat("@deref("); c(node.expr, st, "Expression"); - if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.expr.end)); - compiler.jsBuffer.concat("()"); + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.expr.end)); + if (generateObjJ) + buffer.concat(")"); + else + buffer.concat("()"); if (!generate) compiler.lastPos = node.end; }, ClassStatement: function(node, st, c) { - var compiler = st.compiler; + var compiler = st.compiler, + buffer = compiler.jsBuffer, + generateObjJ = compiler.options.generateObjJ; if (!compiler.generate) { - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start; - compiler.jsBuffer.concat("//"); + buffer.concat("//"); + } + if (generateObjJ) { + buffer.concat("@class "); + c(node.id, st, "IdentifierName"); } var className = node.id.name; @@ -2628,17 +3481,22 @@ ClassStatement: function(node, st, c) { throw compiler.error_message(className + " is already declared as a type", node.id); if (!compiler.getClassDef(className)) { - classDef = new ClassDef(false, className); - compiler.classDefs[className] = classDef; + compiler.classDefs[className] = new ClassDef(false, className); } st.vars[node.id.name] = {type: "class", node: node.id}; }, GlobalStatement: function(node, st, c) { - var compiler = st.compiler; + var compiler = st.compiler, + buffer = compiler.jsBuffer, + generateObjJ = compiler.options.generateObjJ; if (!compiler.generate) { - compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); compiler.lastPos = node.start; - compiler.jsBuffer.concat("//"); + buffer.concat("//"); + } + if (generateObjJ) { + buffer.concat("@global "); + c(node.id, st, "IdentifierName"); } st.rootScope().vars[node.id.name] = {type: "global", node: node.id}; }, @@ -2651,7 +3509,6 @@ PreprocessStatement: function(node, st, c) { } }, TypeDefStatement: function(node, st, c) { - var compiler = st.compiler, generate = compiler.generate, buffer = compiler.jsBuffer, @@ -2665,9 +3522,6 @@ TypeDefStatement: function(node, st, c) { if (compiler.getClassDef(typeDefName)) throw compiler.error_message(typeDefName + " is already declared as class", node.typedefname); - compiler.imBuffer = new StringBuffer(); - compiler.cmBuffer = new StringBuffer(); - if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); @@ -2681,10 +3535,10 @@ TypeDefStatement: function(node, st, c) { buffer.concat("}"); - compiler.jsBuffer = buffer; - - // Skip the "@end" + // Skip to the end if (!generate) compiler.lastPos = node.end; } }); + +}); diff --git a/Objective-J/acorn.js b/Objective-J/acorn.js index 594111ee0..feb02694c 100644 --- a/Objective-J/acorn.js +++ b/Objective-J/acorn.js @@ -13,28 +13,28 @@ // // [ghbt]: https://github.com/marijnh/acorn/issues // +// Objective-J extensions made by Martin Carlberg +// +// Git repositories for Acorn with Objective-J extension is available at +// +// https://github.com/mrcarlberg/acorn.git +// // This file defines the main parser interface. The library also comes // with a [error-tolerant parser][dammit] and an // [abstract syntax tree walker][walk], defined in other files. // // [dammit]: acorn_loose.js // [walk]: util/walk.js -// -// Objective-J extensions made by Martin Carlberg -// -// Git repositories for Acorn with Objective-J extension is available at -// -// https://github.com/mrcarlberg/acorn.git if (typeof exports != "undefined" && !exports.acorn) { exports.acorn = {}; exports.acorn.walk = {}; } -(function(exports) { +(function(exports, walk) { "use strict"; - exports.version = "0.1.01"; + exports.version = "0.3.3-objj-3"; // The main exported interface (under `self.acorn` when in the // browser) is a `parse` function that takes a code string and @@ -49,6 +49,9 @@ if (typeof exports != "undefined" && !exports.acorn) { exports.parse = function(inpt, opts) { input = String(inpt); inputLen = input.length; setOptions(opts); + initPreprocessorState(); + if (options.macros) + defineMacros(options.macros); initTokenState(); return parseTopLevel(options.program); }; @@ -78,6 +81,10 @@ if (typeof exports != "undefined" && !exports.acorn) { // after and before it), but never twice in the before (or after) // array of different nodes. trackComments: false, + // When `trackCommentsIncludeLineBreak` is turned on, the parser will + // include, if present, the line break before the comment and all + // the whitespace in between. + trackCommentsIncludeLineBreak: false, // When `trackSpaces` is turned on, the parser will attach // `spacesBefore` and `spacesAfter` properties to AST nodes // holding arrays of strings. The same spaces may appear in both @@ -90,6 +97,16 @@ if (typeof exports != "undefined" && !exports.acorn) { // line being 1-based and column 0-based) will be attached to the // nodes. locations: false, + // A function can be passed as `onComment` option, which will + // cause Acorn to call that function with `(block, text, start, + // end)` parameters whenever a comment is skipped. `block` is a + // boolean indicating whether this is a block (`/* */`) comment, + // `text` is the content of the comment, and `start` and `end` are + // character offsets that denote the start and end of the comment. + // When the `locations` option is on, two more parameters are + // passed, the full `{line, column}` locations of the start and + // end of the comments. + onComment: null, // Nodes have their start and end characters offsets recorded in // `start` and `end` properties (directly on the node, rather than // the `loc` object, which holds line/column data. To also add a @@ -125,12 +142,20 @@ if (typeof exports != "undefined" && !exports.acorn) { // Preprocess undefine macro function. To delete a macro preprocessUndefineMacro: defaultUndefineMacro, // Preprocess is macro function - preprocessIsMacro: defaultIsMacro + preprocessIsMacro: defaultIsMacro, + // An array of macro objects and/or text definitions may be passed in. + // Definitions may be in one of two forms: + // macro + // macro=body + macros: null, + // Turn off lineNoInErrorMessage to exclude line number in error messages + // Needs to be on to run test cases + lineNoInErrorMessage: true }; function setOptions(opts) { options = opts || {}; - for (var opt in defaultOptions) if (!options.hasOwnProperty(opt)) + for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt)) options[opt] = defaultOptions[opt]; sourceFile = options.sourceFile || null; } @@ -138,6 +163,14 @@ if (typeof exports != "undefined" && !exports.acorn) { var macros; var macrosIsPredicate; + var macrosMakeBuiltin = function(name, macro, endPos) {return new Macro(name, macro, null, endPos - name.length)} + + var macrosBuiltinMacros = { + __OBJJ__: function() {return macrosMakeBuiltin("__OBJJ__", options.objj ? "1" : null, tokPos)}, + __BROWSER__: function() {return macrosMakeBuiltin("__BROWSER__", typeof(window) !== "undefined" ? "1" : null, tokPos)}, + __LINE__: function() {return macrosMakeBuiltin("__LINE__", String(options.locations ? tokCurLine : getLineInfo(input, tokPos).line), tokPos)}, + } + function defaultAddMacro(macro) { macros[macro.identifier] = macro; macrosIsPredicate = null; @@ -153,8 +186,40 @@ if (typeof exports != "undefined" && !exports.acorn) { } function defaultIsMacro(macroIdentifier) { - var x = Object.keys(macros).join(" "); - return (macrosIsPredicate || (macrosIsPredicate = makePredicate(x)))(macroIdentifier); + return (macrosIsPredicate || (macrosIsPredicate = makePredicate(Object.keys(macros).concat(Object.keys(macrosBuiltinMacros).filter(function(key) {return this[key]().macro != null}, macrosBuiltinMacros)).join(" "))))(macroIdentifier); + } + + function preprocessBuiltinMacro(macroIdentifier) { + var builtinMacro = macrosBuiltinMacros[macroIdentifier]; + return builtinMacro ? builtinMacro() : null; + } + + function defineMacros(macroArray) { + for (var i = 0, size = macroArray.length; i < size; i++) { + var savedInput = input; + var macroDefinition = macroArray[i].trim(); + var pos = macroDefinition.indexOf("="); + if (pos === 0) + raise(0, "Invalid macro definition: '" + macroDefinition + "'"); + // If there is no macro body, define the name with the value 1 + var name, body; + if (pos > 0) { + name = macroDefinition.slice(0, pos); + body = macroDefinition.slice(pos + 1); + } + else { + name = macroDefinition; + } + if (macrosBuiltinMacros.hasOwnProperty(name)) + raise(0, "'" + name + "' is a predefined macro name"); + + input = name + (body != null ? " " + body : ""); + inputLen = input.length; + initTokenState(); + preprocessParseDefine(); + input = savedInput; + inputLen = input.length; + } } // The `getLineInfo` function is mostly useful when the @@ -186,6 +251,7 @@ if (typeof exports != "undefined" && !exports.acorn) { input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); + initPreprocessorState(); var t = {}; function getToken(forceRegexp) { @@ -198,14 +264,14 @@ if (typeof exports != "undefined" && !exports.acorn) { getToken.jumpTo = function(pos, reAllowed) { tokPos = pos; if (options.locations) { - tokCurLine = tokLineStart = lineBreak.lastIndex = 0; + tokCurLine = 1; + tokLineStart = lineBreak.lastIndex = 0; var match; while ((match = lineBreak.exec(input)) && match.index < pos) { ++tokCurLine; tokLineStart = match.index + match[0].length; } } - var ch = input.charAt(pos - 1); tokRegexpAllowed = reAllowed; skipSpace(); }; @@ -220,8 +286,13 @@ if (typeof exports != "undefined" && !exports.acorn) { var tokPos; // The start and end offsets of the current token. + // First tokstart is the same as tokStart except when the preprocessor finds a macro. + // Then the tokFirstStart points to the start of the token that will be replaced by the macro. + // tokStart then points at the macros first + // tokMacroOffset is the offset to the current macro for the current token + // tokPosMacroOffset is the offset to the current macro for the current tokPos - var tokStart, tokEnd; + var tokFirstStart, tokStart, tokEnd, tokMacroOffset, tokPosMacroOffset, lastTokMacroOffset; // When `options.locations` is true, these hold objects // containing the tokens start and end line/column pairs. @@ -261,27 +332,21 @@ if (typeof exports != "undefined" && !exports.acorn) { // track of the current line, and know when a new line has been // entered. - var tokCurLine, tokLineStart, tokLineStartNext; + var tokCurLine, tokLineStart; // Same as input but for the current token. If options.preprocess is used // this can differ due to macros. - var tokInput, preTokInput; + var tokInput, preTokInput, tokFirstInput; // These store the position of the previous token, which is useful // when finishing a node and assigning its `end` position. var lastStart, lastEnd, lastEndLoc; - // This is the tokenizer's state for Objective-J. `afterImport` is used - // to make the part between '<' and '>' to be one token if it comes after - // a @import token. - - var tokAfterImport; - // This is the tokenizer's state for Objective-J. 'nodeMessageSendObjectExpression' // is used to store the expression that is already parsed when a subscript was - // not really a subscript + // not really a subscript. var nodeMessageSendObjectExpression; @@ -296,8 +361,24 @@ if (typeof exports != "undefined" && !exports.acorn) { var preTokPos, preTokType, preTokVal, preTokStart, preTokEnd; var preLastStart, preLastEnd; - var preprocessStack = []; - var preprocessMacroParamterListMode = false; + var preprocessStack; + var preprocessStackLastItem; + var preprocessOnlyTransformArgumentsForLastToken; + var preprocessMacroParameterListMode; + var preprocessIsParsingPreprocess; + var preprocessParameterScope; + var preTokParameterScope; + var preprocessOverrideTokEndLoc; + + // True if we are concatenating two tokens. This is needed to handle when the second part is an empty macro + // This is also used when stingifying tokens to get an empty macro + + var preConcatenating; + + // True if we are skipping token when finding #else or #endif after and #if + + var preNotSkipping; + var preIfLevel; // This function is used to raise exceptions on parse errors. It // takes either a `{line, column}` object or an offset integer (into @@ -307,9 +388,11 @@ if (typeof exports != "undefined" && !exports.acorn) { function raise(pos, message) { if (typeof pos == "number") pos = getLineInfo(input, pos); + if (options.lineNoInErrorMessage) + message += " (" + pos.line + ":" + pos.column + ")"; var syntaxError = new SyntaxError(message); - syntaxError.line = pos.line; - syntaxError.column = pos.column; + syntaxError.messageOnLine = pos.line; + syntaxError.messageOnColumn = pos.column; syntaxError.lineStart = pos.lineStart; syntaxError.lineEnd = pos.lineEnd; syntaxError.fileName = sourceFile; @@ -317,6 +400,10 @@ if (typeof exports != "undefined" && !exports.acorn) { throw syntaxError; } + // Reused empty array added for node fields that are always empty. + + var empty = []; + // ## Token types // The assignment of fine-grained, information-carrying type objects @@ -369,7 +456,7 @@ if (typeof exports != "undefined" && !exports.acorn) { // Objective-J @ keywords var _implementation = {keyword: "implementation"}, _outlet = {keyword: "outlet"}, _accessors = {keyword: "accessors"}; - var _end = {keyword: "end"}, _import = {keyword: "import", afterImport: true}; + var _end = {keyword: "end"}, _import = {keyword: "import"}; var _action = {keyword: "action"}, _selector = {keyword: "selector"}, _class = {keyword: "class"}, _global = {keyword: "global"}; var _dictionaryLiteral = {keyword: "{"}, _arrayLiteral = {keyword: "["}; var _ref = {keyword: "ref"}, _deref = {keyword: "deref"}; @@ -396,11 +483,15 @@ if (typeof exports != "undefined" && !exports.acorn) { var _preElse = {keyword: "else"}; var _preEndif = {keyword: "endif"}; var _preElseIf = {keyword: "elif"}; + var _preElseIfTrue = {keyword: "elif (True)"}; + var _preElseIfFalse = {keyword: "elif (false)"}; var _prePragma = {keyword: "pragma"}; var _preDefined = {keyword: "defined"}; var _preBackslash = {keyword: "\\"} - + var _preError = {keyword: "error"}; + var _preWarning = {keyword: "warning"}; var _preprocessParamItem = {type: "preprocessParamItem"} + var _preprocessSkipLine = {type: "skipLine"} // Map keyword names to token types. @@ -430,9 +521,9 @@ if (typeof exports != "undefined" && !exports.acorn) { // Map Preprocessor keyword names to token types. - var keywordTypesPreprocess = {"define": _preDefine, "pragma": _prePragma, "ifdef": _preIfdef, "ifndef": _preIfndef, + var keywordTypesPreprocessor = {"define": _preDefine, "pragma": _prePragma, "ifdef": _preIfdef, "ifndef": _preIfndef, "undef": _preUndef, "if": _preIf, "endif": _preEndif, "else": _preElse, "elif": _preElseIf, - "defined": _preDefined}; + "defined": _preDefined, "warning": _preWarning, "error": _preError}; // Punctuation token types. Again, the `type` property is purely for debugging. @@ -463,7 +554,7 @@ if (typeof exports != "undefined" && !exports.acorn) { var _slash = {binop: 10, beforeExpr: true, preprocess: true}, _eq = {isAssign: true, beforeExpr: true, preprocess: true}; var _assign = {isAssign: true, beforeExpr: true}, _plusmin = {binop: 9, prefix: true, beforeExpr: true, preprocess: true}; - var _incdec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true}; + var _incdec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true, preprocess: true}; var _bin1 = {binop: 1, beforeExpr: true, preprocess: true}, _bin2 = {binop: 2, beforeExpr: true, preprocess: true}; var _bin3 = {binop: 3, beforeExpr: true, preprocess: true}, _bin4 = {binop: 4, beforeExpr: true, preprocess: true}; var _bin5 = {binop: 5, beforeExpr: true, preprocess: true}, _bin6 = {binop: 6, beforeExpr: true, preprocess: true}; @@ -477,7 +568,7 @@ if (typeof exports != "undefined" && !exports.acorn) { parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string}; - for (var kw in keywordTypes) exports.tokTypes[kw] = keywordTypes[kw]; + for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; // This is a trick taken from Esprima. It turns out that, on // non-Chrome browsers, to check whether a string is in a set, a @@ -555,7 +646,7 @@ if (typeof exports != "undefined" && !exports.acorn) { // The preprocessor keywords. - var isKeywordPreprocess = makePredicate("define pragma if ifdef ifndef else elif endif defined"); + var isKeywordPreprocessor = makePredicate("define undef pragma if ifdef ifndef else elif endif defined error warning"); // ## Character categories @@ -567,7 +658,7 @@ if (typeof exports != "undefined" && !exports.acorn) { var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/; var nonASCIIwhitespaceNoNewLine = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/; var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; - var nonASCIIidentifierChars = "\u0371-\u0374\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; + var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); @@ -582,17 +673,17 @@ if (typeof exports != "undefined" && !exports.acorn) { // Test whether a given character code starts an identifier. - function isIdentifierStart(code) { + var isIdentifierStart = exports.isIdentifierStart = function(code) { if (code < 65) return code === 36; if (code < 91) return true; if (code < 97) return code === 95; if (code < 123)return true; return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); - } + }; // Test whether a given character is part of an identifier. - function isIdentifierChar(code) { + var isIdentifierChar = exports.isIdentifierChar = function(code) { if (code < 48) return code === 36; if (code < 58) return true; if (code < 65) return false; @@ -600,7 +691,7 @@ if (typeof exports != "undefined" && !exports.acorn) { if (code < 97) return code === 95; if (code < 123)return true; return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); - } + }; // ## Tokenizer @@ -610,73 +701,119 @@ if (typeof exports != "undefined" && !exports.acorn) { function line_loc_t() { this.line = tokCurLine; this.column = tokPos - tokLineStart; + if (preprocessStackLastItem) { + var macro = preprocessStackLastItem.macro; + var locationOffset = macro.locationOffset; + if (locationOffset) { + var macroCurrentLine = locationOffset.line; + if (macroCurrentLine) this.line += macroCurrentLine; + var macroCurrentLineStart = locationOffset.column; + // Only add column offset if we are on the first line + if (macroCurrentLineStart) this.column += tokPosMacroOffset - (tokCurLine === 0 ? macroCurrentLineStart : 0); + } + } + } + + function PositionOffset(line, column) { + this.line = line; + this.column = column; + if (preprocessStackLastItem) { + var macro = preprocessStackLastItem.macro; + var locationOffset = macro.locationOffset; + if (locationOffset) { + var macroCurrentLine = locationOffset.line; + if (macroCurrentLine) this.line += macroCurrentLine; + var macroCurrentLineStart = locationOffset.column; + if (macroCurrentLineStart) this.column += macroCurrentLineStart; + } + } } // Reset the token state. Used at the start of a parse. function initTokenState() { - macros = Object.create(null); tokCurLine = 1; - tokPos = tokLineStart = 0; + tokPos = tokLineStart = lastTokMacroOffset = tokMacroOffset = tokPosMacroOffset = 0; tokRegexpAllowed = true; tokComments = null; tokSpaces = null; skipSpace(); } + // Reset the token state. Used at the start of a parse. + + function initPreprocessorState() { + macros = Object.create(null); + macrosIsPredicate = null; + preprocessParameterScope = null; + preTokParameterScope = null; + preprocessMacroParameterListMode = false; + preprocessIsParsingPreprocess = false; + preprocessStack = []; + preprocessStackLastItem = null; + preprocessOnlyTransformArgumentsForLastToken = null; + preNotSkipping = true; + preConcatenating = false; + preIfLevel = []; + } + // Called at the end of every token. Sets `tokEnd`, `tokVal`, // `tokCommentsAfter`, `tokSpacesAfter`, and `tokRegexpAllowed`, and skips the space // after the token, so that the next one's `tokStart` will point at // the right position. -var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _preEndif]; - - function finishToken(type, val) { - // If we get any of these preprocess tokens skip it and read next - if (type in preprocessTokens) return readToken(); - tokEnd = tokPos; - if (options.locations) tokEndLoc = new line_loc_t; + function finishToken(type, val, overrideTokEnd) { + if (overrideTokEnd) { + tokEnd = overrideTokEnd; + if (options.locations) tokEndLoc = preprocessOverrideTokLoc; + } else { + tokEnd = tokPos; + if (options.locations) tokEndLoc = new line_loc_t; + } tokType = type; skipSpace(); if (options.preprocess && input.charCodeAt(tokPos) === 35 && input.charCodeAt(tokPos + 1) === 35) { // '##' - var val1 = type === _name ? val : type.keyword; + var val1 = val != null ? val : type.keyword || type.type; tokPos += 2; - if (val1) { + if (val1 != null) { + // Save current line and current line start. This is needed when option.locations is true + var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); + // Save positions on first token to get start and end correct on node if cancatenated token is invalid + var saveTokInput = tokInput, saveTokEnd = tokEnd, saveTokStart = tokStart, start = tokStart + tokMacroOffset, variadicName = preprocessStackLastItem && preprocessStackLastItem.macro && preprocessStackLastItem.macro.variadicName; skipSpace(); - readToken(); - var val2 = tokType === _name ? tokVal : tokType.keyword; - if (val2) { - var concat = "" + val1 + val2, - code = concat.charCodeAt(0), - tok; - if (isIdentifierStart(code)) - tok = readWord(concat) !== false; - - // We might got a word token from the concatenation - if (tok) return tok; - // FIXME: Is not using the concatenated token - tok = getTokenFromCode(code, finishToken); - if (tok === false) { - unexpected(); - } - // We have now got another type of token from the concatenation - return tok; - } else { - // FIXME: Second token was not of right type. Save second token and return the first. When readToken is called again return the second. + if (variadicName && variadicName === input.slice(tokPos, tokPos + variadicName.length)) var isVariadic = true; + preConcatenating = true; + readToken(null, 2); // Don't transform macros + preConcatenating = false; + var val2 = tokVal != null ? tokVal : tokType.keyword || tokType.type; + if (val2 != null) { + // Skip token if it is a ',' concatenated with an empty variadic parameter + if (isVariadic && val1 === "," && val2 === "") return readToken(); + var concat = "" + val1 + val2, val2TokStart = tokStart + tokPosMacroOffset; + // If the macro defines anything add it to the preprocess input stack + var concatMacro = new Macro(null, concat, null, start, false, null, false, positionOffset); + var r = readTokenFromMacro(concatMacro, tokPosMacroOffset, preprocessStackLastItem ? preprocessStackLastItem.parameterDict : null, null, tokPos, next, null); + // Consumed the whole macro in one bite? If not the tokenizer can't create a single token from the two concatenated tokens + if (preprocessStackLastItem && preprocessStackLastItem.macro === concatMacro) { + tokType = type; + tokStart = saveTokStart; + tokEnd = saveTokEnd; + tokInput = saveTokInput; + tokPosMacroOffset = val2TokStart - val1.length; // reset the macro offset to the second token to get start and end correct on node + if (!isVariadic) /*raise(tokStart,*/console.log("Warning: pasting formed '" + concat + "', an invalid preprocessing token"); + } else return r; } } } - tokVal = val; lastTokCommentsAfter = tokCommentsAfter; lastTokSpacesAfter = tokSpacesAfter; tokCommentsAfter = tokComments; tokSpacesAfter = tokSpaces; tokRegexpAllowed = type.beforeExpr; - tokAfterImport = type.afterImport; } - function skipBlockComment() { + function skipBlockComment(lastIsNewlinePos, dontTrack) { var startLoc = options.onComment && options.locations && new line_loc_t; var start = tokPos, end = input.indexOf("*/", tokPos += 2); if (end === -1) raise(tokPos - 2, "Unterminated comment"); @@ -689,26 +826,30 @@ var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _pr tokLineStart = match.index + match[0].length; } } - if (options.onComment) - options.onComment(true, input.slice(start + 2, end), start, tokPos, - startLoc, options.locations && new line_loc_t); - if (options.trackComments) - (tokComments || (tokComments = [])).push(input.slice(start, end)); + if (!dontTrack) { + if (options.onComment) + options.onComment(true, input.slice(start + 2, end), start, tokPos, + startLoc, options.locations && new line_loc_t); + if (options.trackComments) + (tokComments || (tokComments = [])).push(input.slice(lastIsNewlinePos != null && options.trackCommentsIncludeLineBreak ? lastIsNewlinePos : start, tokPos)); + } } - function skipLineComment() { + function skipLineComment(lastIsNewlinePos, dontTrack) { var start = tokPos; var startLoc = options.onComment && options.locations && new line_loc_t; var ch = input.charCodeAt(tokPos+=2); - while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8329) { + while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { ++tokPos; ch = input.charCodeAt(tokPos); } - if (options.onComment) - options.onComment(false, input.slice(start + 2, tokPos), start, tokPos, - startLoc, options.locations && new line_loc_t); - if (options.trackComments) - (tokComments || (tokComments = [])).push(input.slice(start, tokPos)); + if (!dontTrack) { + if (options.onComment) + options.onComment(false, input.slice(start + 2, tokPos), start, tokPos, + startLoc, options.locations && new line_loc_t); + if (options.trackComments) + (tokComments || (tokComments = [])).push(input.slice(lastIsNewlinePos != null && options.trackCommentsIncludeLineBreak ? lastIsNewlinePos : start, tokPos)); + } } function preprocesSkipRestOfLine() { @@ -717,11 +858,15 @@ var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _pr // If the last none whitespace character is a '\' the line will continue on the the next line. // Here we break the way gcc works as it joins the lines first and then tokenize it. Because of // this we can't have a newline in the middle of a word. - while (tokPos < inputLen && ((ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8329) || last === 92)) { // White space and '\' + while (tokPos < inputLen && ((ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) || last === 92)) { // White space and '\' if (ch != 32 && ch != 9 && ch != 160 && (ch < 5760 || !nonASCIIwhitespaceNoNewLine.test(String.fromCharCode(ch)))) last = ch; ch = input.charCodeAt(++tokPos); } + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } } // Called at the start of the parse and after every token. Skips @@ -733,53 +878,90 @@ var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _pr function skipSpace() { tokComments = null; tokSpaces = null; - var spaceStart = tokPos; + onlySkipSpace(); + } + + function onlySkipSpace(dontSkipEOL, dontSkipMacroBoundary, dontSkipComments) { + var spaceStart = tokPos, + lastIsNewlinePos; for(;;) { var ch = input.charCodeAt(tokPos); if (ch === 32) { // ' ' ++tokPos; - } else if(ch === 13) { + } else if (ch === 13 && !dontSkipEOL) { + lastIsNewlinePos = tokPos; ++tokPos; var next = input.charCodeAt(tokPos); - if(next === 10) { + if (next === 10) { ++tokPos; } - if(options.locations) { + if (options.locations) { ++tokCurLine; tokLineStart = tokPos; } - } else if (ch === 10) { + } else if (ch === 10 && !dontSkipEOL) { + lastIsNewlinePos = tokPos; ++tokPos; - ++tokCurLine; - tokLineStart = tokPos; - } else if(ch < 14 && ch > 8) { + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + } else if (ch === 9) { ++tokPos; - } else if (ch === 47) { // '/' + } else if (ch === 47 && !dontSkipComments) { // '/' var next = input.charCodeAt(tokPos+1); if (next === 42) { // '*' if (options.trackSpaces) (tokSpaces || (tokSpaces = [])).push(input.slice(spaceStart, tokPos)); - skipBlockComment(); + skipBlockComment(lastIsNewlinePos); spaceStart = tokPos; } else if (next === 47) { // '/' if (options.trackSpaces) (tokSpaces || (tokSpaces = [])).push(input.slice(spaceStart, tokPos)); - skipLineComment(); + skipLineComment(lastIsNewlinePos); spaceStart = tokPos; } else break; - } else if (ch === 160) { // '\xa0' - ++tokPos; - } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { + } else if (ch === 160 || ch === 11 || ch === 12 || (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch)))) { // '\xa0', VT, FF, Unicode whitespaces ++tokPos; } else if (tokPos >= inputLen) { - if (options.preprocess && preprocessStack.length) { + if (options.preprocess) { + if (dontSkipMacroBoundary) return true; + if (!preprocessStack.length) break; // If we are at the end of the input inside a macro continue at last position var lastItem = preprocessStack.pop(); tokPos = lastItem.end; input = lastItem.input; inputLen = lastItem.inputLen; - lastEnd = lastItem.lastEnd; - lastStart = lastItem.lastStart; + tokCurLine = lastItem.currentLine; + tokLineStart = lastItem.currentLineStart; + /*tokStart = *///tokFirstStart = lastItem.tokStart; + //lastEnd = lastItem.lastEnd; + //lastStart = lastItem.lastStart; + preprocessOnlyTransformArgumentsForLastToken = lastItem.onlyTransformArgumentsForLastToken; + preprocessParameterScope = lastItem.parameterScope; + tokPosMacroOffset = lastItem.macroOffset; + // Set the last item + var lastIndex = preprocessStack.length; + preprocessStackLastItem = lastIndex ? preprocessStack[lastIndex - 1] : null; + onlySkipSpace(dontSkipEOL); + } else { + break; + } + } else if (ch === 92 && options.preprocess) { // '\' + // Check if we have an escaped newline. We are using a relaxed treatment of escaped newlines like gcc. + // We allow spaces, horizontal and vertical tabs, and form feeds between the backslash and the subsequent newline + var pos = tokPos + 1; + ch = input.charCodeAt(pos); + while (pos < inputLen && (ch === 32 || ch === 9 || ch === 11 || ch === 12 || (ch >= 5760 && nonASCIIwhitespaceNoNewLine.test(String.fromCharCode(ch))))) + ch = input.charCodeAt(++pos); + lineBreak.lastIndex = 0; + var match = lineBreak.exec(input.slice(pos, pos + 2)); + if (match && match.index === 0) { + tokPos = pos + match[0].length; + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } } else { break; } @@ -801,202 +983,216 @@ var preprocessTokens = [_preIf, _preIfdef, _preIfndef, _preElse, _preElseIf, _pr // The `forceRegexp` parameter is used in the one case where the // `tokRegexpAllowed` trick does not work. See `parseStatement`. - function readToken_dot(code, finishToken) { + function readToken_dot(code, finisher) { var next = input.charCodeAt(tokPos+1); - if (next >= 48 && next <= 57) return readNumber(String.fromCharCode(code), finishToken); + if (next >= 48 && next <= 57) return readNumber(String.fromCharCode(code), finisher); if (next === 46 && options.objj && input.charCodeAt(tokPos+2) === 46) { //'.' tokPos += 3; - return finishToken(_dotdotdot); + return finisher(_dotdotdot); } ++tokPos; - return finishToken(_dot); + return finisher(_dot); } - function readToken_slash(finishToken) { // '/' + function readToken_slash(finisher) { // '/' var next = input.charCodeAt(tokPos+1); if (tokRegexpAllowed) {++tokPos; return readRegexp();} - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(_slash, 1, finishToken); + if (next === 61) return finishOp(_assign, 2, finisher); + return finishOp(_slash, 1, finisher); } - function readToken_mult_modulo(finishToken) { // '%*' + function readToken_mult_modulo(finisher) { // '%*' var next = input.charCodeAt(tokPos+1); - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(_bin10, 1, finishToken); + if (next === 61) return finishOp(_assign, 2, finisher); + return finishOp(_bin10, 1, finisher); } - function readToken_pipe_amp(code, finishToken) { // '|&' + function readToken_pipe_amp(code, finisher) { // '|&' var next = input.charCodeAt(tokPos+1); - if (next === code) return finishOp(code === 124 ? _bin1 : _bin2, 2, finishToken); - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(code === 124 ? _bin3 : _bin5, 1, finishToken); + if (next === code) return finishOp(code === 124 ? _bin1 : _bin2, 2, finisher); + if (next === 61) return finishOp(_assign, 2, finisher); + return finishOp(code === 124 ? _bin3 : _bin5, 1, finisher); } - function readToken_caret(finishToken) { // '^' + function readToken_caret(finisher) { // '^' var next = input.charCodeAt(tokPos+1); - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(_bin4, 1, finishToken); + if (next === 61) return finishOp(_assign, 2, finisher); + return finishOp(_bin4, 1, finisher); } - function readToken_plus_min(code, finishToken) { // '+-' + function readToken_plus_min(code, finisher) { // '+-' var next = input.charCodeAt(tokPos+1); - if (next === code) return finishOp(_incdec, 2, finishToken); - if (next === 61) return finishOp(_assign, 2, finishToken); - return finishOp(_plusmin, 1, finishToken); + if (next === code) return finishOp(_incdec, 2, finisher); + if (next === 61) return finishOp(_assign, 2, finisher); + return finishOp(_plusmin, 1, finisher); } - function readToken_lt_gt(code, finishToken) { // '<>' - if (tokAfterImport && options.objj && code === 60) { // '<' - var str = []; - for (;;) { - if (tokPos >= inputLen) raise(tokStart, "Unterminated import statement"); + function readToken_lt_gt(code, finisher) { // '<>' + if (tokType === _import && options.objj && code === 60) { // '<' + for (var start = tokPos + 1;;) { var ch = input.charCodeAt(++tokPos); - if (ch === 62) { // '>' - ++tokPos; - return finishToken(_filename, String.fromCharCode.apply(null, str)); - } - str.push(ch); + if (ch === 62) // '>' + return finisher(_filename, input.slice(start, tokPos++)); + if (tokPos >= inputLen || ch === 13 || ch === 10 || ch === 8232 || ch === 8233) + raise(tokStart, "Unterminated import statement"); } } var next = input.charCodeAt(tokPos+1); var size = 1; if (next === code) { size = code === 62 && input.charCodeAt(tokPos+2) === 62 ? 3 : 2; - if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1, finishToken); - return finishOp(_bin8, size, finishToken); + if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1, finisher); + return finishOp(_bin8, size, finisher); } if (next === 61) size = input.charCodeAt(tokPos+2) === 61 ? 3 : 2; - return finishOp(_bin7, size, finishToken); + return finishOp(_bin7, size, finisher); } - function readToken_eq_excl(code, finishToken) { // '=!' + function readToken_eq_excl(code, finisher) { // '=!' var next = input.charCodeAt(tokPos+1); - if (next === 61) return finishOp(_bin6, input.charCodeAt(tokPos+2) === 61 ? 3 : 2, finishToken); - return finishOp(code === 61 ? _eq : _prefix, 1, finishToken); + if (next === 61) return finishOp(_bin6, input.charCodeAt(tokPos+2) === 61 ? 3 : 2, finisher); + return finishOp(code === 61 ? _eq : _prefix, 1, finisher); } - function readToken_at(code, finishToken) { // '@' + function readToken_at(code, finisher) { // '@' var next = input.charCodeAt(++tokPos); if (next === 34 || next === 39) // Read string if "'" or '"' - return readString(next, finishToken); + return readString(next, finisher); if (next === 123) // Read dictionary literal if "{" - return finishToken(_dictionaryLiteral); - if (next === 91) // Ready array literal if "[" - return finishToken(_arrayLiteral); + return finisher(_dictionaryLiteral); + if (next === 91) // Read array literal if "[" + return finisher(_arrayLiteral); var word = readWord1(), token = objJAtKeywordTypes[word]; - if (!token) raise(tokPos, "Unrecognized Objective-J keyword '@" + word + "'"); - return finishToken(token); + if (!token) raise(tokStart, "Unrecognized Objective-J keyword '@" + word + "'"); + return finisher(token); } -// True if we are skipping token when finding #else or #endif after and #if - -var preNotSkipping = true; -var preIfLevel = 0; - - function readToken_preprocess(finishTokenFunction) { // '#' + function readToken_preprocess(finisher) { // '#' ++tokPos; - preprocessReadToken(); + preprocessSkipSpace(); + preprocessReadToken(false, true); // Dont track and it is a preprocessToken switch (preTokType) { case _preDefine: - preprocessReadToken(); - var macroIdentifierEnd = preTokEnd; - var macroIdentifier = preprocessGetIdent(); - // '(' Must follow directly after identifier to be a valid macro with parameters - if (input.charCodeAt(macroIdentifierEnd) === 40) { // '(' - preprocessExpect(_parenL); - var parameters = []; - var first = true; - while (!preprocessEat(_parenR)) { - if (!first) preprocessExpect(_comma, "Expected ',' between macro parameters"); else first = false; - parameters.push(preprocessGetIdent()); - } + if (preNotSkipping) { + preprocessParseDefine(); + } else { + return finisher(_preDefine); } - var start = tokPos = preTokStart; - preprocesSkipRestOfLine(); - var macroString = input.slice(start, tokPos); - macroString = macroString.replace(/\\/g, " "); - options.preprocessAddMacro(new Macro(macroIdentifier, macroString, parameters)); break; case _preUndef: preprocessReadToken(); options.preprocessUndefineMacro(preprocessGetIdent()); - preprocesSkipRestOfLine(); break; case _preIf: if (preNotSkipping) { - preIfLevel++; - preprocessReadToken(); - var expr = preprocessParseExpression(); + // We dont't allow regex when parsing preprocess expression + var saveTokRegexpAllowed = tokRegexpAllowed; + tokRegexpAllowed = false; + preIfLevel.push(_preIf); + preprocessReadToken(false, false, true); + var expr = preprocessParseExpression(true); // Process macros var test = preprocessEvalExpression(expr); - if (!test) - preNotSkipping = false - preprocessSkipToElseOrEndif(!test); + if (!test) { + preNotSkipping = false; + preprocessSkipToElseOrEndif(); + } + tokRegexpAllowed = saveTokRegexpAllowed; } else { - return finishTokenFunction(_preIf); + return finisher(_preIf); } break; case _preIfdef: if (preNotSkipping) { - preIfLevel++; + preIfLevel.push(_preIf); preprocessReadToken(); var ident = preprocessGetIdent(); - var test = options.preprocessGetMacro(ident); - if (!test) + var test = options.preprocessIsMacro(ident); + if (!test) { preNotSkipping = false - //preprocessExpect(_eol); - preprocessSkipToElseOrEndif(!test); + preprocessSkipToElseOrEndif(); + } } else { - //preprocesSkipRestOfLine(); - return finishTokenFunction(_preIfdef); + return finisher(_preIfdef); } break; case _preIfndef: if (preNotSkipping) { - preIfLevel++; + preIfLevel.push(_preIf); preprocessReadToken(); var ident = preprocessGetIdent(); - var test = options.preprocessGetMacro(ident); - if (test) + var test = options.preprocessIsMacro(ident); + if (test) { preNotSkipping = false - //preprocessExpect(_eol); - preprocessSkipToElseOrEndif(test); + preprocessSkipToElseOrEndif(); + } } else { //preprocesSkipRestOfLine(); - return finishTokenFunction(_preIfndef); + return finisher(_preIfndef); } break; case _preElse: - if (preIfLevel) { + if (preIfLevel.length) { if (preNotSkipping) { - preNotSkipping = false; - finishTokenFunction(_preElse); - preprocessReadToken(); - preprocessSkipToElseOrEndif(true, true); // no else + if(preIfLevel[preIfLevel.length - 1] === _preIf) { + preIfLevel[preIfLevel.length - 1] = _preElse; + preNotSkipping = false; + finisher(_preElse); + preprocessReadToken(); + preprocessSkipToElseOrEndif(true); // no else + } else + raise(preTokStart, "#else after #else"); } else { - return finishTokenFunction(_preElse); + preIfLevel[preIfLevel.length - 1] = _preElse; + return finisher(_preElse); } } else raise(preTokStart, "#else without #if"); break; - case _preEndif: - if (preIfLevel) { + case _preElseIf: + if (preIfLevel.length) { if (preNotSkipping) { - preIfLevel--; + if(preIfLevel[preIfLevel.length - 1] === _preIf) { + preNotSkipping = false; + finisher(_preElseIf); + preprocessReadToken(); + preprocessSkipToElseOrEndif(true); // no else + } else + raise(preTokStart, "#elsif after #else"); + } else { + // We dont't allow regex when parsing preprocess expression + var saveTokRegexpAllowed = tokRegexpAllowed; + tokRegexpAllowed = false; + preNotSkipping = true; + preprocessReadToken(false, false, true); + var expr = preprocessParseExpression(true); + preNotSkipping = false; + tokRegexpAllowed = saveTokRegexpAllowed; + var test = preprocessEvalExpression(expr); + return finisher(test ? _preElseIfTrue : _preElseIfFalse); + } + } else + raise(preTokStart, "#elif without #if"); + break; + + case _preEndif: + if (preIfLevel.length) { + if (preNotSkipping) { + preIfLevel.pop(); break; } } else { raise(preTokStart, "#endif without #if"); } - return finishTokenFunction(_preEndif); + return finisher(_preEndif); break; case _prePragma: @@ -1007,20 +1203,88 @@ var preIfLevel = 0; preprocesSkipRestOfLine(); break; + case _preWarning: + preprocessReadToken(false, false, true); + var expr = preprocessParseExpression(); + console.log("Warning: " + String(preprocessEvalExpression(expr))); + break; + + case _preError: + var start = preTokStart; + preprocessReadToken(false, false, true); + var expr = preprocessParseExpression(); + raise(start, "Error: " + String(preprocessEvalExpression(expr))); + break; + default: + if (preprocessStackLastItem) { + // If the current macro has parameters check if this word is one of them and should be stringifyed + if (preprocessStackLastItem.parameterDict && preprocessStackLastItem.macro.isParameterFunction()(preTokVal)) { + var macro = preprocessStackLastItem.parameterDict[preTokVal]; + if (macro) { + return finishToken(_string, macro.macro); + } + } + } raise(preTokStart, "Invalid preprocessing directive"); preprocesSkipRestOfLine(); // Return the complete line as a token to make it possible to create a PreProcessStatement if we are between two statements - return finishTokenFunction(_preprocess); + return finisher(_preprocess); //raise(tokPos, "Invalid preprocessing directive '" + (preTokType.keyword || preTokVal) + "' " + input.slice(tokStart, tokPos)); } - // Drop this token and read next non preprocess token - finishToken(_preprocess); + // Drop the regular token as this was a preprocess token and then read next token + //tokPos = preTokStart; + if (preTokType === _eol && options.trackSpaces) { + if (tokSpaces && tokSpaces.length) + tokSpaces.push("\n" + tokSpaces.pop()); + else + tokSpaces = ["\n"]; + } + preprocessFinishToken(_preprocess, null, null, true); // skipEOL return readToken(); } + function preprocessParseDefine() { + preprocessIsParsingPreprocess = true; + preprocessReadToken(); + var macroIdentifierEnd = preTokEnd; + var macroIdentifier = preprocessGetIdent(); + // '(' Must follow directly after identifier to be a valid macro with parameters + if (input.charCodeAt(macroIdentifierEnd) === 40) { // '(' + preprocessExpect(_parenL); + var parameters = []; + var variadic = false; + var first = true; + while (!preprocessEat(_parenR)) { + if (variadic) raise(preTokStart, "Variadic parameter must be last"); + if (!first) preprocessExpect(_comma, "Expected ',' between macro parameters"); else first = false; + parameters.push(preprocessEat(_dotdotdot) ? variadic = true && "__VA_ARGS__" : preprocessGetIdent()); + if (preprocessEat(_dotdotdot)) variadic = true; + } + } + var start = preTokStart; + var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); + while(preTokType !== _eol && preTokType !== _eof) + preprocessReadToken(); + + var macroString = input.slice(start, preTokStart); + macroString = macroString.replace(/\\/g, " "); + // If variadic get the last parameter for the variadic parameter name + options.preprocessAddMacro(new Macro(macroIdentifier, macroString, parameters, start, false, null, variadic && parameters[parameters.length - 1], positionOffset)); + preprocessIsParsingPreprocess = false; + } + function preprocessEvalExpression(expr) { - return exports.walk.recursive(expr, {}, { + return walk.recursive(expr, {}, { + LogicalExpression: function(node, st, c) { + var left = node.left, right = node.right; + switch (node.operator) { + case "||": + return c(left, st) || c(right, st); + case "&&": + return c(left, st) && c(right, st); + } + }, BinaryExpression: function(node, st, c) { var left = node.left, right = node.right; switch(node.operator) { @@ -1038,65 +1302,97 @@ var preIfLevel = 0; return c(left, st) < c(right, st); case ">": return c(left, st) > c(right, st); - case "=": + case "^": + return c(left, st) ^ c(right, st); + case "&": + return c(left, st) & c(right, st); + case "|": + return c(left, st) | c(right, st); case "==": + return c(left, st) == c(right, st); case "===": return c(left, st) === c(right, st); + case "!=": + return c(left, st) != c(right, st); + case "!==": + return c(left, st) !== c(right, st); case "<=": return c(left, st) <= c(right, st); case ">=": return c(left, st) >= c(right, st); - case "&&": - return c(left, st) && c(right, st); - case "||": - return c(left, st) || c(right, st); + case ">>": + return c(left, st) >> c(right, st); + case ">>>": + return c(left, st) >>> c(right, st); + case "<<": + return c(left, st) << c(right, st); + } + }, + UnaryExpression: function(node, st, c) { + var arg = node.argument; + switch (node.operator) { + case "-": + return -c(arg, st); + case "+": + return +c(arg, st); + case "!": + return !c(arg, st); + case "~": + return ~c(arg, st); } }, Literal: function(node, st, c) { return node.value; }, Identifier: function(node, st, c) { - var name = node.name, - macro = options.preprocessGetMacro(name); - return (macro && parseInt(macro.macro)) || 0; + // If it is not macro expanded it should be counted as a zero + return 0; }, DefinedExpression: function(node, st, c) { - return !!options.preprocessGetMacro(node.id.name); + var objectNode = node.object; + if (objectNode.type === "Identifier") { + // If the macro has parameters it will not expand and we have to check here if it exists + var name = objectNode.name, + macro = options.preprocessGetMacro(name) || preprocessBuiltinMacro(name); + return macro || 0; + } else { + return c(objectNode, st); + } } }, {}); } - function getTokenFromCode(code, finishToken, allowEndOfLineToken) { + function getTokenFromCode(code, finisher, allowEndOfLineToken) { switch(code) { // The interpretation of a dot depends on whether it is followed // by a digit. case 46: // '.' - return readToken_dot(code, finishToken); + return readToken_dot(code, finisher); // Punctuation tokens. - case 40: ++tokPos; return finishToken(_parenL); - case 41: ++tokPos; return finishToken(_parenR); - case 59: ++tokPos; return finishToken(_semi); - case 44: ++tokPos; return finishToken(_comma); - case 91: ++tokPos; return finishToken(_bracketL); - case 93: ++tokPos; return finishToken(_bracketR); - case 123: ++tokPos; return finishToken(_braceL); - case 125: ++tokPos; return finishToken(_braceR); - case 58: ++tokPos; return finishToken(_colon); - case 63: ++tokPos; return finishToken(_question); + case 40: ++tokPos; return finisher(_parenL); + case 41: ++tokPos; return finisher(_parenR); + case 59: ++tokPos; return finisher(_semi); + case 44: ++tokPos; return finisher(_comma); + case 91: ++tokPos; return finisher(_bracketL); + case 93: ++tokPos; return finisher(_bracketR); + case 123: ++tokPos; return finisher(_braceL); + case 125: ++tokPos; return finisher(_braceR); + case 58: ++tokPos; return finisher(_colon); + case 63: ++tokPos; return finisher(_question); // '0x' is a hexadecimal number. case 48: // '0' var next = input.charCodeAt(tokPos+1); - if (next === 120 || next === 88) return readHexNumber(finishToken); + if (next === 120 || next === 88) return readHexNumber(finisher); // Anything else beginning with a digit is an integer, octal // number, or float. case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9 - return readNumber(false, finishToken); + return readNumber(false, finisher); // Quotes produce strings. case 34: case 39: // '"', "'" - return readString(code, finishToken); + return readString(code, finisher); // Operators are parsed inline in tiny state machines. '=' (61) is // often referred to. `finishOp` simply skips the amount of @@ -1104,116 +1400,203 @@ var preIfLevel = 0; // of the type given by its first argument. case 47: // '/' - return readToken_slash(finishToken); + return readToken_slash(finisher); case 37: case 42: // '%*' - return readToken_mult_modulo(finishToken); + return readToken_mult_modulo(finisher); case 124: case 38: // '|&' - return readToken_pipe_amp(code, finishToken); + return readToken_pipe_amp(code, finisher); case 94: // '^' - return readToken_caret(finishToken); + return readToken_caret(finisher); case 43: case 45: // '+-' - return readToken_plus_min(code, finishToken); + return readToken_plus_min(code, finisher); case 60: case 62: // '<>' - return readToken_lt_gt(code, finishToken, finishToken); + return readToken_lt_gt(code, finisher); case 61: case 33: // '=!' - return readToken_eq_excl(code, finishToken); + return readToken_eq_excl(code, finisher); case 126: // '~' - return finishOp(_prefix, 1, finishToken); + return finishOp(_prefix, 1, finisher); case 64: // '@' if (options.objj) - return readToken_at(code, finishToken); + return readToken_at(code, finisher); return false; case 35: // '#' if (options.preprocess) { - return readToken_preprocess(finishToken); + if (preprocessIsParsingPreprocess) { + ++tokPos; + return finisher(_preprocess); + } + // Check if it is the first token on the line + lineBreak.lastIndex = 0; + var match = lineBreak.exec(input.slice(lastEnd, tokPos)); + if (lastEnd !== 0 && lastEnd !== tokPos && !match) { + if (preprocessStackLastItem) { + // Stringify next token + return preprocessStringify(); + } else { + raise(tokPos, "Preprocessor directives may only be used at the beginning of a line"); + } + } + + return readToken_preprocess(finisher); } return false; case 92: // '\' if (options.preprocess) { - return finishOp(_preBackslash, 1, finishToken); + return finishOp(_preBackslash, 1, finisher); } return false; } - if (allowEndOfLineToken && newline.test(String.fromCharCode(code))) { - return finishOp(_eol, 1, finishToken); + if (allowEndOfLineToken) { + var r; + if (code === 13) { + r = finishOp(_eol, input.charCodeAt(tokPos+1) === 10 ? 2 : 1, finisher); + } else if (code === 10 || code === 8232 || code === 8233) { + r = finishOp(_eol, 1, finisher); + } else { + return false; + } + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + return r; } return false; -} + } -// Returns true if it stops at a line break + // Stringify next token and return with it as a literal string. - function preprocessSkipSpace() { - while (tokPos < inputLen) { - var ch = input.charCodeAt(tokPos); - if (ch === 32 || ch === 9 || ch === 160 || (ch >= 5760 && nonASCIIwhitespaceNoNewLine.test(String.fromCharCode(ch)))) { - ++tokPos; - } else if (ch === 92) { // '\' - // Check if we have an escaped newline. We are using a relaxed treatment of escaped newlines like gcc. - // We allow spaces, horizontal and vertical tabs, and form feeds between the backslash and the subsequent newline - var pos = tokPos + 1; - ch = input.charCodeAt(pos); - while (pos < inputLen && (ch === 32 || ch === 9 || ch === 11 || ch === 12 || (ch >= 5760 && nonASCIIwhitespaceNoNewLine.test(String.fromCharCode(ch))))) - ch = input.charCodeAt(++pos); - lineBreak.lastIndex = 0; - var match = lineBreak.exec(input.slice(pos, pos + 2)); - if (match && match.index === 0) { - tokPos = pos + match[0].length; - } else { - return false; - } + function preprocessStringify() { + var saveStackLength = preprocessStack.length, saveLastItem = preprocessStackLastItem; + tokPos++; // Skip '#' + preConcatenating = true; // To get empty sting if macro is empty + next(false, 2); // Don't prescan arguments + preConcatenating = false; + var start = tokStart + tokMacroOffset; + var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); + var string; + if (tokType === _string) { + var quote = tokInput.slice(tokStart, tokStart + 1); + var escapedQuote = quote === '"' ? '\\"' : "'"; + string = escapedQuote; + string += preprocessStringifyEscape(tokVal); + string += escapedQuote; + } else { + string = tokVal != null ? tokVal : tokType.keyword || tokType.type; + } + while (preprocessStack.length > saveStackLength && saveLastItem === preprocessStack[saveStackLength - 1]) { + preConcatenating = true; // To get empty sting if macro is empty + next(false, 2); // Don't prescan arguments + preConcatenating = false; + // Add a space if there is one or more withespaces + if (lastEnd !== tokStart) string += " "; + if (tokType === _string) { + var quote = tokInput.slice(tokStart, tokStart + 1); + var escapedQuote = quote === '"' ? '\\"' : "'"; + string += escapedQuote; + string += preprocessStringifyEscape(tokVal); + string += escapedQuote; } else { - lineBreak.lastIndex = 0; - var match = lineBreak.exec(input.slice(tokPos, tokPos + 2)); - return match && match.index === 0; + string += tokVal != null ? tokVal : tokType.keyword || tokType.type; } } + var stringifyMacro = new Macro(null, '"' + string + '"', null, start, false, null, false, positionOffset); + return readTokenFromMacro(stringifyMacro, tokPosMacroOffset, null, null, tokPos, next); } - function preprocessSkipToElseOrEndif(test, skipElse) { - if (test) { - var ifLevel = 0; - while (ifLevel > 0 || (preTokType != _preEndif && (preTokType != _preElse || skipElse))) { - switch (preTokType) { - case _preIf: - case _preIfdef: - case _preIfndef: - ifLevel++; - break; + // Escape characters in stringify string. - case _preEndif: - ifLevel--; - break; - - case _eof: - preNotSkipping = true; - raise(preTokStart, "Missing #endif"); - } - preprocessReadToken(); + function preprocessStringifyEscape(aString) { + for (var escaped = "", pos = 0, size = aString.length, ch = aString.charCodeAt(pos); pos < size; ch = aString.charCodeAt(++pos)) { + switch (ch) { + case 34: escaped += '\\\\\\"'; break; // " + case 10: escaped += "\\\\n"; break; // LF (\n) + case 13: escaped += "\\\\r"; break; // CR (\r) + case 9: escaped += "\\\\t"; break; // TAB (\t) + case 8: escaped += "\\\\b"; break; // BS (\b) + case 11: escaped += "\\\\v"; break; // VT (\v) + case 0x00A0: escaped += "\\\\u00A0"; break; // CR (\r) + case 0x2028: escaped += "\\\\u2028"; break; // LINE SEPARATOR + case 0x2029: escaped += "\\\\u2029"; break; // PARAGRAPH SEPARATOR + case 92: escaped += "\\\\"; break; // BACKSLASH + default: escaped += aString.charAt(pos); break; } - preNotSkipping = true; - if (preTokType === _preEndif) - preIfLevel--; } + return escaped; } - function preprocessReadToken() { + // Skip whitespaces sometimes without line breaks + // Returns true if it stops at a line break. + + function preprocessSkipSpace(skipComments, skipEOL) { + onlySkipSpace(!skipEOL); + lineBreak.lastIndex = 0; + var match = lineBreak.exec(input.slice(tokPos, tokPos + 2)); + return match && match.index === 0; + } + + function preprocessSkipToElseOrEndif(skipElse) { + var ifLevel = []; + while (ifLevel.length > 0 || (preTokType !== _preEndif && ((preTokType !== _preElse && preTokType !== _preElseIfTrue) || skipElse))) { + switch (preTokType) { + case _preIf: + case _preIfdef: + case _preIfndef: + ifLevel.push(_preIf); + break; + + case _preElse: + if (ifLevel[ifLevel.length - 1] !== _preIf) + raise(preTokStart, "#else after #else"); + else + ifLevel[ifLevel.length - 1] = _preElse; + break; + + case _preElseIf: + if (ifLevel[ifLevel.length - 1] !== _preIf) + raise(preTokStart, "#elif after #else"); + break; + + case _preEndif: + ifLevel.pop(); + break; + + case _eof: + preNotSkipping = true; + raise(preTokStart, "Missing #endif"); + } + preprocessReadToken(true); + } + preNotSkipping = true; + if (preTokType === _preEndif) + preIfLevel.pop(); + } + +// preprocessToken is used to cancel preNotSkipping when calling from readToken_preprocess. +// FIXME: Refactor to not use this parameter preprocessToken. It is kind of confusing and it should be possible to do in another way + function preprocessReadToken(skipComments, preprocessToken, processMacros) { preTokStart = tokPos; preTokInput = input; - if (tokPos >= inputLen) return _eof; + preTokParameterScope = preprocessParameterScope; + if (tokPos >= inputLen) return preprocessFinishToken(_eof); var code = input.charCodeAt(tokPos); - if (preprocessMacroParamterListMode && code !== 41 && code !== 44) { // ')', ',' + if (!preprocessToken && !preNotSkipping && code !== 35) { // '#' + // If we are skipping take the whole line if the token does not start with '#' (preprocess tokens) + preprocesSkipRestOfLine(); + return preprocessFinishToken(_preprocessSkipLine, input.slice(preTokStart, tokPos++)); + } else if (preprocessMacroParameterListMode && code !== 41 && code !== 44) { // ')', ',' var parenLevel = 0; // If we are parsing a macro parameter list parentheses within each argument must balance while(tokPos < inputLen && (parenLevel || (code !== 41 && code !== 44))) { // ')', ',' @@ -1221,47 +1604,76 @@ var preIfLevel = 0; parenLevel++; if (code === 41) // ')' parenLevel--; + if (code === 34 || code === 39) {// '"' "'" We have a quote so go all the way to the end of the quote + var quote = code; + code = input.charCodeAt(++tokPos); + while(tokPos < inputLen && code !== quote) { + if (code === 92) { // '\' + code = input.charCodeAt(++tokPos); + if (code !== quote) continue; + } + code = input.charCodeAt(++tokPos); + } + } code = input.charCodeAt(++tokPos); } return preprocessFinishToken(_preprocessParamItem, input.slice(preTokStart, tokPos)); } - if (isIdentifierStart(code) || (code === 92 /* '\' */ && input.charCodeAt(tokPos +1) === 117 /* 'u' */)) return preprocessReadWord(); - if (getTokenFromCode(code, preprocessFinishToken, true) === false) { + if (isIdentifierStart(code) || (code === 92 /* '\' */ && input.charCodeAt(tokPos +1) === 117 /* 'u' */)) return preprocessReadWord(processMacros); + if (getTokenFromCode(code, skipComments ? preprocessFinishTokenSkipComments : preprocessFinishToken, true) === false) { // Allow _eol token // If we are here, we either found a non-ASCII identifier // character, or something that's entirely disallowed. var ch = String.fromCharCode(code); - if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return preprocessReadWord(); + if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return preprocessReadWord(processMacros); raise(tokPos, "Unexpected character '" + ch + "'"); } } - function preprocessReadWord() { + function preprocessReadWord(processMacros) { var word = readWord1(); - preprocessFinishToken(isKeywordPreprocess(word) ? keywordTypesPreprocess[word] : _name, word); + var type = _name; + if (processMacros && options.preprocess) { + var readMacroWordReturn = readMacroWord(word, preprocessNext); + if (readMacroWordReturn === true) + return true; + } + + if (!containsEsc && isKeywordPreprocessor(word)) type = keywordTypesPreprocessor[word]; + preprocessFinishToken(type, word, readMacroWordReturn); // If readMacroWord returns anything except 'true' it is the real tokEndPos } - function preprocessFinishToken(type, val) { + function preprocessFinishToken(type, val, overrideTokEnd, skipEOL) { + preTokType = type; + preTokVal = val; + preTokEnd = overrideTokEnd || tokPos; + //tokRegexpAllowed = type.beforeExpr; + preprocessSkipSpace(false, skipEOL); // Dont skip comments + } + +// FIXME: Find out if this is really used? + function preprocessFinishTokenSkipComments(type, val) { preTokType = type; preTokVal = val; preTokEnd = tokPos; - preprocessSkipSpace(); + preprocessSkipSpace(true); // 'true' for skip comments } // Continue to the next token. - function preprocessNext() { - preLastStart = tokStart; - preLastEnd = tokEnd; - //lastEndLoc = tokEndLoc; - return preprocessReadToken(); + function preprocessNext(stealth, onlyTransformArguments, forceRegexp, processMacros) { + if (!stealth) { + preLastStart = tokStart; + preLastEnd = tokEnd; + } + return preprocessReadToken(false, false, processMacros); } // Predicate that tests whether the next token is of the given // type, and if yes, consumes it as a side effect. - function preprocessEat(type) { + function preprocessEat(type, processMacros) { if (preTokType === type) { - preprocessNext(); + preprocessNext(false, false, null, processMacros); return true; } } @@ -1269,35 +1681,40 @@ var preIfLevel = 0; // Expect a token of a given type. If found, consume it, otherwise, // raise with errorMessage or an unexpected token error. - function preprocessExpect(type, errorMessage) { - if (preTokType === type) preprocessReadToken(); + function preprocessExpect(type, errorMessage, processMacros) { + if (preTokType === type) preprocessReadToken(processMacros); else raise(preTokStart, errorMessage || "Unexpected token"); } - function preprocessGetIdent() { - var ident = preTokType === _name ? preTokVal : ((!options.forbidReserved || preTokType.okAsIdent) && preTokType.keyword) || raise(preTokStart, "Expected Macro identifier"); - preprocessNext(); + function debug() { + // debugger; + } + function preprocessGetIdent(processMacros) { + var ident = preTokType === _name ? preTokVal : ((!options.forbidReserved || preTokType.okAsIdent) && preTokType.keyword) || debug(); //raise(preTokStart, "Expected Macro identifier"); + //tokRegexpAllowed = false; + preprocessNext(false, false, null, processMacros); return ident; } - function preprocessParseIdent() { + function preprocessParseIdent(processMacros) { var node = startNode(); - node.name = preprocessGetIdent(); + node.name = preprocessGetIdent(processMacros); return preprocessFinishNode(node, "Identifier"); } // Parse an expression — either a single token that is an // expression, an expression started by a keyword like `defined`, // or an expression wrapped in punctuation like `()`. + // When `processMacros` is true any macros will we transformed to its definition - function preprocessParseExpression() { - return preprocessParseExprOps(); + function preprocessParseExpression(processMacros) { + return preprocessParseExprOps(processMacros); } // Start the precedence parser. - function preprocessParseExprOps() { - return preprocessParseExprOp(preprocessParseMaybeUnary(), -1); + function preprocessParseExprOps(processMacros) { + return preprocessParseExprOp(preprocessParseMaybeUnary(processMacros), -1, processMacros); } // Parse binary operators with the operator precedence parsing @@ -1306,7 +1723,7 @@ var preIfLevel = 0; // defer further parser to one of its callers when it encounters an // operator that has a lower precedence than the set it is parsing. - function preprocessParseExprOp(left, minPrec) { + function preprocessParseExprOp(left, minPrec, processMacros) { var prec = preTokType.binop; if (prec) { if (!preTokType.preprocess) raise(preTokStart, "Unsupported macro operator"); @@ -1314,10 +1731,10 @@ var preIfLevel = 0; var node = startNodeFrom(left); node.left = left; node.operator = preTokVal; - preprocessNext(); - node.right = preprocessParseExprOp(preprocessParseMaybeUnary(), prec); - var node = preprocessFinishNode(node, /*/&&|\|\|/.test(node.operator) ? "LogicalExpression" : */"BinaryExpression"); - return preprocessParseExprOp(node, minPrec); + preprocessNext(false, false, null, processMacros); + node.right = preprocessParseExprOp(preprocessParseMaybeUnary(processMacros), prec, processMacros); + var node = preprocessFinishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression"); + return preprocessParseExprOp(node, minPrec, processMacros); } } return left; @@ -1325,43 +1742,43 @@ var preIfLevel = 0; // Parse an unary expression if possible - function preprocessParseMaybeUnary() { + function preprocessParseMaybeUnary(processMacros) { if (preTokType.preprocess && preTokType.prefix) { var node = startNode(); - node.operator = tokVal; + node.operator = preTokVal; node.prefix = true; - preprocessNext(); - node.argument = preprocessParseMaybeUnary(); + preprocessNext(false, false, null, processMacros); + node.argument = preprocessParseMaybeUnary(processMacros); return preprocessFinishNode(node, "UnaryExpression"); } - return preprocessParseExprAtom(); + return preprocessParseExprAtom(processMacros); } // Parse an atomic macro expression — either a single token that is an // expression, an expression started by a keyword like `defined`, // or an expression wrapped in punctuation like `()`. - function preprocessParseExprAtom() { + function preprocessParseExprAtom(processMacros) { switch (preTokType) { case _name: - return preprocessParseIdent(); + return preprocessParseIdent(processMacros); case _num: case _string: - return preprocessParseStringNumLiteral(); + return preprocessParseStringNumLiteral(processMacros); case _parenL: var tokStart1 = preTokStart; - preprocessNext(); - var val = preprocessParseExpression(); + preprocessNext(false, false, null, processMacros); + var val = preprocessParseExpression(processMacros); val.start = tokStart1; val.end = preTokEnd; - preprocessExpect(_parenR, "Expected closing ')' in macro expression"); + preprocessExpect(_parenR, "Expected closing ')' in macro expression", processMacros); return val; case _preDefined: var node = startNode(); - preprocessNext(); - node.id = preprocessParseIdent(); + preprocessNext(false, false, null, processMacros); + node.object = preprocessParseDefinedExpression(processMacros); return preprocessFinishNode(node, "DefinedExpression"); default: @@ -1369,11 +1786,36 @@ var preIfLevel = 0; } } - function preprocessParseStringNumLiteral() { + // Parse an 'Defined' macro expression — either a single token that is an + // identifier, number, string or an expression wrapped in punctuation like `()`. + + function preprocessParseDefinedExpression(processMacros) { + switch (preTokType) { + case _name: + return preprocessParseIdent(processMacros); + + case _num: case _string: + return preprocessParseStringNumLiteral(processMacros); + + case _parenL: + var tokStart1 = preTokStart; + preprocessNext(false, false, null, processMacros); + var val = preprocessParseDefinedExpression(processMacros); + val.start = tokStart1; + val.end = preTokEnd; + preprocessExpect(_parenR, "Expected closing ')' in macro expression", processMacros); + return val; + + default: + unexpected(); + } + } + + function preprocessParseStringNumLiteral(processMacros) { var node = startNode(); node.value = preTokVal; node.raw = preTokInput.slice(preTokStart, preTokEnd); - preprocessNext(); + preprocessNext(false, false, null, processMacros); return preprocessFinishNode(node, "Literal"); } @@ -1383,20 +1825,26 @@ var preIfLevel = 0; return node; } - function readToken(forceRegexp) { - tokStart = tokPos; - tokInput = input; - if (options.locations) tokStartLoc = new line_loc_t; + function readToken(forceRegexp, onlyTransformMacroArguments, stealth) { tokCommentsBefore = tokComments; tokSpacesBefore = tokSpaces; + if (!forceRegexp) tokStart = tokPos; + else tokPos = tokStart + 1; + if (!stealth) { + tokFirstStart = tokStart; + tokFirstInput = input; + } + tokInput = input; + tokMacroOffset = tokPosMacroOffset; + preTokParameterScope = preprocessParameterScope; + if (options.locations) tokStartLoc = new line_loc_t; if (forceRegexp) return readRegexp(); - if (tokPos >= inputLen) - return finishToken(_eof); + if (tokPos >= inputLen) return finishToken(_eof); var code = input.charCodeAt(tokPos); // Identifier or keyword. '\uXXXX' sequences are allowed in // identifiers, so '\' also dispatches to that. - if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord(); + if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord(null, onlyTransformMacroArguments, forceRegexp); var tok = getTokenFromCode(code, finishToken); @@ -1404,16 +1852,16 @@ var preIfLevel = 0; // If we are here, we either found a non-ASCII identifier // character, or something that's entirely disallowed. var ch = String.fromCharCode(code); - if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord(); + if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord(null, onlyTransformMacroArguments, forceRegexp); raise(tokPos, "Unexpected character '" + ch + "'"); } return tok; } - function finishOp(type, size, finishToken) { + function finishOp(type, size, finisher) { var str = input.slice(tokPos, tokPos + size); tokPos += size; - finishToken(type, str); + finisher(type, str); } // Parse a regular expression. Some context-awareness is necessary, @@ -1463,17 +1911,17 @@ var preIfLevel = 0; return total; } - function readHexNumber(finishToken) { + function readHexNumber(finisher) { tokPos += 2; // 0x var val = readInt(16); if (val == null) raise(tokStart + 2, "Expected hexadecimal number"); if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number"); - return finishToken(_num, val); + return finisher(_num, val); } // Read an integer, octal integer, or floating-point number. - function readNumber(startsWithDot, finishToken) { + function readNumber(startsWithDot, finisher) { var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48; if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number"); if (input.charCodeAt(tokPos) === 46) { @@ -1485,7 +1933,7 @@ var preIfLevel = 0; if (next === 69 || next === 101) { // 'eE' next = input.charCodeAt(++tokPos); if (next === 43 || next === 45) ++tokPos; // '+-' - if (readInt(10) === null) raise(start, "Invalid number") + if (readInt(10) === null) raise(start, "Invalid number"); isFloat = true; } if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number"); @@ -1495,22 +1943,20 @@ var preIfLevel = 0; else if (!octal || str.length === 1) val = parseInt(str, 10); else if (/[89]/.test(str) || strict) raise(start, "Invalid number"); else val = parseInt(str, 8); - return finishToken(_num, val); + return finisher(_num, val); } // Read a string value, interpreting backslash-escapes. - var rs_str = []; - - function readString(quote, finishToken) { + function readString(quote, finisher) { tokPos++; - rs_str.length = 0; + var out = ""; for (;;) { if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant"); var ch = input.charCodeAt(tokPos); if (ch === quote) { ++tokPos; - return finishToken(_string, String.fromCharCode.apply(null, rs_str)); + return finisher(_string, out); } if (ch === 92) { // '\' ch = input.charCodeAt(++tokPos); @@ -1521,30 +1967,30 @@ var preIfLevel = 0; ++tokPos; if (octal) { if (strict) raise(tokPos - 2, "Octal literal in strict mode"); - rs_str.push(parseInt(octal, 8)); + out += String.fromCharCode(parseInt(octal, 8)); tokPos += octal.length - 1; } else { switch (ch) { - case 110: rs_str.push(10); break; // 'n' -> '\n' - case 114: rs_str.push(13); break; // 'r' -> '\r' - case 120: rs_str.push(readHexChar(2)); break; // 'x' - case 117: rs_str.push(readHexChar(4)); break; // 'u' - case 85: rs_str.push(readHexChar(8)); break; // 'U' - case 116: rs_str.push(9); break; // 't' -> '\t' - case 98: rs_str.push(8); break; // 'b' -> '\b' - case 118: rs_str.push(11); break; // 'v' -> '\u000b' - case 102: rs_str.push(12); break; // 'f' -> '\f' - case 48: rs_str.push(0); break; // 0 -> '\0' + case 110: out += "\n"; break; // 'n' -> '\n' + case 114: out += "\r"; break; // 'r' -> '\r' + case 120: out += String.fromCharCode(readHexChar(2)); break; // 'x' + case 117: out += String.fromCharCode(readHexChar(4)); break; // 'u' + case 85: out += String.fromCharCode(readHexChar(8)); break; // 'U' + case 116: out += "\t"; break; // 't' -> '\t' + case 98: out += "\b"; break; // 'b' -> '\b' + case 118: out += "\u000b"; break; // 'v' -> '\u000b' + case 102: out += "\f"; break; // 'f' -> '\f' + case 48: out += "\0"; break; // 0 -> '\0' case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; // '\r\n' case 10: // ' \n' if (options.locations) { tokLineStart = tokPos; ++tokCurLine; } break; - default: rs_str.push(ch); break; + default: out += String.fromCharCode(ch); break; } } } else { - if (ch === 13 || ch === 10 || ch === 8232 || ch === 8329) raise(tokStart, "Unterminated string constant"); - rs_str.push(ch); // '\' + if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant"); + out += String.fromCharCode(ch); // '\' ++tokPos; } } @@ -1602,63 +2048,13 @@ var preIfLevel = 0; // words when necessary. Argument preReadWord is used to concatenate // The word is then passed in from caller. - function readWord(preReadWord) { + function readWord(preReadWord, onlyTransformMacroArguments, forceRegexp) { var word = preReadWord || readWord1(); var type = _name; - var reservedError; if (options.preprocess) { - var macro; - var i = preprocessStack.length; - if (i > 0) { - var lastItem = preprocessStack[i - 1]; - // If the current macro has parameters check if this word is one of them and should be translated - if (lastItem.parameterDict && lastItem.macro.isParameterFunction()(word)) { - macro = lastItem.parameterDict[word]; - } - } - // Does the word match agains any of the know macro names - if (!macro && options.preprocessIsMacro(word)) - macro = options.preprocessGetMacro(word); - if (macro) { - var macroStart = tokStart; - var parameters; - var hasParameters = macro.parameters; - var nextIsParenL; - if (hasParameters) - nextIsParenL = tokPos < inputLen && input.charCodeAt(tokPos) === 40; // '(' - if (!hasParameters || nextIsParenL) { - // Now we know that we have a matching macro. Get parameters if needed - var macroString = macro.macro; - var lastTokPos = tokPos; - if (nextIsParenL) { - var first = true; - var noParams = 0; - parameters = Object.create(null); - preprocessReadToken(); - preprocessMacroParamterListMode = true; - preprocessExpect(_parenL); - lastTokPos = tokPos; - while (!preprocessEat(_parenR)) { - if (!first) preprocessExpect(_comma, "Expected ',' between macro parameters"); else first = false; - var ident = hasParameters[noParams++]; - var val = preTokVal; - preprocessExpect(_preprocessParamItem); - parameters[ident] = new Macro(ident, val); - lastTokPos = tokPos; - } - preprocessMacroParamterListMode = false; - } - // If the macro defines anything add it to the preprocess input stack - if (macroString) { - preprocessStack.push({macro: macro, parameterDict: parameters, start: macroStart, end:lastTokPos, input: input, inputLen: inputLen, lastStart: tokStart, lastEnd: lastTokPos}); - input = macroString; - inputLen = macroString.length; - tokPos = 0; - } - // Now read the next token - return next(); - } - } + var readMacroWordReturn = readMacroWord(word, next, onlyTransformMacroArguments, forceRegexp); + if (readMacroWordReturn === true) + return true; } if (!containsEsc) { @@ -1669,18 +2065,273 @@ var preIfLevel = 0; strict && isStrictReservedWord(word)) raise(tokStart, "The keyword '" + word + "' is reserved"); } - return finishToken(type, word); + return finishToken(type, word, readMacroWordReturn); // If readMacroWord returns anything except 'true' it is the real tokEndPos } - function Macro(ident, macro, parameters) { + // If the word is a macro return true as the token is already finished. If not just return 'undefined'. + + function readMacroWord(word, nextFinisher, onlyTransformArguments, forceRegexp) { + var macro, + lastStackItem = preprocessStackLastItem, + oldParameterScope = preprocessParameterScope; + if (lastStackItem) { + var scope = preTokParameterScope || preprocessStackLastItem; + // If the current macro has parameters check if this word is one of them and should be translated + if (scope.parameterDict && scope.macro.isParameterFunction()(word)) { + macro = scope.parameterDict[word]; + // If it is a variadic macro and we can't find anything in the variadic parameter just get next token + if (!macro && scope.macro.variadicName === word) { + // Don't do this if we are stringifying or concatenating as we then want an empty string + if (preConcatenating) { + finishToken(_name, ""); + return true; + } else { + onlySkipSpace(); + nextFinisher(true, onlyTransformArguments, forceRegexp, true); // Stealth and Preprocess macros. + } + return true; + } + // Lets look ahead to find out if we find a '##' for token concatenate + // We don't want to prescan spaces across macro boundary as the macro stack will fall apart + // So we do a special prescan if we have to cross a boundary all in the name of speed + if (onlySkipSpace(true, true)) { // don't skip EOL and don't skip macro boundary. + if (preprocessPrescanFor(35, 35)) // Prescan across boundary for '##' as we crossed a boundary + onlyTransformArguments = 2; + } else if (input.charCodeAt(tokPos) === 35 && input.charCodeAt(tokPos + 1) === 35) { // '##' + onlyTransformArguments = 2; + } + preprocessParameterScope = macro && macro.parameterScope; + onlyTransformArguments--; + } + } + // Does the word match against any of the known macro names + // Don't match if: + // 1. We already has found a argument macro + // 2. We are doing concatenating. Here it is only valid for the last token. + if (!macro && (!onlyTransformArguments && !preprocessOnlyTransformArgumentsForLastToken || tokPos < inputLen) && options.preprocessIsMacro(word)) { + preprocessParameterScope = null; + macro = options.preprocessGetMacro(word); + if (macro) { + // Check if this macro is already referenced by looking in the stack + // Don't do it if the input in the stack is an argument. We want to simulate 'expand arguments first' + if (!preprocessStackLastItem || !preprocessStackLastItem.macro.isArgument) { + var i = preprocessStack.length, + lastMacroItem; + while (i > 0) { + var item = preprocessStack[--i], + macroItem = item.macro; + if (macroItem.identifier === word && !(lastMacroItem && lastMacroItem.isArgument)) { + macro = null; + } + lastMacroItem = macroItem; + } + } + } else { + macro = preprocessBuiltinMacro(word); + } + } + if (macro) { + var macroStart = tokStart; + var parameters; + var hasParameters = macro.parameters; + var nextIsParenL; + if (hasParameters) { + // Ok, we should have parameters for the macro. Lets look ahead to find out if we find a '(' + // First save current position and loc for tokEndPos + var pos = tokPos; + var loc; + if (options.locations) loc = new line_loc_t; + if ((onlySkipSpace(true, true) && preprocessPrescanFor(40)) || input.charCodeAt(tokPos) === 40) { // '(' + nextIsParenL = true; + } else { + // We didn't find a '(' so don't transform to the macro. Return the real tokEndPos so we get correct token end values. + preprocessOverrideTokEndLoc = loc; + return pos; + } + } + if (!hasParameters || nextIsParenL) { + // Now we know that we have a matching macro. Get parameters if needed + var macroString = macro.macro; + //var lastTokPos = tokPos; + if (nextIsParenL) { + var variadicName = macro.variadicName; + var first = true; + var noParams = 0; + parameters = Object.create(null); + onlySkipSpace(true); + //preprocessReadToken(); + //preprocessMacroParameterListMode = true; + //preprocessExpect(_parenL); + //lastTokPos = tokPos; + if (input.charCodeAt(tokPos++) !== 40) raise(tokPos - 1, "Expected '(' before macro prarameters"); + onlySkipSpace(true, true, true); + var code = input.charCodeAt(tokPos++); + while (tokPos < inputLen && code !== 41) { + if (first) + first = false; + else + if (code === 44) { // ',' + onlySkipSpace(true, true, true); + code = input.charCodeAt(tokPos++); + } else + raise(tokPos - 1, "Expected ',' between macro parameters"); + var ident = hasParameters[noParams++]; + var variadicAndLastParameter = variadicName && hasParameters.length === noParams; + var paramStart = tokPos - 1, parenLevel = 0; + // Calculate current line and current line start. + var positionOffset = options.locations && new PositionOffset(tokCurLine, tokLineStart); + // When parsing a macro parameter list parentheses within each argument must balance + // If it is variadic and we are on the last paramter collect all the rest of the parameters + while(tokPos < inputLen && (parenLevel || (code !== 41 && (code !== 44 || variadicAndLastParameter)))) { // ')', ',' + if (code === 40) // '(' + parenLevel++; + if (code === 41) // ')' + parenLevel--; + if (code === 34 || code === 39) {// '"' "'" We have a quote so go all the way to the end of the quote + var quote = code; + code = input.charCodeAt(tokPos++); + while(tokPos < inputLen && code !== quote) { + if (code === 92) { // '\' + code = input.charCodeAt(tokPos++); + if (code !== quote) continue; + } + code = input.charCodeAt(tokPos++); + } + } + code = input.charCodeAt(tokPos++); + } + var val = input.slice(paramStart, tokPos - 1); + //var val = preTokType === _preprocessParamItem ? preTokVal : ""; + parameters[ident] = new Macro(ident, val, null, paramStart + tokMacroOffset, true, preTokParameterScope || preprocessStackLastItem, false, positionOffset); // true = 'Is argument', false = 'Not varadic' + } + if (code !== 41) raise(tokPos, "Expected ')' after macro prarameters"); + onlySkipSpace(true, true); // Don't skip EOL and don't skip macro boundary + //preprocessMacroParameterListMode = false; + //preprocessExpect(_parenR); + } + // If the macro defines anything add it to the preprocess input stack + return readTokenFromMacro(macro, tokPosMacroOffset, parameters, oldParameterScope, tokPos, nextFinisher, onlyTransformArguments, forceRegexp); + } + } + } + + // Here we pre scan for first and second character. + // The first thing should be to skip spaces and comments + // Return true if the first characters after spaces are first and second + // This is very simular to the function onlySkipSpace. Maybe the same + // function can be used with some refactoring? + function preprocessPrescanFor(first, second) { + var i = preprocessStack.length; + stackloop: + while (i-- > 0) { + var stackItem = preprocessStack[i], + scanPos = stackItem.end, + scanInput = stackItem.input, + scanInputLen = stackItem.inputLen; + + for(;;) { + var ch = scanInput.charCodeAt(scanPos); + if (ch === 32) { // ' ' + ++scanPos; + } else if (ch === 13) { + ++scanPos; + var next = scanInput.charCodeAt(scanPos); + if (next === 10) { + ++scanPos; + } + } else if (ch === 10) { + ++scanPos; + } else if (ch === 9) { + ++scanPos; + } else if (ch === 47) { // '/' + var next = scanInput.charCodeAt(scanPos+1); + if (next === 42) { // '*' + var end = scanInput.indexOf("*/", scanPos += 2); + if (end === -1) raise(scanPos - 2, "Unterminated comment"); + scanPos = end + 2; + } else if (next === 47) { // '/' + ch = scanInput.charCodeAt(scanPos += 2); + while (scanPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { + ++scanPos; + ch = scanInput.charCodeAt(scanPos); + } + } else break stackloop; + } else if (ch === 160 || ch === 11 || ch === 12 || (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch)))) { // '\xa0', VT, FF, Unicode whitespaces + ++scanPos; + } else if (scanPos >= scanInputLen) { + continue stackloop; + } else if (ch === 92) { // '\' + // Check if we have an escaped newline. We are using a relaxed treatment of escaped newlines like gcc. + // We allow spaces, horizontal and vertical tabs, and form feeds between the backslash and the subsequent newline + var pos = scanPos + 1; + ch = scanInput.charCodeAt(pos); + while (pos < scanInputLen && (ch === 32 || ch === 9 || ch === 11 || ch === 12 || (ch >= 5760 && nonASCIIwhitespaceNoNewLine.test(String.fromCharCode(ch))))) + ch = scanInput.charCodeAt(++pos); + lineBreak.lastIndex = 0; + var match = lineBreak.exec(scanInput.slice(pos, pos + 2)); + if (match && match.index === 0) { + scanPos = pos + match[0].length; + } else { + break stackloop; + } + } else { + break stackloop; + } + } + } + return scanInput.charCodeAt(scanPos) === first && (second == null || scanInput.charCodeAt(scanPos + 1) === second); + } + + // Push macro to stack and start read from it. + // Just read next token if the macro is empty + function readTokenFromMacro(macro, macroOffset, parameters, parameterScope, end, nextFinisher, onlyTransformArguments, forceRegexp) { + var macroString = macro.macro; + // If we are evaluation a macro expresion an empty macro definition means true or '1' + if(!macroString && nextFinisher === preprocessNext) macroString = "1"; + if (macroString) { + preprocessStackLastItem = {macro: macro, macroOffset: macroOffset, parameterDict: parameters, /*start: macroStart,*/ end:end, inputLen: inputLen, tokStart: tokStart, onlyTransformArgumentsForLastToken: preprocessOnlyTransformArgumentsForLastToken, currentLine: tokCurLine, currentLineStart: tokLineStart/*, lastStart: lastStart, lastEnd: lastEnd*/}; + if (parameterScope) preprocessStackLastItem.parameterScope = parameterScope; + preprocessStackLastItem.input = input; + preprocessStack.push(preprocessStackLastItem); + preprocessOnlyTransformArgumentsForLastToken = onlyTransformArguments; + input = macroString; + inputLen = macroString.length; + tokPosMacroOffset = macro.start; + tokPos = 0; + tokCurLine = 0; + tokLineStart = 0; + } else if (preConcatenating) { + // If we are concatenating or stringifying and the macro is empty just make an empty string. + finishToken(_name, ""); + return true; + } + // Now read the next token + onlySkipSpace(); + nextFinisher(true, onlyTransformArguments, forceRegexp, true); // Stealth and Preprocess macros + return true; + } + + // ident is the identifier name for the macro + // macro is the macro string + // parameters is an array with the parameters for the macro + // start is the offset to where the macro is defined + // isArgument is true if the macro is a parameter + // parameterScope is the parameter scope + // varadicName is the name of the varadic parameter if it is a varadic macro + // locationOffset is the current line that the macro starts at and the position on the line + var Macro = exports.Macro = function Macro(ident, macro, parameters, start, isArgument, parameterScope, variadicName, locationOffset) { this.identifier = ident; - if (macro) this.macro = macro; + if (macro != null) this.macro = macro; if (parameters) this.parameters = parameters; + if (start != null) this.start = start; + if (isArgument) this.isArgument = true; + if (parameterScope) this.parameterScope = parameterScope; + if (variadicName) this.variadicName = variadicName; + if (locationOffset) this.locationOffset = locationOffset; } Macro.prototype.isParameterFunction = function() { - var y = (this.parameters || []).join(" "); - return this.isParameterFunctionVar || (this.isParameterFunctionVar = makePredicate(y)); + return this.isParameterFunctionVar || (this.isParameterFunctionVar = makePredicate((this.parameters || []).join(" "))); } // ## Parser @@ -1706,13 +2357,18 @@ var preIfLevel = 0; // ### Parser utilities // Continue to the next token. + // Stealth is to preserve lastEnd etc to get correct end positions on nodes when the + // preprocessor needs to drop one token and read next - function next() { - lastStart = tokStart; - lastEnd = tokEnd; - lastEndLoc = tokEndLoc; + function next(stealth, onlyTransformArguments, forceRegexp) { + if (!stealth) { + lastStart = tokStart; + lastEnd = tokEnd; + lastEndLoc = tokEndLoc; + lastTokMacroOffset = tokMacroOffset; + } nodeMessageSendObjectExpression = null; - return readToken(); + readToken(forceRegexp, onlyTransformArguments, stealth); } // Enter strict mode. Re-reads the next token to please pedantic @@ -1721,6 +2377,10 @@ var preIfLevel = 0; function setStrict(strct) { strict = strct; tokPos = lastEnd; + while (tokPos < tokLineStart) { + tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1; + --tokCurLine; + } skipSpace(); readToken(); } @@ -1730,7 +2390,7 @@ var preIfLevel = 0; function node_t() { this.type = null; - this.start = tokStart; + this.start = tokStart + tokMacroOffset; this.end = null; } @@ -1795,12 +2455,13 @@ var preIfLevel = 0; var lastFinishedNode; function finishNode(node, type) { + var nodeEnd = lastEnd + lastTokMacroOffset; node.type = type; - node.end = lastEnd; + node.end = nodeEnd; if (options.trackComments) { if (lastTokCommentsAfter) { node.commentsAfter = lastTokCommentsAfter; - tokCommentsAfter = null; + lastTokCommentsAfter = null; } else if (lastFinishedNode && lastFinishedNode.end === lastEnd && lastFinishedNode.commentsAfter) { node.commentsAfter = lastFinishedNode.commentsAfter; @@ -1823,7 +2484,7 @@ var preIfLevel = 0; if (options.locations) node.loc.end = lastEndLoc; if (options.ranges) - node.range[1] = lastEnd; + node.range[1] = nodeEnd; return node; } @@ -1848,7 +2509,7 @@ var preIfLevel = 0; function canInsertSemicolon() { return !options.strictSemicolons && - (tokType === _eof || tokType === _braceR || newline.test(tokInput.slice(lastEnd, tokStart)) || + (tokType === _eof || tokType === _braceR || newline.test(tokFirstInput.slice(lastEnd, tokFirstStart)) || (nodeMessageSendObjectExpression && options.objj)); } @@ -1918,7 +2579,7 @@ var preIfLevel = 0; // does not help. function parseStatement() { - if (tokType === _slash) + if (tokType === _slash || tokType === _assign && tokVal == "/=") readToken(true); var starttype = tokType, node = startNode(); @@ -2065,8 +2726,8 @@ var preIfLevel = 0; case _try: next(); node.block = parseBlock(); - node.handlers = []; - while (tokType === _catch) { + node.handler = null; + if (tokType === _catch) { var clause = startNode(); next(); expect(_parenL, "Expected '(' after 'catch'"); @@ -2076,10 +2737,11 @@ var preIfLevel = 0; expect(_parenR, "Expected closing ')' after catch"); clause.guard = null; clause.body = parseBlock(); - node.handlers.push(finishNode(clause, "CatchClause")); + node.handler = finishNode(clause, "CatchClause"); } + node.guardedHandlers = empty; node.finalizer = eat(_finally) ? parseBlock() : null; - if (!node.handlers.length && !node.finalizer) + if (!node.handler && !node.finalizer) raise(node.start, "Missing catch or finally clause"); return finishNode(node, "TryStatement"); @@ -2111,7 +2773,7 @@ var preIfLevel = 0; next(); return finishNode(node, "EmptyStatement"); - // This is a Objective-J statement + // Objective-J case _interface: if (options.objj) { next(); @@ -2152,7 +2814,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _implementation: if (options.objj) { next(); @@ -2176,19 +2838,6 @@ var preIfLevel = 0; } next(); } - if (tokVal === '<') { - next(); - var protocols = [], - first = true; - node.protocols = protocols; - while (tokVal !== '>') { - if (!first) - expect(_comma, "Expected ',' between protocol names"); - else first = false; - protocols.push(parseIdent(true)); - } - next(); - } if (eat(_braceL)) { node.ivardeclarations = []; for (;;) { @@ -2206,9 +2855,9 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _protocol: - // If next token is a left parenthesis it is a ProtocolLiternal expression so bail out + // If next token is a left parenthesis it is a ProtocolLiteral expression so bail out if (options.objj && input.charCodeAt(tokPos) !== 40) { // '(' next(); node.protocolname = parseIdent(true); @@ -2240,7 +2889,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _import: if (options.objj) { next(); @@ -2256,7 +2905,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _preprocess: if (options.objj) { next(); @@ -2264,7 +2913,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _class: if (options.objj) { next(); @@ -2273,7 +2922,7 @@ var preIfLevel = 0; } break; - // This is a Objective-J statement + // Objective-J case _global: if (options.objj) { next(); @@ -2290,7 +2939,6 @@ var preIfLevel = 0; return finishNode(node, "TypeDefStatement"); } break; - } // The indentation is one step to the right here to make sure it @@ -2331,7 +2979,6 @@ var preIfLevel = 0; if (outlet) decl.outlet = outlet; decl.ivartype = type; - // print("keyword: " + type.name + " is class " + type.typeisclass) decl.id = parseIdent(); if (strict && isStrictBadIdWord(decl.id.name)) raise(decl.id.start, "Binding " + decl.id.name + " in strict mode"); @@ -2470,11 +3117,11 @@ var preIfLevel = 0; while (!eat(_braceR)) { var stmt = parseStatement(); node.body.push(stmt); - if (first && isUseStrict(stmt)) { + if (first && allowStrict && isUseStrict(stmt)) { oldStrict = strict; setStrict(strict = true); } - first = false + first = false; } if (strict && !oldStrict) setStrict(false); return finishNode(node, "BlockStatement"); @@ -2582,7 +3229,7 @@ var preIfLevel = 0; // Start the precedence parser. function parseExprOps(noIn) { - return parseExprOp(parseMaybeUnary(noIn), -1, noIn); + return parseExprOp(parseMaybeUnary(), -1, noIn); } // Parse binary operators with the operator precedence parsing @@ -2599,7 +3246,7 @@ var preIfLevel = 0; node.left = left; node.operator = tokVal; next(); - node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn); + node.right = parseExprOp(parseMaybeUnary(), prec, noIn); var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression"); return parseExprOp(node, minPrec, noIn); } @@ -2609,13 +3256,14 @@ var preIfLevel = 0; // Parse unary operators, both prefix and postfix. - function parseMaybeUnary(noIn) { + function parseMaybeUnary() { if (tokType.prefix) { var node = startNode(), update = tokType.isUpdate; node.operator = tokVal; node.prefix = true; + tokRegexpAllowed = true; next(); - node.argument = parseMaybeUnary(noIn); + node.argument = parseMaybeUnary(); if (update) checkLVal(node.argument); else if (strict && node.operator === "delete" && node.argument.type === "Identifier") @@ -2697,17 +3345,17 @@ var preIfLevel = 0; return finishNode(node, "Literal"); case _parenL: - var tokStartLoc1 = tokStartLoc, tokStart1 = tokStart; + var tokStartLoc1 = tokStartLoc, macroOffset = tokMacroOffset, tokStart1 = tokStart + macroOffset; next(); var val = parseExpression(); val.start = tokStart1; - val.end = tokEnd; + val.end = tokEnd + macroOffset; if (options.locations) { val.loc.start = tokStartLoc1; val.loc.end = tokEndLoc; } if (options.ranges) - val.range = [tokStart1, tokEnd]; + val.range = [tokStart1, tokEnd + lastTokMacroOffset]; expect(_parenR, "Expected closing ')' in expression"); return val; @@ -2789,7 +3437,7 @@ var preIfLevel = 0; return finishNode(node, "Dereference"); default: - if(tokType.okAsIdent) + if (tokType.okAsIdent) return parseIdent(); unexpected(); @@ -2862,7 +3510,7 @@ var preIfLevel = 0; node.callee = parseSubscripts(parseExprAtom(false), true); if (eat(_parenL)) node.arguments = parseExprList(_parenR, tokType === _parenR ? null : parseExpression(true), false); - else node.arguments = []; + else node.arguments = empty; return finishNode(node, "NewExpression"); } @@ -3009,6 +3657,7 @@ var preIfLevel = 0; function parseIdent(liberal) { var node = startNode(); node.name = tokType === _name ? tokVal : (((liberal && !options.forbidReserved) || tokType.okAsIdent) && tokType.keyword) || unexpected(); + tokRegexpAllowed = false; next(); return finishNode(node, "Identifier"); } @@ -3029,11 +3678,12 @@ var preIfLevel = 0; // 'int' can be followed by an optinal 'long'. 'long' can be followed by an optional extra 'long' function parseObjectiveJType(startFrom) { - var node = startFrom ? startNodeFrom(startFrom) : startNode(); + var node = startFrom ? startNodeFrom(startFrom) : startNode(), allowProtocol = false; if (tokType === _name) { // It should be a class name node.name = tokVal; node.typeisclass = true; + allowProtocol = true; next(); } else { node.typeisclass = false; @@ -3041,59 +3691,65 @@ var preIfLevel = 0; // Do nothing more if it is 'void' if (!eat(_void)) { if (eat(_id)) { - // Is it 'id' followed by a '<' parse protocols. Do nothing more if it is only 'id' - if (tokVal === '<') { - var first = true, - protocols = []; - node.protocols = protocols; - do { - next(); - if (first) - first = false; - else - eat(_comma); - protocols.push(parseIdent(true)); - } while (tokVal !== '>'); - next(); - } + allowProtocol = true; } else { // Now check if it is some basic type or an approved combination of basic types var nextKeyWord; if (eat(_float) || eat(_boolean) || eat(_SEL) || eat(_double)) - nextKeyWord = tokType.keyword; + { + nextKeyWord = tokType.keyword; + } else { - if (eat(_signed) || eat(_unsigned)) - nextKeyWord = tokType.keyword || true; - if (eat(_char) || eat(_byte) || eat(_short)) { - if (nextKeyWord) - node.name += " " + nextKeyWord; - nextKeyWord = tokType.keyword || true; - } else { - if (eat(_int)) { - if (nextKeyWord) - node.name += " " + nextKeyWord; - nextKeyWord = tokType.keyword || true; - } - if (eat(_long)) { - if (nextKeyWord) - node.name += " " + nextKeyWord; - nextKeyWord = tokType.keyword || true; - if (eat(_long)) { - node.name += " " + nextKeyWord; - } - } - } - if (!nextKeyWord) { - // It must be a class name if it was not a basic type. // FIXME: This is not true - node.name = (!options.forbidReserved && tokType.keyword) || unexpected(); - node.typeisclass = true; - next(); - } + if (eat(_signed) || eat(_unsigned)) + nextKeyWord = tokType.keyword || true; + if (eat(_char) || eat(_byte) || eat(_short)) { + if (nextKeyWord) + node.name += " " + nextKeyWord; + nextKeyWord = tokType.keyword || true; + } else { + if (eat(_int)) { + if (nextKeyWord) + node.name += " " + nextKeyWord; + nextKeyWord = tokType.keyword || true; + } + if (eat(_long)) { + if (nextKeyWord) + node.name += " " + nextKeyWord; + nextKeyWord = tokType.keyword || true; + if (eat(_long)) { + node.name += " " + nextKeyWord; + } + } + } + if (!nextKeyWord) { + // It must be a class name if it was not a basic type. // FIXME: This is not true + node.name = (!options.forbidReserved && tokType.keyword) || unexpected(); + node.typeisclass = true; + allowProtocol = true; + next(); + } } } } } - return finishNode(node, "ObjectiveJType"); + if (allowProtocol) { + // Is it 'id' or classname followed by a '<' then parse protocols. + if (tokVal === '<') { + var first = true, + protocols = []; + node.protocols = protocols; + do { + next(); + if (first) + first = false; + else + eat(_comma); + protocols.push(parseIdent(true)); + } while (tokVal !== '>'); + next(); + } + } + return finishNode(node, "ObjectiveJType"); } -})(typeof exports === "undefined" ? (self.acorn = {}) : exports.acorn); +})(exports.acorn, exports.acorn.walk); diff --git a/Objective-J/acornwalk.js b/Objective-J/acornwalk.js index 8d5666529..456fec8a4 100644 --- a/Objective-J/acornwalk.js +++ b/Objective-J/acornwalk.js @@ -101,8 +101,7 @@ if (!exports.acorn) { }; exports.TryStatement = function(node, st, c) { c(node.block, st, "Statement"); - for (var i = 0; i < node.handlers.length; ++i) - c(node.handlers[i].body, st, "ScopeBody"); + if (node.handler) c(node.handler.body, st, "ScopeBody"); if (node.finalizer) c(node.finalizer, st, "Statement"); }; exports.WhileStatement = function(node, st, c) { @@ -267,10 +266,10 @@ if (!exports.acorn) { }, TryStatement: function(node, scope, c) { c(node.block, scope, "Statement"); - for (var i = 0; i < node.handlers.length; ++i) { - var handler = node.handlers[i], inner = makeScope(scope); - inner.vars[handler.param.name] = {type: "catch clause", node: handler.param}; - c(handler.body, inner, "ScopeBody"); + if (node.handler) { + var inner = makeScope(scope); + inner.vars[node.handler.param.name] = {type: "catch clause", node: node.handler.param}; + c(node.handler.body, inner, "ScopeBody"); } if (node.finalizer) c(node.finalizer, scope, "Statement"); }, diff --git a/README.markdown b/README.markdown index d8ffab704..eb1a3ef25 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/cappuccino/cappuccino.svg?branch=master)](https://travis-ci.org/cappuccino/cappuccino) +[![Build Status](https://travis-ci.org/cappuccino/cappuccino.svg?branch=master)](https://travis-ci.org/cappuccino/cappuccino) [![Join the chat at https://gitter.im/cappuccino/cappuccino](https://badges.gitter.im/cappuccino/cappuccino.svg)](https://gitter.im/cappuccino/cappuccino?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Welcome to Cappuccino! ====================== @@ -15,12 +15,15 @@ with the complexities of traditional web technologies like HTML, CSS, or even the DOM. The unpleasantries of building complex cross browser applications are abstracted away for you. -For more information, see . +For more information, see . Follow [@cappuccino](https://twitter.com/cappuccino) on Twitter for updates on the project. System Requirements ------------------- -To run Cappuccino applications, all you need is a web browser that understands -JavaScript. +To run Cappuccino applications, all you need is a HTML5 compliant web browser. + +To develop Cappuccino applications, all you need is a simple text editor and the starter package. + +However, Cappuccino's build system and the Xcode integration bring the eases of Cocoa development to web development. To build Cappuccino itself, please read below. More information is available here: [Getting and Building the Source](http://wiki.github.com/cappuccino/cappuccino/getting-and-building-the-source>). @@ -35,7 +38,7 @@ Getting Started --------------- To get started, download and install the current release version of Cappuccino: - $ curl https://raw.githubusercontent.com/cappuccino/cappuccino/v0.9.8/bootstrap.sh >/tmp/cappuccino_bootstrap.sh && bash /tmp/cappuccino_bootstrap.sh + $ curl https://raw.githubusercontent.com/cappuccino/cappuccino/v0.9.9/bootstrap.sh >/tmp/cappuccino_bootstrap.sh && bash /tmp/cappuccino_bootstrap.sh If you'd just like to get started using Cappuccino for your web apps, you are done. diff --git a/Rakefile b/Rakefile deleted file mode 100644 index ebd27ab18..000000000 --- a/Rakefile +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env ruby - -puts <<-eos - Building with rake is no longer supported, use "jake" instead. - - The commands are all the same (e.g. rake install -> jake install). - - If you do not have jake, you can run sudo ./bootstrap.sh to install jake and it's dependencies. - - PLEASE remove your Build folder ($CAPP_BUILD if it's set) when switching from rake to jake. -eos - -exit(1); diff --git a/Tests/AppKit/BundleTest/Info.plist b/Tests/AppKit/BundleTest/Info.plist new file mode 100644 index 000000000..bb4429725 --- /dev/null +++ b/Tests/AppKit/BundleTest/Info.plist @@ -0,0 +1,10 @@ + + + + + Main cib file base name + MainMenu.cib + CPBundleName + BundleTest + + diff --git a/Tests/AppKit/BundleTest/Resources/NSViewController.cib b/Tests/AppKit/BundleTest/Resources/NSViewController.cib new file mode 100644 index 000000000..45f5c8599 --- /dev/null +++ b/Tests/AppKit/BundleTest/Resources/NSViewController.cib @@ -0,0 +1 @@ +280NPLIST;1.0;D;K;4;$topD;K;18;CPCibObjectDataKeyD;K;6;CP$UIDd;1;2E;E;K;8;$objectsA;S;5;$nullD;K;10;$classnameS;16;_CPCibObjectDataK;8;$classesA;S;16;_CPCibObjectDataS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;1E;K;28;_CPCibObjectDataNamesKeysKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataNamesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataClassesKeysKeyD;K;6;CP$UIDd;1;0E;K;32;_CPCibObjectDataClassesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataConnectionsKeyD;K;6;CP$UIDd;1;4E;K;28;_CPCibObjectDataFrameworkKeyD;K;6;CP$UIDd;1;0E;K;26;_CPCibObjectDataNextOidKeyD;K;6;CP$UIDd;1;5E;K;30;_CPCibObjectDataObjectsKeysKeyD;K;6;CP$UIDd;1;6E;K;32;_CPCibObjectDataObjectsValuesKeyD;K;6;CP$UIDd;1;7E;K;26;_CPCibObjectDataOidKeysKeyD;K;6;CP$UIDd;1;8E;K;28;_CPCibObjectDataOidValuesKeyD;K;6;CP$UIDd;1;9E;K;28;_CPCibObjectDataFileOwnerKeyD;K;6;CP$UIDd;2;11E;K;33;_CPCibObjectDataVisibleWindowsKeyD;K;6;CP$UIDd;2;13E;E;D;K;10;$classnameS;7;CPArrayK;8;$classesA;S;7;CPArrayS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;15E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;18E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;18;_CPCibCustomObjectK;8;$classesA;S;18;_CPCibCustomObjectS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;19E;E;D;K;10;$classnameS;5;CPSetK;8;$classesA;S;5;CPSetS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;12E;K;15;CPSetObjectsKeyD;K;6;CP$UIDd;2;20E;E;D;K;10;$classnameS;20;CPCibOutletConnectorK;8;$classesA;S;20;CPCibOutletConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;11E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;18E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;21E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;22E;E;D;K;10;$classnameS;18;_CPCibClassSwapperK;8;$classesA;S;18;_CPCibClassSwapperS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;17E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;23E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;24E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;24E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;25E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;26E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;27E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;27E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;28E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;29E;K;30;_CPCibClassSwapperClassNameKeyD;K;6;CP$UIDd;2;30E;K;38;_CPCibClassSwapperOriginalClassNameKeyD;K;6;CP$UIDd;2;31E;E;S;16;CPViewControllerD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;4;viewS;13;CPApplicationd;1;0S;20;{{0, 0}, {480, 272}}d;2;12S;6;normalS;6;{1, 1}F;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;6;NSViewS;6;CPViewE;K;9;$archiverS;15;CPKeyedArchiverK;8;$versionS;6;100000E; \ No newline at end of file diff --git a/Tests/AppKit/BundleTest/Resources/NSViewController.xib b/Tests/AppKit/BundleTest/Resources/NSViewController.xib new file mode 100644 index 000000000..9a80f715c --- /dev/null +++ b/Tests/AppKit/BundleTest/Resources/NSViewController.xib @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Tests/AppKit/CPCollectionViewTest.j b/Tests/AppKit/CPCollectionViewTest.j index 2f73fd9e7..7f676150c 100644 --- a/Tests/AppKit/CPCollectionViewTest.j +++ b/Tests/AppKit/CPCollectionViewTest.j @@ -62,6 +62,49 @@ [self assert:[CPIndexSet indexSet] equals:[_collectionView selectionIndexes]]; } +- (void)_testCollectionViewItemIsSelected +{ + var itemPrototype = [[CPCollectionViewItem alloc] init]; + [_collectionView setItemPrototype:itemPrototype]; + [_collectionView setContent:[1, 2, 3]]; + + [_collectionView setSelectionIndexes:[CPIndexSet indexSetWithIndex:1]]; + [self assertTrue:[[_collectionView itemAtIndex:1] isSelected]]; + + [_collectionView setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]]; + [self assertTrue:[[_collectionView itemAtIndex:0] isSelected]]; + // The item is correctly deselected when the selection indexes changes. + [self assertFalse:[[_collectionView itemAtIndex:1] isSelected]]; +} + +- (void)testBindingSupport +{ + var content = [1,2,3]; + var ac = [[CPArrayController alloc] initWithContent:content]; + [ac setSelectsInsertedObjects:YES]; + [_collectionView bind:CPContentBinding toObject:ac withKeyPath:@"arrangedObjects" options:nil]; + [_collectionView bind:CPSelectionIndexesBinding toObject:ac withKeyPath:@"selectionIndexes" options:nil]; + + [self assert:[1,2,3] equals:[_collectionView content]]; + + [ac setContent:[6,7]]; + [self assert:[6,7] equals:[_collectionView content]]; + + [ac setSelectionIndexes:[CPIndexSet indexSetWithIndex:1]]; + [self assert:[CPIndexSet indexSetWithIndex:1] equals:[_collectionView selectionIndexes]]; + + [ac insertObject:4 atArrangedObjectIndex:2]; + [self assert:[CPIndexSet indexSetWithIndex:2] equals:[_collectionView selectionIndexes]]; + + // collection view selection is reflected on the array controller selection + [_collectionView setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]]; + [self assert:[CPIndexSet indexSetWithIndex:0] equals:[ac selectionIndexes]]; + + // collection view content is reflected on the array controller content + //[_collectionView setContent:[8,9]]; + //[self assert:[8,9] equals:[ac content]]; +} + - (void)testSetContentAndSelectionIndexes { // Changing the content does not automatically clear the selection indexes. The previous diff --git a/Tests/AppKit/CPPopUpButtonTest.j b/Tests/AppKit/CPPopUpButtonTest.j index a66ec0088..d18db9bb2 100644 --- a/Tests/AppKit/CPPopUpButtonTest.j +++ b/Tests/AppKit/CPPopUpButtonTest.j @@ -324,6 +324,36 @@ [self assert:2 equals:[[self button] indexOfSelectedItem]]; } +- (void)testSimpleObjectBindingArrayControllerWithLateContent +{ + var arrayController = [[CPArrayController alloc] init], + objectController = [[CPObjectController alloc] init], + testObject = [CPMutableDictionary dictionary], + martin = [CPDictionary dictionaryWithJSObject:{@"name": @"Martin"}], + malte = [CPDictionary dictionaryWithJSObject:{@"name": @"Malte"}], + johan = [CPDictionary dictionaryWithJSObject:{@"name": @"Johan"}], + menuObjects = [martin, malte, johan]; + + [testObject setObject:@"I'm a testObject" forKey:@"Who am I"]; + [objectController setContent:testObject]; + + [button bind:CPContentBinding toObject:arrayController withKeyPath:@"arrangedObjects" options:nil]; + [button bind:CPContentValuesBinding toObject:arrayController withKeyPath:@"arrangedObjects.name" options:nil]; + [button bind:CPSelectedObjectBinding toObject:objectController withKeyPath:@"selection.xxx" options:nil]; + + [testObject setObject:martin forKey:@"xxx"]; + [arrayController setContent:menuObjects]; + [self assert:0 equals:[[self button] indexOfSelectedItem]]; + [arrayController setContent:nil]; + [testObject setObject:malte forKey:@"xxx"]; + [arrayController setContent:menuObjects]; + [self assert:1 equals:[[self button] indexOfSelectedItem]]; + [arrayController setContent:nil]; + [testObject setObject:johan forKey:@"xxx"]; + [arrayController setContent:menuObjects]; + [self assert:2 equals:[[self button] indexOfSelectedItem]]; +} + - (void)testObjectBindingNullPlaceholderOption { var arrayController = [[CPArrayController alloc] init], diff --git a/Tests/AppKit/CPPredicateEditorTest.j b/Tests/AppKit/CPPredicateEditorTest.j index 32f040cc5..02d39a5fa 100644 --- a/Tests/AppKit/CPPredicateEditorTest.j +++ b/Tests/AppKit/CPPredicateEditorTest.j @@ -57,4 +57,31 @@ } } +- (void)testObjectValueWithRightWildcard +{ + [self _testAttribute:CPDateAttributeType value:[CPDate date]]; + [self _testAttribute:CPInteger16AttributeType value:2.0]; + [self _testAttribute:CPDoubleAttributeType value:2.5]; + [self _testAttribute:CPFloatAttributeType value:2.5]; + [self _testAttribute:CPStringAttributeType value:@"toto"]; + [self _testAttribute:CPBooleanAttributeType value:1]; +} + +- (void)_testAttribute:(int)attr value:(id)value +{ + var leftExp = [CPExpression expressionForKeyPath:@"keypath"], + rightExp = [CPExpression expressionForConstantValue:value], + predicate = [CPComparisonPredicate predicateWithLeftExpression:leftExp rightExpression:rightExp modifier:0 type:CPEqualToPredicateOperatorType options:0], + compound = [[CPCompoundPredicate alloc] initWithType:CPAndPredicateType subpredicates:@[predicate]]; + + var t1 = [[CPPredicateEditorRowTemplate alloc] initWithCompoundTypes:[0,1,2]], + t2 = [[CPPredicateEditorRowTemplate alloc] initWithLeftExpressions:@[leftExp] rightExpressionAttributeType:attr modifier:0 operators:@[CPEqualToPredicateOperatorType] options:0]; + + [_editor setRowTemplates:@[t1, t2]]; + + [_editor setObjectValue:compound]; + [_editor reloadPredicate]; + [self assert:compound equals:[_editor objectValue]]; +} + @end diff --git a/Tests/AppKit/CPTextViewTest.j b/Tests/AppKit/CPTextViewTest.j new file mode 100644 index 000000000..343b28dbc --- /dev/null +++ b/Tests/AppKit/CPTextViewTest.j @@ -0,0 +1,87 @@ +@import +@import + +@implementation CPTextViewTest : OJTestCase +{ + CPWindow theWindow; + CPTextView textView; + + CPString stringValue; + + OJMoqSpy delegateSpy +} + +- (void)setUp +{ + // This will init the global var CPApp which are used internally in the AppKit + [[CPApplication alloc] init]; + + // setup a reasonable table + theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0) styleMask:CPWindowNotSizable]; + + textView = [[CPTextView alloc] initWithFrame:CGRectMake(0,0,300,300)]; + + stringValue = @"My string is here"; + + [textView setString:stringValue]; + [textView setDelegate:self]; + + [[theWindow contentView] addSubview:textView]; + + delegateSpy = spy(self); +} + +- (void)tearDown +{ + [delegateSpy reset]; +} + +- (void)testMakeCPTextViewInstance +{ + [self assertNotNull:textView]; +} + +- (void)testTextViewSetStringMethod +{ + [self assert:stringValue equals:[textView stringValue]]; +} + +- (void)testTextViewSelectionRange +{ + //TODO : uncomment once ojtest will be up to date on travis + var range; + // + //[delegateSpy selector:@selector(textView:willChangeSelectionFromCharacterRange:toCharacterRange:) times:1 arguments:[textView, CPMakeRange(0,0), CPMakeRange(0, 18)]]; + //[delegateSpy selector:@selector(textViewDidChangeSelection:) times:1]; + //[textView selectAll:self]; + //range = [[textView selectedRanges] firstObject]; + //[self assert:0 equals:range.location]; + //[self assert:18 equals:range.length]; + // [delegateSpy verifyThatAllExpectationsHaveBeenMet]; + // + // + // [delegateSpy reset]; + // [delegateSpy selector:@selector(textView:willChangeSelectionFromCharacterRange:toCharacterRange:) times:1 arguments:[textView, CPMakeRange(0, 18), CPMakeRange(3, 6)]]; + // [delegateSpy selector:@selector(textViewDidChangeSelection:) times:1]; + //[textView setSelectedRange:CPMakeRange(3, 6)]; + //range = [[textView selectedRanges] firstObject]; + //[self assert:3 equals:range.location]; + //[self assert:6 equals:range.length]; + // [delegateSpy verifyThatAllExpectationsHaveBeenMet]; +} + +@end + +@implementation CPTextViewTest (CPTextViewTestDelegate) + +- (CPRange)textView:(CPTextView)aTextView willChangeSelectionFromCharacterRange:(CPRange)oldSelectedCharRange toCharacterRange:(CPRange)newSelectedCharRange +{ + return newSelectedCharRange; +} + +- (void)textViewDidChangeSelection:(CPNotification)aNotification +{ + +} + +@end \ No newline at end of file diff --git a/Tests/AppKit/CPViewControllerTest.j b/Tests/AppKit/CPViewControllerTest.j new file mode 100644 index 000000000..7ebed52fc --- /dev/null +++ b/Tests/AppKit/CPViewControllerTest.j @@ -0,0 +1,89 @@ +@import +@import + +var methodsCalled; + +@implementation CPViewControllerTest : OJTestCase +{ + CPBundle bundle; +} + +- (void)setUp +{ + bundle = [CPBundle bundleWithPath:@"Tests/AppKit/BundleTest"]; + [bundle loadWithDelegate:self]; +} + +- (void)bundleDidFinishLoading:(CPBundle)aBundle +{ + +} + +- (void)testViewControllerCallbacks +{ + methodsCalled = @[]; + + var expectedResult = @[@"viewDidLoad", + @"viewWillAppear", + @"viewDidAppear", + @"viewWillDisappear", + @"viewDidDisappear"]; + + [self assertTrue:[bundle isLoaded]]; + var viewController = [[ViewController alloc] initWithCibName:@"NSViewController.cib" bundle:bundle]; + [self assertNotNull:viewController]; + + var superview = [[CPView alloc] initWithFrame:CGRectMakeZero()]; + var view = [viewController view]; + + [superview addSubview:view]; + [view removeFromSuperview]; + [self assert:expectedResult equals:methodsCalled]; + + // Explicitely change the view. + methodsCalled = []; + expectedResult = @[@"viewWillAppear", + @"viewDidAppear", + @"viewWillDisappear", + @"viewDidDisappear"]; + + var newView = [[CPView alloc] initWithFrame:CGRectMake(0,0,100,100)]; + [viewController setView:newView]; + + [superview addSubview:newView]; + [newView removeFromSuperview]; + // Checks that with receive notifs from the new view and not from the old view. + [self assert:expectedResult equals:methodsCalled]; +} + +@end + +@implementation ViewController : CPViewController +{ +} + +- (void)viewDidLoad +{ + [methodsCalled addObject:_cmd]; +} + +- (void)viewDidAppear +{ + [methodsCalled addObject:_cmd]; +} + +- (void)viewWillAppear +{ + [methodsCalled addObject:_cmd]; +} + +- (void)viewDidDisappear +{ + [methodsCalled addObject:_cmd]; +} + +- (void)viewWillDisappear +{ + [methodsCalled addObject:_cmd]; +} +@end diff --git a/Tests/AppKit/CPViewTest.j b/Tests/AppKit/CPViewTest.j index 426a6c086..93edeb549 100644 --- a/Tests/AppKit/CPViewTest.j +++ b/Tests/AppKit/CPViewTest.j @@ -895,70 +895,184 @@ var updateTrackingAreasCalls, [self assert:nil equals:[view effectiveAppearance]]; } +- (void)testViewDidHideDidUnhide +{ + var expectedResult = [@"viewDidHide_view1", @"viewDidUnhide_view1"]; + + [view1 setHidden:YES]; + [view1 setHidden:NO]; + + [self assert:expectedResult equals:methodCalled]; +} + +- (void)testAddViewRemoveView +{ + var expectedResult = [@"viewWillMoveToSuperview_view2", + @"viewDidMoveToSuperview_view2", + @"viewWillMoveToSuperview_view2", + @"viewDidMoveToSuperview_view2", + @"viewWillMoveToWindow_view2", + @"viewDidMoveToWindow_view2"]; + + [view1 addSubview:view2]; + [view2 removeFromSuperview]; + + [self assert:expectedResult equals:methodCalled]; +} + +- (void)testViewGainedHiddenAncestor +{ + var expectedResult = [@"viewDidHide_view1", + @"viewWillMoveToSuperview_view3", + @"viewDidMoveToSuperview_view3", + @"viewWillMoveToSuperview_view2", + @"viewDidHide_view2", + @"viewDidHide_view3", + @"viewDidMoveToSuperview_view2"]; + + [view1 setHidden:YES]; + [view2 addSubview:view3]; + CPLog.warn("will add view2"); + [view1 addSubview:view2]; + + [self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected " + [view2 identifier] + "isHiddenOrHasHiddenAncestor = YES"]; + [self assertTrue: [view3 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES"]; + + [self assertFalse:[view2 isHidden]]; + [self assertFalse:[view3 isHidden]]; + + [self assert:expectedResult equals:methodCalled]; +} + +- (void)testRemoveViewsHiddenByAncestor +{ + var expectedResult = @[ + @"viewWillMoveToSuperview_view2", + @"viewDidUnhide_view2", + @"viewDidUnhide_view3", + @"viewDidMoveToSuperview_view2", + @"viewWillMoveToWindow_view2", + @"viewWillMoveToWindow_view3", + @"viewDidMoveToWindow_view3", + @"viewDidMoveToWindow_view2" +]; + + [view1 setHidden:YES]; + [view1 addSubview:view2]; + [view2 addSubview:view3]; + + [self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES" ]; + [self assertFalse:[view2 isHidden]]; + + [self assertTrue: [view3 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES" ]; + [self assertFalse:[view3 isHidden]]; + + methodCalled = []; + + [view2 removeFromSuperview]; + + [self assertFalse: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = NO" ]; + [self assertFalse: [view3 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = NO" ]; + + [self assert:expectedResult equals:methodCalled]; +} + +- (void)testRemoveHiddenView +{ + var expectedResult = [@"viewWillMoveToSuperview_view2", @"viewDidMoveToSuperview_view2", @"viewWillMoveToWindow_view2", @"viewDidMoveToWindow_view2"]; + + [view1 addSubview:view2]; + [view2 setHidden:YES]; + + methodCalled = []; + + [view2 removeFromSuperview]; + [self assertTrue: [view2 isHiddenOrHasHiddenAncestor] message:@"Expected isHiddenOrHasHiddenAncestor = YES" ]; + [self assert:expectedResult equals:methodCalled]; +} + +- (void)testLostHiddenAncestorAfterMovingToNewSuperview +{ + var expectedResult = @[ + @"viewWillMoveToSuperview_view2", + @"viewDidMoveToSuperview_view2", + @"viewDidHide_view1", + @"viewDidHide_view2", + @"viewWillMoveToSuperview_view2", + @"viewDidUnhide_view2", + @"viewDidMoveToSuperview_view2" +]; + + [view1 addSubview:view2]; + [view1 setHidden:YES]; + [view3 addSubview:view2]; + + [self assert:expectedResult equals:methodCalled]; +} // TrackingAreaAdditions - (void)testTrackingAreas { var trackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect owner:self userInfo:nil]; - + [self assert:0 equals:[[view trackingAreas] count] message:@"Initially, a view has no tracking area"]; // - + [view addTrackingArea:trackingArea]; [self assert:1 equals:[[view trackingAreas] count] message:@"After adding a tracking area"]; [self assert:view equals:[trackingArea view] message:@"Tracking area should be linked to view"]; // - + [view removeTrackingArea:trackingArea]; [self assert:0 equals:[[view trackingAreas] count] message:@"After removing the only tracking area"]; [self assert:nil equals:[trackingArea view] message:@"Tracking area should be unlinked"]; - + // - + [view addTrackingArea:trackingArea]; [view addTrackingArea:trackingArea]; [view addTrackingArea:trackingArea]; [self assert:1 equals:[[view trackingAreas] count] message:@"Adding the same tracking area multiple times should add it once"]; [self assert:view equals:[trackingArea view] message:@"Tracking area should be linked to view"]; - + var trackingArea2 = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow owner:self userInfo:nil]; // - + [view addTrackingArea:trackingArea2]; [self assert:2 equals:[[view trackingAreas] count] message:@"After adding a second tracking area"]; [self assert:view equals:[trackingArea2 view] message:@"Tracking area should be linked to view"]; // - + [view removeAllTrackingAreas]; [self assert:0 equals:[[view trackingAreas] count] message:@"After removing all tracking areas"]; [self assert:nil equals:[trackingArea view] message:@"Tracking area should be unlinked"]; [self assert:nil equals:[trackingArea2 view] message:@"Tracking area should be unlinked"]; // - + [view addTrackingArea:trackingArea]; - + var contentView = [window contentView]; [contentView addSubview:view]; [self assert:0 equals:updateTrackingAreasCalls message:@"Putting a view with a CPTrackingAreaInVisibleRect in a window should not call updateTrackingAreas"]; - + [view removeFromSuperview]; // - + [view addTrackingArea:trackingArea2]; [contentView addSubview:view]; [self assert:1 equals:updateTrackingAreasCalls message:@"Putting a view with a non CPTrackingAreaInVisibleRect in a window should call updateTrackingAreas"]; - + [view removeAllTrackingAreas]; - + // - + var viewTA = [[CPTrackingAreaView alloc] initWithFrame:CGRectMakeZero()]; updateTrackingAreasCalls = 0; @@ -966,17 +1080,17 @@ var updateTrackingAreasCalls, [self assert:1 equals:updateTrackingAreasCalls message:@"Putting a view with no tracking areas in a window should call updateTrackingAreas"]; // - + updateTrackingAreasCalls = 0; [viewTA addTrackingArea:trackingArea]; [viewTA setFrame:CGRectMake(10, 10, 10, 10)]; [self assert:0 equals:updateTrackingAreasCalls message:@"Changing geometry of a view with a CPTrackingAreaInVisibleRect should not call updateTrackingAreas"]; - + // - + updateTrackingAreasCalls = 0; - + [viewTA addTrackingArea:trackingArea2]; [viewTA setFrame:CGRectMake(20, 20, 20, 20)]; [self assert:1 equals:updateTrackingAreasCalls message:@"Changing geometry of a view with a non CPTrackingAreaInVisibleRect should call updateTrackingAreas"]; @@ -1048,7 +1162,7 @@ var updateTrackingAreasCalls, [viewTA removeAllTrackingAreas]; [viewTA addTrackingArea:trackingAreaAllWithDrag]; - + // Mouse enters the tracking area while dragging (option set) [self moveMouseAtPoint:CGPointMake(21, 21) dragging:YES]; @@ -1103,7 +1217,7 @@ var updateTrackingAreasCalls, [self assert:0 equals:mouseExitedCalls message:@"Mouse entering inner tracking area should not call mouseExited"]; [self assert:1 equals:mouseMovedCalls message:@"Mouse entering inner tracking area should call mouseMoved"]; [self assert:1 equals:cursorUpdateCalls message:@"Mouse entering inner tracking area should call cursorUpdate"]; - + // Mouse moves in inner view [self moveMouseAtPoint:CGPointMake(27, 27) dragging:NO]; @@ -1112,7 +1226,7 @@ var updateTrackingAreasCalls, [self assert:0 equals:mouseExitedCalls message:@"Mouse moving in inner tracking area should not call mouseExited"]; [self assert:2 equals:mouseMovedCalls message:@"Mouse moving in inner tracking area should call mouseMoved for both views"]; [self assert:0 equals:cursorUpdateCalls message:@"Mouse moving in inner tracking area should not call cursorUpdate"]; - + // Mouse leaves inner view but remains in outer view [self moveMouseAtPoint:CGPointMake(36, 36) dragging:NO]; @@ -1121,40 +1235,38 @@ var updateTrackingAreasCalls, [self assert:1 equals:mouseExitedCalls message:@"Mouse moving from inner to outer tracking area should call mouseExited (for inner)"]; [self assert:1 equals:mouseMovedCalls message:@"Mouse moving from inner to outer tracking area should call mouseMoved (for outer)"]; [self assert:1 equals:cursorUpdateCalls message:@"Mouse moving from inner to outer tracking area should call cursorUpdate (for outer)"]; - + [self assert:innerViewTA equals:involvedViewForMouseExited message:@"Inner view should receive mouseExited"]; [self assert:viewTA equals:involvedViewForCursorUpdate message:@"Outer view should receive cursorUpdate"]; // Complex test for cursor update frontmost tracking area detection - var viewA = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(0, 30, 40, 40)]; - var viewB = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(30, 20, 40, 40)]; - var viewC = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(10, 0, 40, 40)]; + var viewA = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(0, 30, 40, 40)], + viewB = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(30, 20, 40, 40)], + viewC = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(10, 0, 40, 40)]; [contentView setSubviews:[CPArray array]]; [contentView addSubview:viewA]; [contentView addSubview:viewB]; [contentView addSubview:viewC]; - var subviewA = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(20, 0, 20, 20)]; - var subviewB = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)]; - var subviewC = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(0, 20, 40, 20)]; + var subviewA = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(20, 0, 20, 20)], + subviewB = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)], + subviewC = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(0, 20, 40, 20)]; [viewA addSubview:subviewA]; [viewB addSubview:subviewB]; [viewC addSubview:subviewC]; - var options = CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect; - var options2 = CPTrackingMouseEnteredAndExited | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect; - var options3 = CPTrackingCursorUpdate | CPTrackingActiveInActiveApp; - - var viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil]; - var viewBTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewB userInfo:nil]; - var viewCTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options2 owner:viewC userInfo:nil]; - - var subviewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:subviewA userInfo:nil]; - var subviewBTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:subviewB userInfo:nil]; - var subviewCtrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMake(20, 0, 20, 20) options:options3 owner:subviewC userInfo:nil]; + var options = CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect, + options2 = CPTrackingMouseEnteredAndExited | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect, + options3 = CPTrackingCursorUpdate | CPTrackingActiveInActiveApp, + viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil], + viewBTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewB userInfo:nil], + viewCTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options2 owner:viewC userInfo:nil], + subviewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:subviewA userInfo:nil], + subviewBTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:subviewB userInfo:nil], + subviewCtrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMake(20, 0, 20, 20) options:options3 owner:subviewC userInfo:nil]; [viewA addTrackingArea:viewATrackingArea]; [viewB addTrackingArea:viewBTrackingArea]; @@ -1222,18 +1334,18 @@ var updateTrackingAreasCalls, // Cursor tests - var viewA = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(20, 20, 40, 40)]; - var viewB = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(10, 10, 80, 80)]; - var viewC = [[CPTrackingAreaViewWithoutCursorUpdate alloc] initWithFrame:CGRectMake(10, 10, 80, 80)]; + var viewA = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(20, 20, 40, 40)], + viewB = [[CPTrackingAreaView alloc] initWithFrame:CGRectMake(10, 10, 80, 80)], + viewC = [[CPTrackingAreaViewWithoutCursorUpdate alloc] initWithFrame:CGRectMake(10, 10, 80, 80)]; [contentView setSubviews:[CPArray array]]; [contentView addSubview:viewA]; var options = CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect; - var viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil]; - var viewBTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewB userInfo:nil]; - var viewCTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewC userInfo:nil]; + var viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil], + viewBTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewB userInfo:nil], + viewCTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewC userInfo:nil]; [viewA addTrackingArea:viewATrackingArea]; [viewB addTrackingArea:viewBTrackingArea]; @@ -1280,7 +1392,7 @@ var updateTrackingAreasCalls, [self mouseUpAtPoint:CGPointMake(10, 10)]; [self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"Step 1.7 : cursor should be an arrow"]; - + // [self moveMouseAtPoint:CGPointMake(1, 1) dragging:NO]; @@ -1294,7 +1406,7 @@ var updateTrackingAreasCalls, [self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO]; [self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"Step 2.1 : cursor should be an arrow"]; - + // Step 2.2 : inside the superview / outside the subview [self moveMouseAtPoint:CGPointMake(15, 15) dragging:NO]; @@ -1312,7 +1424,7 @@ var updateTrackingAreasCalls, [self moveMouseAtPoint:CGPointMake(15, 15) dragging:NO]; [self assert:[CPCursor crosshairCursor] equals:[CPCursor currentCursor] message:@"Step 2.4 : cursor should be a crosshair"]; - + // Step 2.5 : outside the superview [self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO]; @@ -1357,7 +1469,142 @@ var updateTrackingAreasCalls, [self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO]; [self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"Step 3.5 : cursor should be an arrow"]; - + +} + +- (void)testTrackingAreasLiveViewHierarchyModification +{ + // 1. viewB inside viewA with mouseEntered removing itself + + var viewA = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(20, 20, 40, 40)], + viewB = [[CPTrackingAreaViewLiveRemoval alloc] initWithFrame:CGRectMake(10, 10, 20, 20)]; + + [[window contentView] setSubviews:[CPArray arrayWithObject:viewA]]; + [viewA addSubview:viewB]; + + var options = CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect, + viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil], + viewBTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewB userInfo:nil]; + + [viewA addTrackingArea:viewATrackingArea]; + [viewB addTrackingArea:viewBTrackingArea]; + + // Step 1.1 : enter viewA + + [self moveMouseAtPoint:CGPointMake(25, 25) dragging:NO]; + + [self assert:1 equals:mouseEnteredCalls message:@"1.1 There should be one and only one mouseEntered call"]; + [self assert:0 equals:mouseExitedCalls message:@"1.1 There should be no mouseExited call"]; + [self assert:0 equals:mouseMovedCalls message:@"1.1 There should be no mouseMoved call"]; + [self assert:1 equals:cursorUpdateCalls message:@"1.1 There should be one and only one cursorUpdate call"]; + + [self assert:viewA equals:involvedViewForMouseEntered message:@"1.1 viewA should receive mouseEntered"]; + [self assert:viewA equals:involvedViewForCursorUpdate message:@"1.1 viewA should receive cursorUpdate"]; + + // Step 1.2 : enter viewB + + [self moveMouseAtPoint:CGPointMake(40, 40) dragging:NO]; + + [self assert:1 equals:mouseEnteredCalls message:@"1.2 There should be one and only one mouseEntered call"]; + [self assert:0 equals:mouseExitedCalls message:@"1.2 There should be no mouseExited call"]; + [self assert:0 equals:mouseMovedCalls message:@"1.2 There should be no mouseMoved call"]; + [self assert:0 equals:cursorUpdateCalls message:@"1.2 There should be no cursorUpdate call"]; + + [self assert:viewB equals:involvedViewForMouseEntered message:@"1.2 viewB should receive mouseEntered"]; + + // Step 1.3 : move back to viewA (there should be no more viewB) + + [self moveMouseAtPoint:CGPointMake(25, 25) dragging:NO]; + + [self assert:0 equals:mouseEnteredCalls message:@"1.3 There should be no mouseEntered call"]; + [self assert:0 equals:mouseExitedCalls message:@"1.3 There should be no mouseExited call"]; + [self assert:0 equals:mouseMovedCalls message:@"1.3 There should be no mouseMoved call"]; + [self assert:1 equals:cursorUpdateCalls message:@"1.3 There should be one and only one cursorUpdate call"]; + + [self assert:viewA equals:involvedViewForCursorUpdate message:@"1.3 viewA should receive cursorUpdate"]; + + // Step 1.4 : exit viewA + + [self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO]; + + [self assert:0 equals:mouseEnteredCalls message:@"1.4 There should be one and only one mouseEntered call"]; + [self assert:1 equals:mouseExitedCalls message:@"1.4 There should be one and only on mouseExited call"]; + [self assert:0 equals:mouseMovedCalls message:@"1.4 There should be no mouseMoved call"]; + [self assert:0 equals:cursorUpdateCalls message:@"1.4 There should be no cursorUpdate call"]; + + [self assert:viewA equals:involvedViewForMouseExited message:@"1.4 viewA should receive mouseExited"]; + + // 2. viewA with mouseEntered adding viewB inside it. Testing if viewB receive mouseEntered & cursorUpdate + + var viewA = [[CPTrackingAreaViewLiveAddition alloc] initWithFrame:CGRectMake(20, 20, 40, 40)]; + + [[window contentView] setSubviews:[CPArray arrayWithObject:viewA]]; + + var options = CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect, + viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil]; + + [viewA addTrackingArea:viewATrackingArea]; + + // Step 2.1 : enter viewA (then add viewB thus enter also viewB) + + [self moveMouseAtPoint:CGPointMake(25, 25) dragging:NO]; + + [self assert:2 equals:mouseEnteredCalls message:@"2.1 There should be two mouseEntered calls"]; + [self assert:0 equals:mouseExitedCalls message:@"2.1 There should be no mouseExited call"]; + [self assert:0 equals:mouseMovedCalls message:@"2.1 There should be no mouseMoved call"]; + [self assert:2 equals:cursorUpdateCalls message:@"2.1 There should be two cursorUpdate calls"]; + + [self assert:[[viewA subviews] firstObject] equals:involvedViewForMouseEntered message:@"2.1 viewB should receive mouseEntered"]; + [self assert:[[viewA subviews] firstObject] equals:involvedViewForCursorUpdate message:@"2.1 viewB should receive cursorUpdate"]; + [self assert:[CPCursor crosshairCursor] equals:[CPCursor currentCursor] message:@"2.1 Final cursor should be crosshair cursor, determined by viewB"]; + + // Step 2.2 : exit viewA (thus also viewB) + + [self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO]; + + [self assert:0 equals:mouseEnteredCalls message:@"2.2 There should be no mouseEntered calls"]; + [self assert:2 equals:mouseExitedCalls message:@"2.2 There should be two mouseExited call"]; + [self assert:0 equals:mouseMovedCalls message:@"2.2 There should be no mouseMoved call"]; + [self assert:0 equals:cursorUpdateCalls message:@"2.2 There should be no cursorUpdate calls"]; + + [self assert:[[viewA subviews] firstObject] equals:involvedViewForMouseExited message:@"2.2 viewB should receive mouseExited"]; + [self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"2.2 Cursor should be an arrow"]; + + // 3. viewA with mouseEntered adding viewB inside it BUT with CPTrackingAssumeInside. Testing if viewB receive only cursorUpdate + + var viewA = [[CPTrackingAreaViewLiveAddition2 alloc] initWithFrame:CGRectMake(20, 20, 40, 40)]; + + [[window contentView] setSubviews:[CPArray arrayWithObject:viewA]]; + + var options = CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect, + viewATrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:options owner:viewA userInfo:nil]; + + [viewA addTrackingArea:viewATrackingArea]; + + // Step 3.1 : enter viewA (then add viewB thus enter also viewB) + + [self moveMouseAtPoint:CGPointMake(25, 25) dragging:NO]; + + [self assert:1 equals:mouseEnteredCalls message:@"3.1 There should be two mouseEntered calls"]; + [self assert:0 equals:mouseExitedCalls message:@"3.1 There should be no mouseExited call"]; + [self assert:0 equals:mouseMovedCalls message:@"3.1 There should be no mouseMoved call"]; + [self assert:2 equals:cursorUpdateCalls message:@"3.1 There should be two cursorUpdate calls"]; + + [self assert:viewA equals:involvedViewForMouseEntered message:@"3.1 viewB should receive mouseEntered"]; + [self assert:[[viewA subviews] firstObject] equals:involvedViewForCursorUpdate message:@"3.1 viewB should receive cursorUpdate"]; + [self assert:[CPCursor crosshairCursor] equals:[CPCursor currentCursor] message:@"3.1 Final cursor should be crosshair cursor, determined by viewB"]; + + // Step 3.2 : exit viewA (thus also viewB) + + [self moveMouseAtPoint:CGPointMake(5, 5) dragging:NO]; + + [self assert:0 equals:mouseEnteredCalls message:@"3.2 There should be no mouseEntered calls"]; + [self assert:2 equals:mouseExitedCalls message:@"3.2 There should be two mouseExited call"]; + [self assert:0 equals:mouseMovedCalls message:@"3.2 There should be no mouseMoved call"]; + [self assert:0 equals:cursorUpdateCalls message:@"3.2 There should be no cursorUpdate calls"]; + + [self assert:[[viewA subviews] firstObject] equals:involvedViewForMouseExited message:@"3.2 viewB should receive mouseExited"]; + [self assert:[CPCursor arrowCursor] equals:[CPCursor currentCursor] message:@"3.2 Cursor should be an arrow"]; } - (void)updateTrackingAreas @@ -1414,21 +1661,16 @@ var updateTrackingAreasCalls, @end @implementation CPTrackingAreaView : CPView -{ - -} - (void)mouseEntered:(CPEvent)anEvent { mouseEnteredCalls++; - involvedViewForMouseEntered = [[anEvent trackingArea] view]; } - (void)mouseExited:(CPEvent)anEvent { mouseExitedCalls++; - involvedViewForMouseExited = [[anEvent trackingArea] view]; } @@ -1440,7 +1682,6 @@ var updateTrackingAreasCalls, - (void)cursorUpdate:(CPEvent)anEvent { cursorUpdateCalls++; - involvedViewForCursorUpdate = [[anEvent trackingArea] view]; } @@ -1452,22 +1693,73 @@ var updateTrackingAreasCalls, @end @implementation CPTrackingAreaViewWithCursorUpdate : CPTrackingAreaView -{ - -} - (void)cursorUpdate:(CPEvent)anEvent { [[CPCursor crosshairCursor] set]; - [super cursorUpdate:anEvent]; } @end @implementation CPTrackingAreaViewWithoutCursorUpdate : CPView -{ +@end + +@implementation CPTrackingAreaViewLiveRemoval : CPTrackingAreaView + +- (void)cursorUpdate:(CPEvent)anEvent +{ + [[CPCursor pointingHandCursor] set]; + [super cursorUpdate:anEvent]; +} + +- (void)mouseEntered:(CPEvent)anEvent +{ + [self removeFromSuperview]; + [super mouseEntered:anEvent]; +} + +@end + +@implementation CPTrackingAreaViewLiveAddition : CPTrackingAreaView + +- (void)cursorUpdate:(CPEvent)anEvent +{ + [[CPCursor pointingHandCursor] set]; + [super cursorUpdate:anEvent]; +} + +- (void)mouseEntered:(CPEvent)anEvent +{ + var viewB = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; + + [self addSubview:viewB]; + + [viewB addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect owner:viewB userInfo:nil]]; + + [super mouseEntered:anEvent]; +} + +@end + +@implementation CPTrackingAreaViewLiveAddition2 : CPTrackingAreaView + +- (void)cursorUpdate:(CPEvent)anEvent +{ + [[CPCursor pointingHandCursor] set]; + [super cursorUpdate:anEvent]; +} + +- (void)mouseEntered:(CPEvent)anEvent +{ + var viewB = [[CPTrackingAreaViewWithCursorUpdate alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; + + [self addSubview:viewB]; + + [viewB addTrackingArea:[[CPTrackingArea alloc] initWithRect:CGRectMakeZero() options:CPTrackingMouseEnteredAndExited | CPTrackingCursorUpdate | CPTrackingActiveInActiveApp | CPTrackingInVisibleRect | CPTrackingAssumeInside owner:viewB userInfo:nil]]; + + [super mouseEntered:anEvent]; } @end @@ -1513,6 +1805,18 @@ var updateTrackingAreasCalls, [methodCalled addObject:string]; } +- (void)viewDidUnhide +{ + var string = _cmd + @"_" + [self identifier]; + [methodCalled addObject:string]; +} + +- (void)viewDidHide +{ + var string = _cmd + @"_" + [self identifier]; + [methodCalled addObject:string]; +} + - (BOOL)acceptsFirstResponder { return YES; diff --git a/Tests/CucumberTests/CPViewControllerTest/AppController.j b/Tests/CucumberTests/CPViewControllerTest/AppController.j new file mode 100644 index 000000000..ba8497c92 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/AppController.j @@ -0,0 +1,46 @@ +/* + * AppController.j + * CPViewControllerTest + * + * Created by You on May 9, 2016. + * Copyright 2016, Your Company All rights reserved. + */ + +@import +@import + + +@implementation AppController : CPObject +{ + @outlet CPWindow theWindow; + @outlet CPViewController viewController; + BOOL isViewLoaded; +} + +- (void)applicationDidFinishLaunching:(CPNotification)aNotification +{ + // This is called when the application is done loading. +} + +- (void)awakeFromCib +{ + // This is called when the cib is done loading. + // You can implement this method on any object instantiated from a Cib. + // It's a useful hook for setting up current UI values, and other things. + + isViewLoaded = NO; + // In this case, we want the window from Cib to become our full browser window + [theWindow setFullPlatformWindow:YES]; +} + +- (IBAction)load:(id)sender +{ + [viewController loadViewWithCompletionHandler:function(view, error) + { + [view setBackgroundColor:[CPColor redColor]]; + [view setFrameOrigin:CGPointMake(100,100)]; + [[theWindow contentView] addSubview:view]; + }]; +} + +@end diff --git a/Tests/CucumberTests/CPViewControllerTest/CPResponder+CuCapp.j b/Tests/CucumberTests/CPViewControllerTest/CPResponder+CuCapp.j new file mode 100644 index 000000000..8b2a7a250 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/CPResponder+CuCapp.j @@ -0,0 +1,94 @@ +/* +* Copyright (c) 2014 Nuage Networks +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + + +// Import this Categories from your application +// You can now user -(void)setCucappIdentifier: and -(CPString)cucappIdentifier +// to set and get your cucapp IDs. +// Then from a test, you can use it as a selector like //CPView[cucappIdentifier="my-button"] + +@import +@import + +@implementation CPResponder (cucappAdditions) + +- (void)setCucappIdentifier:(CPString)anIdentifier +{ + self.__cucappIdentifier = anIdentifier; +} + +- (CPString)cucappIdentifier +{ + return self.__cucappIdentifier; +} + +@end + +@implementation CPMenuItem (cucappAdditionsMenu) + +- (void)setCucappIdentifier:(CPString)anIdentifier +{ + [[self _menuItemView] setCucappIdentifier:anIdentifier]; +} + +- (CPString)cucappIdentifier +{ + [[self _menuItemView] cucappIdentifier]; +} + +@end + +function load_cucapp_CLI(path) +{ + if (!path) + path = "Cucapp/lib/Cucumber.j" + + try { + objj_importFile(path, true, function() { + [Cucumber stopCucumber]; + CPLog.debug("Cucapp CLI has been well loaded"); + _addition_cpapplication_send_event_method(); + }); + + } + catch(e) + { + [CPException raise:CPInvalidArgumentException reason:@"Invalid path for the lib Cucumber"]; + } +} + + +function load_cucapp_record(path) +{ + if (!path) + path = "CuCapp+Record.j" + + try { + objj_importFile(path, true, function() { + CPLog.debug("Cucapp record has been well loaded"); + }); + } + catch(e) + { + [CPException raise:CPInvalidArgumentException reason:@"Invalid path for the lib Cucapp+Record.j"]; + } +} diff --git a/Tests/CucumberTests/CPViewControllerTest/CuCapp+Record.j b/Tests/CucumberTests/CPViewControllerTest/CuCapp+Record.j new file mode 100644 index 000000000..5975dae5c --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/CuCapp+Record.j @@ -0,0 +1,591 @@ +@import + +function start_record(path) +{ + [CPWindow start_record]; +} + +function stop_record() +{ + [CPWindow stop_record]; +} + +function save_record(fileName) +{ + if (!fileName) + fileName = @"record" + + var eventRecords = [CPWindow eventRecords], + JSONEvents = []; + + for (var i = 0; i < [eventRecords count]; i++) + { + var eventRecord = eventRecords[i]; + [JSONEvents addObject:[eventRecord objectToJSON]]; + } + + var pom = document.createElement('a'); + pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(JSONEvents, null, 4))); + pom.setAttribute('download', fileName + ".json"); + pom.click(); +} + +function play_record(file_name, cucumber_path) +{ + if (!cucumber_path) + cucumber_path = path = "../../Cucapp/lib/Cucumber.j"; + + load_cucapp_CLI(cucumber_path); + + setTimeout(function(){ + _load_javascript_file(file_name) + },1000); +} + +function _load_javascript_file(file_name) +{ + var AJAX_req = new XMLHttpRequest(); + AJAX_req.open( "GET", file_name, true ); + AJAX_req.setRequestHeader("Content-type", "application/json"); + + AJAX_req.onreadystatechange = function() + { + if (AJAX_req.readyState == 4) + { + var recordingEvents = JSON.parse(AJAX_req.responseText); + + for (var i = 0; i < [recordingEvents count]; i++) + { + var recordingEvent = JSON.parse(recordingEvents[i]), + type = recordingEvent["event"]["type"]; + + if (type == CPLeftMouseDown && i < [recordingEvents count] - 1) + { + var nextRecordingEvent = JSON.parse(recordingEvents[i + 1]), + nextType = nextRecordingEvent["event"]["type"]; + + if (nextType == CPMouseMoved) + { + for (var j = i; j < [recordingEvents count]; j++) + { + var tmpRecordingEvent = JSON.parse(recordingEvents[j]), + tmpType = tmpRecordingEvent["event"]["type"]; + + if (tmpType == CPLeftMouseUp) + { + setTimeout(_simulate_drag_event, recordingEvent["event"]["timestamp"] * 1000, recordingEvent, tmpRecordingEvent); + i = j; + break; + } + } + + continue; + } + } + + setTimeout(_simulate_event, recordingEvent["event"]["timestamp"] * 1000, recordingEvent); + } + } + } + + AJAX_req.send(); +} + +function _simulate_drag_event(event1, event2) +{ + var keyView1 = event1["keyView"], + valueView1 = event1["valueView"], + keyView2 = event2["keyView"], + valueView2 = event2["valueView"], + locationInWindow1 = CGPointMake(event1["event"]["locationInWindow"]["x"], event1["event"]["locationInWindow"]["y"]); + locationInWindow2 = CGPointMake(event2["event"]["locationInWindow"]["x"], event2["event"]["locationInWindow"]["y"]); + + if (keyView1 && valueView1 && keyView2 && valueView2) + simulate_dragged_click_view_to_view(keyView1, valueView1, keyView2, valueView2); + else if (keyView1 && valueView1) + simulate_dragged_click_view_to_point(keyView1, valueView1, locationInWindow1.x, locationInWindow1.y); + else + simulate_dragged_click_point_to_point(locationInWindow1.x, locationInWindow1.y, locationInWindow2.x, locationInWindow2.y); +} + +function _simulate_event(event) +{ + var type = event["event"]["type"], + keyView = event["keyView"], + valueView = event["valueView"], + characters = event["event"]["characters"], + deltaX = event["event"]["deltaX"], + deltaY = event["event"]["deltaY"], + deltaZ = event["event"]["deltaZ"], + deltaZ = event["event"]["deltaZ"], + locationInWindow = CGPointMake(event["event"]["locationInWindow"]["x"], event["event"]["locationInWindow"]["y"]); + + switch (type) + { + case CPScrollWheel: + + if (keyView && valueView) + simulate_scroll_wheel_on_view(keyView, valueView, deltaX, deltaY) + + break; + + case CPLeftMouseDown: + + if (keyView && valueView) + simulate_left_click_on_view(keyView, valueView); + else + simulate_left_click_on_point(locationInWindow.x, locationInWindow.y) + + break; + + case CPRightMouseDown: + + if (keyView && valueView) + simulate_right_click_on_view(keyView, valueView); + else + simulate_right_click_on_point(locationInWindow.x, locationInWindow.y) + + break; + + case CPKeyDown: + simulate_keyboard_event(characters); + break; + } +} + + +var eventRecords, + recording = NO; + +@implementation CPWindow (cucappRecord) + ++ (CPArray)eventRecords +{ + return eventRecords; +} + ++ (void)start_record +{ + eventRecords = []; + recording = YES; +} + ++ (void)stop_record +{ + recording = NO; +} + +/*! + Dispatches events that are sent to it from CPApplication. + @param anEvent the event to be dispatched +*/ +- (void)sendEvent:(CPEvent)anEvent +{ + var type = [anEvent type], + sheet = [self attachedSheet], + recordingEvent = [[RecordingEvent alloc] initWithEvent:anEvent]; + + if (recordingEvent && type != CPFlagsChanged && type != CPMouseMoved) + [eventRecords addObject:recordingEvent]; + + // If a sheet is attached events get filtered here. + // It is not clear what events should be passed to the view, perhaps all? + // CPLeftMouseDown is needed for window moving and resizing to work. + // CPMouseMoved is needed for rollover effects on title bar buttons. + + if (sheet) + { + switch (type) + { + case CPLeftMouseDown: + + // This is needed when a doubleClick occurs when the sheet is closing or opening + if (!_parentWindow) + return; + + [recordingEvent setView:_windowView]; + + [_windowView mouseDown:anEvent]; + + // -dw- if the window is clicked, the sheet should come to front, and become key, + // and the window should be immediately behind + [sheet makeKeyAndOrderFront:self]; + + return; + + case CPMouseMoved: + // Allow these through to the parent + break; + + default: + // Everything else is filtered + return; + } + } + + var point = [anEvent locationInWindow]; + + switch (type) + { + case CPFlagsChanged: + return [[self firstResponder] flagsChanged:anEvent]; + + case CPKeyUp: + return [[self firstResponder] keyUp:anEvent]; + + case CPKeyDown: + if ([anEvent charactersIgnoringModifiers] === CPTabCharacter) + { + if ([anEvent modifierFlags] & CPShiftKeyMask) + [self selectPreviousKeyView:self]; + else + [self selectNextKeyView:self]; + + // Make sure the browser doesn't try to do its own tab handling. + // This is important or the browser might blur the shared text field or token field input field, + // even that we just moved it to a new first responder. + [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO] + return; + } + else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter) + { + var didTabBack = [self selectPreviousKeyView:self]; + + if (didTabBack) + { + // Make sure the browser doesn't try to do its own tab handling. + // This is important or the browser might blur the shared text field or token field input field, + // even that we just moved it to a new first responder. + [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO] + } + return didTabBack; + } + else if ([anEvent charactersIgnoringModifiers] == CPEscapeFunctionKey && [self _processKeyboardUIKey:anEvent]) + { + return; + } + + [[self firstResponder] keyDown:anEvent]; + + // Trigger the default button if needed + // FIXME: Is this only applicable in a sheet? See isse: #722. + if (![self disableKeyEquivalentForDefaultButton]) + { + var defaultButton = [self defaultButton], + keyEquivalent = [defaultButton keyEquivalent], + modifierMask = [defaultButton keyEquivalentModifierMask]; + + if ([anEvent _triggersKeyEquivalent:keyEquivalent withModifierMask:modifierMask]) + [[self defaultButton] performClick:self]; + } + + return; + + case CPScrollWheel: + [recordingEvent setView:[_windowView hitTest:point]]; + + return [[_windowView hitTest:point] scrollWheel:anEvent]; + + case CPLeftMouseUp: + case CPRightMouseUp: + var hitTestedView = _leftMouseDownView, + selector = type == CPRightMouseUp ? @selector(rightMouseUp:) : @selector(mouseUp:); + + if (!hitTestedView) + hitTestedView = [_windowView hitTest:point]; + + [recordingEvent setView:hitTestedView]; + + [hitTestedView performSelector:selector withObject:anEvent]; + + _leftMouseDownView = nil; + + return; + + case CPLeftMouseDown: + case CPRightMouseDown: + // This will return _windowView if it is within a resize region + _leftMouseDownView = [_windowView hitTest:point]; + + [recordingEvent setView:_leftMouseDownView]; + + if (_leftMouseDownView !== _firstResponder && [_leftMouseDownView acceptsFirstResponder]) + [self makeFirstResponder:_leftMouseDownView]; + + [CPApp activateIgnoringOtherApps:YES]; + + var theWindow = [anEvent window], + selector = type == CPRightMouseDown ? @selector(rightMouseDown:) : @selector(mouseDown:); + + if ([theWindow isKeyWindow] || ([theWindow becomesKeyOnlyIfNeeded] && ![_leftMouseDownView needsPanelToBecomeKey])) + return [_leftMouseDownView performSelector:selector withObject:anEvent]; + else + { + // FIXME: delayed ordering? + [self makeKeyAndOrderFront:self]; + + if ([_leftMouseDownView acceptsFirstMouse:anEvent]) + return [_leftMouseDownView performSelector:selector withObject:anEvent]; + } + break; + + case CPLeftMouseDragged: + case CPRightMouseDragged: + if (!_leftMouseDownView) + { + [recordingEvent setView:[_windowView hitTest:point]]; + return [[_windowView hitTest:point] mouseDragged:anEvent]; + } + + [recordingEvent setView:_leftMouseDownView]; + + var selector; + + if (type == CPRightMouseDragged) + { + selector = @selector(rightMouseDragged:) + if (![_leftMouseDownView respondsToSelector:selector]) + selector = nil; + } + + if (!selector) + selector = @selector(mouseDragged:) + + return [_leftMouseDownView performSelector:selector withObject:anEvent]; + + case CPMouseMoved: + [_windowView setCursorForLocation:point resizing:NO]; + + // Ignore mouse moves for parents of sheets + if (!_acceptsMouseMovedEvents || sheet) + return; + + if (!_mouseEnteredStack) + _mouseEnteredStack = []; + + var hitTestView = [_windowView hitTest:point]; + + if ([_mouseEnteredStack count] && [_mouseEnteredStack lastObject] === hitTestView) + return [hitTestView mouseMoved:anEvent]; + + var view = hitTestView, + mouseEnteredStack = []; + + while (view) + { + mouseEnteredStack.unshift(view); + + view = [view superview]; + } + + var deviation = MIN(_mouseEnteredStack.length, mouseEnteredStack.length); + + while (deviation--) + if (_mouseEnteredStack[deviation] === mouseEnteredStack[deviation]) + break; + + var index = deviation + 1, + count = _mouseEnteredStack.length; + + if (index < count) + { + var event = [CPEvent mouseEventWithType:CPMouseExited location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0]; + + for (; index < count; ++index) + [_mouseEnteredStack[index] mouseExited:event]; + } + + index = deviation + 1; + count = mouseEnteredStack.length; + + if (index < count) + { + var event = [CPEvent mouseEventWithType:CPMouseEntered location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0]; + + for (; index < count; ++index) + [mouseEnteredStack[index] mouseEntered:event]; + } + + _mouseEnteredStack = mouseEnteredStack; + + [hitTestView mouseMoved:anEvent]; + } +} + +@end + + +@implementation RecordingEvent : CPObject +{ + CPEvent _event @accessors(property=event); + CPString _keyView @accessors(property=keyView); + CPString _valueView @accessors(property=valueView); + CGPoint _offsetView @accessors(property=offsetView); + int _offsetXPercentage @accessors(property=offsetXPercentage); + int _offsetYPercentage @accessors(property=offsetYPercentage); +} + +- (id)initWithEvent:(CPEvent)anEvent +{ + if (self = [super init]) + { + _event = anEvent; + _offsetView = CGPointMakeZero(); + _keyView = @""; + _valueView = @""; + } + + return self; +} + +- (void)setView:(CPView)aView +{ + if ([aView respondsToSelector:@selector(cucappIdentifier)]) + { + _keyView = @"cucappIdentifier"; + _valueView = [aView cucappIdentifier]; + } + else if ([aView respondsToSelector:@selector(identifier)]) + { + _keyView = @"identifier"; + _valueView = [aView identifier]; + } + else if ([aView respondsToSelector:@selector(title)]) + { + _keyView = @"title"; + _valueView = [aView title]; + } + else if ([aView respondsToSelector:@selector(placeholderString)]) + { + _keyView = @"placeholderString"; + _valueView = [aView placeholderString]; + } + else if ([aView respondsToSelector:@selector(text)]) + { + _keyView = @"text"; + _valueView = [aView text]; + } + else if ([aView respondsToSelector:@selector(tag)]) + { + _keyView = @"tag"; + _valueView = [aView tag]; + } + else if ([aView respondsToSelector:@selector(label)]) + { + _keyView = @"label"; + _valueView = [aView label]; + } + else if ([aView respondsToSelector:@selector(objectValue)]) + { + _keyView = @"objectValue"; + _valueView = [aView objectValue]; + } + + var globalPoint = [[aView superview] convertPointToBase:[aView frameOrigin]], + globalEventPoint = [_event locationInWindow]; + + _offsetView = CGPointMake(globalEventPoint.x - globalPoint.x, globalEventPoint.y - globalPoint.y); + + _offsetXPercentage = _offsetView.x * 100 / [aView frameSize].width; + _offsetYPercentage = _offsetView.y * 100 / [aView frameSize].height; +} + +- (CPString)objectToJSON +{ + var json = {}; + + json["keyView"] = _keyView; + json["valueView"] = _valueView; + json["offsetXPercentage"] = _offsetXPercentage; + json["offsetYPercentage"] = _offsetYPercentage; + json["offsetView"] = {"x" : _offsetView.x, "y" : _offsetView.y}; + + var event = {}; + event["type"] = [_event type]; + event["deltaX"] = [_event deltaX]; + event["deltaY"] = [_event deltaY]; + event["deltaZ"] = [_event deltaZ]; + event["characters"] = [_event characters]; + event["charactersIgnoringModifiers"] = [_event charactersIgnoringModifiers]; + event["clickCount"] = [_event clickCount]; + event["modifierFlags"] = [_event modifierFlags]; + event["locationInWindow"] = {"x" : [_event locationInWindow].x, "y" : [_event locationInWindow].y}; + event["keyCode"] = [_event keyCode]; + event["timestamp"] = [_event timestamp]; + + json["event"] = event; + + return JSON.stringify(json, null, 4); +} + +@end + + +@implementation CPApplication (cucappRecord) + +/*! + Dispatches events to other objects. + @param anEvent the event to dispatch +*/ +- (void)sendEvent:(CPEvent)anEvent +{ + _currentEvent = anEvent; + CPEventModifierFlags = [anEvent modifierFlags]; + + var theWindow = [anEvent window]; + + // Check if this is a candidate for key equivalent... + if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent]) + // The key equivalent was handled. + return; + + if ([anEvent type] == CPMouseMoved) + { + if (theWindow !== _lastMouseMoveWindow) + [_lastMouseMoveWindow _mouseExitedResizeRect]; + + _lastMouseMoveWindow = theWindow; + } + + /* + Event listeners are processed from back to front so that newer event listeners normally take + precedence. If during the execution of a callback a new event listener is added, it should + be inserted after the current callback but before any higher priority callbacks. This makes + repeating event listeners (those that reinsert themselves) stable relative to each other. + */ + for (var i = _eventListeners.length - 1; i >= 0; i--) + { + var listener = _eventListeners[i]; + + if (listener._mask & (1 << [anEvent type])) + { + _eventListeners.splice(i, 1); + // In case the callback wants to add more listeners. + _eventListenerInsertionIndex = i; + listener._callback(anEvent); + + var type = [anEvent type], + recordingEvent = [[RecordingEvent alloc] initWithEvent:anEvent]; + + if (recordingEvent && type != CPFlagsChanged && type != CPMouseMoved) + [eventRecords addObject:recordingEvent]; + + if (theWindow) + [recordingEvent setView:[theWindow._windowView hitTest:[anEvent locationInWindow]]]; + + if (listener._dequeue) + { + // Don't process the event normally and don't send it to any other listener. + _eventListenerInsertionIndex = _eventListeners.length; + return; + } + } + } + + _eventListenerInsertionIndex = _eventListeners.length; + + if (theWindow) + [theWindow sendEvent:anEvent]; +} + +@end diff --git a/Tests/CucumberTests/CPViewControllerTest/Info.plist b/Tests/CucumberTests/CPViewControllerTest/Info.plist new file mode 100644 index 000000000..b1c37d97c --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/Info.plist @@ -0,0 +1,14 @@ + + + + + Main cib file base name + MainMenu.cib + CPBundleName + CPViewControllerTest + CPBundleVersion + 1.0 + CPHumanReadableCopyright + Copyright © 2016, Your Company All rights reserved. + + diff --git a/Tests/CucumberTests/CPViewControllerTest/Jakefile b/Tests/CucumberTests/CPViewControllerTest/Jakefile new file mode 100644 index 000000000..a74e8bd3f --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/Jakefile @@ -0,0 +1,194 @@ +/* + * Jakefile + * CPViewControllerTest + * + * Created by You on May 9, 2016. + * Copyright 2016, Your Company All rights reserved. + */ + +var ENV = require("system").env, + FILE = require("file"), + JAKE = require("jake"), + task = JAKE.task, + FileList = JAKE.FileList, + app = require("cappuccino/jake").app, + configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug", + OS = require("os"), + projectName = "CPViewControllerTest"; + +app (projectName, function(task) +{ + ENV["OBJJ_INCLUDE_PATHS"] = "Frameworks"; + + if (configuration === "Debug") + ENV["OBJJ_INCLUDE_PATHS"] = FILE.join(ENV["OBJJ_INCLUDE_PATHS"], configuration); + + task.setBuildIntermediatesPath(FILE.join("Build", "CPViewControllerTest.build", configuration)); + task.setBuildPath(FILE.join("Build", configuration)); + + task.setProductName("CPViewControllerTest"); + task.setIdentifier("com.yourcompany.CPViewControllerTest"); + task.setVersion("1.0"); + task.setAuthor("Your Company"); + task.setEmail("feedback @nospam@ yourcompany.com"); + task.setSummary("CPViewControllerTest"); + task.setSources(new FileList("**/*.j").exclude(FILE.join("Build", "**")).exclude(FILE.join("Frameworks", "Source", "**"))); + task.setResources(new FileList("Resources/**")); + task.setIndexFilePath("index.html"); + task.setInfoPlistPath("Info.plist"); + + if (configuration === "Debug") + task.setCompilerFlags("-DDEBUG -g"); + else + task.setCompilerFlags("-O2"); +}); + +task ("default", [projectName], function() +{ + printResults(configuration); +}); + +task ("build", ["default"], function() +{ + updateApplicationSize(); +}); + +task ("debug", function() +{ + configuration = ENV["CONFIGURATION"] = "Debug"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("release", function() +{ + configuration = ENV["CONFIGURATION"] = "Release"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("run", ["debug"], function() +{ + OS.system(["open", FILE.join("Build", "Debug", projectName, "index.html")]); +}); + +task ("run-release", ["release"], function() +{ + OS.system(["open", FILE.join("Build", "Release", projectName, "index.html")]); +}); + +task ("deploy", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Deployment", projectName)); + OS.system(["press", "-f", FILE.join("Build", "Release", projectName), FILE.join("Build", "Deployment", projectName)]); + printResults("Deployment") +}); + +task ("desktop", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Desktop", projectName)); + require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", projectName), FILE.join("Build", "Desktop", projectName, "CPViewControllerTest.app")); + printResults("Desktop") +}); + +task ("run-desktop", ["desktop"], function() +{ + OS.system([FILE.join("Build", "Desktop", projectName, "CPViewControllerTest.app", "Contents", "MacOS", "NativeHost"), "-i"]); +}); + +function printResults(configuration) +{ + print("----------------------------"); + print(configuration+" app built at path: "+FILE.join("Build", configuration, projectName)); + print("----------------------------"); +} + +function updateApplicationSize() +{ + print("Calculating application file sizes..."); + + var contents = FILE.read(FILE.join("Build", configuration, projectName, "Info.plist"), { charset:"UTF-8" }), + format = CFPropertyList.sniffedFormatOfString(contents), + plist = CFPropertyList.propertyListFromString(contents), + totalBytes = {executable:0, data:0, mhtml:0}; + + // Get the size of all framework executables and sprite data + var frameworksDir = "Frameworks"; + + if (configuration === "Debug") + frameworksDir = FILE.join(frameworksDir, "Debug"); + + var frameworks = FILE.list(frameworksDir); + + frameworks.forEach(function(framework) + { + if (framework !== "Source") + addBundleFileSizes(FILE.join(frameworksDir, framework), totalBytes); + }); + + // Read in the default theme name, and attempt to get its size + var themeName = plist.valueForKey("CPDefaultTheme") || "Aristo2", + themePath = nil; + + if (themeName === "Aristo" || themeName === "Aristo2") + themePath = FILE.join(frameworksDir, "AppKit", "Resources", themeName + ".blend"); + else + themePath = FILE.join("Frameworks", "Resources", themeName + ".blend"); + + if (FILE.isDirectory(themePath)) + addBundleFileSizes(themePath, totalBytes); + + // Add sizes for the app + addBundleFileSizes(FILE.join("Build", configuration, projectName), totalBytes); + + print("Executables: " + totalBytes.executable + ", sprite data: " + totalBytes.data + ", total: " + (totalBytes.executable + totalBytes.data)); + + var dict = new CFMutableDictionary(); + + dict.setValueForKey("executable", totalBytes.executable); + dict.setValueForKey("data", totalBytes.data); + dict.setValueForKey("mhtml", totalBytes.mhtml); + + plist.setValueForKey("CPApplicationSize", dict); + + FILE.write(FILE.join("Build", configuration, projectName, "Info.plist"), CFPropertyList.stringFromPropertyList(plist, format), { charset:"UTF-8" }); +} + +function addBundleFileSizes(bundlePath, totalBytes) +{ + var bundleName = FILE.basename(bundlePath), + environment = bundleName === "Foundation" ? "Objj" : "Browser", + bundlePath = FILE.join(bundlePath, environment + ".environment"); + + if (FILE.isDirectory(bundlePath)) + { + var filename = bundleName + ".sj", + filePath = new FILE.Path(FILE.join(bundlePath, filename)); + + if (filePath.exists()) + totalBytes.executable += filePath.size(); + + filePath = new FILE.Path(FILE.join(bundlePath, "dataURLs.txt")); + + if (filePath.exists()) + totalBytes.data += filePath.size(); + + filePath = new FILE.Path(FILE.join(bundlePath, "MHTMLData.txt")); + + if (filePath.exists()) + totalBytes.mhtml += filePath.size(); + + filePath = new FILE.Path(FILE.join(bundlePath, "MHTMLPaths.txt")); + + if (filePath.exists()) + totalBytes.mhtml += filePath.size(); + } +} + +task ("cucumber-test", function() +{ + var SYSTEM = require("system"); + + OS.system("ln -s " + SYSTEM.prefix + "/packages/cucapp/Cucapp Cucapp") + var code = OS.system("cucumber"); + OS.system("rm -f Cucapp; rm -f cucumber.html") + OS.exit(code); +}); \ No newline at end of file diff --git a/Tests/CucumberTests/CPViewControllerTest/Resources/MainMenu.cib b/Tests/CucumberTests/CPViewControllerTest/Resources/MainMenu.cib new file mode 100644 index 000000000..a547b2801 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/Resources/MainMenu.cib @@ -0,0 +1 @@ +280NPLIST;1.0;D;K;4;$topD;K;18;CPCibObjectDataKeyD;K;6;CP$UIDd;1;2E;E;K;8;$objectsA;S;5;$nullD;K;10;$classnameS;16;_CPCibObjectDataK;8;$classesA;S;16;_CPCibObjectDataS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;1E;K;28;_CPCibObjectDataNamesKeysKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataNamesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataClassesKeysKeyD;K;6;CP$UIDd;1;0E;K;32;_CPCibObjectDataClassesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataConnectionsKeyD;K;6;CP$UIDd;1;4E;K;28;_CPCibObjectDataFrameworkKeyD;K;6;CP$UIDd;1;0E;K;26;_CPCibObjectDataNextOidKeyD;K;6;CP$UIDd;1;5E;K;30;_CPCibObjectDataObjectsKeysKeyD;K;6;CP$UIDd;1;6E;K;32;_CPCibObjectDataObjectsValuesKeyD;K;6;CP$UIDd;1;7E;K;26;_CPCibObjectDataOidKeysKeyD;K;6;CP$UIDd;1;8E;K;28;_CPCibObjectDataOidValuesKeyD;K;6;CP$UIDd;1;9E;K;28;_CPCibObjectDataFileOwnerKeyD;K;6;CP$UIDd;2;11E;K;33;_CPCibObjectDataVisibleWindowsKeyD;K;6;CP$UIDd;2;13E;E;D;K;10;$classnameS;7;CPArrayK;8;$classesA;S;7;CPArrayS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;15E;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;17E;D;K;6;CP$UIDd;2;18E;D;K;6;CP$UIDd;2;20E;D;K;6;CP$UIDd;2;22E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;23E;D;K;6;CP$UIDd;2;25E;D;K;6;CP$UIDd;2;27E;D;K;6;CP$UIDd;2;29E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;31E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;32E;D;K;6;CP$UIDd;2;34E;D;K;6;CP$UIDd;2;36E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;25E;D;K;6;CP$UIDd;2;27E;D;K;6;CP$UIDd;2;29E;D;K;6;CP$UIDd;2;27E;D;K;6;CP$UIDd;2;31E;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;18;_CPCibCustomObjectK;8;$classesA;S;18;_CPCibCustomObjectS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;37E;E;D;K;10;$classnameS;5;CPSetK;8;$classesA;S;5;CPSetS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;12E;K;15;CPSetObjectsKeyD;K;6;CP$UIDd;2;38E;E;D;K;10;$classnameS;20;CPCibOutletConnectorK;8;$classesA;S;20;CPCibOutletConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;11E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;32E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;39E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;32E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;25E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;40E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;32E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;34E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;41E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;36E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;34E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;42E;E;D;K;10;$classnameS;21;CPCibControlConnectorK;8;$classesA;S;21;CPCibControlConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;19E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;29E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;32E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;43E;E;D;K;10;$classnameS;21;CPCibBindingConnectorK;8;$classesA;S;21;CPCibBindingConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;21E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;31E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;36E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;44E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;2;45E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;2;46E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;2;48E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;37E;E;D;K;10;$classnameS;20;_CPCibWindowTemplateK;8;$classesA;S;20;_CPCibWindowTemplateS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;24E;K;30;_CPCibWindowTemplateMaxSizeKeyD;K;6;CP$UIDd;2;49E;K;32;_CPCibWindowTemplateViewClassKeyD;K;6;CP$UIDd;1;0E;K;34;_CPCibWindowTemplateWindowClassKeyD;K;6;CP$UIDd;2;50E;K;30;_CPCibWindowTemplateWTFlagsKeyD;K;6;CP$UIDd;2;51E;K;33;_CPCibWindowTemplateWindowRectKeyD;K;6;CP$UIDd;2;52E;K;33;_CPCibWindowTemplateScreenRectKeyD;K;6;CP$UIDd;2;53E;K;30;_CPCibWindowTempatStyleMaskKeyD;K;6;CP$UIDd;2;54E;K;34;_CPCibWindowTemplateWindowTitleKeyD;K;6;CP$UIDd;2;55E;K;33;_CPCibWindowTemplateWindowViewKeyD;K;6;CP$UIDd;2;27E;E;D;K;10;$classnameS;6;CPViewK;8;$classesA;S;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;26E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;56E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;57E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;57E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;2;58E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;59E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;60E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;60E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;61E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;62E;E;D;K;10;$classnameS;8;CPButtonK;8;$classesA;S;8;CPButtonS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;28E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;27E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;56E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;63E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;64E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;27E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;65E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;2;66E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;67E;K;15;$aimage-scalingD;K;6;CP$UIDd;2;56E;K;6;$afontD;K;6;CP$UIDd;2;69E;K;17;$aline-break-modeD;K;6;CP$UIDd;2;56E;K;11;$aalignmentD;K;6;CP$UIDd;2;70E;K;7;$aimageD;K;6;CP$UIDd;2;72E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;2;73E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;60E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;60E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;61E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;74E;K;17;CPControlValueKeyD;K;6;CP$UIDd;2;56E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;2;75E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;2;56E;K;33;CPControlBaseWrittingDirectionKeyD;K;6;CP$UIDd;2;76E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;2;77E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;2;78E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;2;61E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;2;79E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;2;56E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;2;80E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;2;70E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;2;56E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;10;$classnameS;11;CPTextFieldK;8;$classesA;S;11;CPTextFieldS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;30E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;27E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;56E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;81E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;82E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;27E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;65E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;2;83E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;84E;K;6;$afontD;K;6;CP$UIDd;2;85E;K;17;$aline-break-modeD;K;6;CP$UIDd;2;70E;K;11;$aalignmentD;K;6;CP$UIDd;2;75E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;2;86E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;60E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;60E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;61E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;87E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;2;80E;K;17;CPControlValueKeyD;K;6;CP$UIDd;2;88E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;2;75E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;2;56E;K;33;CPControlBaseWrittingDirectionKeyD;K;6;CP$UIDd;2;76E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;2;61E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;2;61E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;2;61E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;2;90E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;2;70E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;2;75E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;2;61E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;2;61E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;2;80E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;91E;E;D;K;10;$classnameS;16;CPViewControllerK;8;$classesA;S;16;CPViewControllerS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;33E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;23;CPViewControllerViewKeyD;K;6;CP$UIDd;1;0E;K;24;CPViewControllerTitleKeyD;K;6;CP$UIDd;1;0E;K;26;CPViewControllerCibNameKeyD;K;6;CP$UIDd;2;92E;K;25;CPViewControllerBundleKeyD;K;6;CP$UIDd;1;0E;E;D;K;10;$classnameS;18;CPObjectControllerK;8;$classesA;S;18;CPObjectControllerS;12;CPControllerS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;35E;K;28;CPObjectControllerContentKeyD;K;6;CP$UIDd;1;0E;K;36;CPObjectControllerObjectClassNameKeyD;K;6;CP$UIDd;2;93E;K;31;CPObjectControllerIsEditableKeyD;K;6;CP$UIDd;2;80E;K;49;CPObjectControllerAutomaticallyPreparesContentKeyD;K;6;CP$UIDd;2;61E;K;37;CPObjectControllerUsesLazyFetchingKeyD;K;6;CP$UIDd;2;61E;K;40;CPObjectControllerIsUsingManagedProxyKeyD;K;6;CP$UIDd;2;61E;K;33;CPObjectControllerManagedProxyKeyD;K;6;CP$UIDd;2;95E;E;S;13;CPApplicationD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;25E;E;E;S;8;delegateS;9;theWindowS;14;viewControllerS;7;contentS;5;load:S;44;displayPatternValue1: selection.isViewLoadedS;20;displayPatternValue1S;22;selection.isViewLoadedD;K;10;$classnameS;12;CPDictionaryK;8;$classesA;S;12;CPDictionaryS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;47E;K;10;CP.objectsD;K;24;CPNoSelectionPlaceholderD;K;6;CP$UIDd;2;96E;K;16;CPDisplayPatternD;K;6;CP$UIDd;2;97E;E;E;S;32;{10000000000000, 10000000000000}S;8;CPWindowd;10;1879048192S;24;{{335, 128}, {480, 360}}S;21;{{0, 0}, {1440, 878}}d;1;7S;6;Windowd;1;0S;20;{{0, 0}, {480, 360}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;29E;D;K;6;CP$UIDd;2;31E;E;E;S;6;normalS;6;{1, 1}F;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;20;{{26, 20}, {58, 25}}S;18;{{0, 0}, {58, 25}}d;2;36S;6;buttonS;27;bordered+controlSizeRegularD;K;10;$classnameS;6;CPFontK;8;$classesA;S;6;CPFontS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;68E;K;13;CPFontNameKeyD;K;6;CP$UIDd;2;98E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;2;76E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;2;80E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;2;61E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;2;80E;E;d;1;2D;K;10;$classnameS;17;_CPThemeAttributeK;8;$classesA;S;17;_CPThemeAttributeS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;71E;K;4;nameD;K;6;CP$UIDd;2;99E;K;12;defaultValueD;K;6;CP$UIDd;3;101E;K;5;stateD;K;6;CP$UIDd;3;102E;K;5;valueD;K;6;CP$UIDd;3;103E;E;S;4;loadD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;d;1;4d;2;-1S;4;LoadS;0;d;2;14T;S;22;{{101, 20}, {159, 21}}S;19;{{0, 0}, {159, 21}}S;9;textfieldS;18;controlSizeRegularD;K;6;$classD;K;6;CP$UIDd;2;68E;K;13;CPFontNameKeyD;K;6;CP$UIDd;2;98E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;104E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;2;61E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;2;61E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;2;80E;E;S;6;resultD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;13;isViewLoaded=D;K;10;$classnameS;7;CPColorK;8;$classesA;S;7;CPColorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;89E;K;20;CPColorComponentsKeyD;K;6;CP$UIDd;3;105E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;106E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;59E;E;S;13;AppControllerS;14;ViewControllerS;19;CPMutableDictionaryD;K;10;$classnameS;15;_CPManagedProxyK;8;$classesA;S;15;_CPManagedProxyS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;94E;K;27;CPManagedProxyEntityNameKeyD;K;6;CP$UIDd;1;0E;K;31;CPManagedProxyFetchPredicateKeyD;K;6;CP$UIDd;1;0E;E;S;2;NOS;23;isViewLoaded=%{value1}@S;28;_CPFontSystemFacePlaceholderS;5;imageD;K;10;$classnameS;6;CPNullK;8;$classesA;S;6;CPNullS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;100E;E;S;11;highlightedD;K;6;$classD;K;6;CP$UIDd;2;68E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;107E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;108E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;2;61E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;2;61E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;2;61E;E;d;2;17D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;109E;D;K;6;CP$UIDd;3;109E;D;K;6;CP$UIDd;3;109E;D;K;6;CP$UIDd;3;110E;E;E;S;5;colorS;18;.AppleSystemUIFontd;2;13f;18;0.6862745098039216d;1;1E;K;9;$archiverS;15;CPKeyedArchiverK;8;$versionS;6;100000E; \ No newline at end of file diff --git a/Tests/CucumberTests/CPViewControllerTest/Resources/MainMenu.xib b/Tests/CucumberTests/CPViewControllerTest/Resources/MainMenu.xib new file mode 100644 index 000000000..3c0c28991 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/Resources/MainMenu.xib @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + isViewLoaded=%{value1}@ + NO + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/CucumberTests/CPViewControllerTest/Resources/ViewController.cib b/Tests/CucumberTests/CPViewControllerTest/Resources/ViewController.cib new file mode 100644 index 000000000..45f5c8599 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/Resources/ViewController.cib @@ -0,0 +1 @@ +280NPLIST;1.0;D;K;4;$topD;K;18;CPCibObjectDataKeyD;K;6;CP$UIDd;1;2E;E;K;8;$objectsA;S;5;$nullD;K;10;$classnameS;16;_CPCibObjectDataK;8;$classesA;S;16;_CPCibObjectDataS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;1E;K;28;_CPCibObjectDataNamesKeysKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataNamesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataClassesKeysKeyD;K;6;CP$UIDd;1;0E;K;32;_CPCibObjectDataClassesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataConnectionsKeyD;K;6;CP$UIDd;1;4E;K;28;_CPCibObjectDataFrameworkKeyD;K;6;CP$UIDd;1;0E;K;26;_CPCibObjectDataNextOidKeyD;K;6;CP$UIDd;1;5E;K;30;_CPCibObjectDataObjectsKeysKeyD;K;6;CP$UIDd;1;6E;K;32;_CPCibObjectDataObjectsValuesKeyD;K;6;CP$UIDd;1;7E;K;26;_CPCibObjectDataOidKeysKeyD;K;6;CP$UIDd;1;8E;K;28;_CPCibObjectDataOidValuesKeyD;K;6;CP$UIDd;1;9E;K;28;_CPCibObjectDataFileOwnerKeyD;K;6;CP$UIDd;2;11E;K;33;_CPCibObjectDataVisibleWindowsKeyD;K;6;CP$UIDd;2;13E;E;D;K;10;$classnameS;7;CPArrayK;8;$classesA;S;7;CPArrayS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;15E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;18E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;18;_CPCibCustomObjectK;8;$classesA;S;18;_CPCibCustomObjectS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;19E;E;D;K;10;$classnameS;5;CPSetK;8;$classesA;S;5;CPSetS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;12E;K;15;CPSetObjectsKeyD;K;6;CP$UIDd;2;20E;E;D;K;10;$classnameS;20;CPCibOutletConnectorK;8;$classesA;S;20;CPCibOutletConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;11E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;18E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;21E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;22E;E;D;K;10;$classnameS;18;_CPCibClassSwapperK;8;$classesA;S;18;_CPCibClassSwapperS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;17E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;23E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;24E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;24E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;25E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;26E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;27E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;27E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;28E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;29E;K;30;_CPCibClassSwapperClassNameKeyD;K;6;CP$UIDd;2;30E;K;38;_CPCibClassSwapperOriginalClassNameKeyD;K;6;CP$UIDd;2;31E;E;S;16;CPViewControllerD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;4;viewS;13;CPApplicationd;1;0S;20;{{0, 0}, {480, 272}}d;2;12S;6;normalS;6;{1, 1}F;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;6;NSViewS;6;CPViewE;K;9;$archiverS;15;CPKeyedArchiverK;8;$versionS;6;100000E; \ No newline at end of file diff --git a/Tests/CucumberTests/CPViewControllerTest/Resources/ViewController.xib b/Tests/CucumberTests/CPViewControllerTest/Resources/ViewController.xib new file mode 100644 index 000000000..d3c693d46 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/Resources/ViewController.xib @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Tests/CucumberTests/CPViewControllerTest/features/step_definitions/steps.rb b/Tests/CucumberTests/CPViewControllerTest/features/step_definitions/steps.rb new file mode 100644 index 000000000..ca318f6ea --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/features/step_definitions/steps.rb @@ -0,0 +1,173 @@ +# Given the application is lauched +Given /^the application is launched$/ do + launched = app.gui.command "launched" + + if !launched + raise "The application was not launched" + end +end + +# Given I wait for n seconds +Given /^I wait for (\d+) seconds?$/ do |n| + sleep(eval("#{n.to_i}")) +end + +# When I close the popover +When /^I close the popover$/ do + step "I hit the key escape" +end + + +# When I hit the key c +When /^I hit the key (.*)$/ do |key| + step "I hit the mask none and the key #{key}" +end + + +# When I hit the mask shif and the key c +When /^I hit the mask (.*) and the key (.*)$/ do |mask, key| + simulate_keyboard_event(key, mask) +end + + +# When the keys cucapp +When /^I hit the keys (.*)$/ do |keys| + step "I hit the mask none and keys #{keys}" +end + + +# When I hit the mask shif and the keys cucapp +When /^I hit the mask (.*) and keys (.*)$/ do |mask, keys| + simulate_keyboard_event(keys, mask) +end + + +# When I select all +When /^I select all$/ do + app.gui.simulate_keyboard_event "a", [$CPCommandKeyMask] +end + + +# When I save the document +When /^I save the document$/ do + app.gui.simulate_keyboard_event "s", [$CPCommandKeyMask] +end + + +# When I (click|right click|double click) on the field with the value cucapp +When /^I (click|right click|double click) on the (\w*\-*\w*) with the value (.*)$/ do |click_type, element, value| + step "I #{click_type} on the #{element} with the property object-value set to #{value}" +end + + +# When I click on the field with the property cucapp-identifier set to cucapp-identifier-button-add +When /^I (click|right click|double click) on the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |click_type, element, property, property_value| + step "I #{click_type} with the key mask none on the #{element} with the property #{property} set to #{property_value}" +end + + +# When I click with the mask shift on the field with the property cucapp-identifier set to cucapp-identifier-button-add +When /^I (click|right click|double click) with the key mask (.*) on the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |click_type, mask, element, property, property_value| + + type = $mouse_left_click + + if click_type == "right click" + type = $mouse_right_click + end + + if click_type == "double click" + type = $mouse_double_click + end + + simulate_click(type, element, property, property_value, mask) +end + + +# When I do a drag and drop from the field with the value cucapp to the field with the value cappuccino +When /^I do a drag and drop from the (\w*\-*\w*) with the value (.*) to the (\w*\-*\w*) with the value (.*)$/ do |element, value, second_element, second_value| + step "I do a drag and drop from the #{element} with the property object-value set to #{value} to the #{second_element} with the property object-value set to #{second_value}" +end + + +# When I do a drag and drop from the field with the property title set to cucapp to the field with the property title set to cappuccino +When /^I do a drag and drop from the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) to the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |element, property, property_value, second_element, second_property, second_property_value| + step "I do a drag and drop with the key mask none from the #{element} with the property #{property} set to #{property_value} to the #{second_element} with the property #{second_property} set to #{second_property_value}" +end + + +# When I do a drag and drop with the key mask shift from the field with the property title set to cucapp to the field with the property title set to cappuccino +When /^I do a drag and drop with the key mask (.*) from the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) to the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |mask, element, property, property_value, second_element, second_property, second_property_value| + simulate_drag_and_drop(element, property, property_value, second_element, second_property, second_property_value, mask) +end + + +# When I vertically|horizontally scroll on the field with the value cappuccino +When /^I (vertically|horizontally) scroll on the (\w*\-*\w*) with the value (.*)$/ do |direction, element, value| + step "When I #{direction} scroll 10 times on the #{element} with the value #{value}" +end + + +# When I vertically|horizontally scroll 10 times on the field with the value cappuccino +When /^I (vertically|horizontally) scroll ([0-9]*) times on the (\w*\-*\w*) with the value (.*)$/ do |direction, times, element, value| + step "I #{direction} scroll #{times} times on the #{element} with the property object-value set to #{value}" +end + + +# When I vertically|horizontally scroll 10 times on the field with the property title set to cappuccino +When /^I (vertically|horizontally) scroll ([0-9]*) times on the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |direction, times, element, property, property_value| + step "I #{direction} scroll #{times} times with the key mask none on the #{element} with the property #{property} set to #{property_value}" +end + + +# When I vertically|horizontally scroll 10 times on the field with the property title set to cappuccino +When /^I (vertically|horizontally) scroll ([0-9]*) times with the key mask (.*) on the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |direction, times, mask, element, property, property_value| + + vertically = false + horizontally = false + + if direction == "vertically" + vertically = true + end + + if direction == "horizontally" + horizontally = true + end + + simulate_scroll(element, property, property_value, times, mask, horizontally, vertically) +end + + +# When I select the item name of the pop-up-button with the property cucapp-identifier set to cucappIdentifierPopUpButton +When /^I select the item (.*) of the pop-up-button with the property (\w*\-*\w*) set to (.*)$/ do |item_name, property, property_value| + select_pop_up_button_item(item_name, property, property_value) +end + + +# Then the field with the property title set to name should be focused +Then /^the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) should be focused$/ do |element, property, property_value| + app.gui.is_control_focused(create_xpath(element, property, property_value)) +end + + +# Then the field should not have a value +Then /^the (\w*\-*\w*) should not have a value$/ do |element| + step "the #{element} with the property object-value set to #{value} should have the value #{value}" +end + + +# Then the field with the property cucapp-identifier set to cucapp-identifier-textfield-description should not have a value +Then /^the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) should not have a value$/ do |element, property, property_value| + check_value_control(element, property, property_value, nil) +end + + +# Then the field should have the value cucapp +Then /^the (\w*\-*\w*) should have the value (.*)$/ do |element, value| + step "the #{element} with the property object-value set to #{value} should have the value #{value}" +end + + +# Then the field with the property cucapp-identifier set to cucapp-identifier-textfield-description should have the value cucapp +Then /^the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) should have the value (.*)$/ do |element, property, property_value, value| + check_value_control(element, property, property_value, value) +end diff --git a/Tests/CucumberTests/CPViewControllerTest/features/support/Cucumber+Extensions.j b/Tests/CucumberTests/CPViewControllerTest/features/support/Cucumber+Extensions.j new file mode 100644 index 000000000..6c0c74363 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/features/support/Cucumber+Extensions.j @@ -0,0 +1,19 @@ +@import + +@implementation Cucumber (CuCapp) + +- (CPString)valueIsEqual:(CPArray)params +{ + var obj = cucumber_objects[params[0]], + value = params[1]; + + if (!obj) + return '{"result" : "__CUKE_ERROR__"}'; + + if ([obj respondsToSelector:@selector(stringValue)] && value === [obj stringValue]) + return '{"result" : "OK"}'; + + return '{"result" : "__CUKE_ERROR__"}'; +} + +@end \ No newline at end of file diff --git a/Tests/CucumberTests/CPViewControllerTest/features/support/cappuccino_mappings.rb b/Tests/CucumberTests/CPViewControllerTest/features/support/cappuccino_mappings.rb new file mode 100644 index 000000000..fd8dfddba --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/features/support/cappuccino_mappings.rb @@ -0,0 +1,62 @@ +$cappuccino_control_mappings = { + "image-view-text" => "_CPImageAndTextView", + "menu-item" => "_CPMenuItemView", + "tool-bar-item" => "_CPToolbarItemView", + "box" => "CPBox", + "button" => "CPButton", + "button-bar" => "CPButtonBar", + "collection-view" => "CPCollectionView", + "combo-box" => "CPComboBox", + "control" => "CPControl", + "check-box" => "CPCheckBox", + "date-picker" => "CPDatePicker", + "image-view" => "CPImageView", + "level-indicator" => "CPLevelIndicator", + "outline-view" => "CPOutlineView", + "pop-up-button" => "CPPopUpButton", + "predicate-editor" => "CPPredicateEditor", + "radio-button" => "CPRadio", + "rule-editor" => "CPRuleEditor", + "scroller" => "CPScroller", + "scroll-view" => "CPScrollView", + "search-field" => "CPSearchField", + "secure-field" => "CPSecureTextField", + "segemented-control" => "CPSegmentedControl", + "slider" => "CPSlider", + "stepper" => "CPStepper", + "tab-view" => "CPTabView", + "table" => "CPTableView", + "field" => "CPTextField", + "token-field" => "CPTokenField", + "view" => "CPView", + "none" => nil +} + +$property_mappings = { + "cucapp-identifier" => "cucappIdentifier", + "id" => "id", + "identifier" => "identifier", + "label" => "label", + "object-value" => "objectValue", + "placeholder" => "placeholderString", + "tag" => "tag", + "title" => "title", + "text" => "text", + "none" => nil +} + +$key_mappings = { + "command" => $CPCommandKeyMask, + "shift" => $CPShiftKeyMask, + "option" => $CPAlternateKeyMask, + "control" => $CPControlKeyMask, + "delete" => $CPDeleteCharacter, + "escape" => $CPEscapeFunctionKey, + "enter" => $CPNewlineCharacter, + "left-arrow" => $CPLeftArrowFunctionKey, + "right-arrow" => $CPRightArrowFunctionKey, + "top-arrow" => $CPTopArrowFunctionKey, + "down-arrow" => $CPDownArrowFunctionKey, + "tab" => $CPTabCharacter, + "none" => nil +} \ No newline at end of file diff --git a/Tests/CucumberTests/CPViewControllerTest/features/support/encumber_category.rb b/Tests/CucumberTests/CPViewControllerTest/features/support/encumber_category.rb new file mode 100644 index 000000000..985d40100 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/features/support/encumber_category.rb @@ -0,0 +1,14 @@ +module Encumber + + class GUI + def value_is_equal(xpath, value) + + if !value + value = "" + end + + result = command 'valueIsEqual', id_for_element(xpath), value + raise "Value #{value} not found" if result["result"] != "OK" + end + end +end \ No newline at end of file diff --git a/Tests/CucumberTests/CPViewControllerTest/features/support/env.rb b/Tests/CucumberTests/CPViewControllerTest/features/support/env.rb new file mode 100644 index 000000000..949cb9b72 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/features/support/env.rb @@ -0,0 +1,20 @@ +$: << File.join(File.dirname(__FILE__), '..', '..', 'Cucapp') + +require 'cucapp.rb' +require 'logger' + +module AppHelper + + def app + @app ||= Cucapp.new + end + + def log + @log ||= ENV['log'] + end + +end + +World( + AppHelper +) \ No newline at end of file diff --git a/Tests/CucumberTests/CPViewControllerTest/features/support/hooks.rb b/Tests/CucumberTests/CPViewControllerTest/features/support/hooks.rb new file mode 100644 index 000000000..cb022d6e5 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/features/support/hooks.rb @@ -0,0 +1,7 @@ +Before do + app.reset() +end + +After do + app.quit() +end \ No newline at end of file diff --git a/Tests/CucumberTests/CPViewControllerTest/features/support/steps_helpers.rb b/Tests/CucumberTests/CPViewControllerTest/features/support/steps_helpers.rb new file mode 100644 index 000000000..701df0cb6 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/features/support/steps_helpers.rb @@ -0,0 +1,92 @@ +def check_value_control(element, property, property_value, value) + xpath = create_xpath(element, property, property_value) + + app.gui.wait_for xpath + app.gui.value_is_equal xpath, value +end + +def simulate_keyboard_event(keys, mask) + app.gui.simulate_keyboard_events cappuccino_key(keys), [cappuccino_key(mask)] +end + +def simulate_click(type, element, property, property_value, mask) + xpath = create_xpath(element, property, property_value) + + app.gui.wait_for xpath + + if type == $mouse_double_click + app.gui.simulate_double_click xpath, [cappuccino_key(mask)] + elsif type == $mouse_right_click + app.gui.simulate_right_click xpath, [cappuccino_key(mask)] + else + app.gui.simulate_left_click xpath, [cappuccino_key(mask)] + end +end + +def simulate_drag_and_drop(element, property, property_value, second_element, second_property, second_property_value, mask) + + xpath1 = create_xpath(element, property, property_value) + xpath2 = create_xpath(second_element, second_property, second_property_value) + + app.gui.simulate_dragged_click_view_to_view xpath1, xpath2, [cappuccino_key(mask)] +end + +def simulate_scroll(element, property, property_value, times, mask, horizontal, vertical) + xpath = create_xpath(element, property, property_value) + delta_x = 0 + delta_y = 0 + + if horizontal + delta_x = 1 + end + + if vertical + delta_y = 1 + end + + for i in 0..times.to_i + app.gui.simulate_scroll_wheel xpath, delta_x, delta_y, [cappuccino_key(mask)] + end +end + +def select_pop_up_button_item(item_name, property, property_value) + simulate_click($mouse_left_click, "pop-up-button", property, property_value, []) + + pop_up_button_xpath = create_xpath("pop-up-button", property, property_value) + + pop_up_button_item_xpath = create_xpath("image-view-text", "text", item_name) + + while !app.gui.wait_for_element(pop_up_button_item_xpath, 0.05) && app.gui.pop_up_button_can_scroll_up(pop_up_button_xpath) + simulate_keyboard_event("up-arrow", []) + end + + while !app.gui.wait_for_element(pop_up_button_item_xpath, 0.05) && app.gui.pop_up_button_can_scroll_down(pop_up_button_xpath) + simulate_keyboard_event("down-arrow", []) + end + + if not app.gui.wait_for(pop_up_button_item_xpath) + raise "Menu item #{item_name} not found !" + end + + simulate_click($mouse_left_click, "image-view-text", "text", item_name, []) +end + +def cappuccino_key(key) + if $key_mappings.has_key?(key) + key = $key_mappings[key] + end + + return key +end + +def create_xpath(element, property, property_value) + if !$cappuccino_control_mappings.has_key?(element) + raise "Element #{element} not found in the hash cappuccino_control_mappings. You should complete the hash $cappuccino_control_mappings in env.rb" + end + + if !$property_mappings.has_key?(property) + raise "Property #{property} not found in the hash $property_mappings. You should complete the hash $property_mappings in env.rb" + end + + return "//" + $cappuccino_control_mappings[element] + "["+ $property_mappings[property] +"='#{property_value}']" +end \ No newline at end of file diff --git a/Tests/CucumberTests/CPViewControllerTest/features/test_application.feature b/Tests/CucumberTests/CPViewControllerTest/features/test_application.feature new file mode 100644 index 000000000..13b4d5128 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/features/test_application.feature @@ -0,0 +1,8 @@ +Feature: Test the CPViewController asynchronous loading +This test is used to make sure that the isViewLoaded property is true when the loading has ended. + + Scenario: Check if the application is launched + Given the application is launched + When I click on the button with the property identifier set to load + Given I wait for 1 second + Then the field with the property identifier set to result should have the value isViewLoaded=true diff --git a/Tests/CucumberTests/CPViewControllerTest/index-debug.html b/Tests/CucumberTests/CPViewControllerTest/index-debug.html new file mode 100644 index 000000000..a63afc525 --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/index-debug.html @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + CPViewControllerTest + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/CucumberTests/CPViewControllerTest/index.html b/Tests/CucumberTests/CPViewControllerTest/index.html new file mode 100644 index 000000000..1556ed16a --- /dev/null +++ b/Tests/CucumberTests/CPViewControllerTest/index.html @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + CPViewControllerTest + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/Manual/Issue-1357-fix/main.j b/Tests/CucumberTests/CPViewControllerTest/main.j similarity index 58% rename from Tests/Manual/Issue-1357-fix/main.j rename to Tests/CucumberTests/CPViewControllerTest/main.j index ca820e685..1ed076b00 100644 --- a/Tests/Manual/Issue-1357-fix/main.j +++ b/Tests/CucumberTests/CPViewControllerTest/main.j @@ -1,9 +1,9 @@ /* * AppController.j - * CPViewNoDisplayAfterHidingAndResizeBug + * CPViewControllerTest * - * Created by You on February 24, 2013. - * Copyright 2013, Your Company All rights reserved. + * Created by You on May 9, 2016. + * Copyright 2016, Your Company All rights reserved. */ @import diff --git a/Tests/Foundation/CPKeyValueObservingTest.j b/Tests/Foundation/CPKeyValueObservingTest.j index 59b501b1c..76da13b72 100644 --- a/Tests/Foundation/CPKeyValueObservingTest.j +++ b/Tests/Foundation/CPKeyValueObservingTest.j @@ -265,6 +265,22 @@ var _getCheeseCounter; [self assert:dependantKeyPathTester equals:_lastObject]; } +- (void)testSendNotificationsForMultipleDependantKeyPathsToTheSameObject +{ + var observingTester = [ObservingTester testerWithCheese:@"cheese"], + dependantKeyPathTester = [DependantKeyPathsTester testerWithObservingTester:observingTester], + anotherDependantKeyPathTester = [DependantKeyPathsTester testerWithObservingTester:observingTester]; + + [dependantKeyPathTester addObserver:self forKeyPath:@"observedCheese" options:CPKeyValueObservingOptionNew context:nil]; + [anotherDependantKeyPathTester addObserver:self forKeyPath:@"observedCheese" options:CPKeyValueObservingOptionNew context:nil]; + [observingTester setCheese:@"changed cheese"]; + + [self assert:@"observedCheese" equals:_lastKeyPath]; + [self assert:@"observedCheese" equals:_secondLastKeyPath]; + [self assertTrue:[[dependantKeyPathTester, anotherDependantKeyPathTester] containsObject:_lastObject] message:@"Last observed object must be one of the DependantKeyPathsTester"]; + [self assertTrue:[[dependantKeyPathTester, anotherDependantKeyPathTester] containsObject:_secondLastObject] message:@"Second last observed object must be one of the DependantKeyPathsTester"]; +} + - (void)testOnlyInsertObject_AtKeyIndex_Implemented { var insertSelector = @selector(insertObject:inObjectsAtIndex:), diff --git a/Tests/Foundation/CPNumberTest.j b/Tests/Foundation/CPNumberTest.j index 5939a2bc8..86c0141ef 100644 --- a/Tests/Foundation/CPNumberTest.j +++ b/Tests/Foundation/CPNumberTest.j @@ -12,4 +12,45 @@ [self assertThrows:function () { [34 compare:[CPNull null]] }]; } +- (void)testIntValue +{ + /* + For reference: + + [[NSNumber numberWithDouble:3.1] intValue]: 3 + [[NSNumber numberWithDouble:3.9] intValue]: 3 + [[NSNumber numberWithDouble:-3.1] intValue]: -3 + [[NSNumber numberWithDouble:-3.9] intValue]: -3 + [[NSNumber numberWithDouble:3.1] integerValue]: 3 + [[NSNumber numberWithDouble:3.9] integerValue]: 3 + [[NSNumber numberWithDouble:-3.1] integerValue]: -3 + [[NSNumber numberWithDouble:-3.9] integerValue]: -3 + */ + + var testStrings = [ +// [090, 90], // Removed cause Rhino does not support numbers starting with '0' + [-1, -1], + [3.1415, 3], + [3.5415, 3], + [-3.1415, -3], + [-3.5415, -3], + [2.7183, 2], + [-0, 0], + [00, 0], + [-00, 0], + [+001, 1], + ]; + + for (var i = 0; i < testStrings.length; i++) + [self assert:[testStrings[i][0] shortValue] equals:testStrings[i][1]]; + for (var i = 0; i < testStrings.length; i++) + [self assert:[testStrings[i][0] intValue] equals:testStrings[i][1]]; + for (var i = 0; i < testStrings.length; i++) + [self assert:[testStrings[i][0] longValue] equals:testStrings[i][1]]; + for (var i = 0; i < testStrings.length; i++) + [self assert:[testStrings[i][0] longLongValue] equals:testStrings[i][1]]; + for (var i = 0; i < testStrings.length; i++) + [self assert:[testStrings[i][0] integerValue] equals:testStrings[i][1]]; +} + @end diff --git a/Tests/Foundation/CPPredicateTest.j b/Tests/Foundation/CPPredicateTest.j index ff89760c0..3a9840e96 100644 --- a/Tests/Foundation/CPPredicateTest.j +++ b/Tests/Foundation/CPPredicateTest.j @@ -79,6 +79,17 @@ var expression_minusset = [CPExpression expressionForMinusSet:set with:array]; [self assertNotNull:expression_minusset message:"MinusSet Expression should not be nil"]; + + var expression_block = [CPExpression expressionForBlock:function(obj, args, bindings) + { + return obj; + } arguments:nil]; + + [self assertNotNull:expression_block message:"Block Expression should not be nil"]; + + var expression_conditional = [CPExpression expressionForConditional:[CPPredicate predicateWithValue:YES] trueExpression:expression_minusset falseExpression:expression_block]; + + [self assertNotNull:expression_conditional message:"Conditional Expression should not be nil"]; } - (void)testSetExpressionEvaluation @@ -147,6 +158,27 @@ [self assertTrue:([eval isEqual:expected]) message:"'" + [expression predicateFormat] + "' result is "+ eval + "but should be " + expected]; } +- (void)testBlockExpressionEvaluation +{ + var block = function(obj, args, bindings) + { + return [obj stringByAppendingString:[args componentsJoinedByString:"-"]]; + }; + + var expression = [CPExpression expressionForBlock:block arguments:[[CPExpression expressionForConstantValue:"A"], [CPExpression expressionForConstantValue:"B"]]]; + + var eval = [expression expressionValueWithObject:"OBJ" context:@{}]; + [self assertTrue:([eval isEqual:"OBJA-B"]) message:"'" + [expression description] + "' result is "+ eval + "but should be " + "OBJA-B"]; +} + +- (void)testConditionalExpressionEvaluation +{ + var expression = [CPExpression expressionForConditional:[CPPredicate predicateWithFormat:"SELF = $variable"] trueExpression:[CPExpression expressionForConstantValue:"TRUE_EXP"] falseExpression:[CPExpression expressionForConstantValue:"FALSE_EXP"]]; + + var eval = [expression expressionValueWithObject:"OBJ" context:@{"variable":"OBJ"}]; + [self assertTrue:([eval isEqual:"TRUE_EXP"]) message:"'" + [expression description] + "' result is "+ eval + "but should be " + "TRUE_EXP"]; +} + - (void)testOptions { var pred = [[CPComparisonPredicate alloc] initWithLeftExpression:[CPExpression expressionForConstantValue:@"àa"] rightExpression:[CPExpression expressionForConstantValue:@"aà"] modifier:CPDirectPredicateModifier type:CPLikePredicateOperatorType options:3]; @@ -381,6 +413,9 @@ predicate = [CPPredicate predicateWithFormat:@"a UNION {'b'} = {'a','b'}"]; var left = [[predicate leftExpression] expressionValueWithObject:object context:nil]; [self assertTrue:[left isEqualToSet:result] message:"Expression eval " + left + " should be " + result]; + + predicate = [CPPredicate predicateWithFormat:@"TERNARY(Record1.Children[SIZE] = 1, 'Single', Record1.Name) = 'John'"]; + [self assertTrue:[predicate evaluateWithObject:dict] message:"Predicate " + predicate + " should evaluate to TRUE"]; } - (void)testExpressionAndPredicateIsEqual @@ -439,6 +474,10 @@ pred1 = [CPPredicate predicateWithFormat:@"a = 'a' AND b = 'b'"]; pred2 = [CPPredicate predicateWithFormat:@"a = 'a' AND b = 'b'"]; [self assert:pred1 equals:pred2]; + + pred1 = [CPPredicate predicateWithFormat:@"TERNARY(Record1.Children[FIRST] BEGINSWITH Record1.Name, 'SAME', 'DIFFERENT') = 'DIFFERENT'"]; + pred2 = [CPPredicate predicateWithFormat:@"TERNARY(Record1.Children[FIRST] BEGINSWITH Record1.Name, 'SAME', 'DIFFERENT') = 'DIFFERENT'"]; + [self assert:pred1 equals:pred2]; } - (void)testExpressionAndPredicateIsNotEqualToNil @@ -490,6 +529,10 @@ [self assert:pred1 notEqual:nil]; + pred1 = [CPPredicate predicateWithFormat:@"TERNARY(Record1.Children[FIRST] BEGINSWITH Record1.Name, 'SAME', 'DIFFERENT') = 'DIFFERENT'"]; + + [self assert:pred1 notEqual:nil]; + pred1 = [CPPredicate predicateWithFormat:@"$x CONTAINS 'a'"]; [self assert:pred1 notEqual:nil]; diff --git a/Tests/Foundation/CPStringTest.j b/Tests/Foundation/CPStringTest.j index 326c781db..5443003c0 100644 --- a/Tests/Foundation/CPStringTest.j +++ b/Tests/Foundation/CPStringTest.j @@ -165,6 +165,25 @@ [self assert:[testStrings[i][0] boolValue] equals:testStrings[i][1]]; } +- (void)testintValue +{ + var testStrings = [ + [" 090", 90], + [" -1", -1], + [" 3.1415", 3], + [" 2.7183", 2], + [" -0", 0], + [" 00", 0], + [" -00", 0], + [" +001", 1], + ]; + + for (var i = 0; i < testStrings.length; i++) + [self assert:[testStrings[i][0] intValue] equals:testStrings[i][1]]; + for (var i = 0; i < testStrings.length; i++) + [self assert:[testStrings[i][0] integerValue] equals:testStrings[i][1]]; +} + - (void)testCommonPrefixWithString { var testStringsCase = [ diff --git a/Tests/Manual/ArrayControllerRemovingFirstTest/AppController.j b/Tests/Manual/ArrayControllerRemovingFirstTest/AppController.j index 1ddd8d6f6..69e2158fc 100644 --- a/Tests/Manual/ArrayControllerRemovingFirstTest/AppController.j +++ b/Tests/Manual/ArrayControllerRemovingFirstTest/AppController.j @@ -29,7 +29,7 @@ [arrayController setContent:items]; var label = [CPTextField labelWithTitle:@"Press buttons to see [Remove First By Object] fails while [Remove First By Index] succeeds"]; - [label setFrameOrigin:CGPointMake(20, 20)]; + [label setFrameOrigin:CGPointMake(20, 20)]; [contentView addSubview:label]; var field = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:100]; @@ -65,7 +65,8 @@ - (void)removeFirstByIndex:(id)sender { - [arrayController removeObjectAtArrangedObjectIndex:0]; + if ([[arrayController contentArray] count]) + [arrayController removeObjectAtArrangedObjectIndex:0]; } - (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context diff --git a/Tests/Manual/CGCanvasContext/AppController.j b/Tests/Manual/CGCanvasContext/AppController.j index c65005a7c..1f024b4d4 100644 --- a/Tests/Manual/CGCanvasContext/AppController.j +++ b/Tests/Manual/CGCanvasContext/AppController.j @@ -50,6 +50,10 @@ var innerRect = CGRectInset(aRect, CGRectGetWidth(aRect)/2 - 10, CGRectGetHeight(aRect)/2 - 10); CGContextStrokeRectWithWidth(context, innerRect, 4); + CGContextSetTextPosition(context, innerRect.origin.x + 10, innerRect.origin.x + 10); + CGContextSetFillColor(context, [CPColor blueColor]); + CGContextShowText(context, 'Hello World Canvas!'); + [self unlockFocus]; } @@ -67,7 +71,7 @@ var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; - [label setStringValue:@"Hello World!"]; + [label setStringValue:@"Do you see the Hello World Canvas?"]; [label setFont:[CPFont boldSystemFontOfSize:24.0]]; [label sizeToFit]; diff --git a/Tests/Manual/CPAnimatablePropertyContainerTest/AppController.j b/Tests/Manual/CPAnimatablePropertyContainerTest/AppController.j new file mode 100644 index 000000000..1ce9f1aa9 --- /dev/null +++ b/Tests/Manual/CPAnimatablePropertyContainerTest/AppController.j @@ -0,0 +1,533 @@ +/* + * AppController.j + * CPAnimatablePropertyContainerTest + * + * Created by You on December 3, 2012. + * Copyright 2012, Your Company All rights reserved. + */ + +@import + +CPLogRegister(CPLogConsole); + +var ANIMATIONS_NAMES = ["Fade In", "Fade Out", "Background Color", "Frame Origin", "Bounce", "Frame Size", "Frame"]; + +@implementation AppController : CPObject +{ + @outlet CPWindow theWindow; //this "outlet" is connected automatically by the Cib + + @outlet CPBox group1Box; + @outlet CPBox group2Box; + + @outlet CPView animationSandbox; + + CPView leftView; + CPView rightView; + + CPArray animations; + + float duration1 @accessors; + float duration2 @accessors; + + CPString message1 @accessors; + CPString message2 @accessors; + + CPInteger selectedTimingFunction1 @accessors; + CPInteger selectedTimingFunction2 @accessors; + + CPString timingFunction1 @accessors; + CPString timingFunction2 @accessors; +} + +- (id)init +{ + self = [super init]; + + animations = [CPArray array]; + + [ANIMATIONS_NAMES enumerateObjectsUsingBlock:function(anim, idx) + { + var dict = [CPDictionary dictionaryWithObjectsAndKeys:anim, @"name", NO, @"enabled1", NO, @"enabled2"]; + [animations addObject:dict]; + }]; + + duration1 = 1.0; + duration2 = 1.0; + + message1 = @"Done for View 1"; + message2 = @"Done for View 2"; + + timingFunction1 = @"0,1,1,0"; + timingFunction2 = @"0,1,1,0"; + + selectedTimingFunction1 = 1; + selectedTimingFunction2 = 1; + + return self; +} + +- (void)applicationDidFinishLaunching:(CPNotification)aNotification +{ + // This is called when the application is done loading. +} + +- (void)awakeFromCib +{ + // This is called when the cib is done loading. + // You can implement this method on any object instantiated from a Cib. + // It's a useful hook for setting up current UI values, and other things. + + [[theWindow contentView] setBackgroundColor:[CPColor colorWithRed:1 green:238/255 blue:185/255 alpha:1]]; + [animationSandbox setBackgroundColor:[CPColor whiteColor]]; + + [self revert:nil]; + // In this case, we want the window from Cib to become our full browser window + [theWindow setFullPlatformWindow:YES]; +} + +- (IBAction)runAnimationsGroup1:(id)sender +{ + [[CPAnimationContext currentContext] setDuration:duration1]; + [[CPAnimationContext currentContext] setTimingFunction:[self timingFunctionForGroup:1 fromPopUp:selectedTimingFunction1]]; + [[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message1]]; + + [self runAnimationsForGroup:1]; +} + +- (IBAction)runAnimationsGroup2:(id)sender +{ + [[CPAnimationContext currentContext] setDuration:duration2]; + [[CPAnimationContext currentContext] setTimingFunction:[self timingFunctionForGroup:2 fromPopUp:selectedTimingFunction2]]; + [[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message2]]; + + [self runAnimationsForGroup:2]; +} + +- (IBAction)animate:(id)sender +{ + [[CPAnimationContext currentContext] setDuration:duration1]; + [[CPAnimationContext currentContext] setTimingFunction:[self timingFunctionForGroup:1 fromPopUp:selectedTimingFunction2]]; + [[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message1]]; + + var tag = [sender tag]; + var width = tag ? 100 : 200; + [[sender animator] setFrameSize:CGSizeMake(width, CGRectGetHeight([sender frame]))]; + + [sender setTag:(1 - tag)]; +} + +- (IBAction)runBothGroups:(id)sender +{ + [CPAnimationContext runAnimationGroup:function(context) + { + [context setDuration:duration1]; + [context setTimingFunction:[self timingFunctionForGroup:1 fromPopUp:selectedTimingFunction1]]; + [self runAnimationsForGroup:1]; + + } completionHandler:function() + { + CPLogConsole(message1); + }]; + + [CPAnimationContext runAnimationGroup:function(context) + { + [context setDuration:duration2]; + [context setTimingFunction:[self timingFunctionForGroup:2 fromPopUp:selectedTimingFunction2]]; + [self runAnimationsForGroup:2]; + + } completionHandler:function() + { + CPLogConsole(message2); + }]; +} + +/* +- (IBAction)runInGroups:(id)sender +{ + [CPAnimationContext beginGrouping]; + + [[CPAnimationContext currentContext] setDuration:duration1]; + [[CPAnimationContext currentContext] setTimingFunction:[self timingFunctionFromString:timingFunction1]]; + [[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message1]]; + [self runAnimationsForGroup:1]; + + [CPAnimationContext endGrouping]; + + + [CPAnimationContext beginGrouping]; + + [[CPAnimationContext currentContext] setDuration:duration2]; + [[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message2]]; + [self runAnimationsForGroup:2]; + + [CPAnimationContext endGrouping]; +} + +- (IBAction)emptyGroupTest:(id)sender +{ + [CPAnimationContext beginGrouping]; + + [[CPAnimationContext currentContext] setDuration:10]; + [[CPAnimationContext currentContext] setCompletionHandler:function() + { + CPLogConsole("Context duration is 10s but no animations were set. We run the completionHandler (this message) immediately and return"); + }]; + + [CPAnimationContext endGrouping]; +} +*/ +- (IBAction)removeFromSuperview:(id)sender +{ + [[leftView animator] removeFromSuperview]; + [[rightView animator] removeFromSuperview]; +} + +- (IBAction)revert:(id)sender +{ + [self setupGroup1:nil]; + [self setupGroup2:nil]; +} +/* +- (IBAction)addSubview:(id)sender +{ + [[theWindow contentView] addSubview:]; +} +*/ +- (void)runAnimationsForGroup:(CPInteger)group +{ + var aView = group == 1 ? leftView : rightView, + enabledKey = @"enabled" + group; + + var enabledIndexes = [animations indexesOfObjectsPassingTest:function(obj, idx) + { + return [obj objectForKey:enabledKey]; + }]; + + if ([enabledIndexes count] == 0) + return; + + if ([enabledIndexes containsIndex:0]) + { + [aView setAlphaValue:0]; + [[aView animator] setAlphaValue:1]; + } + else if ([enabledIndexes containsIndex:1]) + { + [[aView animator] setAlphaValue:0]; + } + + if ([enabledIndexes containsIndex:2]) + { + [[aView animator] setBackgroundColor:[CPColor magentaColor]]; + } + + if ([enabledIndexes containsIndex:6]) + { + var frame = CGRectMakeCopy([aView frame]), + origin = frame.origin, + size = frame.size; + + origin.x += 450; + origin.y += 200; + + size.width += 100; + size.height += 100; + + [[aView animator] setFrame:frame]; + return; + } + + if ([enabledIndexes containsIndex:4]) + { + [[aView animations] setObject:[self bounceAnimation:aView] forKey:@"frameOrigin"]; + + var origin = CGPointMakeCopy([aView frameOrigin]); + [[aView animator] setFrameOrigin:origin]; + } + else if ([enabledIndexes containsIndex:3]) + { + [[aView animations] removeObjectForKey:@"frameOrigin"]; + + var origin = CGPointMakeCopy([aView frameOrigin]); + origin.x +=550; + origin.y +=300; + [[aView animator] setFrameOrigin:origin]; + } + + if ([enabledIndexes containsIndex:5]) + { + var size = CGSizeMakeCopy([aView frameSize]); + size.width += 300; + size.height += 200; + [[aView animator] setFrameSize:size]; + } +} + +- (CAMediaTimingFunction)controlsPointsForGroup:(CPInteger)aGroup +{ + var aString = (aGroup == 1) ? timingFunction1 : timingFunction2; + + if (!aString || ![aString length]) + return nil; + + var controlsPoints = [aString componentsSeparatedByString:@","]; + + return [[controlsPoints[0] floatValue], [controlsPoints[1] floatValue], [controlsPoints[2] floatValue], [controlsPoints[3] floatValue]]; +} + +- (CAMediaTimingFunction)timingFunctionForGroup:(CPInteger)aGroup fromPopUp:(CPInteger)selectedTag +{ + var controlsPoints; + + switch (selectedTag) + { + case 1: controlsPoints = [0, 0, 1, 1]; + break; + case 2: controlsPoints = [0.42, 0, 1, 1]; + break; + case 3: controlsPoints = [0, 0, 0.58, 1]; + break; + case 4: controlsPoints = [0.42, 0, 0.58, 1]; + break; + case 0: controlsPoints = [self controlsPointsForGroup:aGroup]; + break; + } + + return [CAMediaTimingFunction functionWithControlPoints:controlsPoints[0] :controlsPoints[1] :controlsPoints[2] :controlsPoints[3]]; +} + +- (Function)completionHandlerFromString:(CPString)aMessage +{ + if (!aMessage || ![aMessage length]) + return nil; + + var s = new Date(); + return function() + { + var e = new Date() - s; + CPLogConsole(aMessage + " in " + e + " ms"); + }; +} + +- (CAKeyframeAnimation)bounceAnimation:(CPView)aView +{ + var anim = [CAKeyframeAnimation animation], + easein = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], + easeout = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; + + [anim setKeyTimes:[0, 0.25, 0.5, 0.75, 1]]; + var origin = CGPointMakeCopy([aView frameOrigin]); + [anim setValues:[origin, CGPointMake(origin.x, origin.y + 50), origin, CGPointMake(origin.x, origin.y + 25), origin]]; + [anim setTimingFunctions:[easeout, easein, easeout, easein]]; + + return anim; +} + +- (CABasicAnimation)fadeOutAnimation +{ + var animation = [CABasicAnimation animationWithKeyPath:@"alphaValue"]; + [animation setDuration:0.2]; + [animation setFromValue:1]; + [animation setToValue:0]; + + [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]]; + + return animation; +} + +- (IBAction)setupGroup1:(id)sender +{ + var contentView = [group1Box contentView]; + + var hasSubviews = [[contentView viewWithTag:1000] state] * 2, + customLayout = [[contentView viewWithTag:1001] state] * 4, + customDraw = [[contentView viewWithTag:1002] state] * 8, + customDrawSubviews = [[contentView viewWithTag:1003] state], + autoLayout = [[contentView viewWithTag:1004] state], + options = hasSubviews | customLayout | customDraw; + + [leftView removeFromSuperview]; + leftView = [self viewWithOptions:options]; + + if (leftView) + { + [leftView setFrame:CGRectMake(0, 0, 200, 200)]; + [animationSandbox addSubview:leftView]; + } + + if (hasSubviews) + [self addSubviewsToView:leftView autoLayout:(autoLayout && !customLayout) customDrawSubviews:customDrawSubviews]; +} + +- (IBAction)setupGroup2:(id)sender +{ + var contentView = [group2Box contentView]; + + var hasSubviews = [[contentView viewWithTag:1000] state] * 2, + customLayout = [[contentView viewWithTag:1001] state] * 4, + customDraw = [[contentView viewWithTag:1002] state] * 8, + options = hasSubviews | customLayout | customDraw; + + [rightView removeFromSuperview]; + rightView = [self viewWithOptions:options]; + + if (rightView) + { + [rightView setFrame:CGRectMake(250, 0, 200, 200)]; + [animationSandbox addSubview:rightView]; + } + + if (leftView) + [animationSandbox addSubview:leftView]; + + if (hasSubviews) + [self addSubviewsToView:rightView autoLayout:!customLayout customDrawSubviews:NO]; +} + +- (void)viewWithOptions:(CPInteger)options +{ + var view = nil, + hasSubviews = NO; + + switch (options) + { + case 0: view = [[ColorView alloc] initWithFrame:CGRectMakeZero()]; + break; + + case 2: view = [[ColorView alloc] initWithFrame:CGRectMakeZero()]; + hasSubviews = YES; + break; + + case 4: + case 6: view = [[CustomLayoutView alloc] initWithFrame:CGRectMakeZero()]; + [view setBackgroundColor:[CPColor randomColor]]; + hasSubviews = YES; + break; + + case 12: + case 14: view = [[CustomLayoutDrawView alloc] initWithFrame:CGRectMakeZero()]; + hasSubviews = YES; + break; + + case 8: view = [[DrawView alloc] initWithFrame:CGRectMakeZero()]; + break; + + case 10: view = [[DrawView alloc] initWithFrame:CGRectMakeZero()]; + hasSubviews = YES; + break; + + } + + var anims = [CPDictionary dictionaryWithObject:[self fadeOutAnimation] forKey:@"CPAnimationTriggerOrderOut"]; + [view setAnimations:anims]; + + return view; +} + +- (void)addSubviewsToView:(CPView)aView autoLayout:(BOOL)autoLayout customDrawSubviews:(BOOL)customDrawSubviews +{ + var subViewClass = customDrawSubviews ? [DrawView class] : [ColorView class]; + + for (var i = 0; i < 5; i++) + { + var view = [[subViewClass alloc] initWithFrame:CGRectMake(50, i * 40, 80, 26)]; + if (autoLayout) + [view setAutoresizingMask:CPViewWidthSizable|CPViewMinYMargin]; + + [aView addSubview:view]; + } +} + +@end + +@implementation ColorView : CPView +{ +} + +- (id)initWithFrame:(CGRect)aFrame +{ + self = [super initWithFrame:aFrame]; + + [self setBackgroundColor:[CPColor randomColor]]; + + return self; +} + +@end + +@implementation DrawView : CPView +{ + CPColor color @accessors; +} + +- (id)initWithFrame:(CGRect)aFrame +{ + self = [super initWithFrame:aFrame]; + + color = [CPColor randomColor]; + + return self; +} + +- (void)drawRect:(CGRect)aRect +{ + var context = [[CPGraphicsContext currentContext] graphicsPort], + bounds = [self bounds]; + + CGContextSetLineWidth(context, 2); + CGContextSetStrokeColor(context, [CPColor blackColor]); + CGContextSetFillColor(context, color); + + CGContextBeginPath(context); + CGContextFillRect(context, bounds); + + var height = CGRectGetHeight(bounds), + width = CGRectGetWidth(bounds); + + CGContextStrokeLineSegments(context, [CGPointMake(10,0), CGPointMake(10, height), + CGPointMake(0, 10), CGPointMake(CGRectGetWidth(aRect), 10), + CGPointMake(width - 10, 0), CGPointMake(width - 10, height), + CGPointMake(0, height - 10), CGPointMake(width, height - 10)], 8); +} + +@end + +@implementation CustomLayoutView : CPView +{ +} + +- (void)layoutSubviews +{ + var subviews = [self subviews], + count = [subviews count] - 1; + + var dx = (CGRectGetWidth([self frame]) - 100) / count, + dy = (CGRectGetHeight([self frame]) - 26) / count + + [subviews enumerateObjectsUsingBlock:function(view, idx, stop) + { + [view setFrameOrigin:CGPointMake(dx * idx, dy * idx)]; + }]; +} + +@end + +@implementation CustomLayoutDrawView : DrawView +{ +} + +- (void)layoutSubviews +{ + var subviews = [self subviews], + count = [subviews count] - 1; + + var dx = (CGRectGetWidth([self frame]) - 100) / count, + dy = (CGRectGetHeight([self frame]) - 26) / count + + [subviews enumerateObjectsUsingBlock:function(view, idx, stop) + { + [view setFrameOrigin:CGPointMake(dx * idx, dy * idx)]; + }]; +} + +@end \ No newline at end of file diff --git a/Tests/Manual/Issue-1357-fix/Info.plist b/Tests/Manual/CPAnimatablePropertyContainerTest/Info.plist similarity index 52% rename from Tests/Manual/Issue-1357-fix/Info.plist rename to Tests/Manual/CPAnimatablePropertyContainerTest/Info.plist index 55e6ab70e..fdb27037c 100644 --- a/Tests/Manual/Issue-1357-fix/Info.plist +++ b/Tests/Manual/CPAnimatablePropertyContainerTest/Info.plist @@ -2,11 +2,9 @@ - CPApplicationDelegateClass - AppController + Main cib file base name + MainMenu.cib CPBundleName - CPViewNoDisplayAfterHidingAndResizeBug - CPPrincipalClass - CPApplication + CPAnimatablePropertyContainerTest diff --git a/Tests/Manual/Issue-1357-fix/Jakefile b/Tests/Manual/CPAnimatablePropertyContainerTest/Jakefile similarity index 51% rename from Tests/Manual/Issue-1357-fix/Jakefile rename to Tests/Manual/CPAnimatablePropertyContainerTest/Jakefile index bb12b2e12..142cbe5ad 100644 --- a/Tests/Manual/Issue-1357-fix/Jakefile +++ b/Tests/Manual/CPAnimatablePropertyContainerTest/Jakefile @@ -1,9 +1,9 @@ /* * Jakefile - * CPViewNoDisplayAfterHidingAndResizeBug + * CPAnimatablePropertyContainerTest * - * Created by You on February 24, 2013. - * Copyright 2013, Your Company All rights reserved. + * Created by You on December 3, 2012. + * Copyright 2012, Your Company All rights reserved. */ var ENV = require("system").env, @@ -15,21 +15,22 @@ var ENV = require("system").env, configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug", OS = require("os"); -app ("CPViewNoDisplayAfterHidingAndResizeBug", function(task) +app ("CPAnimatablePropertyContainerTest", function(task) { - task.setBuildIntermediatesPath(FILE.join("Build", "CPViewNoDisplayAfterHidingAndResizeBug.build", configuration)); + task.setBuildIntermediatesPath(FILE.join("Build", "CPAnimatablePropertyContainerTest.build", configuration)); task.setBuildPath(FILE.join("Build", configuration)); - task.setProductName("CPViewNoDisplayAfterHidingAndResizeBug"); - task.setIdentifier("com.yourcompany.CPViewNoDisplayAfterHidingAndResizeBug"); + task.setProductName("CPAnimatablePropertyContainerTest"); + task.setIdentifier("com.yourcompany.CPAnimatablePropertyContainerTest"); task.setVersion("1.0"); task.setAuthor("Your Company"); task.setEmail("feedback @nospam@ yourcompany.com"); - task.setSummary("CPViewNoDisplayAfterHidingAndResizeBug"); + task.setSummary("CPAnimatablePropertyContainerTest"); 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"); @@ -37,7 +38,7 @@ app ("CPViewNoDisplayAfterHidingAndResizeBug", function(task) task.setCompilerFlags("-O"); }); -task ("default", ["CPViewNoDisplayAfterHidingAndResizeBug"], function() +task ("default", ["CPAnimatablePropertyContainerTest"], function() { printResults(configuration); }); @@ -58,36 +59,36 @@ task ("release", function() task ("run", ["debug"], function() { - OS.system(["open", FILE.join("Build", "Debug", "CPViewNoDisplayAfterHidingAndResizeBug", "index.html")]); + OS.system(["open", FILE.join("Build", "Debug", "CPAnimatablePropertyContainerTest", "index.html")]); }); task ("run-release", ["release"], function() { - OS.system(["open", FILE.join("Build", "Release", "CPViewNoDisplayAfterHidingAndResizeBug", "index.html")]); + OS.system(["open", FILE.join("Build", "Release", "CPAnimatablePropertyContainerTest", "index.html")]); }); task ("deploy", ["release"], function() { - FILE.mkdirs(FILE.join("Build", "Deployment", "CPViewNoDisplayAfterHidingAndResizeBug")); - OS.system(["press", "-f", FILE.join("Build", "Release", "CPViewNoDisplayAfterHidingAndResizeBug"), FILE.join("Build", "Deployment", "CPViewNoDisplayAfterHidingAndResizeBug")]); + FILE.mkdirs(FILE.join("Build", "Deployment", "CPAnimatablePropertyContainerTest")); + OS.system(["press", "-f", FILE.join("Build", "Release", "CPAnimatablePropertyContainerTest"), FILE.join("Build", "Deployment", "CPAnimatablePropertyContainerTest")]); printResults("Deployment") }); task ("desktop", ["release"], function() { - FILE.mkdirs(FILE.join("Build", "Desktop", "CPViewNoDisplayAfterHidingAndResizeBug")); - require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "CPViewNoDisplayAfterHidingAndResizeBug"), FILE.join("Build", "Desktop", "CPViewNoDisplayAfterHidingAndResizeBug", "CPViewNoDisplayAfterHidingAndResizeBug.app")); + FILE.mkdirs(FILE.join("Build", "Desktop", "CPAnimatablePropertyContainerTest")); + require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "CPAnimatablePropertyContainerTest"), FILE.join("Build", "Desktop", "CPAnimatablePropertyContainerTest", "CPAnimatablePropertyContainerTest.app")); printResults("Desktop") }); task ("run-desktop", ["desktop"], function() { - OS.system([FILE.join("Build", "Desktop", "CPViewNoDisplayAfterHidingAndResizeBug", "CPViewNoDisplayAfterHidingAndResizeBug.app", "Contents", "MacOS", "NativeHost"), "-i"]); + OS.system([FILE.join("Build", "Desktop", "CPAnimatablePropertyContainerTest", "CPAnimatablePropertyContainerTest.app", "Contents", "MacOS", "NativeHost"), "-i"]); }); function printResults(configuration) { print("----------------------------"); - print(configuration+" app built at path: "+FILE.join("Build", configuration, "CPViewNoDisplayAfterHidingAndResizeBug")); + print(configuration+" app built at path: "+FILE.join("Build", configuration, "CPAnimatablePropertyContainerTest")); print("----------------------------"); } diff --git a/Tests/Manual/CPAnimatablePropertyContainerTest/Resources/MainMenu.cib b/Tests/Manual/CPAnimatablePropertyContainerTest/Resources/MainMenu.cib new file mode 100644 index 000000000..67cd58584 --- /dev/null +++ b/Tests/Manual/CPAnimatablePropertyContainerTest/Resources/MainMenu.cib @@ -0,0 +1 @@ +280NPLIST;1.0;D;K;4;$topD;K;18;CPCibObjectDataKeyD;K;6;CP$UIDd;1;2E;E;K;8;$objectsA;S;5;$nullD;K;10;$classnameS;16;_CPCibObjectDataK;8;$classesA;S;16;_CPCibObjectDataS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;1E;K;28;_CPCibObjectDataNamesKeysKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataNamesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataClassesKeysKeyD;K;6;CP$UIDd;1;0E;K;32;_CPCibObjectDataClassesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataConnectionsKeyD;K;6;CP$UIDd;1;4E;K;28;_CPCibObjectDataFrameworkKeyD;K;6;CP$UIDd;1;0E;K;26;_CPCibObjectDataNextOidKeyD;K;6;CP$UIDd;1;5E;K;30;_CPCibObjectDataObjectsKeysKeyD;K;6;CP$UIDd;1;6E;K;32;_CPCibObjectDataObjectsValuesKeyD;K;6;CP$UIDd;1;7E;K;26;_CPCibObjectDataOidKeysKeyD;K;6;CP$UIDd;1;8E;K;28;_CPCibObjectDataOidValuesKeyD;K;6;CP$UIDd;1;9E;K;28;_CPCibObjectDataFileOwnerKeyD;K;6;CP$UIDd;2;11E;K;33;_CPCibObjectDataVisibleWindowsKeyD;K;6;CP$UIDd;2;13E;E;D;K;10;$classnameS;7;CPArrayK;8;$classesA;S;7;CPArrayS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;15E;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;17E;D;K;6;CP$UIDd;2;18E;D;K;6;CP$UIDd;2;19E;D;K;6;CP$UIDd;2;20E;D;K;6;CP$UIDd;2;21E;D;K;6;CP$UIDd;2;23E;D;K;6;CP$UIDd;2;24E;D;K;6;CP$UIDd;2;25E;D;K;6;CP$UIDd;2;26E;D;K;6;CP$UIDd;2;27E;D;K;6;CP$UIDd;2;28E;D;K;6;CP$UIDd;2;29E;D;K;6;CP$UIDd;2;30E;D;K;6;CP$UIDd;2;31E;D;K;6;CP$UIDd;2;32E;D;K;6;CP$UIDd;2;33E;D;K;6;CP$UIDd;2;34E;D;K;6;CP$UIDd;2;35E;D;K;6;CP$UIDd;2;37E;D;K;6;CP$UIDd;2;38E;D;K;6;CP$UIDd;2;39E;D;K;6;CP$UIDd;2;40E;D;K;6;CP$UIDd;2;41E;D;K;6;CP$UIDd;2;42E;D;K;6;CP$UIDd;2;43E;D;K;6;CP$UIDd;2;44E;D;K;6;CP$UIDd;2;45E;D;K;6;CP$UIDd;2;46E;D;K;6;CP$UIDd;2;47E;D;K;6;CP$UIDd;2;48E;D;K;6;CP$UIDd;2;49E;D;K;6;CP$UIDd;2;50E;D;K;6;CP$UIDd;2;51E;D;K;6;CP$UIDd;2;52E;D;K;6;CP$UIDd;2;53E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;54E;D;K;6;CP$UIDd;2;56E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;60E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;61E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;63E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;65E;D;K;6;CP$UIDd;2;66E;D;K;6;CP$UIDd;2;67E;D;K;6;CP$UIDd;2;68E;D;K;6;CP$UIDd;2;69E;D;K;6;CP$UIDd;2;70E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;71E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;73E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;75E;D;K;6;CP$UIDd;2;77E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;78E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;79E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;80E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;81E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;82E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;83E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;84E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;85E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;86E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;87E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;88E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;89E;D;K;6;CP$UIDd;2;90E;D;K;6;CP$UIDd;2;91E;D;K;6;CP$UIDd;2;92E;D;K;6;CP$UIDd;2;93E;D;K;6;CP$UIDd;2;94E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;95E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;2;97E;D;K;6;CP$UIDd;2;99E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;100E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;101E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;102E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;103E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;104E;D;K;6;CP$UIDd;3;105E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;106E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;107E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;108E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;109E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;110E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;112E;D;K;6;CP$UIDd;3;114E;D;K;6;CP$UIDd;3;116E;D;K;6;CP$UIDd;3;117E;D;K;6;CP$UIDd;3;119E;D;K;6;CP$UIDd;3;121E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;122E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;123E;D;K;6;CP$UIDd;3;124E;D;K;6;CP$UIDd;3;125E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;126E;D;K;6;CP$UIDd;1;0E;D;K;6;CP$UIDd;3;127E;D;K;6;CP$UIDd;3;128E;D;K;6;CP$UIDd;3;129E;D;K;6;CP$UIDd;3;130E;D;K;6;CP$UIDd;3;132E;D;K;6;CP$UIDd;3;134E;D;K;6;CP$UIDd;3;136E;D;K;6;CP$UIDd;3;137E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;56E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;60E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;61E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;63E;D;K;6;CP$UIDd;3;136E;D;K;6;CP$UIDd;3;136E;D;K;6;CP$UIDd;3;136E;D;K;6;CP$UIDd;3;136E;D;K;6;CP$UIDd;3;136E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;70E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;71E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;73E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;77E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;78E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;79E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;80E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;81E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;82E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;83E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;84E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;85E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;86E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;87E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;88E;D;K;6;CP$UIDd;3;137E;D;K;6;CP$UIDd;3;137E;D;K;6;CP$UIDd;3;137E;D;K;6;CP$UIDd;3;137E;D;K;6;CP$UIDd;3;137E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;94E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;95E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;2;97E;D;K;6;CP$UIDd;2;99E;D;K;6;CP$UIDd;2;97E;D;K;6;CP$UIDd;3;100E;D;K;6;CP$UIDd;2;97E;D;K;6;CP$UIDd;3;101E;D;K;6;CP$UIDd;2;97E;D;K;6;CP$UIDd;3;102E;D;K;6;CP$UIDd;2;97E;D;K;6;CP$UIDd;3;103E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;3;104E;D;K;6;CP$UIDd;3;105E;D;K;6;CP$UIDd;3;104E;D;K;6;CP$UIDd;3;106E;D;K;6;CP$UIDd;3;104E;D;K;6;CP$UIDd;3;107E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;3;108E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;3;109E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;3;110E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;3;112E;D;K;6;CP$UIDd;3;112E;D;K;6;CP$UIDd;3;112E;D;K;6;CP$UIDd;3;112E;D;K;6;CP$UIDd;3;119E;D;K;6;CP$UIDd;3;121E;D;K;6;CP$UIDd;3;119E;D;K;6;CP$UIDd;3;122E;D;K;6;CP$UIDd;2;58E;D;K;6;CP$UIDd;3;123E;D;K;6;CP$UIDd;3;124E;D;K;6;CP$UIDd;3;125E;D;K;6;CP$UIDd;3;124E;D;K;6;CP$UIDd;3;126E;D;K;6;CP$UIDd;3;123E;D;K;6;CP$UIDd;3;123E;D;K;6;CP$UIDd;3;123E;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;63E;D;K;6;CP$UIDd;2;88E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;18;_CPCibCustomObjectK;8;$classesA;S;18;_CPCibCustomObjectS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;3;138E;E;D;K;10;$classnameS;5;CPSetK;8;$classesA;S;5;CPSetS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;12E;K;15;CPSetObjectsKeyD;K;6;CP$UIDd;3;139E;E;D;K;10;$classnameS;20;CPCibOutletConnectorK;8;$classesA;S;20;CPCibOutletConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;11E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;140E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;130E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;75E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;141E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;130E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;110E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;142E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;130E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;97E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;143E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;130E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;104E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;144E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;130E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;56E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;145E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;134E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;146E;E;D;K;10;$classnameS;21;CPCibControlConnectorK;8;$classesA;S;21;CPCibControlConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;100E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;147E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;101E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;147E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;99E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;147E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;106E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;148E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;105E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;148E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;107E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;148E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;109E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;149E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;108E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;150E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;110E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;151E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;77E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;152E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;78E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;153E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;102E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;147E;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;103E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;147E;E;D;K;10;$classnameS;21;CPCibBindingConnectorK;8;$classesA;S;21;CPCibBindingConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;88E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;154E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;155E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;156E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;158E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;86E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;159E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;161E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;162E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;86E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;163E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;164E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;156E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;165E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;63E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;166E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;155E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;167E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;168E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;84E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;169E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;170E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;171E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;84E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;172E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;164E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;167E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;173E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;79E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;174E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;175E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;176E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;60E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;177E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;178E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;179E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;82E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;180E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;181E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;182E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;81E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;180E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;181E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;183E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;70E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;184E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;185E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;186E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;73E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;134E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;184E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;185E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;187E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;132E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;130E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;188E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;189E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;190E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;191E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;122E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;132E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;192E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;193E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;194E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;121E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;132E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;195E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;196E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;197E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;126E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;132E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;195E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;196E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;198E;E;D;K;6;$classD;K;6;CP$UIDd;2;36E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;3;125E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;3;132E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;3;199E;K;31;CPCibBindingConnectorBindingKeyD;K;6;CP$UIDd;3;160E;K;31;CPCibBindingConnectorKeyPathKeyD;K;6;CP$UIDd;3;200E;K;31;CPCibBindingConnectorOptionsKeyD;K;6;CP$UIDd;3;201E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;3;138E;E;D;K;10;$classnameS;20;_CPCibWindowTemplateK;8;$classesA;S;20;_CPCibWindowTemplateS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;55E;K;30;_CPCibWindowTemplateMaxSizeKeyD;K;6;CP$UIDd;3;202E;K;32;_CPCibWindowTemplateViewClassKeyD;K;6;CP$UIDd;1;0E;K;34;_CPCibWindowTemplateWindowClassKeyD;K;6;CP$UIDd;3;203E;K;30;_CPCibWindowTemplateWTFlagsKeyD;K;6;CP$UIDd;3;204E;K;33;_CPCibWindowTemplateWindowRectKeyD;K;6;CP$UIDd;3;205E;K;33;_CPCibWindowTemplateScreenRectKeyD;K;6;CP$UIDd;3;206E;K;30;_CPCibWindowTempatStyleMaskKeyD;K;6;CP$UIDd;3;207E;K;34;_CPCibWindowTemplateWindowTitleKeyD;K;6;CP$UIDd;3;208E;K;33;_CPCibWindowTemplateWindowViewKeyD;K;6;CP$UIDd;2;58E;E;D;K;10;$classnameS;6;CPViewK;8;$classesA;S;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;57E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;210E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;210E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;211E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;D;K;10;$classnameS;11;CPTextFieldK;8;$classesA;S;11;CPTextFieldS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;215E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;216E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;219E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;225E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;224E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;227E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;228E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;229E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;231E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;232E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;10;$classnameS;13;CPPopUpButtonK;8;$classesA;S;13;CPPopUpButtonS;8;CPButtonS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;62E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;3;136E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;233E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;234E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;235E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;236E;K;11;$aalignmentD;K;6;CP$UIDd;3;209E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;220E;K;6;$afontD;K;6;CP$UIDd;3;237E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;1;0E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;238E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;209E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;10;$classnameS;10;CPMenuItemK;8;$classesA;S;10;CPMenuItemS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;239E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;63E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;16;CPMenuItemTagKeyD;K;6;CP$UIDd;3;241E;K;18;CPMenuItemStateKeyD;K;6;CP$UIDd;3;241E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;136E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;243E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;63E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;16;CPMenuItemTagKeyD;K;6;CP$UIDd;3;221E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;136E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;244E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;63E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;16;CPMenuItemTagKeyD;K;6;CP$UIDd;3;245E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;136E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;246E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;63E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;16;CPMenuItemTagKeyD;K;6;CP$UIDd;3;220E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;136E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;247E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;63E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;136E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;248E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;249E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;250E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;251E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;225E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;227E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;252E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;253E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;254E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;232E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;10;$classnameS;9;CPStepperK;8;$classesA;S;9;CPStepperS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;72E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;255E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;257E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;209E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;258E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;18;CPStepperIncrementD;K;6;CP$UIDd;3;241E;K;17;CPStepperMaxValueD;K;6;CP$UIDd;3;259E;K;19;CPStepperAutorepeatD;K;6;CP$UIDd;3;224E;E;D;K;10;$classnameS;16;_CPCibCustomViewK;8;$classesA;S;16;_CPCibCustomViewS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;74E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;260E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;261E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;28;_CPCibCustomViewClassNameKeyD;K;6;CP$UIDd;3;263E;E;D;K;10;$classnameS;8;CPButtonK;8;$classesA;S;8;CPButtonS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;76E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;264E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;265E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;142E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;236E;K;11;$aalignmentD;K;6;CP$UIDd;3;221E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;266E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;267E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;209E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;76E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;268E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;265E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;142E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;236E;K;11;$aalignmentD;K;6;CP$UIDd;3;221E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;269E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;267E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;209E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;270E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;216E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;219E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;225E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;224E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;227E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;271E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;229E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;231E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;232E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;72E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;272E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;257E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;209E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;258E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;18;CPStepperIncrementD;K;6;CP$UIDd;3;241E;K;17;CPStepperMaxValueD;K;6;CP$UIDd;3;259E;K;19;CPStepperAutorepeatD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;273E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;249E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;250E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;274E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;225E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;227E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;275E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;276E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;254E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;232E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;277E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;278E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;219E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;225E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;224E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;227E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;3;279E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;280E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;281E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;282E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;283E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;232E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;284E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;278E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;219E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;225E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;224E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;224E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;227E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;3;279E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;285E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;281E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;286E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;283E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;232E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;62E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;3;137E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;287E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;234E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;235E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;236E;K;11;$aalignmentD;K;6;CP$UIDd;3;209E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;220E;K;6;$afontD;K;6;CP$UIDd;3;288E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;1;0E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;238E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;209E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;247E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;88E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;137E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;246E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;88E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;16;CPMenuItemTagKeyD;K;6;CP$UIDd;3;220E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;137E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;244E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;88E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;16;CPMenuItemTagKeyD;K;6;CP$UIDd;3;245E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;137E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;243E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;88E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;16;CPMenuItemTagKeyD;K;6;CP$UIDd;3;221E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;137E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;64E;K;18;CPMenuItemTitleKeyD;K;6;CP$UIDd;3;239E;K;19;CPMenuItemTargetKeyD;K;6;CP$UIDd;2;88E;K;19;CPMenuItemActionKeyD;K;6;CP$UIDd;3;240E;K;16;CPMenuItemTagKeyD;K;6;CP$UIDd;3;241E;K;18;CPMenuItemStateKeyD;K;6;CP$UIDd;3;241E;K;17;CPMenuItemMenuKeyD;K;6;CP$UIDd;3;137E;K;38;CPMenuItemKeyEquivalentModifierMaskKeyD;K;6;CP$UIDd;3;242E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;289E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;290E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;291E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;292E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;232E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;293E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;290E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;221E;K;6;$afontD;K;6;CP$UIDd;3;294E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;35;CPControlSendsActionOnEndEditingKeyD;K;6;CP$UIDd;3;224E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;295E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;232E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;221E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;220E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;224E;E;D;K;10;$classnameS;5;CPBoxK;8;$classesA;S;5;CPBoxS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;96E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;296E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;297E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;298E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;25;CPViewAutoresizesSubviewsD;K;6;CP$UIDd;3;209E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;299E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;12;CPBoxTypeKeyD;K;6;CP$UIDd;3;209E;K;18;CPBoxBorderTypeKeyD;K;6;CP$UIDd;3;241E;K;10;CPBoxTitleD;K;6;CP$UIDd;3;300E;K;18;CPBoxTitlePositionD;K;6;CP$UIDd;3;221E;K;14;CPBoxTitleViewD;K;6;CP$UIDd;1;0E;E;D;K;10;$classnameS;10;CPCheckBoxK;8;$classesA;S;10;CPCheckBoxS;8;CPButtonS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;301E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;302E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;303E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;304E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;301E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;306E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;301E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;307E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;308E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;304E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;301E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;309E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;301E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;310E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;311E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;304E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;301E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;312E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;301E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;313E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;314E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;304E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;301E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;312E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;301E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;315E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;316E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;304E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;301E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;317E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;96E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;318E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;319E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;320E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;25;CPViewAutoresizesSubviewsD;K;6;CP$UIDd;3;209E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;299E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;12;CPBoxTypeKeyD;K;6;CP$UIDd;3;209E;K;18;CPBoxBorderTypeKeyD;K;6;CP$UIDd;3;241E;K;10;CPBoxTitleD;K;6;CP$UIDd;3;321E;K;18;CPBoxTitlePositionD;K;6;CP$UIDd;3;221E;K;14;CPBoxTitleViewD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;322E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;310E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;311E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;304E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;322E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;312E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;322E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;307E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;323E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;304E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;322E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;309E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;322E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;302E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;303E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;304E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;322E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;230E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;306E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;76E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;324E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;325E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;142E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;236E;K;11;$aalignmentD;K;6;CP$UIDd;3;221E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;326E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;267E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;209E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;76E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;327E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;325E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;142E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;236E;K;11;$aalignmentD;K;6;CP$UIDd;3;221E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;326E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;267E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;209E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;76E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;328E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;329E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;262E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;142E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;236E;K;11;$aalignmentD;K;6;CP$UIDd;3;221E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;330E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;267E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;209E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;221E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;1;0E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;1;0E;E;D;K;10;$classnameS;12;CPScrollViewK;8;$classesA;S;12;CPScrollViewS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;111E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;331E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;332E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;333E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;334E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;23;CPScrollViewContentViewD;K;6;CP$UIDd;3;336E;K;29;CPScrollViewHeaderClipViewKeyD;K;6;CP$UIDd;3;337E;K;21;CPScrollViewVScrollerD;K;6;CP$UIDd;3;114E;K;21;CPScrollViewHScrollerD;K;6;CP$UIDd;3;117E;K;23;CPScrollViewVLineScrollD;K;6;CP$UIDd;3;338E;K;23;CPScrollViewVPageScrollD;K;6;CP$UIDd;3;338E;K;23;CPScrollViewHLineScrollD;K;6;CP$UIDd;3;338E;K;23;CPScrollViewHPageScrollD;K;6;CP$UIDd;3;338E;K;24;CPScrollViewHasVScrollerD;K;6;CP$UIDd;3;224E;K;24;CPScrollViewHasHScrollerD;K;6;CP$UIDd;3;224E;K;29;CPScrollViewAutohidesScrollerD;K;6;CP$UIDd;3;224E;K;25;CPScrollViewCornerViewKeyD;K;6;CP$UIDd;1;0E;K;31;CPScrollViewBottomCornerViewKeyD;K;6;CP$UIDd;3;339E;K;25;CPScrollViewBorderTypeKeyD;K;6;CP$UIDd;3;221E;K;28;CPScrollViewScrollerStyleKeyD;K;6;CP$UIDd;1;0E;K;32;CPScrollViewScrollerKnobStyleKeyD;K;6;CP$UIDd;3;209E;E;D;K;10;$classnameS;10;CPScrollerK;8;$classesA;S;10;CPScrollerS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;113E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;112E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;340E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;341E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;112E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;342E;K;17;CPViewIsHiddenKeyD;K;6;CP$UIDd;3;343E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;344E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;345E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;18;CPControlTargetKeyD;K;6;CP$UIDd;3;112E;K;18;CPControlActionKeyD;K;6;CP$UIDd;3;346E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;1;0E;K;23;CPScrollerIsVerticalKeyD;K;6;CP$UIDd;3;224E;K;24;CPScrollerKnobProportionD;K;6;CP$UIDd;3;347E;K;18;CPScrollerStyleKeyD;K;6;CP$UIDd;1;0E;E;D;K;10;$classnameS;17;CPTableHeaderViewK;8;$classesA;S;17;CPTableHeaderViewS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;115E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;337E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;348E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;348E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;337E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;349E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;29;CPTableHeaderViewTableViewKeyD;K;6;CP$UIDd;3;119E;E;D;K;6;$classD;K;6;CP$UIDd;3;113E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;112E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;350E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;351E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;112E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;342E;K;17;CPViewIsHiddenKeyD;K;6;CP$UIDd;3;343E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;344E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;345E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;18;CPControlTargetKeyD;K;6;CP$UIDd;3;112E;K;18;CPControlActionKeyD;K;6;CP$UIDd;3;352E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;1;0E;K;23;CPScrollerIsVerticalKeyD;K;6;CP$UIDd;3;214E;K;24;CPScrollerKnobProportionD;K;6;CP$UIDd;3;353E;K;18;CPScrollerStyleKeyD;K;6;CP$UIDd;1;0E;E;D;K;10;$classnameS;11;CPTableViewK;8;$classesA;S;11;CPTableViewS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;118E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;336E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;354E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;354E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;336E;K;21;CPViewBackgroundColorD;K;6;CP$UIDd;3;355E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;356E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;12;$agrid-colorD;K;6;CP$UIDd;3;357E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;1;0E;K;24;CPTableViewDataSourceKeyD;K;6;CP$UIDd;1;0E;K;22;CPTableViewDelegateKeyD;K;6;CP$UIDd;1;0E;K;23;CPTableViewRowHeightKeyD;K;6;CP$UIDd;3;358E;K;30;CPTableViewIntercellSpacingKeyD;K;6;CP$UIDd;3;359E;K;37;CPTableViewSelectionHighlightStyleKeyD;K;6;CP$UIDd;3;360E;K;37;CPTableViewColumnAutoresizingStyleKeyD;K;6;CP$UIDd;3;220E;K;31;CPTableViewMultipleSelectionKeyD;K;6;CP$UIDd;3;214E;K;28;CPTableViewEmptySelectionKeyD;K;6;CP$UIDd;3;224E;K;30;CPTableViewColumnReorderingKeyD;K;6;CP$UIDd;3;214E;K;28;CPTableViewColumnResizingKeyD;K;6;CP$UIDd;3;214E;K;29;CPTableViewColumnSelectionKeyD;K;6;CP$UIDd;3;214E;K;26;CPTableViewTableColumnsKeyD;K;6;CP$UIDd;3;361E;K;23;CPTableViewGridColorKeyD;K;6;CP$UIDd;3;357E;K;27;CPTableViewGridStyleMaskKeyD;K;6;CP$UIDd;3;209E;K;39;CPTableViewUsesAlternatingBackgroundKeyD;K;6;CP$UIDd;3;224E;K;34;CPTableViewAlternatingRowColorsKeyD;K;6;CP$UIDd;1;0E;K;24;CPTableViewCornerViewKeyD;K;6;CP$UIDd;3;363E;K;24;CPTableViewHeaderViewKeyD;K;6;CP$UIDd;3;116E;K;26;CPTableViewAutosaveNameKeyD;K;6;CP$UIDd;1;0E;E;D;K;10;$classnameS;13;CPTableColumnK;8;$classesA;S;13;CPTableColumnS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;120E;K;26;CPTableColumnIdentifierKeyD;K;6;CP$UIDd;1;0E;K;21;CPTableColumnWidthKeyD;K;6;CP$UIDd;3;364E;K;24;CPTableColumnMinWidthKeyD;K;6;CP$UIDd;3;365E;K;24;CPTableColumnMaxWidthKeyD;K;6;CP$UIDd;3;302E;K;26;CPTableColumnHeaderViewKeyD;K;6;CP$UIDd;3;367E;K;24;CPTableColumnDataViewKeyD;K;6;CP$UIDd;3;368E;K;28;CPTableColumnResizingMaskKeyD;K;6;CP$UIDd;3;245E;K;24;CPTableColumnIsHiddenKeyD;K;6;CP$UIDd;3;214E;K;26;CPTableColumnIsEditableKeyD;K;6;CP$UIDd;3;214E;K;28;CPSortDescriptorPrototypeKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;3;120E;K;26;CPTableColumnIdentifierKeyD;K;6;CP$UIDd;1;0E;K;21;CPTableColumnWidthKeyD;K;6;CP$UIDd;3;365E;K;24;CPTableColumnMinWidthKeyD;K;6;CP$UIDd;3;365E;K;24;CPTableColumnMaxWidthKeyD;K;6;CP$UIDd;3;302E;K;26;CPTableColumnHeaderViewKeyD;K;6;CP$UIDd;3;369E;K;24;CPTableColumnDataViewKeyD;K;6;CP$UIDd;3;370E;K;28;CPTableColumnResizingMaskKeyD;K;6;CP$UIDd;3;245E;K;24;CPTableColumnIsHiddenKeyD;K;6;CP$UIDd;3;214E;K;26;CPTableColumnIsEditableKeyD;K;6;CP$UIDd;3;224E;K;28;CPSortDescriptorPrototypeKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;3;111E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;58E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;371E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;372E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;373E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;58E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;217E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;334E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;23;CPScrollViewContentViewD;K;6;CP$UIDd;3;374E;K;29;CPScrollViewHeaderClipViewKeyD;K;6;CP$UIDd;3;375E;K;21;CPScrollViewVScrollerD;K;6;CP$UIDd;3;129E;K;21;CPScrollViewHScrollerD;K;6;CP$UIDd;3;127E;K;23;CPScrollViewVLineScrollD;K;6;CP$UIDd;3;338E;K;23;CPScrollViewVPageScrollD;K;6;CP$UIDd;3;338E;K;23;CPScrollViewHLineScrollD;K;6;CP$UIDd;3;338E;K;23;CPScrollViewHPageScrollD;K;6;CP$UIDd;3;338E;K;24;CPScrollViewHasVScrollerD;K;6;CP$UIDd;3;224E;K;24;CPScrollViewHasHScrollerD;K;6;CP$UIDd;3;224E;K;29;CPScrollViewAutohidesScrollerD;K;6;CP$UIDd;3;224E;K;25;CPScrollViewCornerViewKeyD;K;6;CP$UIDd;1;0E;K;31;CPScrollViewBottomCornerViewKeyD;K;6;CP$UIDd;3;376E;K;25;CPScrollViewBorderTypeKeyD;K;6;CP$UIDd;3;221E;K;28;CPScrollViewScrollerStyleKeyD;K;6;CP$UIDd;1;0E;K;32;CPScrollViewScrollerKnobStyleKeyD;K;6;CP$UIDd;3;209E;E;D;K;6;$classD;K;6;CP$UIDd;3;118E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;374E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;377E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;377E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;374E;K;21;CPViewBackgroundColorD;K;6;CP$UIDd;3;355E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;356E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;12;$agrid-colorD;K;6;CP$UIDd;3;357E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;1;0E;K;24;CPTableViewDataSourceKeyD;K;6;CP$UIDd;1;0E;K;22;CPTableViewDelegateKeyD;K;6;CP$UIDd;1;0E;K;23;CPTableViewRowHeightKeyD;K;6;CP$UIDd;3;358E;K;30;CPTableViewIntercellSpacingKeyD;K;6;CP$UIDd;3;359E;K;37;CPTableViewSelectionHighlightStyleKeyD;K;6;CP$UIDd;3;360E;K;37;CPTableViewColumnAutoresizingStyleKeyD;K;6;CP$UIDd;3;220E;K;31;CPTableViewMultipleSelectionKeyD;K;6;CP$UIDd;3;214E;K;28;CPTableViewEmptySelectionKeyD;K;6;CP$UIDd;3;224E;K;30;CPTableViewColumnReorderingKeyD;K;6;CP$UIDd;3;214E;K;28;CPTableViewColumnResizingKeyD;K;6;CP$UIDd;3;214E;K;29;CPTableViewColumnSelectionKeyD;K;6;CP$UIDd;3;214E;K;26;CPTableViewTableColumnsKeyD;K;6;CP$UIDd;3;378E;K;23;CPTableViewGridColorKeyD;K;6;CP$UIDd;3;357E;K;27;CPTableViewGridStyleMaskKeyD;K;6;CP$UIDd;3;209E;K;39;CPTableViewUsesAlternatingBackgroundKeyD;K;6;CP$UIDd;3;224E;K;34;CPTableViewAlternatingRowColorsKeyD;K;6;CP$UIDd;1;0E;K;24;CPTableViewCornerViewKeyD;K;6;CP$UIDd;3;379E;K;24;CPTableViewHeaderViewKeyD;K;6;CP$UIDd;3;128E;K;26;CPTableViewAutosaveNameKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;3;120E;K;26;CPTableColumnIdentifierKeyD;K;6;CP$UIDd;1;0E;K;21;CPTableColumnWidthKeyD;K;6;CP$UIDd;3;365E;K;24;CPTableColumnMinWidthKeyD;K;6;CP$UIDd;3;365E;K;24;CPTableColumnMaxWidthKeyD;K;6;CP$UIDd;3;302E;K;26;CPTableColumnHeaderViewKeyD;K;6;CP$UIDd;3;380E;K;24;CPTableColumnDataViewKeyD;K;6;CP$UIDd;3;381E;K;28;CPTableColumnResizingMaskKeyD;K;6;CP$UIDd;3;245E;K;24;CPTableColumnIsHiddenKeyD;K;6;CP$UIDd;3;214E;K;26;CPTableColumnIsEditableKeyD;K;6;CP$UIDd;3;224E;K;28;CPSortDescriptorPrototypeKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;3;120E;K;26;CPTableColumnIdentifierKeyD;K;6;CP$UIDd;1;0E;K;21;CPTableColumnWidthKeyD;K;6;CP$UIDd;3;382E;K;24;CPTableColumnMinWidthKeyD;K;6;CP$UIDd;3;365E;K;24;CPTableColumnMaxWidthKeyD;K;6;CP$UIDd;3;302E;K;26;CPTableColumnHeaderViewKeyD;K;6;CP$UIDd;3;383E;K;24;CPTableColumnDataViewKeyD;K;6;CP$UIDd;3;384E;K;28;CPTableColumnResizingMaskKeyD;K;6;CP$UIDd;3;245E;K;24;CPTableColumnIsHiddenKeyD;K;6;CP$UIDd;3;214E;K;26;CPTableColumnIsEditableKeyD;K;6;CP$UIDd;3;214E;K;28;CPSortDescriptorPrototypeKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;3;113E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;123E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;385E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;386E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;123E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;342E;K;17;CPViewIsHiddenKeyD;K;6;CP$UIDd;3;343E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;344E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;345E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;18;CPControlTargetKeyD;K;6;CP$UIDd;3;123E;K;18;CPControlActionKeyD;K;6;CP$UIDd;3;352E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;1;0E;K;23;CPScrollerIsVerticalKeyD;K;6;CP$UIDd;3;214E;K;24;CPScrollerKnobProportionD;K;6;CP$UIDd;3;353E;K;18;CPScrollerStyleKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;3;115E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;375E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;348E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;348E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;375E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;349E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;29;CPTableHeaderViewTableViewKeyD;K;6;CP$UIDd;3;124E;E;D;K;6;$classD;K;6;CP$UIDd;3;113E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;123E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;387E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;341E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;123E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;342E;K;17;CPViewIsHiddenKeyD;K;6;CP$UIDd;3;343E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;344E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;345E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;18;CPControlTargetKeyD;K;6;CP$UIDd;3;123E;K;18;CPControlActionKeyD;K;6;CP$UIDd;3;346E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;1;0E;K;23;CPScrollerIsVerticalKeyD;K;6;CP$UIDd;3;224E;K;24;CPScrollerKnobProportionD;K;6;CP$UIDd;3;388E;K;18;CPScrollerStyleKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;3;389E;E;D;K;10;$classnameS;17;CPArrayControllerK;8;$classesA;S;17;CPArrayControllerS;18;CPObjectControllerS;12;CPControllerS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;131E;K;28;CPObjectControllerContentKeyD;K;6;CP$UIDd;1;0E;K;36;CPObjectControllerObjectClassNameKeyD;K;6;CP$UIDd;3;390E;K;31;CPObjectControllerIsEditableKeyD;K;6;CP$UIDd;3;224E;K;49;CPObjectControllerAutomaticallyPreparesContentKeyD;K;6;CP$UIDd;3;214E;K;37;CPArrayControllerAvoidsEmptySelectionD;K;6;CP$UIDd;3;214E;K;49;CPArrayControllerClearsFilterPredicateOnInsertionD;K;6;CP$UIDd;3;214E;K;41;CPArrayControllerFilterRestrictsInsertionD;K;6;CP$UIDd;3;224E;K;35;CPArrayControllerPreservesSelectionD;K;6;CP$UIDd;3;214E;K;39;CPArrayControllerSelectsInsertedObjectsD;K;6;CP$UIDd;3;214E;K;47;CPArrayControllerAlwaysUsesMultipleValuesMarkerD;K;6;CP$UIDd;3;214E;K;47;CPArrayControllerAutomaticallyRearrangesObjectsD;K;6;CP$UIDd;3;214E;E;D;K;10;$classnameS;18;CPObjectControllerK;8;$classesA;S;18;CPObjectControllerS;12;CPControllerS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;133E;K;28;CPObjectControllerContentKeyD;K;6;CP$UIDd;1;0E;K;36;CPObjectControllerObjectClassNameKeyD;K;6;CP$UIDd;3;390E;K;31;CPObjectControllerIsEditableKeyD;K;6;CP$UIDd;3;224E;K;49;CPObjectControllerAutomaticallyPreparesContentKeyD;K;6;CP$UIDd;3;214E;E;D;K;10;$classnameS;6;CPMenuK;8;$classesA;S;6;CPMenuS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;135E;K;14;CPMenuTitleKeyD;K;6;CP$UIDd;3;391E;K;14;CPMenuItemsKeyD;K;6;CP$UIDd;3;392E;E;D;K;6;$classD;K;6;CP$UIDd;3;135E;K;14;CPMenuTitleKeyD;K;6;CP$UIDd;3;391E;K;14;CPMenuItemsKeyD;K;6;CP$UIDd;3;393E;E;S;13;CPApplicationD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;56E;E;E;S;8;delegateS;16;animationSandboxS;6;buttonS;9;group1BoxS;9;group2BoxS;9;theWindowS;7;contentS;12;setupGroup1:S;12;setupGroup2:S;20;runAnimationsGroup1:S;20;runAnimationsGroup2:S;14;runBothGroups:S;20;removeFromSuperview:S;7;revert:S;46;selectedTag: selection.selectedTimingFunction2S;11;selectedTagS;33;selection.selectedTimingFunction2D;K;10;$classnameS;12;CPDictionaryK;8;$classesA;S;12;CPDictionaryS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;32;value: selection.timingFunction2S;5;valueS;25;selection.timingFunction2D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;41;hidden: selection.selectedTimingFunction2S;6;hiddenD;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;46;selectedTag: selection.selectedTimingFunction1S;33;selection.selectedTimingFunction1D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;32;value: selection.timingFunction1S;25;selection.timingFunction1D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;41;hidden: selection.selectedTimingFunction1D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;25;value: selection.message2S;18;selection.message2D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;25;value: selection.message1S;18;selection.message1D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;26;value: selection.duration2S;19;selection.duration2D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;26;value: selection.duration1S;19;selection.duration1D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;24;contentArray: animationsS;12;contentArrayS;10;animationsD;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;31;value: arrangedObjects.enabled2S;24;arrangedObjects.enabled2D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;27;value: arrangedObjects.nameS;20;arrangedObjects.nameD;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;31;value: arrangedObjects.enabled1S;24;arrangedObjects.enabled1D;K;6;$classD;K;6;CP$UIDd;3;157E;K;10;CP.objectsD;E;E;S;32;{10000000000000, 10000000000000}S;8;CPWindowd;10;1946157056S;26;{{335, -126}, {1319, 763}}S;22;{{0, 0}, {1680, 1027}}d;1;7S;6;Windowd;1;0S;21;{{0, 0}, {1319, 763}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;123E;D;K;6;CP$UIDd;3;112E;D;K;6;CP$UIDd;2;73E;D;K;6;CP$UIDd;2;70E;D;K;6;CP$UIDd;2;81E;D;K;6;CP$UIDd;2;82E;D;K;6;CP$UIDd;2;60E;D;K;6;CP$UIDd;2;61E;D;K;6;CP$UIDd;2;79E;D;K;6;CP$UIDd;2;80E;D;K;6;CP$UIDd;2;71E;D;K;6;CP$UIDd;2;83E;D;K;6;CP$UIDd;2;84E;D;K;6;CP$UIDd;2;85E;D;K;6;CP$UIDd;3;109E;D;K;6;CP$UIDd;3;108E;D;K;6;CP$UIDd;3;110E;D;K;6;CP$UIDd;2;75E;D;K;6;CP$UIDd;2;97E;D;K;6;CP$UIDd;3;104E;D;K;6;CP$UIDd;2;77E;D;K;6;CP$UIDd;2;63E;D;K;6;CP$UIDd;2;78E;D;K;6;CP$UIDd;2;86E;D;K;6;CP$UIDd;2;87E;D;K;6;CP$UIDd;2;88E;D;K;6;CP$UIDd;2;94E;D;K;6;CP$UIDd;2;95E;E;E;S;6;normalS;6;{1, 1}F;S;24;{{1199, 121}, {104, 22}}S;19;{{0, 0}, {104, 22}}d;2;33S;9;textfieldS;47;bezeled+controlSizeRegular+editable+placeholderd;1;4d;1;2D;K;10;$classnameS;6;CPFontK;8;$classesA;S;6;CPFontS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;395E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;T;S;0;D;K;10;$classnameS;7;CPColorK;8;$classesA;S;7;CPColorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;226E;K;20;CPColorComponentsKeyD;K;6;CP$UIDd;3;396E;E;S;24;{{1058, 129}, {137, 17}}S;19;{{0, 0}, {137, 17}}S;18;controlSizeRegularS;19;Completion message:D;K;6;$classD;K;6;CP$UIDd;3;226E;K;20;CPColorComponentsKeyD;K;6;CP$UIDd;3;397E;E;S;22;{{1145, 94}, {99, 25}}S;18;{{0, 0}, {99, 25}}S;12;popup-buttonS;27;bordered+controlSizeRegularD;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;338E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;d;2;12S;6;LinearS;17;_popUpItemAction:d;1;1d;7;1048576S;7;Ease-inS;8;Ease Outd;1;3S;11;Ease In-OutS;6;CustomS;22;{{1226, 55}, {39, 22}}S;18;{{0, 0}, {39, 22}}S;38;bezeled+controlSizeRegular+placeholderD;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;398E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;S;22;{{1163, 63}, {60, 17}}S;18;{{0, 0}, {60, 17}}S;9;Duration:S;20;{{1269, 58}, {0, 0}}S;16;{{0, 0}, {0, 0}}S;7;stepperd;5;65540d;3;100S;24;{{20, 243}, {1000, 500}}S;21;{{0, 0}, {1000, 500}}d;2;36S;6;CPViewS;22;{{20, 203}, {172, 25}}S;19;{{0, 0}, {172, 25}}S;21;Remove From Superviewd;2;14S;23;{{216, 203}, {172, 25}}S;6;RevertS;24;{{1199, 463}, {104, 22}}S;24;{{1058, 471}, {137, 17}}S;21;{{1269, 408}, {0, 0}}S;23;{{1225, 405}, {39, 22}}D;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;398E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;S;23;{{1163, 413}, {59, 17}}S;18;{{0, 0}, {59, 17}}S;22;{{1241, 89}, {62, 22}}S;18;{{0, 0}, {62, 22}}S;7;0,0,1,1S;23;{{1058, 100}, {89, 17}}S;18;{{0, 0}, {89, 17}}D;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;338E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;S;16;Timing Function:S;23;{{1241, 435}, {62, 22}}S;23;{{1058, 446}, {89, 17}}D;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;338E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;S;23;{{1145, 440}, {99, 25}}D;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;338E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;S;22;{{1058, 60}, {89, 18}}S;18;{{0, 0}, {89, 18}}D;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;267E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;S;7;Group 1S;23;{{1058, 413}, {89, 18}}D;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;267E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;S;7;Group 2S;21;{{21, 22}, {438, 87}}S;19;{{0, 0}, {438, 87}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;301E;E;E;S;3;boxS;64;Animation group 1 contains a view with the following properties:D;K;6;$classD;K;6;CP$UIDd;2;57E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;97E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;436E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;437E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;438E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;97E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;401E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;3;402E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;d;4;1000S;20;{{15, 9}, {138, 21}}S;19;{{0, 0}, {138, 21}}S;9;check-boxS;12;Has Subviewsd;4;1001S;21;{{155, 9}, {138, 21}}S;13;Custom Layoutd;4;1002S;21;{{299, 9}, {138, 21}}S;14;Custom Drawingd;4;1003S;21;{{34, 50}, {138, 21}}d;4;1004S;21;{{34, 29}, {138, 21}}S;11;Auto LayoutS;22;{{21, 126}, {438, 51}}S;19;{{0, 0}, {438, 51}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;322E;E;E;S;64;Animation group 2 contains a view with the following properties:D;K;6;$classD;K;6;CP$UIDd;2;57E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;104E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;439E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;440E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;441E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;104E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;401E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;3;402E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;S;21;{{154, 9}, {138, 21}}S;23;{{468, 140}, {128, 25}}S;19;{{0, 0}, {128, 25}}S;14;Run AnimationsS;22;{{467, 54}, {128, 25}}S;22;{{609, 94}, {135, 25}}S;19;{{0, 0}, {135, 25}}S;15;Run Both GroupsS;25;{{1059, 496}, {240, 247}}S;20;{{0, 0}, {240, 247}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;336E;D;K;6;CP$UIDd;3;114E;D;K;6;CP$UIDd;3;117E;D;K;6;CP$UIDd;3;337E;D;K;6;CP$UIDd;3;363E;D;K;6;CP$UIDd;3;339E;E;E;S;10;scrollviewD;K;10;$classnameS;10;CPClipViewK;8;$classesA;S;10;CPClipViewS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;335E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;112E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;399E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;354E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;400E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;112E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;401E;K;21;CPViewBackgroundColorD;K;6;CP$UIDd;3;355E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;3;402E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;24;CPScrollViewDocumentViewD;K;6;CP$UIDd;3;119E;E;D;K;6;$classD;K;6;CP$UIDd;3;335E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;112E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;403E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;404E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;405E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;112E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;338E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;3;406E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;24;CPScrollViewDocumentViewD;K;6;CP$UIDd;3;116E;E;d;2;10D;K;6;$classD;K;6;CP$UIDd;2;57E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;112E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;256E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;112E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;S;23;{{224, 128}, {15, 102}}S;19;{{0, 0}, {15, 102}}d;1;8d;11;-2147483648S;8;scrollerS;27;controlSizeRegular+disabledS;27;_verticalScrollerDidScroll:f;18;0.8717948717948718S;19;{{0, 0}, {238, 25}}S;14;tableHeaderRowS;21;{{1, 113}, {223, 15}}S;19;{{0, 0}, {223, 15}}S;29;_horizontalScrollerDidScroll:f;16;0.99581589958159S;20;{{0, 0}, {238, 229}}D;K;6;$classD;K;6;CP$UIDd;3;226E;K;20;CPColorComponentsKeyD;K;6;CP$UIDd;3;407E;E;S;9;tableviewD;K;6;$classD;K;6;CP$UIDd;3;226E;K;20;CPColorComponentsKeyD;K;6;CP$UIDd;3;408E;E;d;2;20S;6;{3, 2}d;2;-1D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;122E;D;K;6;CP$UIDd;3;121E;E;E;D;K;10;$classnameS;13;_CPCornerViewK;8;$classesA;S;13;_CPCornerViewS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;362E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;112E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;409E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;410E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;112E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;342E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;411E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;3;412E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;d;3;182d;2;40D;K;10;$classnameS;24;_CPTableColumnHeaderViewK;8;$classesA;S;24;_CPTableColumnHeaderViewS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;366E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;256E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;413E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;414E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;16;$atext-alignmentD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;415E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;38;_CPTableColumnHeaderViewStringValueKeyD;K;6;CP$UIDd;3;416E;K;32;_CPTableColumnHeaderViewImageKeyD;K;6;CP$UIDd;1;0E;K;31;_CPTableColumnHeaderViewFontKeyD;K;6;CP$UIDd;3;417E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;256E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;418E;K;11;$aalignmentD;K;6;CP$UIDd;3;209E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;220E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;419E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;420E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;355E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;220E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;209E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;214E;E;D;K;6;$classD;K;6;CP$UIDd;3;366E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;256E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;421E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;414E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;16;$atext-alignmentD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;415E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;38;_CPTableColumnHeaderViewStringValueKeyD;K;6;CP$UIDd;3;422E;K;32;_CPTableColumnHeaderViewImageKeyD;K;6;CP$UIDd;1;0E;K;31;_CPTableColumnHeaderViewFontKeyD;K;6;CP$UIDd;3;417E;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;423E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;423E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;424E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;20;$avertical-alignmentD;K;6;CP$UIDd;3;221E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;241E;K;15;$aimage-scalingD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;425E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;241E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;3;426E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;3;427E;E;S;25;{{1059, 154}, {240, 198}}S;20;{{0, 0}, {240, 198}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;374E;D;K;6;CP$UIDd;3;129E;D;K;6;CP$UIDd;3;127E;D;K;6;CP$UIDd;3;375E;D;K;6;CP$UIDd;3;379E;D;K;6;CP$UIDd;3;376E;E;E;D;K;6;$classD;K;6;CP$UIDd;3;335E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;123E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;428E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;377E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;429E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;123E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;401E;K;21;CPViewBackgroundColorD;K;6;CP$UIDd;3;355E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;3;402E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;24;CPScrollViewDocumentViewD;K;6;CP$UIDd;3;124E;E;D;K;6;$classD;K;6;CP$UIDd;3;335E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;123E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;430E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;404E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;431E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;123E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;338E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;3;406E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;24;CPScrollViewDocumentViewD;K;6;CP$UIDd;3;128E;E;D;K;6;$classD;K;6;CP$UIDd;2;57E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;123E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;256E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;123E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;S;20;{{0, 0}, {238, 180}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;125E;D;K;6;CP$UIDd;3;126E;E;E;D;K;6;$classD;K;6;CP$UIDd;3;362E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;123E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;3;209E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;432E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;410E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;123E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;342E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;411E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;3;412E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;D;K;6;$classD;K;6;CP$UIDd;3;366E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;256E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;433E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;414E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;16;$atext-alignmentD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;415E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;38;_CPTableColumnHeaderViewStringValueKeyD;K;6;CP$UIDd;3;422E;K;32;_CPTableColumnHeaderViewImageKeyD;K;6;CP$UIDd;1;0E;K;31;_CPTableColumnHeaderViewFontKeyD;K;6;CP$UIDd;3;417E;E;D;K;6;$classD;K;6;CP$UIDd;2;98E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;423E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;423E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;305E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;424E;K;11;$aalignmentD;K;6;CP$UIDd;3;220E;K;20;$avertical-alignmentD;K;6;CP$UIDd;3;221E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;16;$aimage-positionD;K;6;CP$UIDd;3;241E;K;15;$aimage-scalingD;K;6;CP$UIDd;3;221E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;209E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;220E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;16;CPButtonTitleKeyD;K;6;CP$UIDd;3;425E;K;25;CPButtonAlternateTitleKeyD;K;6;CP$UIDd;3;225E;K;27;CPButtonAllowsMixedStateKeyD;K;6;CP$UIDd;3;214E;K;23;CPButtonHighlightsByKeyD;K;6;CP$UIDd;3;241E;K;23;CPButtonShowsStateByKeyD;K;6;CP$UIDd;3;241E;K;32;CPButtonImageDimsWhenDisabledKeyD;K;6;CP$UIDd;3;224E;K;24;CPButtonImagePositionKeyD;K;6;CP$UIDd;3;241E;K;28;CPButtonKeyEquivalentMaskKeyD;K;6;CP$UIDd;3;209E;K;24;CPButtonPeriodicDelayKeyD;K;6;CP$UIDd;3;426E;K;27;CPButtonPeriodicIntervalKeyD;K;6;CP$UIDd;3;427E;E;d;3;175D;K;6;$classD;K;6;CP$UIDd;3;366E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;256E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;3;434E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;414E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;16;$atext-alignmentD;K;6;CP$UIDd;3;209E;K;6;$afontD;K;6;CP$UIDd;3;415E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;38;_CPTableColumnHeaderViewStringValueKeyD;K;6;CP$UIDd;3;416E;K;32;_CPTableColumnHeaderViewImageKeyD;K;6;CP$UIDd;1;0E;K;31;_CPTableColumnHeaderViewFontKeyD;K;6;CP$UIDd;3;417E;E;D;K;6;$classD;K;6;CP$UIDd;2;59E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;256E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;256E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;3;218E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;418E;K;11;$aalignmentD;K;6;CP$UIDd;3;209E;K;17;$aline-break-modeD;K;6;CP$UIDd;3;220E;K;6;$afontD;K;6;CP$UIDd;3;223E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;K;17;CPControlValueKeyD;K;6;CP$UIDd;3;419E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;3;420E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;3;209E;K;24;CPTextFieldIsEditableKeyD;K;6;CP$UIDd;3;214E;K;26;CPTextFieldIsSelectableKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldDrawsBackgroundKeyD;K;6;CP$UIDd;3;214E;K;29;CPTextFieldBackgroundColorKeyD;K;6;CP$UIDd;3;355E;K;27;CPTextFieldLineBreakModeKeyD;K;6;CP$UIDd;3;220E;K;23;CPTextFieldAlignmentKeyD;K;6;CP$UIDd;3;209E;K;31;CPTextFieldPlaceholderStringKeyD;K;6;CP$UIDd;1;0E;K;29;CPTextFieldUsesSingleLineModeD;K;6;CP$UIDd;3;214E;K;16;CPTextFieldWrapsD;K;6;CP$UIDd;3;214E;K;18;CPTextFieldScrollsD;K;6;CP$UIDd;3;214E;E;S;19;{{1, 1}, {250, 15}}S;19;{{0, 0}, {250, 15}}S;22;{{224, 79}, {15, 102}}f;18;0.9166666666666666S;13;AppControllerS;19;CPMutableDictionaryS;10;OtherViewsD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;65E;D;K;6;CP$UIDd;2;66E;D;K;6;CP$UIDd;2;67E;D;K;6;CP$UIDd;2;68E;D;K;6;CP$UIDd;2;69E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;93E;D;K;6;CP$UIDd;2;92E;D;K;6;CP$UIDd;2;91E;D;K;6;CP$UIDd;2;90E;D;K;6;CP$UIDd;2;89E;E;E;S;29;.Helvetica Neue DeskInterfaced;2;13D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;241E;D;K;6;CP$UIDd;3;241E;D;K;6;CP$UIDd;3;241E;D;K;6;CP$UIDd;3;241E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;435E;D;K;6;CP$UIDd;3;435E;D;K;6;CP$UIDd;3;435E;D;K;6;CP$UIDd;3;241E;E;E;d;2;15S;20;{{1, 1}, {238, 229}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;119E;E;E;d;2;18S;6;_NS:11S;21;{{1, 230}, {238, 17}}S;19;{{0, 0}, {238, 17}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;116E;E;E;S;6;_NS:15D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;241E;D;K;6;CP$UIDd;3;241E;D;K;6;CP$UIDd;3;241E;D;K;6;CP$UIDd;3;241E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;442E;D;K;6;CP$UIDd;3;442E;D;K;6;CP$UIDd;3;442E;D;K;6;CP$UIDd;3;241E;E;E;S;22;{{224, 222}, {14, 25}}S;18;{{0, 0}, {14, 25}}S;10;cornerviewS;6;_NS:19D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;444E;E;E;S;12;columnHeaderD;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;394E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;445E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;214E;E;S;9;AnimationD;K;6;$classD;K;6;CP$UIDd;3;222E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;446E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;238E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;3;224E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;3;214E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;3;224E;E;S;32;controlSizeRegular+tableDataViewS;9;Text Celld;4;3072D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;447E;E;E;S;3;RunS;17;{{0, 0}, {0, 21}}S;42;controlSizeRegular+roundRect+tableDataViewS;5;Checkf;3;0.5f;4;0.05S;20;{{1, 1}, {238, 180}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;124E;E;E;S;21;{{1, 181}, {238, 17}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;128E;E;E;S;22;{{224, 173}, {14, 25}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;448E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;449E;E;E;f;18;0.6862745098039216S;19;{{1, 9}, {444, 77}}S;19;{{0, 0}, {444, 77}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;99E;D;K;6;CP$UIDd;3;100E;D;K;6;CP$UIDd;3;101E;D;K;6;CP$UIDd;3;102E;D;K;6;CP$UIDd;3;103E;E;E;S;19;{{1, 9}, {444, 41}}S;19;{{0, 0}, {444, 41}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;105E;D;K;6;CP$UIDd;3;106E;D;K;6;CP$UIDd;3;107E;E;E;f;3;0.8D;K;10;$classnameS;19;_CPImageAndTextViewK;8;$classesA;S;19;_CPImageAndTextViewS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;443E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;367E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;450E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;451E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;367E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;401E;K;17;CPViewHitTestsKeyD;K;6;CP$UIDd;3;214E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;d;2;11S;28;_CPFontSystemFacePlaceholderD;K;6;$classD;K;6;CP$UIDd;3;443E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;369E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;450E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;451E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;369E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;401E;K;17;CPViewHitTestsKeyD;K;6;CP$UIDd;3;214E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;D;K;6;$classD;K;6;CP$UIDd;3;443E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;380E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;450E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;451E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;380E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;401E;K;17;CPViewHitTestsKeyD;K;6;CP$UIDd;3;214E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;D;K;6;$classD;K;6;CP$UIDd;3;443E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;3;383E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;3;450E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;3;451E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;3;383E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;3;401E;K;17;CPViewHitTestsKeyD;K;6;CP$UIDd;3;214E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;3;212E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;3;213E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;3;213E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;3;214E;E;S;18;{{5, 0}, {-10, 0}}S;18;{{0, 0}, {-10, 0}}E;K;9;$archiverS;15;CPKeyedArchiverK;8;$versionS;6;100000E; \ No newline at end of file diff --git a/Tests/Manual/CPAnimatablePropertyContainerTest/Resources/MainMenu.xib b/Tests/Manual/CPAnimatablePropertyContainerTest/Resources/MainMenu.xib new file mode 100644 index 000000000..7e2e7cf5a --- /dev/null +++ b/Tests/Manual/CPAnimatablePropertyContainerTest/Resources/MainMenu.xib @@ -0,0 +1,2932 @@ + + + + 1050 + 14C109 + 6250 + 1344.72 + 757.30 + + com.apple.InterfaceBuilder.CocoaPlugin + 6250 + + + NSArrayController + NSBox + NSButton + NSButtonCell + NSCustomObject + NSCustomView + NSMenu + NSMenuItem + NSObjectController + NSPopUpButton + NSPopUpButtonCell + NSScrollView + NSScroller + NSStepper + NSStepperCell + NSTableColumn + NSTableHeaderView + NSTableView + NSTextField + NSTextFieldCell + NSView + NSWindowTemplate + + + com.apple.InterfaceBuilder.CocoaPlugin + + + PluginDependencyRecalculationVersion + + + + + NSApplication + + + FirstResponder + + + NSApplication + + + 7 + 2 + {{335, 390}, {1319, 763}} + 1946157056 + Window + NSWindow + + + + + 256 + + + + 265 + + + + 2322 + + + + 256 + {238, 180} + + + + _NS:13 + YES + NO + YES + + + 256 + {238, 17} + + + + _NS:16 + + + + + -2147483392 + {{224, 0}, {16, 17}} + + + + _NS:19 + + + + 40 + 40 + 1000 + + 75497536 + 2048 + Run + + YES + 11 + 3100 + + + 3 + MC4zMzMzMzI5ODU2AA + + + 6 + System + headerTextColor + + 3 + MAA + + + + + 67108864 + 268435456 + Check + + YES + 13 + 1044 + + _NS:9 + + 1215582464 + 2 + + NSImage + NSSwitch + + + NSSwitch + + + + 200 + 25 + + 3 + YES + YES + + + + 175 + 40 + 1000 + + 75497536 + 2048 + Animation + + + + + + 67108928 + 2048 + Text Cell + + + + 6 + System + controlBackgroundColor + + 3 + MC42NjY2NjY2NjY3AA + + + + 6 + System + controlTextColor + + + + 3 + YES + + + + 3 + 2 + + 3 + MQA + + + 6 + System + gridColor + + 3 + MC41AA + + + 20 + 314572800 + + + 4 + 15 + 0 + NO + -1 + 0 + 1 + + + {{1, 17}, {238, 180}} + + + + _NS:11 + + + 4 + YES + + + + -2147483392 + {{224, 17}, {15, 102}} + + + + _NS:58 + NO + _doScroller: + + + _doScroller: + 0.91666666666666663 + + + + -2147483392 + {{1, 182}, {250, 15}} + + + + _NS:60 + NO + _doScroller: + + 1 + + _doScroller: + 0.99581589958159 + + + + 2338 + + + + {{1, 0}, {238, 17}} + + + + _NS:15 + + YES + + + + {{1059, 411}, {240, 198}} + + + + _NS:9 + 133682 + + + + + + QSAAAEEgAABBsAAAQbAAAA + 0.25 + 4 + 1 + + + + 265 + + + + 2322 + + + + 256 + {238, 229} + + + + _NS:13 + YES + NO + YES + + + 256 + {238, 17} + + + + _NS:16 + + + + + -2147483392 + {{224, 0}, {16, 17}} + + + + _NS:19 + + + + 40 + 40 + 1000 + + 75497536 + 2048 + Run + + + 3 + MC4zMzMzMzI5ODU2AA + + + + + 67108864 + 268435456 + Check + + _NS:9 + + 1215582464 + 2 + + + + + 200 + 25 + + 3 + YES + YES + + + + 182 + 40 + 1000 + + 75497536 + 2048 + Animation + + + + + + 67108928 + 2048 + Text Cell + + + + + + 3 + YES + + + + 3 + 2 + + + 20 + 314572800 + + + 4 + 15 + 0 + NO + -1 + 0 + 1 + + + {{1, 17}, {238, 229}} + + + + _NS:11 + + + 4 + YES + + + + -2147483392 + {{224, 17}, {15, 102}} + + + + _NS:58 + NO + _doScroller: + + + _doScroller: + 0.87179487179487181 + + + + -2147483392 + {{1, 119}, {223, 15}} + + + + _NS:60 + NO + _doScroller: + + 1 + + _doScroller: + 0.99581589958159 + + + + 2338 + + + + {{1, 0}, {238, 17}} + + + + _NS:15 + + YES + + + + {{1059, 20}, {240, 247}} + + + + _NS:9 + 133682 + + + + + + QSAAAEEgAABBsAAAQbAAAA + 0.25 + 4 + 1 + + + + 265 + {{1266, 680}, {19, 27}} + + + + _NS:1099 + YES + + 786464 + 0 + _NS:1099 + + 100 + 1 + YES + + NO + + + + 265 + {{1230, 683}, {31, 22}} + + + + _NS:9 + YES + + -2075131840 + 272630784 + + + YES + 15 + 1044 + + _NS:9 + + YES + + 6 + System + textBackgroundColor + + + + 6 + System + textColor + + + + NO + 1 + + + + 265 + {{1266, 330}, {19, 27}} + + + + _NS:1099 + YES + + 786464 + 0 + _NS:1099 + + 100 + 1 + YES + + NO + + + + 265 + {{1229, 333}, {31, 22}} + + + + _NS:9 + YES + + -2075131840 + 272630784 + + + YES + 15 + 1044 + + _NS:9 + + YES + + + + NO + 1 + + + + 265 + {{1203, 617}, {96, 22}} + + + + _NS:9 + YES + + -1804599231 + 272630784 + + + _NS:9 + + YES + + + + NO + 1 + + + + 265 + {{1056, 617}, {141, 17}} + + + + _NS:1535 + YES + + 68157504 + 272630784 + Completion message: + + _NS:1535 + + + 6 + System + controlColor + + + + + NO + 1 + + + + 265 + {{1203, 275}, {96, 22}} + + + + _NS:9 + YES + + -1804599231 + 272630784 + + + _NS:9 + + YES + + + + NO + 1 + + + + 265 + {{1056, 275}, {141, 17}} + + + + _NS:1535 + YES + + 68157504 + 272630784 + Completion message: + + _NS:1535 + + + + + NO + 1 + + + + 265 + {{1161, 683}, {64, 17}} + + + + _NS:1535 + YES + + 68157504 + 272630784 + Duration: + + _NS:1535 + + + + + NO + 1 + + + + 265 + {{1161, 333}, {63, 17}} + + + + _NS:1535 + YES + + 68157504 + 272630784 + Duration: + + _NS:1535 + + + + + NO + 1 + + + + 265 + {{1245, 649}, {54, 22}} + + + + _NS:9 + YES + + -1804599231 + 272630784 + + + 0,0,1,1 + _NS:9 + + YES + + + + NO + 1 + + + + 265 + {{1056, 646}, {93, 17}} + + + + _NS:1535 + YES + + 68157504 + 272630784 + Timing Function: + + YES + 10 + 1044 + + _NS:1535 + + + + + NO + 1 + + + + 268 + {{461, 681}, {140, 32}} + + + + _NS:9 + YES + + 67108864 + 134217728 + Run Animations + + _NS:9 + + -2038284288 + 129 + + + 200 + 25 + + NO + + + + 268 + {{462, 595}, {140, 32}} + + + + _NS:9 + YES + + 67108864 + 134217728 + Run Animations + + _NS:9 + + -2038284288 + 129 + + + 200 + 25 + + NO + + + + 268 + {{603, 641}, {147, 32}} + + + + _NS:9 + YES + + 67108864 + 134217728 + Run Both Groups + + _NS:9 + + -2038284288 + 129 + + + 200 + 25 + + NO + + + + 268 + {{20, 20}, {1000, 500}} + + + + _NS:9 + NSView + + + + 12 + + + + 274 + + + + 268 + {{16, 49}, {128, 18}} + + + + _NS:9 + 1000 + YES + + 67108864 + 268435456 + Has Subviews + + _NS:9 + + 1211912448 + 2 + + + + + 200 + 25 + + NO + + + + 268 + {{156, 49}, {128, 18}} + + + + _NS:9 + 1001 + YES + + 67108864 + 268435456 + Custom Layout + + _NS:9 + + 1211912448 + 2 + + + + + 200 + 25 + + NO + + + + 268 + {{300, 49}, {128, 18}} + + + + _NS:9 + 1002 + YES + + 67108864 + 268435456 + Custom Drawing + + _NS:9 + + 1211912448 + 2 + + + + + 200 + 25 + + NO + + + + 268 + {{35, 8}, {128, 18}} + + + + _NS:9 + 1003 + YES + + 67108864 + 268435456 + Custom Drawing + + _NS:9 + + 1211912448 + 2 + + + + + 200 + 25 + + NO + + + + 268 + {{35, 29}, {128, 18}} + + + + _NS:9 + 1004 + YES + + 67108864 + 268435456 + Auto Layout + + _NS:9 + + 1211912448 + 2 + + + + + 200 + 25 + + NO + + + {{1, 1}, {444, 77}} + + + + _NS:11 + + + {{17, 650}, {446, 93}} + + + + _NS:9 + {0, 0} + + 67108864 + 0 + Animation group 1 contains a view with the following properties: + + + + 6 + System + labelColor + + + + + 1 + 0 + 2 + NO + + 1 + MCAwIDAgMC40MgA + + + 1 + MCAwIDAgMC4xAA + + + + + 12 + + + + 274 + + + + 268 + {{300, 13}, {128, 18}} + + + + _NS:9 + 1002 + YES + + 67108864 + 268435456 + Custom Drawing + + _NS:9 + + 1211912448 + 2 + + + + + 200 + 25 + + NO + + + + 268 + {{155, 13}, {128, 18}} + + + + _NS:9 + 1001 + YES + + 67108864 + 268435456 + Custom Layout + + _NS:9 + + 1211912448 + 2 + + + + + 200 + 25 + + NO + + + + 268 + {{16, 13}, {128, 18}} + + + + _NS:9 + 1000 + YES + + 67108864 + 268435456 + Has Subviews + + _NS:9 + + 1211912448 + 2 + + + + + 200 + 25 + + NO + + + {{1, 1}, {444, 41}} + + + + _NS:11 + + + {{17, 582}, {446, 57}} + + + + _NS:9 + {0, 0} + + 67108864 + 0 + Animation group 2 contains a view with the following properties: + + + + + + 1 + 0 + 2 + NO + + 3 + MCAwLjEAA + + + + + 268 + {{14, 532}, {184, 32}} + + + + _NS:9 + YES + + 67108864 + 134217728 + Remove From Superview + + _NS:9 + + -2038284288 + 129 + + + 200 + 25 + + NO + + + + 265 + {{1143, 645}, {104, 26}} + + + + _NS:9 + YES + + -2076180416 + 2048 + + YES + 10 + 1044 + + _NS:9 + + 109199360 + 129 + + + 400 + 75 + + + Linear + + 1048576 + 2147483647 + 1 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + _popUpItemAction: + 1 + + + YES + + OtherViews + + + + + Ease-in + + 1048576 + 2147483647 + + + _popUpItemAction: + 2 + + + + + Ease Out + + 1048576 + 2147483647 + + + _popUpItemAction: + 3 + + + + + Ease In-Out + + 1048576 + 2147483647 + + + _popUpItemAction: + 4 + + + + + Custom + + 1048576 + 2147483647 + + + _popUpItemAction: + + + + + + 1 + YES + YES + 2 + + NO + + + + 268 + {{210, 532}, {184, 32}} + + + + _NS:9 + YES + + 67108864 + 134217728 + Revert + + _NS:9 + + -2038284288 + 129 + + + 200 + 25 + + NO + + + + 265 + {{1245, 303}, {54, 22}} + + + + _NS:9 + YES + + -1804599231 + 272630784 + + + 0,0,1,1 + _NS:9 + + YES + + + + NO + 1 + + + + 265 + {{1056, 300}, {93, 17}} + + + + _NS:1535 + YES + + 68157504 + 272630784 + Timing Function: + + YES + 10 + 1044 + + _NS:1535 + + + + + NO + 1 + + + + 265 + {{1143, 299}, {104, 26}} + + + + _NS:9 + YES + + -2076180416 + 2048 + + YES + 10 + 1044 + + _NS:9 + + 109199360 + 129 + + + 400 + 75 + + + Linear + + 1048576 + 2147483647 + 1 + + + _popUpItemAction: + 1 + + + YES + + OtherViews + + + + + Ease-in + + 1048576 + 2147483647 + + + _popUpItemAction: + 2 + + + + + Ease Out + + 1048576 + 2147483647 + + + _popUpItemAction: + 3 + + + + + Ease In-Out + + 1048576 + 2147483647 + + + _popUpItemAction: + 4 + + + + + Custom + + 1048576 + 2147483647 + + + _popUpItemAction: + + + + + + 1 + YES + YES + 2 + + NO + + + + 265 + {{1056, 685}, {93, 18}} + + + + _NS:1535 + YES + + 68157504 + 272630784 + Group 1 + + YES + 14 + 1044 + + _NS:1535 + + + + + NO + 1 + + + + 265 + {{1056, 332}, {93, 18}} + + + + _NS:1535 + YES + + 68157504 + 272630784 + Group 2 + + YES + 14 + 1044 + + _NS:1535 + + + + + NO + 1 + + + {1319, 763} + + + + + {{0, 0}, {1680, 1027}} + {10000000000000, 10000000000000} + YES + + + AppController + + + YES + + YES + + + YES + + + + + + + + delegate + + + + 451 + + + + theWindow + + + + 687 + + + + animationSandbox + + + + 717 + + + + setupGroup1: + + + + 720 + + + + setupGroup1: + + + + 721 + + + + setupGroup1: + + + + 722 + + + + group1Box + + + + 723 + + + + group2Box + + + + 724 + + + + runAnimationsGroup1: + + + + 725 + + + + runAnimationsGroup2: + + + + 726 + + + + runBothGroups: + + + + 727 + + + + removeFromSuperview: + + + + 731 + + + + setupGroup2: + + + + 743 + + + + setupGroup2: + + + + 744 + + + + setupGroup2: + + + + 745 + + + + revert: + + + + 746 + + + + button + + + + 795 + + + + setupGroup1: + + + + 823 + + + + setupGroup1: + + + + 826 + + + + value: arrangedObjects.enabled1 + + + + + + value: arrangedObjects.enabled1 + value + arrangedObjects.enabled1 + 2 + + + 564 + + + + value: arrangedObjects.name + + + + + + value: arrangedObjects.name + value + arrangedObjects.name + 2 + + + 544 + + + + value: arrangedObjects.name + + + + + + value: arrangedObjects.name + value + arrangedObjects.name + 2 + + + 567 + + + + value: arrangedObjects.enabled2 + + + + + + value: arrangedObjects.enabled2 + value + arrangedObjects.enabled2 + 2 + + + 565 + + + + contentArray: animations + + + + + + contentArray: animations + contentArray + animations + 2 + + + 562 + + + + content + + + + 546 + + + + value: selection.duration1 + + + + + + value: selection.duration1 + value + selection.duration1 + 2 + + + 574 + + + + value: selection.duration1 + + + + + + value: selection.duration1 + value + selection.duration1 + 2 + + + 569 + + + + value: selection.duration2 + + + + + + value: selection.duration2 + value + selection.duration2 + 2 + + + 572 + + + + value: selection.duration2 + + + + + + value: selection.duration2 + value + selection.duration2 + 2 + + + 570 + + + + value: selection.message1 + + + + + + value: selection.message1 + value + selection.message1 + 2 + + + 586 + + + + value: selection.message2 + + + + + + value: selection.message2 + value + selection.message2 + 2 + + + 587 + + + + value: selection.timingFunction1 + + + + + + value: selection.timingFunction1 + value + selection.timingFunction1 + 2 + + + 595 + + + + hidden: selection.selectedTimingFunction1 + + + + + + hidden: selection.selectedTimingFunction1 + hidden + selection.selectedTimingFunction1 + 2 + + + 769 + + + + selectedTag: selection.selectedTimingFunction1 + + + + + + selectedTag: selection.selectedTimingFunction1 + selectedTag + selection.selectedTimingFunction1 + 2 + + + 763 + + + + hidden: selection.selectedTimingFunction2 + + + + + + hidden: selection.selectedTimingFunction2 + hidden + selection.selectedTimingFunction2 + 2 + + + 788 + + + + value: selection.timingFunction2 + + + + + + value: selection.timingFunction2 + value + selection.timingFunction2 + 2 + + + 790 + + + + selectedTag: selection.selectedTimingFunction2 + + + + + + selectedTag: selection.selectedTimingFunction2 + selectedTag + selection.selectedTimingFunction2 + 2 + + + 786 + + + + + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 371 + + + + + + + + 372 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 450 + + + + + 500 + + + + + + + + + + + 501 + + + + + + + + + 502 + + + + + 503 + + + + + 504 + + + + + 505 + + + + + + + + 506 + + + + + + + + 507 + + + + + 518 + + + + + + + + + + + 521 + + + + + 522 + + + + + 523 + + + + + 524 + + + + + + + + + 525 + + + + + + + + 526 + + + + + + + + 528 + + + + + 539 + + + + + 540 + + + + + 541 + + + + + 545 + + + + + 548 + + + + + + + + 549 + + + + + 550 + + + + + + + + 551 + + + + + 555 + + + + + + + + 556 + + + + + + + + 557 + + + + + 558 + + + + + 578 + + + + + + + + 579 + + + + + 580 + + + + + + + + 581 + + + + + 582 + + + + + + + + 583 + + + + + + + + 584 + + + + + 585 + + + + + 589 + + + + + + + + 590 + + + + + 591 + + + + + + + + 592 + + + + + 593 + + + + + + + + 594 + + + + + 596 + + + + + + + + 597 + + + + + 710 + + + + + + + + 711 + + + + + 712 + + + + + + + + 713 + + + + + 714 + + + + + + + + 715 + + + + + 716 + + + + + 718 + + + + + + + + + + + + 700 + + + + + + + + 701 + + + + + 696 + + + + + + + + 697 + + + + + 698 + + + + + + + + 699 + + + + + 719 + + + + + + + + + + 704 + + + + + + + + 707 + + + + + 703 + + + + + + + + 708 + + + + + 705 + + + + + + + + 706 + + + + + 728 + + + + + + + + 729 + + + + + 732 + + + + + + + + 733 + + + + + + + + 734 + + + + + + + + + + + + 735 + + + + + 736 + + + + + 737 + + + + + 738 + + + + + 739 + + + + + 740 + + + + + + + + 741 + + + + + 770 + + + + + + + + 771 + + + + + + + + 772 + + + + + + + + 773 + + + + + + + + 774 + + + + + + + + + + + + 775 + + + + + 776 + + + + + 777 + + + + + 778 + + + + + 779 + + + + + 780 + + + + + 781 + + + + + 791 + + + + + + + + 792 + + + + + 793 + + + + + + + + 794 + + + + + 821 + + + + + + + + 822 + + + + + 824 + + + + + + + + 825 + + + + + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{303, 221}, {480, 360}} + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + + + 826 + + + + + AppController + NSObject + + id + id + id + id + id + id + id + id + + + + animate: + id + + + removeFromSuperview: + id + + + revert: + id + + + runAnimationsGroup1: + id + + + runAnimationsGroup2: + id + + + runBothGroups: + id + + + setupGroup1: + id + + + setupGroup2: + id + + + + NSView + NSBox + NSBox + NSWindow + + + + animationSandbox + NSView + + + group1Box + NSBox + + + group2Box + NSBox + + + theWindow + NSWindow + + + + IBProjectSource + ../.XcodeSupport/AppController.h + + + + + 0 + IBCocoaFramework + NO + + com.apple.InterfaceBuilder.CocoaPlugin.macosx + + + + com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 + + + YES + 3 + + {12, 12} + {10, 2} + {15, 15} + + + diff --git a/Tests/Manual/CPAnimatablePropertyContainerTest/Resources/spinner.gif b/Tests/Manual/CPAnimatablePropertyContainerTest/Resources/spinner.gif new file mode 100644 index 000000000..77b36416b Binary files /dev/null and b/Tests/Manual/CPAnimatablePropertyContainerTest/Resources/spinner.gif differ diff --git a/Tests/Manual/Issue-1357-fix/index-debug.html b/Tests/Manual/CPAnimatablePropertyContainerTest/index-debug.html similarity index 81% rename from Tests/Manual/Issue-1357-fix/index-debug.html rename to Tests/Manual/CPAnimatablePropertyContainerTest/index-debug.html index 636db06c8..10b7505ee 100644 --- a/Tests/Manual/Issue-1357-fix/index-debug.html +++ b/Tests/Manual/CPAnimatablePropertyContainerTest/index-debug.html @@ -1,38 +1,33 @@ - - + + - + - - + - + + - - + + - - - - CPViewNoDisplayAfterHidingAndResizeBug + CPAnimatablePropertyContainerTest - +