Initial commit

This commit is contained in:
Alex Rozanski
2010-01-09 19:01:39 +00:00
commit 2823daeb4f
25 changed files with 9042 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
//
// AppDelegate.h
// PXSourceList
//
// Created by Alex Rozanski on 08/01/2010.
// Copyright 2010 Alex Rozanski http://perspx.com
//
#import <Cocoa/Cocoa.h>
#import "PXSourceList.h"
@interface AppDelegate : NSObject <NSApplicationDelegate, PXSourceListDataSource, PXSourceListDelegate> {
IBOutlet PXSourceList *sourceList;
IBOutlet NSTextField *selectedItemLabel;
NSMutableArray *sourceListItems;
}
@end
+181
View File
@@ -0,0 +1,181 @@
//
// AppDelegate.m
// PXSourceList
//
// Created by Alex Rozanski on 08/01/2010.
// Copyright 2010 Alex Rozanski http://perspx.com
//
#import "AppDelegate.h"
#import "SourceListItem.h"
@implementation AppDelegate
//MARK: -
#pragma mark Init/Dealloc
- (void)awakeFromNib
{
[selectedItemLabel setStringValue:@"(none)"];
sourceListItems = [[NSMutableArray alloc] init];
//Set up the "Library" parent item and children
SourceListItem *libraryItem = [SourceListItem itemWithTitle:@"LIBRARY" identifier:@"library"];
SourceListItem *musicItem = [SourceListItem itemWithTitle:@"Music" identifier:@"music"];
[musicItem setIcon:[NSImage imageNamed:@"music.png"]];
SourceListItem *moviesItem = [SourceListItem itemWithTitle:@"Movies" identifier:@"movies"];
[moviesItem setIcon:[NSImage imageNamed:@"movies.png"]];
SourceListItem *podcastsItem = [SourceListItem itemWithTitle:@"Podcasts" identifier:@"podcasts"];
[podcastsItem setIcon:[NSImage imageNamed:@"podcasts.png"]];
[podcastsItem setBadgeValue:10];
SourceListItem *audiobooksItem = [SourceListItem itemWithTitle:@"Audiobooks" identifier:@"audiobooks"];
[audiobooksItem setIcon:[NSImage imageNamed:@"audiobooks.png"]];
[libraryItem setChildren:[NSArray arrayWithObjects:musicItem, moviesItem, podcastsItem,
audiobooksItem, nil]];
//Set up the "Playlists" parent item and children
SourceListItem *playlistsItem = [SourceListItem itemWithTitle:@"PLAYLISTS" identifier:@"playlists"];
SourceListItem *playlist1Item = [SourceListItem itemWithTitle:@"Playlist1" identifier:@"playlist1"];
//Create a second-level group to demonstrate
SourceListItem *playlist2Item = [SourceListItem itemWithTitle:@"Playlist2" identifier:@"playlist2"];
SourceListItem *playlist3Item = [SourceListItem itemWithTitle:@"Playlist3" identifier:@"playlist3"];
[playlist1Item setIcon:[NSImage imageNamed:@"playlist.png"]];
[playlist2Item setIcon:[NSImage imageNamed:@"playlist.png"]];
[playlist3Item setIcon:[NSImage imageNamed:@"playlist.png"]];
SourceListItem *playlistGroup = [SourceListItem itemWithTitle:@"Playlist Group" identifier:@"playlistgroup"];
SourceListItem *playlistGroupItem = [SourceListItem itemWithTitle:@"Child Playlist" identifier:@"childplaylist"];
[playlistGroup setIcon:[NSImage imageNamed:@"playlistFolder.png"]];
[playlistGroupItem setIcon:[NSImage imageNamed:@"playlist.png"]];
[playlistGroup setChildren:[NSArray arrayWithObject:playlistGroupItem]];
[playlistsItem setChildren:[NSArray arrayWithObjects:playlist1Item, playlistGroup,playlist2Item,
playlist3Item, nil]];
[sourceListItems addObject:libraryItem];
[sourceListItems addObject:playlistsItem];
[sourceList reloadData];
}
- (void)dealloc
{
[sourceListItems release];
[super dealloc];
}
//MARK: -
#pragma mark Source List Data Source Methods
- (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item
{
//Works the same way as the NSOutlineView data source: `nil` means a parent item
if(item==nil) {
return [sourceListItems count];
}
else {
return [[item children] count];
}
}
- (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item
{
//Works the same way as the NSOutlineView data source: `nil` means a parent item
if(item==nil) {
return [sourceListItems objectAtIndex:index];
}
else {
return [[item children] objectAtIndex:index];
}
}
- (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item
{
return [item title];
}
- (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item
{
[item setTitle:object];
}
- (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item
{
return [item hasChildren];
}
- (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item
{
return [item hasBadge];
}
- (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item
{
return [item badgeValue];
}
- (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item
{
return [item hasIcon];
}
- (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item
{
return [item icon];
}
//MARK: -
#pragma mark Source List Delegate Methods
- (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group
{
if([[group identifier] isEqualToString:@"library"])
return YES;
return NO;
}
- (void)sourceListSelectionDidChange:(NSNotification *)notification
{
NSIndexSet *selectedIndexes = [sourceList selectedRowIndexes];
//Set the label text to represent the new selection
if([selectedIndexes count]>1)
[selectedItemLabel setStringValue:@"(multiple)"];
else if([selectedIndexes count]==1) {
NSString *identifier = [[sourceList itemAtRow:[selectedIndexes firstIndex]] identifier];
[selectedItemLabel setStringValue:identifier];
}
else {
[selectedItemLabel setStringValue:@"(none)"];
}
}
- (void)sourceListDeleteKeyPressedOnRows:(NSNotification *)notification
{
NSIndexSet *rows = [[notification userInfo] objectForKey:@"rows"];
NSLog(@"Delete key pressed on rows %@", rows);
//Do something here
}
@end
+37
View File
@@ -0,0 +1,37 @@
//
// PXSourceList.h
// PXSourceList
//
// Created by Alex Rozanski on 05/09/2009.
// Copyright 2009-10 Alex Rozanski http://perspx.com
//
#import <Cocoa/Cocoa.h>
#import "PXSourceListDelegate.h"
#import "PXSourceListDataSource.h"
@interface PXSourceList: NSOutlineView <NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuDelegate>
{
id <PXSourceListDelegate> _secondaryDelegate; //Used to store the publicly visible delegate
id <PXSourceListDataSource> _secondaryDataSource; //Used to store the publicly visible data source
NSSize _iconSize; //The size of icons in the Source List. Defaults to 16x16
BOOL _isRowRightClicked; //Whether an item has been right-clicked
NSUInteger _rightClickedRowIndex; //The index of the item that has been right-clicked
}
@property NSSize iconSize;
@property (assign) id<PXSourceListDataSource> dataSource;
@property (assign) id<PXSourceListDelegate> delegate;
- (NSUInteger)numberOfGroups; //Returns the number of groups in the Source List
- (BOOL)isGroupItem:(id)item; //Returns whether `item` is a group
- (BOOL)isGroupAlwaysExpanded:(id)group; //Returns whether `group` is displayed as always expanded
- (BOOL)itemHasBadge:(id)item; //Returns whether `item` has a badge
- (NSInteger)badgeValueForItem:(id)item; //Returns the badge value for `item`
@end
+880
View File
@@ -0,0 +1,880 @@
//
// PXSourceList.m
// PXSourceList
//
// Created by Alex Rozanski on 05/09/2009.
// Copyright 2009-10 Alex Rozanski http://perspx.com
//
#import "PXSourceList.h"
//Layout constants
#define MIN_BADGE_WIDTH 22.0 //The minimum badge width for each item (default 22.0)
#define BADGE_HEIGHT 14.0 //The badge height for each item (default 14.0)
#define BADGE_MARGIN 5.0 //The spacing between the badge and the cell for that row
#define ROW_RIGHT_MARGIN 5.0 //The spacing between the right edge of the badge and the edge of the table column
#define ICON_SPACING 2.0 //The spacing between the icon and it's adjacent cell
#define DISCLOSURE_TRIANGLE_SPACE 18.0 //The indentation reserved for disclosure triangles for non-group items
//Drawing constants
#define BADGE_BACKGROUND_COLOR [NSColor colorWithCalibratedRed:(152/255.0) green:(168/255.0) blue:(202/255.0) alpha:1]
#define BADGE_HIDDEN_BACKGROUND_COLOR [NSColor colorWithDeviceWhite:(180/255.0) alpha:1]
#define BADGE_SELECTED_TEXT_COLOR [NSColor keyboardFocusIndicatorColor]
#define BADGE_SELECTED_UNFOCUSED_TEXT_COLOR [NSColor colorWithCalibratedRed:(153/255.0) green:(169/255.0) blue:(203/255.0) alpha:1]
#define BADGE_SELECTED_HIDDEN_TEXT_COLOR [NSColor colorWithCalibratedWhite:(170/255.0) alpha:1]
#define BADGE_FONT [NSFont boldSystemFontOfSize:11]
#define RIGHT_CLICKED_BGCOLOR [NSColor colorWithCalibratedRed:(207/255.0) green:(221/255.0) blue:(247/255.0) alpha:1]
#define RIGHT_CLICKED_OUTLINE_COLOR [NSColor colorWithCalibratedRed:(84/255.0) green:(101/255.0) blue:(226/255.0) alpha:1]
#define RIGHT_CLICKED_SELECTED_OUTLINE_COLOR [NSColor whiteColor]
//Delegate notification constants
NSString * const PXSLSelectionIsChangingNotification = @"PXSourceListSelectionIsChanging";
NSString * const PXSLSelectionDidChangeNotification = @"PXSourceListSelectionDidChange";
NSString * const PXSLItemWillExpandNotification = @"PXSourceListItemWillExpand";
NSString * const PXSLItemDidExpandNotification = @"PXSourceListItemDidExpand";
NSString * const PXSLItemWillCollapseNotification = @"PXSourceListItemWillCollapse";
NSString * const PXSLItemDidCollapseNotification = @"PXSourceListItemDidCollapse";
NSString * const PXSLDeleteKeyPressedOnRowsNotification = @"PXSourceListDeleteKeyPressedOnRows";
@interface PXSourceList ()
- (NSSize)sizeOfBadgeAtRow:(NSInteger)rowIndex;
- (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame;
- (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector;
@end
//MARK: -
@implementation PXSourceList
@synthesize iconSize = _iconSize;
@dynamic dataSource;
@dynamic delegate;
#pragma mark Init/Dealloc
- (id)initWithCoder:(NSCoder*)decoder
{
if(self=[super initWithCoder:decoder])
{
[self setDelegate:(id<PXSourceListDelegate>)[super delegate]];
[super setDelegate:self];
[self setDataSource:(id<PXSourceListDataSource>)[super dataSource]];
[super setDataSource:self];
_iconSize = NSMakeSize(16,16);
}
return self;
}
- (void)dealloc
{
//Remove ourselves as the delegate and data source to be safe
[super setDataSource:nil];
[super setDelegate:nil];
[super dealloc];
}
//MARK: -
#pragma mark Custom Accessors
- (void)setDelegate:(id<PXSourceListDelegate>)aDelegate
{
//Unregister the old delegate from receiving notifications
[[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
_secondaryDelegate = aDelegate;
//Register the new delegate to receive notifications
[self registerDelegateToReceiveNotification:PXSLSelectionIsChangingNotification
withSelector:@selector(sourceListSelectionIsChanging:)];
[self registerDelegateToReceiveNotification:PXSLSelectionDidChangeNotification
withSelector:@selector(sourceListSelectionDidChange:)];
[self registerDelegateToReceiveNotification:PXSLItemWillExpandNotification
withSelector:@selector(sourceListItemWillExpand:)];
[self registerDelegateToReceiveNotification:PXSLItemDidExpandNotification
withSelector:@selector(sourceListItemDidExpand:)];
[self registerDelegateToReceiveNotification:PXSLItemWillCollapseNotification
withSelector:@selector(sourceListItemWillCollapse:)];
[self registerDelegateToReceiveNotification:PXSLItemDidCollapseNotification
withSelector:@selector(sourceListItemWillCollapse:)];
[self registerDelegateToReceiveNotification:PXSLDeleteKeyPressedOnRowsNotification
withSelector:@selector(sourceListDeleteKeyPressedOnRows:)];
}
- (void)setDataSource:(id<PXSourceListDataSource>)aDataSource
{
_secondaryDataSource = aDataSource;
[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;
}
}
//MARK: -
#pragma mark Data Management
- (void)reloadData
{
[super reloadData];
//Expand items that are displayed as always expanded
if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)] &&
[_secondaryDelegate respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)])
{
for(NSUInteger i=0;i<[self numberOfGroups];i++)
{
id item = [_secondaryDataSource sourceList:self child:i ofItem:nil];
if([self isGroupAlwaysExpanded:item]) {
[self expandItem:item expandChildren:NO];
}
}
}
//Select the first non-group row if no rows are selected, and empty selection is disallowed
if(![self allowsEmptySelection]&&[self numberOfSelectedRows]==0)
{
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([_secondaryDataSource respondsToSelector:@selector(sourceList:numberOfChildrenOfItem:)]) {
return [_secondaryDataSource sourceList:self numberOfChildrenOfItem:nil];
}
return 0;
}
- (BOOL)isGroupItem:(id)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([_secondaryDelegate respondsToSelector:@selector(sourceList:isGroupAlwaysExpanded:)]) {
return [_secondaryDelegate sourceList:self isGroupAlwaysExpanded:group];
}
}
return NO;
}
- (BOOL)itemHasBadge:(id)item
{
if([_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasBadge:)]) {
return [_secondaryDataSource sourceList:self itemHasBadge:item];
}
return NO;
}
- (NSInteger)badgeValueForItem:(id)item
{
//Make sure that the item has a badge
if(![self itemHasBadge:item]) {
return NSNotFound;
}
if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeValueForItem:)]) {
return [_secondaryDataSource sourceList:self badgeValueForItem:item];
}
return NSNotFound;
}
//MARK: -
#pragma mark Selection Handling
- (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);
}
//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 [super frameOfOutlineCellAtRow:row];
}
- (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row
{
id item = [self itemAtRow:row];
NSCell *cell = [self preparedCellAtColumn:column row:row];
NSSize cellSize = [cell cellSize];
NSRect cellFrame = [super frameOfCellAtColumn:column row:row];
if([self isGroupItem:item])
{
return NSMakeRect(NSMinX(cellFrame),
NSMidY(cellFrame)-(cellSize.height/2.0),
NSWidth(cellFrame),
cellSize.height);
}
else
{
CGFloat leftIndent = [self levelForRow:row]*[self indentationPerLevel]+DISCLOSURE_TRIANGLE_SPACE;
//Calculate space left for a badge if need be
CGFloat rightIndent = [self sizeOfBadgeAtRow:row].width+ROW_RIGHT_MARGIN;
//Allow space for an icon if need be
if(![self isGroupItem:item]&&[_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasIcon:)])
{
if([_secondaryDataSource sourceList:self itemHasIcon:item]) {
leftIndent += [self iconSize].width+(ICON_SPACING*2);
}
}
return NSMakeRect(leftIndent,
NSMidY(cellFrame)-(cellSize.height/2.0),
NSWidth(cellFrame)-rightIndent-leftIndent,
cellSize.height);
}
}
//This method calculates and returns the size of the badge for the row index passed to the method. If the
//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];
//Make sure that the item has a badge
if(![self itemHasBadge:rowItem]) {
return NSZeroSize;
}
NSAttributedString *badgeAttrString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", [self badgeValueForItem:rowItem]]
attributes:[NSDictionary dictionaryWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName, nil]];
NSSize stringSize = [badgeAttrString size];
//Calculate the width needed to display the text or the minimum width if it's smaller
CGFloat width = stringSize.width+(2*BADGE_MARGIN);
if(width<MIN_BADGE_WIDTH) {
width = MIN_BADGE_WIDTH;
}
[badgeAttrString release];
return NSMakeSize(width, BADGE_HEIGHT);
}
//MARK: -
#pragma mark Drawing
- (void)drawRow:(NSInteger)rowIndex clipRect:(NSRect)clipRect
{
//If the row has been right-clicked...
if(_isRowRightClicked&&_rightClickedRowIndex==rowIndex)
{
NSRect rowRect = [self rectOfRow:rowIndex];
NSUInteger selectedRow = [[self selectedRowIndexes] firstIndex];
NSBezierPath *roundRectPath = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rowRect, 2,2)
xRadius:5
yRadius:5];
[roundRectPath setLineWidth:2];
if(_rightClickedRowIndex==selectedRow)
{
[RIGHT_CLICKED_SELECTED_OUTLINE_COLOR set];
[roundRectPath stroke];
}
else
{
[RIGHT_CLICKED_BGCOLOR set];
[roundRectPath fill];
[RIGHT_CLICKED_OUTLINE_COLOR set];
[roundRectPath stroke];
}
}
[super drawRow:rowIndex clipRect:clipRect];
id item = [self itemAtRow:rowIndex];
//Draw an icon if the item has one
if(![self isGroupItem:item]&&[_secondaryDataSource respondsToSelector:@selector(sourceList:itemHasIcon:)])
{
if([_secondaryDataSource sourceList:self itemHasIcon:item])
{
NSRect cellFrame = [self frameOfCellAtColumn:0 row:rowIndex];
NSSize iconSize = [self iconSize];
NSRect iconRect = NSMakeRect(NSMinX(cellFrame)-iconSize.width-ICON_SPACING,
NSMidY(cellFrame)-(iconSize.width/2.0),
iconSize.width,
iconSize.height);
if([_secondaryDataSource respondsToSelector:@selector(sourceList:iconForItem:)])
{
NSImage *icon = [_secondaryDataSource sourceList:self iconForItem:item];
if(icon!=nil)
{
[icon setFlipped:[self isFlipped]];
NSSize iconSize = [icon size];
NSRect drawRect = NSMakeRect(NSMidX(iconRect)-(iconSize.width/2.0),
NSMidY(iconRect)-(iconSize.height/2.0),
iconSize.width,
iconSize.height);
[icon drawInRect:drawRect 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-ROW_RIGHT_MARGIN,
NSMidY(rowRect)-(badgeSize.height/2.0),
badgeSize.width,
badgeSize.height);
[self drawBadgeForRow:rowIndex inRect:badgeFrame];
}
}
- (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame
{
id rowItem = [self itemAtRow:rowIndex];
NSBezierPath *badgePath = [NSBezierPath bezierPathWithRoundedRect:badgeFrame
xRadius:(BADGE_HEIGHT/2.0)
yRadius:(BADGE_HEIGHT/2.0)];
//Get window and control state to determine colours used
BOOL isVisible = [[NSApp mainWindow] isVisible];
BOOL isFocused = [[[self window] firstResponder] isEqual:self];
NSInteger rowBeingEdited = [self editedRow];
//Set the attributes based on the row state
NSDictionary *attributes;
NSColor *backgroundColor;
if([[self selectedRowIndexes] containsIndex:rowIndex])
{
backgroundColor = [NSColor whiteColor];
//Set the text color based on window and control state
NSColor *textColor;
if(isVisible && (isFocused || rowBeingEdited==rowIndex)) {
textColor = BADGE_SELECTED_TEXT_COLOR;
}
else if(isVisible && !isFocused) {
textColor = BADGE_SELECTED_UNFOCUSED_TEXT_COLOR;
}
else {
textColor = BADGE_SELECTED_HIDDEN_TEXT_COLOR;
}
attributes = [[NSDictionary alloc] initWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName,
textColor, NSForegroundColorAttributeName, nil];
}
else
{
//Set the text colour based on window and control state
NSColor *badgeColor = [NSColor whiteColor];
if(isVisible) {
//If the data source returns a custom colour..
if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeBackgroundColorForItem:)]) {
backgroundColor = [_secondaryDataSource sourceList:self badgeBackgroundColorForItem:rowItem];
if(backgroundColor==nil)
backgroundColor = BADGE_BACKGROUND_COLOR;
}
else { //Otherwise use the default (purple-blue colour)
backgroundColor = BADGE_BACKGROUND_COLOR;
}
//If the delegate wants a custom badge text colour..
if([_secondaryDataSource respondsToSelector:@selector(sourceList:badgeTextColorForItem:)]) {
badgeColor = [_secondaryDataSource sourceList:self badgeTextColorForItem:rowItem];
if(badgeColor==nil)
badgeColor = [NSColor whiteColor];
}
}
else { //Gray colour
backgroundColor = BADGE_HIDDEN_BACKGROUND_COLOR;
}
attributes = [[NSDictionary alloc] initWithObjectsAndKeys:BADGE_FONT, NSFontAttributeName,
badgeColor, NSForegroundColorAttributeName, nil];
}
[backgroundColor set];
[badgePath fill];
//Draw the badge text
NSAttributedString *badgeAttrString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", [self badgeValueForItem:rowItem]]
attributes:attributes];
NSSize stringSize = [badgeAttrString size];
NSPoint badgeTextPoint = NSMakePoint(NSMidX(badgeFrame)-(stringSize.width/2.0), //Center in the badge frame
NSMidY(badgeFrame)-(stringSize.height/2.0)); //Center in the badge frame
[badgeAttrString drawAtPoint:badgeTextPoint];
[attributes release];
[badgeAttrString release];
}
//MARK: -
#pragma mark Keyboard Handling
- (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)
{
//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 Context Menu Handling
- (void)rightMouseDown:(NSEvent*)event
{
NSInteger selectedRow = [self rowAtPoint:[self convertPoint:[event locationInWindow] fromView:nil]];
if(selectedRow!=-1&&![self isGroupItem:[self itemAtRow:selectedRow]])
{
if([_secondaryDataSource respondsToSelector:@selector(sourceList:contextMenuForItem:)])
{
NSMenu *contextMenu = [_secondaryDataSource sourceList:self contextMenuForItem:[self itemAtRow:selectedRow]];
if(contextMenu!=nil)
{
[contextMenu setDelegate:self];
//Row needs to be redrawn with round selection rectangle
_isRowRightClicked = YES;
_rightClickedRowIndex = selectedRow;
//Let's be economical
[self setNeedsDisplayInRect:[self rectOfRow:selectedRow]];
[NSMenu popUpContextMenu:contextMenu withEvent:event forView:self];
}
}
}
}
- (void)menuDidClose:(NSMenu *)menu
{
_isRowRightClicked = NO;
//Row needs to be redrawn to clear round selection rectangle
[self setNeedsDisplayInRect:[self rectOfRow:_rightClickedRowIndex]];
}
//MARK: -
#pragma mark NSOutlineView Data Source methods
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
return [_secondaryDataSource sourceList:self numberOfChildrenOfItem:item];
}
return 0;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
return [_secondaryDataSource sourceList:self child:index ofItem:item];
}
return nil;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
return [_secondaryDataSource sourceList:self isItemExpandable:item];
}
return NO;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
return [_secondaryDataSource sourceList:self objectValueForItem:item];
}
return nil;
}
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
if([_secondaryDataSource conformsToProtocol:@protocol(PXSourceListDataSource)]) {
[_secondaryDataSource sourceList:self setObjectValue:object forItem:item];
}
}
- (id)outlineView:(NSOutlineView *)outlineView itemForPersistentObject:(id)object
{
if([_secondaryDataSource respondsToSelector:@selector(sourceList:itemForPersistentObject:)]) {
return [_secondaryDataSource sourceList:self itemForPersistentObject:object];
}
return nil;
}
- (id)outlineView:(NSOutlineView *)outlineView persistentObjectForItem:(id)item
{
if([_secondaryDataSource respondsToSelector:@selector(sourceList:persistentObjectForItem:)]) {
return [_secondaryDataSource sourceList:self persistentObjectForItem:item];
}
return nil;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pasteboard
{
if([_secondaryDataSource respondsToSelector:@selector(sourceList:writeItems:toPasteboard:)]) {
return [_secondaryDataSource sourceList:self writeItems:items toPasteboard:pasteboard];
}
return NO;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
if([_secondaryDataSource respondsToSelector:@selector(sourceList:validateDrop:proposedItem:proposedChildIndex:)]) {
return [_secondaryDataSource sourceList:self validateDrop:info proposedItem:item proposedChildIndex:index];
}
return NSDragOperationNone;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
{
if([_secondaryDataSource respondsToSelector:@selector(sourceList:acceptDrop:item:childIndex:)]) {
return [_secondaryDataSource sourceList:self acceptDrop:info item:item childIndex:index];
}
return NO;
}
- (NSArray *)outlineView:(NSOutlineView *)outlineView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedItems:(NSArray *)items
{
if([_secondaryDataSource respondsToSelector:@selector(sourceList:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:)]) {
return [_secondaryDataSource sourceList:self namesOfPromisedFilesDroppedAtDestination:dropDestination forDraggedItems:items];
}
return nil;
}
//MARK: -
#pragma mark NSOutlineView Delegate methods
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
{
if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldExpandItem:)]) {
return [_secondaryDelegate sourceList:self shouldExpandItem:item];
}
return YES;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item
{
//Make sure the item isn't displayed as always expanded
if([self isGroupItem:item])
{
if([self isGroupAlwaysExpanded:item]) {
return NO;
}
}
if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldCollapseItem:)]) {
return [_secondaryDelegate sourceList:self shouldExpandItem:item];
}
return YES;
}
- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
if([_secondaryDelegate respondsToSelector:@selector(sourceList:dataCellForItem:)]) {
return [_secondaryDelegate sourceList:self dataCellForItem:item];
}
NSInteger row = [self rowForItem:item];
//Return the default table column
return [[[self tableColumns] objectAtIndex:0] dataCellForRow:row];
}
- (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]) {
if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldSelectItem:)]) {
return [_secondaryDelegate sourceList:self shouldSelectItem:item];
}
}
else {
return NO;
}
return YES;
}
- (NSIndexSet *)outlineView:(NSOutlineView *)outlineView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes
{
//The outline view will try to select the first row if -[allowsEmptySelection:] is set to NO – if this is a group row
//stop it from doing so and leave it to our implementation of-[reloadData] which will select the first non-group row
//for us.
if([self numberOfSelectedRows]==0) {
if([self isGroupItem:[self itemAtRow:[proposedSelectionIndexes firstIndex]]]) {
return [NSIndexSet indexSet];
}
}
if([_secondaryDelegate respondsToSelector:@selector(sourceList:selectionIndexesForProposedSelection:)]) {
return [_secondaryDelegate sourceList:self selectionIndexesForProposedSelection:proposedSelectionIndexes];
}
//Since we implement this method, something must be returned to the outline view
return proposedSelectionIndexes;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
//Group titles can't be edited
if([self isGroupItem:item])
return NO;
if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldEditItem:)]) {
return [_secondaryDelegate sourceList:self shouldEditItem:item];
}
return YES;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
if([_secondaryDelegate respondsToSelector:@selector(sourceList:shouldTrackCell:forItem:)]) {
return [_secondaryDelegate sourceList:self shouldTrackCell:cell forItem:item];
}
return NO;
}
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
{
if([_secondaryDelegate respondsToSelector:@selector(sourceList:heightOfRowByItem:)]) {
return [_secondaryDelegate sourceList:self heightOfRowByItem:item];
}
return [self rowHeight];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
{
return [self isGroupItem:item];
}
//MARK: -
#pragma mark Notification handling
/* Notification wrappers */
- (void)outlineViewSelectionIsChanging:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:PXSLSelectionIsChangingNotification object:self];
}
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:PXSLSelectionDidChangeNotification object:self];
}
- (void)outlineViewItemWillExpand:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemWillExpandNotification
object:self
userInfo:[notification userInfo]];
}
- (void)outlineViewItemDidExpand:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemDidExpandNotification
object:self
userInfo:[notification userInfo]];
}
- (void)outlineViewItemWillCollapse:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemWillCollapseNotification
object:self
userInfo:[notification userInfo]];
}
- (void)outlineViewItemDidCollapse:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:PXSLItemDidCollapseNotification
object:self
userInfo:[notification userInfo]];
}
- (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector
{
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
//Set the delegate as a receiver of the notification if it implements the notification method
if([_secondaryDelegate respondsToSelector:selector]) {
[defaultCenter addObserver:_secondaryDelegate
selector:selector
name:notification
object:self];
}
}
@end
//MARK: -
+43
View File
@@ -0,0 +1,43 @@
//
// PXSourceListDataSource.h
// PXViewKit
//
// Created by Alex Rozanski on 17/10/2009.
// Copyright 2009-10 Alex Rozanski http://perspx.com
//
#import <Cocoa/Cocoa.h>
@class PXSourceList;
@protocol PXSourceListDataSource <NSObject>
@required
- (NSUInteger)sourceList:(PXSourceList*)sourceList numberOfChildrenOfItem:(id)item;
- (id)sourceList:(PXSourceList*)aSourceList child:(NSUInteger)index ofItem:(id)item;
- (id)sourceList:(PXSourceList*)aSourceList objectValueForItem:(id)item;
- (BOOL)sourceList:(PXSourceList*)aSourceList isItemExpandable:(id)item;
@optional
- (void)sourceList:(PXSourceList*)aSourceList setObjectValue:(id)object forItem:(id)item;
- (BOOL)sourceList:(PXSourceList*)aSourceList itemHasBadge:(id)item;
- (NSInteger)sourceList:(PXSourceList*)aSourceList badgeValueForItem:(id)item;
- (NSColor*)sourceList:(PXSourceList*)aSourceList badgeTextColorForItem:(id)item;
- (NSColor*)sourceList:(PXSourceList*)aSourceList badgeBackgroundColorForItem:(id)item;
- (BOOL)sourceList:(PXSourceList*)aSourceList itemHasIcon:(id)item;
- (NSImage*)sourceList:(PXSourceList*)aSourceList iconForItem:(id)item;
- (NSMenu*)sourceList:(PXSourceList*)aSourceList contextMenuForItem:(id)item;
//The rest of these methods are basically "wrappers" for the NSOutlineViewDataSource methods
- (id)sourceList:(PXSourceList*)aSourceList itemForPersistentObject:(id)object;
- (id)sourceList:(PXSourceList*)aSourceList persistentObjectForItem:(id)item;
- (BOOL)sourceList:(PXSourceList*)aSourceList writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard;
- (NSDragOperation)sourceList:(PXSourceList*)sourceList validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index;
- (BOOL)sourceList:(PXSourceList*)aSourceList acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index;
- (NSArray *)sourceList:(PXSourceList*)aSourceList namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedItems:(NSArray *)items;
@end
+61
View File
@@ -0,0 +1,61 @@
//
// PXSourceListDelegate.h
// PXViewKit
//
// Created by Alex Rozanski on 17/10/2009.
// Copyright 2009-10 Alex Rozanski http://perspx.com
//
#import <Cocoa/Cocoa.h>
@class PXSourceList;
@protocol PXSourceListDelegate <NSObject>
@optional
//Extra methods
- (BOOL)sourceList:(PXSourceList*)aSourceList isGroupAlwaysExpanded:(id)group;
//Basically NSOutlineViewDelegate wrapper methods
- (BOOL)sourceList:(PXSourceList*)aSourceList shouldSelectItem:(id)item;
- (NSIndexSet*)sourceList:(PXSourceList*)aSourceList selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes;
- (BOOL)sourceList:(PXSourceList*)aSourceList shouldEditItem:(id)item;
- (BOOL)sourceList:(PXSourceList*)aSourceList shouldTrackCell:(NSCell *)cell forItem:(id)item;
- (BOOL)sourceList:(PXSourceList*)aSourceList shouldExpandItem:(id)item;
- (BOOL)sourceList:(PXSourceList*)aSourceList shouldCollapseItem:(id)item;
- (CGFloat)sourceList:(PXSourceList*)aSourceList heightOfRowByItem:(id)item;
- (NSCell*)sourceList:(PXSourceList*)aSourceList dataCellForItem:(id)item;
@end
@interface NSObject (PXSourceListNotifications)
//Selection
- (void)sourceListSelectionIsChanging:(NSNotification *)notification;
- (void)sourceListSelectionDidChange:(NSNotification *)notification;
//Item expanding/collapsing
- (void)sourceListItemWillExpand:(NSNotification *)notification;
- (void)sourceListItemDidExpand:(NSNotification *)notification;
- (void)sourceListItemWillCollapse:(NSNotification *)notification;
- (void)sourceListItemDidCollapse:(NSNotification *)notification;
- (void)sourceListDeleteKeyPressedOnRows:(NSNotification *)notification;
@end
//PXSourceList delegate notifications
extern NSString * const PXSLSelectionIsChangingNotification;
extern NSString * const PXSLSelectionDidChangeNotification;
extern NSString * const PXSLItemWillExpandNotification;
extern NSString * const PXSLItemDidExpandNotification;
extern NSString * const PXSLItemWillCollapseNotification;
extern NSString * const PXSLItemDidCollapseNotification;
extern NSString * const PXSLDeleteKeyPressedOnRowsNotification;
+53
View File
@@ -0,0 +1,53 @@
//
// SourceListItem.h
// PXSourceList
//
// Created by Alex Rozanski on 08/01/2010.
// Copyright 2010 Alex Rozanski http://perspx.com
//
#import <Cocoa/Cocoa.h>
/*An example of a class that could be used to represent a Source List Item
Provides a title, an identifier, and an icon to be shown, as well as a badge value and a property to determine
whether the current item has a badge or not (`badgeValue` is set to -1 if no badge is shown)
Used to form a hierarchical model of SourceListItem instances – similar to the Source List tree structure
and easily accessible by the data source with the "children" property
SourceListItem *parent
- SourceListItem *child1;
- SourceListItem *child2;
- SourceListItem *childOfChild2;
- SourceListItem *anotherChildOfChild2;
- SourceListItem *child3;
*/
@interface SourceListItem : NSObject {
NSString *title;
NSString *identifier;
NSImage *icon;
NSInteger badgeValue;
NSArray *children;
}
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *identifier;
@property (nonatomic, retain) NSImage *icon;
@property NSInteger badgeValue;
@property (nonatomic, copy) NSArray *children;
//Convenience methods
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier;
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier icon:(NSImage*)anIcon;
- (BOOL)hasBadge;
- (BOOL)hasChildren;
- (BOOL)hasIcon;
@end
+84
View File
@@ -0,0 +1,84 @@
//
// SourceListItem.m
// PXSourceList
//
// Created by Alex Rozanski on 08/01/2010.
// Copyright 2010 Alex Rozanski http://perspx.com
//
#import "SourceListItem.h"
@implementation SourceListItem
@synthesize title;
@synthesize identifier;
@synthesize icon;
@synthesize badgeValue;
@synthesize children;
//MARK: -
#pragma mark Init/Dealloc
- (id)init
{
if(self=[super init])
{
badgeValue = -1; //We don't want a badge value by default
}
return self;
}
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier
{
SourceListItem *item = [SourceListItem itemWithTitle:aTitle identifier:anIdentifier icon:nil];
return item;
}
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier icon:(NSImage*)anIcon
{
SourceListItem *item = [[[SourceListItem alloc] init] autorelease];
[item setTitle:aTitle];
[item setIdentifier:anIdentifier];
[item setIcon:anIcon];
return item;
}
- (void)dealloc
{
[title release];
[identifier release];
[icon release];
[children release];
[super dealloc];
}
//MARK: -
#pragma mark Custom Accessors
- (BOOL)hasBadge
{
return badgeValue!=-1;
}
- (BOOL)hasChildren
{
return [children count]>0;
}
- (BOOL)hasIcon
{
return icon!=nil;
}
@end
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+353
View File
@@ -0,0 +1,353 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 45;
objects = {
/* Begin PBXBuildFile section */
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
D205EDF010F7A609004C6545 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D205EDEF10F7A609004C6545 /* MainMenu.xib */; };
D205EDF710F7A693004C6545 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D205EDF610F7A693004C6545 /* AppDelegate.m */; };
D2456CAA10F8F7F500B8045C /* audiobooks.png in Resources */ = {isa = PBXBuildFile; fileRef = D2456CA410F8F7F500B8045C /* audiobooks.png */; };
D2456CAB10F8F7F500B8045C /* movies.png in Resources */ = {isa = PBXBuildFile; fileRef = D2456CA510F8F7F500B8045C /* movies.png */; };
D2456CAC10F8F7F500B8045C /* music.png in Resources */ = {isa = PBXBuildFile; fileRef = D2456CA610F8F7F500B8045C /* music.png */; };
D2456CAD10F8F7F500B8045C /* playlist.png in Resources */ = {isa = PBXBuildFile; fileRef = D2456CA710F8F7F500B8045C /* playlist.png */; };
D2456CAE10F8F7F500B8045C /* playlistFolder.png in Resources */ = {isa = PBXBuildFile; fileRef = D2456CA810F8F7F500B8045C /* playlistFolder.png */; };
D2456CAF10F8F7F500B8045C /* podcasts.png in Resources */ = {isa = PBXBuildFile; fileRef = D2456CA910F8F7F500B8045C /* podcasts.png */; };
D2456D1A10F9093800B8045C /* PXSourceList.m in Sources */ = {isa = PBXBuildFile; fileRef = D2456D1910F9093800B8045C /* PXSourceList.m */; };
D265C2D710E3814B00A1E824 /* TODO.rtf in Resources */ = {isa = PBXBuildFile; fileRef = D265C2D610E3814B00A1E824 /* TODO.rtf */; };
D2EA6EC210D9205A00402758 /* PXSourceList.m in Sources */ = {isa = PBXBuildFile; fileRef = D2EA6EC110D9205A00402758 /* PXSourceList.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
089C165DFE840E0CC02AAC07 /* InfoPlist.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = InfoPlist.strings; path = Resources/English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
256AC3F00F4B6AF500CF3369 /* PXSourceList_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PXSourceList_Prefix.pch; sourceTree = "<group>"; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
8D1107310486CEB800E47090 /* PXSourceList-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "PXSourceList-Info.plist"; sourceTree = "<group>"; };
8D1107320486CEB800E47090 /* PXSourceList.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PXSourceList.app; sourceTree = BUILT_PRODUCTS_DIR; };
D205EDDD10F7A1EE004C6545 /* README.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.markdown; sourceTree = "<group>"; };
D205EDEF10F7A609004C6545 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = Resources/MainMenu.xib; sourceTree = "<group>"; };
D205EDF510F7A693004C6545 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Classes/AppDelegate.h; sourceTree = "<group>"; };
D205EDF610F7A693004C6545 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Classes/AppDelegate.m; sourceTree = "<group>"; };
D205EE7D10F7B5AB004C6545 /* SourceListItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SourceListItem.h; path = Classes/SourceListItem.h; sourceTree = "<group>"; };
D2456CA410F8F7F500B8045C /* audiobooks.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = audiobooks.png; path = Resources/audiobooks.png; sourceTree = "<group>"; };
D2456CA510F8F7F500B8045C /* movies.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = movies.png; path = Resources/movies.png; sourceTree = "<group>"; };
D2456CA610F8F7F500B8045C /* music.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = music.png; path = Resources/music.png; sourceTree = "<group>"; };
D2456CA710F8F7F500B8045C /* playlist.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = playlist.png; path = Resources/playlist.png; sourceTree = "<group>"; };
D2456CA810F8F7F500B8045C /* playlistFolder.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = playlistFolder.png; path = Resources/playlistFolder.png; sourceTree = "<group>"; };
D2456CA910F8F7F500B8045C /* podcasts.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = podcasts.png; path = Resources/podcasts.png; sourceTree = "<group>"; };
D2456D1910F9093800B8045C /* PXSourceList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PXSourceList.m; path = Classes/PXSourceList.m; sourceTree = "<group>"; };
D265C2D610E3814B00A1E824 /* TODO.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = TODO.rtf; sourceTree = "<group>"; };
D2EA6EC010D9205A00402758 /* PXSourceList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PXSourceList.h; path = Classes/PXSourceList.h; sourceTree = "<group>"; };
D2EA6EC110D9205A00402758 /* PXSourceList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PXSourceList.m; path = Classes/PXSourceList.m; sourceTree = "<group>"; };
D2EA6EFF10D925AA00402758 /* PXSourceListDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PXSourceListDataSource.h; path = Classes/PXSourceListDataSource.h; sourceTree = "<group>"; };
D2EA6F0010D925AA00402758 /* PXSourceListDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PXSourceListDelegate.h; path = Classes/PXSourceListDelegate.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8D11072E0486CEB800E47090 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
D205EDF510F7A693004C6545 /* AppDelegate.h */,
D205EDF610F7A693004C6545 /* AppDelegate.m */,
D205EE7D10F7B5AB004C6545 /* SourceListItem.h */,
D2456D1910F9093800B8045C /* PXSourceList.m */,
D2EA702910D92EB300402758 /* PXSourceList */,
);
name = Classes;
sourceTree = "<group>";
};
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
isa = PBXGroup;
children = (
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
);
name = "Linked Frameworks";
sourceTree = "<group>";
};
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
isa = PBXGroup;
children = (
29B97324FDCFA39411CA2CEA /* AppKit.framework */,
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */,
29B97325FDCFA39411CA2CEA /* Foundation.framework */,
);
name = "Other Frameworks";
sourceTree = "<group>";
};
19C28FACFE9D520D11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
8D1107320486CEB800E47090 /* PXSourceList.app */,
);
name = Products;
sourceTree = "<group>";
};
29B97314FDCFA39411CA2CEA /* PXSourceList */ = {
isa = PBXGroup;
children = (
D265C2D610E3814B00A1E824 /* TODO.rtf */,
D205EDDD10F7A1EE004C6545 /* README.markdown */,
080E96DDFE201D6D7F000001 /* Classes */,
29B97315FDCFA39411CA2CEA /* Other Sources */,
29B97317FDCFA39411CA2CEA /* Resources */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
);
name = PXSourceList;
sourceTree = "<group>";
};
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
isa = PBXGroup;
children = (
256AC3F00F4B6AF500CF3369 /* PXSourceList_Prefix.pch */,
29B97316FDCFA39411CA2CEA /* main.m */,
);
name = "Other Sources";
sourceTree = "<group>";
};
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
children = (
D27B7D2710E0C925004D7DBC /* Icons */,
8D1107310486CEB800E47090 /* PXSourceList-Info.plist */,
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
D205EDEF10F7A609004C6545 /* MainMenu.xib */,
);
name = Resources;
sourceTree = "<group>";
};
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
);
name = Frameworks;
sourceTree = "<group>";
};
D27B7D2710E0C925004D7DBC /* Icons */ = {
isa = PBXGroup;
children = (
D2456CA410F8F7F500B8045C /* audiobooks.png */,
D2456CA510F8F7F500B8045C /* movies.png */,
D2456CA610F8F7F500B8045C /* music.png */,
D2456CA710F8F7F500B8045C /* playlist.png */,
D2456CA810F8F7F500B8045C /* playlistFolder.png */,
D2456CA910F8F7F500B8045C /* podcasts.png */,
);
name = Icons;
sourceTree = "<group>";
};
D2EA702910D92EB300402758 /* PXSourceList */ = {
isa = PBXGroup;
children = (
D2EA6EC010D9205A00402758 /* PXSourceList.h */,
D2EA6EC110D9205A00402758 /* PXSourceList.m */,
D2EA6EFF10D925AA00402758 /* PXSourceListDataSource.h */,
D2EA6F0010D925AA00402758 /* PXSourceListDelegate.h */,
);
name = PXSourceList;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
8D1107260486CEB800E47090 /* PXSourceList */ = {
isa = PBXNativeTarget;
buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "PXSourceList" */;
buildPhases = (
8D1107290486CEB800E47090 /* Resources */,
8D11072C0486CEB800E47090 /* Sources */,
8D11072E0486CEB800E47090 /* Frameworks */,
D205F09310F7D40E004C6545 /* ShellScript */,
);
buildRules = (
);
dependencies = (
);
name = PXSourceList;
productInstallPath = "$(HOME)/Applications";
productName = PXSourceList;
productReference = 8D1107320486CEB800E47090 /* PXSourceList.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject;
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "PXSourceList" */;
compatibilityVersion = "Xcode 3.1";
hasScannedForEncodings = 1;
mainGroup = 29B97314FDCFA39411CA2CEA /* PXSourceList */;
projectDirPath = "";
projectRoot = "";
targets = (
8D1107260486CEB800E47090 /* PXSourceList */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8D1107290486CEB800E47090 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
D265C2D710E3814B00A1E824 /* TODO.rtf in Resources */,
D205EDF010F7A609004C6545 /* MainMenu.xib in Resources */,
D2456CAA10F8F7F500B8045C /* audiobooks.png in Resources */,
D2456CAB10F8F7F500B8045C /* movies.png in Resources */,
D2456CAC10F8F7F500B8045C /* music.png in Resources */,
D2456CAD10F8F7F500B8045C /* playlist.png in Resources */,
D2456CAE10F8F7F500B8045C /* playlistFolder.png in Resources */,
D2456CAF10F8F7F500B8045C /* podcasts.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
D205F09310F7D40E004C6545 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "DOXYGEN=/Users/alexrozanski/Development/Xcode/OSX/Documentation/doxygen\nDOXYFILE=$SOURCE_ROOT/Doxyfile\n\nAPPLEDOC=/Users/alexrozanski/Development/Xcode/osx/Documentation/appledoc\nSTRIPDTAGS=/Users/alexrozanski/Development/Xcode/osx/Documentation/StripDoxygenTags\n\n#THE OUTPUT DIRECTORY HAS ITS CONTENTS REMOVED ON BUILD!\n\n#$APPLEDOC -p \"$PROJECT_NAME\" -i $SOURCE_ROOT -o $SOURCE_ROOT/documentation/output -t \"/Library/Application Support/appledoc\" -c $SOURCE_ROOT/Doxyfile --date-time-template \"Last updated: %Y-%m-%d\" --xhtml\n\n#Strip the Doxygen tags\n\n#$STRIPDTAGS -i $SOURCE_ROOT -o /Users/alexrozanski/Development/Xcode/osx/Github/PXSourceList\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8D11072C0486CEB800E47090 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8D11072D0486CEB800E47090 /* main.m in Sources */,
D2EA6EC210D9205A00402758 /* PXSourceList.m in Sources */,
D205EDF710F7A693004C6545 /* AppDelegate.m in Sources */,
D2456D1A10F9093800B8045C /* PXSourceList.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
089C165DFE840E0CC02AAC07 /* InfoPlist.strings */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
C01FCF4B08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_FIX_AND_CONTINUE = YES;
GCC_MODEL_TUNING = G5;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = PXSourceList_Prefix.pch;
INFOPLIST_FILE = "PXSourceList-Info.plist";
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = PXSourceList;
};
name = Debug;
};
C01FCF4C08A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_MODEL_TUNING = G5;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = PXSourceList_Prefix.pch;
INFOPLIST_FILE = "PXSourceList-Info.plist";
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = PXSourceList;
};
name = Release;
};
C01FCF4F08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
ONLY_ACTIVE_ARCH = YES;
PREBINDING = NO;
SDKROOT = macosx10.6;
};
name = Debug;
};
C01FCF5008A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
PREBINDING = NO;
SDKROOT = macosx10.5;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "PXSourceList" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4B08A954540054247B /* Debug */,
C01FCF4C08A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "PXSourceList" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4F08A954540054247B /* Debug */,
C01FCF5008A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
}
+7
View File
@@ -0,0 +1,7 @@
//
// Prefix header for all source files of the 'PXSourceList' target in the 'PXSourceList' project
//
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif
+60
View File
@@ -0,0 +1,60 @@
#PXSourceList
A Source List control for use with the Mac OS X 10.5 SDK or above.
[Download the documentation][1]
##Intention
[Source Lists][2] are used in a lot of Mac OS X applications, but the support for such controls is quite primitive – at best you create an Outline View with Source List highlighting, but none of the features such as badging are built in by default. `PXSourceList` is a reusable control – within the context of Source Lists – which makes creating applications with Source Lists a much easier process.
##Using the code
There are only a few steps involved:
1. Download the source, and copy `PXSourceList.h`, `PXSourceList.m`, `PXSourceListDelegate.h` and `PXSourceListDataSource.h` into your Xcode project.
2. To create the control in Interface Builder, drag an `NSOutlineView` object over to a window and in the Identity Inspector for the Outline View, change the class to `PXSourceList`. In the Attributes Inspector, set it to have only 1 column, uncheck "Headers" in the "Columns" section and set "Highlight" to "Source List" – there is an NIB in the example project bundled with the source.
3. Make sure to `#import "PXSourceList.h"` for files that require it (the delegate and data source protocol files are imported in this main header), and ensure that your class(es) that are the `delegate` and/or `dataSource` for the Source List conform to the `PXSourceListDelegate` and `PXSourceListDataSource` protocols respectively.
**Note:** If you intend to use PXSourceList with the 10.5 SDK, you will need to remove some of the protocols that PXSourceList conforms to which do not exist – in `PXSourceList.h`, remove the `<NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuDelegate>` from the interface declaration.
There is also an example project bundled with the source to see how the control is used.
##How the control works
I have tried to structure PXSourceList in a way such that it fits common Cocoa design patterns and therefore makes it easier to use.
PXSourceList adapts the delegate and data source design patterns, and extends those of the `NSOutlineViewDelegate` and `NSOutlineViewDataSource`, much in the way that these extend the appropriate `NSTableView` protocols.
If you want more information have a look at the [Outline View Programming Topics for Cocoa][3] – the Source List delegate and data source implementation work in much the same way, but with methods added and removed, as detailed in the documentation.
##Documentation
Documentation is available for PXSourceList, downloadable [here][4]. Provided in the ZIP file is a folder containing HTML documentation, or a docset which can be opened in Xcode and which is then searchable from the Xcode Developer Documentation.
If you feel that any areas of the documentation are lacking or missing, please feel free to [let me know][5], which will be much appreciated.
**Note:** the documentation for the Source List notifications can be found in the `NSObject(PXSourceListNotifications)` reference, which is linked to from the documentation index page.
##Known Issues
- Calling `delegate` or `dataSource` on the Source List returns the Source List instance. This is, unfortunately, a side effect of how delegate and data source methods are handled within the Source List – I hope to work around it in the future.
- No bindings implementation (yet).
##Attribution
I was spurred along the way by many sources, but in particular [BWToolkit][6] by Brandon Walkin and Fraser Kuyvenhoven, which gave me the idea of how to handle the Source List delegate and data source methods.
Also Brian Dunagan's post on [Source List badging][7] and determining state for the various colours was a great help when I came to the drawing code for that.
The documentation was created using [Doxygen][8] and [appledoc][9], thanks of which go to the developers of both.
The icons used in the demo project bundled with the source code are from the [Fugue icon set][10] by Yusuke Kamiyamane.
[1]: http://perspx.com/wp-content/uploads/2010/01/PXSourceList_0-7_Documentation.zip
[2]: http://developer.apple.com/Mac/library/documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGWindows/XHIGWindows.html#//apple_ref/doc/uid/20000961-CHDDIGDE
[3]: http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/OutlineView/Articles/UsingOutlineDataSource.html
[4]: http://perspx.com/wp-content/uploads/2010/01/PXSourceList_0-7_Documentation.zip
[5]: http://perspx.com/contact
[6]: http://brandonwalkin.com/bwtoolkit/
[7]: http://www.bdunagan.com/2008/11/10/cocoa-tutorial-source-list-badges-part-2/
[8]: http://www.doxygen.org/
[9]: http://www.gentlebytes.com/freeware/appledoc/
[10]: http://www.pinvoke.com/
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.custom.PXSourceList.docset</string>
<key>CFBundleName</key>
<string>PXSourceList</string>
<key>DocSetFeedName</key>
<string>Custom documentation</string>
</dict>
</plist>
@@ -0,0 +1,2 @@
/* Localized versions of Info.plist keys */
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

+13
View File
@@ -0,0 +1,13 @@
{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf250
{\fonttbl\f0\fnil\fcharset0 Monaco;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
\f0\fs20 \cf0 \strike \strikec0 1. Source List badge colours for Graphite theme set in system preferences\strike0\striked0 \
2. Implement custom drawing for drag and drop\
\strike \strikec0 3. Sort out way that Source List returns badge values \'96 use NSNumber?\strike0\striked0 \
- No, return NSNotFound for -badgeValueForItem: instead if item doesn't have badge \
4. Provide a bindings interface\
5. Cross-group keyboard implementation?\
6. Provide an isGroupItem: delegate method?\
7. Change the way the Source List handles drag and drop to something more like iTunes/Finder?}
+14
View File
@@ -0,0 +1,14 @@
//
// main.m
// PXSourceList
//
// Created by Alex Rozanski on 16/12/2009.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
}