aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks/PSMTabBar/PSMTabBarControl.m
diff options
context:
space:
mode:
Diffstat (limited to 'Frameworks/PSMTabBar/PSMTabBarControl.m')
-rw-r--r--Frameworks/PSMTabBar/PSMTabBarControl.m2120
1 files changed, 2120 insertions, 0 deletions
diff --git a/Frameworks/PSMTabBar/PSMTabBarControl.m b/Frameworks/PSMTabBar/PSMTabBarControl.m
new file mode 100644
index 00000000..9242f8a1
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabBarControl.m
@@ -0,0 +1,2120 @@
+//
+// PSMTabBarControl.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 10/13/05.
+// Copyright 2005 Positive Spin Media. All rights reserved.
+//
+
+#import "PSMTabBarControl.h"
+#import "PSMTabBarCell.h"
+#import "PSMOverflowPopUpButton.h"
+#import "PSMRolloverButton.h"
+#import "PSMTabStyle.h"
+#import "PSMMetalTabStyle.h"
+#import "PSMAquaTabStyle.h"
+#import "PSMUnifiedTabStyle.h"
+#import "PSMCardTabStyle.h"
+#import "PSMAdiumTabStyle.h"
+#import "PSMTabDragAssistant.h"
+#import "PSMTabBarController.h"
+
+#include <Carbon/Carbon.h> /* for GetKeys() and KeyMap */
+#include <bitstring.h>
+
+@interface PSMTabBarControl (Private)
+
+ // constructor/destructor
+- (void)initAddedProperties;
+
+ // accessors
+- (NSEvent *)lastMouseDownEvent;
+- (void)setLastMouseDownEvent:(NSEvent *)event;
+
+ // contents
+- (void)addTabViewItem:(NSTabViewItem *)item;
+- (void)removeTabForCell:(PSMTabBarCell *)cell;
+
+ // draw
+- (void)update;
+- (void)update:(BOOL)animate;
+- (void)_setupTrackingRectsForCell:(PSMTabBarCell *)cell;
+- (void)_positionOverflowMenu;
+- (void)_checkWindowFrame;
+
+ // actions
+- (void)overflowMenuAction:(id)sender;
+- (void)closeTabClick:(id)sender;
+- (void)tabClick:(id)sender;
+- (void)tabNothing:(id)sender;
+
+ // notification handlers
+- (void)frameDidChange:(NSNotification *)notification;
+- (void)windowDidMove:(NSNotification *)aNotification;
+- (void)windowDidUpdate:(NSNotification *)notification;
+
+ // NSTabView delegate
+- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem;
+- (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem;
+- (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem;
+- (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)tabView;
+
+ // archiving
+- (void)encodeWithCoder:(NSCoder *)aCoder;
+- (id)initWithCoder:(NSCoder *)aDecoder;
+
+ // convenience
+- (void)_bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item;
+- (id)cellForPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame;
+
+- (void)_animateCells:(NSTimer *)timer;
+@end
+
+@implementation PSMTabBarControl
+
+#pragma mark -
+#pragma mark Characteristics
++ (NSBundle *)bundle;
+{
+ static NSBundle *bundle = nil;
+ if (!bundle) bundle = [NSBundle bundleForClass:[PSMTabBarControl class]];
+ return bundle;
+}
+
+/*!
+ @method availableCellWidth
+ @abstract The number of pixels available for cells
+ @discussion Calculates the number of pixels available for cells based on margins and the window resize badge.
+ @returns Returns the amount of space for cells.
+*/
+
+- (CGFloat)availableCellWidth
+{
+ return [self frame].size.width - [style leftMarginForTabBarControl] - [style rightMarginForTabBarControl] - _resizeAreaCompensation;
+}
+
+/*!
+ @method genericCellRect
+ @abstract The basic rect for a tab cell.
+ @discussion Creates a generic frame for a tab cell based on the current control state.
+ @returns Returns a basic rect for a tab cell.
+*/
+
+- (NSRect)genericCellRect
+{
+ NSRect aRect=[self frame];
+ aRect.origin.x = [style leftMarginForTabBarControl];
+ aRect.origin.y = 0.0;
+ aRect.size.width = [self availableCellWidth];
+ aRect.size.height = [style tabCellHeight];
+ return aRect;
+}
+
+#pragma mark -
+#pragma mark Constructor/destructor
+
+- (void)initAddedProperties
+{
+ _cells = [[NSMutableArray alloc] initWithCapacity:10];
+ _controller = [[PSMTabBarController alloc] initWithTabBarControl:self];
+ _animationTimer = nil;
+
+ // default config
+ _currentStep = kPSMIsNotBeingResized;
+ _orientation = PSMTabBarHorizontalOrientation;
+ _canCloseOnlyTab = NO;
+ _disableTabClose = NO;
+ _showAddTabButton = NO;
+ _hideForSingleTab = NO;
+ _sizeCellsToFit = NO;
+ _isHidden = NO;
+ _awakenedFromNib = NO;
+ _automaticallyAnimates = NO;
+ _useOverflowMenu = YES;
+ _allowsBackgroundTabClosing = YES;
+ _allowsResizing = NO;
+ _selectsTabsOnMouseDown = NO;
+ _alwaysShowActiveTab = NO;
+ _allowsScrubbing = NO;
+ _cellMinWidth = 100;
+ _cellMaxWidth = 280;
+ _cellOptimumWidth = 130;
+ _tearOffStyle = PSMTabBarTearOffAlphaWindow;
+ style = [[PSMMetalTabStyle alloc] init];
+
+ // the overflow button/menu
+ NSRect overflowButtonRect = NSMakeRect([self frame].size.width - [style rightMarginForTabBarControl] + 1, 0, [style rightMarginForTabBarControl] - 1, [self frame].size.height);
+ _overflowPopUpButton = [[PSMOverflowPopUpButton alloc] initWithFrame:overflowButtonRect pullsDown:YES];
+ [_overflowPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMinXMargin];
+ [_overflowPopUpButton setHidden:YES];
+ [self addSubview:_overflowPopUpButton];
+ [self _positionOverflowMenu];
+
+ // new tab button
+ NSRect addTabButtonRect = NSMakeRect([self frame].size.width - [style rightMarginForTabBarControl] + 1, 3.0, 16.0, 16.0);
+ _addTabButton = [[PSMRolloverButton alloc] initWithFrame:addTabButtonRect];
+ if (_addTabButton) {
+ NSImage *newButtonImage = [style addTabButtonImage];
+ if (newButtonImage) {
+ [_addTabButton setUsualImage:newButtonImage];
+ }
+ newButtonImage = [style addTabButtonPressedImage];
+ if (newButtonImage) {
+ [_addTabButton setAlternateImage:newButtonImage];
+ }
+ newButtonImage = [style addTabButtonRolloverImage];
+ if (newButtonImage) {
+ [_addTabButton setRolloverImage:newButtonImage];
+ }
+ [_addTabButton setTitle:@""];
+ [_addTabButton setImagePosition:NSImageOnly];
+ [_addTabButton setButtonType:NSMomentaryChangeButton];
+ [_addTabButton setBordered:NO];
+ [_addTabButton setBezelStyle:NSShadowlessSquareBezelStyle];
+ [self addSubview:_addTabButton];
+
+ if (_showAddTabButton) {
+ [_addTabButton setHidden:NO];
+ } else {
+ [_addTabButton setHidden:YES];
+ }
+ [_addTabButton setNeedsDisplay:YES];
+ }
+}
+
+- (id)initWithFrame:(NSRect)frame
+{
+ self = [super initWithFrame:frame];
+ if (self) {
+ // Initialization
+ [self initAddedProperties];
+ [self registerForDraggedTypes:[NSArray arrayWithObjects:@"PSMTabBarControlItemPBType", nil]];
+
+ // resize
+ [self setPostsFrameChangedNotifications:YES];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:) name:NSViewFrameDidChangeNotification object:self];
+ }
+ [self setTarget:self];
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ //stop any animations that may be running
+ [_animationTimer invalidate];
+ [_animationTimer release]; _animationTimer = nil;
+
+ [_showHideAnimationTimer invalidate];
+ [_showHideAnimationTimer release]; _showHideAnimationTimer = nil;
+
+ //Also unwind the spring, if it's wound.
+ [_springTimer invalidate];
+ [_springTimer release]; _springTimer = nil;
+
+ //unbind all the items to prevent crashing
+ //not sure if this is necessary or not
+ NSArray *cells = [NSArray arrayWithArray:_cells]; // create a copy as we will change the original array while being enumerated
+ NSEnumerator *enumerator = [cells objectEnumerator];
+ PSMTabBarCell *nextCell;
+ while ( (nextCell = [enumerator nextObject]) ) {
+ [self removeTabForCell:nextCell];
+ }
+
+ [_overflowPopUpButton release];
+ [_cells release];
+ [_controller release];
+ [tabView release];
+ [_addTabButton release];
+ [partnerView release];
+ [_lastMouseDownEvent release];
+ [style release];
+
+ [self unregisterDraggedTypes];
+
+ [super dealloc];
+}
+
+- (void)awakeFromNib
+{
+ // build cells from existing tab view items
+ NSArray *existingItems = [tabView tabViewItems];
+ NSEnumerator *e = [existingItems objectEnumerator];
+ NSTabViewItem *item;
+ while ( (item = [e nextObject]) ) {
+ if (![[self representedTabViewItems] containsObject:item]) {
+ [self addTabViewItem:item];
+ }
+ }
+}
+
+- (void)viewWillMoveToWindow:(NSWindow *)aWindow {
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+
+ [center removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil];
+ [center removeObserver:self name:NSWindowDidResignKeyNotification object:nil];
+ [center removeObserver:self name:NSWindowDidUpdateNotification object:nil];
+ [center removeObserver:self name:NSWindowDidMoveNotification object:nil];
+
+ if (_showHideAnimationTimer) {
+ [_showHideAnimationTimer invalidate];
+ [_showHideAnimationTimer release]; _showHideAnimationTimer = nil;
+ }
+
+ if (aWindow) {
+ [center addObserver:self selector:@selector(windowStatusDidChange:) name:NSWindowDidBecomeKeyNotification object:aWindow];
+ [center addObserver:self selector:@selector(windowStatusDidChange:) name:NSWindowDidResignKeyNotification object:aWindow];
+ [center addObserver:self selector:@selector(windowDidUpdate:) name:NSWindowDidUpdateNotification object:aWindow];
+ [center addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:aWindow];
+ }
+}
+
+- (void)windowStatusDidChange:(NSNotification *)notification
+{
+ [self setNeedsDisplay:YES];
+}
+
+#pragma mark -
+#pragma mark Accessors
+
+- (NSMutableArray *)cells
+{
+ return _cells;
+}
+
+- (NSEvent *)lastMouseDownEvent
+{
+ return _lastMouseDownEvent;
+}
+
+- (void)setLastMouseDownEvent:(NSEvent *)event
+{
+ [event retain];
+ [_lastMouseDownEvent release];
+ _lastMouseDownEvent = event;
+}
+
+- (id)delegate
+{
+ return delegate;
+}
+
+- (void)setDelegate:(id)object
+{
+ delegate = object;
+
+ NSMutableArray *types = [NSMutableArray arrayWithObject:@"PSMTabBarControlItemPBType"];
+
+ //Update the allowed drag types
+ if ([self delegate] && [[self delegate] respondsToSelector:@selector(allowedDraggedTypesForTabView:)]) {
+ [types addObjectsFromArray:[[self delegate] allowedDraggedTypesForTabView:tabView]];
+ }
+ [self unregisterDraggedTypes];
+ [self registerForDraggedTypes:types];
+}
+
+- (NSTabView *)tabView
+{
+ return tabView;
+}
+
+- (void)setTabView:(NSTabView *)view
+{
+ [view retain];
+ [tabView release];
+ tabView = view;
+}
+
+- (id<PSMTabStyle>)style
+{
+ return style;
+}
+
+- (NSString *)styleName
+{
+ return [style name];
+}
+
+- (void)setStyle:(id <PSMTabStyle>)newStyle
+{
+ if (style != newStyle) {
+ [style autorelease];
+ style = [newStyle retain];
+
+ // restyle add tab button
+ if (_addTabButton) {
+ NSImage *newButtonImage = [style addTabButtonImage];
+ if (newButtonImage) {
+ [_addTabButton setUsualImage:newButtonImage];
+ }
+
+ newButtonImage = [style addTabButtonPressedImage];
+ if (newButtonImage) {
+ [_addTabButton setAlternateImage:newButtonImage];
+ }
+
+ newButtonImage = [style addTabButtonRolloverImage];
+ if (newButtonImage) {
+ [_addTabButton setRolloverImage:newButtonImage];
+ }
+ }
+
+ [self update];
+ }
+}
+
+- (void)setStyleNamed:(NSString *)name
+{
+ id <PSMTabStyle> newStyle;
+/*
+ if ([name isEqualToString:@"Aqua"]) {
+ newStyle = [[PSMAquaTabStyle alloc] init];
+ } else if ([name isEqualToString:@"Unified"]) {
+ newStyle = [[PSMUnifiedTabStyle alloc] init];
+ } else if ([name isEqualToString:@"Adium"]) {
+ newStyle = [[PSMAdiumTabStyle alloc] init];
+ } else if ([name isEqualToString:@"Card"]) {
+ newStyle = [[PSMCardTabStyle alloc] init];
+ } else {
+*/
+ newStyle = [[PSMMetalTabStyle alloc] init];
+/*
+ }
+*/
+
+ [self setStyle:newStyle];
+ [newStyle release];
+}
+
+- (PSMTabBarOrientation)orientation
+{
+ return _orientation;
+}
+
+- (void)setOrientation:(PSMTabBarOrientation)value
+{
+ PSMTabBarOrientation lastOrientation = _orientation;
+ _orientation = value;
+
+ if (_tabBarWidth < 10) {
+ _tabBarWidth = 120;
+ }
+
+ if (lastOrientation != _orientation) {
+ [[self style] setOrientation:_orientation];
+
+ [self _positionOverflowMenu]; //move the overflow popup button to the right place
+ [self update:NO];
+ }
+}
+
+- (BOOL)canCloseOnlyTab
+{
+ return _canCloseOnlyTab;
+}
+
+- (void)setCanCloseOnlyTab:(BOOL)value
+{
+ _canCloseOnlyTab = value;
+ if ([_cells count] == 1) {
+ [self update];
+ }
+}
+
+- (BOOL)disableTabClose
+{
+ return _disableTabClose;
+}
+
+- (void)setDisableTabClose:(BOOL)value
+{
+ _disableTabClose = value;
+ [self update];
+}
+
+- (BOOL)hideForSingleTab
+{
+ return _hideForSingleTab;
+}
+
+- (void)setHideForSingleTab:(BOOL)value
+{
+ _hideForSingleTab = value;
+ [self update];
+}
+
+- (BOOL)showAddTabButton
+{
+ return _showAddTabButton;
+}
+
+- (void)setShowAddTabButton:(BOOL)value
+{
+ _showAddTabButton = value;
+ if (!NSIsEmptyRect([_controller addButtonRect]))
+ [_addTabButton setFrame:[_controller addButtonRect]];
+
+ [_addTabButton setHidden:!_showAddTabButton];
+ [_addTabButton setNeedsDisplay:YES];
+
+ [self update];
+}
+
+- (NSInteger)cellMinWidth
+{
+ return _cellMinWidth;
+}
+
+- (void)setCellMinWidth:(NSInteger)value
+{
+ _cellMinWidth = value;
+ [self update];
+}
+
+- (NSInteger)cellMaxWidth
+{
+ return _cellMaxWidth;
+}
+
+- (void)setCellMaxWidth:(NSInteger)value
+{
+ _cellMaxWidth = value;
+ [self update];
+}
+
+- (NSInteger)cellOptimumWidth
+{
+ return _cellOptimumWidth;
+}
+
+- (void)setCellOptimumWidth:(NSInteger)value
+{
+ _cellOptimumWidth = value;
+ [self update];
+}
+
+- (BOOL)sizeCellsToFit
+{
+ return _sizeCellsToFit;
+}
+
+- (void)setSizeCellsToFit:(BOOL)value
+{
+ _sizeCellsToFit = value;
+ [self update];
+}
+
+- (BOOL)useOverflowMenu
+{
+ return _useOverflowMenu;
+}
+
+- (void)setUseOverflowMenu:(BOOL)value
+{
+ _useOverflowMenu = value;
+ [self update];
+}
+
+- (PSMRolloverButton *)addTabButton
+{
+ return _addTabButton;
+}
+
+- (PSMOverflowPopUpButton *)overflowPopUpButton
+{
+ return _overflowPopUpButton;
+}
+
+- (BOOL)allowsBackgroundTabClosing
+{
+ return _allowsBackgroundTabClosing;
+}
+
+- (void)setAllowsBackgroundTabClosing:(BOOL)value
+{
+ _allowsBackgroundTabClosing = value;
+}
+
+- (BOOL)allowsResizing
+{
+ return _allowsResizing;
+}
+
+- (void)setAllowsResizing:(BOOL)value
+{
+ _allowsResizing = value;
+}
+
+- (BOOL)selectsTabsOnMouseDown
+{
+ return _selectsTabsOnMouseDown;
+}
+
+- (void)setSelectsTabsOnMouseDown:(BOOL)value
+{
+ _selectsTabsOnMouseDown = value;
+}
+
+- (BOOL)automaticallyAnimates
+{
+ return _automaticallyAnimates;
+}
+
+- (void)setAutomaticallyAnimates:(BOOL)value
+{
+ _automaticallyAnimates = value;
+}
+
+- (BOOL)alwaysShowActiveTab
+{
+ return _alwaysShowActiveTab;
+}
+
+- (void)setAlwaysShowActiveTab:(BOOL)value
+{
+ _alwaysShowActiveTab = value;
+}
+
+- (BOOL)allowsScrubbing
+{
+ return _allowsScrubbing;
+}
+
+- (void)setAllowsScrubbing:(BOOL)value
+{
+ _allowsScrubbing = value;
+}
+
+- (PSMTabBarTearOffStyle)tearOffStyle
+{
+ return _tearOffStyle;
+}
+
+- (void)setTearOffStyle:(PSMTabBarTearOffStyle)tearOffStyle
+{
+ _tearOffStyle = tearOffStyle;
+}
+
+#pragma mark -
+#pragma mark Functionality
+
+- (void)addTabViewItem:(NSTabViewItem *)item
+{
+ // create cell
+ PSMTabBarCell *cell = [[PSMTabBarCell alloc] initWithControlView:self];
+ NSRect cellRect, lastCellFrame = [[_cells lastObject] frame];
+
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ cellRect = [self genericCellRect];
+ cellRect.size.width = 30;
+ cellRect.origin.x = lastCellFrame.origin.x + lastCellFrame.size.width;
+ } else {
+ cellRect = /*lastCellFrame*/[self genericCellRect];
+ cellRect.size.width = lastCellFrame.size.width;
+ cellRect.size.height = 0;
+ cellRect.origin.y = lastCellFrame.origin.y + lastCellFrame.size.height;
+ }
+
+ [cell setRepresentedObject:item];
+ [cell setFrame:cellRect];
+
+ // bind it up
+ [self bindPropertiesForCell:cell andTabViewItem:item];
+
+ // add to collection
+ [_cells addObject:cell];
+ [cell release];
+ if ([_cells count] == [tabView numberOfTabViewItems]) {
+ [self update]; // don't update unless all are accounted for!
+ }
+}
+
+- (void)removeTabForCell:(PSMTabBarCell *)cell
+{
+ NSTabViewItem *item = [cell representedObject];
+
+ // unbind
+ [[cell indicator] unbind:@"animate"];
+ [[cell indicator] unbind:@"hidden"];
+ [cell unbind:@"hasIcon"];
+ [cell unbind:@"hasLargeImage"];
+ [cell unbind:@"title"];
+ [cell unbind:@"count"];
+ [cell unbind:@"countColor"];
+ [cell unbind:@"isEdited"];
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(isProcessing)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"isProcessing"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(icon)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"icon"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(objectCount)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"objectCount"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(countColor)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"countColor"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(largeImage)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"largeImage"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(isEdited)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"isEdited"];
+ }
+ }
+
+ // stop watching identifier
+ [item removeObserver:self forKeyPath:@"identifier"];
+
+ // remove indicator
+ if ([[self subviews] containsObject:[cell indicator]]) {
+ [[cell indicator] removeFromSuperview];
+ }
+ // remove tracking
+ [[NSNotificationCenter defaultCenter] removeObserver:cell];
+
+ if ([cell closeButtonTrackingTag] != 0) {
+ [self removeTrackingRect:[cell closeButtonTrackingTag]];
+ [cell setCloseButtonTrackingTag:0];
+ }
+ if ([cell cellTrackingTag] != 0) {
+ [self removeTrackingRect:[cell cellTrackingTag]];
+ [cell setCellTrackingTag:0];
+ }
+
+ // pull from collection
+ [_cells removeObject:cell];
+
+ [self update];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+ // did the tab's identifier change?
+ if ([keyPath isEqualToString:@"identifier"]) {
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ if ([cell representedObject] == object) {
+ [self _bindPropertiesForCell:cell andTabViewItem:object];
+ }
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark Hide/Show
+
+- (void)hideTabBar:(BOOL)hide animate:(BOOL)animate
+{
+ if (!_awakenedFromNib || (_isHidden && hide) || (!_isHidden && !hide) || (_currentStep != kPSMIsNotBeingResized)) {
+ return;
+ }
+
+ [[self subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
+
+ _isHidden = hide;
+ _currentStep = 0;
+ if (!animate) {
+ _currentStep = (NSInteger)kPSMHideAnimationSteps;
+ }
+
+ if (hide) {
+ [_overflowPopUpButton removeFromSuperview];
+ [_addTabButton removeFromSuperview];
+ } else if (!animate) {
+ [self addSubview:_overflowPopUpButton];
+ [self addSubview:_addTabButton];
+ }
+
+ CGFloat partnerOriginalSize, partnerOriginalOrigin, myOriginalSize, myOriginalOrigin, partnerTargetSize, partnerTargetOrigin, myTargetSize, myTargetOrigin;
+
+ // target values for partner
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ // current (original) values
+ myOriginalSize = [self frame].size.height;
+ myOriginalOrigin = [self frame].origin.y;
+ if (partnerView) {
+ partnerOriginalSize = [partnerView frame].size.height;
+ partnerOriginalOrigin = [partnerView frame].origin.y;
+ } else {
+ partnerOriginalSize = [[self window] frame].size.height;
+ partnerOriginalOrigin = [[self window] frame].origin.y;
+ }
+
+ if (partnerView) {
+ // above or below me?
+ if ((myOriginalOrigin - 22) > partnerOriginalOrigin) {
+ // partner is below me
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin + 21;
+ myTargetSize = myOriginalSize - 21;
+ partnerTargetOrigin = partnerOriginalOrigin;
+ partnerTargetSize = partnerOriginalSize + 21;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin - 21;
+ myTargetSize = myOriginalSize + 21;
+ partnerTargetOrigin = partnerOriginalOrigin;
+ partnerTargetSize = partnerOriginalSize - 21;
+ }
+ } else {
+ // partner is above me
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize - 21;
+ partnerTargetOrigin = partnerOriginalOrigin - 21;
+ partnerTargetSize = partnerOriginalSize + 21;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize + 21;
+ partnerTargetOrigin = partnerOriginalOrigin + 21;
+ partnerTargetSize = partnerOriginalSize - 21;
+ }
+ }
+ } else {
+ // for window movement
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize - 21;
+ partnerTargetOrigin = partnerOriginalOrigin + 21;
+ partnerTargetSize = partnerOriginalSize - 21;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize + 21;
+ partnerTargetOrigin = partnerOriginalOrigin - 21;
+ partnerTargetSize = partnerOriginalSize + 21;
+ }
+ }
+ } else /* vertical */ {
+ // current (original) values
+ myOriginalSize = [self frame].size.width;
+ myOriginalOrigin = [self frame].origin.x;
+ if (partnerView) {
+ partnerOriginalSize = [partnerView frame].size.width;
+ partnerOriginalOrigin = [partnerView frame].origin.x;
+ } else {
+ partnerOriginalSize = [[self window] frame].size.width;
+ partnerOriginalOrigin = [[self window] frame].origin.x;
+ }
+
+ if (partnerView) {
+ //to the left or right?
+ if (myOriginalOrigin < partnerOriginalOrigin + partnerOriginalSize) {
+ // partner is to the left
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = 1;
+ partnerTargetOrigin = partnerOriginalOrigin - myOriginalSize + 1;
+ partnerTargetSize = partnerOriginalSize + myOriginalSize - 1;
+ _tabBarWidth = myOriginalSize;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize + _tabBarWidth;
+ partnerTargetOrigin = partnerOriginalOrigin + _tabBarWidth;
+ partnerTargetSize = partnerOriginalSize - _tabBarWidth;
+ }
+ } else {
+ // partner is to the right
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin + myOriginalSize;
+ myTargetSize = 1;
+ partnerTargetOrigin = partnerOriginalOrigin;
+ partnerTargetSize = partnerOriginalSize + myOriginalSize;
+ _tabBarWidth = myOriginalSize;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin - _tabBarWidth;
+ myTargetSize = myOriginalSize + _tabBarWidth;
+ partnerTargetOrigin = partnerOriginalOrigin;
+ partnerTargetSize = partnerOriginalSize - _tabBarWidth;
+ }
+ }
+ } else {
+ // for window movement
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = 1;
+ partnerTargetOrigin = partnerOriginalOrigin + myOriginalSize - 1;
+ partnerTargetSize = partnerOriginalSize - myOriginalSize + 1;
+ _tabBarWidth = myOriginalSize;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = _tabBarWidth;
+ partnerTargetOrigin = partnerOriginalOrigin - _tabBarWidth + 1;
+ partnerTargetSize = partnerOriginalSize + _tabBarWidth - 1;
+ }
+ }
+
+ if (!_isHidden && [[self delegate] respondsToSelector:@selector(desiredWidthForVerticalTabBar:)])
+ myTargetSize = [[self delegate] desiredWidthForVerticalTabBar:self];
+ }
+
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithDouble:myOriginalOrigin], @"myOriginalOrigin", [NSNumber numberWithDouble:partnerOriginalOrigin], @"partnerOriginalOrigin", [NSNumber numberWithDouble:myOriginalSize], @"myOriginalSize", [NSNumber numberWithDouble:partnerOriginalSize], @"partnerOriginalSize", [NSNumber numberWithDouble:myTargetOrigin], @"myTargetOrigin", [NSNumber numberWithDouble:partnerTargetOrigin], @"partnerTargetOrigin", [NSNumber numberWithDouble:myTargetSize], @"myTargetSize", [NSNumber numberWithDouble:partnerTargetSize], @"partnerTargetSize", nil];
+ if (_showHideAnimationTimer) {
+ [_showHideAnimationTimer invalidate];
+ [_showHideAnimationTimer release];
+ }
+ _showHideAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:(1.0 / 30.0) target:self selector:@selector(animateShowHide:) userInfo:userInfo repeats:YES] retain];
+}
+
+- (void)animateShowHide:(NSTimer *)timer
+{
+ // moves the frame of the tab bar and window (or partner view) linearly to hide or show the tab bar
+ NSRect myFrame = [self frame];
+ NSDictionary *userInfo = [timer userInfo];
+ CGFloat myCurrentOrigin = ([[userInfo objectForKey:@"myOriginalOrigin"] doubleValue] + (([[userInfo objectForKey:@"myTargetOrigin"] doubleValue] - [[userInfo objectForKey:@"myOriginalOrigin"] doubleValue]) * (_currentStep/kPSMHideAnimationSteps)));
+ CGFloat myCurrentSize = ([[userInfo objectForKey:@"myOriginalSize"] doubleValue] + (([[userInfo objectForKey:@"myTargetSize"] doubleValue] - [[userInfo objectForKey:@"myOriginalSize"] doubleValue]) * (_currentStep/kPSMHideAnimationSteps)));
+ CGFloat partnerCurrentOrigin = ([[userInfo objectForKey:@"partnerOriginalOrigin"] doubleValue] + (([[userInfo objectForKey:@"partnerTargetOrigin"] doubleValue] - [[userInfo objectForKey:@"partnerOriginalOrigin"] doubleValue]) * (_currentStep/kPSMHideAnimationSteps)));
+ CGFloat partnerCurrentSize = ([[userInfo objectForKey:@"partnerOriginalSize"] doubleValue] + (([[userInfo objectForKey:@"partnerTargetSize"] doubleValue] - [[userInfo objectForKey:@"partnerOriginalSize"] doubleValue]) * (_currentStep/kPSMHideAnimationSteps)));
+
+ NSRect myNewFrame;
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ myNewFrame = NSMakeRect(myFrame.origin.x, myCurrentOrigin, myFrame.size.width, myCurrentSize);
+ } else {
+ myNewFrame = NSMakeRect(myCurrentOrigin, myFrame.origin.y, myCurrentSize, myFrame.size.height);
+ }
+
+ if (partnerView) {
+ // resize self and view
+ NSRect resizeRect;
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ resizeRect = NSMakeRect([partnerView frame].origin.x, partnerCurrentOrigin, [partnerView frame].size.width, partnerCurrentSize);
+ } else {
+ resizeRect = NSMakeRect(partnerCurrentOrigin, [partnerView frame].origin.y, partnerCurrentSize, [partnerView frame].size.height);
+ }
+ [partnerView setFrame:resizeRect];
+ [partnerView setNeedsDisplay:YES];
+ [self setFrame:myNewFrame];
+ } else {
+ // resize self and window
+ NSRect resizeRect;
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ resizeRect = NSMakeRect([[self window] frame].origin.x, partnerCurrentOrigin, [[self window] frame].size.width, partnerCurrentSize);
+ } else {
+ resizeRect = NSMakeRect(partnerCurrentOrigin, [[self window] frame].origin.y, partnerCurrentSize, [[self window] frame].size.height);
+ }
+ [[self window] setFrame:resizeRect display:YES];
+ [self setFrame:myNewFrame];
+ }
+
+ // next
+ _currentStep++;
+ if (_currentStep == kPSMHideAnimationSteps + 1) {
+ _currentStep = kPSMIsNotBeingResized;
+ [self viewDidEndLiveResize];
+ [self update:NO];
+
+ //send the delegate messages
+ if (_isHidden) {
+ if ([[self delegate] respondsToSelector:@selector(tabView:tabBarDidHide:)]) {
+ [[self delegate] tabView:[self tabView] tabBarDidHide:self];
+ }
+ } else {
+ [self addSubview:_overflowPopUpButton];
+ [self addSubview:_addTabButton];
+
+ if ([[self delegate] respondsToSelector:@selector(tabView:tabBarDidUnhide:)]) {
+ [[self delegate] tabView:[self tabView] tabBarDidUnhide:self];
+ }
+ }
+
+ [_showHideAnimationTimer invalidate];
+ [_showHideAnimationTimer release]; _showHideAnimationTimer = nil;
+ }
+ [[self window] display];
+}
+
+- (BOOL)isTabBarHidden
+{
+ return _isHidden;
+}
+
+- (BOOL)isAnimating
+{
+ return _animationTimer != nil;
+}
+
+- (id)partnerView
+{
+ return partnerView;
+}
+
+- (void)setPartnerView:(id)view
+{
+ [partnerView release];
+ [view retain];
+ partnerView = view;
+}
+
+#pragma mark -
+#pragma mark Drawing
+
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (void)drawRect:(NSRect)rect
+{
+ [style drawTabBar:self inRect:rect];
+}
+
+- (void)update
+{
+ [self update:_automaticallyAnimates];
+}
+
+- (void)update:(BOOL)animate
+{
+ // make sure all of our tabs are accounted for before updating
+ if ([[self tabView] numberOfTabViewItems] != [_cells count]) {
+ return;
+ }
+
+ // hide/show? (these return if already in desired state)
+ if ( (_hideForSingleTab) && ([_cells count] <= 1) ) {
+ [self hideTabBar:YES animate:YES];
+ return;
+ } else {
+ [self hideTabBar:NO animate:YES];
+ }
+
+ [self removeAllToolTips];
+ [_controller layoutCells]; //eventually we should only have to call this when we know something has changed
+
+ PSMTabBarCell *currentCell;
+
+ NSMenu *overflowMenu = [_controller overflowMenu];
+ [_overflowPopUpButton setHidden:(overflowMenu == nil)];
+ [_overflowPopUpButton setMenu:overflowMenu];
+
+ if (_animationTimer) {
+ [_animationTimer invalidate];
+ [_animationTimer release]; _animationTimer = nil;
+ }
+
+ if (animate) {
+ NSMutableArray *targetFrames = [NSMutableArray arrayWithCapacity:[_cells count]];
+
+ for (NSInteger i = 0; i < [_cells count]; i++) {
+ currentCell = [_cells objectAtIndex:i];
+
+ //we're going from NSRect -> NSValue -> NSRect -> NSValue here - oh well
+ [targetFrames addObject:[NSValue valueWithRect:[_controller cellFrameAtIndex:i]]];
+ }
+
+ [_addTabButton setHidden:!_showAddTabButton];
+
+ NSAnimation *animation = [[NSAnimation alloc] initWithDuration:0.50 animationCurve:NSAnimationEaseInOut];
+ [animation setAnimationBlockingMode:NSAnimationNonblocking];
+ [animation startAnimation];
+ _animationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0
+ target:self
+ selector:@selector(_animateCells:)
+ userInfo:[NSArray arrayWithObjects:targetFrames, animation, nil]
+ repeats:YES] retain];
+ [animation release];
+ [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSEventTrackingRunLoopMode];
+ [self _animateCells:_animationTimer];
+
+ } else {
+ for (NSInteger i = 0; i < [_cells count]; i++) {
+ currentCell = [_cells objectAtIndex:i];
+ [currentCell setFrame:[_controller cellFrameAtIndex:i]];
+
+ if (![currentCell isInOverflowMenu]) {
+ [self _setupTrackingRectsForCell:currentCell];
+ }
+ }
+
+ [_addTabButton setFrame:[_controller addButtonRect]];
+ [_addTabButton setHidden:!_showAddTabButton];
+ [self setNeedsDisplay:YES];
+ }
+}
+
+- (void)_animateCells:(NSTimer *)timer
+{
+ NSAnimation *animation = [[timer userInfo] objectAtIndex:1];
+ NSArray *targetFrames = [[timer userInfo] objectAtIndex:0];
+ PSMTabBarCell *currentCell;
+ NSInteger cellCount = [_cells count];
+
+ if ((cellCount > 0) && [animation isAnimating]) {
+ //compare our target position with the current position and move towards the target
+ for (NSInteger i = 0; i < [targetFrames count] && i < cellCount; i++) {
+ currentCell = [_cells objectAtIndex:i];
+ NSRect cellFrame = [currentCell frame], targetFrame = [[targetFrames objectAtIndex:i] rectValue];
+ CGFloat sizeChange;
+ CGFloat originChange;
+
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ sizeChange = (targetFrame.size.width - cellFrame.size.width) * [animation currentProgress];
+ originChange = (targetFrame.origin.x - cellFrame.origin.x) * [animation currentProgress];
+ cellFrame.size.width += sizeChange;
+ cellFrame.origin.x += originChange;
+ } else {
+ sizeChange = (targetFrame.size.height - cellFrame.size.height) * [animation currentProgress];
+ originChange = (targetFrame.origin.y - cellFrame.origin.y) * [animation currentProgress];
+ cellFrame.size.height += sizeChange;
+ cellFrame.origin.y += originChange;
+ }
+
+ [currentCell setFrame:cellFrame];
+
+ //highlight the cell if the mouse is over it
+ NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
+ NSRect closeRect = [currentCell closeButtonRectForFrame:cellFrame];
+ [currentCell setHighlighted:NSMouseInRect(mousePoint, cellFrame, [self isFlipped])];
+ [currentCell setCloseButtonOver:NSMouseInRect(mousePoint, closeRect, [self isFlipped])];
+ }
+
+ if (_showAddTabButton) {
+ //animate the add tab button
+ NSRect target = [_controller addButtonRect], frame = [_addTabButton frame];
+ frame.origin.x += (target.origin.x - frame.origin.x) * [animation currentProgress];
+ [_addTabButton setFrame:frame];
+ }
+ } else {
+ //put all the cells where they should be in their final position
+ if (cellCount > 0) {
+ for (NSInteger i = 0; i < [targetFrames count] && i < cellCount; i++) {
+ PSMTabBarCell *currentCell = [_cells objectAtIndex:i];
+ NSRect cellFrame = [currentCell frame], targetFrame = [[targetFrames objectAtIndex:i] rectValue];
+
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ cellFrame.size.width = targetFrame.size.width;
+ cellFrame.origin.x = targetFrame.origin.x;
+ } else {
+ cellFrame.size.height = targetFrame.size.height;
+ cellFrame.origin.y = targetFrame.origin.y;
+ }
+
+ [currentCell setFrame:cellFrame];
+
+ //highlight the cell if the mouse is over it
+ NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
+ NSRect closeRect = [currentCell closeButtonRectForFrame:cellFrame];
+ [currentCell setHighlighted:NSMouseInRect(mousePoint, cellFrame, [self isFlipped])];
+ [currentCell setCloseButtonOver:NSMouseInRect(mousePoint, closeRect, [self isFlipped])];
+ }
+ }
+
+ //set the frame for the add tab button
+ if (_showAddTabButton) {
+ NSRect frame = [_addTabButton frame];
+ frame.origin.x = [_controller addButtonRect].origin.x;
+ [_addTabButton setFrame:frame];
+ }
+
+ [_animationTimer invalidate];
+ [_animationTimer release]; _animationTimer = nil;
+
+ for (NSInteger i = 0; i < cellCount; i++) {
+ currentCell = [_cells objectAtIndex:i];
+
+ //we've hit the cells that are in overflow, stop setting up tracking rects
+ if ([currentCell isInOverflowMenu]) {
+ break;
+ }
+
+ [self _setupTrackingRectsForCell:currentCell];
+ }
+ }
+
+ [self setNeedsDisplay:YES];
+}
+
+- (void)_setupTrackingRectsForCell:(PSMTabBarCell *)cell
+{
+ NSInteger tag, index = [_cells indexOfObject:cell];
+ NSRect cellTrackingRect = [_controller cellTrackingRectAtIndex:index];
+ NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
+ BOOL mouseInCell = NSMouseInRect(mousePoint, cellTrackingRect, [self isFlipped]);
+
+ //set the cell tracking rect
+ [self removeTrackingRect:[cell cellTrackingTag]];
+ tag = [self addTrackingRect:cellTrackingRect owner:cell userData:nil assumeInside:mouseInCell];
+ [cell setCellTrackingTag:tag];
+ [cell setHighlighted:mouseInCell];
+
+ if ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]) {
+ NSRect closeRect = [_controller closeButtonTrackingRectAtIndex:index];
+ BOOL mouseInCloseRect = NSMouseInRect(mousePoint, closeRect, [self isFlipped]);
+
+ //set the close button tracking rect
+ [self removeTrackingRect:[cell closeButtonTrackingTag]];
+ tag = [self addTrackingRect:closeRect owner:cell userData:nil assumeInside:mouseInCloseRect];
+ [cell setCloseButtonTrackingTag:tag];
+
+ [cell setCloseButtonOver:mouseInCloseRect];
+ }
+
+ //set the tooltip tracking rect
+ [self addToolTipRect:[cell frame] owner:self userData:nil];
+}
+
+- (void)_positionOverflowMenu
+{
+ NSRect cellRect, frame = [self frame];
+ cellRect.size.height = [style tabCellHeight];
+ cellRect.size.width = [style rightMarginForTabBarControl];
+
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ cellRect.origin.y = 0;
+ cellRect.origin.x = frame.size.width - [style rightMarginForTabBarControl] + (_resizeAreaCompensation ? -(_resizeAreaCompensation - 1) : 1);
+ [_overflowPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMinXMargin];
+ } else {
+ cellRect.origin.x = 0;
+ cellRect.origin.y = frame.size.height - [style tabCellHeight];
+ cellRect.size.width = frame.size.width;
+ [_overflowPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMinXMargin | NSViewMinYMargin];
+ }
+
+ [_overflowPopUpButton setFrame:cellRect];
+}
+
+- (void)_checkWindowFrame
+{
+ //figure out if the new frame puts the control in the way of the resize widget
+ NSWindow *window = [self window];
+
+ if (window) {
+ NSRect resizeWidgetFrame = [[window contentView] frame];
+ resizeWidgetFrame.origin.x += resizeWidgetFrame.size.width - 22;
+ resizeWidgetFrame.size.width = 22;
+ resizeWidgetFrame.size.height = 22;
+
+ if ([window showsResizeIndicator] && NSIntersectsRect([self frame], resizeWidgetFrame)) {
+ //the resize widgets are larger on metal windows
+ _resizeAreaCompensation = [window styleMask] & NSTexturedBackgroundWindowMask ? 20 : 8;
+ } else {
+ _resizeAreaCompensation = 0;
+ }
+
+ [self _positionOverflowMenu];
+ }
+}
+
+#pragma mark -
+#pragma mark Mouse Tracking
+
+- (BOOL)mouseDownCanMoveWindow
+{
+ return NO;
+}
+
+- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
+{
+ return YES;
+}
+
+- (void)mouseDown:(NSEvent *)theEvent
+{
+ _didDrag = NO;
+
+ // keep for dragging
+ [self setLastMouseDownEvent:theEvent];
+ // what cell?
+ NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
+ NSRect frame = [self frame];
+
+ if ([self orientation] == PSMTabBarVerticalOrientation && [self allowsResizing] && partnerView && (mousePt.x > frame.size.width - 3)) {
+ _resizing = YES;
+ }
+
+ NSRect cellFrame;
+ PSMTabBarCell *cell = [self cellForPoint:mousePt cellFrame:&cellFrame];
+ if (cell) {
+ BOOL overClose = NSMouseInRect(mousePt, [cell closeButtonRectForFrame:cellFrame], [self isFlipped]);
+ if (overClose &&
+ ![self disableTabClose] &&
+ ![cell isCloseButtonSuppressed] &&
+ ([self allowsBackgroundTabClosing] || [[cell representedObject] isEqualTo:[tabView selectedTabViewItem]] || [theEvent modifierFlags] & NSCommandKeyMask)) {
+ [cell setCloseButtonOver:NO];
+ [cell setCloseButtonPressed:YES];
+ _closeClicked = YES;
+ } else {
+ [cell setCloseButtonPressed:NO];
+ if (_selectsTabsOnMouseDown) {
+ [self performSelector:@selector(tabClick:) withObject:cell];
+ }
+ }
+ [self setNeedsDisplay:YES];
+ }
+}
+
+- (void)mouseDragged:(NSEvent *)theEvent
+{
+ if ([self lastMouseDownEvent] == nil) {
+ return;
+ }
+
+ NSPoint currentPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
+
+ if (_resizing) {
+ NSRect frame = [self frame];
+ CGFloat resizeAmount = [theEvent deltaX];
+ if ((currentPoint.x > frame.size.width && resizeAmount > 0) || (currentPoint.x < frame.size.width && resizeAmount < 0)) {
+ [[NSCursor resizeLeftRightCursor] push];
+
+ NSRect partnerFrame = [partnerView frame];
+
+ //do some bounds checking
+ if ((frame.size.width + resizeAmount > [self cellMinWidth]) && (frame.size.width + resizeAmount < [self cellMaxWidth])) {
+ frame.size.width += resizeAmount;
+ partnerFrame.size.width -= resizeAmount;
+ partnerFrame.origin.x += resizeAmount;
+
+ [self setFrame:frame];
+ [partnerView setFrame:partnerFrame];
+ [[self superview] setNeedsDisplay:YES];
+ }
+ }
+ return;
+ }
+
+ NSRect cellFrame;
+ NSPoint trackingStartPoint = [self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil];
+ PSMTabBarCell *cell = [self cellForPoint:trackingStartPoint cellFrame:&cellFrame];
+ if (cell) {
+ //check to see if the close button was the target in the clicked cell
+ //highlight/unhighlight the close button as necessary
+ NSRect iconRect = [cell closeButtonRectForFrame:cellFrame];
+
+ if (_closeClicked && NSMouseInRect(trackingStartPoint, iconRect, [self isFlipped]) &&
+ ([self allowsBackgroundTabClosing] || [[cell representedObject] isEqualTo:[tabView selectedTabViewItem]])) {
+ [cell setCloseButtonPressed:NSMouseInRect(currentPoint, iconRect, [self isFlipped])];
+ [self setNeedsDisplay:YES];
+ return;
+ }
+
+ CGFloat dx = fabs(currentPoint.x - trackingStartPoint.x);
+ CGFloat dy = fabs(currentPoint.y - trackingStartPoint.y);
+ CGFloat distance = sqrt(dx * dx + dy * dy);
+
+ if (distance >= 10 && !_didDrag && ![[PSMTabDragAssistant sharedDragAssistant] isDragging] &&
+ [self delegate] && [[self delegate] respondsToSelector:@selector(tabView:shouldDragTabViewItem:fromTabBar:)] &&
+ [[self delegate] tabView:tabView shouldDragTabViewItem:[cell representedObject] fromTabBar:self]) {
+ _didDrag = YES;
+ [[PSMTabDragAssistant sharedDragAssistant] startDraggingCell:cell fromTabBar:self withMouseDownEvent:[self lastMouseDownEvent]];
+ }
+ }
+}
+
+- (void)mouseUp:(NSEvent *)theEvent
+{
+ if (_resizing) {
+ _resizing = NO;
+ [[NSCursor arrowCursor] set];
+ } else {
+ // what cell?
+ NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
+ NSRect cellFrame, mouseDownCellFrame;
+ PSMTabBarCell *cell = [self cellForPoint:mousePt cellFrame:&cellFrame];
+ PSMTabBarCell *mouseDownCell = [self cellForPoint:[self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil] cellFrame:&mouseDownCellFrame];
+ if (cell) {
+ NSPoint trackingStartPoint = [self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil];
+ NSRect iconRect = [mouseDownCell closeButtonRectForFrame:mouseDownCellFrame];
+
+ if ((NSMouseInRect(mousePt, iconRect,[self isFlipped])) && ![self disableTabClose] && ![cell isCloseButtonSuppressed] && [mouseDownCell closeButtonPressed]) {
+ if (([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0) {
+ //If the user is holding Option, close all other tabs
+ NSEnumerator *enumerator = [[[[self cells] copy] autorelease] objectEnumerator];
+ PSMTabBarCell *otherCell;
+
+ while ((otherCell = [enumerator nextObject])) {
+ if (otherCell != cell)
+ [self performSelector:@selector(closeTabClick:) withObject:otherCell];
+ }
+
+ //Fix the close button for the clicked tab not to be pressed
+ [cell setCloseButtonPressed:NO];
+
+ } else {
+ //Otherwise, close this tab
+ [self performSelector:@selector(closeTabClick:) withObject:cell];
+ }
+
+ } else if (NSMouseInRect(mousePt, mouseDownCellFrame, [self isFlipped]) &&
+ (!NSMouseInRect(trackingStartPoint, [cell closeButtonRectForFrame:cellFrame], [self isFlipped]) || ![self allowsBackgroundTabClosing] || [self disableTabClose])) {
+ [mouseDownCell setCloseButtonPressed:NO];
+ // If -[self selectsTabsOnMouseDown] is TRUE, we already performed tabClick: on mouseDown.
+ if (![self selectsTabsOnMouseDown]) {
+ [self performSelector:@selector(tabClick:) withObject:cell];
+ }
+
+ } else {
+ [mouseDownCell setCloseButtonPressed:NO];
+ [self performSelector:@selector(tabNothing:) withObject:cell];
+ }
+ }
+
+ _closeClicked = NO;
+ }
+}
+
+- (NSMenu *)menuForEvent:(NSEvent *)event
+{
+ NSMenu *menu = nil;
+ NSTabViewItem *item = [[self cellForPoint:[self convertPoint:[event locationInWindow] fromView:nil] cellFrame:nil] representedObject];
+
+ if (item && [[self delegate] respondsToSelector:@selector(tabView:menuForTabViewItem:)]) {
+ menu = [[self delegate] tabView:tabView menuForTabViewItem:item];
+ }
+ return menu;
+}
+
+#pragma mark -
+#pragma mark Drag and Drop
+
+- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)theEvent
+{
+ return YES;
+}
+
+// NSDraggingSource
+- (NSUInteger)draggingSourceOperationMaskForLocal:(BOOL)isLocal
+{
+ return (isLocal ? NSDragOperationMove : NSDragOperationNone);
+}
+
+- (BOOL)ignoreModifierKeysWhileDragging
+{
+ return YES;
+}
+
+- (void)draggedImage:(NSImage *)anImage beganAt:(NSPoint)screenPoint
+{
+ [[PSMTabDragAssistant sharedDragAssistant] draggingBeganAt:screenPoint];
+}
+
+- (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenPoint
+{
+ [[PSMTabDragAssistant sharedDragAssistant] draggingMovedTo:screenPoint];
+}
+
+// NSDraggingDestination
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ if([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
+
+ if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
+ ![[self delegate] tabView:[[sender draggingSource] tabView] shouldDropTabViewItem:[[[PSMTabDragAssistant sharedDragAssistant] draggedCell] representedObject] inTabBar:self]) {
+ return NSDragOperationNone;
+ }
+
+ [[PSMTabDragAssistant sharedDragAssistant] draggingEnteredTabBar:self atPoint:[self convertPoint:[sender draggingLocation] fromView:nil]];
+ return NSDragOperationMove;
+ }
+
+ return NSDragOperationNone;
+}
+
+- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ PSMTabBarCell *cell = [self cellForPoint:[self convertPoint:[sender draggingLocation] fromView:nil] cellFrame:nil];
+
+ if ([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
+
+ if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
+ ![[self delegate] tabView:[[sender draggingSource] tabView] shouldDropTabViewItem:[[[PSMTabDragAssistant sharedDragAssistant] draggedCell] representedObject] inTabBar:self]) {
+ return NSDragOperationNone;
+ }
+
+ [[PSMTabDragAssistant sharedDragAssistant] draggingUpdatedInTabBar:self atPoint:[self convertPoint:[sender draggingLocation] fromView:nil]];
+ return NSDragOperationMove;
+ } else if (cell) {
+ //something that was accepted by the delegate was dragged on
+
+ //Test for the space bar (the skip-the-delay key).
+ enum { virtualKeycodeForSpace = 49 }; //Source: IM:Tx (Fig. C-2)
+ union {
+ KeyMap keymap;
+ char bits[16];
+ } keymap;
+ GetKeys(keymap.keymap);
+ if ((GetCurrentEventKeyModifiers() == 0) && bit_test(keymap.bits, virtualKeycodeForSpace)) {
+ //The user pressed the space bar. This skips the delay; the user wants to pop the spring on this tab *now*.
+
+ //For some reason, it crashes if I call -fire here. I don't know why. It doesn't crash if I simply set the fire date to now.
+ [_springTimer setFireDate:[NSDate date]];
+ } else {
+ //Wind the spring for a spring-loaded drop.
+ //The delay time comes from Finder's defaults, which specifies it in milliseconds.
+ //If the delegate can't handle our spring-loaded drop, we'll abort it when the timer fires. See fireSpring:. This is simpler than constantly (checking for spring-loaded awareness and tearing down/rebuilding the timer) at every delegate change.
+
+ //If the user has dragged to a different tab, reset the timer.
+ if (_tabViewItemWithSpring != [cell representedObject]) {
+ [_springTimer invalidate];
+ [_springTimer release]; _springTimer = nil;
+ _tabViewItemWithSpring = [cell representedObject];
+ }
+ if (!_springTimer) {
+ //Finder's default delay time, as of Tiger, is 668 ms. If the user has never changed it, there's no setting in its defaults, so we default to that amount.
+ NSNumber *delayNumber = [(NSNumber *)CFPreferencesCopyAppValue((CFStringRef)@"SpringingDelayMilliseconds", (CFStringRef)@"com.apple.finder") autorelease];
+ NSTimeInterval delaySeconds = delayNumber ? [delayNumber doubleValue] / 1000.0 : 0.668;
+ _springTimer = [[NSTimer scheduledTimerWithTimeInterval:delaySeconds
+ target:self
+ selector:@selector(fireSpring:)
+ userInfo:sender
+ repeats:NO] retain];
+ }
+ }
+ return NSDragOperationCopy;
+ }
+
+ return NSDragOperationNone;
+}
+
+- (void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ [_springTimer invalidate];
+ [_springTimer release]; _springTimer = nil;
+
+ [[PSMTabDragAssistant sharedDragAssistant] draggingExitedTabBar:self];
+}
+
+- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+ //validate the drag operation only if there's a valid tab bar to drop into
+ return [[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] == NSNotFound ||
+ [[PSMTabDragAssistant sharedDragAssistant] destinationTabBar] != nil;
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ if ([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
+ [[PSMTabDragAssistant sharedDragAssistant] performDragOperation];
+ } else if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:acceptedDraggingInfo:onTabViewItem:)]) {
+ //forward the drop to the delegate
+ [[self delegate] tabView:tabView acceptedDraggingInfo:sender onTabViewItem:[[self cellForPoint:[self convertPoint:[sender draggingLocation] fromView:nil] cellFrame:nil] representedObject]];
+ }
+ return YES;
+}
+
+- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+ [[PSMTabDragAssistant sharedDragAssistant] draggedImageEndedAt:aPoint operation:operation];
+}
+
+- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
+{
+
+}
+
+#pragma mark -
+#pragma mark Spring-loading
+
+- (void)fireSpring:(NSTimer *)timer
+{
+ NSAssert1(timer == _springTimer, @"Spring fired by unrecognized timer %@", timer);
+
+ id <NSDraggingInfo> sender = [timer userInfo];
+ PSMTabBarCell *cell = [self cellForPoint:[self convertPoint:[sender draggingLocation] fromView:nil] cellFrame:nil];
+ [tabView selectTabViewItem:[cell representedObject]];
+
+ _tabViewItemWithSpring = nil;
+ [_springTimer invalidate];
+ [_springTimer release]; _springTimer = nil;
+}
+
+#pragma mark -
+#pragma mark Actions
+
+- (void)overflowMenuAction:(id)sender
+{
+ NSTabViewItem *tabViewItem = (NSTabViewItem *)[sender representedObject];
+ [tabView selectTabViewItem:tabViewItem];
+}
+
+- (void)closeTabClick:(id)sender
+{
+ NSTabViewItem *item = [sender representedObject];
+ [sender retain];
+ if(([_cells count] == 1) && (![self canCloseOnlyTab]))
+ return;
+
+ if ([[self delegate] respondsToSelector:@selector(tabView:shouldCloseTabViewItem:)]) {
+ if (![[self delegate] tabView:tabView shouldCloseTabViewItem:item]) {
+ // fix mouse downed close button
+ [sender setCloseButtonPressed:NO];
+ [sender release];
+ return;
+ }
+ }
+
+ [item retain];
+
+ [tabView removeTabViewItem:item];
+ [item release];
+ [sender release];
+}
+
+- (void)tabClick:(id)sender
+{
+ [tabView selectTabViewItem:[sender representedObject]];
+}
+
+- (void)tabNothing:(id)sender
+{
+ //[self update]; // takes care of highlighting based on state
+}
+
+- (void)frameDidChange:(NSNotification *)notification
+{
+ [self _checkWindowFrame];
+
+ // trying to address the drawing artifacts for the progress indicators - hackery follows
+ // this one fixes the "blanking" effect when the control hides and shows itself
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ [[cell indicator] stopAnimation:self];
+
+ [[cell indicator] performSelector:@selector(startAnimation:)
+ withObject:nil
+ afterDelay:0];
+ }
+
+ [self update:NO];
+}
+
+- (void)viewDidMoveToWindow
+{
+ [self _checkWindowFrame];
+}
+
+- (void)viewWillStartLiveResize
+{
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ [[cell indicator] stopAnimation:self];
+ }
+ [self setNeedsDisplay:YES];
+}
+
+-(void)viewDidEndLiveResize
+{
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ [[cell indicator] startAnimation:self];
+ }
+
+ [self _checkWindowFrame];
+ [self update:NO];
+}
+
+- (void)resetCursorRects
+{
+ [super resetCursorRects];
+ if ([self orientation] == PSMTabBarVerticalOrientation) {
+ NSRect frame = [self frame];
+ [self addCursorRect:NSMakeRect(frame.size.width - 2, 0, 2, frame.size.height) cursor:[NSCursor resizeLeftRightCursor]];
+ }
+}
+
+- (void)windowDidMove:(NSNotification *)aNotification
+{
+ [self setNeedsDisplay:YES];
+}
+
+- (void)windowDidUpdate:(NSNotification *)notification
+{
+ // hide? must readjust things if I'm not supposed to be showing
+ // this block of code only runs when the app launches
+ if ([self hideForSingleTab] && ([_cells count] <= 1) && !_awakenedFromNib) {
+ // must adjust frames now before display
+ NSRect myFrame = [self frame];
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ if (partnerView) {
+ NSRect partnerFrame = [partnerView frame];
+ // above or below me?
+ if (myFrame.origin.y - 22 > [partnerView frame].origin.y) {
+ // partner is below me
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y + 21, myFrame.size.width, myFrame.size.height - 21)];
+ [partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y, partnerFrame.size.width, partnerFrame.size.height + 21)];
+ } else {
+ // partner is above me
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, myFrame.size.width, myFrame.size.height - 21)];
+ [partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y - 21, partnerFrame.size.width, partnerFrame.size.height + 21)];
+ }
+ [partnerView setNeedsDisplay:YES];
+ [self setNeedsDisplay:YES];
+ } else {
+ // for window movement
+ NSRect windowFrame = [[self window] frame];
+ [[self window] setFrame:NSMakeRect(windowFrame.origin.x, windowFrame.origin.y + 21, windowFrame.size.width, windowFrame.size.height - 21) display:YES];
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, myFrame.size.width, myFrame.size.height - 21)];
+ }
+ } else {
+ if (partnerView) {
+ NSRect partnerFrame = [partnerView frame];
+ //to the left or right?
+ if (myFrame.origin.x < [partnerView frame].origin.x) {
+ // partner is to the left
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, 1, myFrame.size.height)];
+ [partnerView setFrame:NSMakeRect(partnerFrame.origin.x - myFrame.size.width + 1, partnerFrame.origin.y, partnerFrame.size.width + myFrame.size.width - 1, partnerFrame.size.height)];
+ } else {
+ // partner to the right
+ [self setFrame:NSMakeRect(myFrame.origin.x + myFrame.size.width, myFrame.origin.y, 1, myFrame.size.height)];
+ [partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y, partnerFrame.size.width + myFrame.size.width, partnerFrame.size.height)];
+ }
+ _tabBarWidth = myFrame.size.width;
+ [partnerView setNeedsDisplay:YES];
+ [self setNeedsDisplay:YES];
+ } else {
+ // for window movement
+ NSRect windowFrame = [[self window] frame];
+ [[self window] setFrame:NSMakeRect(windowFrame.origin.x + myFrame.size.width - 1, windowFrame.origin.y, windowFrame.size.width - myFrame.size.width + 1, windowFrame.size.height) display:YES];
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, 1, myFrame.size.height)];
+ }
+ }
+
+ _isHidden = YES;
+
+ if ([[self delegate] respondsToSelector:@selector(tabView:tabBarDidHide:)]) {
+ [[self delegate] tabView:[self tabView] tabBarDidHide:self];
+ }
+ }
+
+ _awakenedFromNib = YES;
+ [self setNeedsDisplay:YES];
+
+ //we only need to do this once
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidUpdateNotification object:nil];
+}
+
+#pragma mark -
+#pragma mark Menu Validation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)sender
+{
+ [sender setState:([[sender representedObject] isEqualTo:[tabView selectedTabViewItem]]) ? NSOnState : NSOffState];
+
+ return [[self delegate] respondsToSelector:@selector(tabView:validateOverflowMenuItem:forTabViewItem:)] ?
+ [[self delegate] tabView:[self tabView] validateOverflowMenuItem:sender forTabViewItem:[sender representedObject]] : YES;
+}
+
+#pragma mark -
+#pragma mark NSTabView Delegate
+
+- (void)tabView:(NSTabView *)aTabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
+{
+ // here's a weird one - this message is sent before the "tabViewDidChangeNumberOfTabViewItems"
+ // message, thus I can end up updating when there are no cells, if no tabs were (yet) present
+ NSInteger tabIndex = [aTabView indexOfTabViewItem:tabViewItem];
+
+ if ([_cells count] > 0 && tabIndex < [_cells count]) {
+ PSMTabBarCell *thisCell = [_cells objectAtIndex:tabIndex];
+ if (_alwaysShowActiveTab && [thisCell isInOverflowMenu]) {
+
+ //temporarily disable the delegate in order to move the tab to a different index
+ id tempDelegate = [aTabView delegate];
+ [aTabView setDelegate:nil];
+
+ // move it all around first
+ [tabViewItem retain];
+ [thisCell retain];
+ [aTabView removeTabViewItem:tabViewItem];
+ [aTabView insertTabViewItem:tabViewItem atIndex:0];
+ [_cells removeObjectAtIndex:tabIndex];
+ [_cells insertObject:thisCell atIndex:0];
+ [thisCell setIsInOverflowMenu:NO]; //very important else we get a fun recursive loop going
+ [[_cells objectAtIndex:[_cells count] - 1] setIsInOverflowMenu:YES]; //these 2 lines are pretty uncool and this logic needs to be updated
+ [thisCell release];
+ [tabViewItem release];
+
+ [aTabView setDelegate:tempDelegate];
+
+ //reset the selection since removing it changed the selection
+ [aTabView selectTabViewItem:tabViewItem];
+
+ [self update];
+ } else {
+ [_controller setSelectedCell:thisCell];
+ [self setNeedsDisplay:YES];
+ }
+ }
+
+ if ([[self delegate] respondsToSelector:@selector(tabView:didSelectTabViewItem:)]) {
+ [[self delegate] performSelector:@selector(tabView:didSelectTabViewItem:) withObject:aTabView withObject:tabViewItem];
+ }
+}
+
+- (BOOL)tabView:(NSTabView *)aTabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem
+{
+ if ([[self delegate] respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)]) {
+ return [[self delegate] tabView:aTabView shouldSelectTabViewItem:tabViewItem];
+ } else {
+ return YES;
+ }
+}
+- (void)tabView:(NSTabView *)aTabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
+{
+ if ([[self delegate] respondsToSelector:@selector(tabView:willSelectTabViewItem:)]) {
+ [[self delegate] performSelector:@selector(tabView:willSelectTabViewItem:) withObject:aTabView withObject:tabViewItem];
+ }
+}
+
+- (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)aTabView
+{
+ NSArray *tabItems = [tabView tabViewItems];
+ // go through cells, remove any whose representedObjects are not in [tabView tabViewItems]
+ NSEnumerator *e = [[[_cells copy] autorelease] objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ //remove the observer binding
+ if ([cell representedObject] && ![tabItems containsObject:[cell representedObject]]) {
+ if ([[self delegate] respondsToSelector:@selector(tabView:didCloseTabViewItem:)]) {
+ [[self delegate] tabView:aTabView didCloseTabViewItem:[cell representedObject]];
+ }
+
+ [self removeTabForCell:cell];
+ }
+ }
+
+ // go through tab view items, add cell for any not present
+ NSMutableArray *cellItems = [self representedTabViewItems];
+ NSEnumerator *ex = [tabItems objectEnumerator];
+ NSTabViewItem *item;
+ while ( (item = [ex nextObject]) ) {
+ if (![cellItems containsObject:item]) {
+ [self addTabViewItem:item];
+ }
+ }
+
+ // pass along for other delegate responses
+ if ([[self delegate] respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)]) {
+ [[self delegate] performSelector:@selector(tabViewDidChangeNumberOfTabViewItems:) withObject:aTabView];
+ }
+
+ // reset cursor tracking for the add tab button if one exists
+ if ([self addTabButton]) [[self addTabButton] resetCursorRects];
+}
+
+#pragma mark -
+#pragma mark Tooltips
+
+- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)userData
+{
+ if ([[self delegate] respondsToSelector:@selector(tabView:toolTipForTabViewItem:)]) {
+ return [[self delegate] tabView:[self tabView] toolTipForTabViewItem:[[self cellForPoint:point cellFrame:nil] representedObject]];
+ }
+ return nil;
+}
+
+#pragma mark -
+#pragma mark Archiving
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ [super encodeWithCoder:aCoder];
+ if ([aCoder allowsKeyedCoding]) {
+ [aCoder encodeObject:_cells forKey:@"PSMcells"];
+ [aCoder encodeObject:tabView forKey:@"PSMtabView"];
+ [aCoder encodeObject:_overflowPopUpButton forKey:@"PSMoverflowPopUpButton"];
+ [aCoder encodeObject:_addTabButton forKey:@"PSMaddTabButton"];
+ [aCoder encodeObject:style forKey:@"PSMstyle"];
+ [aCoder encodeInteger:_orientation forKey:@"PSMorientation"];
+ [aCoder encodeBool:_canCloseOnlyTab forKey:@"PSMcanCloseOnlyTab"];
+ [aCoder encodeBool:_disableTabClose forKey:@"PSMdisableTabClose"];
+ [aCoder encodeBool:_hideForSingleTab forKey:@"PSMhideForSingleTab"];
+ [aCoder encodeBool:_allowsBackgroundTabClosing forKey:@"PSMallowsBackgroundTabClosing"];
+ [aCoder encodeBool:_allowsResizing forKey:@"PSMallowsResizing"];
+ [aCoder encodeBool:_selectsTabsOnMouseDown forKey:@"PSMselectsTabsOnMouseDown"];
+ [aCoder encodeBool:_showAddTabButton forKey:@"PSMshowAddTabButton"];
+ [aCoder encodeBool:_sizeCellsToFit forKey:@"PSMsizeCellsToFit"];
+ [aCoder encodeInteger:_cellMinWidth forKey:@"PSMcellMinWidth"];
+ [aCoder encodeInteger:_cellMaxWidth forKey:@"PSMcellMaxWidth"];
+ [aCoder encodeInteger:_cellOptimumWidth forKey:@"PSMcellOptimumWidth"];
+ [aCoder encodeInteger:_currentStep forKey:@"PSMcurrentStep"];
+ [aCoder encodeBool:_isHidden forKey:@"PSMisHidden"];
+ [aCoder encodeObject:partnerView forKey:@"PSMpartnerView"];
+ [aCoder encodeBool:_awakenedFromNib forKey:@"PSMawakenedFromNib"];
+ [aCoder encodeObject:_lastMouseDownEvent forKey:@"PSMlastMouseDownEvent"];
+ [aCoder encodeObject:delegate forKey:@"PSMdelegate"];
+ [aCoder encodeBool:_useOverflowMenu forKey:@"PSMuseOverflowMenu"];
+ [aCoder encodeBool:_automaticallyAnimates forKey:@"PSMautomaticallyAnimates"];
+ [aCoder encodeBool:_alwaysShowActiveTab forKey:@"PSMalwaysShowActiveTab"];
+ }
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ self = [super initWithCoder:aDecoder];
+ if (self) {
+
+ // Initialization
+ [self initAddedProperties];
+ [self registerForDraggedTypes:[NSArray arrayWithObjects:@"PSMTabBarControlItemPBType", nil]];
+
+ if ([aDecoder allowsKeyedCoding]) {
+ _cells = [[aDecoder decodeObjectForKey:@"PSMcells"] retain];
+ tabView = [[aDecoder decodeObjectForKey:@"PSMtabView"] retain];
+ _overflowPopUpButton = [[aDecoder decodeObjectForKey:@"PSMoverflowPopUpButton"] retain];
+ _addTabButton = [[aDecoder decodeObjectForKey:@"PSMaddTabButton"] retain];
+ style = [[aDecoder decodeObjectForKey:@"PSMstyle"] retain];
+ _orientation = (PSMTabBarOrientation)[aDecoder decodeIntegerForKey:@"PSMorientation"];
+ _canCloseOnlyTab = [aDecoder decodeBoolForKey:@"PSMcanCloseOnlyTab"];
+ _disableTabClose = [aDecoder decodeBoolForKey:@"PSMdisableTabClose"];
+ _hideForSingleTab = [aDecoder decodeBoolForKey:@"PSMhideForSingleTab"];
+ _allowsBackgroundTabClosing = [aDecoder decodeBoolForKey:@"PSMallowsBackgroundTabClosing"];
+ _allowsResizing = [aDecoder decodeBoolForKey:@"PSMallowsResizing"];
+ _selectsTabsOnMouseDown = [aDecoder decodeBoolForKey:@"PSMselectsTabsOnMouseDown"];
+ _showAddTabButton = [aDecoder decodeBoolForKey:@"PSMshowAddTabButton"];
+ _sizeCellsToFit = [aDecoder decodeBoolForKey:@"PSMsizeCellsToFit"];
+ _cellMinWidth = [aDecoder decodeIntegerForKey:@"PSMcellMinWidth"];
+ _cellMaxWidth = [aDecoder decodeIntegerForKey:@"PSMcellMaxWidth"];
+ _cellOptimumWidth = [aDecoder decodeIntegerForKey:@"PSMcellOptimumWidth"];
+ _currentStep = [aDecoder decodeIntegerForKey:@"PSMcurrentStep"];
+ _isHidden = [aDecoder decodeBoolForKey:@"PSMisHidden"];
+ partnerView = [[aDecoder decodeObjectForKey:@"PSMpartnerView"] retain];
+ _awakenedFromNib = [aDecoder decodeBoolForKey:@"PSMawakenedFromNib"];
+ _lastMouseDownEvent = [[aDecoder decodeObjectForKey:@"PSMlastMouseDownEvent"] retain];
+ _useOverflowMenu = [aDecoder decodeBoolForKey:@"PSMuseOverflowMenu"];
+ _automaticallyAnimates = [aDecoder decodeBoolForKey:@"PSMautomaticallyAnimates"];
+ _alwaysShowActiveTab = [aDecoder decodeBoolForKey:@"PSMalwaysShowActiveTab"];
+ delegate = [[aDecoder decodeObjectForKey:@"PSMdelegate"] retain];
+ }
+
+ // resize
+ [self setPostsFrameChangedNotifications:YES];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:) name:NSViewFrameDidChangeNotification object:self];
+ }
+
+ [self setTarget:self];
+ return self;
+}
+
+#pragma mark -
+#pragma mark IB Palette
+
+- (NSSize)minimumFrameSizeFromKnobPosition:(NSInteger)position
+{
+ return NSMakeSize(100.0, 22.0);
+}
+
+- (NSSize)maximumFrameSizeFromKnobPosition:(NSInteger)knobPosition
+{
+ return NSMakeSize(10000.0, 22.0);
+}
+
+- (void)placeView:(NSRect)newFrame
+{
+ // this is called any time the view is resized in IB
+ [self setFrame:newFrame];
+ [self update:NO];
+}
+
+#pragma mark -
+#pragma mark Convenience
+
+- (void)bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item
+{
+ [self _bindPropertiesForCell:cell andTabViewItem:item];
+
+ // watch for changes in the identifier
+ [item addObserver:self forKeyPath:@"identifier" options:0 context:nil];
+}
+
+- (void)_bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item
+{
+ // bind the indicator to the represented object's status (if it exists)
+ [[cell indicator] setHidden:YES];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(isProcessing)]) {
+ NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
+ [bindingOptions setObject:NSNegateBooleanTransformerName forKey:@"NSValueTransformerName"];
+ [[cell indicator] bind:@"animate" toObject:[item identifier] withKeyPath:@"isProcessing" options:nil];
+ [[cell indicator] bind:@"hidden" toObject:[item identifier] withKeyPath:@"isProcessing" options:bindingOptions];
+ [[item identifier] addObserver:cell forKeyPath:@"isProcessing" options:0 context:nil];
+ }
+ }
+
+ // bind for the existence of an icon
+ [cell setHasIcon:NO];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(icon)]) {
+ NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
+ [bindingOptions setObject:NSIsNotNilTransformerName forKey:@"NSValueTransformerName"];
+ [cell bind:@"hasIcon" toObject:[item identifier] withKeyPath:@"icon" options:bindingOptions];
+ [[item identifier] addObserver:cell forKeyPath:@"icon" options:0 context:nil];
+ }
+ }
+
+ // bind for the existence of a counter
+ [cell setCount:0];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(objectCount)]) {
+ [cell bind:@"count" toObject:[item identifier] withKeyPath:@"objectCount" options:nil];
+ [[item identifier] addObserver:cell forKeyPath:@"objectCount" options:0 context:nil];
+ }
+ }
+
+ // bind for the color of a counter
+ [cell setCountColor:nil];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(countColor)]) {
+ [cell bind:@"countColor" toObject:[item identifier] withKeyPath:@"countColor" options:nil];
+ [[item identifier] addObserver:cell forKeyPath:@"countColor" options:0 context:nil];
+ }
+ }
+
+ // bind for a large image
+ [cell setHasLargeImage:NO];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(largeImage)]) {
+ NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
+ [bindingOptions setObject:NSIsNotNilTransformerName forKey:@"NSValueTransformerName"];
+ [cell bind:@"hasLargeImage" toObject:[item identifier] withKeyPath:@"largeImage" options:bindingOptions];
+ [[item identifier] addObserver:cell forKeyPath:@"largeImage" options:0 context:nil];
+ }
+ }
+
+ [cell setIsEdited:NO];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(isEdited)]) {
+ [cell bind:@"isEdited" toObject:[item identifier] withKeyPath:@"isEdited" options:nil];
+ [[item identifier] addObserver:cell forKeyPath:@"isEdited" options:0 context:nil];
+ }
+ }
+
+ // bind my string value to the label on the represented tab
+ [cell bind:@"title" toObject:item withKeyPath:@"label" options:nil];
+}
+
+- (NSMutableArray *)representedTabViewItems
+{
+ NSMutableArray *temp = [NSMutableArray arrayWithCapacity:[_cells count]];
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject])) {
+ if ([cell representedObject]) {
+ [temp addObject:[cell representedObject]];
+ }
+ }
+ return temp;
+}
+
+- (id)cellForPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame
+{
+ if ([self orientation] == PSMTabBarHorizontalOrientation && !NSPointInRect(point, [self genericCellRect])) {
+ return nil;
+ }
+
+ NSInteger i, cnt = [_cells count];
+ for (i = 0; i < cnt; i++) {
+ PSMTabBarCell *cell = [_cells objectAtIndex:i];
+
+ if (NSPointInRect(point, [cell frame])) {
+ if (outFrame) {
+ *outFrame = [cell frame];
+ }
+ return cell;
+ }
+ }
+ return nil;
+}
+
+- (PSMTabBarCell *)lastVisibleTab
+{
+ NSInteger i, cellCount = [_cells count];
+ for (i = 0; i < cellCount; i++) {
+ if ([[_cells objectAtIndex:i] isInOverflowMenu]) {
+ return [_cells objectAtIndex:(i - 1)];
+ }
+ }
+ return [_cells objectAtIndex:(cellCount - 1)];
+}
+
+- (NSInteger)numberOfVisibleTabs
+{
+ NSInteger i, cellCount = 0;
+ PSMTabBarCell *nextCell;
+
+ for (i = 0; i < [_cells count]; i++) {
+ nextCell = [_cells objectAtIndex:i];
+
+ if ([nextCell isInOverflowMenu]) {
+ break;
+ }
+
+ if (![nextCell isPlaceholder]) {
+ cellCount++;
+ }
+ }
+
+ return cellCount;
+}
+
+#pragma mark -
+#pragma mark Accessibility
+
+-(BOOL)accessibilityIsIgnored {
+ return NO;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute {
+ id attributeValue = nil;
+ if ([attribute isEqualToString: NSAccessibilityRoleAttribute]) {
+ attributeValue = NSAccessibilityGroupRole;
+ } else if ([attribute isEqualToString: NSAccessibilityChildrenAttribute]) {
+ attributeValue = NSAccessibilityUnignoredChildren(_cells);
+ } else {
+ attributeValue = [super accessibilityAttributeValue:attribute];
+ }
+ return attributeValue;
+}
+
+- (id)accessibilityHitTest:(NSPoint)point {
+ id hitTestResult = self;
+
+ NSEnumerator *enumerator = [_cells objectEnumerator];
+ PSMTabBarCell *cell = nil;
+ PSMTabBarCell *highlightedCell = nil;
+
+ while (!highlightedCell && (cell = [enumerator nextObject])) {
+ if ([cell isHighlighted]) {
+ highlightedCell = cell;
+ }
+ }
+
+ if (highlightedCell) {
+ hitTestResult = [highlightedCell accessibilityHitTest:point];
+ }
+
+ return hitTestResult;
+}
+
+@end