18 Commits

Author SHA1 Message Date
Alex Rozanski e0071d0aed Bump project to 2.0.7 2015-05-08 11:19:40 +01:00
Alex Rozanski bc5727fba6 PXSourceList.m: fix whitespace woes.
- Convert all tabs to spaces.
- Remove trailing whitespace.
2015-05-08 11:16:28 +01:00
Alex Rozanski b902e04b9c Fix -setFlipped: deprecation warning.
This removes old, pre-10.6 drawing code (including call to -setFlipped:) as we
no longer support 10.6.
2015-05-08 11:13:08 +01:00
Alex Rozanski 8f7081f4d5 Bump project to 2.0.6 2015-05-08 11:01:58 +01:00
Alex Rozanski 2bf9e6a6f1 Merge pull request #49 from dusek/accessibility-badge-viewbased
Fix PXSourceListBadgeCell accessibility
2015-05-08 10:56:40 +01:00
Boris Dušek ab0408c8fe Fix PXSourceListBadgeCell accessibility
Now in the view-based source list, instead of just "Photos", VoiceOver
reads "Photos, 264"
2015-03-28 12:45:02 +01:00
Alex Rozanski 929b1651af Add badges to README. 2014-09-25 09:42:48 -04:00
Alex Rozanski 2eb6414e6b Bump project to 2.0.5 2014-06-07 14:45:37 -04:00
Alex Rozanski 2550dce3b7 Fix #43: sourceListDeleteKeyPressedOnRows: called twice.
This issue was being caused by the fact that the overridden
-setDelegate: method on PXSourceListDelegateDataSourceProxy doesn't
correctly remove the old delegate as observing the PXSourceList
notifications.

Setting the same delegate twice will cause this issue to
occur.
2014-06-07 14:39:44 -04:00
Alex Rozanski c62b8b2c92 Bump project to 2.0.4. 2014-05-11 13:17:39 -04:00
Alex Rozanski 67772622cf Remove unused badgeMargin constant. 2014-05-11 13:12:42 -04:00
Alex Rozanski a555035042 Merge pull request #41 from CrazyCatcher/master
fix a Zeroing Weak References problem
2014-05-11 12:59:55 -04:00
crazycatcher 0129de341e fix a Zeroing Weak References problem 2014-05-06 20:12:51 +08:00
Alex Rozanski 7a82f63e9b Add Release Notes for 2.0.3. 2014-03-25 21:41:47 +00:00
Alex Rozanski b215e263ef Bump project to 2.0.3. 2014-03-25 21:41:41 +00:00
Alex Rozanski 5ce29c98c9 Fix issue in view-based source list example.
This fixes an issue where items created with the add button couldn't be
dragged.
2014-03-25 21:37:06 +00:00
Alex Rozanski 7df260c6f7 Fix #40: Editing titles on cell based source list causes exception.
The cause of this bug was returning YES in
-[PXSourceListDelegateDataSourceProxy respondsToSelector:] for the
NSControl delegate methods -controlTextDidEndEditing:,
-controlTextDidBeginEditing: and -controlTextDidChange: when they were
called on the proxy because NSOutlineView implements them internally.
However we weren't returning a method signature for them in -methodSignatureForSelector:
which was throwing an exception.

This fix has two components:
- We only allow forwarding of NSOutlineView(Delegate|DataSource) methods
  to the source list PXSourceListDelegateDataSourceProxy (which
  was the original intention). If PXSourceList returns YES for
  -respondsToSelector: we ignore it if the method is not from one of
  these two protocols.
- The NSControl delegate methods have been added to the fast-path
  forwarding delegate methods array in
  PXSourceListDelegateDataSourceProxy (the array which contains method
  names which can be forwarded to the source list's delegate as-is, without
  modifying the selector or arguments).

