mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-09 04:07:12 +00:00
Merge remote-tracking branch 'cappuccino/master'
This commit is contained in:
@@ -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"
|
||||
|
||||
+11
-2
@@ -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];
|
||||
|
||||
+42
-23
@@ -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
|
||||
|
||||
+14
-2
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -255,6 +255,8 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
|
||||
|
||||
var colorPanel = [CPColorPanel sharedColorPanel];
|
||||
|
||||
[colorPanel setPlatformWindow:[[self window] platformWindow]];
|
||||
|
||||
[colorPanel setColor:_color];
|
||||
[colorPanel orderFront:self];
|
||||
}
|
||||
|
||||
+2
-4
@@ -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];
|
||||
|
||||
@@ -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(' '),
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
|
||||
@import "CPFont.j"
|
||||
@import "CPShadow.j"
|
||||
@import "CPView.j"
|
||||
@import "CPText.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
@import "CPTrackingArea.j"
|
||||
|
||||
|
||||
+16
-6
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
@import <Foundation/CPBundle.j>
|
||||
|
||||
@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",
|
||||
|
||||
+208
-1
@@ -22,9 +22,12 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@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,
|
||||
|
||||
+6
-5
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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);
|
||||
}
|
||||
|
||||
+105
-3
@@ -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
|
||||
+2
-69
@@ -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
|
||||
|
||||
+27
-12
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+30
-28
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+300
-5
@@ -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 <Foundation/Foundation.j>
|
||||
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPView.j"
|
||||
|
||||
@global CPStringPboardType
|
||||
@class CPAttributedString
|
||||
@class _CPRTFParser
|
||||
|
||||
@protocol CPTextDelegate <CPObject>
|
||||
|
||||
- (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;
|
||||
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
|
||||
|
||||
+46
-47
@@ -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
|
||||
@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
|
||||
|
||||
@@ -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 <Foundation/CPObject.j>
|
||||
/*
|
||||
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
|
||||
@@ -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]];
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 <Foundation/CPObject.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@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
|
||||
@@ -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 <Foundation/CPGeometry.j>
|
||||
@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
|
||||
@@ -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 <Foundation/CPNotificationCenter.j>
|
||||
@import <Foundation/CPAttributedString.j>
|
||||
|
||||
@import "CPText.j"
|
||||
@import "CPFont.j"
|
||||
|
||||
@class CPLayoutManager;
|
||||
|
||||
CPTextStorageEditedAttributes = 1;
|
||||
CPTextStorageEditedCharacters = 2;
|
||||
|
||||
CPTextStorageWillProcessEditingNotification = @"CPTextStorageWillProcessEditingNotification";
|
||||
CPTextStorageDidProcessEditingNotification = @"CPTextStorageDidProcessEditingNotification";
|
||||
|
||||
@protocol CPTextStorageDelegate <CPObject>
|
||||
|
||||
- (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 <CPTextStorageDelegate> _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 <CPTextStorageDelegate>)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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 <Foundation/CPObject.j>
|
||||
|
||||
@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
|
||||
@@ -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 <Foundation/CPAttributedString.j>
|
||||
@import <Foundation/CPGeometry.j>
|
||||
@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
|
||||
@@ -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 <Foundation/CPAttributedString.j>
|
||||
@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
|
||||
@@ -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
|
||||
|
||||
+4
-5
@@ -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 */
|
||||
|
||||
+132
-74
@@ -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]];
|
||||
|
||||
+174
-2
@@ -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
|
||||
|
||||
|
||||
|
||||
+239
-155
@@ -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];
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,84 @@
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@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
|
||||
@@ -0,0 +1,23 @@
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@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
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@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
|
||||
@@ -0,0 +1,710 @@
|
||||
@import "CABasicAnimation.j"
|
||||
@import "CAKeyframeAnimation.j"
|
||||
@import "CPView.j"
|
||||
|
||||
@import <Foundation/CPTimer.j>
|
||||
|
||||
@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;
|
||||
};
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
@import <Foundation/CPProxy.j>
|
||||
@import "CPAnimationContext.j"
|
||||
|
||||
var _supportsCSSAnimations = null;
|
||||
|
||||
@protocol CPAnimatablePropertyContainer <CPObject>
|
||||
|
||||
+ (id)defaultAnimationForKey:(CPString)key;
|
||||
- (id)animationForKey:(CPString)key;
|
||||
|
||||
- (id)animator;
|
||||
- (CPDictionary)animations;
|
||||
- (void)setAnimations:(CPDictionary)animations;
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPObjectAnimator : CPProxy
|
||||
{
|
||||
id <CPAnimatablePropertyContainer> _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
|
||||
@@ -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 <tim@timdown.co.uk>
|
||||
* 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;
|
||||
})();
|
||||
@@ -24,6 +24,7 @@
|
||||
@import "CPCompatibility.j"
|
||||
@import "CGGeometry.j"
|
||||
@import "CGPath.j"
|
||||
@import "CGContextText.j"
|
||||
|
||||
@typedef CGContext
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -60,6 +60,7 @@ var PrimaryPlatformWindow = NULL;
|
||||
|
||||
BOOL _mouseIsDown;
|
||||
BOOL _mouseDownIsRightClick;
|
||||
int _firstMouseDownButton;
|
||||
CGPoint _lastMouseEventLocation;
|
||||
CPWindow _mouseDownWindow;
|
||||
CPTimeInterval _lastMouseUp;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
-5
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+260
-253
@@ -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<Key>: 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<Key>: 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
|
||||
{
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
+19
-21
@@ -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
|
||||
|
||||
@@ -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--)
|
||||
|
||||
@@ -28,3 +28,5 @@
|
||||
@import "_CPAggregateExpression.j"
|
||||
@import "_CPSetExpression.j"
|
||||
@import "_CPSubqueryExpression.j"
|
||||
@import "_CPBlockExpression.j"
|
||||
@import "_CPConditionalExpression.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];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 '/'
|
||||
|
||||
+32
-24
@@ -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
|
||||
|
||||
/*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 : ""));
|
||||
}
|
||||
|
||||
|
||||
@@ -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({});
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
+1592
-738
File diff suppressed because it is too large
Load Diff
+1182
-526
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
},
|
||||
|
||||
+8
-5
@@ -1,4 +1,4 @@
|
||||
[](https://travis-ci.org/cappuccino/cappuccino)
|
||||
[](https://travis-ci.org/cappuccino/cappuccino) [](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 <http://cappuccino-project.org>.
|
||||
For more information, see <http://cappuccino-project.org>. 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.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user