mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-10 12:47:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7994c1480 | ||
|
|
c605ca4531 | ||
|
|
c288930a3c | ||
|
|
596676a562 | ||
|
|
cc3d9f337c | ||
|
|
8d210100a5 | ||
|
|
d25e96902f | ||
|
|
ad792d9772 | ||
|
|
9e7b737960 | ||
|
|
9bc6a69b76 | ||
|
|
bacc50c271 | ||
|
|
9f459f053e | ||
|
|
4195f3b8c6 | ||
|
|
4926824482 | ||
|
|
cb2f539cf7 | ||
|
|
7f31be7cb1 | ||
|
|
78b9c5b576 | ||
|
|
01e35efdbb | ||
|
|
bef1687b1f |
+70
-108
@@ -27,7 +27,6 @@
|
||||
@import "CPObjectController.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
|
||||
|
||||
/*!
|
||||
|
||||
@class CPArrayController
|
||||
@@ -285,7 +284,8 @@
|
||||
|
||||
if (value === nil)
|
||||
value = [];
|
||||
else if (![value isKindOfClass:[CPArray class]])
|
||||
|
||||
if (![value isKindOfClass:[CPArray class]])
|
||||
value = [value];
|
||||
|
||||
var oldSelectedObjects = nil,
|
||||
@@ -339,6 +339,14 @@
|
||||
[self setContent:anArray];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_setContentSet:(id)aSet
|
||||
{
|
||||
[self setContent:[aSet allObjects]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the content array of the controller.
|
||||
@return id the content array of the receiver
|
||||
@@ -348,14 +356,6 @@
|
||||
return [self content];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_setContentSet:(id)aSet
|
||||
{
|
||||
[self setContent:[aSet allObjects]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the content of the receiver as a CPSet.
|
||||
|
||||
@@ -564,13 +564,9 @@
|
||||
- (BOOL)setSelectionIndexes:(CPIndexSet)indexes
|
||||
{
|
||||
[self _selectionWillChange]
|
||||
|
||||
// When explicitly setting the selection, ignore avoidsEmptySelection
|
||||
var changed = [self __setSelectionIndexes:indexes obeyAvoidsEmptySelection:NO];
|
||||
|
||||
[self _selectionDidChangeNotify:NO];
|
||||
|
||||
return changed;
|
||||
var r = [self __setSelectionIndexes:indexes];
|
||||
[self _selectionDidChange];
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -579,7 +575,7 @@
|
||||
*/
|
||||
- (BOOL)__setSelectionIndex:(int)theIndex
|
||||
{
|
||||
return [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:theIndex] obeyAvoidsEmptySelection:YES];
|
||||
return [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:theIndex]];
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -588,37 +584,38 @@
|
||||
*/
|
||||
- (BOOL)__setSelectionIndexes:(CPIndexSet)indexes
|
||||
{
|
||||
[self __setSelectionIndexes:indexes obeyAvoidsEmptySelection:YES];
|
||||
}
|
||||
var newIndexes = indexes;
|
||||
|
||||
/*
|
||||
Like setSelectionIndexes but don't fire any change notifications.
|
||||
@ignore
|
||||
*/
|
||||
- (BOOL)__setSelectionIndexes:(CPIndexSet)indexes obeyAvoidsEmptySelection:(BOOL)obeyAvoidsEmptySelection
|
||||
{
|
||||
if (!indexes)
|
||||
indexes = [CPIndexSet indexSet];
|
||||
if (!newIndexes)
|
||||
newIndexes = [CPIndexSet indexSet];
|
||||
|
||||
if (![indexes count])
|
||||
if (![newIndexes count])
|
||||
{
|
||||
if (obeyAvoidsEmptySelection && _avoidsEmptySelection && [[self arrangedObjects] count])
|
||||
indexes = [CPIndexSet indexSetWithIndex:0];
|
||||
if (_avoidsEmptySelection && [[self arrangedObjects] count])
|
||||
newIndexes = [CPIndexSet indexSetWithIndex:0];
|
||||
}
|
||||
else
|
||||
{
|
||||
var objectsCount = [[self arrangedObjects] count];
|
||||
|
||||
// Don't trash the input - the caller might depend on it or we might have been
|
||||
// given _selectionIndexes as the input in which case the equality test below
|
||||
// would always succeed despite our change below.
|
||||
newIndexes = [newIndexes copy];
|
||||
|
||||
// Remove out of bounds indexes.
|
||||
[indexes removeIndexesInRange:CPMakeRange(objectsCount, [indexes lastIndex] + 1)];
|
||||
[newIndexes removeIndexesInRange:CPMakeRange(objectsCount, [newIndexes lastIndex] + 1)];
|
||||
// When avoiding empty selection and the deleted selection was at the bottom, select the last item.
|
||||
if (![indexes count] && _avoidsEmptySelection && objectsCount)
|
||||
indexes = [CPIndexSet indexSetWithIndex:objectsCount - 1];
|
||||
if (![newIndexes count] && _avoidsEmptySelection && objectsCount)
|
||||
newIndexes = [CPIndexSet indexSetWithIndex:objectsCount - 1];
|
||||
}
|
||||
|
||||
if ([_selectionIndexes isEqualToIndexSet:indexes])
|
||||
if ([_selectionIndexes isEqualToIndexSet:newIndexes])
|
||||
return NO;
|
||||
|
||||
_selectionIndexes = [indexes copy];
|
||||
// If we haven't already created our own index instance, make sure to copy it here so that
|
||||
// the copy the user sent in is decoupled from our internal copy.
|
||||
_selectionIndexes = indexes === newIndexes ? [indexes copy] : newIndexes;
|
||||
|
||||
// Push back the new selection to the model for selectionIndexes if we have one.
|
||||
// There won't be an infinite loop because of the equality check above.
|
||||
@@ -650,10 +647,11 @@
|
||||
[self willChangeValueForKey:@"selectionIndexes"];
|
||||
[self _selectionWillChange];
|
||||
|
||||
[self __setSelectedObjects:objects obeyAvoidsEmptySelection:NO];
|
||||
var r = [self __setSelectedObjects:objects];
|
||||
|
||||
[self didChangeValueForKey:@"selectionIndexes"];
|
||||
[self _selectionDidChangeNotify:NO];
|
||||
[self _selectionDidChange];
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -661,15 +659,6 @@
|
||||
@ignore
|
||||
*/
|
||||
- (BOOL)__setSelectedObjects:(CPArray)objects
|
||||
{
|
||||
[self __setSelectedObjects:objects obeyAvoidsEmptySelection:YES];
|
||||
}
|
||||
|
||||
/*
|
||||
Like setSelectedObjects but don't fire any change notifications.
|
||||
@ignore
|
||||
*/
|
||||
- (BOOL)__setSelectedObjects:(CPArray)objects obeyAvoidsEmptySelection:(BOOL)obeyAvoidsEmptySelection
|
||||
{
|
||||
var set = [CPIndexSet indexSet],
|
||||
count = [objects count],
|
||||
@@ -683,7 +672,7 @@
|
||||
[set addIndex:index];
|
||||
}
|
||||
|
||||
[self __setSelectionIndexes:set obeyAvoidsEmptySelection:obeyAvoidsEmptySelection];
|
||||
[self __setSelectionIndexes:set];
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -752,13 +741,21 @@
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"content"];
|
||||
[self _willModifyContent];
|
||||
|
||||
/*
|
||||
If the content array is bound then our addObject: message below will cause the observed
|
||||
array to change. The binding will call setContent:_contentObject on this array
|
||||
controller to let it know about the change. We want to ignore that message since we
|
||||
A) already have the right _contentObject and B) properly update _arrangedObjects
|
||||
by hand below.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
[_contentObject addObject:object];
|
||||
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
[self _didModifyContent];
|
||||
_disableSetContent = NO;
|
||||
|
||||
if (willClearPredicate)
|
||||
{
|
||||
@@ -779,15 +776,14 @@
|
||||
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:1];
|
||||
}
|
||||
/*
|
||||
else if (_filterPredicate !== nil)
|
||||
...
|
||||
Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does
|
||||
not appear in arrangedObjects and we do not have to update at all.
|
||||
else if (_filterPredicate !== nil)
|
||||
...
|
||||
// Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does
|
||||
// not appear in arrangedObjects and we do not have to update at all.
|
||||
*/
|
||||
|
||||
// This will also send notificaitons for arrangedObjects.
|
||||
[self didChangeValueForKey:@"content"];
|
||||
|
||||
if (willClearPredicate)
|
||||
[self didChangeValueForKey:@"filterPredicate"];
|
||||
}
|
||||
@@ -813,7 +809,10 @@
|
||||
|
||||
[self willChangeValueForKey:@"content"];
|
||||
|
||||
[self _willModifyContent];
|
||||
/*
|
||||
See _disableSetContent explanation in addObject:.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
|
||||
// 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:,
|
||||
@@ -822,7 +821,7 @@
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
[self _didModifyContent];
|
||||
_disableSetContent = NO;
|
||||
|
||||
if (willClearPredicate)
|
||||
[self __setFilterPredicate:nil];
|
||||
@@ -840,7 +839,6 @@
|
||||
[self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]];
|
||||
|
||||
[self didChangeValueForKey:@"content"];
|
||||
|
||||
if (willClearPredicate)
|
||||
[self didChangeValueForKey:@"filterPredicate"];
|
||||
}
|
||||
@@ -853,13 +851,15 @@
|
||||
- (void)removeObject:(id)object
|
||||
{
|
||||
[self willChangeValueForKey:@"content"];
|
||||
[self _willModifyContent];
|
||||
|
||||
// See _disableSetContent explanation in addObject:.
|
||||
_disableSetContent = YES;
|
||||
|
||||
[_contentObject removeObject:object];
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
[self _didModifyContent];
|
||||
_disableSetContent = NO;
|
||||
|
||||
if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
|
||||
{
|
||||
@@ -910,15 +910,6 @@
|
||||
[self removeObjectsAtArrangedObjectIndexes:_selectionIndexes];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes the object at the specified index in the controller's arranged objects from the content array.
|
||||
@param int index - index of the object to remove.
|
||||
*/
|
||||
- (void)removeObjectAtArrangedObjectIndex:(int)index
|
||||
{
|
||||
[self removeObjectsAtArrangedObjectIndexes:[CPIndexSet indexSetWithIndex:index]];
|
||||
}
|
||||
|
||||
/*!
|
||||
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.
|
||||
@@ -927,21 +918,20 @@
|
||||
{
|
||||
[self willChangeValueForKey:@"content"];
|
||||
|
||||
/*
|
||||
See _disableSetContent explanation in addObject:.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
|
||||
var arrangedObjects = [self arrangedObjects],
|
||||
index = [anIndexSet lastIndex],
|
||||
position = CPNotFound,
|
||||
newSelectionIndexes = [_selectionIndexes copy],
|
||||
proxy = [_CPKVOProxy proxyForObject:self];
|
||||
newSelectionIndexes = [_selectionIndexes copy];
|
||||
|
||||
while (index !== CPNotFound)
|
||||
{
|
||||
var object = [arrangedObjects objectAtIndex:index];
|
||||
|
||||
// Make sure arrangedObjects does not notify because of content change
|
||||
[proxy suppressNotificationsForKeyPath:@"arrangedObjects"];
|
||||
|
||||
// First try the simple case which should work if there are no sort descriptors.
|
||||
if ([_contentObject objectAtIndex:index] === object)
|
||||
[_contentObject removeObjectAtIndex:index];
|
||||
@@ -955,9 +945,6 @@
|
||||
contentIndex = [_contentObject indexOfObjectIdenticalTo:object];
|
||||
[_contentObject removeObjectAtIndex:contentIndex];
|
||||
}
|
||||
|
||||
// Now arrangedObjects can notify
|
||||
[proxy unsuppressNotificationsForKeyPath:@"arrangedObjects"];
|
||||
[arrangedObjects removeObjectAtIndex:index];
|
||||
|
||||
// Deselect this row if it was selected, and either way shift all selection indexes
|
||||
@@ -967,7 +954,6 @@
|
||||
|
||||
index = [anIndexSet indexLessThanIndex:index];
|
||||
}
|
||||
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
_disableSetContent = NO;
|
||||
@@ -1014,13 +1000,15 @@
|
||||
- (void)_removeObjects:(CPArray)objects
|
||||
{
|
||||
[self willChangeValueForKey:@"content"];
|
||||
[self _willModifyContent];
|
||||
|
||||
// See _disableSetContent explanation in addObject:.
|
||||
_disableSetContent = YES;
|
||||
|
||||
[_contentObject removeObjectsInArray:objects];
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
[self _didModifyContent];
|
||||
_disableSetContent = NO;
|
||||
|
||||
var arrangedObjects = [self arrangedObjects],
|
||||
position = [arrangedObjects indexOfObject:[objects objectAtIndex:0]];
|
||||
@@ -1057,39 +1045,13 @@
|
||||
return [self isEditable];
|
||||
}
|
||||
|
||||
// Internal
|
||||
- (void)_willModifyContent
|
||||
{
|
||||
/*
|
||||
If the content array is bound then our addObject: message below will cause the observed
|
||||
array to change. The binding will call setContent:_contentObject on this array
|
||||
controller to let it know about the change. We want to ignore that message since we
|
||||
A) already have the right _contentObject and B) properly update _arrangedObjects
|
||||
by hand below.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
|
||||
/*
|
||||
When mutating arrangedObjects, we mutate the content first. But mutating the content
|
||||
will trigger a notification that the arrangedObjects have changed, which we want to
|
||||
suppress, since the arrangedObjects mutation will do the correct notification.
|
||||
*/
|
||||
[[_CPKVOProxy proxyForObject:self] suppressNotificationsForKeyPath:@"arrangedObjects"];
|
||||
}
|
||||
|
||||
- (void)_didModifyContent
|
||||
{
|
||||
_disableSetContent = NO;
|
||||
[[_CPKVOProxy proxyForObject:self] unsuppressNotificationsForKeyPath:@"arrangedObjects"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPArrayController (CPBinder)
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (theBinding === @"content" || theBinding === @"contentArray")
|
||||
if (theBinding == @"contentArray")
|
||||
return [_CPArrayControllerContentBinder class];
|
||||
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
@@ -1104,7 +1066,7 @@
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
isCompound = [self handlesContentAsCompoundValue] || keyPath.indexOf("@") !== -1;
|
||||
isCompound = [self handlesContentAsCompoundValue];
|
||||
|
||||
if (!isCompound)
|
||||
{
|
||||
|
||||
@@ -131,8 +131,8 @@
|
||||
|
||||
_contentObject = aContent;
|
||||
|
||||
[self didChangeValueForKey:@"contentObject"];
|
||||
[self _selectionDidChange];
|
||||
[self didChangeValueForKey:@"contentObject"];
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), "repositories.rb")
|
||||
require File.join(File.dirname(__FILE__), "dependencies.rb")
|
||||
|
||||
# Keep this structure to allow the build system to update version numbers.
|
||||
VERSION_NUMBER = "1.0.0.015"
|
||||
VERSION_NUMBER = "1.0.0.018"
|
||||
|
||||
|
||||
# Shorten expressions
|
||||
|
||||
@@ -259,7 +259,7 @@ var concat = Array.prototype.concat,
|
||||
if (aRange.location < 0 || CPMaxRange(aRange) > self.length)
|
||||
[CPException raise:CPRangeException reason:_cmd + " aRange out of bounds"];
|
||||
|
||||
if (otherRange.location < 0 || CPMaxRange(otherRange) > anArray.length)
|
||||
if (otherRange && (otherRange.location < 0 || CPMaxRange(otherRange) > anArray.length))
|
||||
[CPException raise:CPRangeException reason:_cmd + " otherRange out of bounds"];
|
||||
|
||||
if (otherRange && (otherRange.location !== 0 || otherRange.length !== [anArray count]))
|
||||
|
||||
@@ -228,10 +228,10 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
id _targetObject;
|
||||
Class _nativeClass;
|
||||
CPDictionary _changesForKey;
|
||||
CPDictionary _nestingForKey;
|
||||
Object _observersForKey;
|
||||
int _observersForKeyLength;
|
||||
CPSet _replacedKeys;
|
||||
CPSet _suppressedNotifications;
|
||||
}
|
||||
|
||||
+ (id)proxyForObject:(CPObject)anObject
|
||||
@@ -248,12 +248,12 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_targetObject = aTarget;
|
||||
_nativeClass = [aTarget class];
|
||||
_observersForKey = {};
|
||||
_changesForKey = {};
|
||||
_observersForKeyLength = 0;
|
||||
_suppressedNotifications = [CPSet set];
|
||||
_targetObject = aTarget;
|
||||
_nativeClass = [aTarget class];
|
||||
_observersForKey = {};
|
||||
_changesForKey = {};
|
||||
_nestingForKey = {};
|
||||
_observersForKeyLength = 0;
|
||||
|
||||
[self _replaceClass];
|
||||
aTarget[KVOProxyKey] = self;
|
||||
@@ -631,12 +631,12 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
{
|
||||
// Fire change events for the dependent keys
|
||||
var dependentKeysForClass = _nativeClass[DependentKeysKey],
|
||||
dependentKeys = [dependentKeysForClass[theKeyPath] allObjects],
|
||||
isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey];
|
||||
dependantKeys = [dependentKeysForClass[theKeyPath] allObjects];
|
||||
|
||||
for (var i = 0; i < [dependentKeys count]; i++)
|
||||
var isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey];
|
||||
for (var i = 0; i < [dependantKeys count]; i++)
|
||||
{
|
||||
var dependantKey = [dependentKeys objectAtIndex:i];
|
||||
var dependantKey = [dependantKeys objectAtIndex:i];
|
||||
[self _sendNotificationsForKey:dependantKey changeOptions:theChanges isBefore:isBeforeFlag];
|
||||
}
|
||||
}
|
||||
@@ -717,6 +717,18 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
|
||||
if (isBefore)
|
||||
{
|
||||
if (changes)
|
||||
{
|
||||
// "willChange:X" nesting.
|
||||
var level = _nestingForKey[aKey];
|
||||
if (!level)
|
||||
[CPException raise:CPInternalInconsistencyException reason:@"_changesForKey without _nestingForKey"];
|
||||
_nestingForKey[aKey] = level + 1;
|
||||
// Only notify on the first willChange..., silently note any following nested calls.
|
||||
return;
|
||||
}
|
||||
_nestingForKey[aKey] = 1;
|
||||
|
||||
changes = changeOptions;
|
||||
|
||||
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
|
||||
@@ -774,9 +786,19 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!changes)
|
||||
var level = _nestingForKey[aKey];
|
||||
if (!changes || !level)
|
||||
[CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
|
||||
|
||||
_nestingForKey[aKey] = level - 1;
|
||||
if (level - 1 > 0)
|
||||
{
|
||||
// willChange... was called multiple times. Only fire observation notifications when
|
||||
// didChange... has been called an equal number of times.
|
||||
return;
|
||||
}
|
||||
delete _nestingForKey[aKey];
|
||||
|
||||
[changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
|
||||
|
||||
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
|
||||
@@ -814,6 +836,8 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
|
||||
[changes setObject:newValue forKey:CPKeyValueChangeNewKey];
|
||||
}
|
||||
|
||||
delete _changesForKey[aKey];
|
||||
}
|
||||
|
||||
var observers = [_observersForKey[aKey] allValues],
|
||||
@@ -844,23 +868,12 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
{
|
||||
var keyPath = dependentKeyPaths[index];
|
||||
|
||||
if (![_suppressedNotifications containsObject:keyPath])
|
||||
[self _sendNotificationsForKey:keyPath
|
||||
changeOptions:isBefore ? [changeOptions copy] : _changesForKey[keyPath]
|
||||
isBefore:isBefore];
|
||||
[self _sendNotificationsForKey:keyPath
|
||||
changeOptions:isBefore ? [changeOptions copy] : _changesForKey[keyPath]
|
||||
isBefore:isBefore];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)suppressNotificationsForKeyPath:(CPString)keyPath
|
||||
{
|
||||
[_suppressedNotifications addObject:keyPath];
|
||||
}
|
||||
|
||||
- (void)unsuppressNotificationsForKeyPath:(CPString)keyPath
|
||||
{
|
||||
[_suppressedNotifications removeObject:keyPath];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPKVOModelSubclass
|
||||
|
||||
@@ -464,6 +464,45 @@
|
||||
[self assert:1 equals:[observations count] message:@"exactly 1 notification for insertObject x 2 (clearsFilterPredicate YES)"];
|
||||
}
|
||||
|
||||
/**
|
||||
Replicate the situation found in the Bindings manual test where one array controller's
|
||||
contents depend on the selection of another.
|
||||
*/
|
||||
- (void)testObservationBetweenBoundControllers
|
||||
{
|
||||
var companies = [Companies new],
|
||||
companiesController = [CPArrayController new],
|
||||
employeesController = [CPArrayController new];
|
||||
|
||||
[companiesController bind:@"contentArray" toObject:companies withKeyPath:@"items" options:nil];
|
||||
[employeesController bind:@"contentArray" toObject:companiesController withKeyPath:@"selection.employees" options:nil];
|
||||
|
||||
[companiesController addObserver:self forKeyPath:@"selection" options:0 context:@"companies.selection"];
|
||||
[companiesController addObserver:self forKeyPath:@"selectionIndexes" options:0 context:@"companies.selectionIndexes"];
|
||||
[companiesController addObserver:self forKeyPath:@"arrangedObjects" options:0 context:@"companies.arrangedObjects"];
|
||||
[employeesController addObserver:self forKeyPath:@"selectionIndexes" options:0 context:@"employees.selectionIndexes"];
|
||||
[employeesController addObserver:self forKeyPath:@"selection" options:0 context:@"employees.selection"];
|
||||
[employeesController addObserver:self forKeyPath:@"arrangedObjects" options:0 context:@"employees.arrangedObjects"];
|
||||
|
||||
observations = [];
|
||||
|
||||
[companiesController setSelectionIndexes:[CPIndexSet indexSetWithIndex:1]];
|
||||
|
||||
//for (var i = 0; i < [observations count]; i++)
|
||||
// CPLog.error(observations[i].context);
|
||||
|
||||
// There should be exactly one observation of each kind, and they should be in the right
|
||||
// order.
|
||||
[self assert:5 equals:[observations count]];
|
||||
[self assert:@"companies.selectionIndexes" equals:observations[0].context];
|
||||
[self assert:@"employees.arrangedObjects" equals:observations[1].context];
|
||||
[self assert:@"employees.selectionIndexes" equals:observations[2].context];
|
||||
[self assert:@"employees.selection" equals:observations[3].context];
|
||||
// This observation registers after the employees update because we are later in the
|
||||
// observation queue than the binding.
|
||||
[self assert:@"companies.selection" equals:observations[4].context];
|
||||
}
|
||||
|
||||
- (void)testCompoundKeyPaths
|
||||
{
|
||||
var departmentNameField = [[CPTextField alloc] init];
|
||||
@@ -524,6 +563,18 @@
|
||||
[self assert:[1, 2, 1] equals:[ac arrangedObjects] message:"only one copy of 1 removed + the right copy should be removed"];
|
||||
}
|
||||
|
||||
- (void)testSetOutOfBoundsSelectedIndexes
|
||||
{
|
||||
var ac = [CPArrayController new],
|
||||
contentArray = [1, 2, 3],
|
||||
indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 4)];
|
||||
|
||||
[ac setContent:contentArray];
|
||||
[ac setSelectionIndexes:indexes];
|
||||
[self assert:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 3)] equals:[ac selectionIndexes]];
|
||||
[self assert:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 4)] equals:indexes];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:keyPath
|
||||
ofObject:anActivity
|
||||
change:change
|
||||
@@ -534,6 +585,7 @@
|
||||
|
||||
[observations addObject:{
|
||||
keyPath: keyPath,
|
||||
context: context,
|
||||
oldValue: [change valueForKey:CPKeyValueChangeOldKey],
|
||||
newValue: [change valueForKey:CPKeyValueChangeNewKey]
|
||||
}];
|
||||
@@ -620,4 +672,25 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
@implementation Companies : CPObject
|
||||
{
|
||||
CPMutableArray items @accessors;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
items = [CPMutableArray array];
|
||||
[items addObject:[CPDictionary dictionaryWithObjectsAndKeys:@"Spacely Sprockets", @"name", [CPArray arrayWithObjects:@"Tom", @"Dick", @"Harry"], @"employees"]];
|
||||
[items addObject:[CPDictionary dictionaryWithObjectsAndKeys:@"Cogswell Cogs", @"name", [CPArray arrayWithObjects:@"Jane", @"Mary", @"Vic"], @"employees"]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -78,27 +78,6 @@
|
||||
[contentView addSubview:employeesScrollView];
|
||||
[contentView addSubview:text];
|
||||
|
||||
var button = [CPButton buttonWithTitle:@"Add Item"];
|
||||
|
||||
[button setFrameOrigin:CGPointMake(500, 30)];
|
||||
[button setTarget:self];
|
||||
[button setAction:@selector(addItem:)];
|
||||
[contentView addSubview:button];
|
||||
|
||||
button = [CPButton buttonWithTitle:@"Insert Item"];
|
||||
|
||||
[button setFrameOrigin:CGPointMake(500, 70)];
|
||||
[button setTarget:self];
|
||||
[button setAction:@selector(insertItem:)];
|
||||
[contentView addSubview:button];
|
||||
|
||||
button = [CPButton buttonWithTitle:@"Remove Item"];
|
||||
|
||||
[button setFrameOrigin:CGPointMake(500, 110)];
|
||||
[button setTarget:self];
|
||||
[button setAction:@selector(removeItem:)];
|
||||
[contentView addSubview:button];
|
||||
|
||||
companiesController = [CPArrayController new];
|
||||
[companiesController bind:@"contentArray" toObject:companies withKeyPath:@"items" options:nil];
|
||||
|
||||
@@ -129,24 +108,4 @@
|
||||
console.log("\nkeyPath: %s\ncontext: %s\nnew: %s\nold: %s", keyPath, context, [[change valueForKey:CPKeyValueChangeNewKey] description], [[change valueForKey:CPKeyValueChangeOldKey] description]);
|
||||
}
|
||||
|
||||
- (void)addItem:(id)sender
|
||||
{
|
||||
var item = [CPDictionary dictionaryWithObjectsAndKeys:@"Seven Dwarves", @"name", [CPArray arrayWithObjects:@"Sneezy", @"Dopey", @"Sleepy", @"Doc", @"Bashful", @"Happy", @"Grumpy"], @"employees"];
|
||||
|
||||
[companiesController addObject:item];
|
||||
}
|
||||
|
||||
- (void)insertItem:(id)sender
|
||||
{
|
||||
var item = [CPDictionary dictionaryWithObjectsAndKeys:@"Acme Corp", @"name", [CPArray arrayWithObjects:@"Road Runner", @"Wile E. Coyote"], @"employees"];
|
||||
|
||||
[companiesController insertObject:item atArrangedObjectIndex:1];
|
||||
}
|
||||
|
||||
- (void)removeItem:(id)sender
|
||||
{
|
||||
if ([[companiesController arrangedObjects] count])
|
||||
[companiesController removeObjectAtArrangedObjectIndex:0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
require("../../common.jake");
|
||||
|
||||
var OS = require("os");
|
||||
var task = require("jake").task
|
||||
var stream = require("narwhal/term").stream;
|
||||
var applicationName = "xcodecapp-cocoa.app";
|
||||
var OS = require("os"),
|
||||
task = require("jake").task,
|
||||
stream = require("narwhal/term").stream,
|
||||
applicationName = "xcodecapp-cocoa.app";
|
||||
|
||||
task ("build", function()
|
||||
{
|
||||
@@ -22,9 +22,7 @@ task ("build", function()
|
||||
cp_r(FILE.join("build", "Release", "xcodecapp-cocoa.app"), supportPath);
|
||||
FILE.chmod(FILE.join(supportPath, "Contents", "MacOS", "xcodecapp-cocoa"), 0755);
|
||||
|
||||
if (FILE.exists(installPath))
|
||||
FILE.remove(installPath);
|
||||
FILE.symlink(supportPath, installPath);
|
||||
OS.system(["ln", "-sf", supportPath, installPath]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user