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 "CPAnimation.j"
@import "CPApplication.j" @import "CPApplication.j"
@import "CPArrayController.j" @import "CPArrayController.j"
@import "CPAttributedString+Additions.j"
@import "CPBezierPath.j" @import "CPBezierPath.j"
@import "CPBox.j" @import "CPBox.j"
@import "CPBrowser.j" @import "CPBrowser.j"
@@ -83,6 +84,7 @@
@import "CPSound.j" @import "CPSound.j"
@import "CPSplitView.j" @import "CPSplitView.j"
@import "CPStepper.j" @import "CPStepper.j"
@import "CPString+Additions.j"
@import "CPTableColumn.j" @import "CPTableColumn.j"
@import "CPTableView.j" @import "CPTableView.j"
@import "CPTabView.j" @import "CPTabView.j"
+132 -22
View File
@@ -313,6 +313,8 @@
// Don't use [super setContent:] as that would fire the contentObject change. // Don't use [super setContent:] as that would fire the contentObject change.
// We need to be in control of when notifications fire. // 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; _contentObject = value;
if (_clearsFilterPredicateOnInsertion && _filterPredicate != nil) if (_clearsFilterPredicateOnInsertion && _filterPredicate != nil)
@@ -737,6 +739,10 @@
*/ */
_disableSetContent = YES; _disableSetContent = YES;
[_contentObject addObject:object]; [_contentObject addObject:object];
// Allow handlesContentAsCompoundValue reverse sets to trigger.
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO; _disableSetContent = NO;
if (willClearPredicate) 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 id anObject - The object to add to the collection.
@param int anIndex - The index to insert the object at. @param int anIndex - The index to insert the object at.
@@ -781,8 +788,12 @@
if (![self canAdd]) if (![self canAdd])
return; return;
if (_clearsFilterPredicateOnInsertion) var willClearPredicate = NO;
if (_clearsFilterPredicateOnInsertion && _filterPredicate)
{
[self willChangeValueForKey:@"filterPredicate"]; [self willChangeValueForKey:@"filterPredicate"];
willClearPredicate = YES;
}
[self willChangeValueForKey:@"content"]; [self willChangeValueForKey:@"content"];
@@ -790,10 +801,17 @@
See _disableSetContent explanation in addObject:. See _disableSetContent explanation in addObject:.
*/ */
_disableSetContent = YES; _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; _disableSetContent = NO;
if (_clearsFilterPredicateOnInsertion) if (willClearPredicate)
[self __setFilterPredicate:nil]; [self __setFilterPredicate:nil];
[[self arrangedObjects] insertObject:anObject atIndex:anIndex]; [[self arrangedObjects] insertObject:anObject atIndex:anIndex];
@@ -809,7 +827,7 @@
[self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]]; [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]];
[self didChangeValueForKey:@"content"]; [self didChangeValueForKey:@"content"];
if (_clearsFilterPredicateOnInsertion) if (willClearPredicate)
[self didChangeValueForKey:@"filterPredicate"]; [self didChangeValueForKey:@"filterPredicate"];
} }
@@ -820,26 +838,28 @@
*/ */
- (void)removeObject:(id)object - (void)removeObject:(id)object
{ {
[self willChangeValueForKey:@"content"]; [self willChangeValueForKey:@"content"];
/* // See _disableSetContent explanation in addObject:.
See _disableSetContent explanation in addObject:. _disableSetContent = YES;
*/
_disableSetContent = YES;
[_contentObject removeObject:object];
_disableSetContent = NO;
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 // selectionIndexes change notification will be fired as a result of the
// content change. Don't fire manually. // content change. Don't fire manually.
var pos = [_arrangedObjects indexOfObject:object]; var pos = [_arrangedObjects indexOfObject:object];
[_arrangedObjects removeObjectAtIndex:pos]; [_arrangedObjects removeObjectAtIndex:pos];
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:-1]; [_selectionIndexes shiftIndexesStartingAtIndex:pos by:-1];
} }
[self didChangeValueForKey:@"content"]; [self didChangeValueForKey:@"content"];
} }
/*! /*!
@@ -875,16 +895,61 @@
*/ */
- (void)remove:(id)sender - (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. 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. @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]]; [contentArray addObject:[objects objectAtIndex:i]];
[self setContent:contentArray]; [self setContent:contentArray];
// Allow handlesContentAsCompoundValue reverse sets to trigger.
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
} }
/*! /*!
@@ -922,11 +989,13 @@
{ {
[self willChangeValueForKey:@"content"]; [self willChangeValueForKey:@"content"];
/* // See _disableSetContent explanation in addObject:.
See _disableSetContent explanation in addObject:.
*/
_disableSetContent = YES; _disableSetContent = YES;
[_contentObject removeObjectsInArray:objects]; [_contentObject removeObjectsInArray:objects];
// Allow handlesContentAsCompoundValue reverse sets to trigger.
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO; _disableSetContent = NO;
var arrangedObjects = [self arrangedObjects], var arrangedObjects = [self arrangedObjects],
@@ -985,12 +1054,53 @@
var destination = [_info objectForKey:CPObservedObjectKey], var destination = [_info objectForKey:CPObservedObjectKey],
keyPath = [_info objectForKey:CPObservedKeyPathKey], keyPath = [_info objectForKey:CPObservedKeyPathKey],
options = [_info objectForKey:CPOptionsKey], options = [_info objectForKey:CPOptionsKey],
isCompound = [self handlesContentAsCompoundValue];
if (!isCompound)
{
newValue = [destination mutableArrayValueForKeyPath:keyPath]; 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]; 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]; [_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 @end
var CPArrayControllerAvoidsEmptySelection = @"CPArrayControllerAvoidsEmptySelection", 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; CPDictionary _info;
id _source; id _source;
JSObject _suppressedNotifications;
} }
+ (void)exposeBinding:(CPString)aBinding forClass:(Class)aClass + (void)exposeBinding:(CPString)aBinding forClass:(Class)aClass
@@ -122,6 +124,7 @@ var CPBindingOperationAnd = 0,
{ {
_source = aSource; _source = aSource;
_info = [CPDictionary dictionaryWithObjects:[aDestination, aKeyPath] forKeys:[CPObservedObjectKey, CPObservedKeyPathKey]]; _info = [CPDictionary dictionaryWithObjects:[aDestination, aKeyPath] forKeys:[CPObservedObjectKey, CPObservedKeyPathKey]];
_suppressedNotifications = {};
if (options) if (options)
[_info setObject:options forKey:CPOptionsKey]; [_info setObject:options forKey:CPOptionsKey];
@@ -169,6 +172,10 @@ var CPBindingOperationAnd = 0,
if (!changes) if (!changes)
return; return;
var objectSuppressions = _suppressedNotifications[[anObject UID]];
if (objectSuppressions && objectSuppressions[aKeyPath])
return;
[self setValueFor:context]; [self setValueFor:context];
} }
@@ -226,6 +233,44 @@ var CPBindingOperationAnd = 0,
return [[options objectForKey:CPContinuouslyUpdatesValueBindingOption] boolValue]; 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 @end
@implementation CPObject (KeyValueBindingCreation) @implementation CPObject (KeyValueBindingCreation)
@@ -235,7 +280,6 @@ var CPBindingOperationAnd = 0,
[CPBinder exposeBinding:aBinding forClass:[self class]]; [CPBinder exposeBinding:aBinding forClass:[self class]];
} }
+ (Class)_binderClassForBinding:(CPString)theBinding + (Class)_binderClassForBinding:(CPString)theBinding
{ {
return [CPBinder class]; return [CPBinder class];
+9
View File
@@ -223,6 +223,15 @@ var _CPLevelIndicatorBezelColor = nil,
return _isEditable; 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 - (void)mouseDown:(CPEvent)anEvent
{ {
if (![self isEditable] || ![self isEnabled]) if (![self isEditable] || ![self isEnabled])
+12 -1
View File
@@ -498,7 +498,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
- (CPString)description - (CPString)description
{ {
return "<_CPObservableArray: "+[super description]+" >"; return "<_CPObservableArray: " + [super description] + " >";
} }
- (id)initWithArray:(CPArray)anArray - (id)initWithArray:(CPArray)anArray
@@ -710,6 +710,17 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
{ {
[[_controller selectedObjects] setValue:theValue forKeyPath:theKeyPath]; [[_controller selectedObjects] setValue:theValue forKeyPath:theKeyPath];
[_cachedValues removeObjectForKey: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 - (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; _labelSize = [_labelField frame].size;
_minSize = CGSizeMake(MAX(_labelSize.width, minSize.width), _labelSize.height + minSize.height + LABEL_MARGIN + TOP_MARGIN); _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]; [_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 "CPRange.j"
@import "CPString.j" @import "CPString.j"
CPFontAttributeName = "font";
CPForegroundColorAttributeName = "color";
/*! /*!
@class CPAttributedString @class CPAttributedString
@ingroup foundation @ingroup foundation
@@ -42,6 +46,11 @@
implements functionality from both NSAttributedString and implements functionality from both NSAttributedString and
NSMutableAttributedString. However, to ease converting of existing NSMutableAttributedString. However, to ease converting of existing
Objective-C code a CPMutableAttributedString alias to this class exists. 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 @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]; [domain setObject:anObject forKey:aKey];
[self domainDidChange:aDomain];
_searchListNeedsReload = YES; _searchListNeedsReload = YES;
[self domainDidChange:aDomain];
} }
/*! /*!
@@ -211,9 +210,8 @@ var StandardUserDefaults;
return; return;
[domain removeObjectForKey:aKey]; [domain removeObjectForKey:aKey];
[self domainDidChange:aDomain];
_searchListNeedsReload = YES; _searchListNeedsReload = YES;
[self domainDidChange:aDomain];
} }
/*! /*!
+2
View File
@@ -21,6 +21,7 @@
*/ */
@import "CPArray.j" @import "CPArray.j"
@import "CPAttributedString.j"
@import "CPBundle.j" @import "CPBundle.j"
@import "CPCharacterSet.j" @import "CPCharacterSet.j"
@import "CPCoder.j" @import "CPCoder.j"
@@ -49,6 +50,7 @@
@import "CPNotificationCenter.j" @import "CPNotificationCenter.j"
@import "CPNull.j" @import "CPNull.j"
@import "CPNumber.j" @import "CPNumber.j"
@import "CPNumberFormatter.j"
@import "CPObject.j" @import "CPObject.j"
@import "CPObjJRuntime.j" @import "CPObjJRuntime.j"
@import "CPOperation.j" @import "CPOperation.j"
+26
View File
@@ -33,6 +33,15 @@ var CLS_CLASS = 0x1,
#define GETMETA(aClass) (ISMETA(aClass) ? aClass : aClass.isa) #define GETMETA(aClass) (ISMETA(aClass) ? aClass : aClass.isa)
#define ISINITIALIZED(aClass) GETINFO(GETMETA(aClass), CLS_INITIALIZED) #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) GLOBAL(objj_ivar) = function(/*String*/ aName, /*String*/ aType)
{ {
this.name = aName; this.name = aName;
@@ -604,6 +613,10 @@ DISPLAY_NAME(ivar_getTypeEncoding);
// Sending Messages // Sending Messages
#ifdef MAXIMUM_RECURSION_CHECKS
var __objj_msgSend__StackDepth = 0;
#endif
GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector) GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
{ {
if (aReceiver == nil) if (aReceiver == nil)
@@ -613,6 +626,13 @@ GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
CLASS_GET_METHOD_IMPLEMENTATION(var implementation, isa, 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) switch(arguments.length)
{ {
case 2: return implementation(aReceiver, aSelector); case 2: return implementation(aReceiver, aSelector);
@@ -621,6 +641,12 @@ GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
} }
return implementation.apply(aReceiver, arguments); return implementation.apply(aReceiver, arguments);
#ifdef MAXIMUM_RECURSION_CHECKS
} finally {
__objj_msgSend__StackDepth--;
}
#endif
} }
DISPLAY_NAME(objj_msgSend); DISPLAY_NAME(objj_msgSend);
+42
View File
@@ -80,6 +80,7 @@
[arrayController insertObject:object atArrangedObjectIndex:1]; [arrayController insertObject:object atArrangedObjectIndex:1];
[self assert:object equals:[[arrayController arrangedObjects] objectAtIndex:1]]; [self assert:object equals:[[arrayController arrangedObjects] objectAtIndex:1]];
[self assertTrue:[[arrayController content] containsObject:object] message:@"object should be inserted into content"];
} }
- (void)testAddObjectUpdatesArrangedObjectsWithoutSortDescriptors - (void)testAddObjectUpdatesArrangedObjectsWithoutSortDescriptors
@@ -438,6 +439,31 @@
[self assert:1 equals:[observations count] message:@"exactly 1 notification for addObject (clearsFilterPredicate YES)"]; [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 - (void)testCompoundKeyPaths
{ {
var departmentNameField = [[CPTextField alloc] init]; var departmentNameField = [[CPTextField alloc] init];
@@ -482,6 +508,22 @@
[self assertTrue:[[arrayController arrangedObjects] count] > 0]; [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 - (void)observeValueForKeyPath:keyPath
ofObject:anActivity ofObject:anActivity
change:change change:change
+23
View File
@@ -249,6 +249,29 @@
CPLog(@"here: "+aKeyPath+" value: "+[anObject valueForKey:aKeyPath]); 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 @end
@implementation BindingTester : CPObject @implementation BindingTester : CPObject
+17 -1
View File
@@ -18,6 +18,7 @@ CPUserDefaultsTestKey2 = @"KEY2";
@implementation CPUserDefaultsTest : OJTestCase @implementation CPUserDefaultsTest : OJTestCase
{ {
CPUserDefaults target; CPUserDefaults target;
id lastObservedCPUserDefaultsTestKey1;
} }
- (void)setUp - (void)setUp
@@ -40,7 +41,6 @@ CPUserDefaultsTestKey2 = @"KEY2";
} }
- (void)testSetObjectForKey - (void)testSetObjectForKey
{ {
[target setObject:[CPArray arrayWithObjects:@"cell1", @"cell2"] forKey:CPUserDefaultsTestKey1]; [target setObject:[CPArray arrayWithObjects:@"cell1", @"cell2"] forKey:CPUserDefaultsTestKey1];
@@ -133,7 +133,23 @@ CPUserDefaultsTestKey2 = @"KEY2";
[target removeObjectForKey:CPUserDefaultsTestKey1]; [target removeObjectForKey:CPUserDefaultsTestKey1];
[self assert:[target dataForKey:CPUserDefaultsTestKey1] equals:nil]; [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 @end
+1
View File
@@ -46,6 +46,7 @@
@import "NSMenu.j" @import "NSMenu.j"
@import "NSMenuItem.j" @import "NSMenuItem.j"
@import "NSNibConnector.j" @import "NSNibConnector.j"
@import "NSNumberFormatter.j"
@import "NSObjectController.j" @import "NSObjectController.j"
@import "NSOutlineView.j" @import "NSOutlineView.j"
@import "NSPopUpButton.j" @import "NSPopUpButton.j"
-6
View File
@@ -3,9 +3,3 @@
} }
@end @end
@implementation NSNumberFormatter : CPObject
{
}
@end
+1
View File
@@ -58,6 +58,7 @@
_numberOfMajorTickMarks = [cell numberOfMajorTickMarks]; _numberOfMajorTickMarks = [cell numberOfMajorTickMarks];
[self setEditable:[cell isEditable]]; [self setEditable:[cell isEditable]];
[self setEnabled:[cell isEnabled]];
[self setContinuous:[cell isContinuous]]; [self setContinuous:[cell isContinuous]];
return self; 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 try
{ {
var options = parseOptions(args); var options = parseOptions(args),
ret = 0;
if (options.watch) if (options.watch)
watch(options); watch(options);
else else
convert(options); if (!convert(options))
ret = 2;
} }
catch (anException) catch (anException)
{ {
CPLog.fatal(exceptionReason(anException)); CPLog.fatal(exceptionReason(anException));
OS.exit(1); OS.exit(1);
} }
OS.exit(ret);
} }
function convert(options, inputFile) function convert(options, inputFile)
Regular → Executable
View File