mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-19 08:53:30 +00:00
fixed various binding issues
This commit is contained in:
+149
-228
@@ -25,12 +25,6 @@
|
||||
@import "CPKeyValueBinding.j"
|
||||
@import "CPTreeNode.j"
|
||||
|
||||
/*!
|
||||
@class CPTreeController
|
||||
|
||||
CPTreeController is a bindings-compatible class that manages a tree of objects.
|
||||
It provides selection and sort management for hierarchical data.
|
||||
*/
|
||||
@implementation CPTreeController : CPObjectController
|
||||
{
|
||||
BOOL _avoidsEmptySelection;
|
||||
@@ -43,10 +37,9 @@
|
||||
CPString _leafKeyPath;
|
||||
|
||||
CPArray _sortDescriptors;
|
||||
id _arrangedObjects; // The proxy root node containing the tree
|
||||
|
||||
CPArray _selectionIndexPaths; // Array of CPIndexPath objects
|
||||
id _arrangedObjects;
|
||||
|
||||
CPArray _selectionIndexPaths;
|
||||
BOOL _disableSetContent;
|
||||
}
|
||||
|
||||
@@ -91,7 +84,7 @@
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingCanInsert
|
||||
{
|
||||
return[CPSet setWithObjects:@"selectionIndexPaths"];
|
||||
return [CPSet setWithObjects:@"selectionIndexPaths"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingCanInsertChild
|
||||
@@ -101,20 +94,16 @@
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
if (self = [super init])
|
||||
{
|
||||
_preservesSelection = YES;
|
||||
_selectsInsertedObjects = YES;
|
||||
_avoidsEmptySelection = YES;
|
||||
_alwaysUsesMultipleValuesMarker = NO;
|
||||
|
||||
_childrenKeyPath = @"children";
|
||||
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -126,57 +115,22 @@
|
||||
}
|
||||
|
||||
- (void)prepareContent
|
||||
{
|
||||
[self _setContentArray:[[self newObject]]];
|
||||
{[self _setContentArray:[CPArray arrayWithObject:[self newObject]]];
|
||||
}
|
||||
|
||||
// --- Properties ---
|
||||
- (BOOL)preservesSelection { return _preservesSelection; }
|
||||
- (void)setPreservesSelection:(BOOL)value { _preservesSelection = value; }
|
||||
|
||||
- (BOOL)preservesSelection
|
||||
{
|
||||
return _preservesSelection;
|
||||
}
|
||||
- (BOOL)selectsInsertedObjects { return _selectsInsertedObjects; }
|
||||
- (void)setSelectsInsertedObjects:(BOOL)value { _selectsInsertedObjects = value; }
|
||||
|
||||
- (void)setPreservesSelection:(BOOL)value
|
||||
{
|
||||
_preservesSelection = value;
|
||||
}
|
||||
- (BOOL)avoidsEmptySelection { return _avoidsEmptySelection; }
|
||||
- (void)setAvoidsEmptySelection:(BOOL)value { _avoidsEmptySelection = value; }
|
||||
|
||||
- (BOOL)selectsInsertedObjects
|
||||
{
|
||||
return _selectsInsertedObjects;
|
||||
}
|
||||
|
||||
- (void)setSelectsInsertedObjects:(BOOL)value
|
||||
{
|
||||
_selectsInsertedObjects = value;
|
||||
}
|
||||
|
||||
- (BOOL)avoidsEmptySelection
|
||||
{
|
||||
return _avoidsEmptySelection;
|
||||
}
|
||||
|
||||
- (void)setAvoidsEmptySelection:(BOOL)value
|
||||
{
|
||||
_avoidsEmptySelection = value;
|
||||
}
|
||||
|
||||
- (BOOL)alwaysUsesMultipleValuesMarker
|
||||
{
|
||||
return _alwaysUsesMultipleValuesMarker;
|
||||
}
|
||||
|
||||
- (void)setAlwaysUsesMultipleValuesMarker:(BOOL)aFlag
|
||||
{
|
||||
_alwaysUsesMultipleValuesMarker = aFlag;
|
||||
}
|
||||
|
||||
- (CPArray)sortDescriptors
|
||||
{
|
||||
return _sortDescriptors;
|
||||
}
|
||||
- (BOOL)alwaysUsesMultipleValuesMarker { return _alwaysUsesMultipleValuesMarker; }
|
||||
- (void)setAlwaysUsesMultipleValuesMarker:(BOOL)aFlag { _alwaysUsesMultipleValuesMarker = aFlag; }
|
||||
|
||||
- (CPArray)sortDescriptors { return _sortDescriptors; }
|
||||
- (void)setSortDescriptors:(CPArray)value
|
||||
{
|
||||
if (_sortDescriptors === value)
|
||||
@@ -186,71 +140,31 @@
|
||||
[self _rearrangeObjects];
|
||||
}
|
||||
|
||||
// --- Key Paths ---
|
||||
|
||||
- (CPString)childrenKeyPath
|
||||
{
|
||||
return _childrenKeyPath;
|
||||
}
|
||||
|
||||
- (CPString)childrenKeyPath { return _childrenKeyPath; }
|
||||
- (void)setChildrenKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
if (_childrenKeyPath === aKeyPath)
|
||||
return;
|
||||
|
||||
_childrenKeyPath = aKeyPath;
|
||||
[self rearrangeObjects];
|
||||
if (_childrenKeyPath === aKeyPath) return;
|
||||
_childrenKeyPath = aKeyPath;[self rearrangeObjects];
|
||||
}
|
||||
|
||||
- (CPString)countKeyPath
|
||||
{
|
||||
return _countKeyPath;
|
||||
}
|
||||
- (CPString)countKeyPath { return _countKeyPath; }
|
||||
- (void)setCountKeyPath:(CPString)aKeyPath { _countKeyPath = aKeyPath; }
|
||||
|
||||
- (void)setCountKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
_countKeyPath = aKeyPath;
|
||||
}
|
||||
- (CPString)leafKeyPath { return _leafKeyPath; }
|
||||
- (void)setLeafKeyPath:(CPString)aKeyPath { _leafKeyPath = aKeyPath; }
|
||||
|
||||
- (CPString)leafKeyPath
|
||||
{
|
||||
return _leafKeyPath;
|
||||
}
|
||||
|
||||
- (void)setLeafKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
_leafKeyPath = aKeyPath;
|
||||
}
|
||||
|
||||
// --- Node Key Path Overrides ---
|
||||
|
||||
- (CPString)childrenKeyPathForNode:(CPTreeNode)node
|
||||
{
|
||||
return [self childrenKeyPath];
|
||||
}
|
||||
|
||||
- (CPString)countKeyPathForNode:(CPTreeNode)node
|
||||
{
|
||||
return [self countKeyPath];
|
||||
}
|
||||
|
||||
- (CPString)leafKeyPathForNode:(CPTreeNode)node
|
||||
{
|
||||
return [self leafKeyPath];
|
||||
}
|
||||
|
||||
// --- Content and Arranged Objects ---
|
||||
- (CPString)childrenKeyPathForNode:(CPTreeNode)node { return [self childrenKeyPath]; }
|
||||
- (CPString)countKeyPathForNode:(CPTreeNode)node { return [self countKeyPath]; }
|
||||
- (CPString)leafKeyPathForNode:(CPTreeNode)node { return [self leafKeyPath]; }
|
||||
|
||||
- (void)setContent:(id)value
|
||||
{
|
||||
if (_disableSetContent)
|
||||
return;
|
||||
|
||||
if (value == nil)
|
||||
value = [];
|
||||
if (_disableSetContent) return;
|
||||
|
||||
if (!value)
|
||||
value = [CPArray array];
|
||||
if (![value isKindOfClass:[CPArray class]])
|
||||
value = [value];
|
||||
value = [CPArray arrayWithObject:value];
|
||||
|
||||
var oldSelectedObjects = nil,
|
||||
oldSelectionIndexPaths = nil;
|
||||
@@ -264,25 +178,13 @@
|
||||
|
||||
[self _rearrangeObjects];
|
||||
|
||||
if ([self preservesSelection])
|
||||
[self __setSelectedObjects:oldSelectedObjects];
|
||||
if ([self preservesSelection])[self __setSelectedObjects:oldSelectedObjects];
|
||||
else[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
|
||||
}
|
||||
|
||||
- (void)_setContentArray:(id)anArray
|
||||
{
|
||||
[self setContent:anArray];
|
||||
}
|
||||
|
||||
- (id)contentArray
|
||||
{
|
||||
return [self content];
|
||||
}
|
||||
|
||||
- (id)arrangedObjects
|
||||
{
|
||||
return _arrangedObjects;
|
||||
}
|
||||
- (void)_setContentArray:(id)anArray {[self setContent:anArray]; }
|
||||
- (id)contentArray { return [self content]; }
|
||||
- (id)arrangedObjects { return _arrangedObjects; }
|
||||
|
||||
- (void)rearrangeObjects
|
||||
{
|
||||
@@ -301,18 +203,16 @@
|
||||
else
|
||||
oldSelectionIndexPaths = [self selectionIndexPaths];
|
||||
|
||||
// Rebuild the proxy tree from the content using _childrenKeyPath and _sortDescriptors[self __rebuildArrangedObjectsTree];
|
||||
[self __rebuildArrangedObjectsTree];
|
||||
|
||||
if ([self preservesSelection])
|
||||
[self __setSelectedObjects:oldSelectedObjects];
|
||||
else
|
||||
[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
|
||||
if ([self preservesSelection])[self __setSelectedObjects:oldSelectedObjects];
|
||||
else[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
|
||||
}
|
||||
|
||||
- (void)__rebuildArrangedObjectsTree
|
||||
{
|
||||
var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil];
|
||||
var contentArray = [self contentArray];
|
||||
var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil],
|
||||
contentArray = [self contentArray];
|
||||
|
||||
if (contentArray && [contentArray count] > 0)
|
||||
{
|
||||
@@ -323,22 +223,20 @@
|
||||
_arrangedObjects = rootNode;
|
||||
}
|
||||
|
||||
// Recursively builds the node tree mapping model objects to CPTreeNodes
|
||||
- (CPArray)_buildTreeNodesForObjects:(CPArray)objects
|
||||
{
|
||||
var count = [objects count];
|
||||
if (count === 0)
|
||||
return[];
|
||||
if (count === 0) return [CPArray array];
|
||||
|
||||
var sortedObjects = objects;
|
||||
if ([_sortDescriptors count] > 0)
|
||||
if (_sortDescriptors && [_sortDescriptors count] > 0)
|
||||
sortedObjects = [objects sortedArrayUsingDescriptors:_sortDescriptors];
|
||||
|
||||
var nodes = [CPMutableArray arrayWithCapacity:count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var obj = sortedObjects[i],
|
||||
var obj = [sortedObjects objectAtIndex:i],
|
||||
node = [[CPTreeNode alloc] initWithRepresentedObject:obj];
|
||||
|
||||
if (_childrenKeyPath)
|
||||
@@ -349,16 +247,12 @@
|
||||
var childNodes = [self _buildTreeNodesForObjects:childObjects];
|
||||
[[node mutableChildNodes] addObjectsFromArray:childNodes];
|
||||
}
|
||||
}
|
||||
|
||||
[nodes addObject:node];
|
||||
}[nodes addObject:node];
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
// --- Selection Management ---
|
||||
|
||||
- (CPIndexPath)selectionIndexPath
|
||||
{
|
||||
return [_selectionIndexPaths count] > 0 ? [_selectionIndexPaths objectAtIndex:0] : nil;
|
||||
@@ -366,20 +260,18 @@
|
||||
|
||||
- (BOOL)setSelectionIndexPath:(CPIndexPath)indexPath
|
||||
{
|
||||
var paths = indexPath ? [indexPath] : [];
|
||||
return [self setSelectionIndexPaths:paths];
|
||||
var paths = indexPath ? [CPArray arrayWithObject:indexPath] : [CPArray array];
|
||||
return[self setSelectionIndexPaths:paths];
|
||||
}
|
||||
|
||||
- (CPArray)selectionIndexPaths
|
||||
{
|
||||
return _selectionIndexPaths;
|
||||
}
|
||||
- (CPArray)selectionIndexPaths { return _selectionIndexPaths; }
|
||||
|
||||
- (BOOL)setSelectionIndexPaths:(CPArray)indexPaths
|
||||
{
|
||||
[self _selectionWillChange];
|
||||
var result = [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO];
|
||||
[self _selectionDidChange];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -388,12 +280,12 @@
|
||||
var newPaths = indexPaths;
|
||||
|
||||
if (!newPaths)
|
||||
newPaths = [];
|
||||
newPaths = [CPArray array];
|
||||
|
||||
if (![newPaths count] && avoidEmpty)
|
||||
{
|
||||
if ([[[self arrangedObjects] childNodes] count] > 0)
|
||||
newPaths = [[CPIndexPath indexPathWithIndex:0]];
|
||||
newPaths = [CPArray arrayWithObject:[CPIndexPath indexPathWithIndex:0]];
|
||||
}
|
||||
|
||||
if ([_selectionIndexPaths isEqualToArray:newPaths])
|
||||
@@ -402,7 +294,11 @@
|
||||
_selectionIndexPaths = [newPaths copy];
|
||||
|
||||
var binderClass = [[self class] _binderClassForBinding:@"selectionIndexPaths"];
|
||||
[[binderClass getBinding:@"selectionIndexPaths" forObject:self] reverseSetValueFor:@"selectionIndexPaths"];
|
||||
if (binderClass)
|
||||
{
|
||||
var binding = [binderClass getBinding:@"selectionIndexPaths" forObject:self];
|
||||
if (binding)[binding reverseSetValueFor:@"selectionIndexPaths"];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -410,8 +306,9 @@
|
||||
- (BOOL)addSelectionIndexPaths:(CPArray)indexPaths
|
||||
{
|
||||
var newPaths = [_selectionIndexPaths mutableCopy];
|
||||
|
||||
[newPaths addObjectsFromArray:indexPaths];
|
||||
// Remove duplicates and maintain sorted order (omitted for brevity)
|
||||
|
||||
return [self setSelectionIndexPaths:newPaths];
|
||||
}
|
||||
|
||||
@@ -424,12 +321,12 @@
|
||||
|
||||
- (CPArray)selectedNodes
|
||||
{
|
||||
var nodes = [],
|
||||
var nodes = [CPMutableArray array],
|
||||
count = [_selectionIndexPaths count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var node = [[self arrangedObjects] descendantNodeAtIndexPath:_selectionIndexPaths[i]];
|
||||
var node = [[self arrangedObjects] descendantNodeAtIndexPath:[_selectionIndexPaths objectAtIndex:i]];
|
||||
if (node)
|
||||
[nodes addObject:node];
|
||||
}
|
||||
@@ -438,55 +335,70 @@
|
||||
|
||||
- (CPArray)selectedObjects
|
||||
{
|
||||
var objects = [],
|
||||
var objects = [CPMutableArray array],
|
||||
nodes = [self selectedNodes],
|
||||
count = [nodes count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
[objects addObject:[nodes[i] representedObject]];
|
||||
[objects addObject:[[nodes objectAtIndex:i] representedObject]];
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
- (BOOL)__setSelectedObjects:(CPArray)objects
|
||||
{
|
||||
// Search the tree for index paths matching the passed objects and update selection
|
||||
// (Omitted recursive search for brevity)
|
||||
return YES;
|
||||
if (!objects || [objects count] === 0)
|
||||
return[self __setSelectionIndexPaths:[CPArray array] avoidEmpty:_avoidsEmptySelection];
|
||||
|
||||
var newPaths = [CPMutableArray array];
|
||||
for (var i = 0, count = [objects count]; i < count; i++)
|
||||
{
|
||||
var path = [self _indexPathForObject:[objects objectAtIndex:i] inNode:[self arrangedObjects]];
|
||||
if (path)
|
||||
[newPaths addObject:path];
|
||||
}
|
||||
|
||||
return[self __setSelectionIndexPaths:newPaths avoidEmpty:_avoidsEmptySelection];
|
||||
}
|
||||
|
||||
// --- Adding, Inserting, Removing ---
|
||||
|
||||
- (BOOL)canInsert
|
||||
- (CPIndexPath)_indexPathForObject:(id)anObject inNode:(CPTreeNode)node
|
||||
{
|
||||
return[self isEditable];
|
||||
if ([node representedObject] === anObject && [node parentNode] != nil)
|
||||
return [node indexPath];
|
||||
|
||||
var children = [node childNodes];
|
||||
if (children)
|
||||
{
|
||||
for (var i = 0, count = [children count]; i < count; i++)
|
||||
{
|
||||
var found = [self _indexPathForObject:anObject inNode:[children objectAtIndex:i]];
|
||||
if (found)
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)canInsertChild
|
||||
{
|
||||
return [self isEditable] && [_selectionIndexPaths count] > 0;
|
||||
}
|
||||
|
||||
- (BOOL)canAddChild
|
||||
{
|
||||
return [self canInsertChild];
|
||||
}
|
||||
- (BOOL)canInsert { return[self isEditable]; }
|
||||
- (BOOL)canInsertChild { return [self isEditable] &&[_selectionIndexPaths count] > 0; }
|
||||
- (BOOL)canAddChild { return [self canInsertChild]; }
|
||||
|
||||
- (void)add:(id)sender
|
||||
{
|
||||
if (![self canInsert])
|
||||
return;
|
||||
if (![self canInsert]) return;
|
||||
|
||||
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject];
|
||||
var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject],
|
||||
selectionPath = [self selectionIndexPath];
|
||||
|
||||
var selectionPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:[[[self arrangedObjects] childNodes] count]];
|
||||
if (!selectionPath)
|
||||
selectionPath = [CPIndexPath indexPathWithIndex:[[[self arrangedObjects] childNodes] count]];
|
||||
|
||||
// Increment the last index by 1 to add *after* the current selection
|
||||
var length = [selectionPath length],
|
||||
lastIndex = [selectionPath indexAtPosition:length - 1];
|
||||
lastIndex = [selectionPath indexAtPosition:length - 1],
|
||||
insertPath = [selectionPath indexPathByRemovingLastIndex];
|
||||
|
||||
var insertPath = [selectionPath indexPathByRemovingLastIndex];
|
||||
insertPath = [insertPath indexPathByAddingIndex:lastIndex + 1];
|
||||
|
||||
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
|
||||
}
|
||||
|
||||
@@ -497,28 +409,25 @@
|
||||
|
||||
var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject],
|
||||
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]],
|
||||
childCount = [[parentNode childNodes] count];
|
||||
|
||||
var insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount];
|
||||
childCount = [[parentNode childNodes] count],
|
||||
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount];
|
||||
|
||||
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
|
||||
}
|
||||
|
||||
- (void)insert:(id)sender
|
||||
{
|
||||
if (![self canInsert])
|
||||
return;
|
||||
if (![self canInsert]) return;
|
||||
|
||||
var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject];
|
||||
var indexPath = [self selectionIndexPath] ||[CPIndexPath indexPathWithIndex:0];
|
||||
var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject],
|
||||
indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0];
|
||||
|
||||
[self insertObject:newObject atArrangedObjectIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (void)insertChild:(id)sender
|
||||
{
|
||||
if (![self canInsertChild])
|
||||
return;
|
||||
if (![self canInsertChild]) return;
|
||||
|
||||
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
|
||||
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0];
|
||||
@@ -527,7 +436,8 @@
|
||||
}
|
||||
|
||||
- (void)insertObject:(id)anObject atArrangedObjectIndexPath:(CPIndexPath)indexPath
|
||||
{[self insertObjects:[anObject] atArrangedObjectIndexPaths:[indexPath]];
|
||||
{
|
||||
[self insertObjects:[CPArray arrayWithObject:anObject] atArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]];
|
||||
}
|
||||
|
||||
- (void)insertObjects:(CPArray)objects atArrangedObjectIndexPaths:(CPArray)indexPaths
|
||||
@@ -537,35 +447,42 @@
|
||||
var count = [objects count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var object = objects[i],
|
||||
path = indexPaths[i],
|
||||
var object = [objects objectAtIndex:i],
|
||||
path = [indexPaths objectAtIndex:i],
|
||||
length = [path length];
|
||||
|
||||
if (length === 1)
|
||||
{
|
||||
// Insert at root level[_contentObject insertObject:object atIndex:[path indexAtPosition:0]];
|
||||
{[_contentObject insertObject:object atIndex:[path indexAtPosition:0]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert into a parent node's children
|
||||
var parentPath = [path indexPathByRemovingLastIndex],
|
||||
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath],
|
||||
parentObj = [parentNode representedObject],
|
||||
childIndex = [path indexAtPosition:length - 1];
|
||||
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
|
||||
|
||||
var children = [parentObj valueForKeyPath:_childrenKeyPath];
|
||||
if (!children)
|
||||
if (parentNode)
|
||||
{
|
||||
children = [CPMutableArray array];
|
||||
[parentObj setValue:children forKeyPath:_childrenKeyPath];
|
||||
var parentObj = [parentNode representedObject],
|
||||
childIndex = [path indexAtPosition:length - 1];
|
||||
|
||||
var children = [parentObj valueForKeyPath:_childrenKeyPath];
|
||||
if (!children)
|
||||
{
|
||||
children = [CPMutableArray array];
|
||||
[parentObj setValue:children forKeyPath:_childrenKeyPath];
|
||||
}
|
||||
|
||||
var mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
|
||||
|
||||
[mutableChildren insertObject:object atIndex:childIndex];
|
||||
}
|
||||
[children insertObject:object atIndex:childIndex];
|
||||
}
|
||||
}
|
||||
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
_disableSetContent = NO;
|
||||
var binding = [[self class] _binderClassForBinding:@"contentArray"];
|
||||
if (binding)
|
||||
[[binding getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
_disableSetContent = NO;
|
||||
[self _rearrangeObjects];
|
||||
|
||||
if ([self selectsInsertedObjects])[self setSelectionIndexPaths:indexPaths];
|
||||
@@ -574,12 +491,12 @@
|
||||
}
|
||||
|
||||
- (void)remove:(id)sender
|
||||
{[self removeObjectsAtArrangedObjectIndexPaths:_selectionIndexPaths];
|
||||
{
|
||||
[self removeObjectsAtArrangedObjectIndexPaths:_selectionIndexPaths];
|
||||
}
|
||||
|
||||
- (void)removeObjectAtArrangedObjectIndexPath:(CPIndexPath)indexPath
|
||||
{
|
||||
[self removeObjectsAtArrangedObjectIndexPaths:[indexPath]];
|
||||
{[self removeObjectsAtArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]];
|
||||
}
|
||||
|
||||
- (void)removeObjectsAtArrangedObjectIndexPaths:(CPArray)indexPaths
|
||||
@@ -587,47 +504,50 @@
|
||||
[self willChangeValueForKey:@"content"];
|
||||
_disableSetContent = YES;
|
||||
|
||||
// Remove in reverse order to prevent shifting indices from invalidating remaining paths
|
||||
var sortedPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)],
|
||||
count = [sortedPaths count];
|
||||
|
||||
for (var i = count - 1; i >= 0; i--)
|
||||
{
|
||||
var path = sortedPaths[i],
|
||||
var path = [sortedPaths objectAtIndex:i],
|
||||
length = [path length];
|
||||
|
||||
if (length === 1)
|
||||
{
|
||||
[_contentObject removeObjectAtIndex:[path indexAtPosition:0]];
|
||||
{[_contentObject removeObjectAtIndex:[path indexAtPosition:0]];
|
||||
}
|
||||
else
|
||||
{
|
||||
var parentPath = [path indexPathByRemovingLastIndex],
|
||||
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath],
|
||||
parentObj = [parentNode representedObject],
|
||||
childIndex = [path indexAtPosition:length - 1];
|
||||
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
|
||||
|
||||
var children = [parentObj valueForKeyPath:_childrenKeyPath];
|
||||
if (children)
|
||||
[children removeObjectAtIndex:childIndex];
|
||||
if (parentNode)
|
||||
{
|
||||
var parentObj = [parentNode representedObject],
|
||||
childIndex = [path indexAtPosition:length - 1],
|
||||
mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
|
||||
|
||||
if (mutableChildren && childIndex <[mutableChildren count])
|
||||
[mutableChildren removeObjectAtIndex:childIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
_disableSetContent = NO;
|
||||
var binding = [[self class] _binderClassForBinding:@"contentArray"];
|
||||
if (binding)
|
||||
[[binding getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
_disableSetContent = NO;
|
||||
[self _rearrangeObjects];
|
||||
[self didChangeValueForKey:@"content"];
|
||||
}
|
||||
|
||||
- (void)moveNode:(CPTreeNode)node toIndexPath:(CPIndexPath)indexPath
|
||||
{
|
||||
[self moveNodes:[node] toIndexPath:indexPath];
|
||||
[self moveNodes:[CPArray arrayWithObject:node] toIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (void)moveNodes:(CPArray)nodes toIndexPath:(CPIndexPath)startingIndexPath
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."];
|
||||
{[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -663,7 +583,8 @@ CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath
|
||||
|
||||
if (![self content] && [self automaticallyPreparesContent])
|
||||
[self prepareContent];
|
||||
else if (![self content])[self _setContentArray:[]];
|
||||
else if (![self content])
|
||||
[self _setContentArray:[CPArray array]];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* CPTreeControllerTest.j
|
||||
*
|
||||
* Test suite for CPTreeController, adapted for Cappuccino
|
||||
* Test suite for CPTreeController
|
||||
*/
|
||||
|
||||
@import <Foundation/CPArray.j>
|
||||
@@ -23,20 +23,17 @@
|
||||
|
||||
- (CPArray)makeTestTree
|
||||
{
|
||||
// Root Level
|
||||
var engineering = [OrgNode nodeWithName:@"Engineering"],
|
||||
marketing =[OrgNode nodeWithName:@"Marketing"];
|
||||
marketing = [OrgNode nodeWithName:@"Marketing"];
|
||||
|
||||
// Children of Engineering
|
||||
var webTeam = [OrgNode nodeWithName:@"Web Team"],
|
||||
backendTeam = [OrgNode nodeWithName:@"Backend Team"];
|
||||
|
||||
backendTeam = [OrgNode nodeWithName:@"Backend Team"];
|
||||
|
||||
[engineering setChildren:[CPMutableArray arrayWithObjects:webTeam, backendTeam]];
|
||||
|
||||
// Children of Web Team
|
||||
var dev1 =[OrgNode nodeWithName:@"Francisco"],
|
||||
dev2 = [OrgNode nodeWithName:@"Ross"];
|
||||
|
||||
var dev1 = [OrgNode nodeWithName:@"Francisco"],
|
||||
dev2 = [OrgNode nodeWithName:@"Ross"];
|
||||
|
||||
[webTeam setChildren:[CPMutableArray arrayWithObjects:dev1, dev2]];
|
||||
|
||||
return [CPMutableArray arrayWithObjects:engineering, marketing];
|
||||
@@ -44,23 +41,25 @@
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
// Init global CPApp used internally in AppKit
|
||||
[[CPApplication alloc] init];
|
||||
|
||||
_contentArray = [self makeTestTree];
|
||||
_treeController = [[CPTreeController alloc] init];
|
||||
[_treeController setChildrenKeyPath:@"children"];[_treeController setContent:[_contentArray copy]];
|
||||
[_treeController setChildrenKeyPath:@"children"];
|
||||
[_treeController setContent:[_contentArray copy]];
|
||||
}
|
||||
|
||||
- (void)testInitWithContent
|
||||
{[self assert:[_contentArray count] equals:[[_treeController contentArray] count]];
|
||||
{
|
||||
[self assert:[_contentArray count] equals:[[_treeController contentArray] count]];
|
||||
[self assert:[CPTreeNode class] equals:[[[self treeController] arrangedObjects] class] message:@"arranged objects should be a proxy CPTreeNode root"];
|
||||
}
|
||||
|
||||
- (void)testInitWithoutContent
|
||||
{
|
||||
var emptyController = [[CPTreeController alloc] init];
|
||||
[self assert:[] equals:[emptyController contentArray]];[self assert:0 equals:[[[emptyController arrangedObjects] childNodes] count]];
|
||||
[self assert:[CPArray array] equals:[emptyController contentArray]];
|
||||
[self assert:0 equals:[[[emptyController arrangedObjects] childNodes] count]];
|
||||
}
|
||||
|
||||
- (void)testSetContent
|
||||
@@ -75,54 +74,50 @@
|
||||
- (void)testSelectionPaths
|
||||
{
|
||||
var controller = [self treeController];
|
||||
|
||||
// Select Engineering -> Web Team (Index Path: [0, 0])
|
||||
var path = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0];[controller setSelectionIndexPath:path];
|
||||
|
||||
var selectedPath = [controller selectionIndexPath];[self assert:path equals:selectedPath];
|
||||
|
||||
|
||||
var path = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0];
|
||||
[controller setSelectionIndexPath:path];
|
||||
|
||||
var selectedPath = [controller selectionIndexPath];
|
||||
[self assert:path equals:selectedPath];
|
||||
|
||||
var selectedNodes = [controller selectedNodes];
|
||||
[self assert:1 equals:[selectedNodes count]];
|
||||
[self assert:@"Web Team" equals:[[selectedNodes[0] representedObject] name]];
|
||||
[self assert:@"Web Team" equals:[[[selectedNodes objectAtIndex:0] representedObject] name]];
|
||||
}
|
||||
|
||||
- (void)testAddChild
|
||||
{
|
||||
var controller = [self treeController];
|
||||
[controller setObjectClass:[OrgNode class]];
|
||||
|
||||
// Select Engineering -> Backend Team (Index Path:[0, 1])
|
||||
|
||||
var parentPath = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:1];
|
||||
[controller setSelectionIndexPath:parentPath];
|
||||
|
||||
// Insert a child into Backend Team
|
||||
|
||||
var newDev = [OrgNode nodeWithName:@"Tom"];
|
||||
var insertPath =[parentPath indexPathByAddingIndex:0];
|
||||
|
||||
var insertPath = [parentPath indexPathByAddingIndex:0];
|
||||
[controller insertObject:newDev atArrangedObjectIndexPath:insertPath];
|
||||
|
||||
// Validate it was added to the content
|
||||
|
||||
var engineering = [[controller contentArray] objectAtIndex:0],
|
||||
backendTeam = [[engineering children] objectAtIndex:1];
|
||||
|
||||
[self assert:1 equals:[[backendTeam children] count] message:@"Child should be added to the model object's children array"];[self assert:@"Tom" equals:[[[backendTeam children] objectAtIndex:0] name]];
|
||||
backendTeam = [[engineering children] objectAtIndex:1];
|
||||
|
||||
[self assert:1 equals:[[backendTeam children] count] message:@"Child should be added to the model object's children array"];
|
||||
[self assert:@"Tom" equals:[[[backendTeam children] objectAtIndex:0] name]];
|
||||
}
|
||||
|
||||
- (void)testInsertObjectAtArrangedObjectIndexPath
|
||||
{
|
||||
var controller = [self treeController];
|
||||
|
||||
// Insert at root level, index 1 (between Engineering and Marketing)
|
||||
var path =[CPIndexPath indexPathWithIndex:1];
|
||||
var hrDept = [OrgNode nodeWithName:@"Human Resources"];[controller insertObject:hrDept atArrangedObjectIndexPath:path];
|
||||
|
||||
|
||||
var path = [CPIndexPath indexPathWithIndex:1];
|
||||
var hrDept = [OrgNode nodeWithName:@"Human Resources"];
|
||||
[controller insertObject:hrDept atArrangedObjectIndexPath:path];
|
||||
[self assert:3 equals:[[controller contentArray] count]];
|
||||
[self assert:hrDept equals:[[controller contentArray] objectAtIndex:1]];
|
||||
|
||||
// Insert nested (Engineering -> HR)
|
||||
|
||||
var nestedPath = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0];
|
||||
var subDept =[OrgNode nodeWithName:@"Sub Dept"];
|
||||
|
||||
var subDept = [OrgNode nodeWithName:@"Sub Dept"];
|
||||
|
||||
[controller insertObject:subDept atArrangedObjectIndexPath:nestedPath];
|
||||
var engChildren = [[[controller contentArray] objectAtIndex:0] children];
|
||||
[self assert:subDept equals:[engChildren objectAtIndex:0] message:@"Object should be inserted at the correct nested index path"];
|
||||
@@ -131,61 +126,56 @@
|
||||
- (void)testRemoveObjectAtArrangedObjectIndexPath
|
||||
{
|
||||
var controller = [self treeController];
|
||||
|
||||
// Remove Engineering -> Web Team -> Francisco (Index Path: [0, 0, 0])
|
||||
|
||||
var path = [[[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0] indexPathByAddingIndex:0];
|
||||
|
||||
|
||||
[controller removeObjectAtArrangedObjectIndexPath:path];
|
||||
|
||||
|
||||
var engineering = [[controller contentArray] objectAtIndex:0],
|
||||
webTeam = [[engineering children] objectAtIndex:0];[self assert:1 equals:[[webTeam children] count] message:@"Francisco should be removed, leaving only Ross"];
|
||||
webTeam = [[engineering children] objectAtIndex:0];
|
||||
|
||||
[self assert:1 equals:[[webTeam children] count] message:@"Francisco should be removed, leaving only Ross"];
|
||||
[self assert:@"Ross" equals:[[[webTeam children] objectAtIndex:0] name]];
|
||||
}
|
||||
|
||||
- (void)testRemoveObjectsAtArrangedObjectIndexPaths
|
||||
{
|
||||
var controller = [self treeController];
|
||||
|
||||
// Remove both Engineering (0) and Marketing (1)
|
||||
var paths = [[CPIndexPath indexPathWithIndex:0],
|
||||
[CPIndexPath indexPathWithIndex:1]
|
||||
];
|
||||
|
||||
|
||||
var paths = [CPArray arrayWithObjects:[CPIndexPath indexPathWithIndex:0],[CPIndexPath indexPathWithIndex:1]];
|
||||
|
||||
[controller removeObjectsAtArrangedObjectIndexPaths:paths];
|
||||
|
||||
|
||||
[self assert:0 equals:[[controller contentArray] count] message:@"All root nodes should be removed"];
|
||||
}
|
||||
|
||||
- (void)testAvoidsEmptySelection
|
||||
{
|
||||
var controller = [self treeController];[controller setAvoidsEmptySelection:YES];
|
||||
|
||||
// Set empty selection manually
|
||||
[controller setSelectionIndexPaths:[]];
|
||||
|
||||
var controller = [self treeController];
|
||||
[controller setAvoidsEmptySelection:YES];
|
||||
[controller setSelectionIndexPaths:[CPArray array]];
|
||||
|
||||
[self assertTrue:([[controller selectionIndexPaths] count] == 1) message:@"Selection should fallback to the first item when avoidsEmptySelection is YES"];
|
||||
[self assert:[CPIndexPath indexPathWithIndex:0] equals:[controller selectionIndexPath]];
|
||||
|
||||
[controller setAvoidsEmptySelection:NO];[controller setSelectionIndexPaths:[]];
|
||||
|
||||
[controller setAvoidsEmptySelection:NO];
|
||||
[controller setSelectionIndexPaths:[CPArray array]];
|
||||
[self assertTrue:([[controller selectionIndexPaths] count] == 0) message:@"Selection should be allowed to be empty when avoidsEmptySelection is NO"];
|
||||
}
|
||||
|
||||
- (void)testChildrenKeyPathOverride
|
||||
{
|
||||
var controller = [[CPTreeController alloc] init];
|
||||
// Use a custom key path
|
||||
[controller setChildrenKeyPath:@"subItems"];
|
||||
|
||||
var data =[OrgNode nodeWithName:@"Root"];
|
||||
|
||||
var data = [OrgNode nodeWithName:@"Root"];
|
||||
[data setValue:[CPMutableArray arrayWithObject:[OrgNode nodeWithName:@"Sub"]] forKey:@"subItems"];
|
||||
|
||||
|
||||
[controller setContent:[CPMutableArray arrayWithObject:data]];
|
||||
|
||||
// Insert at [0, 0]
|
||||
|
||||
var path = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0];
|
||||
var newItem = [OrgNode nodeWithName:@"New Sub"];[controller insertObject:newItem atArrangedObjectIndexPath:path];
|
||||
|
||||
var newItem = [OrgNode nodeWithName:@"New Sub"];
|
||||
[controller insertObject:newItem atArrangedObjectIndexPath:path];
|
||||
|
||||
var subs = [data valueForKey:@"subItems"];
|
||||
[self assert:2 equals:[subs count]];
|
||||
[self assert:newItem equals:[subs objectAtIndex:0] message:@"Object should be inserted using the custom childrenKeyPath"];
|
||||
@@ -193,36 +183,36 @@
|
||||
|
||||
- (void)testContentBinding
|
||||
{
|
||||
var controller = [[CPTreeController alloc] init];[controller bind:@"contentArray" toObject:self withKeyPath:@"contentArray" options:nil];
|
||||
var controller = [[CPTreeController alloc] init];
|
||||
|
||||
[controller bind:@"contentArray" toObject:self withKeyPath:@"contentArray" options:nil];
|
||||
|
||||
[self assert:[self contentArray] equals:[controller contentArray]];
|
||||
|
||||
// Verify proxy tree rebuilt
|
||||
[self assert:2 equals:[[[controller arrangedObjects] childNodes] count]];
|
||||
}
|
||||
|
||||
- (void)testSelectedObjects
|
||||
{
|
||||
var controller =[self treeController];
|
||||
|
||||
// Select Marketing [1]
|
||||
var path =[CPIndexPath indexPathWithIndex:1];
|
||||
var controller = [self treeController];
|
||||
|
||||
var path = [CPIndexPath indexPathWithIndex:1];
|
||||
[controller setSelectionIndexPath:path];
|
||||
|
||||
var selectedObjects =[controller selectedObjects];
|
||||
[self assert:1 equals:[selectedObjects count]];[self assert:@"Marketing" equals:[selectedObjects[0] name]];
|
||||
|
||||
var selectedObjects = [controller selectedObjects];
|
||||
[self assert:1 equals:[selectedObjects count]];
|
||||
[self assert:@"Marketing" equals:[[selectedObjects objectAtIndex:0] name]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
/*
|
||||
* Dummy Model Class for Testing
|
||||
*/
|
||||
@implementation OrgNode : CPObject
|
||||
{
|
||||
CPString _name @accessors(property=name);
|
||||
CPMutableArray _children @accessors(property=children);
|
||||
CPMutableArray _subItems @accessors(property=subItems); // For testing custom key paths
|
||||
CPMutableArray _subItems @accessors(property=subItems);
|
||||
}
|
||||
|
||||
+ (id)nodeWithName:(CPString)aName
|
||||
@@ -236,7 +226,7 @@
|
||||
{
|
||||
_name = aName;
|
||||
_children = [CPMutableArray array];
|
||||
_subItems =[CPMutableArray array];
|
||||
_subItems = [CPMutableArray array];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -244,7 +234,8 @@
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return [CPString stringWithFormat:@"<OrgNode %@>", [self name]];
|
||||
return[CPString stringWithFormat:@"<OrgNode %@>", [self name]];
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user