2013-06-23 01:32:48 +00:00
|
|
|
/*
|
2014-02-01 22:54:30 +00:00
|
|
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
2013-06-23 01:32:48 +00:00
|
|
|
|
|
|
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
|
|
|
which should be included with this package. The terms are also available at
|
|
|
|
http://www.hardcoded.net/licenses/bsd_license
|
|
|
|
*/
|
|
|
|
|
|
|
|
#import "HSOutline.h"
|
|
|
|
#import "Utils.h"
|
|
|
|
|
|
|
|
#define CHILDREN_COUNT_PROPERTY @"children_count"
|
|
|
|
|
|
|
|
@implementation HSOutline
|
|
|
|
- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(HSOutlineView *)aView
|
|
|
|
{
|
|
|
|
self = [super initWithPyRef:aPyRef wrapperClass:aWrapperClass callbackClassName:aCallbackClassName view:aView];
|
|
|
|
itemData = [[NSMutableDictionary dictionary] retain];
|
|
|
|
/* Dictionaries don't retain its keys because it copies them. Our items are NSIndexPath and when
|
|
|
|
an index path has the same value, it's the same instance. Before OS X 10.7, all these instances
|
|
|
|
stayed in memory, so we didn't need to worry about retaining them. Hoever, it seems now that
|
|
|
|
index path instances are sometimes released. Oops. So, we now need to retain our index path
|
|
|
|
instances and that's why we use itemRetainer.
|
|
|
|
|
|
|
|
In fact, it seems that unlike what the doc says, it's not true that two index paths with the
|
|
|
|
same value will always be the same instance.
|
|
|
|
*/
|
|
|
|
itemRetainer = [[NSMutableSet set] retain];
|
|
|
|
if (([[self view] outlineTableColumn] == nil) && ([[[self view] tableColumns] count] > 0)) {
|
|
|
|
[[self view] setOutlineTableColumn:[[[self view] tableColumns] objectAtIndex:0]];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[itemData release];
|
|
|
|
[itemRetainer release];
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (HSOutlineView *)view
|
|
|
|
{
|
|
|
|
return (HSOutlineView *)view;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setView:(HSOutlineView *)aOutlineView
|
|
|
|
{
|
|
|
|
if ([self view] != nil) {
|
|
|
|
[[self view] setDataSource:nil];
|
|
|
|
[[self view] setDelegate:nil];
|
|
|
|
}
|
|
|
|
[super setView:aOutlineView];
|
|
|
|
if (aOutlineView != nil) {
|
|
|
|
[aOutlineView setDataSource:self];
|
|
|
|
[aOutlineView setDelegate:self];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (PyOutline *)model
|
|
|
|
{
|
|
|
|
return (PyOutline *)model;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Private */
|
|
|
|
- (void)setPySelection
|
|
|
|
{
|
|
|
|
NSMutableArray *paths = [NSMutableArray array];
|
|
|
|
NSIndexSet *indexes = [[self view] selectedRowIndexes];
|
|
|
|
NSInteger i = [indexes firstIndex];
|
|
|
|
while (i != NSNotFound) {
|
|
|
|
NSIndexPath *path = [[self view] itemAtRow:i];
|
|
|
|
[paths addObject:p2a(path)];
|
|
|
|
i = [indexes indexGreaterThanIndex:i];
|
|
|
|
}
|
|
|
|
[[self model] setSelectedPaths:paths];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSIndexPath *)internalizedPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
/* Because NSIndexPath stopped guaranteeing that the same paths always were represented by the
|
|
|
|
same instances, we have to make sure, when we manipulate paths, that we manipulate the same
|
|
|
|
instances as those that were given by outlineView:child:ofItem:
|
|
|
|
*/
|
|
|
|
NSIndexPath *result = [itemRetainer member:path];
|
|
|
|
if (result == nil) {
|
|
|
|
result = path;
|
|
|
|
[itemData setObject:[NSMutableDictionary dictionary] forKey:result];
|
|
|
|
[itemRetainer addObject:result];
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Public */
|
|
|
|
- (void)refresh
|
|
|
|
{
|
|
|
|
[itemData removeAllObjects];
|
|
|
|
// We can't get rid of our instances just yet, we have to wait until after reloadData
|
|
|
|
NSSet *oldRetainer = itemRetainer;
|
|
|
|
itemRetainer = [[NSMutableSet set] retain];
|
|
|
|
[[self view] setDelegate:nil];
|
|
|
|
[[self view] reloadData];
|
|
|
|
[[self view] setDelegate:self];
|
2013-10-20 20:01:59 +00:00
|
|
|
/* Item retainer and releasing
|
|
|
|
|
|
|
|
In theory, [oldRetainer release] should work, but in practice, doing so causes occasional
|
|
|
|
crashes during drag & drop, which I guess keep the reference of an item a bit longer than it
|
|
|
|
should. This is why we autorelease here. See #354.
|
|
|
|
*/
|
|
|
|
[oldRetainer autorelease];
|
2013-06-23 01:32:48 +00:00
|
|
|
[self updateSelection];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *)selectedIndexPaths
|
|
|
|
{
|
|
|
|
NSArray *arrayPaths = [[self model] selectedPaths];
|
|
|
|
NSMutableArray *result = [NSMutableArray array];
|
|
|
|
for (NSArray *arrayPath in arrayPaths) {
|
|
|
|
[result addObject:[self internalizedPath:a2p(arrayPath)]];
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)startEditing
|
|
|
|
{
|
|
|
|
[[self view] startEditing];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)stopEditing
|
|
|
|
{
|
|
|
|
[[self view] stopEditing];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateSelection
|
|
|
|
{
|
|
|
|
[[self view] updateSelection];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)expandItem:(NSIndexPath *)item
|
|
|
|
{
|
|
|
|
[[self view] ensureExpanded:item];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Caching */
|
|
|
|
- (id)property:(NSString *)property valueAtPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
NSMutableDictionary *props = [itemData objectForKey:path];
|
|
|
|
id value = [props objectForKey:property];
|
|
|
|
if (value == nil) {
|
|
|
|
value = [[self model] property:property valueAtPath:p2a(path)];
|
|
|
|
if (value == nil) {
|
|
|
|
value = [NSNull null];
|
|
|
|
}
|
|
|
|
[props setObject:value forKey:property];
|
|
|
|
}
|
|
|
|
if (value == [NSNull null]) {
|
|
|
|
value = nil;
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setProperty:(NSString *)property value:(id)value atPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
NSMutableDictionary *props = [itemData objectForKey:path];
|
|
|
|
[props removeObjectForKey:property];
|
|
|
|
[[self model] setProperty:property value:value atPath:p2a(path)];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)stringProperty:(NSString *)property valueAtPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
return [self property:property valueAtPath:path];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setStringProperty:(NSString *)property value:(NSString *)value atPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
[self setProperty:property value:value atPath:path];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)boolProperty:(NSString *)property valueAtPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
NSNumber *value = [self property:property valueAtPath:path];
|
|
|
|
return [value boolValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setBoolProperty:(NSString *)property value:(BOOL)value atPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
[self setProperty:property value:[NSNumber numberWithBool:value] atPath:path];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSInteger)intProperty:(NSString *)property valueAtPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
NSNumber *value = [self property:property valueAtPath:path];
|
|
|
|
return [value intValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setIntProperty:(NSString *)property value:(int)value atPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
[self setProperty:property value:[NSNumber numberWithInt:value] atPath:path];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)refreshItemAtPath:(NSIndexPath *)path
|
|
|
|
{
|
|
|
|
NSMutableDictionary *props = [itemData objectForKey:path];
|
|
|
|
[props removeAllObjects];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* NSOutlineView data source */
|
|
|
|
|
|
|
|
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
|
|
|
|
{
|
|
|
|
return [self intProperty:CHILDREN_COUNT_PROPERTY valueAtPath:(NSIndexPath *)item];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
|
|
|
|
{
|
|
|
|
NSIndexPath *parent = item;
|
|
|
|
NSIndexPath *path = parent == nil ? [NSIndexPath indexPathWithIndex:index] : [parent indexPathByAddingIndex:index];
|
|
|
|
return [self internalizedPath:path];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)theOutlineView isItemExpandable:(id)item
|
|
|
|
{
|
|
|
|
return [self outlineView:[self view] numberOfChildrenOfItem:item] > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)column item:(id)item
|
|
|
|
{
|
|
|
|
return [[self model] canEditProperty:[column identifier] atPath:p2a((NSIndexPath *)item)];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)column byItem:(id)item
|
|
|
|
{
|
|
|
|
return [self property:[column identifier] valueAtPath:(NSIndexPath *)item];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)value forTableColumn:(NSTableColumn *)column byItem:(id)item
|
|
|
|
{
|
|
|
|
[self setProperty:[column identifier] value:value atPath:(NSIndexPath *)item];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We need to change the model selection at both IsChanging and DidChange. We need to set the
|
|
|
|
model selection at IsChanging before of the arrow clicking. The action launched by this little arrow
|
|
|
|
is performed before DidChange. However, when using the arrow to change the selection, IsChanging is
|
|
|
|
never called
|
|
|
|
*/
|
|
|
|
- (void)outlineViewSelectionIsChanging:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
[self setPySelection];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
[self setPySelection];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* HSOutlineView delegate */
|
|
|
|
- (NSIndexPath *)selectedIndexPath
|
|
|
|
{
|
|
|
|
NSArray *paths = [self selectedIndexPaths];
|
|
|
|
if ([paths count] > 0) {
|
|
|
|
return [paths objectAtIndex:0];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)dataForCopyToPasteboard
|
|
|
|
{
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)outlineViewDidEndEditing:(HSOutlineView *)outlineView
|
|
|
|
{
|
|
|
|
[[self model] saveEdits];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)outlineViewWasDoubleClicked:(HSOutlineView *)outlineView
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)outlineViewCancelsEdition:(HSOutlineView *)outlineView
|
|
|
|
{
|
|
|
|
[[self model] cancelEdits];
|
|
|
|
}
|
|
|
|
@end
|