Commit 17769a39 authored by Felix Paul Kühne's avatar Felix Paul Kühne

macosx: prepare sidebar

removed remnants of the Dudiak interface, added PXSourceList by Alex Rozanski (BSD-new licensed) which will be used for the new implementation, added a to-be-replaced png for the window's topbar
parent 666390fa
......@@ -191,6 +191,7 @@
CC426FD71020D47100A32659 /* Sparkle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = CC426FD01020D44F00A32659 /* Sparkle.framework */; };
CC5560281365CE4800D54038 /* AudioEffects.nib in Resources */ = {isa = PBXBuildFile; fileRef = CC5560261365CE4800D54038 /* AudioEffects.nib */; };
CC707EC8137474A50003010A /* SyncTracks.nib in Resources */ = {isa = PBXBuildFile; fileRef = CC707EC6137474A50003010A /* SyncTracks.nib */; };
CC78DA4413DE056700E9603C /* topbar_background.png in Resources */ = {isa = PBXBuildFile; fileRef = CC78DA4313DE056700E9603C /* topbar_background.png */; };
CC8062641021F8790021EB9A /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CC8062631021F8790021EB9A /* dsa_pub.pem */; };
CC84FB2D130083BB00816D38 /* BGHUDAppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC84FB2C130083BB00816D38 /* BGHUDAppKit.framework */; };
CC84FB481300843200816D38 /* BGHUDAppKit.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = CC84FB2C130083BB00816D38 /* BGHUDAppKit.framework */; };
......@@ -401,6 +402,10 @@
CC0433CC13B28C8C00D7D52E /* spref_cone_Interface_64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = spref_cone_Interface_64.png; path = Resources/prefs/spref_cone_Interface_64.png; sourceTree = "<group>"; };
CC0433CD13B28C8C00D7D52E /* spref_cone_Subtitles_64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = spref_cone_Subtitles_64.png; path = Resources/prefs/spref_cone_Subtitles_64.png; sourceTree = "<group>"; };
CC0433CE13B28C8C00D7D52E /* spref_cone_Video_64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = spref_cone_Video_64.png; path = Resources/prefs/spref_cone_Video_64.png; sourceTree = "<group>"; };
CC0CD0DF13DE0EAE00B0D90D /* PXSourceList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PXSourceList.h; path = ../../../modules/gui/macosx/PXSourceList.h; sourceTree = "<group>"; };
CC0CD0E013DE0EAE00B0D90D /* PXSourceList.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = PXSourceList.m; path = ../../../modules/gui/macosx/PXSourceList.m; sourceTree = "<group>"; };
CC0CD0E113DE0EAE00B0D90D /* PXSourceListDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PXSourceListDataSource.h; path = ../../../modules/gui/macosx/PXSourceListDataSource.h; sourceTree = "<group>"; };
CC0CD0E213DE0EAE00B0D90D /* PXSourceListDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PXSourceListDelegate.h; path = ../../../modules/gui/macosx/PXSourceListDelegate.h; sourceTree = "<group>"; };
CC0FB34B0F8BED1100F057F7 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = Resources/English.lproj/CoreDialogs.nib; sourceTree = "<group>"; };
CC1941240B9C1F8400635F6B /* QTKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QTKit.framework; path = /System/Library/Frameworks/QTKit.framework; sourceTree = "<absolute>"; };
CC1C41D00D9BAD7F002728FA /* noart.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = noart.png; path = Resources/noart.png; sourceTree = "<group>"; };
......@@ -432,10 +437,6 @@
CC5560231365CDC700D54038 /* AudioEffects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AudioEffects.h; path = ../../../modules/gui/macosx/AudioEffects.h; sourceTree = "<group>"; };
CC5560241365CDC800D54038 /* AudioEffects.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = AudioEffects.m; path = ../../../modules/gui/macosx/AudioEffects.m; sourceTree = "<group>"; };
CC5560271365CE4800D54038 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = Resources/English.lproj/AudioEffects.nib; sourceTree = "<group>"; };
CC62B9080FC5DB9D0077BB8C /* sidebarview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sidebarview.h; path = ../../../modules/gui/macosx/sidebarview.h; sourceTree = SOURCE_ROOT; };
CC62B9090FC5DB9D0077BB8C /* sidebarview.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = sidebarview.m; path = ../../../modules/gui/macosx/sidebarview.m; sourceTree = SOURCE_ROOT; };
CC62B90A0FC5DB9D0077BB8C /* sidestatusview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sidestatusview.h; path = ../../../modules/gui/macosx/sidestatusview.h; sourceTree = SOURCE_ROOT; };
CC62B90B0FC5DB9D0077BB8C /* sidestatusview.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = sidestatusview.m; path = ../../../modules/gui/macosx/sidestatusview.m; sourceTree = SOURCE_ROOT; };
CC6C01A90DDF3E9200C7D754 /* intf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = intf.h; path = ../../../modules/gui/minimal_macosx/intf.h; sourceTree = SOURCE_ROOT; };
CC6C01AA0DDF3E9200C7D754 /* intf.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = intf.m; path = ../../../modules/gui/minimal_macosx/intf.m; sourceTree = SOURCE_ROOT; };
CC6C01AB0DDF3E9200C7D754 /* macosx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = macosx.c; path = ../../../modules/gui/minimal_macosx/macosx.c; sourceTree = SOURCE_ROOT; };
......@@ -458,6 +459,7 @@
CC707EC7137474A50003010A /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = Resources/English.lproj/SyncTracks.nib; sourceTree = "<group>"; };
CC772DAC10E621C100675C9B /* VLCProgressPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLCProgressPanel.h; path = ../../../modules/gui/macosx_dialog_provider/VLCProgressPanel.h; sourceTree = SOURCE_ROOT; };
CC772DAD10E621C100675C9B /* VLCProgressPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VLCProgressPanel.m; path = ../../../modules/gui/macosx_dialog_provider/VLCProgressPanel.m; sourceTree = SOURCE_ROOT; };
CC78DA4313DE056700E9603C /* topbar_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = topbar_background.png; path = Resources/mainwindow/topbar_background.png; sourceTree = "<group>"; };
CC8062631021F8790021EB9A /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = Resources/dsa_pub.pem; sourceTree = "<group>"; };
CC84FB2C130083BB00816D38 /* BGHUDAppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BGHUDAppKit.framework; path = ../../contrib/BGHUDAppKit/BGHUDAppKit.framework; sourceTree = SOURCE_ROOT; };
CC962E2C0CC7992800A56695 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = /System/Library/Frameworks/WebKit.framework; sourceTree = "<absolute>"; };
......@@ -601,8 +603,6 @@
DC769AB7085DF0DB001A838D /* wizard.m */,
DCE7BD0708A5724D007B10AE /* bookmarks.h */,
DCE7BD0608A5724D007B10AE /* bookmarks.m */,
CC6D8D9C0A878DED006F2BBE /* AppleRemote.h */,
CC6D8D9D0A878DED006F2BBE /* AppleRemote.m */,
CCC593780AB4A9FB0004FF52 /* embeddedwindow.h */,
CCC593790AB4A9FB0004FF52 /* embeddedwindow.m */,
2AEF857609A5FEC900130822 /* fspanel.h */,
......@@ -611,20 +611,13 @@
CC6EDD500B9CA2140096068A /* eyetv.m */,
CCB24D5E0D54BBAE004D780C /* simple_prefs.h */,
CCB24D5D0D54BBAE004D780C /* simple_prefs.m */,
CC62B9080FC5DB9D0077BB8C /* sidebarview.h */,
CC62B9090FC5DB9D0077BB8C /* sidebarview.m */,
CC62B90A0FC5DB9D0077BB8C /* sidestatusview.h */,
CC62B90B0FC5DB9D0077BB8C /* sidestatusview.m */,
CCF0777913659A8000AF19FD /* SPInvocationGrabbing.h */,
CCF0777A13659A8000AF19FD /* SPInvocationGrabbing.m */,
CCF0777B13659A8000AF19FD /* SPMediaKeyTap.h */,
CCF0777C13659A8000AF19FD /* SPMediaKeyTap.m */,
CC5560231365CDC700D54038 /* AudioEffects.h */,
CC5560241365CDC800D54038 /* AudioEffects.m */,
CC707EC3137464CD0003010A /* TrackSynchronization.h */,
CC707EC4137464CD0003010A /* TrackSynchronization.m */,
CCCE366D13817E4500694F2A /* VideoEffects.h */,
CCCE366E13817E4500694F2A /* VideoEffects.m */,
CC0CD0E513DE0FC600B0D90D /* Imported Code */,
);
name = Classes;
sourceTree = "<group>";
......@@ -692,6 +685,7 @@
CC0430EE13B2461A00D7D52E /* 10th anniversary ui */ = {
isa = PBXGroup;
children = (
CC78DA4313DE056700E9603C /* topbar_background.png */,
CC0432C213B2479E00D7D52E /* bright variant */,
CC04326513B246E600D7D52E /* dark variant */,
);
......@@ -819,6 +813,28 @@
name = "simple prefs (taken from qt4)";
sourceTree = "<group>";
};
CC0CD0E413DE0EB500B0D90D /* PXSourceList */ = {
isa = PBXGroup;
children = (
CC0CD0DF13DE0EAE00B0D90D /* PXSourceList.h */,
CC0CD0E013DE0EAE00B0D90D /* PXSourceList.m */,
CC0CD0E113DE0EAE00B0D90D /* PXSourceListDataSource.h */,
CC0CD0E213DE0EAE00B0D90D /* PXSourceListDelegate.h */,
);
name = PXSourceList;
sourceTree = "<group>";
};
CC0CD0E513DE0FC600B0D90D /* Imported Code */ = {
isa = PBXGroup;
children = (
CC6D8D9C0A878DED006F2BBE /* AppleRemote.h */,
CC6D8D9D0A878DED006F2BBE /* AppleRemote.m */,
CC0CD0E413DE0EB500B0D90D /* PXSourceList */,
CC78DA4A13DE0E0100E9603C /* SPMediaKeyTap */,
);
name = "Imported Code";
sourceTree = "<group>";
};
CC33C26C0D257958008C4683 /* embedded (to be removed) */ = {
isa = PBXGroup;
children = (
......@@ -859,6 +875,17 @@
name = "Minimal Mac OS X Classes";
sourceTree = "<group>";
};
CC78DA4A13DE0E0100E9603C /* SPMediaKeyTap */ = {
isa = PBXGroup;
children = (
CCF0777913659A8000AF19FD /* SPInvocationGrabbing.h */,
CCF0777A13659A8000AF19FD /* SPInvocationGrabbing.m */,
CCF0777B13659A8000AF19FD /* SPMediaKeyTap.h */,
CCF0777C13659A8000AF19FD /* SPMediaKeyTap.m */,
);
name = SPMediaKeyTap;
sourceTree = "<group>";
};
CCC8957F0D9A8A61005AE59C /* OSX-specific source files */ = {
isa = PBXGroup;
children = (
......@@ -1174,6 +1201,7 @@
CC0433D413B28C8C00D7D52E /* spref_cone_Video_64.png in Resources */,
CC9B43AF13B29C79000205AE /* bottom-background.png in Resources */,
CC9B43B113B29FCF000205AE /* bottom-background_dark.png in Resources */,
CC78DA4413DE056700E9603C /* topbar_background.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......
......@@ -52,6 +52,8 @@
// TODO Playlist table, additional ui stuff at the top of the window
IBOutlet id o_playlist_table;
IBOutlet id o_video_view;
IBOutlet id o_split_view;
IBOutlet id o_sidebar_view;
BOOL b_dark_interface;
BOOL b_video_playback_enabled;
......
......@@ -69,7 +69,7 @@ static VLCMainWindow *_o_sharedInstance = nil;
if( [[NSWindow class] instancesRespondToSelector:@selector(setContentBorderThickness:forEdge:)] )
{
b_useTextured = NO;
styleMask ^= NSTexturedBackgroundWindowMask;
// styleMask ^= NSTexturedBackgroundWindowMask;
}
self = [super initWithContentRect:contentRect styleMask:styleMask //& ~NSTitledWindowMask
......@@ -208,7 +208,7 @@ static VLCMainWindow *_o_sharedInstance = nil;
[o_play_btn setImage: o_play_img];
[o_play_btn setAlternateImage: o_play_pressed_img];
[o_video_view setFrame: [o_playlist_table frame]];
[o_video_view setFrame: [o_split_view frame]];
[self setDelegate: self];
[self setExcludedFromWindowsMenu: YES];
// Set that here as IB seems to be buggy
......@@ -219,6 +219,21 @@ static VLCMainWindow *_o_sharedInstance = nil;
[self updateVolumeSlider];
}
- (void)becomeMainWindow
{
[o_sidebar_view setBackgroundColor: [NSColor colorWithCalibratedRed:0.820
green:0.843
blue:0.886
alpha:1.0]];
[super becomeMainWindow];
}
- (void)resignMainWindow
{
[o_sidebar_view setBackgroundColor: [NSColor colorWithCalibratedWhite:0.91 alpha:1.0]];
[super resignMainWindow];
}
#pragma mark -
#pragma mark Button Actions
......
......@@ -55,10 +55,10 @@ SOURCES_macosx = \
bookmarks.m \
embeddedwindow.h \
embeddedwindow.m \
sidebarview.h \
sidebarview.m \
sidestatusview.h \
sidestatusview.m \
PXSourceList.h \
PXSourceList.m \
PXSourceListDataSource.h \
PXSourceListDelegate.h \
fspanel.m \
fspanel.h \
eyetv.h \
......
//
// 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"
#ifndef MAC_OS_X_VERSION_10_6
@protocol NSOutlineViewDelegate <NSObject> @end
@protocol NSOutlineViewDataSource <NSObject> @end
#endif
@interface PXSourceList: NSOutlineView <NSOutlineViewDelegate, NSOutlineViewDataSource>
{
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
}
@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
//
// PXSourceList.m
// PXSourceList
//
// Created by Alex Rozanski on 05/09/2009.
// Copyright 2009-10 Alex Rozanski http://perspx.com
//
// GC-enabled code revised by Stefan Vogt http://byteproject.net
//
#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]
//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";
#pragma mark -
@interface PXSourceList ()
- (NSSize)sizeOfBadgeAtRow:(NSInteger)rowIndex;
- (void)drawBadgeForRow:(NSInteger)rowIndex inRect:(NSRect)badgeFrame;
- (void)registerDelegateToReceiveNotification:(NSString*)notification withSelector:(SEL)selector;
@end
#pragma mark -
@implementation PXSourceList
@synthesize iconSize = _iconSize;
@dynamic dataSource;
@dynamic delegate;
#pragma mark Init/Dealloc/Finalize
- (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];
//Unregister the delegate from receiving notifications
[[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
[super dealloc];
}
- (void)finalize
{
//Remove ourselves as the delegate and data source to be safe
[super setDataSource:nil];
[super setDelegate:nil];
//Unregister the delegate from receiving notifications
[[NSNotificationCenter defaultCenter] removeObserver:_secondaryDelegate name:nil object:self];
[super finalize];
}
#pragma 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(sourceListItemDidCollapse:)];
[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;
}
}
#pragma 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];
}
}
}
//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([_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;
}
#pragma 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);
}
#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 [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];
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]+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(rowRect)-(cellSize.height/2.0),
NSWidth(rowRect)-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);
}
#pragma mark -
#pragma mark Drawing
- (void)drawRow:(NSInteger)rowIndex clipRect:(NSRect)clipRect
{
[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.0f),
iconSize.width,
iconSize.height);
if([_secondaryDataSource respondsToSelector:@selector(sourceList:iconForItem:)])
{
NSImage *icon = [_secondaryDataSource 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];
}
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];