These fix the underlying cause of the exception and implement
the missing behaviour of allowing invocation of these NSControl methods
on the source list's delegate.
2014-03-25 21:29:31 +00:00
Alex Rozanski 8407bc98bb Remove unnecessary computation in -[PXSourceListDelegateDataSourceProxy methodSignatureForSelector:].
Fast-path delegate and data source methods (those whose method
signature doesn't need modification) don't need to be checked in
-methodSignatureForSelector: because they are handled in
-forwardingTargetForSelector: which bypasses the
-methodSignatureForSelector:/forwardInvocation: path.
2014-03-25 21:08:20 +00:00
11 changed files with 391 additions and 351 deletions
+6 -2
View File
@@ -116,7 +116,10 @@ static NSString * const draggingType = @"SourceListExampleDraggingType";
NSImage *albumImage = [NSImage imageNamed:@"album"];
[albumImage setTemplate:YES];
PXSourceListItem *newItem = [PXSourceListItem itemWithTitle:@"New Album" identifier:nil icon:albumImage];
PhotoCollection *collection = [PhotoCollection collectionWithTitle:@"New Album" identifier:nil type:PhotoCollectionTypeUserCreated];
[self.modelObjects addObject:collection];
PXSourceListItem *newItem = [PXSourceListItem itemWithRepresentedObject:collection icon:albumImage];
[self.albumsItem addChildItem:newItem];
NSUInteger childIndex = [[self.albumsItem children] indexOfObject:newItem];
@@ -132,11 +135,12 @@ static NSString * const draggingType = @"SourceListExampleDraggingType";
PXSourceListItem *selectedItem = [self.sourceList itemAtRow:self.sourceList.selectedRow];
PXSourceListItem *parentItem = self.albumsItem;
[self.sourceList removeItemsAtIndexes:[NSIndexSet indexSetWithIndex:[parentItem.children indexOfObject:selectedItem]]
inParent:parentItem
withAnimation:NSTableViewAnimationSlideUp];
[self.modelObjects removeObject:selectedItem.representedObject];
// Only 'album' items can be deleted.
[parentItem removeChildItem:selectedItem];
}
+3 -3
View File
@@ -17,13 +17,13 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0.2</string>
<string>2.0.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2.0.2</string>
<string>2.0.7</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2009-14 Alex Rozanski and other contributors. All rights reserved.</string>
<string>Copyright © 2009-15 Alex Rozanski and other contributors. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
+2 -2
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'PXSourceList'
s.version = '2.0.2'
s.version = '2.0.7'
s.author = { 'Alex Rozanski' => 'alex@rozanski.me' }
s.license = 'BSD'
s.homepage = 'https://github.com/Perspx/PXSourceList'
@@ -23,6 +23,6 @@ Pod::Spec.new do |s|
s.osx.deployment_target = '10.7'
s.public_header_files = 'PXSourceList/*.h'
s.source = { :git => 'https://github.com/Perspx/PXSourceList.git', :tag => '2.0.2' }
s.source = { :git => 'https://github.com/Perspx/PXSourceList.git', :tag => '2.0.7' }
s.source_files = 'PXSourceList/**/*.{h,m}'
end
@@ -13,8 +13,8 @@
@interface PXSourceListDelegateDataSourceProxy : NSProxy <NSOutlineViewDelegate, NSOutlineViewDataSource, PXSourceListDelegate, PXSourceListDataSource>
@property (weak, nonatomic) PXSourceList *sourceList;
@property (weak, nonatomic) id <PXSourceListDelegate> delegate;
@property (weak, nonatomic) id <PXSourceListDataSource> dataSource;
@property (unsafe_unretained, nonatomic) id <PXSourceListDelegate> delegate;
@property (unsafe_unretained, nonatomic) id <PXSourceListDataSource> dataSource;
- (id)initWithSourceList:(PXSourceList *)sourceList;
@@ -36,7 +36,7 @@ static NSArray * __fastPathForwardingDataSourceMethods = nil;
{
__outlineViewDelegateMethods = px_methodNamesForProtocol(@protocol(NSOutlineViewDelegate));
__outlineViewDataSourceMethods = px_methodNamesForProtocol(@protocol(NSOutlineViewDataSource));
__fastPathForwardingDelegateMethods = px_methodNamesForProtocol(@protocol(PXSourceListDelegate));
__fastPathForwardingDelegateMethods = [self fastPathForwardingDelegateMethods];
__fastPathForwardingDataSourceMethods = px_methodNamesForProtocol(@protocol(PXSourceListDataSource));
__requiredOutlineViewDataSourceMethods = @[NSStringFromSelector(@selector(outlineView:numberOfChildrenOfItem:)),
@@ -70,7 +70,7 @@ static NSArray * __fastPathForwardingDataSourceMethods = nil;
- (void)setDelegate:(id<PXSourceListDelegate>)delegate
{
if (self.delegate)
[[NSNotificationCenter defaultCenter] removeObserver:self.delegate name:nil object:self];
[[NSNotificationCenter defaultCenter] removeObserver:self.delegate name:nil object:self.sourceList];
_delegate = delegate;
@@ -100,11 +100,12 @@ static NSArray * __fastPathForwardingDataSourceMethods = nil;
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ([self.sourceList respondsToSelector:aSelector])
return YES;
NSString *methodName = NSStringFromSelector(aSelector);
// Only let the source list override NSOutlineView delegate and data source methods.
if ([self.sourceList respondsToSelector:aSelector] && ([__outlineViewDataSourceMethods containsObject:methodName] || [__outlineViewDelegateMethods containsObject:methodName]))
return YES;
if ([__requiredOutlineViewDataSourceMethods containsObject:methodName])
return YES;
@@ -127,17 +128,14 @@ static NSArray * __fastPathForwardingDataSourceMethods = nil;
return class_conformsToProtocol(object_getClass(self), protocol);
}
// Fast-path delegate and data source methods aren't handled here; they are taken care of in -forwardingTargetForSelector:.
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *methodName = NSStringFromSelector(aSelector);
struct objc_method_description description = {NULL, NULL};
if ([__fastPathForwardingDelegateMethods containsObject:methodName])
description = px_methodDescriptionForProtocolMethod(@protocol(PXSourceListDelegate), aSelector);
else if ([__fastPathForwardingDataSourceMethods containsObject:methodName])
description = px_methodDescriptionForProtocolMethod(@protocol(PXSourceListDataSource), aSelector);
else if ([__outlineViewDelegateMethods containsObject:methodName])
if ([__outlineViewDelegateMethods containsObject:methodName])
description = px_methodDescriptionForProtocolMethod(@protocol(NSOutlineViewDelegate), aSelector);
else if ([__outlineViewDataSourceMethods containsObject:methodName])
description = px_methodDescriptionForProtocolMethod(@protocol(NSOutlineViewDataSource), aSelector);
@@ -397,6 +395,18 @@ static NSArray * __fastPathForwardingDataSourceMethods = nil;
return YES;
}
+ (NSArray *)fastPathForwardingDelegateMethods
{
NSMutableArray *methods = [px_methodNamesForProtocol(@protocol(PXSourceListDelegate)) mutableCopy];
// Add the NSControl delegate methods manually (unfortunately these aren't part of a formal protocol).
[methods addObject:px_methodNameForSelector(@selector(controlTextDidEndEditing:))];
[methods addObject:px_methodNameForSelector(@selector(controlTextDidBeginEditing:))];
[methods addObject:px_methodNameForSelector(@selector(controlTextDidChange:))];
return methods;
}
#pragma mark - Notifications
- (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector
@@ -16,5 +16,6 @@ extern NSString * const px_protocolIsRequiredMethodKey;
NSArray *px_allProtocolMethods(Protocol *protocol);
NSArray *px_methodNamesForProtocol(Protocol *protocol);
id px_methodNameForSelector(SEL selector);
struct objc_method_description px_methodDescriptionForProtocolMethod(Protocol *protocol, SEL selector);
@@ -26,7 +26,7 @@ NSArray *px_allProtocolMethods(Protocol *protocol)
for (unsigned int j = 0; j < numberOfMethodDescriptions; ++j) {
struct objc_method_description methodDescription = methodDescriptions[j];
[methodList addObject:@{px_protocolMethodNameKey: NSStringFromSelector(methodDescription.name),
[methodList addObject:@{px_protocolMethodNameKey: px_methodNameForSelector(methodDescription.name),
px_protocolMethodArgumentTypesKey: [NSString stringWithUTF8String:methodDescription.types],
px_protocolIsRequiredMethodKey: @(isRequiredMethod)}];
}
@@ -47,6 +47,11 @@ NSArray *px_methodNamesForProtocol(Protocol *protocol)
return methodNames;
}
id px_methodNameForSelector(SEL selector)
{
return NSStringFromSelector(selector);
}
struct objc_method_description px_methodDescriptionForProtocolMethod(Protocol *protocol, SEL selector)
{
struct objc_method_description description = {NULL, NULL};
+320 -331
View File
@@ -15,7 +15,6 @@
//Layout constants
static const CGFloat minBadgeWidth = 22.0; // The minimum badge width for each item (default 22.0).
static const CGFloat badgeHeight = 14.0; // The badge height for each item (default 14.0).
static const CGFloat badgeMargin = 5.0; // The spacing between the badge and the cell for that row.
static const CGFloat rowRightMargin = 5.0; // The spacing between the right edge of the badge and the edge of the table column.
static const CGFloat iconSpacing = 2.0; // The spacing between the icon and it's adjacent cell.
static const CGFloat disclosureTriangleSpace = 18.0; // The indentation reserved for disclosure triangles for non-group items.
@@ -45,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
@@ -58,7 +57,7 @@ NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKe
if((self = [super initWithFrame:frameRect])) {
[self PXSL_setup];
}
return self;
}
@@ -70,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];
}
@@ -81,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)
@@ -91,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
@@ -142,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;
}
@@ -235,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
@@ -247,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 -
@@ -260,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;
}
@@ -316,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);
}
}
@@ -368,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
@@ -388,78 +387,68 @@ 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);
}
//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];
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);
}
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];
}
[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];
}
}
- (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame
@@ -481,53 +470,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 -
@@ -536,53 +525,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
+8
View File
@@ -111,4 +111,12 @@ static const CGFloat badgeLeftAndRightPadding = 5.0;
attributes:@{NSFontAttributeName: badgeFont()}];
}
- (id)accessibilityAttributeValue:(NSString *)attribute
{
if ([attribute isEqualToString:NSAccessibilityValueAttribute])
return @(_badgeValue).description;
else
return [super accessibilityAttributeValue:attribute];
}
@end
+5
View File
@@ -1,5 +1,10 @@
#PXSourceList
[![Pod Version](http://img.shields.io/cocoapods/v/PXSourceList.svg)](http://cocoadocs.org/docsets/PXSourceList/2.0.5/)
[![Platform](http://img.shields.io/cocoapods/p/PXSourceList.svg)](http://cocoadocs.org/docsets/PXSourceList/2.0.5/)
[![Licence](http://img.shields.io/cocoapods/l/PXSourceList.svg)](https://github.com/Perspx/PXSourceList/blob/master/LICENSE)
`PXSourceList` is an `NSOutlineView` subclass used for easily implementing source lists in your applications.
PXSourceList requires the OS X 10.7 SDK and above and is licensed under the New BSD License.
+18
View File
@@ -1,5 +1,23 @@
# PXSourceList Release Notes
## 2.0.7
- Remove -setFlipped: call which was causing a deprecation warning on OS X 10.10.
- Fix whitespace in PXSourceList.m.
## 2.0.6
- Merge PR #49: Fix PXSourceListBadgeCell accessibility. Adds accessibility for PXSourceListBadgeCell when using PXSourceList in view-based mode.
## 2.0.5
- Fix #43: sourceListDeleteKeyPressedOnRows: called twice. This was caused by an issue where PXSourceList was incorrectly removing the old delegate as an observer of PXSourceList notifications in -setDelegate:.
## 2.0.4
- PR #41: fix a Zeroing Weak References problem. This fixes an issue where using an `NSWindow`, `NSWindowController` or `NSViewController` as a PXSourceList delegate or dataSource would cause problems on 10.7 because prior to 10.8, these classes could not be referenced by zeroing weak references.
- Remove unused `badgeMargin` constant from PXSourceList.m.
## 2.0.3
- Fix #40: Editing titles on cell based source list causes exception.
- Fix issue in view-based source list example where items created with the add button couldn't be dragged.
## 2.0.2
- Fix #39: Badges not drawn correctly when Source List row is selected.