mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
692 lines
18 KiB
Plaintext
692 lines
18 KiB
Plaintext
/*
|
|
* CPTreeController.j
|
|
* AppKit
|
|
*
|
|
* Daniel Boehringer Mar/2026
|
|
*
|
|
* 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/CPArray.j>
|
|
@import <Foundation/CPIndexPath.j>
|
|
@import "CPObjectController.j"
|
|
@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;
|
|
BOOL _preservesSelection;
|
|
BOOL _selectsInsertedObjects;
|
|
BOOL _alwaysUsesMultipleValuesMarker;
|
|
|
|
CPString _childrenKeyPath;
|
|
CPString _countKeyPath;
|
|
CPString _leafKeyPath;
|
|
|
|
CPArray _sortDescriptors;
|
|
id _arrangedObjects; // The proxy root node containing the tree
|
|
|
|
CPArray _selectionIndexPaths; // Array of CPIndexPath objects
|
|
|
|
BOOL _disableSetContent;
|
|
}
|
|
|
|
+ (void)initialize
|
|
{
|
|
if (self !== [CPTreeController class])
|
|
return;
|
|
|
|
[self exposeBinding:@"contentArray"];
|
|
[self exposeBinding:@"sortDescriptors"];
|
|
}
|
|
|
|
+ (CPSet)keyPathsForValuesAffectingContentArray
|
|
{
|
|
return[CPSet setWithObjects:@"content"];
|
|
}
|
|
|
|
+ (CPSet)keyPathsForValuesAffectingArrangedObjects
|
|
{
|
|
return [CPSet setWithObjects:@"content", @"sortDescriptors", @"childrenKeyPath"];
|
|
}
|
|
|
|
+ (CPSet)keyPathsForValuesAffectingSelectionIndexPath
|
|
{
|
|
return[CPSet setWithObjects:@"selectionIndexPaths"];
|
|
}
|
|
|
|
+ (CPSet)keyPathsForValuesAffectingSelectedObjects
|
|
{
|
|
return [CPSet setWithObjects:@"selectionIndexPaths"];
|
|
}
|
|
|
|
+ (CPSet)keyPathsForValuesAffectingSelectedNodes
|
|
{
|
|
return [CPSet setWithObjects:@"selectionIndexPaths"];
|
|
}
|
|
|
|
+ (CPSet)keyPathsForValuesAffectingCanAddChild
|
|
{
|
|
return[CPSet setWithObjects:@"selectionIndexPaths"];
|
|
}
|
|
|
|
+ (CPSet)keyPathsForValuesAffectingCanInsert
|
|
{
|
|
return[CPSet setWithObjects:@"selectionIndexPaths"];
|
|
}
|
|
|
|
+ (CPSet)keyPathsForValuesAffectingCanInsertChild
|
|
{
|
|
return [CPSet setWithObjects:@"selectionIndexPaths"];
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_preservesSelection = YES;
|
|
_selectsInsertedObjects = YES;
|
|
_avoidsEmptySelection = YES;
|
|
_alwaysUsesMultipleValuesMarker = NO;
|
|
|
|
_childrenKeyPath = @"children";
|
|
|
|
[self _init];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)_init
|
|
{
|
|
_sortDescriptors = [CPArray array];
|
|
_selectionIndexPaths = [CPArray array];
|
|
_arrangedObjects = [[CPTreeNode alloc] initWithRepresentedObject:nil];
|
|
}
|
|
|
|
- (void)prepareContent
|
|
{
|
|
[self _setContentArray:[[self newObject]]];
|
|
}
|
|
|
|
// --- Properties ---
|
|
|
|
- (BOOL)preservesSelection
|
|
{
|
|
return _preservesSelection;
|
|
}
|
|
|
|
- (void)setPreservesSelection:(BOOL)value
|
|
{
|
|
_preservesSelection = 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;
|
|
}
|
|
|
|
- (void)setSortDescriptors:(CPArray)value
|
|
{
|
|
if (_sortDescriptors === value)
|
|
return;
|
|
|
|
_sortDescriptors = [value copy];
|
|
[self _rearrangeObjects];
|
|
}
|
|
|
|
// --- Key Paths ---
|
|
|
|
- (CPString)childrenKeyPath
|
|
{
|
|
return _childrenKeyPath;
|
|
}
|
|
|
|
- (void)setChildrenKeyPath:(CPString)aKeyPath
|
|
{
|
|
if (_childrenKeyPath === aKeyPath)
|
|
return;
|
|
|
|
_childrenKeyPath = aKeyPath;
|
|
[self rearrangeObjects];
|
|
}
|
|
|
|
- (CPString)countKeyPath
|
|
{
|
|
return _countKeyPath;
|
|
}
|
|
|
|
- (void)setCountKeyPath:(CPString)aKeyPath
|
|
{
|
|
_countKeyPath = 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 ---
|
|
|
|
- (void)setContent:(id)value
|
|
{
|
|
if (_disableSetContent)
|
|
return;
|
|
|
|
if (value == nil)
|
|
value = [];
|
|
|
|
if (![value isKindOfClass:[CPArray class]])
|
|
value = [value];
|
|
|
|
var oldSelectedObjects = nil,
|
|
oldSelectionIndexPaths = nil;
|
|
|
|
if ([self preservesSelection])
|
|
oldSelectedObjects = [self selectedObjects];
|
|
else
|
|
oldSelectionIndexPaths = [self selectionIndexPaths];
|
|
|
|
_contentObject = value;
|
|
|
|
[self _rearrangeObjects];
|
|
|
|
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)rearrangeObjects
|
|
{
|
|
[self willChangeValueForKey:@"arrangedObjects"];
|
|
[self _rearrangeObjects];
|
|
[self didChangeValueForKey:@"arrangedObjects"];
|
|
}
|
|
|
|
- (void)_rearrangeObjects
|
|
{
|
|
var oldSelectedObjects = nil,
|
|
oldSelectionIndexPaths = nil;
|
|
|
|
if ([self preservesSelection])
|
|
oldSelectedObjects = [self selectedObjects];
|
|
else
|
|
oldSelectionIndexPaths = [self selectionIndexPaths];
|
|
|
|
// Rebuild the proxy tree from the content using _childrenKeyPath and _sortDescriptors[self __rebuildArrangedObjectsTree];
|
|
|
|
if ([self preservesSelection])
|
|
[self __setSelectedObjects:oldSelectedObjects];
|
|
else
|
|
[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
|
|
}
|
|
|
|
- (void)__rebuildArrangedObjectsTree
|
|
{
|
|
var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil];
|
|
var contentArray = [self contentArray];
|
|
|
|
if (contentArray && [contentArray count] > 0)
|
|
{
|
|
var children = [self _buildTreeNodesForObjects:contentArray];
|
|
[[rootNode mutableChildNodes] addObjectsFromArray:children];
|
|
}
|
|
|
|
_arrangedObjects = rootNode;
|
|
}
|
|
|
|
// Recursively builds the node tree mapping model objects to CPTreeNodes
|
|
- (CPArray)_buildTreeNodesForObjects:(CPArray)objects
|
|
{
|
|
var count = [objects count];
|
|
if (count === 0)
|
|
return[];
|
|
|
|
var sortedObjects = objects;
|
|
if ([_sortDescriptors count] > 0)
|
|
sortedObjects = [objects sortedArrayUsingDescriptors:_sortDescriptors];
|
|
|
|
var nodes = [CPMutableArray arrayWithCapacity:count];
|
|
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var obj = sortedObjects[i],
|
|
node = [[CPTreeNode alloc] initWithRepresentedObject:obj];
|
|
|
|
if (_childrenKeyPath)
|
|
{
|
|
var childObjects = [obj valueForKeyPath:_childrenKeyPath];
|
|
if (childObjects && [childObjects count] > 0)
|
|
{
|
|
var childNodes = [self _buildTreeNodesForObjects:childObjects];
|
|
[[node mutableChildNodes] addObjectsFromArray:childNodes];
|
|
}
|
|
}
|
|
|
|
[nodes addObject:node];
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
// --- Selection Management ---
|
|
|
|
- (CPIndexPath)selectionIndexPath
|
|
{
|
|
return [_selectionIndexPaths count] > 0 ? [_selectionIndexPaths objectAtIndex:0] : nil;
|
|
}
|
|
|
|
- (BOOL)setSelectionIndexPath:(CPIndexPath)indexPath
|
|
{
|
|
var paths = indexPath ? [indexPath] : [];
|
|
return [self setSelectionIndexPaths:paths];
|
|
}
|
|
|
|
- (CPArray)selectionIndexPaths
|
|
{
|
|
return _selectionIndexPaths;
|
|
}
|
|
|
|
- (BOOL)setSelectionIndexPaths:(CPArray)indexPaths
|
|
{
|
|
[self _selectionWillChange];
|
|
var result = [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO];
|
|
[self _selectionDidChange];
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)__setSelectionIndexPaths:(CPArray)indexPaths avoidEmpty:(BOOL)avoidEmpty
|
|
{
|
|
var newPaths = indexPaths;
|
|
|
|
if (!newPaths)
|
|
newPaths = [];
|
|
|
|
if (![newPaths count] && avoidEmpty)
|
|
{
|
|
if ([[[self arrangedObjects] childNodes] count] > 0)
|
|
newPaths = [[CPIndexPath indexPathWithIndex:0]];
|
|
}
|
|
|
|
if ([_selectionIndexPaths isEqualToArray:newPaths])
|
|
return NO;
|
|
|
|
_selectionIndexPaths = [newPaths copy];
|
|
|
|
var binderClass = [[self class] _binderClassForBinding:@"selectionIndexPaths"];
|
|
[[binderClass getBinding:@"selectionIndexPaths" forObject:self] reverseSetValueFor:@"selectionIndexPaths"];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)addSelectionIndexPaths:(CPArray)indexPaths
|
|
{
|
|
var newPaths = [_selectionIndexPaths mutableCopy];
|
|
[newPaths addObjectsFromArray:indexPaths];
|
|
// Remove duplicates and maintain sorted order (omitted for brevity)
|
|
return [self setSelectionIndexPaths:newPaths];
|
|
}
|
|
|
|
- (BOOL)removeSelectionIndexPaths:(CPArray)indexPaths
|
|
{
|
|
var newPaths = [_selectionIndexPaths mutableCopy];
|
|
[newPaths removeObjectsInArray:indexPaths];
|
|
return[self setSelectionIndexPaths:newPaths];
|
|
}
|
|
|
|
- (CPArray)selectedNodes
|
|
{
|
|
var nodes = [],
|
|
count = [_selectionIndexPaths count];
|
|
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var node = [[self arrangedObjects] descendantNodeAtIndexPath:_selectionIndexPaths[i]];
|
|
if (node)
|
|
[nodes addObject:node];
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
- (CPArray)selectedObjects
|
|
{
|
|
var objects = [],
|
|
nodes = [self selectedNodes],
|
|
count = [nodes count];
|
|
|
|
for (var i = 0; i < count; i++)
|
|
[objects addObject:[nodes[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;
|
|
}
|
|
|
|
// --- Adding, Inserting, Removing ---
|
|
|
|
- (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;
|
|
|
|
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject];
|
|
|
|
var selectionPath = [self selectionIndexPath] || [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];
|
|
|
|
var insertPath = [selectionPath indexPathByRemovingLastIndex];
|
|
insertPath = [insertPath indexPathByAddingIndex:lastIndex + 1];
|
|
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
|
|
}
|
|
|
|
- (void)addChild:(id)sender
|
|
{
|
|
if (![self canAddChild])
|
|
return;
|
|
|
|
var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject],
|
|
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]],
|
|
childCount = [[parentNode childNodes] count];
|
|
|
|
var insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount];
|
|
|
|
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
|
|
}
|
|
|
|
- (void)insert:(id)sender
|
|
{
|
|
if (![self canInsert])
|
|
return;
|
|
|
|
var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject];
|
|
var indexPath = [self selectionIndexPath] ||[CPIndexPath indexPathWithIndex:0];
|
|
|
|
[self insertObject:newObject atArrangedObjectIndexPath:indexPath];
|
|
}
|
|
|
|
- (void)insertChild:(id)sender
|
|
{
|
|
if (![self canInsertChild])
|
|
return;
|
|
|
|
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
|
|
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0];
|
|
|
|
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
|
|
}
|
|
|
|
- (void)insertObject:(id)anObject atArrangedObjectIndexPath:(CPIndexPath)indexPath
|
|
{[self insertObjects:[anObject] atArrangedObjectIndexPaths:[indexPath]];
|
|
}
|
|
|
|
- (void)insertObjects:(CPArray)objects atArrangedObjectIndexPaths:(CPArray)indexPaths
|
|
{[self willChangeValueForKey:@"content"];
|
|
_disableSetContent = YES;
|
|
|
|
var count = [objects count];
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var object = objects[i],
|
|
path = indexPaths[i],
|
|
length = [path length];
|
|
|
|
if (length === 1)
|
|
{
|
|
// Insert at root level[_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];
|
|
|
|
var children = [parentObj valueForKeyPath:_childrenKeyPath];
|
|
if (!children)
|
|
{
|
|
children = [CPMutableArray array];
|
|
[parentObj setValue:children forKeyPath:_childrenKeyPath];
|
|
}
|
|
[children insertObject:object atIndex:childIndex];
|
|
}
|
|
}
|
|
|
|
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
|
_disableSetContent = NO;
|
|
|
|
[self _rearrangeObjects];
|
|
|
|
if ([self selectsInsertedObjects])[self setSelectionIndexPaths:indexPaths];
|
|
|
|
[self didChangeValueForKey:@"content"];
|
|
}
|
|
|
|
- (void)remove:(id)sender
|
|
{[self removeObjectsAtArrangedObjectIndexPaths:_selectionIndexPaths];
|
|
}
|
|
|
|
- (void)removeObjectAtArrangedObjectIndexPath:(CPIndexPath)indexPath
|
|
{
|
|
[self removeObjectsAtArrangedObjectIndexPaths:[indexPath]];
|
|
}
|
|
|
|
- (void)removeObjectsAtArrangedObjectIndexPaths:(CPArray)indexPaths
|
|
{
|
|
[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],
|
|
length = [path length];
|
|
|
|
if (length === 1)
|
|
{
|
|
[_contentObject removeObjectAtIndex:[path indexAtPosition:0]];
|
|
}
|
|
else
|
|
{
|
|
var parentPath = [path indexPathByRemovingLastIndex],
|
|
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath],
|
|
parentObj = [parentNode representedObject],
|
|
childIndex = [path indexAtPosition:length - 1];
|
|
|
|
var children = [parentObj valueForKeyPath:_childrenKeyPath];
|
|
if (children)
|
|
[children removeObjectAtIndex:childIndex];
|
|
}
|
|
}
|
|
|
|
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
|
_disableSetContent = NO;
|
|
|
|
[self _rearrangeObjects];
|
|
[self didChangeValueForKey:@"content"];
|
|
}
|
|
|
|
- (void)moveNode:(CPTreeNode)node toIndexPath:(CPIndexPath)indexPath
|
|
{
|
|
[self moveNodes:[node] toIndexPath:indexPath];
|
|
}
|
|
|
|
- (void)moveNodes:(CPArray)nodes toIndexPath:(CPIndexPath)startingIndexPath
|
|
{
|
|
[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."];
|
|
}
|
|
|
|
@end
|
|
|
|
var CPTreeControllerAvoidsEmptySelection = @"CPTreeControllerAvoidsEmptySelection",
|
|
CPTreeControllerPreservesSelection = @"CPTreeControllerPreservesSelection",
|
|
CPTreeControllerSelectsInsertedObjects = @"CPTreeControllerSelectsInsertedObjects",
|
|
CPTreeControllerAlwaysUsesMultipleValuesMarker = @"CPTreeControllerAlwaysUsesMultipleValuesMarker",
|
|
CPTreeControllerChildrenKeyPath = @"CPTreeControllerChildrenKeyPath",
|
|
CPTreeControllerCountKeyPath = @"CPTreeControllerCountKeyPath",
|
|
CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath";
|
|
|
|
@implementation CPTreeController (CPCoding)
|
|
|
|
- (id)initWithCoder:(CPCoder)aCoder
|
|
{
|
|
self = [super initWithCoder:aCoder];
|
|
|
|
if (self)
|
|
{
|
|
_avoidsEmptySelection = [aCoder decodeBoolForKey:CPTreeControllerAvoidsEmptySelection];
|
|
_preservesSelection = [aCoder decodeBoolForKey:CPTreeControllerPreservesSelection];
|
|
_selectsInsertedObjects = [aCoder decodeBoolForKey:CPTreeControllerSelectsInsertedObjects];
|
|
_alwaysUsesMultipleValuesMarker = [aCoder decodeBoolForKey:CPTreeControllerAlwaysUsesMultipleValuesMarker];
|
|
|
|
_childrenKeyPath = [aCoder decodeObjectForKey:CPTreeControllerChildrenKeyPath] || @"children";
|
|
_countKeyPath = [aCoder decodeObjectForKey:CPTreeControllerCountKeyPath];
|
|
_leafKeyPath = [aCoder decodeObjectForKey:CPTreeControllerLeafKeyPath];
|
|
|
|
_sortDescriptors = [CPArray array];
|
|
_selectionIndexPaths = [CPArray array];
|
|
_arrangedObjects = [[CPTreeNode alloc] initWithRepresentedObject:nil];
|
|
|
|
if (![self content] && [self automaticallyPreparesContent])
|
|
[self prepareContent];
|
|
else if (![self content])[self _setContentArray:[]];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
[super encodeWithCoder:aCoder];
|
|
|
|
[aCoder encodeBool:_avoidsEmptySelection forKey:CPTreeControllerAvoidsEmptySelection];
|
|
[aCoder encodeBool:_preservesSelection forKey:CPTreeControllerPreservesSelection];
|
|
[aCoder encodeBool:_selectsInsertedObjects forKey:CPTreeControllerSelectsInsertedObjects];
|
|
[aCoder encodeBool:_alwaysUsesMultipleValuesMarker forKey:CPTreeControllerAlwaysUsesMultipleValuesMarker];
|
|
[aCoder encodeObject:_childrenKeyPath forKey:CPTreeControllerChildrenKeyPath];
|
|
[aCoder encodeObject:_countKeyPath forKey:CPTreeControllerCountKeyPath];
|
|
[aCoder encodeObject:_leafKeyPath forKey:CPTreeControllerLeafKeyPath];
|
|
}
|
|
|
|
- (void)awakeFromCib
|
|
{
|
|
[self _selectionWillChange];
|
|
[self _selectionDidChange];
|
|
}
|
|
|
|
@end
|