5 // Created by Alex Rozanski on 05/09/2009.
6 // Copyright 2009-10 Alex Rozanski http://perspx.com
8 // GC-enabled code revised by Stefan Vogt http://byteproject.net
11 #import "PXSourceList.h"
14 #define MIN_BADGE_WIDTH 22.0 //The minimum badge width for each item (default 22.0)
15 #define BADGE_HEIGHT 14.0 //The badge height for each item (default 14.0)
16 #define BADGE_MARGIN 5.0 //The spacing between the badge and the cell for that row
17 #define ROW_RIGHT_MARGIN 5.0 //The spacing between the right edge of the badge and the edge of the table column
18 #define ICON_SPACING 2.0 //The spacing between the icon and it's adjacent cell
19 #define DISCLOSURE_TRIANGLE_SPACE 18.0 //The indentation reserved for disclosure triangles for non-group items
22 #define BADGE_BACKGROUND_COLOR [NSColor colorWithCalibratedRed:(152/255.0) green:(168/255.0) blue:(202/255.0) alpha:1]
23 #define BADGE_HIDDEN_BACKGROUND_COLOR [NSColor colorWithDeviceWhite:(180/255.0) alpha:1]
24 #define BADGE_SELECTED_TEXT_COLOR [NSColor keyboardFocusIndicatorColor]
25 #define BADGE_SELECTED_UNFOCUSED_TEXT_COLOR [NSColor colorWithCalibratedRed:(153/255.0) green:(169/255.0) blue:(203/255.0) alpha:1]
26 #define BADGE_SELECTED_HIDDEN_TEXT_COLOR [NSColor colorWithCalibratedWhite:(170/255.0) alpha:1]
27 #define BADGE_FONT [NSFont boldSystemFontOfSize:11]
29 //Delegate notification constants
30 NSString * const PXSLSelectionIsChangingNotification = @"PXSourceListSelectionIsChanging";
31 NSString * const PXSLSelectionDidChangeNotification = @"PXSourceListSelectionDidChange";
32 NSString * const PXSLItemWillExpandNotification = @"PXSourceListItemWillExpand";
33 NSString * const PXSLItemDidExpandNotification = @"PXSourceListItemDidExpand";
34 NSString * const PXSLItemWillCollapseNotification = @"PXSourceListItemWillCollapse";
35 NSString * const PXSLItemDidCollapseNotification = @"PXSourceListItemDidCollapse";
36 NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKeyPressedOnRows";
39 @interface PXSourceList ()
41 - (NSSize)sizeOfBadgeAtRow:(NSInteger)rowIndex;
42 - (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame;
43 - (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector;
48 @implementation PXSourceList
50 @synthesize iconSize = _iconSize;
54 #pragma mark Init/Dealloc/Finalize
56 - (id)initWithCoder:(NSCoder*)decoder
58 if(self=[super initWithCoder:decoder])
60 [self setDelegate:(id<PXSourceListDelegate>)[super delegate]];
61 [super setDelegate:self];
63 [self setDataSource:(id<PXSourceListDataSource>)[super dataSource]];
64 [super setDataSource:self];
66 _iconSize = NSMakeSize(16,16);
74 //Remove ourselves as the delegate and data source to be safe
75 [super setDataSource:nil];
76 [super setDelegate:nil];
78 //Unregister the delegate from receiving notifications
79 [[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
86 //Remove ourselves as the delegate and data source to be safe
87 [super setDataSource:nil];
88 [super setDelegate:nil];
90 //Unregister the delegate from receiving notifications
91 [[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
97 #pragma mark Custom Accessors
99 - (void)setDelegate:(id<PXSourceListDelegate>)aDelegate
101 //Unregister the old delegate from receiving notifications
102 [[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
104 _secondaryDelegate = aDelegate;
106 //Register the new delegate to receive notifications
107 [self registerDelegateToReceiveNotification:PXSLSelectionIsChangingNotification
108 withSelector:@selector(sourceListSelectionIsChanging:)];
109 [self registerDelegateToReceiveNotification:PXSLSelectionDidChangeNotification
110 withSelector:@selector(sourceListSelectionDidChange:)];
111 [self registerDelegateToReceiveNotification:PXSLItemWillExpandNotification
112 withSelector:@selector(sourceListItemWillExpand:)];
113 [self registerDelegateToReceiveNotification:PXSLItemDidExpandNotification
114 withSelector:@selector(sourceListItemDidExpand:)];
115 [self registerDelegateToReceiveNotification:PXSLItemWillCollapseNotification
116 withSelector:@selector(sourceListItemWillCollapse:)];
117 [self registerDelegateToReceiveNotification:PXSLItemDidCollapseNotification
118 withSelector:@selector(sourceListItemDidCollapse:)];
119 [self registerDelegateToReceiveNotification:PXSLDeleteKeyPressedOnRowsNotification
120 withSelector:@selector(sourceListDeleteKeyPressedOnRows:)];
124 - (void)setDataSource:(id<PXSourceListDataSource>)aDataSource
126 _secondaryDataSource = aDataSource;
131 - (void)setIconSize:(NSSize)newIconSize
133 _iconSize = newIconSize;
135 CGFloat rowHeight = [self rowHeight];
137 //Make sure icon height does not exceed row height; if so constrain, keeping width and height in proportion
138 if(_iconSize.height>rowHeight)
140 _iconSize.width = _iconSize.width * (rowHeight/_iconSize.height);
141 _iconSize.height = rowHeight;
146 #pragma mark Data Management
152 //Expand items that are displayed as always expanded
153 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)] &&
154 [_secondaryDelegate respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)])
156 for(NSUInteger i=0;i<[self numberOfGroups];i++)
158 id item = [_secondaryDataSource sourceList:self child:i ofItem:nil];
160 if([self isGroupAlwaysExpanded:item]) {
161 [self expandItem:item expandChildren:NO];
167 //If there are selected rows and the item hierarchy has changed, make sure a Group row isn't
169 if([self numberOfSelectedRows]>0) {
170 NSIndexSet *selectedIndexes = [self selectedRowIndexes];
171 NSUInteger firstSelectedRow = [selectedIndexes firstIndex];
173 //Is a group item selected?
174 if([self isGroupItem:[self itemAtRow:firstSelectedRow]]) {
175 //Work backwards to find the first non-group row
177 for(NSUInteger i=firstSelectedRow;i>0;i--)
179 if(![self isGroupItem:[self itemAtRow:i]]) {
180 [self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
186 //If there is no non-group row preceding the currently selected group item, remove the selection
187 //from the Source List
189 [self deselectAll:self];
193 else if(![self allowsEmptySelection]&&[self numberOfSelectedRows]==0)
195 //Select the first non-group row if no rows are selected, and empty selection is disallowed
196 for(NSUInteger i=0;i<[self numberOfRows];i++)
198 if(![self isGroupItem:[self itemAtRow:i]]) {
199 [self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
206 - (NSUInteger)numberOfGroups
208 if([_secondaryDataSource respondsToSelector:@selector(sourceList:numberOfChildrenOfItem:)]) {
209 return [_secondaryDataSource sourceList:self numberOfChildrenOfItem:nil];
216 - (BOOL)isGroupItem:(id)item
218 //Groups are defined as root items (at level 0)
219 return 0==[self levelForItem:item];
223 - (BOOL)isGroupAlwaysExpanded:(id)group
225 //Make sure that the item IS a group to prevent unwanted queries sent to the data source
226 if([self isGroupItem:group]) {
227 //Query the data source
228 if([_secondaryDelegate respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)]) {
229 return [_secondaryDelegate sourceList:self isGroupAlwaysExpanded:group];
237 - (BOOL)itemHasBadge:(id)item
239 if([_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasBadge:)]) {
240 return [_secondaryDataSource sourceList:self itemHasBadge:item];
246 - (NSInteger)badgeValueForItem:(id)item
248 //Make sure that the item has a badge
249 if(![self itemHasBadge:item]) {
253 if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeValueForItem:)]) {
254 return [_secondaryDataSource sourceList:self badgeValueForItem:item];
261 #pragma mark Selection Handling
263 - (void)selectRowIndexes:(NSIndexSet*)indexes byExtendingSelection:(BOOL)extend
265 NSUInteger numberOfIndexes = [indexes count];
267 //Prevent empty selection if we don't want it
268 if(![self allowsEmptySelection]&&0==numberOfIndexes) {
272 //Would use blocks but we're also targeting 10.5...
273 //Get the selected indexes
274 NSUInteger *selectedIndexes = malloc(sizeof(NSUInteger)*numberOfIndexes);
275 [indexes getIndexes:selectedIndexes maxCount:numberOfIndexes inIndexRange:nil];
277 //Loop through the indexes and only add non-group row indexes
278 //Allows selection across groups without selecting the group rows
279 NSMutableIndexSet *newSelectionIndexes = [NSMutableIndexSet indexSet];
280 for(NSInteger i=0;i<numberOfIndexes;i++)
282 if(![self isGroupItem:[self itemAtRow:selectedIndexes[i]]]) {
283 [newSelectionIndexes addIndex:selectedIndexes[i]];
287 //If there are any non-group rows selected
288 if([newSelectionIndexes count]>0) {
289 [super selectRowIndexes:newSelectionIndexes byExtendingSelection:extend];
292 //C memory management... *sigh*
293 free(selectedIndexes);
299 - (NSRect)frameOfOutlineCellAtRow:(NSInteger)row
301 //Return a zero-rect if the item is always expanded (a disclosure triangle will not be drawn)
302 if([self isGroupAlwaysExpanded:[self itemAtRow:row]]) {
306 return [super frameOfOutlineCellAtRow:row];
310 - (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row
312 id item = [self itemAtRow:row];
314 NSCell *cell = [self preparedCellAtColumn:column row:row];
315 NSSize cellSize = [cell cellSize];
316 if (!([cell type] == NSImageCellType) && !([cell type] == NSTextCellType))
317 cellSize = [cell cellSizeForBounds:[super frameOfCellAtColumn:column row:row]];
318 NSRect cellFrame = [super frameOfCellAtColumn:column row:row];
320 NSRect rowRect = [self rectOfRow:row];
322 if([self isGroupItem:item])
324 CGFloat minX = NSMinX(cellFrame);
326 //Set the origin x-coord; if there are no children of the group at current, there will still be a
327 //margin to the left of the cell (in cellFrame), which we don't want
328 if([self isGroupAlwaysExpanded:[self itemAtRow:row]]) {
332 return NSMakeRect(minX,
333 NSMidY(cellFrame)-(cellSize.height/2.0),
334 NSWidth(rowRect)-minX,
339 CGFloat leftIndent = ([self levelForRow:row] -1)*[self indentationPerLevel]+DISCLOSURE_TRIANGLE_SPACE;
341 //Calculate space left for a badge if need be
342 CGFloat rightIndent = [self sizeOfBadgeAtRow:row].width+ROW_RIGHT_MARGIN;
344 //Allow space for an icon if need be
345 if(![self isGroupItem:item]&&[_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasIcon:)])
347 if([_secondaryDataSource sourceList:self itemHasIcon:item]) {
348 leftIndent += [self iconSize].width+(ICON_SPACING*2);
352 return NSMakeRect(leftIndent,
353 NSMidY(rowRect)-(cellSize.height/2.0),
354 NSWidth(rowRect)-rightIndent-leftIndent,
360 //This method calculates and returns the size of the badge for the row index passed to the method. If the
361 //row for the row index passed to the method does not have a badge, then NSZeroSize is returned.
362 - (NSSize)sizeOfBadgeAtRow:(NSInteger)rowIndex
364 id rowItem = [self itemAtRow:rowIndex];
366 //Make sure that the item has a badge
367 if(![self itemHasBadge:rowItem]) {
371 NSAttributedString *badgeAttrString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", [self badgeValueForItem:rowItem]]
372 attributes:[NSDictionary dictionaryWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName, nil]];
374 NSSize stringSize = [badgeAttrString size];
376 //Calculate the width needed to display the text or the minimum width if it's smaller
377 CGFloat width = stringSize.width+(2*BADGE_MARGIN);
379 if(width<MIN_BADGE_WIDTH) {
380 width = MIN_BADGE_WIDTH;
383 [badgeAttrString release];
385 return NSMakeSize(width, BADGE_HEIGHT);
392 - (void)drawRow:(NSInteger)rowIndex clipRect:(NSRect)clipRect
394 [super drawRow:rowIndex clipRect:clipRect];
396 id item = [self itemAtRow:rowIndex];
398 //Draw an icon if the item has one
399 if(![self isGroupItem:item]&&[_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasIcon:)])
401 if([_secondaryDataSource sourceList:self itemHasIcon:item])
403 NSRect cellFrame = [self frameOfCellAtColumn:0 row:rowIndex];
404 NSSize iconSize = [self iconSize];
405 NSRect iconRect = NSMakeRect(NSMinX(cellFrame)-iconSize.width-ICON_SPACING,
406 NSMidY(cellFrame)-(iconSize.width/2.0f),
410 if([_secondaryDataSource respondsToSelector:@selector(sourceList:iconForItem:)])
412 NSImage *icon = [_secondaryDataSource sourceList:self iconForItem:item];
416 NSSize actualIconSize = [icon size];
418 //If the icon is *smaller* than the size retrieved from the -iconSize property, make sure we
419 //reduce the size of the rectangle to draw the icon in, so that it is not stretched.
420 if((actualIconSize.width<iconSize.width)||(actualIconSize.height<iconSize.height))
422 iconRect = NSMakeRect(NSMidX(iconRect)-(actualIconSize.width/2.0f),
423 NSMidY(iconRect)-(actualIconSize.height/2.0f),
424 actualIconSize.width,
425 actualIconSize.height);
428 //Use 10.6 NSImage drawing if we can
429 if([icon respondsToSelector:@selector(drawInRect:fromRect:operation:fraction:respectFlipped:hints:)]) {
430 [icon drawInRect:iconRect
432 operation:NSCompositeSourceOver
434 respectFlipped:YES hints:nil];
437 [icon setFlipped:[self isFlipped]];
438 [icon drawInRect:iconRect
440 operation:NSCompositeSourceOver
448 //Draw the badge if the item has one
449 if([self itemHasBadge:item])
451 NSRect rowRect = [self rectOfRow:rowIndex];
452 NSSize badgeSize = [self sizeOfBadgeAtRow:rowIndex];
454 NSRect badgeFrame = NSMakeRect(NSMaxX(rowRect)-badgeSize.width-ROW_RIGHT_MARGIN,
455 NSMidY(rowRect)-(badgeSize.height/2.0),
459 [self drawBadgeForRow:rowIndex inRect:badgeFrame];
463 - (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame
465 id rowItem = [self itemAtRow:rowIndex];
467 NSBezierPath *badgePath = [NSBezierPath bezierPathWithRoundedRect:badgeFrame
468 xRadius:(BADGE_HEIGHT/2.0)
469 yRadius:(BADGE_HEIGHT/2.0)];
471 //Get window and control state to determine colours used
472 BOOL isVisible = [[NSApp mainWindow] isVisible];
473 BOOL isFocused = [[[self window] firstResponder] isEqual:self];
474 NSInteger rowBeingEdited = [self editedRow];
476 //Set the attributes based on the row state
477 NSDictionary *attributes;
478 NSColor *backgroundColor;
480 if([[self selectedRowIndexes] containsIndex:rowIndex])
482 backgroundColor = [NSColor whiteColor];
484 //Set the text color based on window and control state
487 if(isVisible && (isFocused || rowBeingEdited==rowIndex)) {
488 textColor = BADGE_SELECTED_TEXT_COLOR;
490 else if(isVisible && !isFocused) {
491 textColor = BADGE_SELECTED_UNFOCUSED_TEXT_COLOR;
494 textColor = BADGE_SELECTED_HIDDEN_TEXT_COLOR;
497 attributes = [[NSDictionary alloc] initWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName,
498 textColor, NSForegroundColorAttributeName, nil];
502 //Set the text colour based on window and control state
503 NSColor *badgeColor = [NSColor whiteColor];
506 //If the data source returns a custom colour..
507 if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeBackgroundColorForItem:)]) {
508 backgroundColor = [_secondaryDataSource sourceList:self badgeBackgroundColorForItem:rowItem];
510 if(backgroundColor==nil)
511 backgroundColor = BADGE_BACKGROUND_COLOR;
513 else { //Otherwise use the default (purple-blue colour)
514 backgroundColor = BADGE_BACKGROUND_COLOR;
517 //If the delegate wants a custom badge text colour..
518 if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeTextColorForItem:)]) {
519 badgeColor = [_secondaryDataSource sourceList:self badgeTextColorForItem:rowItem];
522 badgeColor = [NSColor whiteColor];
526 backgroundColor = BADGE_HIDDEN_BACKGROUND_COLOR;
529 attributes = [[NSDictionary alloc] initWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName,
530 badgeColor, NSForegroundColorAttributeName, nil];
533 [backgroundColor set];
536 //Draw the badge text
537 NSAttributedString *badgeAttrString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", [self badgeValueForItem:rowItem]]
538 attributes:attributes];
539 NSSize stringSize = [badgeAttrString size];
540 NSPoint badgeTextPoint = NSMakePoint(NSMidX(badgeFrame)-(stringSize.width/2.0), //Center in the badge frame
541 NSMidY(badgeFrame)-(stringSize.height/2.0)); //Center in the badge frame
542 [badgeAttrString drawAtPoint:badgeTextPoint];
544 [attributes release];
545 [badgeAttrString release];
549 #pragma mark Keyboard Handling
551 - (void)keyDown:(NSEvent *)theEvent
553 NSIndexSet *selectedIndexes = [self selectedRowIndexes];
555 NSString *keyCharacters = [theEvent characters];
557 //Make sure we have a selection
558 if([selectedIndexes count]>0)
560 if([keyCharacters length]>0)
562 unichar firstKey = [keyCharacters characterAtIndex:0];
564 if(firstKey==NSUpArrowFunctionKey||firstKey==NSDownArrowFunctionKey)
566 //Handle keyboard navigation across groups
567 if([selectedIndexes count]==1&&!([theEvent modifierFlags] & NSShiftKeyMask))
569 int delta = firstKey==NSDownArrowFunctionKey?1:-1; //Search "backwards" if up arrow, "forwards" if down
570 NSInteger newRow = [selectedIndexes firstIndex];
572 //Keep incrementing/decrementing the row until a non-header row is reached
576 //If out of bounds of the number of rows..
577 if(newRow<0||newRow==[self numberOfRows])
579 } while([self isGroupItem:[self itemAtRow:newRow]]);
582 [self selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
586 else if(firstKey==NSDeleteCharacter||firstKey==NSBackspaceCharacter)
588 //Post the notification
589 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLDeleteKeyPressedOnRowsNotification
591 userInfo:[NSDictionary dictionaryWithObject:selectedIndexes forKey:@"rows"]];
598 //We don't care about it
599 [super keyDown:theEvent];
603 #pragma mark Menu Handling
606 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
609 if([_secondaryDelegate respondsToSelector:@selector(sourceList:menuForEvent:item:)]) {
610 NSPoint clickPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
611 NSInteger row = [self rowAtPoint:clickPoint];
612 id clickedItem = [self itemAtRow:row];
613 m = [_secondaryDelegate sourceList:self menuForEvent:theEvent item:clickedItem];
616 m = [super menuForEvent:theEvent];
622 #pragma mark NSOutlineView Data Source methods
624 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
626 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
627 return [_secondaryDataSource sourceList:self numberOfChildrenOfItem:item];
634 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
636 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
637 return [_secondaryDataSource sourceList:self child:index ofItem:item];
644 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
646 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
647 return [_secondaryDataSource sourceList:self isItemExpandable:item];
654 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
656 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
657 return [_secondaryDataSource sourceList:self objectValueForItem:item];
664 - (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
666 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
667 [_secondaryDataSource sourceList:self setObjectValue:object forItem:item];
672 - (id)outlineView:(NSOutlineView *)outlineView itemForPersistentObject:(id)object
674 if([_secondaryDataSource respondsToSelector:@selector(sourceList:itemForPersistentObject:)]) {
675 return [_secondaryDataSource sourceList:self itemForPersistentObject:object];
681 - (id)outlineView:(NSOutlineView *)outlineView persistentObjectForItem:(id)item
683 if([_secondaryDataSource respondsToSelector:@selector(sourceList:persistentObjectForItem:)]) {
684 return [_secondaryDataSource sourceList:self persistentObjectForItem:item];
690 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pasteboard
692 if([_secondaryDataSource respondsToSelector:@selector(sourceList:writeItems:toPasteboard:)]) {
693 return [_secondaryDataSource sourceList:self writeItems:items toPasteboard:pasteboard];
699 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
701 if([_secondaryDataSource respondsToSelector:@selector(sourceList:validateDrop:proposedItem:proposedChildIndex:)]) {
702 return [_secondaryDataSource sourceList:self validateDrop:info proposedItem:item proposedChildIndex:index];
705 return NSDragOperationNone;
708 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
710 if([_secondaryDataSource respondsToSelector:@selector(sourceList:acceptDrop:item:childIndex:)]) {
711 return [_secondaryDataSource sourceList:self acceptDrop:info item:item childIndex:index];
716 - (NSArray *)outlineView:(NSOutlineView *)outlineView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedItems:(NSArray *)items
718 if([_secondaryDataSource respondsToSelector:@selector(sourceList:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:)]) {
719 return [_secondaryDataSource sourceList:self namesOfPromisedFilesDroppedAtDestination:dropDestination forDraggedItems:items];
727 #pragma mark NSOutlineView Delegate methods
729 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
731 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldExpandItem:)]) {
732 return [_secondaryDelegate sourceList:self shouldExpandItem:item];
738 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item
740 //Make sure the item isn't displayed as always expanded
741 if([self isGroupItem:item])
743 if([self isGroupAlwaysExpanded:item]) {
748 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldCollapseItem:)]) {
749 return [_secondaryDelegate sourceList:self shouldCollapseItem:item];
755 - (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item
757 if([_secondaryDelegate respondsToSelector:@selector(sourceList:dataCellForItem:)]) {
758 return [_secondaryDelegate sourceList:self dataCellForItem:item];
761 NSInteger row = [self rowForItem:item];
763 //Return the default table column
764 return [[[self tableColumns] objectAtIndex:0] dataCellForRow:row];
767 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
769 if([_secondaryDelegate respondsToSelector:@selector(sourceList:willDisplayCell:forItem:)]) {
770 [_secondaryDelegate sourceList:self willDisplayCell:cell forItem:item];
774 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
776 //Make sure that the item isn't a group as they can't be selected
777 if(![self isGroupItem:item]) {
778 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldSelectItem:)]) {
779 return [_secondaryDelegate sourceList:self shouldSelectItem:item];
790 - (NSIndexSet *)outlineView:(NSOutlineView *)outlineView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes
792 //The outline view will try to select the first row if -[allowsEmptySelection:] is set to NO – if this is a group row
793 //stop it from doing so and leave it to our implementation of-[reloadData] which will select the first non-group row
795 if([self numberOfSelectedRows]==0) {
796 if([self isGroupItem:[self itemAtRow:[proposedSelectionIndexes firstIndex]]]) {
797 return [NSIndexSet indexSet];
801 if([_secondaryDelegate respondsToSelector:@selector(sourceList:selectionIndexesForProposedSelection:)]) {
802 return [_secondaryDelegate sourceList:self selectionIndexesForProposedSelection:proposedSelectionIndexes];
805 //Since we implement this method, something must be returned to the outline view
806 return proposedSelectionIndexes;
809 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
811 //Group titles can't be edited
812 if([self isGroupItem:item])
815 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldEditItem:)]) {
816 return [_secondaryDelegate sourceList:self shouldEditItem:item];
823 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
825 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldTrackCell:forItem:)]) {
826 return [_secondaryDelegate sourceList:self shouldTrackCell:cell forItem:item];
832 - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
834 if([_secondaryDelegate respondsToSelector:@selector(sourceList:heightOfRowByItem:)]) {
835 return [_secondaryDelegate sourceList:self heightOfRowByItem:item];
838 return [self rowHeight];
841 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
843 return [self isGroupItem:item];
847 #pragma mark Notification handling
849 /* Notification wrappers */
850 - (void)outlineViewSelectionIsChanging:(NSNotification *)notification
852 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLSelectionIsChangingNotification object:self];
856 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
858 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLSelectionDidChangeNotification object:self];
861 - (void)outlineViewItemWillExpand:(NSNotification *)notification
863 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemWillExpandNotification
865 userInfo:[notification userInfo]];
868 - (void)outlineViewItemDidExpand:(NSNotification *)notification
870 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemDidExpandNotification
872 userInfo:[notification userInfo]];
875 - (void)outlineViewItemWillCollapse:(NSNotification *)notification
877 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemWillCollapseNotification
879 userInfo:[notification userInfo]];
882 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
884 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemDidCollapseNotification
886 userInfo:[notification userInfo]];
889 - (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector
891 NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
893 //Set the delegate as a receiver of the notification if it implements the notification method
894 if([_secondaryDelegate respondsToSelector:selector]) {
895 [defaultCenter addObserver:_secondaryDelegate