]> git.sesse.net Git - vlc/blob - modules/gui/macosx/PXSourceList.m
macosx: added an option to disable the native fullscreen mode on Lion
[vlc] / modules / gui / macosx / PXSourceList.m
1 //
2 //  PXSourceList.m
3 //  PXSourceList
4 //
5 //  Created by Alex Rozanski on 05/09/2009.
6 //  Copyright 2009-10 Alex Rozanski http://perspx.com
7 //
8 //  GC-enabled code revised by Stefan Vogt http://byteproject.net
9 //
10
11 #import "PXSourceList.h"
12
13 //Layout constants
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
20
21 //Drawing constants
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]
28
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";
37
38 #pragma mark -
39 @interface PXSourceList ()
40
41 - (NSSize)sizeOfBadgeAtRow:(NSInteger)rowIndex;
42 - (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame;
43 - (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector;
44
45 @end
46
47 #pragma mark -
48 @implementation PXSourceList
49
50 @synthesize iconSize = _iconSize;
51 @dynamic dataSource;
52 @dynamic delegate;
53
54 #pragma mark Init/Dealloc/Finalize
55
56 - (id)initWithCoder:(NSCoder*)decoder
57 {       
58         if(self=[super initWithCoder:decoder])
59         {
60                 [self setDelegate:(id<PXSourceListDelegate>)[super delegate]];
61                 [super setDelegate:self];
62                 
63                 [self setDataSource:(id<PXSourceListDataSource>)[super dataSource]];
64                 [super setDataSource:self];
65                 
66                 _iconSize = NSMakeSize(16,16);
67         }
68         
69         return self;
70 }
71
72 - (void)dealloc
73 {
74         //Remove ourselves as the delegate and data source to be safe
75         [super setDataSource:nil];
76         [super setDelegate:nil];
77         
78         //Unregister the delegate from receiving notifications
79         [[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
80         
81         [super dealloc];
82 }
83
84 - (void)finalize
85 {
86         //Remove ourselves as the delegate and data source to be safe
87         [super setDataSource:nil];
88         [super setDelegate:nil];
89         
90         //Unregister the delegate from receiving notifications
91         [[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
92         
93         [super finalize];
94 }
95
96 #pragma mark -
97 #pragma mark Custom Accessors
98
99 - (void)setDelegate:(id<PXSourceListDelegate>)aDelegate
100 {
101         //Unregister the old delegate from receiving notifications
102         [[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
103         
104         _secondaryDelegate = aDelegate;
105         
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:)];
121 }
122
123
124 - (void)setDataSource:(id<PXSourceListDataSource>)aDataSource
125 {
126         _secondaryDataSource = aDataSource;
127         
128         [self reloadData];
129 }
130
131 - (void)setIconSize:(NSSize)newIconSize
132 {
133         _iconSize = newIconSize;
134         
135         CGFloat rowHeight = [self rowHeight];
136         
137         //Make sure icon height does not exceed row height; if so constrain, keeping width and height in proportion
138         if(_iconSize.height>rowHeight)
139         {
140                 _iconSize.width = _iconSize.width * (rowHeight/_iconSize.height);
141                 _iconSize.height = rowHeight;
142         }
143 }
144
145 #pragma mark -
146 #pragma mark Data Management
147
148 - (void)reloadData
149 {
150         [super reloadData];
151         
152         //Expand items that are displayed as always expanded
153         if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)] &&
154            [_secondaryDelegate respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)])
155         {
156                 for(NSUInteger i=0;i<[self numberOfGroups];i++)
157                 {
158                         id item = [_secondaryDataSource sourceList:self child:i ofItem:nil];
159                         
160                         if([self isGroupAlwaysExpanded:item]) {
161                                 [self expandItem:item expandChildren:NO];
162                         }
163                 }
164                 
165         }
166         
167         //If there are selected rows and the item hierarchy has changed, make sure a Group row isn't
168         //selected
169         if([self numberOfSelectedRows]>0) {
170                 NSIndexSet *selectedIndexes = [self selectedRowIndexes];
171                 NSUInteger firstSelectedRow = [selectedIndexes firstIndex];
172                 
173                 //Is a group item selected?
174                 if([self isGroupItem:[self itemAtRow:firstSelectedRow]]) {
175                         //Work backwards to find the first non-group row
176                         BOOL foundRow = NO;
177                         for(NSUInteger i=firstSelectedRow;i>0;i--)
178                         {
179                                 if(![self isGroupItem:[self itemAtRow:i]]) {
180                                         [self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
181                                         foundRow = YES;
182                                         break;
183                                 }
184                         }
185                         
186                         //If there is no non-group row preceding the currently selected group item, remove the selection
187                         //from the Source List
188                         if(!foundRow) {
189                                 [self deselectAll:self];
190                         }
191                 }
192         }
193         else if(![self allowsEmptySelection]&&[self numberOfSelectedRows]==0)
194         {
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++)
197                 {
198                         if(![self isGroupItem:[self itemAtRow:i]]) {
199                                 [self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
200                                 break;
201                         }
202                 }
203         }
204 }
205
206 - (NSUInteger)numberOfGroups
207 {       
208         if([_secondaryDataSource respondsToSelector:@selector(sourceList:numberOfChildrenOfItem:)]) {
209                 return [_secondaryDataSource sourceList:self numberOfChildrenOfItem:nil];
210         }
211         
212         return 0;
213 }
214
215
216 - (BOOL)isGroupItem:(id)item
217 {
218         //Groups are defined as root items (at level 0)
219         return 0==[self levelForItem:item];
220 }
221
222
223 - (BOOL)isGroupAlwaysExpanded:(id)group
224 {
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];
230                 }
231         }
232         
233         return NO;
234 }
235
236
237 - (BOOL)itemHasBadge:(id)item
238 {
239         if([_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasBadge:)]) {
240                 return [_secondaryDataSource sourceList:self itemHasBadge:item];
241         }
242         
243         return NO;
244 }
245
246 - (NSInteger)badgeValueForItem:(id)item
247 {       
248         //Make sure that the item has a badge
249         if(![self itemHasBadge:item]) {
250                 return NSNotFound;
251         }
252         
253         if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeValueForItem:)]) {
254                 return [_secondaryDataSource sourceList:self badgeValueForItem:item];
255         }
256         
257         return NSNotFound;
258 }
259
260 #pragma mark -
261 #pragma mark Selection Handling
262
263 - (void)selectRowIndexes:(NSIndexSet*)indexes byExtendingSelection:(BOOL)extend
264 {
265         NSUInteger numberOfIndexes = [indexes count];
266         
267         //Prevent empty selection if we don't want it
268         if(![self allowsEmptySelection]&&0==numberOfIndexes) {
269                 return;
270         }
271         
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];
276         
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++)
281         {
282                 if(![self isGroupItem:[self itemAtRow:selectedIndexes[i]]]) {
283                         [newSelectionIndexes addIndex:selectedIndexes[i]];
284                 }
285         }
286         
287         //If there are any non-group rows selected
288         if([newSelectionIndexes count]>0) {
289                 [super selectRowIndexes:newSelectionIndexes byExtendingSelection:extend];
290         }
291         
292         //C memory management... *sigh*
293         free(selectedIndexes);
294 }
295
296 #pragma mark -
297 #pragma mark Layout
298
299 - (NSRect)frameOfOutlineCellAtRow:(NSInteger)row
300 {       
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]]) {
303                 return NSZeroRect;
304         }
305         
306         return [super frameOfOutlineCellAtRow:row];
307 }
308
309
310 - (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row
311 {
312         id item = [self itemAtRow:row];
313         
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];
319         
320         NSRect rowRect = [self rectOfRow:row];
321         
322         if([self isGroupItem:item])
323         {       
324                 CGFloat minX = NSMinX(cellFrame);
325                 
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]]) {
329                         minX = 7;
330                 }
331                 
332                 return NSMakeRect(minX,
333                                                   NSMidY(cellFrame)-(cellSize.height/2.0),
334                                                   NSWidth(rowRect)-minX,
335                                                   cellSize.height);
336         }
337         else
338         {
339                 CGFloat leftIndent = [self levelForRow:row]*[self indentationPerLevel]+DISCLOSURE_TRIANGLE_SPACE;
340                 
341                 //Calculate space left for a badge if need be
342                 CGFloat rightIndent = [self sizeOfBadgeAtRow:row].width+ROW_RIGHT_MARGIN;
343                 
344                 //Allow space for an icon if need be
345                 if(![self isGroupItem:item]&&[_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasIcon:)])
346                 {
347                         if([_secondaryDataSource sourceList:self itemHasIcon:item]) {
348                                 leftIndent += [self iconSize].width+(ICON_SPACING*2);
349                         }
350                 }
351                 
352                 return NSMakeRect(leftIndent,
353                                                   NSMidY(rowRect)-(cellSize.height/2.0),
354                                                   NSWidth(rowRect)-rightIndent-leftIndent,
355                                                   cellSize.height);
356         }
357 }
358
359
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
363 {
364         id rowItem = [self itemAtRow:rowIndex];
365         
366         //Make sure that the item has a badge
367         if(![self itemHasBadge:rowItem]) {
368                 return NSZeroSize;
369         }
370         
371         NSAttributedString *badgeAttrString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", [self badgeValueForItem:rowItem]]
372                                                                                                                                                   attributes:[NSDictionary dictionaryWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName, nil]];
373         
374         NSSize stringSize = [badgeAttrString size];
375         
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);
378         
379         if(width<MIN_BADGE_WIDTH) {
380                 width = MIN_BADGE_WIDTH;
381         }
382         
383         [badgeAttrString release];
384         
385         return NSMakeSize(width, BADGE_HEIGHT);
386 }
387
388
389 #pragma mark -
390 #pragma mark Drawing
391
392 - (void)drawRow:(NSInteger)rowIndex clipRect:(NSRect)clipRect
393 {       
394         [super drawRow:rowIndex clipRect:clipRect];
395         
396         id item = [self itemAtRow:rowIndex];
397         
398         //Draw an icon if the item has one
399         if(![self isGroupItem:item]&&[_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasIcon:)])
400         {
401                 if([_secondaryDataSource sourceList:self itemHasIcon:item])
402                 {
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),
407                                                                                  iconSize.width,
408                                                                                  iconSize.height);
409                         
410                         if([_secondaryDataSource respondsToSelector:@selector(sourceList:iconForItem:)])
411                         {
412                                 NSImage *icon = [_secondaryDataSource sourceList:self iconForItem:item];
413                                 
414                                 if(icon!=nil)
415                                 {
416                                         NSSize actualIconSize = [icon size];
417                                         
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))
421                                         {
422                                                 iconRect = NSMakeRect(NSMidX(iconRect)-(actualIconSize.width/2.0f),
423                                                                                           NSMidY(iconRect)-(actualIconSize.height/2.0f),
424                                                                                           actualIconSize.width,
425                                                                                           actualIconSize.height);
426                                         }
427                                         
428                     //Use 10.6 NSImage drawing if we can
429                     if([icon respondsToSelector:@selector(drawInRect:fromRect:operation:fraction:respectFlipped:hints:)]) {
430                         [icon drawInRect:iconRect
431                                 fromRect:NSZeroRect
432                                operation:NSCompositeSourceOver
433                                 fraction:1
434                           respectFlipped:YES hints:nil];
435                     }
436                     else {
437                         [icon setFlipped:[self isFlipped]];
438                         [icon drawInRect:iconRect
439                                 fromRect:NSZeroRect
440                                operation:NSCompositeSourceOver
441                                 fraction:1];
442                     }
443                                 }
444                         }
445                 }
446         }
447         
448         //Draw the badge if the item has one
449         if([self itemHasBadge:item])
450         {
451                 NSRect rowRect = [self rectOfRow:rowIndex];
452                 NSSize badgeSize = [self sizeOfBadgeAtRow:rowIndex];
453                 
454                 NSRect badgeFrame = NSMakeRect(NSMaxX(rowRect)-badgeSize.width-ROW_RIGHT_MARGIN,
455                                                                            NSMidY(rowRect)-(badgeSize.height/2.0),
456                                                                            badgeSize.width,
457                                                                            badgeSize.height);
458                 
459                 [self drawBadgeForRow:rowIndex inRect:badgeFrame];
460         }
461 }
462
463 - (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame
464 {
465         id rowItem = [self itemAtRow:rowIndex];
466         
467         NSBezierPath *badgePath = [NSBezierPath bezierPathWithRoundedRect:badgeFrame
468                                                                                                                           xRadius:(BADGE_HEIGHT/2.0)
469                                                                                                                           yRadius:(BADGE_HEIGHT/2.0)];
470         
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];
475         
476         //Set the attributes based on the row state
477         NSDictionary *attributes;
478         NSColor *backgroundColor;
479         
480         if([[self selectedRowIndexes] containsIndex:rowIndex])
481         {
482                 backgroundColor = [NSColor whiteColor];
483                 
484                 //Set the text color based on window and control state
485                 NSColor *textColor;
486                 
487                 if(isVisible && (isFocused || rowBeingEdited==rowIndex)) {
488                         textColor = BADGE_SELECTED_TEXT_COLOR;
489                 }
490                 else if(isVisible && !isFocused) {
491                         textColor = BADGE_SELECTED_UNFOCUSED_TEXT_COLOR;
492                 }
493                 else {
494                         textColor = BADGE_SELECTED_HIDDEN_TEXT_COLOR;
495                 }
496                 
497                 attributes = [[NSDictionary alloc] initWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName,
498                                           textColor, NSForegroundColorAttributeName, nil];
499         }
500         else
501         {
502                 //Set the text colour based on window and control state
503                 NSColor *badgeColor = [NSColor whiteColor];
504                 
505                 if(isVisible) {
506                         //If the data source returns a custom colour..
507                         if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeBackgroundColorForItem:)]) {
508                                 backgroundColor = [_secondaryDataSource sourceList:self badgeBackgroundColorForItem:rowItem];
509                                 
510                                 if(backgroundColor==nil)
511                                         backgroundColor = BADGE_BACKGROUND_COLOR;
512                         }
513                         else { //Otherwise use the default (purple-blue colour)
514                                 backgroundColor = BADGE_BACKGROUND_COLOR;
515                         }
516                         
517                         //If the delegate wants a custom badge text colour..
518                         if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeTextColorForItem:)]) {
519                                 badgeColor = [_secondaryDataSource sourceList:self badgeTextColorForItem:rowItem];
520                                 
521                                 if(badgeColor==nil)
522                                         badgeColor = [NSColor whiteColor];
523                         }
524                 }
525                 else { //Gray colour
526                         backgroundColor = BADGE_HIDDEN_BACKGROUND_COLOR;
527                 }
528                 
529                 attributes = [[NSDictionary alloc] initWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName,
530                                           badgeColor, NSForegroundColorAttributeName, nil];
531         }
532         
533         [backgroundColor set];
534         [badgePath fill];
535         
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];
543         
544         [attributes release];
545         [badgeAttrString release];
546 }
547
548 #pragma mark -
549 #pragma mark Keyboard Handling
550
551 - (void)keyDown:(NSEvent *)theEvent
552 {
553         NSIndexSet *selectedIndexes = [self selectedRowIndexes];
554         
555         NSString *keyCharacters = [theEvent characters];
556         
557         //Make sure we have a selection
558         if([selectedIndexes count]>0)
559         {
560                 if([keyCharacters length]>0)
561                 {
562                         unichar firstKey = [keyCharacters characterAtIndex:0];
563                         
564                         if(firstKey==NSUpArrowFunctionKey||firstKey==NSDownArrowFunctionKey)
565                         {
566                                 //Handle keyboard navigation across groups
567                                 if([selectedIndexes count]==1&&!([theEvent modifierFlags] & NSShiftKeyMask))
568                                 {
569                                         int delta = firstKey==NSDownArrowFunctionKey?1:-1;      //Search "backwards" if up arrow, "forwards" if down
570                                         NSInteger newRow = [selectedIndexes firstIndex];
571                                         
572                                         //Keep incrementing/decrementing the row until a non-header row is reached
573                                         do {
574                                                 newRow+=delta;
575                                                 
576                                                 //If out of bounds of the number of rows..
577                                                 if(newRow<0||newRow==[self numberOfRows])
578                                                         break;
579                                         } while([self isGroupItem:[self itemAtRow:newRow]]);
580                                         
581                                         
582                                         [self selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
583                                         return;
584                                 }
585                         }
586                         else if(firstKey==NSDeleteCharacter||firstKey==NSBackspaceCharacter)
587                         {       
588                                 //Post the notification
589                                 [[NSNotificationCenter defaultCenter] postNotificationName:PXSLDeleteKeyPressedOnRowsNotification
590                                                                                                                                         object:self
591                                                                                                                                   userInfo:[NSDictionary dictionaryWithObject:selectedIndexes forKey:@"rows"]];
592                                 
593                                 return;
594                         }
595                 }
596         }
597         
598         //We don't care about it
599         [super keyDown:theEvent];
600 }
601
602 #pragma mark -
603 #pragma mark Menu Handling
604
605
606 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
607 {
608         NSMenu * m = nil;
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];
614         }
615         if (m == nil) {
616                 m = [super menuForEvent:theEvent];
617         }
618         return m;
619 }
620
621 #pragma mark -
622 #pragma mark NSOutlineView Data Source methods
623
624 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
625 {       
626         if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
627                 return [_secondaryDataSource sourceList:self numberOfChildrenOfItem:item];
628         }
629         
630         return 0;
631 }
632
633
634 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
635 {       
636         if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
637                 return [_secondaryDataSource sourceList:self child:index ofItem:item];
638         }
639         
640         return nil;
641 }
642
643
644 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
645 {       
646         if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
647                 return [_secondaryDataSource sourceList:self isItemExpandable:item];
648         }
649         
650         return NO;
651 }
652
653
654 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
655 {
656         if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
657                 return [_secondaryDataSource sourceList:self objectValueForItem:item];
658         }
659         
660         return nil;
661 }
662
663
664 - (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
665 {       
666         if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
667                 [_secondaryDataSource sourceList:self setObjectValue:object forItem:item];
668         }
669 }
670
671
672 - (id)outlineView:(NSOutlineView *)outlineView itemForPersistentObject:(id)object
673 {
674         if([_secondaryDataSource respondsToSelector:@selector(sourceList:itemForPersistentObject:)]) {
675                 return [_secondaryDataSource sourceList:self itemForPersistentObject:object];
676         }
677         
678         return nil;
679 }
680
681 - (id)outlineView:(NSOutlineView *)outlineView persistentObjectForItem:(id)item
682 {
683         if([_secondaryDataSource respondsToSelector:@selector(sourceList:persistentObjectForItem:)]) {
684                 return [_secondaryDataSource sourceList:self persistentObjectForItem:item];
685         }
686         
687         return nil;
688 }
689
690 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pasteboard
691 {
692         if([_secondaryDataSource respondsToSelector:@selector(sourceList:writeItems:toPasteboard:)]) {
693                 return [_secondaryDataSource sourceList:self writeItems:items toPasteboard:pasteboard];
694         }
695         
696         return NO;
697 }
698
699 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
700 {
701         if([_secondaryDataSource respondsToSelector:@selector(sourceList:validateDrop:proposedItem:proposedChildIndex:)]) {
702                 return [_secondaryDataSource sourceList:self validateDrop:info proposedItem:item proposedChildIndex:index];
703         }
704         
705         return NSDragOperationNone;
706 }
707
708 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
709 {
710         if([_secondaryDataSource respondsToSelector:@selector(sourceList:acceptDrop:item:childIndex:)]) {
711                 return [_secondaryDataSource sourceList:self acceptDrop:info item:item childIndex:index];
712         }
713         
714         return NO;
715 }
716 - (NSArray *)outlineView:(NSOutlineView *)outlineView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedItems:(NSArray *)items
717 {
718         if([_secondaryDataSource respondsToSelector:@selector(sourceList:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:)]) {
719                 return [_secondaryDataSource sourceList:self namesOfPromisedFilesDroppedAtDestination:dropDestination forDraggedItems:items];
720         }
721         
722         return nil;
723 }
724
725
726 #pragma mark -
727 #pragma mark NSOutlineView Delegate methods
728
729 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
730 {
731         if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldExpandItem:)]) {
732                 return [_secondaryDelegate sourceList:self shouldExpandItem:item];
733         }
734         
735         return YES;
736 }
737
738 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item
739 {
740         //Make sure the item isn't displayed as always expanded
741         if([self isGroupItem:item])
742         {
743                 if([self isGroupAlwaysExpanded:item]) {
744                         return NO;
745                 }
746         }
747         
748         if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldCollapseItem:)]) {
749                 return [_secondaryDelegate sourceList:self shouldCollapseItem:item];
750         }
751         
752         return YES;
753 }
754
755 - (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item
756 {
757         if([_secondaryDelegate respondsToSelector:@selector(sourceList:dataCellForItem:)]) {
758                 return [_secondaryDelegate sourceList:self dataCellForItem:item];
759         }
760         
761         NSInteger row = [self rowForItem:item];
762         
763         //Return the default table column
764         return [[[self tableColumns] objectAtIndex:0] dataCellForRow:row];
765 }
766
767 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
768 {
769         if([_secondaryDelegate respondsToSelector:@selector(sourceList:willDisplayCell:forItem:)]) {
770                 [_secondaryDelegate sourceList:self willDisplayCell:cell forItem:item];
771         }
772 }
773
774 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
775 {       
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];
780                 }
781         }
782         else {
783                 return NO;
784         }
785         
786         return YES;
787 }
788
789
790 - (NSIndexSet *)outlineView:(NSOutlineView *)outlineView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes
791 {       
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
794         //for us.
795         if([self numberOfSelectedRows]==0) {
796                 if([self isGroupItem:[self itemAtRow:[proposedSelectionIndexes firstIndex]]]) {
797                         return [NSIndexSet indexSet];
798                 }
799         }
800         
801         if([_secondaryDelegate respondsToSelector:@selector(sourceList:selectionIndexesForProposedSelection:)]) {
802                 return [_secondaryDelegate sourceList:self selectionIndexesForProposedSelection:proposedSelectionIndexes];
803         }
804         
805         //Since we implement this method, something must be returned to the outline view
806         return proposedSelectionIndexes;
807 }
808
809 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
810 {
811         //Group titles can't be edited
812         if([self isGroupItem:item])
813                 return NO;
814         
815         if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldEditItem:)]) {
816                 return [_secondaryDelegate sourceList:self shouldEditItem:item];
817         }
818         
819         return YES;
820 }
821
822
823 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
824 {       
825         if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldTrackCell:forItem:)]) {
826                 return [_secondaryDelegate sourceList:self shouldTrackCell:cell forItem:item];
827         }
828         
829         return NO;
830 }
831
832 - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
833 {
834         if([_secondaryDelegate respondsToSelector:@selector(sourceList:heightOfRowByItem:)]) {
835                 return [_secondaryDelegate sourceList:self heightOfRowByItem:item];
836         }       
837         
838         return [self rowHeight];
839 }
840
841 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
842 {
843         return [self isGroupItem:item];
844 }
845
846 #pragma mark -
847 #pragma mark Notification handling
848
849 /* Notification wrappers */
850 - (void)outlineViewSelectionIsChanging:(NSNotification *)notification
851 {
852         [[NSNotificationCenter defaultCenter] postNotificationName:PXSLSelectionIsChangingNotification object:self];
853 }
854
855
856 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
857 {
858         [[NSNotificationCenter defaultCenter] postNotificationName:PXSLSelectionDidChangeNotification object:self];     
859 }
860
861 - (void)outlineViewItemWillExpand:(NSNotification *)notification
862 {
863         [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemWillExpandNotification
864                                                                                                                 object:self
865                                                                                                           userInfo:[notification userInfo]];
866 }
867
868 - (void)outlineViewItemDidExpand:(NSNotification *)notification
869 {
870         [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemDidExpandNotification
871                                                                                                                 object:self
872                                                                                                           userInfo:[notification userInfo]];    
873 }
874
875 - (void)outlineViewItemWillCollapse:(NSNotification *)notification
876 {
877         [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemWillCollapseNotification
878                                                                                                                 object:self
879                                                                                                           userInfo:[notification userInfo]];    
880 }
881
882 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
883 {
884         [[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemDidCollapseNotification
885                                                                                                                 object:self
886                                                                                                           userInfo:[notification userInfo]];    
887 }
888
889 - (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector
890 {
891         NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
892         
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
896                                                   selector:selector
897                                                           name:notification
898                                                         object:self];
899         }
900 }
901
902 @end