|
|
|
@@ -44,12 +44,12 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
#pragma mark - Setup/Teardown
|
|
|
|
|
|
|
|
|
|
- (id)initWithCoder:(NSCoder*)decoder
|
|
|
|
|
{
|
|
|
|
|
if(self=[super initWithCoder:decoder]) {
|
|
|
|
|
{
|
|
|
|
|
if(self=[super initWithCoder:decoder]) {
|
|
|
|
|
[self PXSL_setup];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id)initWithFrame:(NSRect)frameRect
|
|
|
|
@@ -57,7 +57,7 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
if((self = [super initWithFrame:frameRect])) {
|
|
|
|
|
[self PXSL_setup];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -69,9 +69,9 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
|
{
|
|
|
|
|
//Remove ourselves as the delegate and data source to be safe
|
|
|
|
|
[super setDataSource:nil];
|
|
|
|
|
[super setDelegate:nil];
|
|
|
|
|
//Remove ourselves as the delegate and data source to be safe
|
|
|
|
|
[super setDataSource:nil];
|
|
|
|
|
[super setDelegate:nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -80,7 +80,7 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
|
|
|
|
|
- (void)setDelegate:(id<PXSourceListDelegate>)aDelegate
|
|
|
|
|
{
|
|
|
|
|
self.delegateDataSourceProxy.delegate = aDelegate;
|
|
|
|
|
self.delegateDataSourceProxy.delegate = aDelegate;
|
|
|
|
|
|
|
|
|
|
[super setDelegate:nil];
|
|
|
|
|
if (aDelegate)
|
|
|
|
@@ -90,27 +90,27 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
|
|
|
|
|
- (void)setDataSource:(id<PXSourceListDataSource>)aDataSource
|
|
|
|
|
{
|
|
|
|
|
self.delegateDataSourceProxy.dataSource = aDataSource;
|
|
|
|
|
self.delegateDataSourceProxy.dataSource = aDataSource;
|
|
|
|
|
|
|
|
|
|
[super setDataSource:nil];
|
|
|
|
|
if (aDataSource)
|
|
|
|
|
[super setDataSource:self.delegateDataSourceProxy];
|
|
|
|
|
|
|
|
|
|
[self reloadData];
|
|
|
|
|
[self reloadData];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)setIconSize:(NSSize)newIconSize
|
|
|
|
|
{
|
|
|
|
|
_iconSize = newIconSize;
|
|
|
|
|
|
|
|
|
|
CGFloat rowHeight = [self rowHeight];
|
|
|
|
|
|
|
|
|
|
//Make sure icon height does not exceed row height; if so constrain, keeping width and height in proportion
|
|
|
|
|
if(_iconSize.height>rowHeight)
|
|
|
|
|
{
|
|
|
|
|
_iconSize.width = _iconSize.width * (rowHeight/_iconSize.height);
|
|
|
|
|
_iconSize.height = rowHeight;
|
|
|
|
|
}
|
|
|
|
|
_iconSize = newIconSize;
|
|
|
|
|
|
|
|
|
|
CGFloat rowHeight = [self rowHeight];
|
|
|
|
|
|
|
|
|
|
//Make sure icon height does not exceed row height; if so constrain, keeping width and height in proportion
|
|
|
|
|
if(_iconSize.height>rowHeight)
|
|
|
|
|
{
|
|
|
|
|
_iconSize.width = _iconSize.width * (rowHeight/_iconSize.height);
|
|
|
|
|
_iconSize.height = rowHeight;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (PXSourceListBadgeCell *)reusableBadgeCell
|
|
|
|
@@ -141,90 +141,90 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
|
|
|
|
|
- (void)reloadData
|
|
|
|
|
{
|
|
|
|
|
[super reloadData];
|
|
|
|
|
|
|
|
|
|
//Expand items that are displayed as always expanded
|
|
|
|
|
if([self.delegateDataSourceProxy conformsToProtocol:@protocol(PXSourceListDataSource)] &&
|
|
|
|
|
[self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)])
|
|
|
|
|
{
|
|
|
|
|
for(NSUInteger i=0;i<[self numberOfGroups];i++)
|
|
|
|
|
{
|
|
|
|
|
id item = [self.delegateDataSourceProxy sourceList:self child:i ofItem:nil];
|
|
|
|
|
|
|
|
|
|
if([self isGroupAlwaysExpanded:item]) {
|
|
|
|
|
[self expandItem:item expandChildren:NO];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If there are selected rows and the item hierarchy has changed, make sure a Group row isn't
|
|
|
|
|
//selected
|
|
|
|
|
if([self numberOfSelectedRows]>0) {
|
|
|
|
|
NSIndexSet *selectedIndexes = [self selectedRowIndexes];
|
|
|
|
|
NSUInteger firstSelectedRow = [selectedIndexes firstIndex];
|
|
|
|
|
|
|
|
|
|
//Is a group item selected?
|
|
|
|
|
if([self isGroupItem:[self itemAtRow:firstSelectedRow]]) {
|
|
|
|
|
//Work backwards to find the first non-group row
|
|
|
|
|
BOOL foundRow = NO;
|
|
|
|
|
for(NSUInteger i=firstSelectedRow;i>0;i--)
|
|
|
|
|
{
|
|
|
|
|
if(![self isGroupItem:[self itemAtRow:i]]) {
|
|
|
|
|
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
|
|
|
|
|
foundRow = YES;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If there is no non-group row preceding the currently selected group item, remove the selection
|
|
|
|
|
//from the Source List
|
|
|
|
|
if(!foundRow) {
|
|
|
|
|
[self deselectAll:self];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(![self allowsEmptySelection]&&[self numberOfSelectedRows]==0)
|
|
|
|
|
{
|
|
|
|
|
//Select the first non-group row if no rows are selected, and empty selection is disallowed
|
|
|
|
|
for(NSUInteger i=0;i<[self numberOfRows];i++)
|
|
|
|
|
{
|
|
|
|
|
if(![self isGroupItem:[self itemAtRow:i]]) {
|
|
|
|
|
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[super reloadData];
|
|
|
|
|
|
|
|
|
|
//Expand items that are displayed as always expanded
|
|
|
|
|
if([self.delegateDataSourceProxy conformsToProtocol:@protocol(PXSourceListDataSource)] &&
|
|
|
|
|
[self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)])
|
|
|
|
|
{
|
|
|
|
|
for(NSUInteger i=0;i<[self numberOfGroups];i++)
|
|
|
|
|
{
|
|
|
|
|
id item = [self.delegateDataSourceProxy sourceList:self child:i ofItem:nil];
|
|
|
|
|
|
|
|
|
|
if([self isGroupAlwaysExpanded:item]) {
|
|
|
|
|
[self expandItem:item expandChildren:NO];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If there are selected rows and the item hierarchy has changed, make sure a Group row isn't
|
|
|
|
|
//selected
|
|
|
|
|
if([self numberOfSelectedRows]>0) {
|
|
|
|
|
NSIndexSet *selectedIndexes = [self selectedRowIndexes];
|
|
|
|
|
NSUInteger firstSelectedRow = [selectedIndexes firstIndex];
|
|
|
|
|
|
|
|
|
|
//Is a group item selected?
|
|
|
|
|
if([self isGroupItem:[self itemAtRow:firstSelectedRow]]) {
|
|
|
|
|
//Work backwards to find the first non-group row
|
|
|
|
|
BOOL foundRow = NO;
|
|
|
|
|
for(NSUInteger i=firstSelectedRow;i>0;i--)
|
|
|
|
|
{
|
|
|
|
|
if(![self isGroupItem:[self itemAtRow:i]]) {
|
|
|
|
|
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
|
|
|
|
|
foundRow = YES;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If there is no non-group row preceding the currently selected group item, remove the selection
|
|
|
|
|
//from the Source List
|
|
|
|
|
if(!foundRow) {
|
|
|
|
|
[self deselectAll:self];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(![self allowsEmptySelection]&&[self numberOfSelectedRows]==0)
|
|
|
|
|
{
|
|
|
|
|
//Select the first non-group row if no rows are selected, and empty selection is disallowed
|
|
|
|
|
for(NSUInteger i=0;i<[self numberOfRows];i++)
|
|
|
|
|
{
|
|
|
|
|
if(![self isGroupItem:[self itemAtRow:i]]) {
|
|
|
|
|
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:i] byExtendingSelection:NO];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSUInteger)numberOfGroups
|
|
|
|
|
{
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:numberOfChildrenOfItem:)]) {
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self numberOfChildrenOfItem:nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
{
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:numberOfChildrenOfItem:)]) {
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self numberOfChildrenOfItem:nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)isGroupItem:(id)item
|
|
|
|
|
{
|
|
|
|
|
//Groups are defined as root items (at level 0)
|
|
|
|
|
return 0==[self levelForItem:item];
|
|
|
|
|
//Groups are defined as root items (at level 0)
|
|
|
|
|
return 0==[self levelForItem:item];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)isGroupAlwaysExpanded:(id)group
|
|
|
|
|
{
|
|
|
|
|
//Make sure that the item IS a group to prevent unwanted queries sent to the data source
|
|
|
|
|
if([self isGroupItem:group]) {
|
|
|
|
|
//Query the data source
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)]) {
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self isGroupAlwaysExpanded:group];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
|
//Make sure that the item IS a group to prevent unwanted queries sent to the data source
|
|
|
|
|
if([self isGroupItem:group]) {
|
|
|
|
|
//Query the data source
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)]) {
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self isGroupAlwaysExpanded:group];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -234,11 +234,11 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
if (self.isViewBasedSourceList)
|
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:itemHasBadge:)]) {
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self itemHasBadge:item];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:itemHasBadge:)]) {
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self itemHasBadge:item];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSInteger)badgeValueForItem:(id)item
|
|
|
|
@@ -246,12 +246,12 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
// Since badges are managed by custom views and logic in view-based mode, we can't determine this.
|
|
|
|
|
if (self.isViewBasedSourceList || ![self itemHasBadge:item])
|
|
|
|
|
return NSNotFound;
|
|
|
|
|
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:badgeValueForItem:)]) {
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self badgeValueForItem:item];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NSNotFound;
|
|
|
|
|
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:badgeValueForItem:)]) {
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self badgeValueForItem:item];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NSNotFound;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
@@ -259,53 +259,53 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
|
|
|
|
|
- (void)selectRowIndexes:(NSIndexSet*)indexes byExtendingSelection:(BOOL)extend
|
|
|
|
|
{
|
|
|
|
|
NSUInteger numberOfIndexes = [indexes count];
|
|
|
|
|
|
|
|
|
|
//Prevent empty selection if we don't want it
|
|
|
|
|
if(![self allowsEmptySelection]&&0==numberOfIndexes) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Would use blocks but we're also targeting 10.5...
|
|
|
|
|
//Get the selected indexes
|
|
|
|
|
NSUInteger *selectedIndexes = malloc(sizeof(NSUInteger)*numberOfIndexes);
|
|
|
|
|
[indexes getIndexes:selectedIndexes maxCount:numberOfIndexes inIndexRange:nil];
|
|
|
|
|
|
|
|
|
|
//Loop through the indexes and only add non-group row indexes
|
|
|
|
|
//Allows selection across groups without selecting the group rows
|
|
|
|
|
NSMutableIndexSet *newSelectionIndexes = [NSMutableIndexSet indexSet];
|
|
|
|
|
for(NSInteger i=0;i<numberOfIndexes;i++)
|
|
|
|
|
{
|
|
|
|
|
if(![self isGroupItem:[self itemAtRow:selectedIndexes[i]]]) {
|
|
|
|
|
[newSelectionIndexes addIndex:selectedIndexes[i]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If there are any non-group rows selected
|
|
|
|
|
if([newSelectionIndexes count]>0) {
|
|
|
|
|
[super selectRowIndexes:newSelectionIndexes byExtendingSelection:extend];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//C memory management... *sigh*
|
|
|
|
|
free(selectedIndexes);
|
|
|
|
|
NSUInteger numberOfIndexes = [indexes count];
|
|
|
|
|
|
|
|
|
|
//Prevent empty selection if we don't want it
|
|
|
|
|
if(![self allowsEmptySelection]&&0==numberOfIndexes) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Would use blocks but we're also targeting 10.5...
|
|
|
|
|
//Get the selected indexes
|
|
|
|
|
NSUInteger *selectedIndexes = malloc(sizeof(NSUInteger)*numberOfIndexes);
|
|
|
|
|
[indexes getIndexes:selectedIndexes maxCount:numberOfIndexes inIndexRange:nil];
|
|
|
|
|
|
|
|
|
|
//Loop through the indexes and only add non-group row indexes
|
|
|
|
|
//Allows selection across groups without selecting the group rows
|
|
|
|
|
NSMutableIndexSet *newSelectionIndexes = [NSMutableIndexSet indexSet];
|
|
|
|
|
for(NSInteger i=0;i<numberOfIndexes;i++)
|
|
|
|
|
{
|
|
|
|
|
if(![self isGroupItem:[self itemAtRow:selectedIndexes[i]]]) {
|
|
|
|
|
[newSelectionIndexes addIndex:selectedIndexes[i]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If there are any non-group rows selected
|
|
|
|
|
if([newSelectionIndexes count]>0) {
|
|
|
|
|
[super selectRowIndexes:newSelectionIndexes byExtendingSelection:extend];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//C memory management... *sigh*
|
|
|
|
|
free(selectedIndexes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
#pragma mark Layout
|
|
|
|
|
|
|
|
|
|
- (NSRect)frameOfOutlineCellAtRow:(NSInteger)row
|
|
|
|
|
{
|
|
|
|
|
//Return a zero-rect if the item is always expanded (a disclosure triangle will not be drawn)
|
|
|
|
|
if([self isGroupAlwaysExpanded:[self itemAtRow:row]]) {
|
|
|
|
|
return NSZeroRect;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
//Return a zero-rect if the item is always expanded (a disclosure triangle will not be drawn)
|
|
|
|
|
if([self isGroupAlwaysExpanded:[self itemAtRow:row]]) {
|
|
|
|
|
return NSZeroRect;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSRect frame = [super frameOfOutlineCellAtRow:row];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if([self levelForRow:row] > 0) {
|
|
|
|
|
frame.origin.x = [self levelForRow:row] * [self indentationPerLevel];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return frame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -315,51 +315,51 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
if (self.isViewBasedSourceList)
|
|
|
|
|
return [super frameOfCellAtColumn:column row:row];
|
|
|
|
|
|
|
|
|
|
id item = [self itemAtRow:row];
|
|
|
|
|
|
|
|
|
|
NSCell *cell = [self preparedCellAtColumn:column row:row];
|
|
|
|
|
NSSize cellSize = [cell cellSize];
|
|
|
|
|
if (!([cell type] == NSImageCellType) && !([cell type] == NSTextCellType))
|
|
|
|
|
cellSize = [cell cellSizeForBounds:[super frameOfCellAtColumn:column row:row]];
|
|
|
|
|
NSRect cellFrame = [super frameOfCellAtColumn:column row:row];
|
|
|
|
|
|
|
|
|
|
NSRect rowRect = [self rectOfRow:row];
|
|
|
|
|
|
|
|
|
|
if([self isGroupItem:item])
|
|
|
|
|
{
|
|
|
|
|
CGFloat minX = NSMinX(cellFrame);
|
|
|
|
|
|
|
|
|
|
//Set the origin x-coord; if there are no children of the group at current, there will still be a
|
|
|
|
|
//margin to the left of the cell (in cellFrame), which we don't want
|
|
|
|
|
if([self isGroupAlwaysExpanded:[self itemAtRow:row]]) {
|
|
|
|
|
minX = 7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NSMakeRect(minX,
|
|
|
|
|
NSMidY(cellFrame)-(cellSize.height/2.0),
|
|
|
|
|
NSWidth(rowRect)-minX,
|
|
|
|
|
cellSize.height);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CGFloat leftIndent = [self levelForRow:row]*[self indentationPerLevel]+disclosureTriangleSpace;
|
|
|
|
|
|
|
|
|
|
//Calculate space left for a badge if need be
|
|
|
|
|
CGFloat rightIndent = [self sizeOfBadgeAtRow:row].width+rowRightMargin;
|
|
|
|
|
|
|
|
|
|
//Allow space for an icon if need be
|
|
|
|
|
if(![self isGroupItem:item] && [self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:itemHasIcon:)])
|
|
|
|
|
{
|
|
|
|
|
if([self.delegateDataSourceProxy sourceList:self itemHasIcon:item]) {
|
|
|
|
|
leftIndent += [self iconSize].width+(iconSpacing*2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NSMakeRect(leftIndent,
|
|
|
|
|
NSMidY(rowRect)-(cellSize.height/2.0),
|
|
|
|
|
NSWidth(rowRect)-rightIndent-leftIndent,
|
|
|
|
|
cellSize.height);
|
|
|
|
|
}
|
|
|
|
|
id item = [self itemAtRow:row];
|
|
|
|
|
|
|
|
|
|
NSCell *cell = [self preparedCellAtColumn:column row:row];
|
|
|
|
|
NSSize cellSize = [cell cellSize];
|
|
|
|
|
if (!([cell type] == NSImageCellType) && !([cell type] == NSTextCellType))
|
|
|
|
|
cellSize = [cell cellSizeForBounds:[super frameOfCellAtColumn:column row:row]];
|
|
|
|
|
NSRect cellFrame = [super frameOfCellAtColumn:column row:row];
|
|
|
|
|
|
|
|
|
|
NSRect rowRect = [self rectOfRow:row];
|
|
|
|
|
|
|
|
|
|
if([self isGroupItem:item])
|
|
|
|
|
{
|
|
|
|
|
CGFloat minX = NSMinX(cellFrame);
|
|
|
|
|
|
|
|
|
|
//Set the origin x-coord; if there are no children of the group at current, there will still be a
|
|
|
|
|
//margin to the left of the cell (in cellFrame), which we don't want
|
|
|
|
|
if([self isGroupAlwaysExpanded:[self itemAtRow:row]]) {
|
|
|
|
|
minX = 7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NSMakeRect(minX,
|
|
|
|
|
NSMidY(cellFrame)-(cellSize.height/2.0),
|
|
|
|
|
NSWidth(rowRect)-minX,
|
|
|
|
|
cellSize.height);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CGFloat leftIndent = [self levelForRow:row]*[self indentationPerLevel]+disclosureTriangleSpace;
|
|
|
|
|
|
|
|
|
|
//Calculate space left for a badge if need be
|
|
|
|
|
CGFloat rightIndent = [self sizeOfBadgeAtRow:row].width+rowRightMargin;
|
|
|
|
|
|
|
|
|
|
//Allow space for an icon if need be
|
|
|
|
|
if(![self isGroupItem:item] && [self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:itemHasIcon:)])
|
|
|
|
|
{
|
|
|
|
|
if([self.delegateDataSourceProxy sourceList:self itemHasIcon:item]) {
|
|
|
|
|
leftIndent += [self iconSize].width+(iconSpacing*2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NSMakeRect(leftIndent,
|
|
|
|
|
NSMidY(rowRect)-(cellSize.height/2.0),
|
|
|
|
|
NSWidth(rowRect)-rightIndent-leftIndent,
|
|
|
|
|
cellSize.height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -367,14 +367,14 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
//row for the row index passed to the method does not have a badge, then NSZeroSize is returned.
|
|
|
|
|
- (NSSize)sizeOfBadgeAtRow:(NSInteger)rowIndex
|
|
|
|
|
{
|
|
|
|
|
id rowItem = [self itemAtRow:rowIndex];
|
|
|
|
|
id rowItem = [self itemAtRow:rowIndex];
|
|
|
|
|
|
|
|
|
|
if (![self itemHasBadge:rowItem])
|
|
|
|
|
return NSZeroSize;
|
|
|
|
|
if (![self itemHasBadge:rowItem])
|
|
|
|
|
return NSZeroSize;
|
|
|
|
|
|
|
|
|
|
self.reusableBadgeCell.integerValue = [self badgeValueForItem:rowItem];
|
|
|
|
|
|
|
|
|
|
return NSMakeSize(fmax(self.reusableBadgeCell.cellSize.width, minBadgeWidth), badgeHeight);
|
|
|
|
|
return NSMakeSize(fmax(self.reusableBadgeCell.cellSize.width, minBadgeWidth), badgeHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)viewDidMoveToSuperview
|
|
|
|
@@ -387,68 +387,78 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
#pragma mark Drawing
|
|
|
|
|
|
|
|
|
|
- (void)drawRow:(NSInteger)rowIndex clipRect:(NSRect)clipRect
|
|
|
|
|
{
|
|
|
|
|
[super drawRow:rowIndex clipRect:clipRect];
|
|
|
|
|
{
|
|
|
|
|
[super drawRow:rowIndex clipRect:clipRect];
|
|
|
|
|
|
|
|
|
|
// We only do drawing here if the Source List is cell-based.
|
|
|
|
|
if (self.isViewBasedSourceList)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
id item = [self itemAtRow:rowIndex];
|
|
|
|
|
|
|
|
|
|
//Draw an icon if the item has one
|
|
|
|
|
if(![self isGroupItem:item] && [self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:itemHasIcon:)])
|
|
|
|
|
{
|
|
|
|
|
if([self.delegateDataSourceProxy sourceList:self itemHasIcon:item])
|
|
|
|
|
{
|
|
|
|
|
NSRect cellFrame = [self frameOfCellAtColumn:0 row:rowIndex];
|
|
|
|
|
NSSize iconSize = [self iconSize];
|
|
|
|
|
NSRect iconRect = NSMakeRect(NSMinX(cellFrame)-iconSize.width-iconSpacing,
|
|
|
|
|
NSMidY(cellFrame)-(iconSize.height/2.0f),
|
|
|
|
|
iconSize.width,
|
|
|
|
|
iconSize.height);
|
|
|
|
|
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:iconForItem:)])
|
|
|
|
|
{
|
|
|
|
|
NSImage *icon = [self.delegateDataSourceProxy sourceList:self iconForItem:item];
|
|
|
|
|
|
|
|
|
|
if(icon!=nil)
|
|
|
|
|
{
|
|
|
|
|
NSSize actualIconSize = [icon size];
|
|
|
|
|
|
|
|
|
|
//If the icon is *smaller* than the size retrieved from the -iconSize property, make sure we
|
|
|
|
|
//reduce the size of the rectangle to draw the icon in, so that it is not stretched.
|
|
|
|
|
if((actualIconSize.width<iconSize.width)||(actualIconSize.height<iconSize.height))
|
|
|
|
|
{
|
|
|
|
|
iconRect = NSMakeRect(NSMidX(iconRect)-(actualIconSize.width/2.0f),
|
|
|
|
|
NSMidY(iconRect)-(actualIconSize.height/2.0f),
|
|
|
|
|
actualIconSize.width,
|
|
|
|
|
actualIconSize.height);
|
|
|
|
|
|
|
|
|
|
id item = [self itemAtRow:rowIndex];
|
|
|
|
|
|
|
|
|
|
//Draw an icon if the item has one
|
|
|
|
|
if(![self isGroupItem:item] && [self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:itemHasIcon:)])
|
|
|
|
|
{
|
|
|
|
|
if([self.delegateDataSourceProxy sourceList:self itemHasIcon:item])
|
|
|
|
|
{
|
|
|
|
|
NSRect cellFrame = [self frameOfCellAtColumn:0 row:rowIndex];
|
|
|
|
|
NSSize iconSize = [self iconSize];
|
|
|
|
|
NSRect iconRect = NSMakeRect(NSMinX(cellFrame)-iconSize.width-iconSpacing,
|
|
|
|
|
NSMidY(cellFrame)-(iconSize.height/2.0f),
|
|
|
|
|
iconSize.width,
|
|
|
|
|
iconSize.height);
|
|
|
|
|
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:iconForItem:)])
|
|
|
|
|
{
|
|
|
|
|
NSImage *icon = [self.delegateDataSourceProxy sourceList:self iconForItem:item];
|
|
|
|
|
|
|
|
|
|
if(icon!=nil)
|
|
|
|
|
{
|
|
|
|
|
NSSize actualIconSize = [icon size];
|
|
|
|
|
|
|
|
|
|
//If the icon is *smaller* than the size retrieved from the -iconSize property, make sure we
|
|
|
|
|
//reduce the size of the rectangle to draw the icon in, so that it is not stretched.
|
|
|
|
|
if((actualIconSize.width<iconSize.width)||(actualIconSize.height<iconSize.height))
|
|
|
|
|
{
|
|
|
|
|
iconRect = NSMakeRect(NSMidX(iconRect)-(actualIconSize.width/2.0f),
|
|
|
|
|
NSMidY(iconRect)-(actualIconSize.height/2.0f),
|
|
|
|
|
actualIconSize.width,
|
|
|
|
|
actualIconSize.height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Use 10.6 NSImage drawing if we can
|
|
|
|
|
if([icon respondsToSelector:@selector(drawInRect:fromRect:operation:fraction:respectFlipped:hints:)]) {
|
|
|
|
|
[icon drawInRect:iconRect
|
|
|
|
|
fromRect:NSZeroRect
|
|
|
|
|
operation:NSCompositeSourceOver
|
|
|
|
|
fraction:1
|
|
|
|
|
respectFlipped:YES hints:nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[icon drawInRect:iconRect
|
|
|
|
|
fromRect:NSZeroRect
|
|
|
|
|
operation:NSCompositeSourceOver
|
|
|
|
|
fraction:1
|
|
|
|
|
respectFlipped:YES hints:nil];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Draw the badge if the item has one
|
|
|
|
|
if([self itemHasBadge:item])
|
|
|
|
|
{
|
|
|
|
|
NSRect rowRect = [self rectOfRow:rowIndex];
|
|
|
|
|
NSSize badgeSize = [self sizeOfBadgeAtRow:rowIndex];
|
|
|
|
|
|
|
|
|
|
NSRect badgeFrame = NSMakeRect(NSMaxX(rowRect)-badgeSize.width-rowRightMargin,
|
|
|
|
|
NSMidY(rowRect)-(badgeSize.height/2.0),
|
|
|
|
|
badgeSize.width,
|
|
|
|
|
badgeSize.height);
|
|
|
|
|
|
|
|
|
|
[self drawBadgeForRow:rowIndex inRect:badgeFrame];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
[icon setFlipped:[self isFlipped]];
|
|
|
|
|
[icon drawInRect:iconRect
|
|
|
|
|
fromRect:NSZeroRect
|
|
|
|
|
operation:NSCompositeSourceOver
|
|
|
|
|
fraction:1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Draw the badge if the item has one
|
|
|
|
|
if([self itemHasBadge:item])
|
|
|
|
|
{
|
|
|
|
|
NSRect rowRect = [self rectOfRow:rowIndex];
|
|
|
|
|
NSSize badgeSize = [self sizeOfBadgeAtRow:rowIndex];
|
|
|
|
|
|
|
|
|
|
NSRect badgeFrame = NSMakeRect(NSMaxX(rowRect)-badgeSize.width-rowRightMargin,
|
|
|
|
|
NSMidY(rowRect)-(badgeSize.height/2.0),
|
|
|
|
|
badgeSize.width,
|
|
|
|
|
badgeSize.height);
|
|
|
|
|
|
|
|
|
|
[self drawBadgeForRow:rowIndex inRect:badgeFrame];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame
|
|
|
|
@@ -470,53 +480,53 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
|
|
|
|
|
- (void)keyDown:(NSEvent *)theEvent
|
|
|
|
|
{
|
|
|
|
|
NSIndexSet *selectedIndexes = [self selectedRowIndexes];
|
|
|
|
|
|
|
|
|
|
NSString *keyCharacters = [theEvent characters];
|
|
|
|
|
|
|
|
|
|
//Make sure we have a selection
|
|
|
|
|
if([selectedIndexes count]>0)
|
|
|
|
|
{
|
|
|
|
|
if([keyCharacters length]>0)
|
|
|
|
|
{
|
|
|
|
|
unichar firstKey = [keyCharacters characterAtIndex:0];
|
|
|
|
|
|
|
|
|
|
if(firstKey==NSUpArrowFunctionKey||firstKey==NSDownArrowFunctionKey)
|
|
|
|
|
{
|
|
|
|
|
//Handle keyboard navigation across groups
|
|
|
|
|
if([selectedIndexes count]==1&&!([theEvent modifierFlags] & NSShiftKeyMask))
|
|
|
|
|
{
|
|
|
|
|
int delta = firstKey==NSDownArrowFunctionKey?1:-1; //Search "backwards" if up arrow, "forwards" if down
|
|
|
|
|
NSInteger newRow = [selectedIndexes firstIndex];
|
|
|
|
|
|
|
|
|
|
//Keep incrementing/decrementing the row until a non-header row is reached
|
|
|
|
|
do {
|
|
|
|
|
newRow+=delta;
|
|
|
|
|
|
|
|
|
|
//If out of bounds of the number of rows..
|
|
|
|
|
if(newRow<0||newRow==[self numberOfRows])
|
|
|
|
|
break;
|
|
|
|
|
} while([self isGroupItem:[self itemAtRow:newRow]]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(firstKey==NSDeleteCharacter||firstKey==NSBackspaceCharacter||firstKey==0xf728)
|
|
|
|
|
{
|
|
|
|
|
//Post the notification
|
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:PXSLDeleteKeyPressedOnRowsNotification
|
|
|
|
|
object:self
|
|
|
|
|
userInfo:[NSDictionary dictionaryWithObject:selectedIndexes forKey:@"rows"]];
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//We don't care about it
|
|
|
|
|
[super keyDown:theEvent];
|
|
|
|
|
NSIndexSet *selectedIndexes = [self selectedRowIndexes];
|
|
|
|
|
|
|
|
|
|
NSString *keyCharacters = [theEvent characters];
|
|
|
|
|
|
|
|
|
|
//Make sure we have a selection
|
|
|
|
|
if([selectedIndexes count]>0)
|
|
|
|
|
{
|
|
|
|
|
if([keyCharacters length]>0)
|
|
|
|
|
{
|
|
|
|
|
unichar firstKey = [keyCharacters characterAtIndex:0];
|
|
|
|
|
|
|
|
|
|
if(firstKey==NSUpArrowFunctionKey||firstKey==NSDownArrowFunctionKey)
|
|
|
|
|
{
|
|
|
|
|
//Handle keyboard navigation across groups
|
|
|
|
|
if([selectedIndexes count]==1&&!([theEvent modifierFlags] & NSShiftKeyMask))
|
|
|
|
|
{
|
|
|
|
|
int delta = firstKey==NSDownArrowFunctionKey?1:-1; //Search "backwards" if up arrow, "forwards" if down
|
|
|
|
|
NSInteger newRow = [selectedIndexes firstIndex];
|
|
|
|
|
|
|
|
|
|
//Keep incrementing/decrementing the row until a non-header row is reached
|
|
|
|
|
do {
|
|
|
|
|
newRow+=delta;
|
|
|
|
|
|
|
|
|
|
//If out of bounds of the number of rows..
|
|
|
|
|
if(newRow<0||newRow==[self numberOfRows])
|
|
|
|
|
break;
|
|
|
|
|
} while([self isGroupItem:[self itemAtRow:newRow]]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(firstKey==NSDeleteCharacter||firstKey==NSBackspaceCharacter||firstKey==0xf728)
|
|
|
|
|
{
|
|
|
|
|
//Post the notification
|
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:PXSLDeleteKeyPressedOnRowsNotification
|
|
|
|
|
object:self
|
|
|
|
|
userInfo:[NSDictionary dictionaryWithObject:selectedIndexes forKey:@"rows"]];
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//We don't care about it
|
|
|
|
|
[super keyDown:theEvent];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
@@ -525,53 +535,53 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
|
|
|
|
|
|
|
|
|
|
- (NSMenu *)menuForEvent:(NSEvent *)theEvent
|
|
|
|
|
{
|
|
|
|
|
NSMenu * m = nil;
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:menuForEvent:item:)]) {
|
|
|
|
|
NSPoint clickPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
|
|
|
NSInteger row = [self rowAtPoint:clickPoint];
|
|
|
|
|
id clickedItem = [self itemAtRow:row];
|
|
|
|
|
m = [self.delegateDataSourceProxy sourceList:self menuForEvent:theEvent item:clickedItem];
|
|
|
|
|
}
|
|
|
|
|
if (m == nil) {
|
|
|
|
|
m = [super menuForEvent:theEvent];
|
|
|
|
|
}
|
|
|
|
|
return m;
|
|
|
|
|
NSMenu * m = nil;
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:menuForEvent:item:)]) {
|
|
|
|
|
NSPoint clickPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
|
|
|
|
NSInteger row = [self rowAtPoint:clickPoint];
|
|
|
|
|
id clickedItem = [self itemAtRow:row];
|
|
|
|
|
m = [self.delegateDataSourceProxy sourceList:self menuForEvent:theEvent item:clickedItem];
|
|
|
|
|
}
|
|
|
|
|
if (m == nil) {
|
|
|
|
|
m = [super menuForEvent:theEvent];
|
|
|
|
|
}
|
|
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Custom NSOutlineView Delegate Method Implementations
|
|
|
|
|
|
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item
|
|
|
|
|
{
|
|
|
|
|
// Make sure the item isn't displayed as always expanded
|
|
|
|
|
if([self isGroupItem:item] && [self isGroupAlwaysExpanded:item])
|
|
|
|
|
// Make sure the item isn't displayed as always expanded
|
|
|
|
|
if([self isGroupItem:item] && [self isGroupAlwaysExpanded:item])
|
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:shouldCollapseItem:)])
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self shouldCollapseItem:item];
|
|
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
|
|
|
|
|
|
if([self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:shouldCollapseItem:)])
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self shouldCollapseItem:item];
|
|
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
|
|
|
|
|
{
|
|
|
|
|
// Make sure that the item isn't a group as they can't be selected
|
|
|
|
|
if(![self isGroupItem:item] && [self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:shouldSelectItem:)])
|
|
|
|
|
// Make sure that the item isn't a group as they can't be selected
|
|
|
|
|
if(![self isGroupItem:item] && [self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:shouldSelectItem:)])
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self shouldSelectItem:item];
|
|
|
|
|
|
|
|
|
|
return ![self isGroupItem:item];
|
|
|
|
|
return ![self isGroupItem:item];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
|
|
|
|
{
|
|
|
|
|
if(![self isGroupItem:item] && [self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:shouldEditItem:)])
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self shouldEditItem:item];
|
|
|
|
|
|
|
|
|
|
return ![self isGroupItem:item];
|
|
|
|
|
if(![self isGroupItem:item] && [self.delegateDataSourceProxy respondsToSelector:@selector(sourceList:shouldEditItem:)])
|
|
|
|
|
return [self.delegateDataSourceProxy sourceList:self shouldEditItem:item];
|
|
|
|
|
|
|
|
|
|
return ![self isGroupItem:item];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
|
|
|
|
|
{
|
|
|
|
|
return [self isGroupItem:item];
|
|
|
|
|
return [self isGroupItem:item];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|