// // PWOutlineView.m // PWTreeControllerDemo // // Created by Frank Illenberger on 04.03.06. // Copyright 2006 ProjectWizards, Melle, Germany. // #import "PWOutlineView.h" #import "PWTreeController.h" #import "PWTreeControllerNode.h" #import "NSOutlineView-PWExtensions.h" //#import #define PWOutlineAttributesContext (void *)@"PWOutlineAttributesContext" typedef struct { float red1, green1, blue1, alpha1; float red2, green2, blue2, alpha2; } _twoColorsType; extern void _linearColorBlendFunction(void *info, const float *in, float *out); extern void _linearColorReleaseInfoFunction(void *info); static const CGFunctionCallbacks linearFunctionCallbacks = {0, &_linearColorBlendFunction, &_linearColorReleaseInfoFunction}; @implementation PWOutlineView - (void) showActionable: (id) sender { [_delegate showActionable: sender]; } - (void) showToDo: (id) sender { [_delegate showToDo: sender]; } - (void) showDone: (id) sender { [_delegate showDone: sender]; } - (void) group: (id) sender { [_delegate group: sender]; } - (void) ungroup: (id) sender { [_delegate ungroup: sender]; } - (void) delete: (id) sender { [_delegate delete: sender]; } - (void) addChild: (id) sender { [_delegate addChild: sender]; } - (void) addSibling: (id) sender { [_delegate addSibling: sender]; } - (void) textDidEndEditing: (NSNotification *) notification { NSDictionary *userInfo; userInfo = [notification userInfo]; NSNumber *textMovement; textMovement = [userInfo objectForKey: @"NSTextMovement"]; int movementCode; movementCode = [textMovement intValue]; // see if this a 'pressed-return' instance if (movementCode == NSReturnTextMovement) { // hijack the notification and pass a different textMovement // value textMovement = [NSNumber numberWithInt: NSIllegalTextMovement]; NSDictionary *newUserInfo; newUserInfo = [NSDictionary dictionaryWithObject: textMovement forKey: @"NSTextMovement"]; notification = [NSNotification notificationWithName: [notification name] object: [notification object] userInfo: newUserInfo]; } [super textDidEndEditing: notification]; } // textDidEndEditing - (void)setIsExpandedKeyPath:(NSString *)keyPath { [keyPath retain]; [isExpandedKeyPath release]; isExpandedKeyPath = keyPath; } - (NSString *)isExpandedKeyPath { return isExpandedKeyPath; } - (void)setIsExpandedFilteredKeyPath:(NSString *)keyPath { [keyPath retain]; [isExpandedFilteredKeyPath release]; isExpandedFilteredKeyPath = keyPath; } - (NSString *)isExpandedFilteredKeyPath { return isExpandedFilteredKeyPath; } - (void)expandItem:(id)item expandChildren:(BOOL)expandChildren { [super expandItem:item expandChildren:expandChildren]; if(!dontRegisterCollapsing && isExpandedKeyPath) { id source = [self dataSource]; id originalItem = [item representedObject]; NSString *keyPath = [source respondsToSelector:@selector(filterPredicate)] && [source filterPredicate] ? isExpandedFilteredKeyPath : isExpandedKeyPath; if(keyPath && ![[originalItem valueForKey:keyPath] boolValue]) { [originalItem setValue:[NSNumber numberWithBool:YES] forKey:keyPath]; [self restoreCollapsedItems]; } if([[self delegate] respondsToSelector:@selector(itemDidExpand:)]) [[self delegate] performSelector:@selector(itemDidExpand:) withObject:originalItem]; } } - (void)collapseItem:(id)item collapseChildren:(BOOL)collapseChildren { [super collapseItem:item collapseChildren:collapseChildren]; if(!dontRegisterCollapsing && isExpandedKeyPath) { id source = [self dataSource]; id originalItem = [item representedObject]; NSString *keyPath = [source respondsToSelector:@selector(filterPredicate)] && [source filterPredicate] ? isExpandedFilteredKeyPath : isExpandedKeyPath; if(keyPath) [originalItem setValue:[NSNumber numberWithBool:NO] forKey:keyPath]; if([[self delegate] respondsToSelector:@selector(itemDidCollapse:)]) [[self delegate] performSelector:@selector(itemDidCollapse:) withObject:originalItem]; } } - (void)restoreCollapsedStateOfItem:(id)item { @try { id originalItem = [item representedObject]; BOOL collapsed; if([[self dataSource] filterPredicate]) collapsed = isExpandedFilteredKeyPath ? ![[originalItem valueForKey:isExpandedFilteredKeyPath] boolValue] : NO; else collapsed = ![[originalItem valueForKey:isExpandedKeyPath] boolValue]; if(collapsed && [self isItemExpanded:item]) [self collapseItem:item collapseChildren:NO]; else if(!collapsed && ![self isItemExpanded:item] ) [self expandItem:item expandChildren:NO]; } @catch(NSException *e) { } } - (void)restoreCollapsedItems { dontRegisterCollapsing = YES; int rows = [self numberOfRows]; int row=0; while(row=0) { [editItem release]; editItem = [[self originalItemAtRow:[self editedRow]] retain]; editColumn = [self editedColumn]; savedSelectedOriginalItems = [[NSArray alloc] initWithArray:[dataSource selectedObjects]]; } } - (void)restoreState { if(isExpandedKeyPath) [self restoreCollapsedItems]; if(editItem) { // Restore row being edited int row = [self rowForOriginalItem:editItem]; if(editColumn!=-1 && row!=-1 && [self editedRow]==-1) { [self selectRow:row byExtendingSelection:NO]; [self editColumn:editColumn row:row withEvent:nil select:YES]; } [editItem release]; editItem = nil; } if([savedSelectedOriginalItems count]) { [self restoreSelection]; } [savedSelectedOriginalItems release]; savedSelectedOriginalItems = nil; } - (void)reloadData { isReloading = YES; [self saveState]; [super reloadData]; [self restoreState]; isReloading = NO; } - (void)reloadItem:(id)item reloadChildren:(BOOL)reloadChildren { if(!isReloading) { [self saveState]; [super reloadItem:item reloadChildren:reloadChildren]; [self restoreState]; } } - (BOOL)isRestoringSelection { return isRestoringSelection; } - (void)setDontRegisterCollapsing:(BOOL)dont { dontRegisterCollapsing = dont; } - (void)uncoverItem:(PWTreeControllerNode *)item { NSMutableArray *path = [NSMutableArray array]; while(item) { [path insertObject:item atIndex:0]; item = [item parentNode]; } int count = [path count]; for(int index=0; index .04) ? saturation+0.12 : 0.0) brightness:MAX(0.0, brightness-0.045) alpha:alpha]; /* If this view isn't key, use the gray version of the dark color. Note that this varies from the standard gray version that NSCell returns as its highlightColorWithFrame: when the cell is not in a key view, in that this is a lot darker. Mike and I think this is justified for this kind of view -- if you're using the dark selection color to show the selected status, it makes sense to leave it dark. */ NSResponder *firstResponder = [[self window] firstResponder]; if (![firstResponder isKindOfClass:[NSView class]] || ![(NSView *)firstResponder isDescendantOf:self] || ![[self window] isKeyWindow]) { alternateSelectedControlColor = [[alternateSelectedControlColor colorUsingColorSpaceName:NSDeviceWhiteColorSpace] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; lighterColor = [[lighterColor colorUsingColorSpaceName:NSDeviceWhiteColorSpace] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; darkerColor = [[darkerColor colorUsingColorSpaceName:NSDeviceWhiteColorSpace] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; } // Set up the helper function for drawing washes CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); _twoColorsType *twoColors = malloc(sizeof(_twoColorsType)); /* We malloc() the helper data because we may draw this wash during printing, in which case it won't necessarily be evaluated immediately. We need for all the data the shading function needs to draw to potentially outlive us.*/ [lighterColor getRed:&twoColors->red1 green:&twoColors->green1 blue:&twoColors->blue1 alpha:&twoColors->alpha1]; [darkerColor getRed:&twoColors->red2 green:&twoColors->green2 blue:&twoColors->blue2 alpha:&twoColors->alpha2]; static const float domainAndRange[8] = {0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0}; CGFunctionRef linearBlendFunctionRef = CGFunctionCreate(twoColors, 1, domainAndRange, 4, domainAndRange, &linearFunctionCallbacks); NSIndexSet *selectedRowIndexes = [self selectedRowIndexes]; unsigned int rowIndex = [selectedRowIndexes indexGreaterThanOrEqualToIndex:0]; while (rowIndex != NSNotFound) { unsigned int endOfCurrentRunRowIndex, newRowIndex = rowIndex; do { endOfCurrentRunRowIndex = newRowIndex; newRowIndex = [selectedRowIndexes indexGreaterThanIndex:endOfCurrentRunRowIndex]; } while (newRowIndex == endOfCurrentRunRowIndex + 1); NSRect rowRect = NSUnionRect([self rectOfRow:rowIndex], [self rectOfRow:endOfCurrentRunRowIndex]); NSRect topBar, washRect; NSDivideRect(rowRect, &topBar, &washRect, 1.0, NSMinYEdge); // Draw the top line of pixels of the selected row in the alternateSelectedControlColor [alternateSelectedControlColor set]; NSRectFill(topBar); // Draw a soft wash underneath it CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; CGContextSaveGState(context); { CGContextClipToRect(context, (CGRect){{NSMinX(washRect), NSMinY(washRect)}, {NSWidth(washRect), NSHeight(washRect)}}); CGShadingRef cgShading = CGShadingCreateAxial(colorSpace, CGPointMake(0, NSMinY(washRect)), CGPointMake(0, NSMaxY(washRect)), linearBlendFunctionRef, NO, NO); CGContextDrawShading(context, cgShading); CGShadingRelease(cgShading); } CGContextRestoreGState(context); rowIndex = newRowIndex; } CGFunctionRelease(linearBlendFunctionRef); CGColorSpaceRelease(colorSpace); } - (void)selectRow:(int)row byExtendingSelection:(BOOL)extend; { [super selectRow:row byExtendingSelection:extend]; [self setNeedsDisplay:YES]; // we display extra because we draw multiple contiguous selected rows differently, so changing one row's selection can change how others draw. } - (void)deselectRow:(int)row; { [super deselectRow:row]; [self setNeedsDisplay:YES]; // we display extra because we draw multiple contiguous selected rows differently, so changing one row's selection can change how others draw. } // NSTableView (Private) - (id)_highlightColorForCell:(NSCell *)cell; { return nil; } - (void)_windowDidChangeKeyNotification:(NSNotification *)notification; { [self setNeedsDisplay:YES]; } // Actions - (IBAction)expandSelection:(id)sender; { NSArray *selectedItems; NSEnumerator *itemEnumerator; id item; selectedItems = [self selectedItems]; itemEnumerator = [selectedItems objectEnumerator]; while ((item = [itemEnumerator nextObject])) [self expandItemAndChildren:item]; [self setSelectedItems:selectedItems]; } - (IBAction)contractSelection:(id)sender; { NSArray *selectedItems; NSEnumerator *itemEnumerator; id item; selectedItems = [self selectedItems]; itemEnumerator = [selectedItems reverseObjectEnumerator]; while ((item = [itemEnumerator nextObject])) [self collapseItemAndChildren:item]; [self setSelectedItems:selectedItems]; } - (void)keyDown:(NSEvent *)theEvent; { NSString *characters; unichar firstCharacter; unsigned int modifierFlags; characters = [theEvent characters]; modifierFlags = [theEvent modifierFlags]; firstCharacter = [characters characterAtIndex:0]; switch (firstCharacter) { case 13: if (modifierFlags & NSAlternateKeyMask) [self addChild:nil]; else [self addSibling:nil]; return; case NSDeleteFunctionKey: case 127: [self delete: nil]; return; case 'a': [self showActionable: nil]; return; case 't': [self showToDo: nil]; return; case 'd': [self showDone: nil]; return; case 'e': { // not reached if type-ahead selection is turned on int columnIndex, rowIndex; columnIndex = [[self tableColumns] indexOfObject:[self outlineTableColumn]]; rowIndex = [self selectedRow]; [self editColumn:columnIndex row:rowIndex withEvent:nil select:YES]; return; } case 'g': // not reached if type-ahead selection is turned on [self group:nil]; return; case 'u': // not reached if type-ahead selection is turned on [self ungroup:nil]; return; case '<': { NSArray *selectedItems; selectedItems = [self selectedItems]; [self contractAll:nil]; [self setSelectedItems:selectedItems]; return; } case '>': { NSArray *selectedItems; selectedItems = [self selectedItems]; [self expandAll:nil]; [self setSelectedItems:selectedItems]; return; } case NSLeftArrowFunctionKey: if (modifierFlags & NSAlternateKeyMask) { [self contractSelection:nil]; return; } break; case NSRightArrowFunctionKey: if (modifierFlags & NSAlternateKeyMask) { [self expandSelection:nil]; return; } break; default: break; } [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; } @end @implementation NSTableColumn (PWAdditions) - (NSString *)keyPath { return [self identifier]; } - (NSString *)bindingKeyPath { return [self keyPath]; } - (NSSet *)additionalObservingKeyPaths { return nil; } @end