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 "CompatibilityFixes.h"
12 #import "PXSourceList.h"
13 #import "SideBarItem.h"
16 #define MIN_BADGE_WIDTH 22.0 //The minimum badge width for each item (default 22.0)
17 #define BADGE_HEIGHT 14.0 //The badge height for each item (default 14.0)
18 #define BADGE_MARGIN 5.0 //The spacing between the badge and the cell for that row
19 #define ROW_RIGHT_MARGIN 5.0 //The spacing between the right edge of the badge and the edge of the table column
20 #define ICON_SPACING 2.0 //The spacing between the icon and it's adjacent cell
21 #define DISCLOSURE_TRIANGLE_SPACE 18.0 //The indentation reserved for disclosure triangles for non-group items
24 #define BADGE_BACKGROUND_COLOR [NSColor colorWithCalibratedRed:(152/255.0) green:(168/255.0) blue:(202/255.0) alpha:1]
25 #define BADGE_HIDDEN_BACKGROUND_COLOR [NSColor colorWithDeviceWhite:(180/255.0) alpha:1]
26 #define BADGE_SELECTED_TEXT_COLOR [NSColor keyboardFocusIndicatorColor]
27 #define BADGE_SELECTED_UNFOCUSED_TEXT_COLOR [NSColor colorWithCalibratedRed:(153/255.0) green:(169/255.0) blue:(203/255.0) alpha:1]
28 #define BADGE_SELECTED_HIDDEN_TEXT_COLOR [NSColor colorWithCalibratedWhite:(170/255.0) alpha:1]
29 #define BADGE_FONT [NSFont boldSystemFontOfSize:11]
31 //Delegate notification constants
32 NSString * const PXSLSelectionIsChangingNotification = @"PXSourceListSelectionIsChanging";
33 NSString * const PXSLSelectionDidChangeNotification = @"PXSourceListSelectionDidChange";
34 NSString * const PXSLItemWillExpandNotification = @"PXSourceListItemWillExpand";
35 NSString * const PXSLItemDidExpandNotification = @"PXSourceListItemDidExpand";
36 NSString * const PXSLItemWillCollapseNotification = @"PXSourceListItemWillCollapse";
37 NSString * const PXSLItemDidCollapseNotification = @"PXSourceListItemDidCollapse";
38 NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKeyPressedOnRows";
41 @interface PXSourceList ()
43 - (NSSize)sizeOfBadgeAtRow:(NSInteger)rowIndex;
44 - (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame;
45 - (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector;
50 @implementation PXSourceList
52 @synthesize iconSize = _iconSize;
56 #pragma mark Init/Dealloc/Finalize
58 - (id)initWithCoder:(NSCoder*)decoder
60 if(self=[super initWithCoder:decoder])
62 [self setDelegate:(id<PXSourceListDelegate>)[super delegate]];
63 [super setDelegate:self];
64 [self setDataSource:(id<PXSourceListDataSource>)[super dataSource]];
65 [super setDataSource:self];
67 _iconSize = NSMakeSize(16,16);
75 //Unregister the delegate from receiving notifications
76 [[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
83 //Unregister the delegate from receiving notifications
84 [[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
90 #pragma mark Custom Accessors
92 - (void)setDelegate:(id<PXSourceListDelegate>)aDelegate
94 //Unregister the old delegate from receiving notifications
95 [[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
97 _secondaryDelegate = aDelegate;
98 //Register the new delegate to receive notifications
99 [self registerDelegateToReceiveNotification:PXSLSelectionIsChangingNotification withSelector:@selector(sourceListSelectionIsChanging:)];
100 [self registerDelegateToReceiveNotification:PXSLSelectionDidChangeNotification withSelector:@selector(sourceListSelectionDidChange:)];
101 [self registerDelegateToReceiveNotification:PXSLItemWillExpandNotification withSelector:@selector(sourceListItemWillExpand:)];
102 [self registerDelegateToReceiveNotification:PXSLItemDidExpandNotification withSelector:@selector(sourceListItemDidExpand:)];
103 [self registerDelegateToReceiveNotification:PXSLItemWillCollapseNotification withSelector:@selector(sourceListItemWillCollapse:)];
104 [self registerDelegateToReceiveNotification:PXSLItemDidCollapseNotification withSelector:@selector(sourceListItemDidCollapse:)];
105 [self registerDelegateToReceiveNotification:PXSLDeleteKeyPressedOnRowsNotification withSelector:@selector(sourceListDeleteKeyPressedOnRows:)];
109 - (void)setDataSource:(id<PXSourceListDataSource>)aDataSource
111 _secondaryDataSource = aDataSource;
113 if ([self respondsToSelector:@selector(reloadData)])
117 - (void)setIconSize:(NSSize)newIconSize
119 _iconSize = newIconSize;
120 CGFloat rowHeight = [self rowHeight];
122 //Make sure icon height does not exceed row height; if so constrain, keeping width and height in proportion
123 if(_iconSize.height>rowHeight)
125 _iconSize.width = _iconSize.width * (rowHeight/_iconSize.height);
126 _iconSize.height = rowHeight;
131 #pragma mark Data Management
135 if ([super respondsToSelector:@selector(reloadData)])
138 //Expand items that are displayed as always expanded
139 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)] &&
140 [_secondaryDelegate respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)])
142 for(NSUInteger i=0;i<[self numberOfGroups];i++)
144 id item = [_secondaryDataSource sourceList:self child:i ofItem:nil];
146 if([self isGroupAlwaysExpanded:item]) {
147 [self expandItem:item expandChildren:NO];
152 //If there are selected rows and the item hierarchy has changed, make sure a Group row isn't
154 if([self numberOfSelectedRows]>0) {
155 NSIndexSet *selectedIndexes = [self selectedRowIndexes];
156 NSUInteger firstSelectedRow = [selectedIndexes firstIndex];
158 //Is a group item selected?
159 if([self isGroupItem:[self itemAtRow:firstSelectedRow]]) {
160 //Work backwards to find the first non-group row
162 for(NSUInteger i=firstSelectedRow;i>0;i--)
164 if(![self isGroupItem:[self itemAtRow:i]]) {
165 [self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
171 //If there is no non-group row preceding the currently selected group item, remove the selection
172 //from the Source List
174 [self deselectAll:self];
178 else if(![self allowsEmptySelection]&&[self numberOfSelectedRows]==0)
180 //Select the first non-group row if no rows are selected, and empty selection is disallowed
181 for(NSUInteger i=0;i<[self numberOfRows];i++)
183 if(![self isGroupItem:[self itemAtRow:i]]) {
184 [self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
191 - (NSUInteger)numberOfGroups
193 if([_secondaryDataSource respondsToSelector:@selector(sourceList:numberOfChildrenOfItem:)]) {
194 return [_secondaryDataSource sourceList:self numberOfChildrenOfItem:nil];
200 - (BOOL)isGroupItem:(id)item
202 //Groups are defined as root items (at level 0)
203 return 0==[self levelForItem:item];
207 - (BOOL)isGroupAlwaysExpanded:(id)group
209 //Make sure that the item IS a group to prevent unwanted queries sent to the data source
210 if([self isGroupItem:group]) {
211 //Query the data source
212 if([_secondaryDelegate respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)]) {
213 return [_secondaryDelegate sourceList:self isGroupAlwaysExpanded:group];
221 - (BOOL)itemHasBadge:(id)item
223 if([_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasBadge:)]) {
224 return [_secondaryDataSource sourceList:self itemHasBadge:item];
230 - (NSInteger)badgeValueForItem:(id)item
232 //Make sure that the item has a badge
233 if(![self itemHasBadge:item]) {
237 if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeValueForItem:)]) {
238 return [_secondaryDataSource sourceList:self badgeValueForItem:item];
245 #pragma mark Selection Handling
247 - (void)selectRowIndexes:(NSIndexSet*)indexes byExtendingSelection:(BOOL)extend
249 NSUInteger numberOfIndexes = [indexes count];
251 //Prevent empty selection if we don't want it
252 if(![self allowsEmptySelection]&&0==numberOfIndexes) {
256 //Would use blocks but we're also targeting 10.5...
257 //Get the selected indexes
258 NSUInteger *selectedIndexes = malloc(sizeof(NSUInteger)*numberOfIndexes);
259 [indexes getIndexes:selectedIndexes maxCount:numberOfIndexes inIndexRange:nil];
261 //Loop through the indexes and only add non-group row indexes
262 //Allows selection across groups without selecting the group rows
263 NSMutableIndexSet *newSelectionIndexes = [NSMutableIndexSet indexSet];
264 for(NSInteger i=0;i<numberOfIndexes;i++)
266 if(![self isGroupItem:[self itemAtRow:selectedIndexes[i]]]) {
267 [newSelectionIndexes addIndex:selectedIndexes[i]];
271 //If there are any non-group rows selected
272 if([newSelectionIndexes count]>0) {
273 [super selectRowIndexes:newSelectionIndexes byExtendingSelection:extend];
276 //C memory management... *sigh*
277 free(selectedIndexes);
283 - (NSRect)frameOfOutlineCellAtRow:(NSInteger)row
285 //Return a zero-rect if the item is always expanded (a disclosure triangle will not be drawn)
286 if([self isGroupAlwaysExpanded:[self itemAtRow:row]]) {
290 return [super frameOfOutlineCellAtRow:row];
294 - (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row
296 id item = [self itemAtRow:row];
298 NSCell *cell = [self preparedCellAtColumn:column row:row];
299 NSSize cellSize = [cell cellSize];
300 if (!([cell type] == NSImageCellType) && !([cell type] == NSTextCellType))
301 cellSize = [cell cellSizeForBounds:[super frameOfCellAtColumn:column row:row]];
303 NSRect cellFrame = [super frameOfCellAtColumn:column row:row];
304 NSRect rowRect = [self rectOfRow:row];
306 if([self isGroupItem:item])
308 CGFloat minX = NSMinX(cellFrame);
310 //Set the origin x-coord; if there are no children of the group at current, there will still be a
311 //margin to the left of the cell (in cellFrame), which we don't want
312 if([self isGroupAlwaysExpanded:[self itemAtRow:row]]) {
316 return NSMakeRect(minX, NSMidY(cellFrame)-(cellSize.height/2.0), NSWidth(rowRect)-minX, cellSize.height);
320 CGFloat leftIndent = ([self levelForRow:row] -1)*[self indentationPerLevel]+DISCLOSURE_TRIANGLE_SPACE;
322 //Calculate space left for a badge if need be
323 CGFloat rightIndent = [self sizeOfBadgeAtRow:row].width+ROW_RIGHT_MARGIN;
325 //Allow space for an icon if need be
326 if(![self isGroupItem:item]&&[_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasIcon:)])
328 if([_secondaryDataSource sourceList:self itemHasIcon:item]) {
329 leftIndent += [self iconSize].width+(ICON_SPACING*2);
332 return NSMakeRect(leftIndent, NSMidY(rowRect)-(cellSize.height/2.0), NSWidth(rowRect)-rightIndent-leftIndent, cellSize.height);
337 //This method calculates and returns the size of the badge for the row index passed to the method. If the
338 //row for the row index passed to the method does not have a badge, then NSZeroSize is returned.
339 - (NSSize)sizeOfBadgeAtRow:(NSInteger)rowIndex
341 id rowItem = [self itemAtRow:rowIndex];
343 //Make sure that the item has a badge
344 if(![self itemHasBadge:rowItem]) {
348 NSAttributedString *badgeAttrString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld", [self badgeValueForItem:rowItem]] attributes:[NSDictionary dictionaryWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName, nil]];
349 NSSize stringSize = [badgeAttrString size];
351 //Calculate the width needed to display the text or the minimum width if it's smaller
352 CGFloat width = stringSize.width+(2*BADGE_MARGIN);
354 if(width<MIN_BADGE_WIDTH) {
355 width = MIN_BADGE_WIDTH;
358 [badgeAttrString release];
360 return NSMakeSize(width, BADGE_HEIGHT);
367 - (void)drawRow:(NSInteger)rowIndex clipRect:(NSRect)clipRect
369 [super drawRow:rowIndex clipRect:clipRect];
370 id item = [self itemAtRow:rowIndex];
372 //Draw an icon if the item has one
373 if(![self isGroupItem:item]&&[_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasIcon:)])
375 if([_secondaryDataSource sourceList:self itemHasIcon:item])
377 NSRect cellFrame = [self frameOfCellAtColumn:0 row:rowIndex];
378 NSSize iconSize = [self iconSize];
379 NSRect iconRect = NSMakeRect(NSMinX(cellFrame)-iconSize.width-ICON_SPACING, NSMidY(cellFrame)-(iconSize.width/2.0f), iconSize.width, iconSize.height);
381 if([_secondaryDataSource respondsToSelector:@selector(sourceList:iconForItem:)])
383 NSImage *icon = [_secondaryDataSource sourceList:self iconForItem:item];
386 NSSize actualIconSize = [icon size];
387 //If the icon is *smaller* than the size retrieved from the -iconSize property, make sure we
388 //reduce the size of the rectangle to draw the icon in, so that it is not stretched.
389 if((actualIconSize.width<iconSize.width)||(actualIconSize.height<iconSize.height))
391 iconRect = NSMakeRect(NSMidX(iconRect)-(actualIconSize.width/2.0f), NSMidY(iconRect)-(actualIconSize.height/2.0f), actualIconSize.width, actualIconSize.height);
394 //Use 10.6 NSImage drawing if we can
395 if(NSAppKitVersionNumber >= 1115.2) { // Lion
396 [icon drawInRect:iconRect
398 operation:NSCompositeSourceOver
400 respectFlipped:YES hints:nil];
403 [icon setFlipped:[self isFlipped]];
404 [icon drawInRect:iconRect
406 operation:NSCompositeSourceOver
414 //Draw the badge if the item has one
415 if([self itemHasBadge:item])
417 NSRect rowRect = [self rectOfRow:rowIndex];
418 NSSize badgeSize = [self sizeOfBadgeAtRow:rowIndex];
420 NSRect badgeFrame = NSMakeRect(NSMaxX(rowRect)-badgeSize.width-ROW_RIGHT_MARGIN,
421 NSMidY(rowRect)-(badgeSize.height/2.0),
424 [self drawBadgeForRow:rowIndex inRect:badgeFrame];
428 - (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame
430 id rowItem = [self itemAtRow:rowIndex];
431 NSBezierPath *badgePath = [NSBezierPath bezierPathWithRoundedRect:badgeFrame
432 xRadius:(BADGE_HEIGHT/2.0)
433 yRadius:(BADGE_HEIGHT/2.0)];
435 //Get window and control state to determine colours used
436 BOOL isVisible = [[NSApp mainWindow] isVisible];
437 BOOL isFocused = [[[self window] firstResponder] isEqual:self];
438 NSInteger rowBeingEdited = [self editedRow];
440 //Set the attributes based on the row state
441 NSDictionary *attributes;
442 NSColor *backgroundColor;
444 if([[self selectedRowIndexes] containsIndex:rowIndex])
446 backgroundColor = [NSColor whiteColor];
447 //Set the text color based on window and control state
449 if(isVisible && (isFocused || rowBeingEdited==rowIndex)) {
450 textColor = BADGE_SELECTED_TEXT_COLOR;
452 else if(isVisible && !isFocused) {
453 textColor = BADGE_SELECTED_UNFOCUSED_TEXT_COLOR;
456 textColor = BADGE_SELECTED_HIDDEN_TEXT_COLOR;
459 attributes = [[NSDictionary alloc] initWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName, textColor, NSForegroundColorAttributeName, nil];
463 //Set the text colour based on window and control state
464 NSColor *badgeColor = [NSColor whiteColor];
467 //If the data source returns a custom colour..
468 if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeBackgroundColorForItem:)]) {
469 backgroundColor = [_secondaryDataSource sourceList:self badgeBackgroundColorForItem:rowItem];
471 if(backgroundColor==nil)
472 backgroundColor = BADGE_BACKGROUND_COLOR;
474 else { //Otherwise use the default (purple-blue colour)
475 backgroundColor = BADGE_BACKGROUND_COLOR;
478 //If the delegate wants a custom badge text colour..
479 if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeTextColorForItem:)]) {
480 badgeColor = [_secondaryDataSource sourceList:self badgeTextColorForItem:rowItem];
483 badgeColor = [NSColor whiteColor];
487 backgroundColor = BADGE_HIDDEN_BACKGROUND_COLOR;
489 attributes = [[NSDictionary alloc] initWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName, badgeColor, NSForegroundColorAttributeName, nil];
492 [backgroundColor set];
495 //Draw the badge text
496 NSAttributedString *badgeAttrString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld", [self badgeValueForItem:rowItem]] attributes:attributes];
498 NSSize stringSize = [badgeAttrString size];
499 NSPoint badgeTextPoint = NSMakePoint(NSMidX(badgeFrame)-(stringSize.width/2.0), //Center in the badge frame
500 NSMidY(badgeFrame)-(stringSize.height/2.0)); //Center in the badge frame
501 [badgeAttrString drawAtPoint:badgeTextPoint];
502 [attributes release];
503 [badgeAttrString release];
507 #pragma mark Keyboard Handling
509 - (void)keyDown:(NSEvent *)theEvent
511 NSIndexSet *selectedIndexes = [self selectedRowIndexes];
512 NSString *keyCharacters = [theEvent characters];
514 //Make sure we have a selection
515 if([selectedIndexes count]>0)
517 if([keyCharacters length]>0)
519 unichar firstKey = [keyCharacters characterAtIndex:0];
520 if(firstKey==NSUpArrowFunctionKey||firstKey==NSDownArrowFunctionKey)
522 //Handle keyboard navigation across groups
523 if([selectedIndexes count]==1&&!([theEvent modifierFlags] & NSShiftKeyMask))
525 int delta = firstKey==NSDownArrowFunctionKey?1:-1;
526 //Search "backwards" if up arrow, "forwards" if down
528 NSInteger newRow = [selectedIndexes firstIndex];
529 //Keep incrementing/decrementing the row until a non-header row is reached
533 //If out of bounds of the number of rows..
534 if(newRow<0||newRow==[self numberOfRows])
536 } while([self isGroupItem:[self itemAtRow:newRow]]);
538 [self selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
543 else if(firstKey==NSDeleteCharacter||firstKey==NSBackspaceCharacter)
545 //Post the notification
546 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLDeleteKeyPressedOnRowsNotification object:self userInfo:[NSDictionary dictionaryWithObject:selectedIndexes forKey:@"rows"]];
556 //We don't care about it
557 [super keyDown:theEvent];
561 #pragma mark Menu Handling
564 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
568 if([_secondaryDelegate respondsToSelector:@selector(sourceList:menuForEvent:item:)]) {
569 NSPoint clickPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
570 NSInteger row = [self rowAtPoint:clickPoint];
571 id clickedItem = [self itemAtRow:row];
573 if ([clickedItem sdtype] > 0)
574 m = [_secondaryDelegate sourceList:self menuForEvent:theEvent item:clickedItem];
576 m = [super menuForEvent:theEvent];
580 m = [super menuForEvent:theEvent];
587 #pragma mark NSOutlineView Data Source methods
589 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
591 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
592 return [_secondaryDataSource sourceList:self numberOfChildrenOfItem:item];
599 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
601 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
602 return [_secondaryDataSource sourceList:self child:index ofItem:item];
609 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
611 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
612 return [_secondaryDataSource sourceList:self isItemExpandable:item];
619 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
621 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
622 return [_secondaryDataSource sourceList:self objectValueForItem:item];
629 - (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
631 if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
632 [_secondaryDataSource sourceList:self setObjectValue:object forItem:item];
637 - (id)outlineView:(NSOutlineView *)outlineView itemForPersistentObject:(id)object
639 if([_secondaryDataSource respondsToSelector:@selector(sourceList:itemForPersistentObject:)]) {
640 return [_secondaryDataSource sourceList:self itemForPersistentObject:object];
646 - (id)outlineView:(NSOutlineView *)outlineView persistentObjectForItem:(id)item
648 if([_secondaryDataSource respondsToSelector:@selector(sourceList:persistentObjectForItem:)]) {
649 return [_secondaryDataSource sourceList:self persistentObjectForItem:item];
655 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pasteboard
657 if([_secondaryDataSource respondsToSelector:@selector(sourceList:writeItems:toPasteboard:)]) {
658 return [_secondaryDataSource sourceList:self writeItems:items toPasteboard:pasteboard];
664 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
666 if([_secondaryDataSource respondsToSelector:@selector(sourceList:validateDrop:proposedItem:proposedChildIndex:)]) {
667 return [_secondaryDataSource sourceList:self validateDrop:info proposedItem:item proposedChildIndex:index];
670 return NSDragOperationNone;
673 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
675 if([_secondaryDataSource respondsToSelector:@selector(sourceList:acceptDrop:item:childIndex:)]) {
676 return [_secondaryDataSource sourceList:self acceptDrop:info item:item childIndex:index];
681 - (NSArray *)outlineView:(NSOutlineView *)outlineView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedItems:(NSArray *)items
683 if([_secondaryDataSource respondsToSelector:@selector(sourceList:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:)]) {
684 return [_secondaryDataSource sourceList:self namesOfPromisedFilesDroppedAtDestination:dropDestination forDraggedItems:items];
692 #pragma mark NSOutlineView Delegate methods
694 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
696 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldExpandItem:)]) {
697 return [_secondaryDelegate sourceList:self shouldExpandItem:item];
703 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item
705 //Make sure the item isn't displayed as always expanded
706 if([self isGroupItem:item])
708 if([self isGroupAlwaysExpanded:item]) {
713 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldCollapseItem:)]) {
714 return [_secondaryDelegate sourceList:self shouldCollapseItem:item];
720 - (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item
722 if([_secondaryDelegate respondsToSelector:@selector(sourceList:dataCellForItem:)]) {
723 return [_secondaryDelegate sourceList:self dataCellForItem:item];
726 NSInteger row = [self rowForItem:item];
728 //Return the default table column
729 return [[[self tableColumns] objectAtIndex:0] dataCellForRow:row];
732 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
734 if([_secondaryDelegate respondsToSelector:@selector(sourceList:willDisplayCell:forItem:)]) {
735 [_secondaryDelegate sourceList:self willDisplayCell:cell forItem:item];
739 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
741 //Make sure that the item isn't a group as they can't be selected
742 if(![self isGroupItem:item]) {
743 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldSelectItem:)]) {
744 return [_secondaryDelegate sourceList:self shouldSelectItem:item];
755 - (NSIndexSet *)outlineView:(NSOutlineView *)outlineView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes
757 //The outline view will try to select the first row if -[allowsEmptySelection:] is set to NO – if this is a group row
758 //stop it from doing so and leave it to our implementation of-[reloadData] which will select the first non-group row
760 if([self numberOfSelectedRows]==0) {
761 if([self isGroupItem:[self itemAtRow:[proposedSelectionIndexes firstIndex]]]) {
762 return [NSIndexSet indexSet];
766 if([_secondaryDelegate respondsToSelector:@selector(sourceList:selectionIndexesForProposedSelection:)]) {
767 return [_secondaryDelegate sourceList:self selectionIndexesForProposedSelection:proposedSelectionIndexes];
770 //Since we implement this method, something must be returned to the outline view
771 return proposedSelectionIndexes;
774 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
776 //Group titles can't be edited
777 if([self isGroupItem:item])
780 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldEditItem:)]) {
781 return [_secondaryDelegate sourceList:self shouldEditItem:item];
788 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
790 if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldTrackCell:forItem:)]) {
791 return [_secondaryDelegate sourceList:self shouldTrackCell:cell forItem:item];
797 - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
799 if([_secondaryDelegate respondsToSelector:@selector(sourceList:heightOfRowByItem:)]) {
800 return [_secondaryDelegate sourceList:self heightOfRowByItem:item];
803 return [self rowHeight];
806 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
808 return [self isGroupItem:item];
812 #pragma mark Notification handling
814 /* Notification wrappers */
815 - (void)outlineViewSelectionIsChanging:(NSNotification *)notification
817 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLSelectionIsChangingNotification object:self];
821 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
823 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLSelectionDidChangeNotification object:self];
826 - (void)outlineViewItemWillExpand:(NSNotification *)notification
828 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemWillExpandNotification object:self userInfo:[notification userInfo]];
831 - (void)outlineViewItemDidExpand:(NSNotification *)notification
833 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemDidExpandNotification object:self userInfo:[notification userInfo]];
836 - (void)outlineViewItemWillCollapse:(NSNotification *)notification
838 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemWillCollapseNotification object:self userInfo:[notification userInfo]];
841 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
843 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemDidCollapseNotification object:self userInfo:[notification userInfo]];
846 - (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector
848 NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
850 //Set the delegate as a receiver of the notification if it implements the notification method
851 if([_secondaryDelegate respondsToSelector:selector]) {
852 [defaultCenter addObserver:_secondaryDelegate selector:selector name:notification object:self];