mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-28 21:27:40 +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 "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
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
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];
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
_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];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 "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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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];
|
[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];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -3,9 +3,3 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation NSNumberFormatter : CPObject
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
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
Reference in New Issue
Block a user