mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-09 12:17:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a80147bafd | ||
|
|
fc81483371 | ||
|
|
e552b022ba | ||
|
|
6c34bec51e | ||
|
|
f170e4812d | ||
|
|
070728aea9 | ||
|
|
5e3ae58d42 | ||
|
|
b4e4bdb924 | ||
|
|
0f097e0ff4 | ||
|
|
e38845f993 | ||
|
|
f4f4dd8025 | ||
|
|
c942259c90 | ||
|
|
a4fb0920f6 | ||
|
|
cbabb544ab | ||
|
|
4d47b5abf5 | ||
|
|
aac09d8208 | ||
|
|
a127fc9ec3 | ||
|
|
551aa300ea | ||
|
|
485e59b5eb | ||
|
|
fdfcbf1da2 | ||
|
|
0f5eee0258 | ||
|
|
870492eb96 | ||
|
|
b50005049e | ||
|
|
0e890e19ea | ||
|
|
59aabc150c | ||
|
|
b159815349 | ||
|
|
bfe5afaddd | ||
|
|
64e907156c | ||
|
|
5d01ca428c |
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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];
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -3,9 +3,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSNumberFormatter : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -58,6 +58,7 @@
|
||||
_numberOfMajorTickMarks = [cell numberOfMajorTickMarks];
|
||||
|
||||
[self setEditable:[cell isEditable]];
|
||||
[self setEnabled:[cell isEnabled]];
|
||||
[self setContinuous:[cell isContinuous]];
|
||||
|
||||
return self;
|
||||
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user