mirror of
https://github.com/darlinghq/darling-cocotron.git
synced 2024-11-27 05:50:27 +00:00
848 lines
30 KiB
Objective-C
848 lines
30 KiB
Objective-C
/* Copyright (c) 2006-2007 Christopher J. W. Lloyd <cjwl@objc.net>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
#import <AppKit/NSOutlineView.h>
|
|
#import <AppKit/NSInterfaceStyle.h>
|
|
#import <AppKit/NSGraphicsStyle.h>
|
|
#import <AppKit/NSImage.h>
|
|
#import <AppKit/NSColor.h>
|
|
#import <AppKit/NSImageCell.h>
|
|
#import <AppKit/NSTextFieldCell.h>
|
|
#import <AppKit/NSTableColumn.h>
|
|
#import <AppKit/NSStringDrawing.h>
|
|
#import <AppKit/NSButtonCell.h>
|
|
#import <AppKit/NSGraphicsContextFunctions.h>
|
|
#import <Foundation/NSKeyedArchiver.h>
|
|
#import <AppKit/NSRaise.h>
|
|
|
|
NSString * const NSOutlineViewItemWillExpandNotification=@"NSOutlineViewItemWillExpandNotification";
|
|
NSString * const NSOutlineViewItemDidExpandNotification=@"NSOutlineViewItemDidExpandNotification";
|
|
NSString * const NSOutlineViewItemWillCollapseNotification=@"NSOutlineViewItemWillCollapseNotification";
|
|
NSString * const NSOutlineViewItemDidCollapseNotification=@"NSOutlineViewItemDidCollapseNotification";
|
|
|
|
NSString * const NSOutlineViewColumnDidMoveNotification=@"NSOutlineViewColumnDidMoveNotification";
|
|
NSString * const NSOutlineViewColumnDidResizeNotification=@"NSOutlineViewColumnDidResizeNotification";
|
|
|
|
NSString * const NSOutlineViewSelectionDidChangeNotification=@"NSOutlineViewSelectionDidChangeNotification";
|
|
NSString * const NSOutlineViewSelectionIsChangingNotification=@"NSOutlineViewSelectionIsChangingNotification";
|
|
|
|
NSString *const NSOutlineViewDisclosureButtonKey = @"NSOutlineViewDisclosureButtonKey";
|
|
|
|
// We probably don't want this public, but NSOutlineView needs it, and it would prove invaluable to
|
|
// other subclasses of NSTableView.
|
|
@interface NSTableView(NSTableView_notifications)
|
|
|
|
-(BOOL)delegateShouldSelectTableColumn:(NSTableColumn *)tableColumn ;
|
|
-(BOOL)delegateShouldSelectRow:(NSInteger)row;
|
|
-(BOOL)delegateShouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;
|
|
-(BOOL)delegateSelectionShouldChange;
|
|
-(void)noteSelectionIsChanging;
|
|
-(void)noteSelectionDidChange;
|
|
-(void)noteColumnDidResizeWithOldWidth:(CGFloat)oldWidth;
|
|
-(id)dataSourceObjectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;
|
|
-(BOOL)dataSourceCanSetObjectValue;
|
|
-(void)dataSourceSetObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;
|
|
|
|
-(void)drawHighlightedSelectionForColumn:(NSInteger)column row:(NSInteger)row inRect:(NSRect)rect;
|
|
|
|
@end
|
|
|
|
@implementation NSOutlineView
|
|
|
|
static inline BOOL isItemExpanded(NSOutlineView *self,id item){
|
|
return (BOOL)((unsigned)NSMapGet(self->_itemToExpansionState, item));
|
|
}
|
|
|
|
static inline NSInteger numberOfChildrenOfItemAndReload(NSOutlineView *self,id item,BOOL reload){
|
|
NSInteger result;
|
|
|
|
if(!reload)
|
|
result=(NSInteger)NSMapGet(self->_itemToNumberOfChildren,item);
|
|
else {
|
|
result=[self->_dataSource outlineView:self numberOfChildrenOfItem:item];
|
|
|
|
NSMapInsert(self->_itemToNumberOfChildren,item,(void *)result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static inline id childOfItemAtIndex(NSOutlineView *self,id item,NSInteger index){
|
|
#if 1
|
|
//NSLog(@"%s %d",__FILE__,__LINE__);
|
|
id result=[self->_dataSource outlineView:self child:index ofItem:item];
|
|
|
|
//NSLog(@"item %@ child %d = %@",item,index,result);
|
|
|
|
return result;
|
|
#else // broken
|
|
if(item==nil)
|
|
return [self->_dataSource outlineView:self child:index ofItem:item];
|
|
else {
|
|
NSInteger row=(NSInteger)NSMapGet(self->_itemToRow,item);
|
|
|
|
return (id)NSMapGet(self->_rowToItem,(void *)(row+1+index));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
-(void)encodeWithCoder:(NSCoder *)coder {
|
|
NSUnimplementedMethod();
|
|
}
|
|
|
|
-(void)invalidateRowCache {
|
|
_numberOfCachedRows = 0;
|
|
}
|
|
|
|
-initWithCoder:(NSCoder *)coder {
|
|
[super initWithCoder:coder];
|
|
|
|
if([coder allowsKeyedCoding]){
|
|
NSKeyedUnarchiver *keyed=(NSKeyedUnarchiver *)coder;
|
|
|
|
_rowToItem=NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0);
|
|
_itemToRow = NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSIntegerMapValueCallBacks, 0);
|
|
_itemToParent=NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0);
|
|
_itemToLevel = NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSIntegerMapValueCallBacks, 0);
|
|
_itemToExpansionState = NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSIntegerMapValueCallBacks, 0);
|
|
_itemToNumberOfChildren = NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSIntegerMapValueCallBacks, 0);
|
|
|
|
_markerCell = [[NSButtonCell alloc] initImageCell:nil];
|
|
[_markerCell setBezelStyle:NSDisclosureBezelStyle];
|
|
|
|
[self setIndentationPerLevel:_standardRowHeight]; // square it off
|
|
|
|
[self setIndentationMarkerFollowsCell:YES];
|
|
[self setAutoresizesOutlineColumn:YES];
|
|
[self setAutosaveExpandedItems:NO];
|
|
|
|
_editingCellPadding = 10.0;
|
|
|
|
[self invalidateRowCache];
|
|
|
|
_outlineTableColumn=[[[self tableColumns] objectAtIndex:0] retain];
|
|
}
|
|
else {
|
|
[NSException raise:NSInvalidArgumentException format:@"-[%@ %s] is not implemented for coder %@",[self class],sel_getName(_cmd),coder];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
-(id)initWithFrame:(NSRect)frame {
|
|
[super initWithFrame:frame];
|
|
_rowToItem = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0);
|
|
_itemToRow = NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSIntegerMapValueCallBacks, 0);
|
|
_itemToParent=NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0);
|
|
_itemToLevel = NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSIntegerMapValueCallBacks, 0);
|
|
_itemToExpansionState = NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSIntegerMapValueCallBacks, 0);
|
|
_itemToNumberOfChildren = NSCreateMapTable(NSNonOwnedPointerOrNullMapKeyCallBacks, NSIntegerMapValueCallBacks, 0);
|
|
|
|
_markerCell = [[NSButtonCell alloc] initImageCell:nil];
|
|
[_markerCell setBezelStyle:NSDisclosureBezelStyle];
|
|
|
|
[self setIndentationPerLevel:_standardRowHeight]; // square it off
|
|
|
|
[self setIndentationMarkerFollowsCell:YES];
|
|
[self setAutoresizesOutlineColumn:YES];
|
|
[self setAutosaveExpandedItems:NO];
|
|
|
|
_editingCellPadding = 10.0;
|
|
|
|
[self invalidateRowCache];
|
|
|
|
return self;
|
|
}
|
|
|
|
-(void)dealloc {
|
|
NSFreeMapTable(_rowToItem);
|
|
NSFreeMapTable(_itemToRow);
|
|
NSFreeMapTable(_itemToParent);
|
|
NSFreeMapTable(_itemToLevel);
|
|
NSFreeMapTable(_itemToExpansionState);
|
|
NSFreeMapTable(_itemToNumberOfChildren);
|
|
|
|
[_markerCell release];
|
|
[_outlineTableColumn release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSTableColumn *)outlineTableColumn
|
|
{
|
|
return _outlineTableColumn;
|
|
}
|
|
|
|
-itemAtRow:(NSInteger)row {
|
|
return (id)NSMapGet(_rowToItem, (void *)row);
|
|
}
|
|
|
|
-(NSInteger)rowForItem:(id)item {
|
|
return (NSInteger)NSMapGet(_itemToRow, item);
|
|
}
|
|
|
|
-parentForItem:item {
|
|
return NSMapGet(_itemToParent, item);
|
|
}
|
|
|
|
- (BOOL)isExpandable:(id)item
|
|
{
|
|
return [_dataSource outlineView:self isItemExpandable:item];
|
|
}
|
|
|
|
-(NSInteger)levelForItem:(id)item {
|
|
NSInteger level = (NSInteger)NSMapGet(_itemToLevel, item);
|
|
return level;
|
|
}
|
|
|
|
-(NSInteger)levelForRow:(NSInteger)row {
|
|
return [self levelForItem:[self itemAtRow:row]];
|
|
}
|
|
|
|
- (BOOL)isItemExpanded:(id)item
|
|
{
|
|
return isItemExpanded(self,item);
|
|
}
|
|
|
|
-(CGFloat)indentationPerLevel {
|
|
return _indentationPerLevel;
|
|
}
|
|
|
|
- (BOOL)autoresizesOutlineColumn
|
|
{
|
|
return _autoresizesOutlineColumn;
|
|
}
|
|
|
|
- (BOOL)indentationMarkerFollowsCell
|
|
{
|
|
return _indentationMarkerFollowsCell;
|
|
}
|
|
|
|
-(BOOL)autosaveExpandedItems {
|
|
return _autosaveExpandedItems;
|
|
}
|
|
|
|
- (void)setOutlineTableColumn:(NSTableColumn *)tableColumn
|
|
{
|
|
[_outlineTableColumn release];
|
|
_outlineTableColumn = [tableColumn retain];
|
|
}
|
|
|
|
-(void)setIndentationPerLevel:(CGFloat)value {
|
|
_indentationPerLevel = value;
|
|
}
|
|
|
|
- (void)setAutoresizesOutlineColumn:(BOOL)flag
|
|
{
|
|
_autoresizesOutlineColumn = flag;
|
|
}
|
|
|
|
- (void)setIndentationMarkerFollowsCell:(BOOL)flag
|
|
{
|
|
_indentationMarkerFollowsCell = flag;
|
|
}
|
|
|
|
-(void)setAutosaveExpandedItems:(BOOL)flag {
|
|
_autosaveExpandedItems = flag;
|
|
}
|
|
|
|
-(BOOL)_delayResizeButExpandItem:(id)item expandChildren:(BOOL)expandChildren {
|
|
BOOL noteNumberOfRowsChanged=NO;
|
|
BOOL expandThisItem = YES;
|
|
|
|
if(![self isExpandable:item])
|
|
return YES;
|
|
|
|
if ([_delegate respondsToSelector:@selector(outlineView:shouldExpandItem:)])
|
|
if ([_delegate outlineView:self shouldExpandItem:item] == NO)
|
|
expandThisItem = NO;
|
|
|
|
if (expandThisItem) {
|
|
NSDictionary *userInfo=[NSDictionary dictionaryWithObjectsAndKeys:item,@"NSObject",nil];
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:NSOutlineViewItemWillExpandNotification
|
|
object:self
|
|
userInfo:userInfo];
|
|
|
|
NSMapInsert(_itemToExpansionState, item, (void *)YES);
|
|
|
|
[self invalidateRowCache];
|
|
noteNumberOfRowsChanged=YES;
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:NSOutlineViewItemDidExpandNotification
|
|
object:self
|
|
userInfo:userInfo];
|
|
}
|
|
|
|
if (expandChildren) {
|
|
int i,numberOfChildren=numberOfChildrenOfItemAndReload(self,item,YES);
|
|
|
|
for (i = 0; i < numberOfChildren; ++i) {
|
|
id child = [_dataSource outlineView:self child:i ofItem:item];
|
|
|
|
if([self _delayResizeButExpandItem:child expandChildren:YES])
|
|
noteNumberOfRowsChanged=YES;
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
-_objectValueForTableColumn:(NSTableColumn *)column byItem:(id)item {
|
|
if([_dataSource respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem:)])
|
|
return [_dataSource outlineView:self objectValueForTableColumn:column byItem:item];
|
|
|
|
// FIXME: Does it actually send this to the delegate too?
|
|
if([_delegate respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem:)])
|
|
return [_delegate outlineView:self objectValueForTableColumn:column byItem:item];
|
|
|
|
return nil;
|
|
}
|
|
|
|
-(void)_tightenUpColumn:(NSTableColumn *)column forItem:(id)item {
|
|
CGFloat minWidth=[column width],width;
|
|
NSInteger rootLevel=[self levelForItem:item];
|
|
NSInteger i=[self rowForItem:item]+1,numberOfRows=[self numberOfRows];
|
|
|
|
for(;i<numberOfRows;i++) {
|
|
NSCell *dataCell=[column dataCellForRow:i];
|
|
id item=[self itemAtRow:i];
|
|
NSInteger level=[self levelForItem:item];
|
|
|
|
if(level<=rootLevel)
|
|
break;
|
|
|
|
id objectValue=[self _objectValueForTableColumn:column byItem:item];
|
|
if(objectValue!=nil)
|
|
[dataCell setObjectValue:objectValue];
|
|
|
|
width=[[dataCell attributedStringValue] size].width+level*_indentationPerLevel;
|
|
|
|
if(width>minWidth)
|
|
minWidth=width;
|
|
}
|
|
width = [[[column headerCell] attributedStringValue] size].width;
|
|
if(width>minWidth)
|
|
minWidth=width;
|
|
|
|
[column setMinWidth:minWidth];
|
|
}
|
|
|
|
-(void)_tightenUpColumn:(NSTableColumn *)column {
|
|
[self _tightenUpColumn:column forItem:[self itemAtRow:0]];
|
|
}
|
|
|
|
|
|
- (void)expandItem:(id)item expandChildren:(BOOL)expandChildren {
|
|
if([self _delayResizeButExpandItem:item expandChildren:expandChildren]){
|
|
[self noteNumberOfRowsChanged];
|
|
if(_autoresizesOutlineColumn){
|
|
[self _tightenUpColumn: _outlineTableColumn forItem:item];
|
|
if([_outlineTableColumn width]<[_outlineTableColumn minWidth])
|
|
[_outlineTableColumn setWidth:[_outlineTableColumn minWidth]];
|
|
}
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
}
|
|
|
|
- (void)expandItem:(id)item
|
|
{
|
|
[self expandItem:item expandChildren:NO];
|
|
}
|
|
|
|
- (void)collapseItem:(id)item collapseChildren:(BOOL)collapseChildren
|
|
{
|
|
BOOL collapseThisItem = YES;
|
|
|
|
if ([_delegate respondsToSelector:@selector(outlineView:shouldCollapseItem:)])
|
|
if ([_delegate outlineView:self shouldCollapseItem:item] == NO)
|
|
collapseThisItem = NO;
|
|
|
|
if (collapseThisItem) {
|
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
item, @"NSObject", nil];
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:NSOutlineViewItemWillCollapseNotification
|
|
object:self
|
|
userInfo:userInfo];
|
|
|
|
NSMapInsert(_itemToExpansionState, item, (void *)NO);
|
|
|
|
[self invalidateRowCache];
|
|
[self noteNumberOfRowsChanged];
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:NSOutlineViewItemDidCollapseNotification
|
|
object:self
|
|
userInfo:userInfo];
|
|
}
|
|
|
|
if (collapseChildren) {
|
|
NSInteger i,numberOfChildren=numberOfChildrenOfItemAndReload(self,item,YES);
|
|
|
|
for (i = 0; i < numberOfChildren; ++i) {
|
|
id child = [_dataSource outlineView:self child:i ofItem:item];
|
|
|
|
[self collapseItem:child collapseChildren:YES];
|
|
}
|
|
}
|
|
|
|
if (_autoresizesOutlineColumn){
|
|
[self _tightenUpColumn: _outlineTableColumn];
|
|
}
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
- (void)collapseItem:(id)item {
|
|
[self collapseItem:item collapseChildren:NO];
|
|
}
|
|
|
|
-(void)_resetMapTables {
|
|
NSResetMapTable(_rowToItem);
|
|
NSResetMapTable(_itemToRow);
|
|
NSResetMapTable(_itemToParent);
|
|
NSResetMapTable(_itemToLevel);
|
|
// NSResetMapTable(_itemToExpansionState);
|
|
NSResetMapTable(_itemToNumberOfChildren);
|
|
}
|
|
|
|
-(void)reloadData {
|
|
[self _resetMapTables];
|
|
[self invalidateRowCache];
|
|
[super reloadData];
|
|
}
|
|
|
|
- (void)reloadItem:(id)item reloadChildren:(BOOL)reloadChildren {
|
|
[self _resetMapTables];
|
|
[self invalidateRowCache];
|
|
[self noteNumberOfRowsChanged];
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
-(void)reloadItem:(id)item {
|
|
[self reloadItem:item reloadChildren:NO];
|
|
}
|
|
|
|
-(void)setDropItem:(id)item dropChildIndex:(NSInteger)index {
|
|
NSUnimplementedMethod();
|
|
}
|
|
|
|
-(BOOL)shouldCollapseAutoExpandedItemsForDeposited:(BOOL)collapse {
|
|
if (collapse)
|
|
return NO;
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
// override for new outline-related selectors.
|
|
// FIX: Cocoa checks for selectors as they are needed, see -[NSTableView setDataSource].
|
|
-(void)setDataSource:dataSource {
|
|
SEL requiredSelectors[] = {
|
|
@selector(outlineView:child:ofItem:),
|
|
@selector(outlineView:isItemExpandable:),
|
|
@selector(outlineView:numberOfChildrenOfItem:),
|
|
@selector(outlineView:objectValueForTableColumn:byItem:),
|
|
NULL};
|
|
NSInteger i;
|
|
|
|
for (i = 0; requiredSelectors[i] != NULL; ++i)
|
|
if (dataSource!=nil && ![dataSource respondsToSelector:requiredSelectors[i]])
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"NSOutlineView dataSource does not respond to %@", NSStringFromSelector(requiredSelectors[i])];
|
|
|
|
_dataSource=dataSource;
|
|
}
|
|
|
|
-(void)setDelegate:delegate {
|
|
struct {
|
|
NSString *name;
|
|
SEL selector;
|
|
} notes [] = {
|
|
{ NSOutlineViewItemWillExpandNotification, @selector(outlineViewItemWillExpand:) },
|
|
{ NSOutlineViewItemDidExpandNotification, @selector(outlineViewItemDidExpand:) },
|
|
{ NSOutlineViewItemWillCollapseNotification, @selector(outlineViewItemWillCollapse:) },
|
|
{ NSOutlineViewItemDidCollapseNotification, @selector(outlineViewItemDidCollapse:) },
|
|
{ NSOutlineViewColumnDidMoveNotification, @selector(outlineViewColumnDidMove:) },
|
|
{ NSOutlineViewColumnDidResizeNotification, @selector(outlineViewColumnDidResize:) },
|
|
{ NSOutlineViewSelectionIsChangingNotification, @selector(outlineViewSelectionIsChanging:) },
|
|
{ NSOutlineViewSelectionDidChangeNotification, @selector(outlineViewSelectionDidChange:) },
|
|
{ nil, NULL }
|
|
};
|
|
NSInteger i;
|
|
|
|
if (_delegate != nil)
|
|
for (i = 0; notes[i].name != nil; ++i)
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_delegate name:notes[i].name object:self];
|
|
|
|
_delegate=delegate;
|
|
|
|
for (i = 0; notes[i].name != nil; ++i)
|
|
if ([_delegate respondsToSelector:notes[i].selector])
|
|
[[NSNotificationCenter defaultCenter] addObserver:_delegate
|
|
selector:notes[i].selector
|
|
name:notes[i].name
|
|
object:self];
|
|
}
|
|
|
|
static void loadItemIntoMapTables(NSOutlineView *self,id item, NSUInteger *rowCountPtr, NSUInteger recursionLevel,NSHashTable *removeItems){
|
|
NSInteger i,numberOfChildren=numberOfChildrenOfItemAndReload(self,item,YES);
|
|
|
|
for (i = 0; i < numberOfChildren; ++i) {
|
|
id child = [self->_dataSource outlineView:self child:i ofItem:item];
|
|
|
|
NSHashRemove(removeItems,child);
|
|
|
|
// NSLog(@"got child %@ for row %d, level %d", child, *rowCountPtr, recursionLevel);
|
|
|
|
NSMapInsert(self->_rowToItem, (void *)(*rowCountPtr), child);
|
|
NSMapInsert(self->_itemToRow, child, (void *)(*rowCountPtr));
|
|
NSMapInsert(self->_itemToParent, child, item);
|
|
NSMapInsert(self->_itemToLevel, child, (void *)recursionLevel);
|
|
|
|
(*rowCountPtr)++;
|
|
|
|
if(isItemExpanded(self,child))
|
|
loadItemIntoMapTables(self,child,rowCountPtr,recursionLevel+1,removeItems);
|
|
}
|
|
}
|
|
|
|
-(void)loadRootItem {
|
|
NSHashTable *removeItems=NSCreateHashTable(NSNonOwnedPointerHashCallBacks,0);
|
|
|
|
{
|
|
NSMapEnumerator state=NSEnumerateMapTable(_itemToExpansionState);
|
|
void *key,*value;
|
|
|
|
while(NSNextMapEnumeratorPair(&state,&key,&value))
|
|
NSHashInsert(removeItems,key);
|
|
}
|
|
|
|
loadItemIntoMapTables(self,nil,&_numberOfCachedRows,0,removeItems);
|
|
|
|
{
|
|
NSHashEnumerator state=NSEnumerateHashTable(removeItems);
|
|
void *key;
|
|
|
|
while((key= NSNextHashEnumeratorItem(&state))!=NULL)
|
|
NSMapRemove(_itemToExpansionState,key);
|
|
|
|
}
|
|
}
|
|
|
|
-(NSInteger)numberOfRows {
|
|
if (_numberOfCachedRows == 0) {
|
|
[self loadRootItem];
|
|
}
|
|
|
|
return _numberOfCachedRows;
|
|
}
|
|
|
|
-(NSRect)frameOfOutlineCellAtRow:(NSInteger)row {
|
|
NSInteger column=[_tableColumns indexOfObjectIdenticalTo:_outlineTableColumn];
|
|
NSRect result=[super frameOfCellAtColumn:column row:row];
|
|
NSInteger level=[self levelForRow:row];
|
|
CGFloat indentPixels = level * _indentationPerLevel;
|
|
|
|
result.size.width = indentPixels;
|
|
|
|
if (_indentationMarkerFollowsCell)
|
|
result.origin.x += result.size.width;
|
|
|
|
result.size.width = _indentationPerLevel;
|
|
|
|
return result;
|
|
}
|
|
|
|
- (NSRect)_adjustedFrameOfCellAtColumn:(NSInteger)column row:(NSInteger)row objectValue:(id)objectValue {
|
|
NSRect cellRect = [super frameOfCellAtColumn:column row:row];
|
|
NSTableColumn *tableColumn = [_tableColumns objectAtIndex:column];
|
|
|
|
if (tableColumn == _outlineTableColumn) {
|
|
NSCell *dataCell = [tableColumn dataCellForRow:row];
|
|
CGFloat indentPixels = [self levelForRow:row] * _indentationPerLevel;
|
|
CGFloat cellWidth;
|
|
|
|
cellRect.origin.x += (indentPixels + _standardRowHeight) + _intercellSpacing.width;
|
|
|
|
|
|
// instead, give the delegate an opportunity to provide the cell width. (i was keying on attributed
|
|
// string value width, but this broke when i tried to use an NSBrowserCell, naturally..
|
|
|
|
if([_delegate respondsToSelector:@selector(outlineView:widthOfCell:forTableColumn:byItem:)]){
|
|
cellWidth = [_delegate outlineView:self widthOfCell:dataCell forTableColumn:tableColumn byItem:[self itemAtRow:row]] + _intercellSpacing.width;
|
|
}
|
|
else {
|
|
#if 1
|
|
// this is more NSTableView-ish behavior.
|
|
cellWidth=cellRect.size.width -(indentPixels + _standardRowHeight);
|
|
#else
|
|
[dataCell setObjectValue:objectValue];
|
|
|
|
cellWidth=[dataCell cellSize].width + _intercellSpacing.width;
|
|
#endif
|
|
}
|
|
|
|
// since we shrink the cell frame to fit the title, when editing occurs, we need to pad
|
|
// the frame slightly so that the entire title will be visible in the editing cell
|
|
// (space permitting in the column)
|
|
if (column == _editedColumn && row == _editedRow)
|
|
cellWidth += _editingCellPadding;
|
|
|
|
cellRect.size.width = MIN(cellWidth, cellRect.size.width - (indentPixels + _standardRowHeight));
|
|
}
|
|
|
|
return cellRect;
|
|
}
|
|
|
|
-(NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row {
|
|
NSTableColumn *tableColumn=[_tableColumns objectAtIndex:column];
|
|
|
|
if(tableColumn ==_outlineTableColumn){
|
|
id objectValue=[self _objectValueForTableColumn:tableColumn byItem:[self itemAtRow:row]];
|
|
|
|
if(objectValue!=nil)
|
|
return [self _adjustedFrameOfCellAtColumn:column row:row objectValue:objectValue];
|
|
}
|
|
|
|
return [super frameOfCellAtColumn:column row:row];
|
|
}
|
|
|
|
-(void)drawHighlightedSelectionForColumn:(NSInteger)column row:(NSInteger)row inRect:(NSRect)rect
|
|
{
|
|
if ([_tableColumns objectAtIndex:column] == _outlineTableColumn) {
|
|
NSRect newRect = NSInsetRect(rect,1,0);
|
|
// NSDottedFrameRect is kinda weird
|
|
newRect.origin.y++;
|
|
newRect.size.height--;
|
|
[super drawHighlightedSelectionForColumn:column row:row inRect:newRect];
|
|
NSDottedFrameRect(rect);
|
|
}
|
|
else
|
|
[super drawHighlightedSelectionForColumn:column row:row inRect:rect];
|
|
}
|
|
|
|
-(void)_drawGridForItem:(id)item style:(NSGraphicsStyle *)style level:(NSInteger)level {
|
|
NSInteger column = [_tableColumns indexOfObject:_outlineTableColumn];
|
|
NSInteger row = [self rowForItem:item];
|
|
NSRect myFrame = [self frameOfCellAtColumn:column row:row];
|
|
|
|
if (level > 0) {
|
|
NSRect rect = myFrame;
|
|
|
|
// it is safe to assume that all marker cells are the same size.
|
|
rect.origin.x -= rect.size.width/2;
|
|
rect.size.width += rect.size.width/2;
|
|
rect.origin.y += (rect.size.height/2)-1;
|
|
rect.size.height = 1;
|
|
|
|
[style drawOutlineViewGridInRect:rect];
|
|
}
|
|
|
|
if (isItemExpanded(self,item)) {
|
|
NSInteger i,numberOfChildren=numberOfChildrenOfItemAndReload(self,item,NO);
|
|
id lastChild=nil;
|
|
|
|
for(i=0;i<numberOfChildren;i++){
|
|
lastChild=childOfItemAtIndex(self,item,i);
|
|
[self _drawGridForItem:lastChild style:style level:level+1];
|
|
}
|
|
|
|
if(lastChild!=nil){
|
|
NSRect rect=myFrame;
|
|
NSRect lastChildRect=[self frameOfCellAtColumn:column row:[self rowForItem:lastChild]];
|
|
CGFloat delta=lastChildRect.origin.y - myFrame.origin.y;
|
|
|
|
// it is safe to assume that all rows are the same height.
|
|
rect.origin.x += (rect.size.width/2)-1;
|
|
rect.size.width = 1;
|
|
rect.origin.y += rect.size.height/2;
|
|
rect.size.height = delta;
|
|
|
|
[style drawOutlineViewGridInRect:rect];
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void)drawGridInClipRect:(NSRect)clipRect {
|
|
// this doesn't look right at all when the indentation marker isn't set to follow the cell
|
|
NSGraphicsStyle *style=[self graphicsStyle];
|
|
BOOL temp = _indentationMarkerFollowsCell;
|
|
NSInteger i,count=numberOfChildrenOfItemAndReload(self,nil,NO);
|
|
|
|
[[NSColor grayColor] setStroke];
|
|
|
|
[self setIndentationMarkerFollowsCell:YES];
|
|
for(i=0;i<count;i++)
|
|
[self _drawGridForItem:childOfItemAtIndex(self,nil,i) style:style level:0];
|
|
[self setIndentationMarkerFollowsCell:temp];
|
|
|
|
[super drawGridInClipRect:clipRect];
|
|
}
|
|
|
|
-(NSCell *)preparedCellAtColumn:(NSInteger)columnNumber row:(NSInteger)row {
|
|
NSTableColumn *column = [_tableColumns objectAtIndex:columnNumber];
|
|
NSCell *result=[super preparedCellAtColumn:columnNumber row:row];
|
|
|
|
if ([_delegate respondsToSelector:@selector(outlineView:willDisplayCell:forTableColumn:item:)])
|
|
[_delegate outlineView:self willDisplayCell:result forTableColumn:column item:[self itemAtRow:row]];
|
|
|
|
return result;
|
|
}
|
|
|
|
-(void)drawRow:(NSInteger)row clipRect:(NSRect)rect {
|
|
// FIXME: This should be changed to just call super, then draw the markers
|
|
// But there are some fine differences with _adjustedFrame in NSTableView
|
|
NSRange visibleColumns = [self columnsInRect:rect];
|
|
NSInteger drawThisColumn = visibleColumns.location;
|
|
|
|
if (row < 0 || row >= [self numberOfRows])
|
|
[NSException raise:NSInvalidArgumentException format:@"invalid row in drawRow:clipRect:"];
|
|
|
|
for(;drawThisColumn < NSMaxRange(visibleColumns);drawThisColumn++) {
|
|
|
|
if(row == _editedRow && drawThisColumn == _editedColumn && _editingCell!=nil){
|
|
[_backgroundColor setFill];
|
|
NSRectFill(_editingBorder);
|
|
[_editingCell setControlView:self];
|
|
[_editingCell drawWithFrame:_editingFrame inView:self];
|
|
if ([_editingCell focusRingType] != NSFocusRingTypeNone){
|
|
[[NSColor keyboardFocusIndicatorColor] setStroke];
|
|
NSFrameRectWithWidth(_editingBorder, 2.0);
|
|
}
|
|
}
|
|
else {
|
|
NSCell *dataCell=[self preparedCellAtColumn:drawThisColumn row:row];
|
|
[dataCell drawWithFrame:[self frameOfCellAtColumn:drawThisColumn row:row] inView:self];
|
|
}
|
|
|
|
// Marker cell is drawn after data cell so it is on top
|
|
|
|
NSTableColumn *column = [_tableColumns objectAtIndex:drawThisColumn];
|
|
|
|
// special outline behavior. we indent the entire cell so as not to rely on a custom
|
|
// NSCell class (who knows what you might want in the outline view?)
|
|
if (column == _outlineTableColumn) {
|
|
id item = [self itemAtRow:row];
|
|
|
|
if ([self isExpandable:item]) {
|
|
NSRect outlineCellFrame=[self frameOfOutlineCellAtRow:row];
|
|
|
|
if(!NSIsEmptyRect(outlineCellFrame)){
|
|
if (isItemExpanded(self,item))
|
|
[_markerCell setState:NSOnState];
|
|
else
|
|
[_markerCell setState:NSOffState];
|
|
|
|
if ([_delegate respondsToSelector:@selector(outlineView:willDisplayOutlineCell:forTableColumn:item:)])
|
|
[_delegate outlineView:self willDisplayOutlineCell:_markerCell forTableColumn:column item:item];
|
|
|
|
[_markerCell setControlView:self];
|
|
[_markerCell drawWithFrame:outlineCellFrame inView:self];
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// intercept mouse clicks destined for the "dead space" in the indented area or the triangle.
|
|
// proper adjustment of the highlighted area in the outline column is performed elsewhere.
|
|
-(void)mouseDown:(NSEvent *)event {
|
|
NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil];
|
|
|
|
_clickedColumn = [self columnAtPoint:location];
|
|
_clickedRow = [self rowAtPoint:location];
|
|
_clickedItem = [self itemAtRow:_clickedRow];
|
|
|
|
if (_clickedColumn >= 0 && _clickedRow >= 0 &&
|
|
[_tableColumns objectAtIndex:_clickedColumn] == _outlineTableColumn) {
|
|
if (NSPointInRect(location, [self frameOfOutlineCellAtRow:_clickedRow]) && [self isExpandable:_clickedItem]) {
|
|
if (isItemExpanded(self,_clickedItem) == NO)
|
|
[self expandItem:_clickedItem];
|
|
else
|
|
[self collapseItem:_clickedItem];
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
[super mouseDown:event];
|
|
}
|
|
|
|
//
|
|
// NSTableColumn delegate override methods
|
|
//
|
|
-(BOOL)delegateShouldSelectTableColumn:(NSTableColumn *)tableColumn {
|
|
if ([_delegate respondsToSelector:@selector(outlineView:shouldSelectTableColumn:)])
|
|
return [_delegate outlineView:self shouldSelectTableColumn:tableColumn];
|
|
|
|
return YES;
|
|
}
|
|
|
|
-(BOOL)delegateShouldSelectRow:(NSInteger)row
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(outlineView:shouldSelectItem:)])
|
|
return [_delegate outlineView:self shouldSelectItem:[self itemAtRow:row]];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)delegateShouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(outlineView:shouldEditTableColumn:item:)])
|
|
return [_delegate outlineView:self shouldEditTableColumn:tableColumn item:[self itemAtRow:row]];
|
|
|
|
return YES;
|
|
}
|
|
|
|
-(BOOL)delegateSelectionShouldChange
|
|
{
|
|
if ([_delegate respondsToSelector:@selector(selectionShouldChangeInOutlineView:)])
|
|
return [_delegate selectionShouldChangeInOutlineView:self];
|
|
|
|
return YES;
|
|
}
|
|
|
|
-(void)noteSelectionIsChanging {
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:NSOutlineViewSelectionIsChangingNotification
|
|
object:self];
|
|
}
|
|
|
|
-(void)noteSelectionDidChange {
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:NSOutlineViewSelectionDidChangeNotification
|
|
object:self];
|
|
}
|
|
|
|
-(void)noteColumnDidResizeWithOldWidth:(CGFloat)oldWidth
|
|
{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:NSOutlineViewColumnDidResizeNotification
|
|
object:self
|
|
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithFloat:oldWidth], @"NSOldWidth", nil]];
|
|
}
|
|
|
|
-(BOOL)dataSourceCanSetObjectValue
|
|
{
|
|
return [_dataSource respondsToSelector:@selector(outlineView:setObjectValue:forTableColumn:byItem:)];
|
|
}
|
|
|
|
-(void)dataSourceSetObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
|
{
|
|
[_dataSource outlineView:self setObjectValue:object forTableColumn:tableColumn byItem:[self itemAtRow:row]];
|
|
}
|
|
|
|
-(id)dataSourceObjectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
|
{
|
|
return [_dataSource outlineView:self objectValueForTableColumn:tableColumn byItem:[self itemAtRow:row]];
|
|
}
|
|
|
|
-(void)_willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)column row:(NSInteger)row {
|
|
if([_delegate respondsToSelector:@selector(outlineView:willDisplayCell:forTableColumn:item:)])
|
|
[_delegate outlineView:self willDisplayCell:cell forTableColumn:column item:[self itemAtRow:row]];
|
|
}
|
|
|
|
|
|
@end
|