Compare commits

...
29 Commits
Author SHA1 Message Date
Randall Luecke a80147bafd Merge branch 'master' into coretext 2011-06-25 22:03:51 -07:00
Randall Luecke fc81483371 Fix typeo in CPToolbar.j CPToolbarItems to not respect maxSize. Closes #1305. 2011-06-25 00:45:07 -07:00
Klaas Pieter Annema e552b022ba Merge branch 'pull-request-1302' 2011-06-24 12:21:02 +02:00
Antoine Mercadal 6c34bec51e do not return 0 if any conversion fail (return 2 instead) 2011-06-19 19:32:42 +02:00
Randall Luecke f170e4812d Documented CoreText. 2011-06-19 01:00:22 -07:00
Randall Luecke 070728aea9 Typo 2011-06-18 23:57:31 -07:00
Randall Luecke 5e3ae58d42 Added view drawing methods to CPAttributedString 2011-06-18 23:54:56 -07:00
Randall Luecke b4e4bdb924 Added string drawing methods for CPString. 2011-06-18 23:44:32 -07:00
Randall Luecke 0f097e0ff4 Added support for center alginment of in CTFrameDraw. 2011-06-18 22:59:28 -07:00
Randall Luecke e38845f993 Default to left alignment for frameDraw. 2011-06-18 22:54:03 -07:00
Randall Luecke f4f4dd8025 Default the text position point to (0,0) if it's undefined. 2011-06-18 21:53:50 -07:00
Randall Luecke c942259c90 Import CPAttributedString in Foundation.j now that we can actually use it! :D 2011-06-18 18:59:01 -07:00
Nicholas Small a4fb0920f6 Forgot key file of CoreText. 2011-06-16 11:49:05 -04:00
Ross Boucher cbabb544ab bootstrap should have a +x bit. 2011-06-15 12:54:11 -07:00
Nicholas Small 4d47b5abf5 CoreText.
Still needs a lot of work; there are bugs,
performance issues, and missing features.
2011-06-13 01:30:50 -07:00
Alexander Ljungberg aac09d8208 Clicking on a non-editable level indicator in a table row now selects that row. 2011-06-06 00:44:15 -04:00
Alexander Ljungberg a127fc9ec3 'Enabled' support for nib2cib of NSLevelIndicator. 2011-06-06 00:35:41 -04:00
Alexander Ljungberg 551aa300ea Merge branch 'master' of github.com:280north/cappuccino 2011-06-06 00:11:23 -04:00
Alexander Ljungberg 485e59b5eb More handlesContentAsCompoundValue, although fairly guess-work based. More research needed in the future. 2011-06-06 00:11:04 -04:00
Alexander Ljungberg fdfcbf1da2 CPArrayController handlesContentAsCompoundValue support for contentArray binding. 2011-06-05 19:39:28 -04:00
Alexander Ljungberg 0f5eee0258 nib2cib compatible CPNumberFormatter skeleton. 2011-06-05 19:27:21 -04:00
Alexander Ljungberg 870492eb96 Fix selection behaviour in previous commit. 2011-06-05 19:19:14 -04:00
Alexander Ljungberg b50005049e Fixed: array controller remove: would not only remove selected rows, but any other rows isEqual: to the selected rows. 2011-06-05 18:57:36 -04:00
Alexander Ljungberg 0e890e19ea Fixed: during CPUserDefault change notifications, objectForKey: could return the value from prior to the change. 2011-06-05 17:32:56 -04:00
Alexander Ljungberg 59aabc150c Crude but functional infinite recursion checker, optional at compile time. 2011-06-04 01:23:24 -04:00
Alexander Ljungberg b159815349 Suppress binding updates support. 2011-06-04 00:57:57 -04:00
Alexander Ljungberg bfe5afaddd Whitespace cleanup. 2011-06-03 00:05:30 -04:00
Alexander Ljungberg 64e907156c Minor array controller optimisation: don't send a filterPredicate change notification if clearsFilterPredicateOnInsertion is YES but filterPredicate is already nil when insertObject:atArrangedObjectIndex: is called. 2011-06-03 00:00:39 -04:00
Alexander Ljungberg 5d01ca428c Optimise CPArrayController insertObject:atArrangedObjectIndex: by not attempting to arrange the content array. The given index is only meant for the arrangedObjects. 2011-06-02 23:41:03 -04:00
29 changed files with 1618 additions and 38 deletions
+2
View File
@@ -25,6 +25,7 @@
@import "CPAnimation.j"
@import "CPApplication.j"
@import "CPArrayController.j"
@import "CPAttributedString+Additions.j"
@import "CPBezierPath.j"
@import "CPBox.j"
@import "CPBrowser.j"
@@ -83,6 +84,7 @@
@import "CPSound.j"
@import "CPSplitView.j"
@import "CPStepper.j"
@import "CPString+Additions.j"
@import "CPTableColumn.j"
@import "CPTableView.j"
@import "CPTabView.j"
+132 -22
View File
@@ -313,6 +313,8 @@
// Don't use [super setContent:] as that would fire the contentObject change.
// We need to be in control of when notifications fire.
// Note that if we have a contentArray binding, setting the content does /not/
// cause a reverse binding set.
_contentObject = value;
if (_clearsFilterPredicateOnInsertion && _filterPredicate != nil)
@@ -737,6 +739,10 @@
*/
_disableSetContent = YES;
[_contentObject addObject:object];
// Allow handlesContentAsCompoundValue reverse sets to trigger.
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO;
if (willClearPredicate)
@@ -771,7 +777,8 @@
}
/*!
Adds an object at a given index to the receiver's collection.
Adds an object at a given index in the receiver's arrangedObjects. Also add the object
to the content collection (although at the end rather than the given index).
@param id anObject - The object to add to the collection.
@param int anIndex - The index to insert the object at.
@@ -781,8 +788,12 @@
if (![self canAdd])
return;
if (_clearsFilterPredicateOnInsertion)
var willClearPredicate = NO;
if (_clearsFilterPredicateOnInsertion && _filterPredicate)
{
[self willChangeValueForKey:@"filterPredicate"];
willClearPredicate = YES;
}
[self willChangeValueForKey:@"content"];
@@ -790,10 +801,17 @@
See _disableSetContent explanation in addObject:.
*/
_disableSetContent = YES;
[_contentObject insertObject:anObject atIndex:anIndex];
// The atArrangedObjectIndex: part of this method's name only refers to where the
// object goes in arrangedObjects, not in the content array. So use addObject:,
// not insertObject:atIndex: here for speed.
[_contentObject addObject:anObject];
// Allow handlesContentAsCompoundValue reverse sets to trigger.
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO;
if (_clearsFilterPredicateOnInsertion)
if (willClearPredicate)
[self __setFilterPredicate:nil];
[[self arrangedObjects] insertObject:anObject atIndex:anIndex];
@@ -809,7 +827,7 @@
[self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]];
[self didChangeValueForKey:@"content"];
if (_clearsFilterPredicateOnInsertion)
if (willClearPredicate)
[self didChangeValueForKey:@"filterPredicate"];
}
@@ -820,26 +838,28 @@
*/
- (void)removeObject:(id)object
{
[self willChangeValueForKey:@"content"];
[self willChangeValueForKey:@"content"];
/*
See _disableSetContent explanation in addObject:.
*/
_disableSetContent = YES;
[_contentObject removeObject:object];
_disableSetContent = NO;
// See _disableSetContent explanation in addObject:.
_disableSetContent = YES;
if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
{
[_contentObject removeObject:object];
// Allow handlesContentAsCompoundValue reverse sets to trigger.
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO;
if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
{
// selectionIndexes change notification will be fired as a result of the
// content change. Don't fire manually.
var pos = [_arrangedObjects indexOfObject:object];
[_arrangedObjects removeObjectAtIndex:pos];
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:-1];
}
}
[self didChangeValueForKey:@"content"];
[self didChangeValueForKey:@"content"];
}
/*!
@@ -875,16 +895,61 @@
*/
- (void)remove:(id)sender
{
[self removeObjects:[[self arrangedObjects] objectsAtIndexes:[self selectionIndexes]]];
[self removeObjectsAtArrangedObjectIndexes:_selectionIndexes];
}
/*!
Removes the objects at the specified indexes in the controller's arranged objects from the content array.
@param CPIndexSet indexes - indexes of the objects to remove.
*/
- (void)removeObjectsAtArrangedObjectIndexes:(CPIndexSet)indexes
- (void)removeObjectsAtArrangedObjectIndexes:(CPIndexSet)anIndexSet
{
[self _removeObjects:[[self arrangedObjects] objectsAtIndexes:indexes]];
[self willChangeValueForKey:@"content"];
/*
See _disableSetContent explanation in addObject:.
*/
_disableSetContent = YES;
var arrangedObjects = [self arrangedObjects],
index = [anIndexSet lastIndex],
position = CPNotFound,
newSelectionIndexes = [_selectionIndexes copy];
while (index !== CPNotFound)
{
var object = [arrangedObjects objectAtIndex:index];
// First try the simple case which should work if there are no sort descriptors.
if ([_contentObject objectAtIndex:index] === object)
[_contentObject removeObjectAtIndex:index];
else
{
// Since we don't have a reverse mapping between the sorted order and the
// unsorted one, we'll just simply have to remove an arbitrary pointer. It might
// be the 'wrong' one - as in not the one the user selected - but the wrong
// one is still just another pointer to the same object, so the user will not
// be able to see any difference.
contentIndex = [_contentObject indexOfObjectIdenticalTo:object];
[_contentObject removeObjectAtIndex:contentIndex];
}
[arrangedObjects removeObjectAtIndex:index];
// Deselect this row if it was selected, and either way shift all selection indexes
// following it up by 1.
[newSelectionIndexes removeIndex:index];
[newSelectionIndexes shiftIndexesStartingAtIndex:index by:-1];
index = [anIndexSet indexLessThanIndex:index];
}
// Allow handlesContentAsCompoundValue reverse sets to trigger.
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO;
// This will automatically handle the avoidsEmptySelection case.
[self __setSelectionIndexes:newSelectionIndexes];
[self didChangeValueForKey:@"content"];
}
/*!
@@ -904,6 +969,8 @@
[contentArray addObject:[objects objectAtIndex:i]];
[self setContent:contentArray];
// Allow handlesContentAsCompoundValue reverse sets to trigger.
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
}
/*!
@@ -922,11 +989,13 @@
{
[self willChangeValueForKey:@"content"];
/*
See _disableSetContent explanation in addObject:.
*/
// See _disableSetContent explanation in addObject:.
_disableSetContent = YES;
[_contentObject removeObjectsInArray:objects];
// Allow handlesContentAsCompoundValue reverse sets to trigger.
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO;
var arrangedObjects = [self arrangedObjects],
@@ -985,12 +1054,53 @@
var destination = [_info objectForKey:CPObservedObjectKey],
keyPath = [_info objectForKey:CPObservedKeyPathKey],
options = [_info objectForKey:CPOptionsKey],
isCompound = [self handlesContentAsCompoundValue];
if (!isCompound)
{
newValue = [destination mutableArrayValueForKeyPath:keyPath];
}
else
{
// handlesContentAsCompoundValue == YES so we cannot just set up a proxy.
// Every read and every write must go through transformValue and
// reverseTransformValue, and the resulting object cannot be described by
// a key path.
newValue = [destination valueForKeyPath:keyPath];
}
newValue = [self transformValue:newValue withOptions:options];
if (isCompound)
{
// Make sure we can edit our copy of the content. TODO In Cocoa, this copy
// appears to be deferred until the array actually needs to be edited.
newValue = [newValue mutableCopy];
}
[_source setValue:newValue forKey:aBinding];
}
- (void)_contentArrayDidChange
{
// When handlesContentAsCompoundValue == YES, it is not sufficient to modify the content object
// in place because what we are holding is an array 'unwrapped' from a compound value by
// a value transformer. So when we modify it we need a reverse set and transform to create
// a new compound value.
//
// (The Cocoa documentation on the subject is not very clear but after substantial
// experimentation this seems both reasonable and compliant.)
if ([self handlesContentAsCompoundValue])
{
var destination = [_info objectForKey:CPObservedObjectKey],
keyPath = [_info objectForKey:CPObservedKeyPathKey];
[self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
[self reverseSetValueFor:@"contentArray"];
[self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
}
}
@end
var CPArrayControllerAvoidsEmptySelection = @"CPArrayControllerAvoidsEmptySelection",
+62
View File
@@ -0,0 +1,62 @@
/*
* CPAttributedString+Additions.j
* AppKit
*
* Created by Randy Luecke
* Copyright 2011, RCLConcepts, LLC.
*
* 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
*/
/*
AppKit adds two methods to the CPAttributedString class to support drawing string directly in an CPView.
AppKit also adds similar methods to CPString.
*/
@implementation CPAttributedString (AppKitAdditions)
/*!
Draws a string in the current graphics context.
This method and draws the reciver on a single "infinately long" line.
@param aPoint - The starting point to draw the string
*/
- (void)drawAtPoint:(CGPoint)aPoint
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
line = CTLineCreateWithAttributedString([self copy]);
CGContextSetTextPosition(context, aPoint.x, aPoint.y);
CTLineDraw(line, context);
}
/*!
Draws a string in the current graphics context.
@param aRect - The rect for which the string should be drawn into
*/
- (void)drawInRect:(CGRect)aRect
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
frameSetter = CTFramesetterCreateWithAttributedString([self copy]),
path = CGPathCreateMutable();
CGPathAddRect(path, nil, aRect);
var frame = CTFramesetterCreateFrame(frameSetter, CPMakeRange(0, [self length]), path, nil);
CTFrameDraw(frame, context);
}
@end
+45 -1
View File
@@ -39,6 +39,8 @@ var CPBindingOperationAnd = 0,
{
CPDictionary _info;
id _source;
JSObject _suppressedNotifications;
}
+ (void)exposeBinding:(CPString)aBinding forClass:(Class)aClass
@@ -122,6 +124,7 @@ var CPBindingOperationAnd = 0,
{
_source = aSource;
_info = [CPDictionary dictionaryWithObjects:[aDestination, aKeyPath] forKeys:[CPObservedObjectKey, CPObservedKeyPathKey]];
_suppressedNotifications = {};
if (options)
[_info setObject:options forKey:CPOptionsKey];
@@ -169,6 +172,10 @@ var CPBindingOperationAnd = 0,
if (!changes)
return;
var objectSuppressions = _suppressedNotifications[[anObject UID]];
if (objectSuppressions && objectSuppressions[aKeyPath])
return;
[self setValueFor:context];
}
@@ -226,6 +233,44 @@ var CPBindingOperationAnd = 0,
return [[options objectForKey:CPContinuouslyUpdatesValueBindingOption] boolValue];
}
- (BOOL)handlesContentAsCompoundValue
{
var options = [_info objectForKey:CPOptionsKey];
return [[options objectForKey:CPHandlesContentAsCompoundValueBindingOption] boolValue];
}
/*!
Use this to avoid reacting to the notifications coming out of a reverseTransformedValue:.
*/
- (void)suppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
{
if (!anObject)
return;
var uid = [anObject UID],
objectSuppressions = _suppressedNotifications[uid];
if (!objectSuppressions)
_suppressedNotifications[uid] = objectSuppressions = {};
objectSuppressions[aKeyPath] = YES;
}
/*!
Use this to cancel suppressSpecificNotificationFromObject:keyPath:.
*/
- (void)unsuppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
{
if (!anObject)
return;
var uid = [anObject UID],
objectSuppressions = _suppressedNotifications[uid];
if (!objectSuppressions)
return;
delete objectSuppressions[aKeyPath];
}
@end
@implementation CPObject (KeyValueBindingCreation)
@@ -235,7 +280,6 @@ var CPBindingOperationAnd = 0,
[CPBinder exposeBinding:aBinding forClass:[self class]];
}
+ (Class)_binderClassForBinding:(CPString)theBinding
{
return [CPBinder class];
+9
View File
@@ -223,6 +223,15 @@ var _CPLevelIndicatorBezelColor = nil,
return _isEditable;
}
- (CPView)hitTest:(CPPoint)aPoint
{
// Don't swallow clicks when displayed in a table.
if (![self isEditable])
return nil;
return [super hitTest:aPoint];
}
- (void)mouseDown:(CPEvent)anEvent
{
if (![self isEditable] || ![self isEnabled])
+12 -1
View File
@@ -498,7 +498,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
- (CPString)description
{
return "<_CPObservableArray: "+[super description]+" >";
return "<_CPObservableArray: " + [super description] + " >";
}
- (id)initWithArray:(CPArray)anArray
@@ -710,6 +710,17 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
{
[[_controller selectedObjects] setValue:theValue forKeyPath:theKeyPath];
[_cachedValues removeObjectForKey:theKeyPath];
// Allow handlesContentAsCompoundValue to work, based on observation of Cocoa's
// NSArrayController - when handlesContentAsCompoundValue and setValue:forKey:@"selection.X"
// is called, the array controller causes the compound value to be rewritten if
// handlesContentAsCompoundValue == YES. Note that
// A) this doesn't use observation (observe: X is not visible in backtraces)
// B) it only happens through the selection proxy and not on arrangedObject.X, content.X
// or even selectedObjects.X.
// FIXME The main code for this should somehow be in CPArrayController and also work
// for table based row edits.
[[CPBinder getBinding:@"contentArray" forObject:_controller] _contentArrayDidChange];
}
- (void)setValue:(id)theValue forKey:(CPString)theKeyPath
+72
View File
@@ -0,0 +1,72 @@
/*
* CPString+Additions.j
* AppKit
*
* Created by Randy Luecke
* Copyright 2011, RCLConcepts, LLC.
*
* 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
*/
/*
AppKit adds two methods to the CPString class to support drawing string directly in an CPView.
AppKit also adds similar methods to CPAttributedString.
The two drawing methods draw a string object with a single set of attributes that apply to the entire string.
To draw a string with multiple attributes, such as multiple text fonts, you must use an attributed string.
*/
@implementation CPString (AppKitAdditions)
/*!
Draws a string in the current graphics context.
This method applies the attributes to the entier string
and displays it on a single "infinately long" line.
@param aPoint - The starting point to draw the string
@param attributes - the dictionary of attributes to apply to the string
*/
- (void)drawAtPoint:(CGPoint)aPoint withAttributes:(CPDictionary)attributes
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
run = _CTRunCreate([self copy], attributes);
CGContextSetTextPosition(context, aPoint.x, aPoint.y);
CTRunDraw(run, context, nil);
}
/*!
Draws a string in the current graphics context.
This method applies the attributes to the entier string
and displays it within the given rect.
@param aRect - The rect for which the string should be drawn into
@param attributes - the dictionary of attributes to apply to the string
*/
- (void)drawInRect:(CGRect)aRect withAttributes:(CPDictionary)attributes
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
string = [[CPAttributedString alloc] initWithString:self attributes:attributes];
frameSetter = CTFramesetterCreateWithAttributedString(string),
path = CGPathCreateMutable();
CGPathAddRect(path, nil, aRect);
var frame = CTFramesetterCreateFrame(frameSetter, CPMakeRange(0, [string length]), path, nil);
CTFrameDraw(frame, context);
}
@end
+1 -1
View File
@@ -1015,7 +1015,7 @@ var TOP_MARGIN = 5.0,
_labelSize = [_labelField frame].size;
_minSize = CGSizeMake(MAX(_labelSize.width, minSize.width), _labelSize.height + minSize.height + LABEL_MARGIN + TOP_MARGIN);
_maxSize = CGSizeMake(MAX(_labelSize.width, minSize.width), 100000000.0);
_maxSize = CGSizeMake(MIN(_labelSize.width, maxSize.width), 100000000.0);
[_toolbar tile];
}
+99
View File
@@ -0,0 +1,99 @@
/*
* 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);
}
// FIXME: these are hacks that override the default behavior.
function CGContextSetFillColor(/* CGContext */ aContext, /* CPColor */ aColor)
{
aContext.fillStyle = [aColor cssString];
aContext._CPColor = aColor;
}
function CGContextGetFillColor(/* CGContext */ aContext)
{
return aContext._CPColor;
}
+192
View File
@@ -0,0 +1,192 @@
/*
* CTFrame.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
*/
@import "CTLine.j"
kCTFrameProgressionTopToBottom = 0;
kCTFrameProgressionRightToLeft = 1;
kCTFrameProgressionAttributeName = @"kCTFrameProgressionAttributeName";
function _CTFrameCreate(aPath, attributes, lines, attributedString)
{
return {
path: aPath,
attributes: attributes,
lines: lines,
string: attributedString
};
}
_CTFrameCreate.displayName = @"_CTFrameCreate";
/*!
Returns the range of the frame based on the original string
FIX ME: This implementation is wrong
*/
function CTFrameGetStringRange(/* CTFrame */ aFrame)
{
return CPMakeRange();
}
CTFrameGetStringRange.displayName = @"CTFrameGetStringRange";
/*!
Returns a range object with the visisble characters
FIX ME: THis implementation is wrong
*/
function CTFrameGetVisibleStringRange(/* CTFrame */ aFrame)
{
return CPMakeRange();
}
CTFrameGetVisibleStringRange.displayName = @"CTFrameGetVisibleStringRange";
/*!
Returns the path for the frame.
*/
function CTFrameGetPath(/* CTFrame */ aFrame)
{
return aFrame.path;
}
CTFrameGetPath.displayName = @"CTFrameGetPath";
/*!
Returns a dictionary of attributes for the frame.
*/
function CTFrameGetFrameAttributes(/* CTFrame */ aFrame)
{
return aFrame.frameAttributes;
}
CTFrameGetFrameAttributes.displayName = @"CTFrameGetFrameAttributes";
/*!
Returns the array containing CTLines that make up the frame
*/
function CTFrameGetLines(/* CTFrame */ aFrame)
{
return aFrame.lines;
}
CTFrameGetLines.displayName = @"CTFrameGetLines";
/*!
Returns an array of CGPoints for the origin of each CTLine in the frame
*/
function CTFrameGetLineOrigins(/* CTFrame */ aFrame, /* CPRange */ aRange)
{
var results = [],
lines = aRange ? CTFrameGetLinesForRange(aFrame, aRange) : aFrame.lines;
for (var i = -1, count = lines.length; ++i < count;)
results.push(lines[i].origin);
return results;
}
CTFrameGetLineOrigins.displayName = @"CTFrameGetLineOrigins";
/*!
Returns an array of CTLines for a given range.
Divergent from Cocoa and expensive.
*/
function CTFrameGetLinesForRange(/* CTFrame */ aFrame, /* CPRange */ lhs)
{
var lines = aFrame.lines, results = [];
for (var i = -1, count = lines.length; ++i < count;)
{
var line = lines[i],
rhs = CTLineGetStringRange(line);
if ((CPMaxRange(lhs) < rhs.location || CPMaxRange(rhs) < lhs.location) && result.length)
break;
if (lhs.location >= rhs.location && rhs.location <= CPMaxRange(lhs) && CPMaxRange(rhs) > lhs.location)
results.push(line);
}
return results;
}
CTFrameGetLinesForRange.displayName = @"CTFrameGetLinesForRange";
/*!
Returns a CPRange
This is divergent from Cocoa. It's a convenience method used in CPTextView.
*/
function CTFrameGetRangeForPoint(/* CTFrame */ aFrame, /* CGPoint */ aPoint)
{
var lines = aFrame.lines, y = aPoint.y;
for (var i = -1, count = lines.length; ++i < count;)
{
var line = lines[i],
bounds = CTLineGetImageBounds(line),
lineY = line._startPosition.y;
if (y >= lineY && y < bounds.size.height + lineY)
{
// FIXME: we seem to be doing stuff like this with some frequency;
// Maybe it should be normalized at an earlier point.
var index = CTLineGetStringIndexForPosition(line, aPoint),
range = CTLineGetStringRange(line);
range.location += index;
range.length = 0;
return range;
}
}
}
CTFrameGetRangeForPoint.displayName = @"CTFrameGetRangeForPoint";
/*!
Draws the frame to the graphics context.
*/
function CTFrameDraw(/* CTFrame */ aFrame, /* CGContext */ aContext)
{
var origin = aFrame.path.start,
lines = aFrame.lines;
CGContextSetTextPosition(aContext, origin.x, origin.y);
for (var i = -1, count = lines.length; ++i < count;)
{
var line = lines[i],
alignment = [line.string attribute:@"alignment" atIndex:0 effectiveRange:CPMakeRange(0, 0)],
position = CGContextGetTextPosition(aContext);
if (alignment === CPRightTextAlignment)
line.origin = CGPointMake((aFrame.path.elements[1].x - origin.x * 2) - CTLineGetTypographicBounds(line).width, position.y);
else if (alignment === CPCenterTextAlignment)
line.origin = CGPointMake(((aFrame.path.elements[1].x - origin.x) / 2) - CTLineGetTypographicBounds(line).width / 2, position.y);
else
line.origin = CGPointMake(origin.x, position.y);
CGContextSetTextPosition(aContext, line.origin.x, line.origin.y);
CTLineDraw(line, aContext);
}
}
CTFrameDraw.displayName = @"CTFrameDraw";
+122
View File
@@ -0,0 +1,122 @@
/*
* CTFramesetter.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
*/
@import "CTFrame.j"
@import "CTTypesetter.j"
/*!
Creates a typesetter with a given CPAttributedString
*/
function CTFramesetterCreateWithAttributedString(/* CPAttributedString */ aString)
{
return {
string: aString,
typesetter: CTTypesetterCreateWithAttributedString(aString)
};
}
CTFramesetterCreateWithAttributedString.displayName = @"CTFramesetterCreateWithAttributedString";
/*!
Creates a CTFrame with a given typesetter, range, path, and attributes
*/
function CTFramesetterCreateFrame(/* CTFramesetter */ aFramesetter, /* CPRange */ aRange, /* CGPath */ aPath, /* CPDictionary */ frameAttributes)
{
if (aFramesetter._cachedFrame && [aFramesetter._cachedAttributes isEqual:frameAttributes])
return aFramesetter._cachedFrame;
var attributedString = aRange ? [aFramesetter.string attributedSubstringFromRange:aRange] : aFramesetter.string,
nonattributedString = [attributedString string],
splitLines = nonattributedString.split(/\n|\r/g),
lines = [];
var index = 0;
for (var i = -1, count = splitLines.length; ++i < count;)
{
var length = splitLines[i].length;
if (i !== count - 1)
++length;
var range = CPMakeRange(index, length),
line = CTLineCreateWithAttributedString([attributedString attributedSubstringFromRange:range]);
line.range = range; // FIXME: a couple hacks to make managing lines in CPTextView easier
line.prevLine = lastLine;
if (lastLine)
lastLine.nextLine = line;
lines.push(line);
index += length;
var lastLine = line;
}
return aFramesetter._cachedFrame = _CTFrameCreate(aPath, frameAttributes, lines);
}
CTFramesetterCreateFrame.displayName = @"CTFramesetterCreateFrame";
/*!
Returns a CTTypesetter
*/
function CTFramesetterGetTypesetter(/* CTFramesetter */ aFramesetter)
{
return aFramesetter.typesetter;
}
CTFramesetterGetTypesetter.displayName = @"CTFramesetterGetTypesetter";
/*!
Returns a CGSize object with the suggested size for a given frame.
*/
function CTFramesetterSuggestFrameSizeWithConstraints(/* CTFramesetter */ aFramesetter, /* CPRange */ aRange, /* CPDictionary */ frameAttributes, /* CGSize */ constraints, /* {CPRange} */ fitRange)
{
var frame = CTFramesetterCreateFrame(aFramesetter, aRange, null, frameAttributes),
lines = CTFrameGetLines(frame),
width = 0.0,
height = 0.0;
for (var i = -1, count = lines.length; ++i < count;)
{
var bounds = CTLineGetTypographicBounds(lines[i]);
// var bounds = CTLineGetImageBounds(lines[i], [[CPGraphicsContext currentContext] graphicsPort]).size;
width = MAX(width, bounds.width);
height += bounds.lineHeight;
// height += bounds.height;
}
return CGSizeMake(width, height);
}
CTFramesetterSuggestFrameSizeWithConstraints.displayName = @"CTFramesetterSuggestFrameSizeWithConstraints";
/*!
Returns the CPAttributedString for a given framesetter
*/
function CTFramesetterGetAttributedString(/* CTFramesetter */ aFramesetter)
{
return aFramesetter.string;
}
CTFramesetterGetAttributedString.displayName = @"CTFramesetterGetAttributedString";
+258
View File
@@ -0,0 +1,258 @@
/*
* CTLine.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
*/
@import "CTRun.j"
kCTLineTruncationStart = 0;
kCTLineTruncationEnd = 1;
kCTLineTruncationMiddle = 2;
/*!
Creates a new line with a supplied CPAttributedString
*/
function CTLineCreateWithAttributedString(/* CPAttributedString */ aString)
{
var line = {
string: aString,
runs: []
};
_CTLineCreateRuns(line);
return line;
}
CTLineCreateWithAttributedString.displayName = @"CTLineCreateWithAttributedString";
/*!
Creates a new line truncated to a given width.
@param aLine - The input line
@param width - The constraining width
@param truncationToken - The characters to represent the truncation. This is usually an elipsis. If not token is given the string will just clip
FIX ME: Not implemented correctly
*/
function CTLineCreateTruncatedLine(/* CTLine */ aLine, /* float */ width, /* CTLineTruncationType */ truncationType, /* CTLine */ truncationToken)
{
return aLine;
}
CTLineCreateTruncatedLine.displayName = @"CTLineCreateTruncatedLine";
/*!
Returns a CTLine with justified text.
FIX ME: This is not implemented correctly
*/
function CTLineCreateJustifiedLine(/* CTline */ aLine, /* float */ justificationFactor, /* float */ width)
{
return aLine;
}
CTLineCreateJustifiedLine.displayName = @"CTLineCreateJustifiedLine";
/*!
Returns the number of glyphs in a given line.
*/
function CTLineGetGlyphCount(/* CTLine */ aLine)
{
return [aLine.string length];
}
CTLineGetGlyphCount.displayName = @"CTLineGetGlyphCount";
/*!
Returns the array of CTRuns that make up the line.
*/
function CTLineGetGlyphRuns(/* CTLine */ aLine)
{
return aLine.runs;
}
CTLineGetGlyphRuns.displayName = @"CTLineGetGlyphRuns";
/*!
Returns the range for which the CTLine makes up the original string
*/
function CTLineGetStringRange(/* CTLine */ aLine)
{
return CPCopyRange(aLine.range) || CPMakeRange(0, [aLine.string length])
}
CTLineGetStringRange.displayName = @"CTLineGetStringRange";
/*!
No op
*/
function CTLineGetPenOffsetForFlush(/* CTLine */ aLine, /* float */ flushFactor, /* float */ flushWidth)
{
}
CTLineGetPenOffsetForFlush.displayName = @"CTLineGetPenOffsetForFlush";
/*!
Draws the CTLine to the graphics context.
*/
function CTLineDraw(/* CTLine */ aLine, /* CGContext */ aContext)
{
var startPosition = aLine._startPosition = CGContextGetTextPosition(aContext),
height = CTLineGetImageBounds(aLine, aContext).size.height;
// FIXME: This is WRONG. This NEEDS to be in CGContext.
CGContextSetTextPosition(aContext, startPosition.x, startPosition.y + height * 0.5);
var runs = aLine.runs;
for (var i = -1, count = runs.length; ++i < count;)
CTRunDraw(runs[i], aContext);
CGContextSetTextPosition(aContext, startPosition.x, startPosition.y + height);
}
CTLineDraw.displayName = @"CTLineDraw";
/*!
Calcaulates the image bounds for a line.
*/
function CTLineGetImageBounds(/* CTLine */ aLine, /* CGContext */ aContext)
{
if (aLine._imageBounds)
return aLine._imageBounds;
var runs = aLine.runs,
width = 0.0,
height = 0.0;
for (var i = -1, count = runs.length; ++i < count;)
{
var runSize = CTRunGetImageBounds(runs[i], aContext).size;
width += runSize.width;
height = MAX(height, runSize.height);
}
return aLine._imageBounds = CGRectMake(0.0, 0.0, width, height);
}
CTLineGetImageBounds.displayName = @"CTLineGetImageBounds";
/*!
Returns a JSObject: {width: float, ascent: float, descent: float, lineHeight: float}
This method is more expensive than CTLineGetImageBounds.
*/
function CTLineGetTypographicBounds(/* CTLine */ aLine)
{
if (aLine._typographicBounds)
return aLine._typographicBounds;
var runs = aLine.runs,
width = 0.0,
ascent = 0.0,
descent = 0.0,
lineHeight = 0.0;
for (var i = -1, count = runs.length; ++i < count;)
{
var runObject = CTRunGetTypographicBounds(runs[i]);
width += runObject.width;
ascent = MAX(ascent, runObject.ascent);
descent = MAX(descent, runObject.descent);
lineHeight = MAX(lineHeight, runObject.lineHeight);
}
return aLine._typographicBounds = {
width: width,
ascent: ascent,
descent: descent,
lineHeight: lineHeight
};
}
CTLineGetTypographicBounds.displayName = @"CTLineGetTypographicBounds";
/*!
Returns the index of the line based on the original string
*/
function CTLineGetStringIndexForPosition(/* CTLine */ aLine, /* CGPoint */ aPoint)
{
var runs = aLine.runs, x = aPoint.x, index = 0;
for (var i = -1, count = runs.length; ++i < count;)
{
var run = runs[i],
origins = run.glyphOrigins;
for (var j = -1, jcount = origins.length; ++j < jcount;)
{
var origin = origins[j], next;
if (j < jcount - 1)
next = origins[j + 1];
else if (i < count - 1)
next = runs[i + 1].glyphOrigins[0];
else
return index++;
if (x <= (next.x - origin.x) / 2 + origin.x)
return index;
index++;
}
}
}
CTLineGetStringIndexForPosition.displayName = @"CTLineGetStringIndexForPosition";
/*!
Returns the offset corresponding to a string index,
this works well for for movement between adjacent lines or for drawing a custom caret.
*/
function CTLineGetOffsetForStringIndex(/* CTLine */ aLine, /* int */ anIndex, /* float */ secondaryOffset)
{
var runs = aLine.runs;
for (var i = -1, count = runs.length; ++i < count;)
{
var run = runs[i], runRange = run.range;
if (CPLocationInRange(anIndex, runRange))
return run.glyphOrigins[anIndex - runRange.location];
}
}
CTLineGetOffsetForStringIndex.displayName = @"CTLineGetOffsetForStringIndex";
function _CTLineCreateRuns(aLine)
{
var string = aLine.string,
runs = aLine.runs,
rangeEntries = string._rangeEntries;
for (var i = -1, count = rangeEntries.length; ++i < count;)
{
var rangeEntry = rangeEntries[i],
range = rangeEntry.range,
rangeString = [[string string] substringWithRange:range],
attributes = rangeEntry.attributes;
var run = _CTRunCreate(rangeString, attributes);
run.range = range;
runs.push(run);
}
}
_CTLineCreateRuns.displayName = @"_CTLineCreateRuns";
+290
View File
@@ -0,0 +1,290 @@
/*
* CTRun.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
*/
kCTRunStatusNoStatus = 0;
kCTRunStatusRightToLeft = 1 << 0;
kCTRunStatusNonMonotonic = 1 << 1;
kCTRunStatusNonIdentityMatrix = 1 << 2;
/*!
A CTRun represents a span of characters with common attribtues.
*/
function _CTRunCreate(glyphs, attributes)
{
return {
glyphs: glyphs,
attributes: attributes,
status: kCTRunStatusNoStatus
};
}
_CTRunCreate.displayName = @"_CTRunCreate";
/*!
Returns the number of glyphs in the run.
*/
function CTRunGetGlyphCount(/* CTRun */ aRun)
{
return aRun.glyphs.length;
}
CTRunGetGlyphCount.displayName = @"CTRunGetGlyphCount";
/*!
Returns a CPDictionary of attributes for the CTRun
*/
function CTRunGetAttributes(/* CTRun */ aRun)
{
return aRun.attributes;
}
CTRunGetAttributes.displayName = @"CTRunGetAttributes";
/*!
Returns a CTRunStatus
CTRuns have status that can be used to speed up certain operations.
Possible values:
@code
kCTRunStatusNoStatus
kCTRunStatusRightToLeft
kCTRunStatusNonMonotonic
kCTRunStatusNonIdentityMatrix
@endcode
*/
function CTRunGetStatus(/* CTRun */ aRun)
{
return aRun.status || kCTRunStatusNoStatus;
}
CTRunGetStatus.displayName = @"CTRunGetStatus";
/*!
Returns an array of CGGlyphs
FIX ME: Not implemented correctly
*/
function CTRunGetGlyphs(/* CTRun */ aRun, /* CPRange */ aRange)
{
if (!aRun.glyphs)
aRun.glyphs = [];
return aRun.glyphs;
}
CTRunGetGlyphs.displayName = @"CTRunGetGlyphs";
/*!
Returns an array of CGPoints
FIX ME: Not implemented correctly
*/
function CTRunGetPositions(/* CTRun */ aRun, /* CPRange */ aRange)
{
if (!aRun.positions)
aRun.positions = [];
return aRun.positions;
}
CTRunGetPositions.displayName = @"CTRunGetPositions";
/*!
Returns an array of CGSizes
FIX ME: Not implemented correctly
*/
function CTRunGetAdvances(/* CTRun */ aRun, /* CPRange */ aRange)
{
if (!aRun.advances)
aRun.advances = [];
return aRun.advances;
}
CTRunGetAdvances.displayName = @"CTRunGetAdvances";
/*!
Returns an array of indexes.
FIX ME: Not implemented correctly
*/
function CTRunGetStringIndices(/* CTRun */ aRun, /* CPRange */ aRange)
{
if (!aRun.stringIndices)
aRun.stringIndices = [];
return aRun.stringIndices;
}
CTRunGetStringIndices.displayName = @"CTRunGetStringIndices";
/*!
Returns a CPRange containing the location of the run in the parent string
*/
function CTRunGetStringRange(/* CTRun */ aRun)
{
return aRun.range;
}
CTRunGetStringRange.displayName = @"CTRunGetStringRange";
/*!
Returns a JSObject: {width: float, ascender: float, descender: float, lineHeight: float}
More expensive
*/
function CTRunGetTypographicBounds(/* CTRun */ aRun, /* CPRange */ aRange)
{
if (aRun._typographicBounds)
return aRun._typographicBounds;
var attributes = aRun.attributes,
font = [attributes valueForKey:@"font"],
string = _CTRunStringForRange(aRun, aRange);
return aRun._typographicBounds = {
width: [string sizeWithFont:font].width, // FIXME: account for tabs
ascender: [font ascender],
descender: [font descender],
lineHeight: [font defaultLineHeightForFont]
};
}
CTRunGetTypographicBounds.displayName = @"CTRunGetTypographicBounds";
/*!
Returns a CGRect
Cheap
*/
function CTRunGetImageBounds(/* CTRun */ aRun, /* CGContext */ aContext, /* CPRange */ aRange)
{
if (aRun._imageBounds)
return aRun._imageBounds;
_CTRunPrepareDraw(aRun, aContext);
var string = _CTRunStringForRange(aRun, aRange),
width = aContext.measureText(string).width,
height = [CGContextGetFont(aContext) defaultLineHeightForFont];
_CTRunUnprepareDraw(aRun, aContext);
return aRun._imageBounds = CGRectMake(0.0, 0.0, width, height);
}
CTRunGetImageBounds.displayName = @"CTRunGetImageBounds";
// CGAffineTransform
function CTRunGetTextMatrix(/* CTRun */ aRun)
{
}
CTRunGetTextMatrix.displayName = @"CTRunGetTextMatrix";
/*!
Draws the run to the context
*/
function CTRunDraw(/* CTRun */ aRun, /* CGContext */ aContext, /* CPRange */ aRange)
{
_CTRunPrepareDraw(aRun, aContext);
var string = aRange ? [aRun.glyphs substringWithRange:aRange] : aRun.glyphs;
_CTRunDrawShadow(aRun, aContext, string);
var origins = aRun.glyphOrigins = [];
for (var i = -1, count = string.length; ++i < count;)
{
var glyph = string[i];
origins[i] = CGContextGetTextPosition(aContext);
CGContextShowText(aContext, glyph);
}
_CTRunUnprepareDraw(aRun, aContext);
}
CTRunDraw.displayName = @"CTRunDraw";
function _CTRunDrawShadow(aRun, aContext, aString)
{
var attributes = aRun.attributes,
textShadowColor = [attributes valueForKey:@"text-shadow-color"],
textShadowOffset = [attributes valueForKey:@"text-shadow-offset"];
if (textShadowColor && textShadowOffset)
{
var color = CGContextGetFillColor(aContext),
position = CGContextGetTextPosition(aContext);
CGContextSetFillColor(aContext, textShadowColor);
CGContextShowTextAtPoint(aContext, position.x + textShadowOffset.width, position.y + textShadowOffset.height, aString);
CGContextSetFillColor(aContext, color);
CGContextSetTextPosition(aContext, position.x, position.y);
}
}
_CTRunDrawShadow.displayName = @"_CTRunDrawShadow";
function _CTRunPrepareDraw(aRun, aContext)
{
var attributes = aRun.attributes,
font = [attributes valueForKey:@"font"],
color = [attributes valueForKey:@"color"];
if (font)
{
CGContextSelectFont(aContext, font);
aRun._cachedFont = CGContextGetFont(aContext);
}
if (color)
{
CGContextSetFillColor(aContext, color);
aRun._cachedColor = CGContextGetFillColor(aContext);
}
}
_CTRunPrepareDraw.displayName = @"_CTRunPrepareDraw";
function _CTRunUnprepareDraw(aRun, aContext)
{
if (aRun._cachedFont)
{
CGContextSelectFont(aContext, aRun._cachedFont);
aRun._cachedFont = nil;
}
if (aRun._cachedColor)
{
CGContextSetFillColor(aContext, aRun._cachedColor);
aRun._cachedColor = nil;
}
}
_CTRunUnprepareDraw.displayName = @"_CTRunUnprepareDraw";
function _CTRunStringForRange(aRun, aRange)
{
return (!aRange || aRange.length === 0) ? aRun.glyphs : [aRun.glyphs substringWithRange:aRange];
}
_CTRunStringForRange.displayName = @"_CTRunStringForRange";
+60
View File
@@ -0,0 +1,60 @@
/*
* CTTypesetter.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
*/
@import "CTLine.j"
kCTTypesetterOptionDisableBidiProcessing = @"kCTTypesetterOptionDisableBidiProcessing";
kCTTypesetterOptionForcedEmbeddingLevel = @"kCTTypesetterOptionForcedEmbeddingLevel";
// Returns a CTTypesetter
function CTTypesetterCreateWithAttributedString(/* CPAttributedString */ aString)
{
return CTTypesetterCreateWithAttributedStringAndOptions(aString, nil);
}
// Returns a CTTypesetter
function CTTypesetterCreateWithAttributedStringAndOptions(/* CPAttributedString */ aString, /* CPDictionary */ aDictionary)
{
return {
string: aString,
options: aDictionary
}
}
// Returns a CTLine
function CTTypesetterCreateLine(/* CTTypesetter */ aTypesetter, /* CPRange */ aRange)
{
}
// Returns an index
function CTTypesetterSuggestLineBreak(/* CTTypesetter */ aTypesetter, /* int */ startIndex, /* float */ width)
{
}
// Returns an index
function CTTypesetterSuggestClusterBreak(/* CTTypesetter */ aTypesetter, /* int */ startIndex, /* float */ width)
{
}
+29
View File
@@ -0,0 +1,29 @@
/*
* CoreText.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
*/
@import "CTFramesetter.j"
@import "CTFrame.j"
@import "CTTypesetter.j"
@import "CTLine.j"
@import "CTRun.j"
@import "CGContextText.j"
+9
View File
@@ -27,6 +27,10 @@
@import "CPRange.j"
@import "CPString.j"
CPFontAttributeName = "font";
CPForegroundColorAttributeName = "color";
/*!
@class CPAttributedString
@ingroup foundation
@@ -42,6 +46,11 @@
implements functionality from both NSAttributedString and
NSMutableAttributedString. However, to ease converting of existing
Objective-C code a CPMutableAttributedString alias to this class exists.
Below are a list of commonly used attributes:
CPFontAttributeName - Font object
CPForegroundColorAttributeName - CPColor object
*/
@implementation CPAttributedString : CPObject
{
+51
View File
@@ -0,0 +1,51 @@
/*
* CPNumberFormatter.j
* Foundation
*
* Created by Alexander Ljungberg.
* Copyright 2011, WireLoad 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
*/
@import <Foundation/CPString.j>
@import <Foundation/CPFormatter.j>
@implementation CPNumberFormatter : CPFormatter
{
}
@end
@implementation CPNumberFormatter (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
}
@end
+2 -4
View File
@@ -184,9 +184,8 @@ var StandardUserDefaults;
}
[domain setObject:anObject forKey:aKey];
[self domainDidChange:aDomain];
_searchListNeedsReload = YES;
[self domainDidChange:aDomain];
}
/*!
@@ -211,9 +210,8 @@ var StandardUserDefaults;
return;
[domain removeObjectForKey:aKey];
[self domainDidChange:aDomain];
_searchListNeedsReload = YES;
[self domainDidChange:aDomain];
}
/*!
+2
View File
@@ -21,6 +21,7 @@
*/
@import "CPArray.j"
@import "CPAttributedString.j"
@import "CPBundle.j"
@import "CPCharacterSet.j"
@import "CPCoder.j"
@@ -49,6 +50,7 @@
@import "CPNotificationCenter.j"
@import "CPNull.j"
@import "CPNumber.j"
@import "CPNumberFormatter.j"
@import "CPObject.j"
@import "CPObjJRuntime.j"
@import "CPOperation.j"
+26
View File
@@ -33,6 +33,15 @@ var CLS_CLASS = 0x1,
#define GETMETA(aClass) (ISMETA(aClass) ? aClass : aClass.isa)
#define ISINITIALIZED(aClass) GETINFO(GETMETA(aClass), CLS_INITIALIZED)
// MAXIMUM_RECURSION_CHECKS
// If defined, objj_msgSend will check for recursion deeper than MAXIMUM_RECURSION_DEPTH and
// throw an error if found. While crude, this can be helpful when your JavaScript debugger
// crashes on recursion errors (e.g. Safari) or ignores them (e.g. Chrome).
// #define MAXIMUM_RECURSION_CHECKS
#define MAXIMUM_RECURSION_DEPTH 80
GLOBAL(objj_ivar) = function(/*String*/ aName, /*String*/ aType)
{
this.name = aName;
@@ -604,6 +613,10 @@ DISPLAY_NAME(ivar_getTypeEncoding);
// Sending Messages
#ifdef MAXIMUM_RECURSION_CHECKS
var __objj_msgSend__StackDepth = 0;
#endif
GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
{
if (aReceiver == nil)
@@ -613,6 +626,13 @@ GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
CLASS_GET_METHOD_IMPLEMENTATION(var implementation, isa, aSelector);
#ifdef MAXIMUM_RECURSION_CHECKS
if (__objj_msgSend__StackDepth++ > MAXIMUM_RECURSION_DEPTH)
throw new Error("Maximum call stack depth exceeded.");
try {
#endif
switch(arguments.length)
{
case 2: return implementation(aReceiver, aSelector);
@@ -621,6 +641,12 @@ GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
}
return implementation.apply(aReceiver, arguments);
#ifdef MAXIMUM_RECURSION_CHECKS
} finally {
__objj_msgSend__StackDepth--;
}
#endif
}
DISPLAY_NAME(objj_msgSend);
+42
View File
@@ -80,6 +80,7 @@
[arrayController insertObject:object atArrangedObjectIndex:1];
[self assert:object equals:[[arrayController arrangedObjects] objectAtIndex:1]];
[self assertTrue:[[arrayController content] containsObject:object] message:@"object should be inserted into content"];
}
- (void)testAddObjectUpdatesArrangedObjectsWithoutSortDescriptors
@@ -438,6 +439,31 @@
[self assert:1 equals:[observations count] message:@"exactly 1 notification for addObject (clearsFilterPredicate YES)"];
}
/*!
Test that if there is no filter predicate to clear, insertObject:atArrangedObjectIndex: with
clearsFilterPredicate YES does not send a false filterPredicate notification.
*/
- (void)testObservationDuringInsertObject_atArrangedIndex_
{
var arrayController = [self arrayController];
[arrayController addObserver:self forKeyPath:@"filterPredicate" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:nil];
// Add something to clear.
[arrayController setFilterPredicate:[CPPredicate predicateWithFormat:@"(name != %@)", "Francisco"]];
observations = [];
var aPerson = [Employee employeeWithName:@"Alexander" department:[Department departmentWithName:@"Cosmic Path Finding"]];
[arrayController setClearsFilterPredicateOnInsertion:YES];
[self assert:0 equals:[observations count] message:@"no observations before insertObject test"];
[arrayController insertObject:aPerson atArrangedObjectIndex:1];
[self assert:1 equals:[observations count] message:@"exactly 1 notification for insertObject (clearsFilterPredicate YES)"];
// Now that the filter is already cleared, we should not get notified that it clears again on the second insert.
[arrayController insertObject:aPerson atArrangedObjectIndex:1];
[self assert:1 equals:[observations count] message:@"exactly 1 notification for insertObject x 2 (clearsFilterPredicate YES)"];
}
- (void)testCompoundKeyPaths
{
var departmentNameField = [[CPTextField alloc] init];
@@ -482,6 +508,22 @@
[self assertTrue:[[arrayController arrangedObjects] count] > 0];
}
/**
In a table with arranged contents like [1, 1, 2, 1], selecting the second '1' and removing it
should result in [1, 2, 1] - not [2]. E.g. we don't use removeObject:1 but only remove the
actually selected instance.
*/
- (void)testRemove_OneOfMultipleEqualObjects
{
var ac = [CPArrayController new],
contentArray = [1, 1, 2, 1];
[ac setContent:contentArray];
[self assert:[1, 1, 2, 1] equals:[ac arrangedObjects]];
[ac setSelectionIndexes:[CPIndexSet indexSetWithIndex:1]];
[ac remove:nil];
[self assert:[1, 2, 1] equals:[ac arrangedObjects] message:"only one copy of 1 removed + the right copy should be removed"];
}
- (void)observeValueForKeyPath:keyPath
ofObject:anActivity
change:change
+23
View File
@@ -249,6 +249,29 @@
CPLog(@"here: "+aKeyPath+" value: "+[anObject valueForKey:aKeyPath]);
}
- (void)testSuppressNotification
{
var control = [[CPTextField alloc] init],
anotherControl = [[CPTextField alloc] init];
[control setStringValue:@"brown"];
[control bind:CPValueBinding toObject:self withKeyPath:@"FOO" options:nil];
[self setValue:@"green" forKeyPath:@"FOO"];
[self assert:@"green" equals:[control stringValue] message:@"normal binding action"];
var binding = [CPBinder getBinding:CPValueBinding forObject:control];
[binding suppressSpecificNotificationFromObject:self keyPath:@"FOO"];
[self setValue:@"orange" forKeyPath:@"FOO"];
[self assert:@"green" equals:[control stringValue] message:@"binding update suppressed"];
[binding unsuppressSpecificNotificationFromObject:anotherControl keyPath:@"FOO"];
[self setValue:@"blue" forKeyPath:@"FOO"];
[self assert:@"green" equals:[control stringValue] message:@"binding update still suppressed"];
[binding unsuppressSpecificNotificationFromObject:self keyPath:@"FOO"];
[self setValue:@"octarine" forKeyPath:@"FOO"];
[self assert:@"octarine" equals:[control stringValue] message:@"binding update no longer suppressed"];
}
@end
@implementation BindingTester : CPObject
+17 -1
View File
@@ -18,6 +18,7 @@ CPUserDefaultsTestKey2 = @"KEY2";
@implementation CPUserDefaultsTest : OJTestCase
{
CPUserDefaults target;
id lastObservedCPUserDefaultsTestKey1;
}
- (void)setUp
@@ -40,7 +41,6 @@ CPUserDefaultsTestKey2 = @"KEY2";
}
- (void)testSetObjectForKey
{
[target setObject:[CPArray arrayWithObjects:@"cell1", @"cell2"] forKey:CPUserDefaultsTestKey1];
@@ -133,7 +133,23 @@ CPUserDefaultsTestKey2 = @"KEY2";
[target removeObjectForKey:CPUserDefaultsTestKey1];
[self assert:[target dataForKey:CPUserDefaultsTestKey1] equals:nil];
}
- (void)testNotification
{
[target setDouble:5.0 forKey:CPUserDefaultsTestKey1];
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:CPUserDefaultsDidChangeNotification object:target];
// Prod the class to resolve any outstanding _searchListNeedsReload's.
[self assert:5.0 equals:[target objectForKey:CPUserDefaultsTestKey1] message:"normal read"];
[target setDouble:10.0 forKey:CPUserDefaultsTestKey1];
[self assert:[target objectForKey:CPUserDefaultsTestKey1] equals:lastObservedCPUserDefaultsTestKey1 message:"should observe new value"];
}
- (void)userDefaultsDidChange:(CPNotification)aNotification
{
lastObservedCPUserDefaultsTestKey1 = [target objectForKey:CPUserDefaultsTestKey1];
}
@end
+1
View File
@@ -46,6 +46,7 @@
@import "NSMenu.j"
@import "NSMenuItem.j"
@import "NSNibConnector.j"
@import "NSNumberFormatter.j"
@import "NSObjectController.j"
@import "NSOutlineView.j"
@import "NSPopUpButton.j"
-6
View File
@@ -3,9 +3,3 @@
}
@end
@implementation NSNumberFormatter : CPObject
{
}
@end
+1
View File
@@ -58,6 +58,7 @@
_numberOfMajorTickMarks = [cell numberOfMajorTickMarks];
[self setEditable:[cell isEditable]];
[self setEnabled:[cell isEnabled]];
[self setContinuous:[cell isContinuous]];
return self;
+54
View File
@@ -0,0 +1,54 @@
/*
* NSNumberFormatter.j
* nib2cib
*
* Created by Alexander Ljungberg.
* Copyright 2011, WireLoad 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
*/
@import <Foundation/CPNumberFormatter.j>
@implementation CPNumberFormatter (CPCoding)
- (id)NS_initWithCoder:(CPCoder)aCoder
{
self = [super init];
if (self)
{
}
return self;
}
@end
@implementation NSNumberFormatter : CPNumberFormatter
{
}
- (id)initWithCoder:(CPCoder)aCoder
{
return [self NS_initWithCoder:aCoder];
}
- (Class)classForKeyedArchiver
{
return [CPNumberFormatter class];
}
@end
+5 -2
View File
@@ -51,18 +51,21 @@ function main(args)
{
try
{
var options = parseOptions(args);
var options = parseOptions(args),
ret = 0;
if (options.watch)
watch(options);
else
convert(options);
if (!convert(options))
ret = 2;
}
catch (anException)
{
CPLog.fatal(exceptionReason(anException));
OS.exit(1);
}
OS.exit(ret);
}
function convert(options, inputFile)
Regular → Executable
View File